This is an automated email from the ASF dual-hosted git repository.
iuliana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
The following commit(s) were added to refs/heads/master by this push:
new 4605c16 be smarter about picking which component is referenced by DSL
new 8e5b5bf Merge pull request #1253 from ahgittin/dsl-id-search-smarter
4605c16 is described below
commit 4605c160292bc7ca0c6cd5f5e36c8e015903c663
Author: Alex Heneveld <[email protected]>
AuthorDate: Wed Sep 8 16:35:09 2021 +0100
be smarter about picking which component is referenced by DSL
prefer those:
- crossing fewer application boundaries
- closer
---
.../camp/brooklyn/spi/dsl/AppGroupTraverser.java | 139 +++++++++++++++++++
.../spi/dsl/methods/BrooklynDslCommon.java | 5 +-
.../brooklyn/spi/dsl/methods/DslComponent.java | 81 ++++++-----
.../brooklyn/spi/dsl/AppGroupTraverserTest.java | 154 +++++++++++++++++++++
.../camp/brooklyn/spi/dsl/DslYamlTest.java | 2 +-
5 files changed, 337 insertions(+), 44 deletions(-)
diff --git
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/AppGroupTraverser.java
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/AppGroupTraverser.java
new file mode 100644
index 0000000..6f1bdeb
--- /dev/null
+++
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/AppGroupTraverser.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.camp.brooklyn.spi.dsl;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.util.collections.MutableSet;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+// progressively look up and down to application (topology) boundaries
+
+// ie first look subject to application boundaries, ie up to ancestor which is
an application, and through descendants which are applications
+// then look past all of those to next app boundary
+
+// TODO would be nice to have tests which look at parent, children,
grandchildren, older and younger nephews
+
+// TODO ideally would be enhanced to look at the "depth in ancestor" tag, to
work based on entity definition boundaries rather than app boundaries
+
+/**
+ *
+ * Progressively expands groups based on "application" boundaries, returning
each distal group.
+ * Useful e.g. to find nodes matching an ID included in a particular
application definition and not children.
+ <code>
+ - APP1
+ - ENT1
+ - APP2
+ - ENT2
+ - ENT3
+ - APP3
+ - ENT4
+ - ENT5
+ - APP4
+ - ENT6
+ - ENT7
+ - APP5
+ - ENT8
+ </code>
+
+ if this is initialized with ENT4, it will start with that in {@link
#visitedThisTime} and {@link #visited}.
+ one invocation of {@link #next()} will return an instance where {@link
#visitedThisTime} is {APP3,ENT5,APP4,ENT7,APP5}, and {@link #visited} contains
that and ENT4;
+ it will not go below APP4 or APP5 because those are {@link Application}
boundaries, nor will it go above APP3, on that pass.
+ an invocation of {@link #next()} on that instance will then return {@link
#visitedThisTime} containing {APP1,ENT1,APP2,ENT6,ENT8},
+ i.e. the items above APP3 (but not above the next highest ancestor
implementing {@link Application},
+ and its {@link #visited} will (as it always does) contain those items plus
the items previously visited.
+ an invocation of {@link #next()} on that instance will then return {@link
#visitedThisTime} empty.
+ <p>
+ see {@link #findFirstGroupOfMatches(Entity, Predicate)}.
+*/
+public class AppGroupTraverser {
+
+ int depth = -1;
+ Set<Entity> visited = MutableSet.of();
+ Set<Entity> visitedThisTime = MutableSet.of();
+ // _parent_ of the last node we have visited;
+ // after first iteration this should have at least (exactly?) one visited
child which is a topology template (not a node)
+ Entity ancestorBound = null;
+ // children whom we have not yet visited, because they are part of a new
topology template
+ Set<Entity> descendantBounds = MutableSet.of();
+
+ protected AppGroupTraverser() {
+ }
+
+ AppGroupTraverser(Entity source) {
+ this.visitedThisTime.add(source);
+ this.visited.add(source);
+ this.ancestorBound = source.getParent();
+ this.descendantBounds.addAll(source.getChildren());
+ }
+
+ AppGroupTraverser next() {
+ AppGroupTraverser result = new AppGroupTraverser();
+ result.visited.addAll(visited);
+ result.depth = depth + 1;
+ descendantBounds.forEach(c -> result.visitDescendants(c, true));
+ if (ancestorBound != null)
result.visitAncestorsAndTheirDescendants(ancestorBound);
+ return result;
+ }
+
+ protected void visitAncestorsAndTheirDescendants(Entity ancestor) {
+ // go to the top of the containing topology template / application
boundary
+ Entity appAncestor = ancestor;
+ while (!(appAncestor instanceof Application) &&
appAncestor.getParent() != null)
+ appAncestor = appAncestor.getParent();
+ visitDescendants(appAncestor, true);
+ ancestorBound = appAncestor.getParent() != null ?
appAncestor.getParent() : null;
+ }
+
+ protected void visitDescendants(Entity node, boolean isFirst) {
+ if (!isFirst && !visited.add(node)) {
+ // already visited
+ return;
+ }
+ // normal entity
+ visitedThisTime.add(node);
+
+ if (!isFirst && node instanceof Application) {
+ descendantBounds.add(node);
+ } else {
+ node.getChildren().forEach(c -> this.visitDescendants(c, false));
+ }
+ }
+
+ boolean hasNext() {
+ return ancestorBound != null || !descendantBounds.isEmpty();
+ }
+
+ AppGroupTraverser expandUntilMatchesFound(Predicate<Entity> test) {
+ if (visitedThisTime.stream().anyMatch(test) ||
visitedThisTime.isEmpty()) return this;
+ return next().expandUntilMatchesFound(test);
+ }
+
+ /** Progressively expands across {@link
org.apache.brooklyn.api.entity.Application} boundaries until one or more
matching entities are found. */
+ public static List<Entity> findFirstGroupOfMatches(Entity source,
Predicate<Entity> test) {
+ AppGroupTraverser traversed = new
AppGroupTraverser(source).expandUntilMatchesFound(test);
+ return
traversed.visitedThisTime.stream().filter(test).collect(Collectors.toList());
+ }
+
+}
diff --git
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
index 91b85a1..7e22521 100644
---
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
+++
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
@@ -138,11 +138,14 @@ public class BrooklynDslCommon {
public static DslComponent root() {
return new DslComponent(Scope.ROOT);
}
+
@DslAccessible
public static DslComponent scopeRoot() {
return new DslComponent(Scope.SCOPE_ROOT);
}
- // prefer the syntax above to the below now, but not deprecating the below
+ // above is within the current definition
+ // whereas the below _prefers_ in the current definition
+
@DslAccessible
public static DslComponent component(Object id) {
return component("global", id);
diff --git
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
index 01c68e1..f9e3b5b 100644
---
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
+++
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
@@ -18,18 +18,26 @@
*/
package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods;
-import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
-import java.util.Set;
-import java.util.function.Function;
-import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved;
-
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Converter;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Callables;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-
+import java.util.function.Function;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
@@ -38,9 +46,11 @@ import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.AppGroupTraverser;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslAccessible;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslFunctionSource;
+import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.Entities;
@@ -48,7 +58,6 @@ import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.EntityPredicates;
import org.apache.brooklyn.core.location.Locations;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
-import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.util.JavaGroovyEquivalents;
@@ -64,19 +73,6 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.text.Strings;
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Converter;
-import com.google.common.base.Objects;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.util.concurrent.Callables;
-import com.thoughtworks.xstream.annotations.XStreamConverter;
-
public class DslComponent extends BrooklynDslDeferredSupplier<Entity>
implements DslFunctionSource {
private static final long serialVersionUID = -7715984495268724954L;
@@ -278,15 +274,15 @@ public class DslComponent extends
BrooklynDslDeferredSupplier<Entity> implements
return
Maybe.<Entity>ofDisallowingNull(entity()).or(Maybe.<Entity>absent("Context
entity not available when trying to evaluate Brooklyn DSL"));
}
}
-
+
protected Maybe<Entity> callImpl(boolean immediate) throws Exception {
Maybe<Entity> entityMaybe = getEntity(immediate);
if (immediate && entityMaybe.isAbsent()) {
return entityMaybe;
}
EntityInternal entity = (EntityInternal) entityMaybe.get();
-
- Iterable<Entity> entitiesToSearch = null;
+
+ java.util.function.Predicate<Entity> acceptableEntity = x -> true;
Predicate<Entity> notSelfPredicate =
Predicates.not(Predicates.<Entity>equalTo(entity));
switch (scope) {
@@ -297,8 +293,9 @@ public class DslComponent extends
BrooklynDslDeferredSupplier<Entity> implements
case GLOBAL:
if (Entities.isManaged(entity())) {
// use management context if entity is managed (usual
case, more efficient)
- entitiesToSearch = ((EntityManagerInternal)
entity.getManagementContext().getEntityManager())
-
.getAllEntitiesInApplication(entity().getApplication());
+ String appId = entity().getApplicationId();
+ acceptableEntity = ee -> appId!=null &&
appId.equals(ee.getApplicationId());
+
} else {
// otherwise traverse the application
if (entity()!=null && entity().getApplication()!=null)
{
@@ -312,7 +309,7 @@ public class DslComponent extends
BrooklynDslDeferredSupplier<Entity> implements
});
});
}
- entitiesToSearch = visited;
+ acceptableEntity = visited::contains;
} else {
// nothing to do
}
@@ -323,17 +320,16 @@ public class DslComponent extends
BrooklynDslDeferredSupplier<Entity> implements
case SCOPE_ROOT:
return
Maybe.<Entity>of(Entities.catalogItemScopeRoot(entity));
case DESCENDANT:
- entitiesToSearch = Entities.descendantsWithoutSelf(entity);
+ acceptableEntity =
MutableSet.copyOf(Entities.descendantsWithoutSelf(entity))::contains;
break;
case ANCESTOR:
- entitiesToSearch = Entities.ancestorsWithoutSelf(entity);
+ acceptableEntity =
MutableSet.copyOf(Entities.ancestorsWithoutSelf(entity))::contains;
break;
case SIBLING:
- entitiesToSearch = entity.getParent().getChildren();
- entitiesToSearch = Iterables.filter(entitiesToSearch,
notSelfPredicate);
+ acceptableEntity =
MutableSet.copyOf(Iterables.filter(entity.getParent().getChildren(),
notSelfPredicate))::contains;
break;
case CHILD:
- entitiesToSearch = entity.getChildren();
+ acceptableEntity =
MutableSet.copyOf(entity.getChildren())::contains;
break;
default:
throw new IllegalStateException("Unexpected scope "+scope);
@@ -359,9 +355,10 @@ public class DslComponent extends
BrooklynDslDeferredSupplier<Entity> implements
}
// Support being passed an explicit entity via the DSL
- if (maybeComponentId.get() instanceof BrooklynObject) {
- if (Iterables.contains(entitiesToSearch,
maybeComponentId.get())) {
- return Maybe.of((Entity)maybeComponentId.get());
+ Object candidate = maybeComponentId.get();
+ if (candidate instanceof BrooklynObject) {
+ if (acceptableEntity.test((Entity)candidate)) {
+ return Maybe.of((Entity)candidate);
} else {
throw new IllegalStateException("Resolved component "
+ maybeComponentId.get() + " is not in scope '" + scope + "' wrt " + entity);
}
@@ -376,15 +373,15 @@ public class DslComponent extends
BrooklynDslDeferredSupplier<Entity> implements
} else {
desiredComponentId = componentId;
}
-
- Optional<Entity> result = Iterables.tryFind(entitiesToSearch,
EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID,
desiredComponentId));
- if (result.isPresent()) {
- return Maybe.of(result.get());
+
+ List<Entity> firstGroupOfMatches =
AppGroupTraverser.findFirstGroupOfMatches(entity,
+
Predicates.and(EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID,
desiredComponentId), acceptableEntity::test)::apply);
+ if (firstGroupOfMatches.isEmpty()) {
+ firstGroupOfMatches =
AppGroupTraverser.findFirstGroupOfMatches(entity,
+
Predicates.and(EntityPredicates.idEqualTo(desiredComponentId),
acceptableEntity::test)::apply);
}
-
- result = Iterables.tryFind(entitiesToSearch,
EntityPredicates.idEqualTo(desiredComponentId));
- if (result.isPresent()) {
- return Maybe.of(result.get());
+ if (!firstGroupOfMatches.isEmpty()) {
+ return Maybe.of(firstGroupOfMatches.get(0));
}
// could be nice if DSL has an extra .block() method to allow it
to wait for a matching entity.
diff --git
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/AppGroupTraverserTest.java
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/AppGroupTraverserTest.java
new file mode 100644
index 0000000..b1d01f4
--- /dev/null
+++
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/AppGroupTraverserTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.camp.brooklyn.spi.dsl;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import org.testng.annotations.Test;
+
+public class AppGroupTraverserTest extends AbstractYamlTest {
+
+ @Test
+ public void testComplexTreeTraversal() throws Exception {
+ Application app = createAndStartApplication("\n" +
+ "#- APP1\n" +
+ "# - ENT1\n" +
+ "# - APP2\n" +
+ "# - ENT2\n" +
+ "# - ENT3\n" +
+ "# - APP3\n" +
+ "# - ENT4\n" +
+ "# - ENT5\n" +
+ "# - APP4\n" +
+ "# - ENT6\n" +
+ "# - ENT7\n" +
+ "# - APP5\n" +
+ "# - ENT8\n" +
+ "\n" +
+ "id: APP1\n" +
+ "services:\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT1\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicApplication\n" +
+ " id: APP2\n" +
+ " brooklyn.children:\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n"
+
+ " id: ENT2\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n"
+
+ " id: ENT3\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicApplication\n" +
+ " id: APP3\n" +
+ " brooklyn.children:\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n"
+
+ " id: ENT4\n" +
+ " brooklyn.children:\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT5\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicApplication\n" +
+ " id: APP4\n" +
+ " brooklyn.children:\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT6\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT7\n" +
+ "\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicApplication\n" +
+ " id: APP5\n" +
+ " brooklyn.children:\n" +
+ " - type:
org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT8\n");
+
+ String[] IDs = {"ENT1", "ENT2", "ENT3", "ENT4", "ENT5", "ENT6",
+ "ENT7", "ENT8", "APP1", "APP2", "APP3", "APP4",
+ "APP5"};
+
+ Stream.of(IDs).forEach(s -> {
+ Entity entity = findEntity(app, s);
+ assertCanTraverseToEachEntityFromHere(entity, IDs);
+ });
+ }
+
+ static final String APP_findIDISameApp = "id: APP1\n" +
+ "services:\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicApplication\n" +
+ " id: APP2\n" +
+ " brooklyn.children:\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT1\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT2\n" +
+ " name: SAME_APP\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicApplication\n" +
+ " id: APP3\n" +
+ " brooklyn.children:\n" +
+ " - type: org.apache.brooklyn.entity.stock.BasicEntity\n" +
+ " id: ENT2\n" +
+ " name: DIFFERENT_APP\n";
+
+ @Test void testFindIDISameApp__SharedParent() throws Exception {
+ Application app = createAndStartApplication(APP_findIDISameApp);
+
+ Entity ent1 = findEntity(app, "ENT1");
+ Entity ent2 = AppGroupTraverser.findFirstGroupOfMatches(ent1,
EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID,
"ENT2")::apply).get(0);
+
+ assertEquals(ent2.getDisplayName(), "SAME_APP");
+
+ }
+
+ @Test void testFindIDISameApp__Child() throws Exception {
+ Application app = createAndStartApplication(APP_findIDISameApp);
+
+ Entity ent1 = findEntity(app, "APP2");
+ Entity ent2 = AppGroupTraverser.findFirstGroupOfMatches(ent1,
EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID,
"ENT2")::apply).get(0);
+
+ assertEquals(ent2.getDisplayName(), "SAME_APP");
+
+ }
+
+ private void assertCanTraverseToEachEntityFromHere(Entity startEntity,
String[] allNodes) {
+ Set<String> visitedNodes = traverseFullTreeRecordingNodes(startEntity);
+
+ assertEquals(visitedNodes.size(), allNodes.length);
+ Stream.of(allNodes).forEach(s -> assertTrue(visitedNodes.contains(s),
s + " was not found in traversal"));
+ }
+
+ private Set<String> traverseFullTreeRecordingNodes(Entity startEntity) {
+ Set<String> myIDs = new HashSet<>();
+ AppGroupTraverser.findFirstGroupOfMatches(startEntity, entity -> {
+ myIDs.add(entity.getConfig(BrooklynCampConstants.PLAN_ID));
+ return false; //Traverse all nodes
+ });
+ return myIDs;
+ }
+
+ private Entity findEntity(Application app, String s) {
+ return AppGroupTraverser.findFirstGroupOfMatches(app, entity -> {
+ String campId = entity.getConfig(BrooklynCampConstants.PLAN_ID);
+ return s.equals(campId);
+ }).get(0);
+ }
+}
\ No newline at end of file
diff --git
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
index d0128f6..f83343c 100644
---
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
+++
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
@@ -689,7 +689,7 @@ public class DslYamlTest extends AbstractYamlTest {
assertEquals(getConfigEventually(app, DEST), Boolean.TRUE);
}
- @Test
+ @Test(groups="Integration") // because takes 3 seconds ... not sure why!?
public void testDeferredDslObjectAsFirstArgument() throws Exception {
final Entity app = createAndStartApplication(
"services:",