From 54d131a3b7f3cd01b7cea5db65f008fd734f5da3 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Mon, 23 May 2016 23:31:13 +0100 Subject: [PATCH 1/6] Add DSL support for calling effectors in YAML --- .../spi/dsl/methods/BrooklynDslCommon.java | 9 +++ .../spi/dsl/methods/DslComponent.java | 53 +++++++++++++++- .../camp/brooklyn/EffectorsYamlTest.java | 63 +++++++++++++++++++ .../resources/test-app-with-effectors.yaml | 34 ++++++++++ 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java create mode 100644 camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml 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 2836895d16..82319d7df3 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 @@ -70,6 +70,7 @@ import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -233,6 +234,14 @@ public static BrooklynDslDeferredSupplier entityId() { return new DslComponent(Scope.THIS, "").entityId(); } + public static BrooklynDslDeferredSupplier effector(String effectorName, Map args) { + return new DslComponent(Scope.THIS, "").effector(effectorName, args); + } + + public static BrooklynDslDeferredSupplier effector(String effectorName) { + return new DslComponent(Scope.THIS, "").effector(effectorName, ImmutableMap.of()); + } + /** Returns a {@link Sensor}, looking up the sensor on the context if available and using that, * or else defining an untyped (Object) sensor */ @DslAccessible 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 9de33405b7..a67bfbae8f 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 @@ -20,10 +20,12 @@ import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; +import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.Task; @@ -61,6 +63,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Callables; @@ -509,6 +512,54 @@ public String toString() { } } + @DslAccessible + public BrooklynDslDeferredSupplier effector(final String effectorName) { + return new ExecuteEffector(this, effectorName, ImmutableMap.of()); + } + @DslAccessible + public BrooklynDslDeferredSupplier effector(final String effectorName, final Map args) { + return new ExecuteEffector(this, effectorName, args); + } + protected static class ExecuteEffector extends BrooklynDslDeferredSupplier { + private static final long serialVersionUID = 1740899524088902383L; + private final DslComponent component; + private final String effectorName; + private final Map args; + public ExecuteEffector(DslComponent component, String effectorName, Map args) { + this.component = Preconditions.checkNotNull(component); + this.effectorName = effectorName; + this.args = args; + } + @SuppressWarnings("unchecked") + @Override + public Task newTask() { + Entity targetEntity = component.get(); + Maybe> targetEffector = targetEntity.getEntityType().getEffectorByName(effectorName); + if (targetEffector.isAbsentOrNull()) { + throw new IllegalArgumentException("Effector " + effectorName + " not found on entity: " + targetEntity); + } + return (Task) Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args); + } + @Override + public int hashCode() { + return Objects.hashCode(component, effectorName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + ExecuteEffector that = ExecuteEffector.class.cast(obj); + return Objects.equal(this.component, that.component) && + Objects.equal(this.effectorName, that.effectorName); + } + @Override + public String toString() { + return (component.scope==Scope.THIS ? "" : component.toString()+".") + + "effector("+JavaStringEscapes.wrapJavaString(effectorName)+")"; + } + } + @DslAccessible public BrooklynDslDeferredSupplier config(final Object keyNameOrSupplier) { return new DslConfigSupplier(this, keyNameOrSupplier); @@ -763,5 +814,5 @@ public String toString() { return DslToStringHelpers.component(scopeComponent, remainder); } - + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java new file mode 100644 index 0000000000..befeff16f4 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java @@ -0,0 +1,63 @@ +/* + * 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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.util.time.Duration; + +@Test +public class EffectorsYamlTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(EffectorsYamlTest.class); + + @Test + public void testWithAppEnricher() throws Exception { + Entity app = createAndStartApplication(loadYaml("test-app-with-effectors.yaml")); + waitForApplicationTasks(app); + Assert.assertEquals(app.getDisplayName(), "test-app-with-effectors"); + + Entities.dumpInfo(app); + Thread.sleep(Duration.THIRTY_SECONDS.toMilliseconds()); + + Entity start1 = null, start2 = null; + Assert.assertEquals(app.getChildren().size(), 2); + for (Entity child : app.getChildren()) { + if (child.getDisplayName().equals("start1")) + start1 = child; + if (child.getDisplayName().equals("start2")) + start2 = child; + } + Assert.assertNotNull(start1); + Assert.assertNotNull(start2); + EntityAsserts.assertAttributeEquals(start1, Startable.SERVICE_UP, false); + EntityAsserts.assertAttributeEquals(start2, Startable.SERVICE_UP, true); + } + + @Override + protected Logger getLogger() { + return log; + } +} diff --git a/camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml b/camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml new file mode 100644 index 0000000000..1db93c1a0e --- /dev/null +++ b/camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml @@ -0,0 +1,34 @@ +# +# 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. +# +name: test-app-with-effectors +description: Application with effector that stops another entity +origin: https://github.com/apache/brooklyn +services: +- type: org.apache.brooklyn.entity.stock.BasicStartable + id: start1 + name: start1 +- type: org.apache.brooklyn.entity.stock.BasicStartable + id: start2 + name: start2 + brooklyn.initializers: + - type: org.apache.brooklyn.core.sensor.StaticSensor + brooklyn.config: + name: stop + targetType: java.lang.Object + static.value: $brooklyn:component("start1").effector("stop") From a6f8c769da52c1a00430563ab7263281fafd1b75 Mon Sep 17 00:00:00 2001 From: Geoff Macartney Date: Thu, 23 Jun 2016 16:50:53 +0100 Subject: [PATCH 2/6] Updates for effector calling. this includes the following changes: An "avoidSideEffects" capability in ValueResolver, to allow callers to specify that any tasks which are marked as having side effects (a new marker Interface) should be ignored. The use case for this is illustrated by its use here in AbstractConfigurationSupportInternal in the getNonBlocking method invoked by ConfigConstraints.validateAll. This is called during validation of the YAML, and evaluates all config key values, which without this change would result in unwanted repeat executions of effectors (and at a point when their target entities have not yet been started). Similarly it is called during calculation of a hash for the extra salt for VanillaSoftwareProcessSshDriver, again a situation where we do not want effectors being called. An additional effector() method on BrooklynDslCommon, to add support for varArgs passing of the effector arguments, and a matching new method on BrooklynDslDeferredSupplier. The task created by ExecuteEffector is cached, in order to avoid unwanted repeated executor invocations, as illustrated in the test testEffectorCalledOncePerConfigKey in EffectorsYamlTest. test-app-with-effectors.yaml is removed as it's easier to see what's going on with the YAML inline within the tests. New tests are added in EffectorsYamlTest. A new 'sequenceEffector' is added, just added directly to TestEntity, in case it happens to be useful for anyone else. --- .../spi/dsl/methods/BrooklynDslCommon.java | 4 + .../spi/dsl/methods/DslComponent.java | 25 ++- .../camp/brooklyn/EffectorsYamlTest.java | 167 +++++++++++++++--- .../resources/test-app-with-effectors.yaml | 34 ---- .../AbstractConfigurationSupportInternal.java | 1 + .../util/core/task/HasSideEffects.java | 25 +++ .../util/core/task/ValueResolver.java | 18 +- .../brooklyn/core/test/entity/TestEntity.java | 3 + .../core/test/entity/TestEntityImpl.java | 9 +- 9 files changed, 222 insertions(+), 64 deletions(-) delete mode 100644 camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml create mode 100644 core/src/main/java/org/apache/brooklyn/util/core/task/HasSideEffects.java 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 82319d7df3..69c65141bd 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 @@ -242,6 +242,10 @@ public static BrooklynDslDeferredSupplier effector(String effectorName) { return new DslComponent(Scope.THIS, "").effector(effectorName, ImmutableMap.of()); } + public static BrooklynDslDeferredSupplier effector(String effectorName, String... args) { + return new DslComponent(Scope.THIS, "").effector(effectorName, args); + } + /** Returns a {@link Sensor}, looking up the sensor on the context if available and using that, * or else defining an untyped (Object) sensor */ @DslAccessible 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 a67bfbae8f..77a82bf565 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 @@ -20,6 +20,7 @@ import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.Callable; @@ -48,6 +49,7 @@ import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.core.task.BasicExecutionContext; import org.apache.brooklyn.util.core.task.DeferredSupplier; +import org.apache.brooklyn.util.core.task.HasSideEffects; import org.apache.brooklyn.util.core.task.ImmediateSupplier; import org.apache.brooklyn.util.core.task.TaskBuilder; import org.apache.brooklyn.util.core.task.Tasks; @@ -63,6 +65,7 @@ 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; @@ -520,15 +523,27 @@ public BrooklynDslDeferredSupplier effector(final String effectorName) { public BrooklynDslDeferredSupplier effector(final String effectorName, final Map args) { return new ExecuteEffector(this, effectorName, args); } - protected static class ExecuteEffector extends BrooklynDslDeferredSupplier { + public BrooklynDslDeferredSupplier effector(final String effectorName, String... args) { + return new ExecuteEffector(this, effectorName, ImmutableList.copyOf(args)); + } + protected static class ExecuteEffector extends BrooklynDslDeferredSupplier implements HasSideEffects { private static final long serialVersionUID = 1740899524088902383L; private final DslComponent component; private final String effectorName; private final Map args; + private final List argList; + private Task cachedTask; public ExecuteEffector(DslComponent component, String effectorName, Map args) { this.component = Preconditions.checkNotNull(component); this.effectorName = effectorName; this.args = args; + this.argList = null; + } + public ExecuteEffector(DslComponent component, String effectorName, List args) { + this.component = Preconditions.checkNotNull(component); + this.effectorName = effectorName; + this.argList = args; + this.args = null; } @SuppressWarnings("unchecked") @Override @@ -538,8 +553,14 @@ public Task newTask() { if (targetEffector.isAbsentOrNull()) { throw new IllegalArgumentException("Effector " + effectorName + " not found on entity: " + targetEntity); } - return (Task) Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args); + if (null == cachedTask) { + cachedTask = null == argList + ? Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args) + : Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector.get(), argList.toArray()); + } + return (Task) cachedTask; } + @Override public int hashCode() { return Objects.hashCode(component, effectorName); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java index befeff16f4..f7c0667779 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java @@ -18,42 +18,161 @@ */ package org.apache.brooklyn.camp.brooklyn; +import com.google.common.collect.Iterables; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.core.entity.Entities; -import org.apache.brooklyn.core.entity.EntityAsserts; -import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.util.time.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.apache.brooklyn.core.entity.EntityPredicates.displayNameEqualTo; +import static org.testng.Assert.assertEquals; @Test public class EffectorsYamlTest extends AbstractYamlTest { private static final Logger log = LoggerFactory.getLogger(EffectorsYamlTest.class); @Test - public void testWithAppEnricher() throws Exception { - Entity app = createAndStartApplication(loadYaml("test-app-with-effectors.yaml")); - waitForApplicationTasks(app); - Assert.assertEquals(app.getDisplayName(), "test-app-with-effectors"); - - Entities.dumpInfo(app); - Thread.sleep(Duration.THIRTY_SECONDS.toMilliseconds()); - - Entity start1 = null, start2 = null; - Assert.assertEquals(app.getChildren().size(), 2); - for (Entity child : app.getChildren()) { - if (child.getDisplayName().equals("start1")) - start1 = child; - if (child.getDisplayName().equals("start2")) - start2 = child; + public void testEffectorWithVoidReturnInvokedByGetConfig() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " id: entity1", + " brooklyn.config:", + " test.confName: $brooklyn:entity(\"entity1\").effector(\"myEffector\")" + ); + TestEntity testEntity = (TestEntity)Iterables.getOnlyElement(app.getChildren()); + + assertCallHistory(testEntity, "start"); + + // invoke the effector + Assert.assertNull(testEntity.getConfig(TestEntity.CONF_NAME)); + + assertCallHistory(testEntity, "start", "myEffector"); + } + + + @Test(enabled = false, description = "currently not possible to say '$brooklyn:entity(\"entity1\").effector:'") + public void testEffectorMultiLine() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " id: entity1", + " brooklyn.config:", + " test.confName: ", + " $brooklyn:entity(\"entity1\").effector:", + " - myEffector" + ); + TestEntity testEntity = (TestEntity)Iterables.getOnlyElement(app.getChildren()); + + assertCallHistory(testEntity, "start"); + + Assert.assertNull(testEntity.getConfig(TestEntity.CONF_NAME)); + + assertCallHistory(testEntity, "start", "myEffector"); + } + + @Test + public void testEffectorWithReturn() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " id: entity1", + " brooklyn.config:", + " test.confName: ", + " $brooklyn:entity(\"entity1\").effector(\"identityEffector\", \"hello\")" + ); + TestEntity testEntity = (TestEntity)Iterables.getOnlyElement(app.getChildren()); + Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_NAME), "hello"); + assertCallHistory(testEntity, "start", "identityEffector"); + } + + @Test + public void testOwnEffectorWithReturn() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " brooklyn.config:", + " test.confName: ", + " $brooklyn:effector(\"identityEffector\", \"my own effector\")" + ); + TestEntity testEntity = (TestEntity)Iterables.getOnlyElement(app.getChildren()); + Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_NAME), "my own effector"); + assertCallHistory(testEntity, "start", "identityEffector"); + } + + @Test + public void testEffectorCalledOncePerConfigKey() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " id: entity1", + " brooklyn.config:", + " test.confName: ", + " $brooklyn:effector(\"sequenceEffector\")", + " test.confObject: ", + " $brooklyn:effector(\"sequenceEffector\")" + ); + TestEntity testEntity = (TestEntity)Iterables.getOnlyElement(app.getChildren()); + + List callHistory = testEntity.getCallHistory(); + Assert.assertFalse(callHistory.contains("myEffector"), "history = " + callHistory); + + final String firstGetConfig = testEntity.getConfig(TestEntity.CONF_NAME); + Assert.assertEquals(firstGetConfig, "1"); + final String secondGetConfig = testEntity.getConfig(TestEntity.CONF_NAME); + Assert.assertEquals(secondGetConfig, "1"); + Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_OBJECT), Integer.valueOf(2)); + assertCallHistory(testEntity, "start", "sequenceEffector", "sequenceEffector"); + } + + @Test(groups = "Integration") + public void testSshCommandSensorWithEffectorInEnv() throws Exception { + final Path tempFile = Files.createTempFile("testSshCommandSensorWithEffectorInEnv", ".txt"); + getLogger().info("Temp file is {}", tempFile.toAbsolutePath()); + + try { + Entity app = createAndStartApplication( + "location: localhost:(name=localhost)", + "services:", + "- type: " + TestEntity.class.getName(), + " id: testEnt1", + " name: testEnt1", + "- type: " + VanillaSoftwareProcess.class.getName(), + " id: vsp", + " brooklyn.config:", + " launch.command: echo ${MY_ENV_VAR} > " + tempFile.toAbsolutePath(), + " checkRunning.command: true", + " shell.env:", + " MY_ENV_VAR:" , + " $brooklyn:entity(\"testEnt1\").effector(\"identityEffector\", \"from effector\")" + ); + waitForApplicationTasks(app); + + final TestEntity testEnt1 = + (TestEntity) Iterables.filter(app.getChildren(), displayNameEqualTo("testEnt1")).iterator().next(); + assertCallHistory(testEnt1, "start", "identityEffector"); + final String contents = new String(Files.readAllBytes(tempFile)).trim(); + assertEquals(contents, "from effector", "file contents: " + contents); + + } finally { + Files.delete(tempFile); + } + } + + public static void assertCallHistory(TestEntity testEntity, String... expectedCalls) { + List callHistory = testEntity.getCallHistory(); + Assert.assertEquals(callHistory.size(), expectedCalls.length, "history = " + callHistory); + int c = 0; + for (String expected : expectedCalls) { + Assert.assertEquals(callHistory.get(c++), expected, "history = " + callHistory); } - Assert.assertNotNull(start1); - Assert.assertNotNull(start2); - EntityAsserts.assertAttributeEquals(start1, Startable.SERVICE_UP, false); - EntityAsserts.assertAttributeEquals(start2, Startable.SERVICE_UP, true); } @Override diff --git a/camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml b/camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml deleted file mode 100644 index 1db93c1a0e..0000000000 --- a/camp/camp-brooklyn/src/test/resources/test-app-with-effectors.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# -# 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. -# -name: test-app-with-effectors -description: Application with effector that stops another entity -origin: https://github.com/apache/brooklyn -services: -- type: org.apache.brooklyn.entity.stock.BasicStartable - id: start1 - name: start1 -- type: org.apache.brooklyn.entity.stock.BasicStartable - id: start2 - name: start2 - brooklyn.initializers: - - type: org.apache.brooklyn.core.sensor.StaticSensor - brooklyn.config: - name: stop - targetType: java.lang.Object - static.value: $brooklyn:component("start1").effector("stop") diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java b/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java index ce10c86957..4ffd4c41f4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java +++ b/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java @@ -147,6 +147,7 @@ protected Maybe getNonBlockingResolvingSimple(ConfigKey key) { .deep(true) .context(getContext()) .swallowExceptions() + .avoidSideEffects() .get(); return (resolved != marker) ? TypeCoercions.tryCoerce(resolved, key.getTypeToken()) diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/HasSideEffects.java b/core/src/main/java/org/apache/brooklyn/util/core/task/HasSideEffects.java new file mode 100644 index 0000000000..5d3e6a79f3 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/task/HasSideEffects.java @@ -0,0 +1,25 @@ +/* + * 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.util.core.task; + +/** + * Marker interface to indicate that something, e.g. a DeferredSupplier has side effects. + */ +public interface HasSideEffects { +} diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java index f81594eba8..afff971cf3 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java @@ -112,6 +112,7 @@ public class ValueResolver implements DeferredSupplier, Iterable implements DeferredSupplier, Iterable swallowExceptions() { this.swallowExceptions = true; return this; } + + public ValueResolver avoidSideEffects() { + this.avoidSideEffects = true; + return this; + } /** whether the task should be marked as transient; defaults true */ public ValueResolver transientTask(boolean isTransientTask) { @@ -349,7 +356,7 @@ protected Maybe getMaybeInternal() { checkTypeNotNull(); Object v = this.value; - + //if the expected type is a closure or map and that's what we have, we're done (or if it's null); //but not allowed to return a future or DeferredSupplier as the resolved value if (v==null || (!forceDeep && type.isInstance(v) && !Future.class.isInstance(v) && !DeferredSupplier.class.isInstance(v))) @@ -378,6 +385,9 @@ protected Maybe getMaybeInternal() { if (!((TaskAdaptable) v).asTask().isSubmitted() ) { if (exec==null) return Maybe.absent("Value for unsubmitted task '"+getDescription()+"' requested but no execution context available"); + if (v instanceof HasSideEffects && avoidSideEffects) { + return Maybe.absent(); + } exec.submit(((TaskAdaptable) v).asTask()); } } @@ -405,7 +415,9 @@ public Maybe call() throws Exception { } else if (v instanceof DeferredSupplier) { final DeferredSupplier ds = (DeferredSupplier) v; - + if (v instanceof HasSideEffects && avoidSideEffects) { + return Maybe.absent(); + } if ((!Boolean.FALSE.equals(embedResolutionInTask) && (exec!=null || timeout!=null)) || Boolean.TRUE.equals(embedResolutionInTask)) { if (exec==null) return Maybe.absent("Embedding in task needed for '"+getDescription()+"' but no execution context available"); diff --git a/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntity.java b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntity.java index 7a29c1a93d..6233675b27 100644 --- a/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntity.java +++ b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntity.java @@ -100,6 +100,9 @@ public interface TestEntity extends Entity, Startable, EntityLocal, EntityIntern @Effector(description="returns the arg passed in") public Object identityEffector(@EffectorParam(name="arg", description="val to return") Object arg); + + @Effector(description="an example effector with side effects, returns a strictly increasing sequence value") + public Integer sequenceEffector(); @Effector(description="sleeps for the given duration") public void sleepEffector(@EffectorParam(name="duration") Duration duration); diff --git a/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntityImpl.java b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntityImpl.java index 7a2c813278..59fcac3325 100644 --- a/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntityImpl.java +++ b/core/src/test/java/org/apache/brooklyn/core/test/entity/TestEntityImpl.java @@ -56,6 +56,7 @@ public class TestEntityImpl extends AbstractEntity implements TestEntity { protected int sequenceValue = 0; protected AtomicInteger counter = new AtomicInteger(0); + protected AtomicInteger effectorCount = new AtomicInteger(0); protected Map constructorProperties; protected Map configureProperties; protected List callHistory = Collections.synchronizedList(Lists.newArrayList()); @@ -97,7 +98,13 @@ public Object identityEffector(Object arg) { callHistory.add("identityEffector"); return checkNotNull(arg, "arg"); } - + + @Override + public Integer sequenceEffector() { + callHistory.add("sequenceEffector"); + return effectorCount.incrementAndGet(); + } + @Override public void sleepEffector(Duration duration) { if (LOG.isTraceEnabled()) LOG.trace("In sleepEffector for {}", this); From 5118a3598ee6ba96db44a70a560a369e878fe094 Mon Sep 17 00:00:00 2001 From: Geoff Macartney Date: Sat, 25 Jun 2016 12:10:12 +0100 Subject: [PATCH 3/6] Adding integration test for entity interaction. Also adds a formatString test that was used to get the right structure for the entity test, may as well include it now that it is written. --- .../spi/dsl/methods/BrooklynDslCommon.java | 2 +- .../spi/dsl/methods/DslComponent.java | 42 +++++++- .../EffectorsYamlIntegrationTest.java | 98 +++++++++++++++++++ .../camp/brooklyn/EffectorsYamlTest.java | 38 +++++-- .../methods/FormatStringIntegrationTest.java | 75 ++++++++++++++ 5 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlIntegrationTest.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/FormatStringIntegrationTest.java 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 69c65141bd..62ae840da4 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 @@ -242,7 +242,7 @@ public static BrooklynDslDeferredSupplier effector(String effectorName) { return new DslComponent(Scope.THIS, "").effector(effectorName, ImmutableMap.of()); } - public static BrooklynDslDeferredSupplier effector(String effectorName, String... args) { + public static BrooklynDslDeferredSupplier effector(String effectorName, Object... args) { return new DslComponent(Scope.THIS, "").effector(effectorName, args); } 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 77a82bf565..f08e4bd477 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 @@ -20,6 +20,7 @@ import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -31,6 +32,8 @@ import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.api.mgmt.TaskAdaptable; +import org.apache.brooklyn.api.mgmt.TaskFactory; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.Sensor; import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; @@ -46,6 +49,7 @@ 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.collections.MutableMap; import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.core.task.BasicExecutionContext; import org.apache.brooklyn.util.core.task.DeferredSupplier; @@ -60,6 +64,7 @@ import com.google.common.base.CaseFormat; import com.google.common.base.Converter; +import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -68,6 +73,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.Callables; public class DslComponent extends BrooklynDslDeferredSupplier implements DslFunctionSource { @@ -523,7 +529,7 @@ public BrooklynDslDeferredSupplier effector(final String effectorName) { public BrooklynDslDeferredSupplier effector(final String effectorName, final Map args) { return new ExecuteEffector(this, effectorName, args); } - public BrooklynDslDeferredSupplier effector(final String effectorName, String... args) { + public BrooklynDslDeferredSupplier effector(final String effectorName, Object... args) { return new ExecuteEffector(this, effectorName, ImmutableList.copyOf(args)); } protected static class ExecuteEffector extends BrooklynDslDeferredSupplier implements HasSideEffects { @@ -531,7 +537,7 @@ protected static class ExecuteEffector extends BrooklynDslDeferredSupplier args; - private final List argList; + private final List argList; private Task cachedTask; public ExecuteEffector(DslComponent component, String effectorName, Map args) { this.component = Preconditions.checkNotNull(component); @@ -539,12 +545,13 @@ public ExecuteEffector(DslComponent component, String effectorName, Map args) { + public ExecuteEffector(DslComponent component, String effectorName, List args) { this.component = Preconditions.checkNotNull(component); this.effectorName = effectorName; this.argList = args; this.args = null; } + @SuppressWarnings("unchecked") @Override public Task newTask() { @@ -556,11 +563,38 @@ public Task newTask() { if (null == cachedTask) { cachedTask = null == argList ? Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args) - : Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector.get(), argList.toArray()); + : invokeWithDeferredArgs(targetEntity, targetEffector.get(), argList); +// : Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector.get(), argList.toArray()); } return (Task) cachedTask; } + public static Task invokeWithDeferredArgs(final Entity targetEntity, final Effector targetEffector, final List args) { + List> taskArgs = Lists.newArrayList(); + for (Object arg: args) { + if (arg instanceof TaskAdaptable) taskArgs.add((TaskAdaptable)arg); + else if (arg instanceof TaskFactory) taskArgs.add( ((TaskFactory>)arg).newTask() ); + } + + return DependentConfiguration.transformMultiple( + MutableMap.of("displayName", "invoking '"+targetEffector.getName()+"' with "+taskArgs.size()+" task"+(taskArgs.size()!=1?"s":"")), + new Function, Object>() { + @Override public Object apply(List input) { + Iterator tri = input.iterator(); + Object[] vv = new Object[args.size()]; + int i=0; + for (Object arg : args) { + if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) vv[i] = tri.next(); + else if (arg instanceof DeferredSupplier) vv[i] = ((DeferredSupplier) arg).get(); + else vv[i] = arg; + i++; + } + + return Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector, vv); + }}, + taskArgs); + } + @Override public int hashCode() { return Objects.hashCode(component, effectorName); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlIntegrationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlIntegrationTest.java new file mode 100644 index 0000000000..be4cdb50de --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlIntegrationTest.java @@ -0,0 +1,98 @@ +/* + * 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; + +import com.google.common.collect.Iterables; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.entity.software.base.SameServerEntity; +import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.apache.brooklyn.core.entity.EntityPredicates.displayNameEqualTo; +import static org.testng.Assert.assertEquals; + +@Test +public class EffectorsYamlIntegrationTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(EffectorsYamlIntegrationTest.class); + + @Test(groups = "Integration") + public void testInteractingWithAnotherEntityForStartup() throws Exception { + + final Path tempFile = Files.createTempFile("testInteractingWithAnotherEntityForStartup", ".txt"); + getLogger().info("Temp file is {}", tempFile.toAbsolutePath()); + + try { + Entity app = createAndStartApplication( + "location: localhost:(name=localhost)", + "services:", + "- type: " + SameServerEntity.class.getName(), + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " id: testEntity", + " name: testEntity", + " brooklyn.initializers:", + " - type: org.apache.brooklyn.core.sensor.ssh.SshCommandSensor", + " brooklyn.config:", + " name: greeting", + " period: 2s", + " command: |", + " echo hello world", + " - type: " + VanillaSoftwareProcess.class.getName(), + " id: consumerEntity", + " brooklyn.config:", + " install.latch: $brooklyn:entity(\"testEntity\").attributeWhenReady(\"service.isUp\")", + " launch.command: while true; do sleep 3600 ; done & echo $! > ${PID_FILE}", + " shell.env:", + " RESPONSE: $brooklyn:entity(\"testEntity\").effector(\"identityEffector\", $brooklyn:entity(\"testEntity\").attributeWhenReady(\"greeting\"))", + " post.launch.command: echo ${RESPONSE} > " + tempFile.toAbsolutePath() + ); + waitForApplicationTasks(app); + + final String contents = new String(Files.readAllBytes(tempFile)).trim(); + assertEquals(contents, "hello world", "file contents: " + contents); + + } finally { + Files.delete(tempFile); + } + } + + + private void assertCallHistory(TestEntity testEntity, String... expectedCalls) { + List callHistory = testEntity.getCallHistory(); + Assert.assertEquals(callHistory.size(), expectedCalls.length, "history = " + callHistory); + int c = 0; + for (String expected : expectedCalls) { + Assert.assertEquals(callHistory.get(c++), expected, "history = " + callHistory); + } + } + + @Override + protected Logger getLogger() { + return log; + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java index f7c0667779..4d3078b8f9 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EffectorsYamlTest.java @@ -18,7 +18,14 @@ */ package org.apache.brooklyn.camp.brooklyn; -import com.google.common.collect.Iterables; +import static com.google.common.collect.Iterables.filter; +import static org.apache.brooklyn.core.entity.EntityPredicates.displayNameEqualTo; +import static org.testng.Assert.assertEquals; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess; @@ -27,12 +34,7 @@ import org.testng.Assert; import org.testng.annotations.Test; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -import static org.apache.brooklyn.core.entity.EntityPredicates.displayNameEqualTo; -import static org.testng.Assert.assertEquals; +import com.google.common.collect.Iterables; @Test public class EffectorsYamlTest extends AbstractYamlTest { @@ -79,7 +81,7 @@ public void testEffectorMultiLine() throws Exception { } @Test - public void testEffectorWithReturn() throws Exception { + public void testEntityEffectorWithReturn() throws Exception { Entity app = createAndStartApplication( "services:", "- type: " + TestEntity.class.getName(), @@ -107,6 +109,26 @@ public void testOwnEffectorWithReturn() throws Exception { assertCallHistory(testEntity, "start", "identityEffector"); } + @Test + public void testEffectorOnOtherEntityWithReturn() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " id: entityOne", + " name: entityOne", + "- type: " + TestEntity.class.getName(), + " id: entityTwo", + " name: entityTwo", + " brooklyn.config:", + " test.confName: ", + " $brooklyn:entity(\"entityOne\").effector(\"identityEffector\", \"entityOne effector\")" + ); + TestEntity entityOne = (TestEntity) filter(app.getChildren(), displayNameEqualTo("entityOne")).iterator().next(); + TestEntity entityTwo = (TestEntity) filter(app.getChildren(), displayNameEqualTo("entityTwo")).iterator().next(); + Assert.assertEquals(entityTwo.getConfig(TestEntity.CONF_NAME), "entityOne effector"); + assertCallHistory(entityOne, "start", "identityEffector"); + } + @Test public void testEffectorCalledOncePerConfigKey() throws Exception { Entity app = createAndStartApplication( diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/FormatStringIntegrationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/FormatStringIntegrationTest.java new file mode 100644 index 0000000000..8e9fb4c159 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/FormatStringIntegrationTest.java @@ -0,0 +1,75 @@ +/* + * 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.methods; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.entity.software.base.SameServerEntity; +import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.testng.Assert.assertEquals; + +public class FormatStringIntegrationTest extends AbstractYamlTest { + + private static final Logger LOG = LoggerFactory.getLogger(FormatStringIntegrationTest.class); + + @Test(groups = "Integration") + public void testFormatString() throws Exception { + + final Path tempFile = Files.createTempFile("testFormatString", ".txt"); + LOG.info("Temp file is {}", tempFile.toAbsolutePath()); + + try { + Entity app = createAndStartApplication( + "location: localhost:(name=localhost)", + "services:", + "- type: " + SameServerEntity.class.getName(), + " brooklyn.children:", + " - type: " + VanillaSoftwareProcess.class.getName(), + " id: sensorEntity", + " launch.command: while true; do sleep 3600 ; done & echo $! > ${PID_FILE}", + " brooklyn.initializers:", + " - type: org.apache.brooklyn.core.sensor.ssh.SshCommandSensor", + " brooklyn.config:", + " name: greeting", + " period: 2s", + " command: echo world", + " - type: " + VanillaSoftwareProcess.class.getName(), + " id: consumerEntity", + " launch.command: while true; do sleep 3600 ; done & echo $! > ${PID_FILE}", + " shell.env:", + " RESPONSE: $brooklyn:formatString(\"hello %s\", $brooklyn:entity(\"sensorEntity\").attributeWhenReady(\"greeting\"))", + " post.launch.command: echo ${RESPONSE} > " + tempFile.toAbsolutePath() + ); + waitForApplicationTasks(app); + + final String contents = new String(Files.readAllBytes(tempFile)).trim(); + assertEquals(contents, "hello world", "file contents: " + contents); + + } finally { + Files.delete(tempFile); + } + } +} From 8efc762725c03b5860d52038b1b1c49e119d891e Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 5 Jul 2016 07:22:51 +0100 Subject: [PATCH 4/6] Tidy up DSL effector method --- .../spi/dsl/methods/DslComponent.java | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) 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 f08e4bd477..891f1f2f99 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 @@ -560,46 +560,45 @@ public Task newTask() { if (targetEffector.isAbsentOrNull()) { throw new IllegalArgumentException("Effector " + effectorName + " not found on entity: " + targetEntity); } - if (null == cachedTask) { - cachedTask = null == argList - ? Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args) - : invokeWithDeferredArgs(targetEntity, targetEffector.get(), argList); -// : Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector.get(), argList.toArray()); + if (cachedTask == null) { + if (argList == null) { + cachedTask = Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args); + } else { + cachedTask = invokeWithDeferredArgs(targetEntity, targetEffector.get(), argList); + } } return (Task) cachedTask; } - - public static Task invokeWithDeferredArgs(final Entity targetEntity, final Effector targetEffector, final List args) { + private Task invokeWithDeferredArgs(final Entity targetEntity, final Effector targetEffector, final List args) { List> taskArgs = Lists.newArrayList(); - for (Object arg: args) { - if (arg instanceof TaskAdaptable) taskArgs.add((TaskAdaptable)arg); - else if (arg instanceof TaskFactory) taskArgs.add( ((TaskFactory>)arg).newTask() ); + for (Object arg : args) { + if (arg instanceof TaskAdaptable) taskArgs.add((TaskAdaptable) arg); + else if (arg instanceof TaskFactory) taskArgs.add(((TaskFactory>) arg).newTask()); } return DependentConfiguration.transformMultiple( - MutableMap.of("displayName", "invoking '"+targetEffector.getName()+"' with "+taskArgs.size()+" task"+(taskArgs.size()!=1?"s":"")), + MutableMap.of("displayName", "invoking '"+targetEffector.getName()+"' with "+taskArgs.size()+" task"+(taskArgs.size()!=1?"s":"")), new Function, Object>() { - @Override public Object apply(List input) { - Iterator tri = input.iterator(); - Object[] vv = new Object[args.size()]; - int i=0; - for (Object arg : args) { - if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) vv[i] = tri.next(); - else if (arg instanceof DeferredSupplier) vv[i] = ((DeferredSupplier) arg).get(); - else vv[i] = arg; - i++; - } - - return Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector, vv); - }}, - taskArgs); + @Override + public Object apply(List input) { + Iterator tri = input.iterator(); + Object[] vv = new Object[args.size()]; + int i=0; + for (Object arg : args) { + if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) vv[i] = tri.next(); + else if (arg instanceof DeferredSupplier) vv[i] = ((DeferredSupplier) arg).get(); + else vv[i] = arg; + i++; + } + return Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector, vv); + } + }, + taskArgs); } - @Override public int hashCode() { return Objects.hashCode(component, effectorName); } - @Override public boolean equals(Object obj) { if (this == obj) return true; From 3bb0397cac9c2d62d757f5e27547a28c789cf2f4 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Wed, 10 Aug 2016 09:04:04 +0100 Subject: [PATCH 5/6] Add locking on cachedTask for DSL effector --- .../spi/dsl/methods/DslComponent.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) 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 891f1f2f99..3df68dbb25 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 @@ -539,12 +539,14 @@ protected static class ExecuteEffector extends BrooklynDslDeferredSupplier args; private final List argList; private Task cachedTask; + public ExecuteEffector(DslComponent component, String effectorName, Map args) { this.component = Preconditions.checkNotNull(component); this.effectorName = effectorName; this.args = args; this.argList = null; } + public ExecuteEffector(DslComponent component, String effectorName, List args) { this.component = Preconditions.checkNotNull(component); this.effectorName = effectorName; @@ -560,20 +562,26 @@ public Task newTask() { if (targetEffector.isAbsentOrNull()) { throw new IllegalArgumentException("Effector " + effectorName + " not found on entity: " + targetEntity); } - if (cachedTask == null) { - if (argList == null) { - cachedTask = Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args); - } else { - cachedTask = invokeWithDeferredArgs(targetEntity, targetEffector.get(), argList); + synchronized (this) { + if (cachedTask == null) { + if (argList == null) { + cachedTask = Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args); + } else { + cachedTask = invokeWithDeferredArgs(targetEntity, targetEffector.get(), argList); + } } } return (Task) cachedTask; } + private Task invokeWithDeferredArgs(final Entity targetEntity, final Effector targetEffector, final List args) { List> taskArgs = Lists.newArrayList(); for (Object arg : args) { - if (arg instanceof TaskAdaptable) taskArgs.add((TaskAdaptable) arg); - else if (arg instanceof TaskFactory) taskArgs.add(((TaskFactory>) arg).newTask()); + if (arg instanceof TaskAdaptable) { + taskArgs.add((TaskAdaptable) arg); + } else if (arg instanceof TaskFactory) { + taskArgs.add(((TaskFactory>) arg).newTask()); + } } return DependentConfiguration.transformMultiple( @@ -585,9 +593,13 @@ public Object apply(List input) { Object[] vv = new Object[args.size()]; int i=0; for (Object arg : args) { - if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) vv[i] = tri.next(); - else if (arg instanceof DeferredSupplier) vv[i] = ((DeferredSupplier) arg).get(); - else vv[i] = arg; + if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) { + vv[i] = tri.next(); + } else if (arg instanceof DeferredSupplier) { + vv[i] = ((DeferredSupplier) arg).get(); + } else { + vv[i] = arg; + } i++; } return Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector, vv); @@ -595,10 +607,12 @@ public Object apply(List input) { }, taskArgs); } + @Override public int hashCode() { return Objects.hashCode(component, effectorName); } + @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -607,6 +621,7 @@ public boolean equals(Object obj) { return Objects.equal(this.component, that.component) && Objects.equal(this.effectorName, that.effectorName); } + @Override public String toString() { return (component.scope==Scope.THIS ? "" : component.toString()+".") + From 70d9acad2423c932e71d5769565ccf8a6e054494 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 28 Feb 2017 14:34:46 +0000 Subject: [PATCH 6/6] Added changes from @drigonwin for getImmediately --- .../camp/brooklyn/spi/dsl/methods/DslComponent.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 3df68dbb25..a5d9fcd869 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 @@ -31,9 +31,9 @@ import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.Task; -import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.mgmt.TaskAdaptable; import org.apache.brooklyn.api.mgmt.TaskFactory; +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; @@ -60,6 +60,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.groovy.GroovyJavaMethods; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.apache.brooklyn.util.text.Strings; import com.google.common.base.CaseFormat; @@ -554,6 +555,11 @@ public ExecuteEffector(DslComponent component, String effectorName, List getImmediately() { + return Maybe.absent(); + } + @SuppressWarnings("unchecked") @Override public Task newTask() {