Skip to content

Commit bb9d1f8

Browse files
committed
record a checksum so that on plan rewrite we don't block reinstallation
with test case that fails without the equivalent plan checks, but works with them
1 parent c694439 commit bb9d1f8

6 files changed

Lines changed: 101 additions & 18 deletions

File tree

camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.apache.brooklyn.api.typereg.RegisteredType;
2222
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
23+
import org.apache.brooklyn.core.test.entity.TestEntity;
2324
import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
2425
import org.apache.brooklyn.entity.stock.BasicEntity;
2526
import org.apache.brooklyn.test.Asserts;
@@ -55,12 +56,44 @@ public void testAddTypes() throws Exception {
5556
Asserts.assertEquals(item, Iterables.getOnlyElement(itemsInstalled), "Wrong item; installed: "+itemsInstalled);
5657
}
5758

58-
@Test // test broken in super works here
59-
// TODO" comment at https://issues.apache.org/jira/browse/BROOKLYN-343
59+
@Test // test disabled as "broken" in super but works here
6060
public void testSameCatalogReferences() {
6161
super.testSameCatalogReferences();
6262
}
63+
64+
@Test
65+
public void testUpdatingItemAllowedIfEquivalentUnderRewrite() {
66+
String symbolicName = "my.catalog.app.id.duplicate";
67+
// forward reference supported here (but not in super)
68+
// however the plan is rewritten meaning a second install requires special comparison
69+
// (RegisteredTypes "equivalent plan" methods)
70+
addForwardReferencePlan(symbolicName);
71+
72+
// delete one but not the other to prevent resolution and thus rewrite until later validation phase,
73+
// thus initial addition will compare unmodified plan from here against modified plan added above;
74+
// replacement will then succeed only if we've correctly recorded equivalence tags
75+
deleteCatalogEntity("forward-referenced-entity");
6376

77+
addForwardReferencePlan(symbolicName);
78+
}
79+
80+
protected void addForwardReferencePlan(String symbolicName) {
81+
addCatalogItems(
82+
"brooklyn.catalog:",
83+
" id: " + symbolicName,
84+
" version: " + TEST_VERSION,
85+
" itemType: entity",
86+
" items:",
87+
" - id: " + symbolicName,
88+
" itemType: entity",
89+
" item:",
90+
" type: forward-referenced-entity",
91+
" - id: " + "forward-referenced-entity",
92+
" itemType: entity",
93+
" item:",
94+
" type: " + TestEntity.class.getName());
95+
}
96+
6497
// also runs many other tests from super, here using the osgi/type-registry appraoch
6598

6699
}

camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,6 @@ public void testSameCatalogReferences() {
571571
" spec: ",
572572
" $brooklyn:entitySpec:",
573573
" type: referenced-entity");
574-
575574
}
576575

577576
@Test

core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,12 +907,16 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed
907907
sourcePlanYaml = planInterpreter.itemYaml;
908908
}
909909

910+
BasicTypeImplementationPlan plan = new BasicTypeImplementationPlan(format, sourcePlanYaml);
910911
BasicRegisteredType type = (BasicRegisteredType) RegisteredTypes.newInstance(
911912
RegisteredTypeKind.UNRESOLVED,
912-
symbolicName, version, new BasicTypeImplementationPlan(format, sourcePlanYaml),
913+
symbolicName, version, plan,
913914
superTypes, aliases, tags, containingBundle==null ? null : containingBundle.getVersionedName().toString(),
914915
MutableList.<OsgiBundleWithUrl>copyOf(libraryBundles),
915916
displayName, description, catalogIconUrl, catalogDeprecated, catalogDisabled);
917+
RegisteredTypes.notePlanEquivalentToThis(type, plan);
918+
// record original source in case it was changed
919+
RegisteredTypes.notePlanEquivalentToThis(type, new BasicTypeImplementationPlan(format, sourceYaml));
916920

917921
((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(type, force);
918922

@@ -1581,7 +1585,7 @@ public ReferenceWithError<RegisteredType> resolve(RegisteredType typeToValidate)
15811585
}
15821586

15831587
if (!Objects.equal(guesser.getPlanYaml(), yaml)) {
1584-
RegisteredTypes.changePlan(resultT,
1588+
RegisteredTypes.changePlanNotingEquivalent(resultT,
15851589
new BasicTypeImplementationPlan(null /* CampTypePlanTransformer.FORMAT */, guesser.getPlanYaml()));
15861590
changedSomething = true;
15871591
}

core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ private boolean assertSameEnoughToAllowReplacing(RegisteredType oldType, Registe
367367
}
368368
if (Objects.equals(oldType.getContainingBundle(), type.getContainingBundle())) {
369369
// if named bundles equal then contents must be the same (due to bundle checksum); bail out early
370-
if (!oldType.getPlan().equals(type.getPlan())) {
370+
if (!samePlan(oldType, type)) {
371371
String msg = "Cannot add "+type+" to catalog; different plan in "+oldType+" from same bundle "+
372372
type.getContainingBundle()+" is already present";
373373
log.debug(msg+"\n"+
@@ -383,7 +383,7 @@ private boolean assertSameEnoughToAllowReplacing(RegisteredType oldType, Registe
383383
}
384384

385385
// different bundles, either anonymous or same item in two named bundles
386-
if (!oldType.getPlan().equals(type.getPlan())) {
386+
if (!samePlan(oldType, type)) {
387387
// if plan is different, fail
388388
String msg = "Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; different plan in "+oldType+" from bundle "+
389389
oldType.getContainingBundle()+" is already present (throwing)";
@@ -425,6 +425,10 @@ private boolean assertSameEnoughToAllowReplacing(RegisteredType oldType, Registe
425425
+ "item is already present in different bundle "+oldType.getContainingBundle());
426426
}
427427

428+
private boolean samePlan(RegisteredType oldType, RegisteredType type) {
429+
return RegisteredTypes.arePlansEquivalent(oldType, type);
430+
}
431+
428432
@Beta // API stabilising
429433
public void delete(VersionedName type) {
430434
RegisteredType registeredTypeRemoved = localRegisteredTypes.remove(type.toString());

core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.apache.brooklyn.util.exceptions.Exceptions;
5454
import org.apache.brooklyn.util.guava.Maybe;
5555
import org.apache.brooklyn.util.guava.Maybe.Absent;
56+
import org.apache.brooklyn.util.stream.Streams;
5657
import org.apache.brooklyn.util.text.NaturalOrderComparator;
5758
import org.apache.brooklyn.util.text.Strings;
5859
import org.apache.brooklyn.util.text.VersionComparator;
@@ -62,6 +63,7 @@
6263

6364
import com.google.common.annotations.Beta;
6465
import com.google.common.base.Function;
66+
import com.google.common.base.Objects;
6567
import com.google.common.base.Predicate;
6668
import com.google.common.base.Predicates;
6769
import com.google.common.collect.ComparisonChain;
@@ -599,11 +601,57 @@ public static String getIconUrl(BrooklynObject object) {
599601
return item.getIconUrl();
600602
}
601603

604+
/** @deprecated since 0.12.0 see {@link #changePlanNotingEquivalent(RegisteredType, TypeImplementationPlan)} */
605+
@Deprecated
602606
public static RegisteredType changePlan(RegisteredType type, TypeImplementationPlan plan) {
603607
((BasicRegisteredType)type).implementationPlan = plan;
604608
return type;
605609
}
610+
611+
/** Changes the plan set on the given type, returning it,
612+
* and also recording that comparisons checking {@link #arePlansEquivalent(RegisteredType, RegisteredType)}
613+
* should consider the two plans equivalent.
614+
*/
615+
@Beta // would prefer not to need this, but currently it is needed due to how resolver rewrites plan
616+
// and we then check plan equality when considering a re-install, else we spuriously fail on
617+
// identical re-installs (eg with dns-etc-hosts-generator)
618+
public static RegisteredType changePlanNotingEquivalent(RegisteredType type, TypeImplementationPlan plan) {
619+
RegisteredTypes.notePlanEquivalentToThis(type, type.getPlan());
620+
RegisteredTypes.notePlanEquivalentToThis(type, plan);
621+
((BasicRegisteredType)type).implementationPlan = plan;
622+
return type;
623+
}
624+
625+
private static String tagForEquivalentPlan(String input) {
626+
// plans may be trimmed by yaml parser so do that before checking equivalence
627+
// it does mean a format change will be ignored
628+
return "equivalent-plan("+Streams.getMd5Checksum(Streams.newInputStreamWithContents(input.trim()))+")";
629+
}
630+
631+
@Beta
632+
public static void notePlanEquivalentToThis(RegisteredType type, TypeImplementationPlan plan) {
633+
Object data = plan.getPlanData();
634+
if (data==null) throw new IllegalStateException("No plan data for "+plan+" noted equivalent to "+type);
635+
if (!(data instanceof String)) throw new IllegalStateException("Expected plan for equivalent to "+type+" to be a string; was "+data);
636+
((BasicRegisteredType)type).tags.add(tagForEquivalentPlan((String)data));
637+
}
606638

639+
@Beta
640+
public static boolean arePlansEquivalent(RegisteredType type1, RegisteredType type2) {
641+
String plan1 = getImplementationDataStringForSpec(type1);
642+
String plan2 = getImplementationDataStringForSpec(type2);
643+
if (Strings.isNonBlank(plan1) && Strings.isNonBlank(plan2)) {
644+
String p2tag = tagForEquivalentPlan(plan2);
645+
String p1tag = tagForEquivalentPlan(plan1);
646+
// allow same plan under trimming,
647+
// or any recorded tag in either direction
648+
if (Objects.equal(p1tag, p2tag)) return true;
649+
if (type1.getTags().contains(p2tag)) return true;
650+
if (type2.getTags().contains(p1tag)) return true;
651+
}
652+
return Objects.equal(type1.getPlan(), type2.getPlan());
653+
}
654+
607655
public static boolean isTemplate(RegisteredType type) {
608656
if (type==null) return false;
609657
return type.getTags().contains(BrooklynTags.CATALOG_TEMPLATE);

rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,8 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor
175175
assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
176176

177177
// an InterfacesTag should be created for every catalog item
178-
assertEquals(entityItem.getTags().size(), 1);
179-
Object tag = entityItem.getTags().iterator().next();
180-
@SuppressWarnings("unchecked")
181-
List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
178+
Map<String, List<String>> traitsMapTag = Iterables.getOnlyElement(Iterables.filter(entityItem.getTags(), Map.class));
179+
List<String> actualInterfaces = traitsMapTag.get("traits");
182180
List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
183181
assertEquals(actualInterfaces.size(), expectedInterfaces.size());
184182
for (Class<?> expectedInterface : expectedInterfaces) {
@@ -813,10 +811,9 @@ public void testOsgiBundleWithBom() throws Exception {
813811
assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
814812

815813
// an InterfacesTag should be created for every catalog item
816-
assertEquals(entityItem.getTags().size(), 1);
817-
Object tag = entityItem.getTags().iterator().next();
818814
@SuppressWarnings("unchecked")
819-
List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
815+
Map<String, List<String>> traitsMapTag = Iterables.getOnlyElement(Iterables.filter(entityItem.getTags(), Map.class));
816+
List<String> actualInterfaces = traitsMapTag.get("traits");
820817
List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
821818
assertEquals(actualInterfaces.size(), expectedInterfaces.size());
822819
for (Class<?> expectedInterface : expectedInterfaces) {
@@ -889,10 +886,8 @@ public void testOsgiBundleWithBomNotInBrooklynNamespace() throws Exception {
889886
assertEquals(item.getIconUrl(), "classpath:" + iconPath);
890887

891888
// an InterfacesTag should be created for every catalog item
892-
assertEquals(entityItem.getTags().size(), 1);
893-
Object tag = entityItem.getTags().iterator().next();
894-
@SuppressWarnings("unchecked")
895-
List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
889+
Map<String, List<String>> traitsMapTag = Iterables.getOnlyElement(Iterables.filter(entityItem.getTags(), Map.class));
890+
List<String> actualInterfaces = traitsMapTag.get("traits");
896891
List<String> expectedInterfaces = ImmutableList.of(Entity.class.getName(), BrooklynObject.class.getName(), Identifiable.class.getName(), Configurable.class.getName());
897892
assertTrue(actualInterfaces.containsAll(expectedInterfaces), "actual="+actualInterfaces);
898893

0 commit comments

Comments
 (0)