Add RelativeEntityTestCaseImpl
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/07942b12 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/07942b12 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/07942b12 Branch: refs/heads/master Commit: 07942b12168c591fe2a0b1cd1c791c36e5f1afc5 Parents: 0c0ec60 Author: Sam Corbett <sam.corb...@cloudsoftcorp.com> Authored: Mon Jun 20 18:45:50 2016 +0100 Committer: Sam Corbett <sam.corb...@cloudsoftcorp.com> Committed: Wed Jul 6 12:02:07 2016 +0100 ---------------------------------------------------------------------- .../brooklyn/spi/dsl/methods/DslComponent.java | 14 +- .../test/framework/RelativeEntityTestCase.java | 65 +++++++++ .../framework/RelativeEntityTestCaseImpl.java | 144 +++++++++++++++++++ .../framework/RelativeEntityTestCaseTest.java | 135 +++++++++++++++++ 4 files changed, 356 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/07942b12/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java ---------------------------------------------------------------------- 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 cfb343f..b02f736 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 @@ -61,14 +61,24 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { private final DslComponent scopeComponent; private final Scope scope; + /** + * Resolve componentId in the {@link Scope#GLOBAL} scope. + */ public DslComponent(String componentId) { this(Scope.GLOBAL, componentId); } - + + /** + * Resolve componentId in scope relative to the current + * {@link BrooklynTaskTags#getTargetOrContextEntity) target or context} entity. + */ public DslComponent(Scope scope, String componentId) { this(null, scope, componentId); } - + + /** + * Resolve componentId in scope relative to scopeComponent. + */ public DslComponent(DslComponent scopeComponent, Scope scope, String componentId) { Preconditions.checkNotNull(scope, "scope"); this.scopeComponent = scopeComponent; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/07942b12/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCase.java ---------------------------------------------------------------------- diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCase.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCase.java new file mode 100644 index 0000000..3796bf4 --- /dev/null +++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCase.java @@ -0,0 +1,65 @@ +/* + * 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.test.framework; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey; +import org.apache.brooklyn.core.sensor.Sensors; + +import com.google.common.base.Predicates; + +/** + * A test case that resolves target ID relative to an anchor entity. + * <p> + * For example, to run tests against a named child of an entity: + * <pre> + * services + * - id: app + * brooklyn.children: + * id: child-id + * - id: test-case + * brooklyn.config: + * anchor: $brooklyn:component("app").descendant("child-id") + * </pre> + * The anchor entity is resolved from the <code>anchor</code>, <code>target</code> or + * <code>targetId</code>, in order of preference. The latter two are useful when using + * the test with with entities like {@link LoopOverGroupMembersTestCase}. Values for + * <code>target</code> will be overwritten with the resolved entity so child test + * cases work as expected. + */ +@ImplementedBy(RelativeEntityTestCaseImpl.class) +public interface RelativeEntityTestCase extends TargetableTestComponent { + + AttributeSensorAndConfigKey<Entity, Entity> ANCHOR = ConfigKeys.newSensorAndConfigKey(Entity.class, + "anchor", + "Entity from which component should be resolved."); + + ConfigKey<DslComponent> COMPONENT = ConfigKeys.builder(DslComponent.class) + .name("component") + .description("The component to resolve against target") + .constraint(Predicates.<DslComponent>notNull()) + .build(); + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/07942b12/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseImpl.java ---------------------------------------------------------------------- diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseImpl.java new file mode 100644 index 0000000..ea78e7b --- /dev/null +++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseImpl.java @@ -0,0 +1,144 @@ +/* + * 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.test.framework; + +import java.util.Collection; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; + +public class RelativeEntityTestCaseImpl extends TargetableTestComponentImpl implements RelativeEntityTestCase { + + private static final Logger LOG = LoggerFactory.getLogger(RelativeEntityTestCaseImpl.class); + + @Override + public Entity resolveTarget() { + Entity anchor = config().get(ANCHOR); + if (anchor == null) { + anchor = super.resolveTarget(); + } + if (anchor == null) { + throw new IllegalArgumentException("No anchor entity found for " + this); + } + sensors().set(ANCHOR, anchor); + Maybe<Object> component = config().getRaw(COMPONENT); + if (component.isAbsentOrNull()) { + throw new IllegalArgumentException("No component found for " + this); + } else if (!(component.get() instanceof DslComponent)) { + throw new IllegalArgumentException("Expected DslComponent value for component, found " + component.get()); + } + DslComponent finder = DslComponent.class.cast(component.get()); + Task<Entity> task = Entities.submit(anchor, finder); + return task.getUnchecked(); + } + + @Override + public void start(Collection<? extends Location> locations) { + sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STARTING); + + Entity target = resolveTarget(); + if (target == null) { + LOG.debug("Tasks NOT successfully run. RelativeEntityTestCaseImpl target unset"); + setServiceState(false, Lifecycle.ON_FIRE); + return; + } + config().set(BaseTest.TARGET_ENTITY, target); + + boolean success = true; + try { + for (Entity child : getChildren()) { + if (child instanceof Startable) { + Startable test = Startable.class.cast(child); + test.start(locations); + if (Lifecycle.RUNNING.equals(child.sensors().get(Attributes.SERVICE_STATE_ACTUAL))) { + LOG.debug("Task of {} successfully run, targeting {}", this, target); + } else { + LOG.warn("Problem in child test-case of {}, targeting {}", this, target); + success = false; + } + } else { + LOG.info("Ignored child of {} that is not Startable: {}", this, child); + } + if (!success) { + break; + } + } + } catch (Throwable t) { + Exceptions.propagateIfFatal(t); + LOG.warn("Problem in child test-case of " + this + ", targeting " + target, t); + success = false; + } + + if (success) { + LOG.debug("Tasks successfully run. Update state of {} to RUNNING.", this); + setServiceState(true, Lifecycle.RUNNING); + } else { + LOG.debug("Tasks NOT successfully run. Update state of {} to ON_FIRE.", this); + setServiceState(false, Lifecycle.ON_FIRE); + } + } + + @Override + public void stop() { + sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPING); + try { + for (Entity child : this.getChildren()) { + if (child instanceof Startable) ((Startable) child).stop(); + } + LOG.debug("Tasks successfully run. Update state of {} to STOPPED.", this); + setServiceState(false, Lifecycle.STOPPED); + } catch (Throwable t) { + LOG.debug("Tasks NOT successfully run. Update state of {} to ON_FIRE.", this); + setServiceState(false, Lifecycle.ON_FIRE); + throw Exceptions.propagate(t); + } + } + + @Override + public void restart() { + final Collection<Location> locations = Lists.newArrayList(getLocations()); + stop(); + start(locations); + } + + /** + * Sets the state of the Entity. Useful so that the GUI shows the correct icon. + * + * @param serviceUpState Whether or not the entity is up. + * @param serviceStateActual The actual state of the entity. + */ + private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) { + sensors().set(SERVICE_UP, serviceUpState); + sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/07942b12/test-framework/src/test/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseTest.java ---------------------------------------------------------------------- diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseTest.java new file mode 100644 index 0000000..c35b265 --- /dev/null +++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/RelativeEntityTestCaseTest.java @@ -0,0 +1,135 @@ +/* + * 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.test.framework; + +import static org.apache.brooklyn.test.Asserts.assertTrue; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityInitializer; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.sensor.StaticSensor; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.entity.group.Cluster; +import org.apache.brooklyn.entity.group.DynamicCluster; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class RelativeEntityTestCaseTest extends BrooklynAppUnitTestSupport { + + @Test + public void testParentAndChildScope() { + TestEntity parent = app.createAndManageChild(EntitySpec.create(TestEntity.class) + .configure(BrooklynCampConstants.PLAN_ID, "parent-plan")); + TestEntity child = parent.addChild(EntitySpec.create(TestEntity.class) + .configure(BrooklynCampConstants.PLAN_ID, "child-plan")); + + parent.sensors().set(TestEntity.NAME, "parent"); + child.sensors().set(TestEntity.NAME, "child"); + + app.start(ImmutableList.of(app.newSimulatedLocation())); + + TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class) + .child(relativeEntityTestCaseEntitySpec(parent, "child-plan", DslComponent.Scope.CHILD, "child")) + .child(relativeEntityTestCaseEntitySpec(child, "parent-plan", DslComponent.Scope.PARENT, "parent"))); + + testCase.start(app.getLocations()); + assertTrue(testCase.sensors().get(Attributes.SERVICE_UP), "Test case did not pass: " + testCase); + } + + @Test + public void testSiblingScope() { + TestEntity brother = app.createAndManageChild(EntitySpec.create(TestEntity.class) + .configure(BrooklynCampConstants.PLAN_ID, "brother-plan")); + TestEntity sister = app.createAndManageChild(EntitySpec.create(TestEntity.class) + .configure(BrooklynCampConstants.PLAN_ID, "sister-plan")); + + brother.sensors().set(TestEntity.NAME, "brother"); + sister.sensors().set(TestEntity.NAME, "sister"); + + app.start(ImmutableList.of(app.newSimulatedLocation())); + + TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class) + .child(relativeEntityTestCaseEntitySpec(brother, "sister-plan", DslComponent.Scope.SIBLING, "sister")) + .child(relativeEntityTestCaseEntitySpec(sister, "brother-plan", DslComponent.Scope.SIBLING, "brother"))); + + testCase.start(app.getLocations()); + assertTrue(testCase.sensors().get(Attributes.SERVICE_UP), "Test case did not pass: " + testCase); + } + + @Test + public void testCombinationWithLoopOverGroupMembersTest() { + final String sensorName = TestEntity.NAME.getName(); + final String sensorValue = "test-sensor-value"; + EntityInitializer staticSensor = new StaticSensor<>(ConfigBag.newInstance(ImmutableMap.of( + StaticSensor.SENSOR_NAME, sensorName, + StaticSensor.STATIC_VALUE, sensorValue))); + + // Application entities + + EntitySpec<TestEntity> childSpec = EntitySpec.create(TestEntity.class) + .configure(BrooklynCampConstants.PLAN_ID, "child-plan") + .addInitializer(staticSensor); + EntitySpec<TestEntity> groupMemberSpec = EntitySpec.create(TestEntity.class) + .configure(BrooklynCampConstants.PLAN_ID, "group-member-plan") + .child(childSpec); + Entity cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, groupMemberSpec) + .configure(Cluster.INITIAL_SIZE, 3)); + + // Start the cluster. + app.start(ImmutableList.of(app.newSimulatedLocation())); + + LoopOverGroupMembersTestCase groupTest = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class) + .configure(LoopOverGroupMembersTestCase.TARGET_ENTITY, cluster) + .configure(LoopOverGroupMembersTestCase.TEST_SPEC, relativeEntityTestCaseEntitySpec( + /* set by group-loop */ null, "child-plan", DslComponent.Scope.CHILD, sensorValue))); + + groupTest.start(app.getLocations()); + + // Specifically check the result of the loop test. + assertTrue(groupTest.sensors().get(Attributes.SERVICE_UP), "Test case did not pass: " + groupTest); + } + + private EntitySpec<RelativeEntityTestCase> relativeEntityTestCaseEntitySpec( + Entity testRoot, String targetEntityPlanId, DslComponent.Scope scope, String expectedSensorValue) { + EntitySpec<TestSensor> sensorTest = sensorHasValueTest(TestEntity.NAME, expectedSensorValue); + + return EntitySpec.create(RelativeEntityTestCase.class) + .configure(RelativeEntityTestCase.TARGET_ENTITY, testRoot) + .configure(RelativeEntityTestCase.COMPONENT, new DslComponent(scope, targetEntityPlanId)) + .child(sensorTest); + } + + private EntitySpec<TestSensor> sensorHasValueTest(Sensor<?> sensorName, Object expectedValue) { + return EntitySpec.create(TestSensor.class) + .configure(TestSensor.SENSOR_NAME, sensorName.getName()) + .configure(TestSensor.ASSERTIONS, ImmutableMap.of( + TestFrameworkAssertions.EQUAL_TO, expectedValue)); + } + +}