http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessAndChildrenIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessAndChildrenIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessAndChildrenIntegrationTest.java new file mode 100644 index 0000000..3298a2f --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessAndChildrenIntegrationTest.java @@ -0,0 +1,197 @@ +/* + * 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.entity.software.base; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.ChildStartableMode; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Stopwatch; + +public class VanillaSoftwareProcessAndChildrenIntegrationTest { + + // TODO Why are these tests so slow? Even when the sleep time was 3 seconds instead of 10, they would still take about 10 seconds. + + // Note that tests run by jenkins can be extremely time-sensitive. + // e.g. http://brooklyn.builds.cloudsoftcorp.com/job/Brooklyn-Master-Integration/io.brooklyn$brooklyn-software-base/217/testReport/junit/brooklyn.entity.basic/VanillaSoftwareProcessAndChildrenIntegrationTest/testModeBackground/ + // shows a 5 second difference when in background mode, whereas the test originally asserted a difference of <= 1. + // Therefore increasing time that tests will take, but decreasing the sensitivity so we don't get such false-negatives. + + private static final Logger log = LoggerFactory.getLogger(VanillaSoftwareProcessAndChildrenIntegrationTest.class); + + private static final int PARENT_TASK_SLEEP_LENGTH_SECS = 10; + private static final int CHILD_TASK_SLEEP_LENGTH_SECS = 10; + private static final int CONCURRENT_MAX_ACCEPTABLE_DIFF_SECS = PARENT_TASK_SLEEP_LENGTH_SECS - 1; + private static final int SEQUENTIAL_MIN_ACCEPTABLE_DIFF_SECS = PARENT_TASK_SLEEP_LENGTH_SECS - 1; + private static final int EARLY_RETURN_GRACE_MS = 20; + + private TestApplication app; + private Location localhost; + + private VanillaSoftwareProcess p1; + private VanillaSoftwareProcess p2; + + @BeforeMethod(alwaysRun=true) + public void setup() { + app = TestApplication.Factory.newManagedInstanceForTests(); + localhost = app.getManagementContext().getLocationRegistry().resolve("localhost"); + } + + @AfterMethod(alwaysRun=true) + public void shutdown() { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test(groups = "Integration") + public void testModeNone() { + prep(ChildStartableMode.NONE); + long startTime = startApp(); + + Assert.assertNotNull(p1.getAttribute(SoftwareProcess.RUN_DIR)); + Assert.assertNull(p2.getAttribute(SoftwareProcess.RUN_DIR)); + Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime)); + } + + @Test(groups = "Integration") + public void testModeForeground() { + prep(ChildStartableMode.FOREGROUND); + long startTime = startApp(); + + long timediff = timediff(); + Assert.assertTrue( Math.abs(timediff) <= CONCURRENT_MAX_ACCEPTABLE_DIFF_SECS, "should have started concurrently, not with time difference "+timediff+" ("+p1+", "+p2+")" ); + Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime)); + } + + @Test(groups = "Integration") + public void testModeForegroundLate() { + prep(ChildStartableMode.FOREGROUND_LATE); + long startTime = startApp(); + + long timediff = timediff(); + Assert.assertTrue( timediff >= SEQUENTIAL_MIN_ACCEPTABLE_DIFF_SECS, "should have started later, not with time difference "+timediff+" ("+p1+", "+p2+")" ); + Assert.assertTrue(startTime >= 2*PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime)); + } + + @Test(groups = "Integration") + public void testModeBackground() { + prep(ChildStartableMode.BACKGROUND); + long startTime = startApp(); + + checkChildComesUpSoon(); + + long timediff = timediff(); + Assert.assertTrue( Math.abs(timediff) <= CONCURRENT_MAX_ACCEPTABLE_DIFF_SECS, "should have started concurrently, not with time difference "+timediff+" ("+p1+", "+p2+")" ); + Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime)); + } + + @Test(groups = "Integration") + public void testModeBackgroundLate() { + prep(ChildStartableMode.BACKGROUND_LATE); + long startTime = startApp(); + + checkChildNotUpYet(); + checkChildComesUpSoon(); + + long timediff = timediff(); + Assert.assertTrue( Math.abs(timediff) >= SEQUENTIAL_MIN_ACCEPTABLE_DIFF_SECS, "should have started later, not with time difference "+timediff+" ("+p1+", "+p2+")" ); + Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime)); + + // just to prevent warnings + waitForBackgroundedTasks(CHILD_TASK_SLEEP_LENGTH_SECS+1); + app.stop(); + app = null; + } + + private long startApp() { + Stopwatch stopwatch = Stopwatch.createStarted(); + app.start(Collections.singleton(localhost)); + long result = stopwatch.elapsed(TimeUnit.MILLISECONDS); + log.info("Took "+Time.makeTimeStringRounded(result)+" for app.start to complete"); + return result; + } + + private void waitForBackgroundedTasks(int secs) { + // child task is backgrounded; quick and dirty way to make sure it finishes (after setting service_up) + Time.sleep(Duration.seconds(secs)); + } + + private void checkChildNotUpYet() { + Assert.assertFalse(p2.getAttribute(SoftwareProcess.SERVICE_UP)); + } + + private void checkChildComesUpSoon() { + Stopwatch stopwatch = Stopwatch.createStarted(); + EntityTestUtils.assertAttributeEqualsEventually(p2, Attributes.SERVICE_UP, true); + log.info("Took "+Time.makeTimeStringRounded(stopwatch)+" for child-process to be service-up"); + } + + private long timediff() { + Long d1 = getRunTimeUtc(p1); + Long d2 = getRunTimeUtc(p2); + + log.info("timestamps for "+JavaClassNames.callerNiceClassAndMethod(1)+" have difference "+(d2-d1)); + + return d2 - d1; + } + + private Long getRunTimeUtc(VanillaSoftwareProcess p) { + Assert.assertNotNull(p.getAttribute(SoftwareProcess.RUN_DIR)); + return Long.parseLong( Strings.getFirstWordAfter(new ResourceUtils(this).getResourceAsString(Os.mergePaths(p.getAttribute(SoftwareProcess.RUN_DIR), "DATE")), "utc") ); + } + + private void prep(ChildStartableMode mode) { + String parentCmd = "echo utc `date +%s` > DATE ; echo human `date` >> DATE ; " + + "{ nohup sleep 60 & } ; echo $! > $PID_FILE ; sleep "+PARENT_TASK_SLEEP_LENGTH_SECS; + + String childCmd = "echo utc `date +%s` > DATE ; echo human `date` >> DATE ; " + + "{ nohup sleep 60 & } ; echo $! > $PID_FILE ; sleep "+CHILD_TASK_SLEEP_LENGTH_SECS; + + p1 = app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class) + .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, parentCmd) + .configure(VanillaSoftwareProcess.CHILDREN_STARTABLE_MODE, mode) + ); + p2 = p1.addChild(EntitySpec.create(VanillaSoftwareProcess.class) + .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, childCmd)); + Entities.manage(p2); + + log.info("testing "+JavaClassNames.callerNiceClassAndMethod(1)+", using "+p1+" and "+p2); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntity.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntity.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntity.java new file mode 100644 index 0000000..aacbe9f --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntity.java @@ -0,0 +1,27 @@ +/* + * 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.entity.software.base.lifecycle; + +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; + +@ImplementedBy(MyEntityImpl.class) +public interface MyEntity extends SoftwareProcess { + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityApp.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityApp.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityApp.java new file mode 100644 index 0000000..461c676 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityApp.java @@ -0,0 +1,26 @@ +/* + * 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.entity.software.base.lifecycle; + +public class MyEntityApp { + public static void main(String[] args) throws InterruptedException { + System.out.println("Properties: "+System.getProperties()); + Thread.sleep(60*60*1000); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityImpl.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityImpl.java new file mode 100644 index 0000000..15dd95b --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/MyEntityImpl.java @@ -0,0 +1,125 @@ +/* + * 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.entity.software.base.lifecycle; + +import java.util.List; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.entity.java.JavaSoftwareProcessSshDriver; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.software.base.SoftwareProcessDriver; +import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.text.Identifiers; + +public class MyEntityImpl extends SoftwareProcessImpl implements MyEntity { + @Override + public Class<?> getDriverInterface() { + return MyEntityDriver.class; + } + + @Override + protected void connectSensors() { + super.connectSensors(); + connectServiceUpIsRunning(); + } + + @Override + protected void disconnectSensors() { + super.disconnectSensors(); + disconnectServiceUpIsRunning(); + } + + public interface MyEntityDriver extends SoftwareProcessDriver {} + + public static class MyEntitySshDriver extends JavaSoftwareProcessSshDriver implements MyEntityDriver { + + @SetFromFlag("version") + public static final ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "0.1"); + + public MyEntitySshDriver(MyEntityImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + protected String getLogFileLocation() { + return getRunDir()+"/nohup.out"; + } + + @Override + public void install() { + String resourceName = "/"+MyEntityApp.class.getName().replace(".", "/")+".class"; + ResourceUtils r = ResourceUtils.create(this); + if (r.getResourceFromUrl(resourceName) == null) + throw new IllegalStateException("Cannot find resource "+resourceName); + String tmpFile = "/tmp/brooklyn-test-MyEntityApp-"+Identifiers.makeRandomId(6)+".class"; + int result = getMachine().installTo(resourceName, tmpFile); + if (result!=0) throw new IllegalStateException("Cannot install "+resourceName+" to "+tmpFile); + String saveAs = "classes/"+MyEntityApp.class.getPackage().getName().replace(".", "/")+"/"+MyEntityApp.class.getSimpleName()+".class"; + newScript(INSTALLING). + failOnNonZeroResultCode(). + body.append( + "curl -L \"file://"+tmpFile+"\" --create-dirs -o "+saveAs+" || exit 9" + ).execute(); + } + + @Override + public void customize() { + newScript(CUSTOMIZING) + .execute(); + } + + @Override + public void launch() { + newScript(MutableMap.of("usePidFile", true), LAUNCHING) + .body.append( + String.format("nohup java -classpath %s/classes $JAVA_OPTS %s &", getInstallDir(), MyEntityApp.class.getName()) + ).execute(); + } + + @Override + public boolean isRunning() { + //TODO use PID instead + return newScript(MutableMap.of("usePidFile", true), CHECK_RUNNING) + .execute() == 0; + } + + @Override + public void stop() { + newScript(MutableMap.of("usePidFile", true), STOPPING) + .execute(); + } + + @Override + public void kill() { + newScript(MutableMap.of("usePidFile", true), KILLING) + .execute(); + } + + @Override + protected List<String> getCustomJavaConfigOptions() { + return MutableList.<String>builder().addAll(super.getCustomJavaConfigOptions()).add("-Dabc=def").build(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/NaiveScriptRunnerTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/NaiveScriptRunnerTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/NaiveScriptRunnerTest.java new file mode 100644 index 0000000..59103f1 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/NaiveScriptRunnerTest.java @@ -0,0 +1,254 @@ +/* + * 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.entity.software.base.lifecycle; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags.WrappedStream; +import org.apache.brooklyn.entity.software.base.lifecycle.NaiveScriptRunner; +import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.task.BasicExecutionContext; +import org.apache.brooklyn.util.core.task.BasicExecutionManager; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.repeat.Repeater; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.base.Throwables; + +@Test +public class NaiveScriptRunnerTest { + + private static final Logger log = LoggerFactory.getLogger(NaiveScriptRunnerTest.class); + + List<String> commands = new ArrayList<String>(); + + @BeforeMethod + private void setup() { commands.clear(); } + + @SuppressWarnings("rawtypes") + private NaiveScriptRunner newMockRunner(final int result) { + return new NaiveScriptRunner() { + @Override + public int execute(List<String> script, String summaryForLogging) { + return execute(new MutableMap(), script, summaryForLogging); + } + @Override + public int execute(Map flags, List<String> script, String summaryForLogging) { + commands.addAll(script); + return result; + } + }; + } + + @SuppressWarnings("rawtypes") + public static NaiveScriptRunner newLocalhostRunner() { + return new NaiveScriptRunner() { + LocalhostMachineProvisioningLocation location = new LocalhostMachineProvisioningLocation(); + @Override + public int execute(List<String> script, String summaryForLogging) { + return execute(new MutableMap(), script, summaryForLogging); + } + @SuppressWarnings("unchecked") + @Override + public int execute(Map flags, List<String> script, String summaryForLogging) { + try { + Map flags2 = MutableMap.of("logPrefix", "test"); + flags2.putAll(flags); + return location.obtain().execScript(flags2, summaryForLogging, script); + } catch (NoMachinesAvailableException e) { + throw Throwables.propagate(e); + } + } + }; + }; + + public void testHeadBodyFootAndResult() { + ScriptHelper h = new ScriptHelper(newMockRunner(101), "mock"); + int result = h.header. + append("h1", "h2").body.append("b1", "b2").footer.append("f1", "f2"). + execute(); + Assert.assertEquals(result, 101); + Assert.assertEquals(commands, Arrays.asList("h1", "h2", "b1", "b2", "f1", "f2"), "List wrong: "+commands); + } + + public void testFailOnNonZero() { + ScriptHelper h = new ScriptHelper(newMockRunner(106), "mock"); + boolean succeededWhenShouldntHave = false; + try { + h.body.append("ignored"). + failOnNonZeroResultCode(). // will happen + execute(); + succeededWhenShouldntHave = true; + } catch (Exception e) { + log.info("ScriptHelper non-zero causes return code: "+e); + } + if (succeededWhenShouldntHave) Assert.fail("succeeded when shouldn't have"); + } + + public void testFailOnNonZeroDontFailIfZero() { + int result = new ScriptHelper(newMockRunner(0), "mock").body.append("ignored"). + failOnNonZeroResultCode(). // will happen + execute(); + Assert.assertEquals(result, 0); + } + + + @Test(groups = "Integration") + public void testFailingCommandFailsEarly() { + ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). + body.append("curl road://to/nowhere", "exit 11"). + gatherOutput(); + int result = script.execute(); + // should get _1_ from curl failing, not 11 from us + // TODO not sure why though! + Assert.assertEquals(1, result); + } + + // TODO a good way to indicate when downloads fail, as that is quite a common case + // but i think we need quite a bit of scaffolding to detect that problem (without inspecting logs) ... + + @Test(groups = "Integration") + public void testGatherOutputStdout() { + ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). + body.append("echo `echo foo``echo bar`", "exit 8"). + gatherOutput(); + int result = script.execute(); + Assert.assertEquals(8, result); + if (!script.getResultStdout().contains("foobar")) + Assert.fail("STDOUT does not contain expected text 'foobar'.\n"+script.getResultStdout()+ + "\nSTDERR:\n"+script.getResultStderr()); + } + + @Test(groups = "Integration") + public void testGatherOutputStderr() { + ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). + body.append("set -x", "curl road://to/nowhere || exit 11"). + gatherOutput(); + int result = script.execute(); + Assert.assertEquals(11, result); + if (!script.getResultStderr().contains("road")) + Assert.fail("STDERR does not contain expected text 'road'.\n"+script.getResultStderr()+ + "\nSTDOUT:\n"+script.getResultStdout()); + } + + @Test(groups = "Integration") + public void testGatherOutuputNotEnabled() { + ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). + body.append("echo foo", "exit 11"); + int result = script.execute(); + Assert.assertEquals(11, result); + boolean succeededWhenShouldNotHave = false; + try { + script.getResultStdout(); + succeededWhenShouldNotHave = true; + } catch (Exception e) { /* expected */ } + if (succeededWhenShouldNotHave) Assert.fail("Should have failed"); + } + + @Test(groups = "Integration") + public void testStreamsInTask() { + final ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). + body.append("echo `echo foo``echo bar`", "grep absent-text badfile_which_does_not_exist_blaahblahasdewq"). + gatherOutput(); + Assert.assertNull(script.peekTask()); + Task<Integer> task = script.newTask(); + Assert.assertTrue(BrooklynTaskTags.streams(task).size() >= 3, "Expected at least 3 streams: "+BrooklynTaskTags.streams(task)); + Assert.assertFalse(Tasks.isQueuedOrSubmitted(task)); + WrappedStream in = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDIN); + Assert.assertNotNull(in); + Assert.assertTrue(in.streamContents.get().contains("echo foo"), "Expected 'echo foo' but had: "+in.streamContents.get()); + Assert.assertTrue(in.streamSize.get() > 0); + Assert.assertNotNull(script.peekTask()); + } + + @Test(groups = "Integration") + public void testAutoQueueAndRuntimeStreamsInTask() { + final ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). + body.append("echo `echo foo``echo bar`", "grep absent-text badfile_which_does_not_exist_blaahblahasdewq"). + gatherOutput(); + Task<Integer> submitter = Tasks.<Integer>builder().body(new Callable<Integer>() { + public Integer call() { + int result = script.execute(); + return result; + } + }).build(); + BasicExecutionManager em = new BasicExecutionManager("tests"); + BasicExecutionContext ec = new BasicExecutionContext(em); + try { + Assert.assertNull(script.peekTask()); + ec.submit(submitter); + // soon there should be a task which is submitted + Assert.assertTrue(Repeater.create("get script").every(Duration.millis(10)).limitTimeTo(Duration.FIVE_SECONDS).until(new Callable<Boolean>() { + public Boolean call() { + return (script.peekTask() != null) && Tasks.isQueuedOrSubmitted(script.peekTask()); + } + }).run()); + Task<Integer> task = script.peekTask(); + Assert.assertTrue(BrooklynTaskTags.streams(task).size() >= 3, "Expected at least 3 streams: "+BrooklynTaskTags.streams(task)); + // stdin should be populated + WrappedStream in = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDIN); + Assert.assertNotNull(in); + Assert.assertTrue(in.streamContents.get().contains("echo foo"), "Expected 'echo foo' but had: "+in.streamContents.get()); + Assert.assertTrue(in.streamSize.get() > 0); + + // out and err should exist + WrappedStream out = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDOUT); + WrappedStream err = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDERR); + Assert.assertNotNull(out); + Assert.assertNotNull(err); + + // it should soon finish, with exit code + Integer result = task.getUnchecked(Duration.TEN_SECONDS); + Assert.assertNotNull(result); + Assert.assertTrue(result > 0, "Expected non-zero exit code: "+result); + // and should contain foobar in stdout + if (!script.getResultStdout().contains("foobar")) + Assert.fail("Script STDOUT does not contain expected text 'foobar'.\n"+script.getResultStdout()+ + "\nSTDERR:\n"+script.getResultStderr()); + if (!out.streamContents.get().contains("foobar")) + Assert.fail("Task STDOUT does not contain expected text 'foobar'.\n"+out.streamContents.get()+ + "\nSTDERR:\n"+script.getResultStderr()); + // and "No such file or directory" in stderr + if (!script.getResultStderr().contains("No such file or directory")) + Assert.fail("Script STDERR does not contain expected text 'No such ...'.\n"+script.getResultStdout()+ + "\nSTDERR:\n"+script.getResultStderr()); + if (!err.streamContents.get().contains("No such file or directory")) + Assert.fail("Task STDERR does not contain expected text 'No such...'.\n"+out.streamContents.get()+ + "\nSTDERR:\n"+script.getResultStderr()); + } finally { + em.shutdownNow(); + } + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java new file mode 100644 index 0000000..479c24f --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java @@ -0,0 +1,157 @@ +/* + * 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.entity.software.base.lifecycle; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest; +import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.MyService; +import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.MyServiceImpl; +import org.apache.brooklyn.entity.trait.Startable; +import org.apache.brooklyn.sensor.feed.function.FunctionFeed; +import org.apache.brooklyn.sensor.feed.function.FunctionPollConfig; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.TestException; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.FixedListMachineProvisioningLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; + +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; + +public class ScriptHelperTest extends BrooklynAppUnitTestSupport { + + private static final Logger log = LoggerFactory.getLogger(ScriptHelperTest.class); + + private SshMachineLocation machine; + private FixedListMachineProvisioningLocation<SshMachineLocation> loc; + boolean shouldFail = false; + int failCount = 0; + + @BeforeMethod(alwaysRun=true) + @SuppressWarnings("unchecked") + @Override + public void setUp() throws Exception { + super.setUp(); + loc = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)); + machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .configure("address", "localhost")); + loc.addMachine(machine); + } + + @Test + public void testCheckRunningForcesInessential() { + MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class, MyServiceInessentialDriverImpl.class)); + + entity.start(ImmutableList.of(loc)); + SimulatedInessentialIsRunningDriver driver = (SimulatedInessentialIsRunningDriver) entity.getDriver(); + Assert.assertTrue(driver.isRunning()); + + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true); + EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true); + + log.debug("up, now cause failure"); + + driver.setFailExecution(true); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, false); + + log.debug("caught failure, now clear"); + driver.setFailExecution(false); + EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true); + } + + public static class MyServiceInessentialDriverImpl extends MyServiceImpl { + + @Override public Class<?> getDriverInterface() { + return SimulatedInessentialIsRunningDriver.class; + } + + @Override + protected void connectSensors() { + super.connectSensors(); + connectServiceUpIsRunning(); + } + + @Override + public void connectServiceUpIsRunning() { +// super.connectServiceUpIsRunning(); + // run more often + FunctionFeed.builder() + .entity(this) + .period(Duration.millis(10)) + .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING) + .onException(Functions.constant(Boolean.FALSE)) + .callable(new Callable<Boolean>() { + public Boolean call() { + return getDriver().isRunning(); + } + })) + .build(); + } + } + + public static class SimulatedInessentialIsRunningDriver extends SoftwareProcessEntityTest.SimulatedDriver { + private boolean failExecution = false; + + public SimulatedInessentialIsRunningDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public boolean isRunning() { + return newScript(CHECK_RUNNING) + .execute() == 0; + } + + @Override + public int execute(List<String> script, String summaryForLogging) { + if (failExecution) { + throw new TestException("Simulated driver exception"); + } + return 0; + } + + @SuppressWarnings("rawtypes") + @Override + public int execute(Map flags2, List<String> script, String summaryForLogging) { + if (failExecution) { + throw new TestException("Simulated driver exception"); + } + return 0; + } + + public void setFailExecution(boolean failExecution) { + this.failExecution = failExecution; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/StartStopSshDriverTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/StartStopSshDriverTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/StartStopSshDriverTest.java new file mode 100644 index 0000000..1bf3bbe --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/StartStopSshDriverTest.java @@ -0,0 +1,168 @@ +/* + * 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.entity.software.base.lifecycle; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.core.test.entity.TestApplicationImpl; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.core.test.entity.TestEntityImpl; +import org.apache.brooklyn.entity.core.BrooklynConfigKeys; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.internal.ssh.SshTool; +import org.apache.brooklyn.util.core.internal.ssh.cli.SshCliTool; +import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool; +import org.apache.brooklyn.util.stream.StreamGobbler; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.SshMachineLocation; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class StartStopSshDriverTest { + + public class BasicStartStopSshDriver extends AbstractSoftwareProcessSshDriver { + public BasicStartStopSshDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + public boolean isRunning() { return true; } + public void stop() {} + public void kill() {} + public void install() {} + public void customize() {} + public void launch() {} + } + + private static class ThreadIdTransformer implements Function<ThreadInfo, Long> { + @Override + public Long apply(ThreadInfo t) { + return t.getThreadId(); + } + } + + private TestApplication app; + private TestEntity entity; + private SshMachineLocationWithSshTool sshMachineLocation; + private AbstractSoftwareProcessSshDriver driver; + + @SuppressWarnings("rawtypes") + protected static class SshMachineLocationWithSshTool extends SshMachineLocation { + private static final long serialVersionUID = 1L; + + SshTool lastTool; + public SshMachineLocationWithSshTool(Map flags) { super(flags); } + public SshTool connectSsh(Map args) { + SshTool result = super.connectSsh(args); + lastTool = result; + return result; + } + } + + @BeforeMethod(alwaysRun = true) + public void setUp() { + app = new TestApplicationImpl(); + entity = new TestEntityImpl(app); + Entities.startManagement(app); + sshMachineLocation = new SshMachineLocationWithSshTool(ImmutableMap.of("address", "localhost")); + driver = new BasicStartStopSshDriver(entity, sshMachineLocation); + } + + @Test(groups="Integration") + public void testExecuteDoesNotLeaveRunningStreamGobblerThread() { + List<ThreadInfo> existingThreads = getThreadsCalling(StreamGobbler.class); + final List<Long> existingThreadIds = getThreadId(existingThreads); + + List<String> script = Arrays.asList("echo hello"); + driver.execute(script, "mytest"); + + Asserts.succeedsEventually(ImmutableMap.of("timeout", 10*1000), new Runnable() { + @Override + public void run() { + List<ThreadInfo> currentThreads = getThreadsCalling(StreamGobbler.class); + Set<Long> currentThreadIds = MutableSet.copyOf(getThreadId(currentThreads)); + + currentThreadIds.removeAll(existingThreadIds); + assertEquals(currentThreadIds, ImmutableSet.<Long>of()); + } + }); + } + + @Test(groups="Integration") + public void testSshScriptHeaderUsedWhenSpecified() { + entity.setConfig(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER, "#!/bin/bash -e\necho hello world"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + driver.execute(ImmutableMap.of("out", out), Arrays.asList("echo goodbye"), "test"); + String s = out.toString(); + assertTrue(s.contains("goodbye"), "should have said goodbye: "+s); + assertTrue(s.contains("hello world"), "should have said hello: "+s); + assertTrue(sshMachineLocation.lastTool instanceof SshjTool, "expect sshj tool, got "+ + (sshMachineLocation.lastTool!=null ? ""+sshMachineLocation.lastTool.getClass()+":" : "") + sshMachineLocation.lastTool); + } + + @Test(groups="Integration") + public void testSshCliPickedUpWhenSpecified() { + entity.setConfig(BrooklynConfigKeys.SSH_TOOL_CLASS, SshCliTool.class.getName()); + driver.execute(Arrays.asList("echo hi"), "test"); + assertTrue(sshMachineLocation.lastTool instanceof SshCliTool, "expect CLI tool, got "+ + (sshMachineLocation.lastTool!=null ? ""+sshMachineLocation.lastTool.getClass()+":" : "") + sshMachineLocation.lastTool); + } + + private List<ThreadInfo> getThreadsCalling(Class<?> clazz) { + String clazzName = clazz.getCanonicalName(); + List<ThreadInfo> result = MutableList.of(); + ThreadMXBean threadMxbean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threads = threadMxbean.dumpAllThreads(false, false); + + for (ThreadInfo thread : threads) { + StackTraceElement[] stackTrace = thread.getStackTrace(); + for (StackTraceElement stackTraceElement : stackTrace) { + if (clazzName == stackTraceElement.getClassName()) { + result.add(thread); + break; + } + } + } + return result; + } + + private ImmutableList<Long> getThreadId(List<ThreadInfo> existingThreads) { + return FluentIterable.from(existingThreads).transform(new ThreadIdTransformer()).toList(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java new file mode 100644 index 0000000..11fba70 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/ApplicationUsageTrackingTest.java @@ -0,0 +1,224 @@ +/* + * 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.entity.software.base.test.core.mgmt.usage; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.mgmt.internal.UsageListener.ApplicationMetadata; +import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage; +import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent; +import org.apache.brooklyn.core.objs.proxy.EntityProxy; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class ApplicationUsageTrackingTest { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationUsageTrackingTest.class); + + protected TestApplication app; + protected ManagementContextInternal mgmt; + + protected boolean shouldSkipOnBoxBaseDirResolution() { + return true; + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + mgmt = LocalManagementContextForTests.newInstance(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + try { + if (mgmt != null) Entities.destroyAll(mgmt); + } catch (Throwable t) { + LOG.error("Caught exception in tearDown method", t); + } finally { + mgmt = null; + } + } + + @Test + public void testUsageInitiallyEmpty() { + Set<ApplicationUsage> usage = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + assertEquals(usage, ImmutableSet.of()); + } + + @Test + @SuppressWarnings("deprecation") + public void testAddAndRemoveLegacyUsageListener() throws Exception { + final RecordingLegacyUsageListener listener = new RecordingLegacyUsageListener(); + mgmt.getUsageManager().addUsageListener(listener); + + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + app.setCatalogItemId("testCatalogItem"); + app.start(ImmutableList.<Location>of()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getApplicationEvents(); + assertEquals(events.size(), 2, "events="+events); // expect STARTING and RUNNING + + String appId = (String) events.get(0).get(1); + String appName = (String) events.get(0).get(2); + String entityType = (String) events.get(0).get(3); + String catalogItemId = (String) events.get(0).get(4); + Map<?,?> metadata = (Map<?, ?>) events.get(0).get(5); + ApplicationEvent appEvent = (ApplicationEvent) events.get(0).get(6); + + assertEquals(appId, app.getId(), "events="+events); + assertNotNull(appName, "events="+events); + assertEquals(catalogItemId, app.getCatalogItemId(), "events="+events); + assertNotNull(entityType, "events="+events); + assertNotNull(metadata, "events="+events); + assertEquals(appEvent.getState(), Lifecycle.STARTING, "events="+events); + }}); + + + // Remove the listener; will get no more notifications + listener.clearEvents(); + mgmt.getUsageManager().removeUsageListener(listener); + + app.start(ImmutableList.<Location>of()); + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + assertEquals(events.size(), 0, "events="+events); + }}); + } + + @Test + public void testAddAndRemoveUsageListener() throws Exception { + final RecordingUsageListener listener = new RecordingUsageListener(); + mgmt.getUsageManager().addUsageListener(listener); + + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + app.setCatalogItemId("testCatalogItem"); + app.start(ImmutableList.<Location>of()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getApplicationEvents(); + assertEquals(events.size(), 2, "events="+events); // expect STARTING and RUNNING + ApplicationMetadata appMetadata = (ApplicationMetadata) events.get(0).get(1); + ApplicationEvent appEvent = (ApplicationEvent) events.get(0).get(2); + + assertEquals(appMetadata.getApplication(), app, "events="+events); + assertTrue(appMetadata.getApplication() instanceof EntityProxy, "events="+events); + assertEquals(appMetadata.getApplicationId(), app.getId(), "events="+events); + assertNotNull(appMetadata.getApplicationName(), "events="+events); + assertEquals(appMetadata.getCatalogItemId(), app.getCatalogItemId(), "events="+events); + assertNotNull(appMetadata.getEntityType(), "events="+events); + assertNotNull(appMetadata.getMetadata(), "events="+events); + assertEquals(appEvent.getState(), Lifecycle.STARTING, "events="+events); + }}); + + + // Remove the listener; will get no more notifications + listener.clearEvents(); + mgmt.getUsageManager().removeUsageListener(listener); + + app.start(ImmutableList.<Location>of()); + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + assertEquals(events.size(), 0, "events="+events); + }}); + } + + @Test + public void testUsageIncludesStartAndStopEvents() { + // Start event + long preStart = System.currentTimeMillis(); + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + app.start(ImmutableList.<Location>of()); + long postStart = System.currentTimeMillis(); + + Set<ApplicationUsage> usages1 = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + ApplicationUsage usage1 = Iterables.getOnlyElement(usages1); + assertApplicationUsage(usage1, app); + assertApplicationEvent(usage1.getEvents().get(0), Lifecycle.STARTING, preStart, postStart); + assertApplicationEvent(usage1.getEvents().get(1), Lifecycle.RUNNING, preStart, postStart); + + // Stop events + long preStop = System.currentTimeMillis(); + app.stop(); + long postStop = System.currentTimeMillis(); + + Set<ApplicationUsage> usages2 = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + ApplicationUsage usage2 = Iterables.getOnlyElement(usages2); + assertApplicationUsage(usage2, app); + assertApplicationEvent(usage2.getEvents().get(2), Lifecycle.STOPPING, preStop, postStop); + assertApplicationEvent(usage2.getEvents().get(3), Lifecycle.STOPPED, preStop, postStop); + //Apps unmanage themselves on stop + assertApplicationEvent(usage2.getEvents().get(4), Lifecycle.DESTROYED, preStop, postStop); + + assertFalse(mgmt.getEntityManager().isManaged(app), "App should already be unmanaged"); + + Set<ApplicationUsage> usages3 = mgmt.getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + ApplicationUsage usage3 = Iterables.getOnlyElement(usages3); + assertApplicationUsage(usage3, app); + + assertEquals(usage3.getEvents().size(), 5, "usage="+usage3); + } + + private void assertApplicationUsage(ApplicationUsage usage, Application expectedApp) { + assertEquals(usage.getApplicationId(), expectedApp.getId()); + assertEquals(usage.getApplicationName(), expectedApp.getDisplayName()); + assertEquals(usage.getEntityType(), expectedApp.getEntityType().getName()); + } + + private void assertApplicationEvent(ApplicationEvent event, Lifecycle expectedState, long preEvent, long postEvent) { + // Saw times differ by 1ms - perhaps different threads calling currentTimeMillis() can get out-of-order times?! + final int TIMING_GRACE = 5; + + assertEquals(event.getState(), expectedState); + long eventTime = event.getDate().getTime(); + if (eventTime < (preEvent - TIMING_GRACE) || eventTime > (postEvent + TIMING_GRACE)) { + fail("for "+expectedState+": event=" + Time.makeDateString(eventTime) + "("+eventTime + "); " + + "pre=" + Time.makeDateString(preEvent) + " ("+preEvent+ "); " + + "post=" + Time.makeDateString(postEvent) + " ("+postEvent + ")"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java new file mode 100644 index 0000000..44760ac --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/LocationUsageTrackingTest.java @@ -0,0 +1,210 @@ +/* + * 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.entity.software.base.test.core.mgmt.usage; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.core.mgmt.internal.UsageListener.LocationMetadata; +import org.apache.brooklyn.core.mgmt.usage.LocationUsage; +import org.apache.brooklyn.core.mgmt.usage.LocationUsage.LocationEvent; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.time.Time; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class LocationUsageTrackingTest extends BrooklynAppUnitTestSupport { + + private DynamicLocalhostMachineProvisioningLocation loc; + + @BeforeMethod(alwaysRun = true) + @Override + public void setUp() throws Exception { + super.setUp(); + loc = mgmt.getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class)); + } + + @Test + public void testUsageInitiallyEmpty() { + Set<LocationUsage> usage = mgmt.getUsageManager().getLocationUsage(Predicates.alwaysTrue()); + assertEquals(usage, ImmutableSet.of()); + } + + @Test + @SuppressWarnings("deprecation") + public void testAddAndRemoveLegacyUsageListener() throws Exception { + final RecordingLegacyUsageListener listener = new RecordingLegacyUsageListener(); + mgmt.getUsageManager().addUsageListener(listener); + + app.createAndManageChild(EntitySpec.create(SoftwareProcessEntityTest.MyService.class)); + app.start(ImmutableList.of(loc)); + final SshMachineLocation machine = Iterables.getOnlyElement(loc.getAllMachines()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + String locId = (String) events.get(0).get(1); + LocationEvent locEvent = (LocationEvent) events.get(0).get(3); + Map<?,?> metadata = (Map<?, ?>) events.get(0).get(2); + + assertEquals(events.size(), 1, "events="+events); + assertEquals(locId, machine.getId(), "events="+events); + assertNotNull(metadata, "events="+events); + assertEquals(locEvent.getApplicationId(), app.getId(), "events="+events); + assertEquals(locEvent.getState(), Lifecycle.CREATED, "events="+events); + }}); + + // Remove the listener; will get no more notifications + listener.clearEvents(); + mgmt.getUsageManager().removeUsageListener(listener); + + app.stop(); + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + assertEquals(events.size(), 0, "events="+events); + }}); + } + + @Test + public void testAddAndRemoveUsageListener() throws Exception { + final RecordingUsageListener listener = new RecordingUsageListener(); + mgmt.getUsageManager().addUsageListener(listener); + + app.createAndManageChild(EntitySpec.create(SoftwareProcessEntityTest.MyService.class)); + app.start(ImmutableList.of(loc)); + final SshMachineLocation machine = Iterables.getOnlyElement(loc.getAllMachines()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + LocationMetadata locMetadata = (LocationMetadata) events.get(0).get(1); + LocationEvent locEvent = (LocationEvent) events.get(0).get(2); + + assertEquals(events.size(), 1, "events="+events); + assertEquals(locMetadata.getLocation(), machine, "events="+events); + assertEquals(locMetadata.getLocationId(), machine.getId(), "events="+events); + assertNotNull(locMetadata.getMetadata(), "events="+events); + assertEquals(locEvent.getApplicationId(), app.getId(), "events="+events); + assertEquals(locEvent.getState(), Lifecycle.CREATED, "events="+events); + }}); + + // Remove the listener; will get no more notifications + listener.clearEvents(); + mgmt.getUsageManager().removeUsageListener(listener); + + app.stop(); + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + List<List<?>> events = listener.getLocationEvents(); + assertEquals(events.size(), 0, "events="+events); + }}); + } + + @Test + public void testUsageIncludesStartAndStopEvents() { + SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(EntitySpec.create(SoftwareProcessEntityTest.MyService.class)); + + // Start the app; expect record of location in use + long preStart = System.currentTimeMillis(); + app.start(ImmutableList.of(loc)); + long postStart = System.currentTimeMillis(); + SshMachineLocation machine = Iterables.getOnlyElement(loc.getAllMachines()); + + Set<LocationUsage> usages1 = mgmt.getUsageManager().getLocationUsage(Predicates.alwaysTrue()); + LocationUsage usage1 = Iterables.getOnlyElement(usages1); + assertLocationUsage(usage1, machine); + assertLocationEvent(usage1.getEvents().get(0), entity, Lifecycle.CREATED, preStart, postStart); + + // Stop the app; expect record of location no longer in use + long preStop = System.currentTimeMillis(); + app.stop(); + long postStop = System.currentTimeMillis(); + + Set<LocationUsage> usages2 = mgmt.getUsageManager().getLocationUsage(Predicates.alwaysTrue()); + LocationUsage usage2 = Iterables.getOnlyElement(usages2); + assertLocationUsage(usage2, machine); + assertLocationEvent(usage2.getEvents().get(1), app.getApplicationId(), entity.getId(), entity.getEntityType().getName(), Lifecycle.DESTROYED, preStop, postStop); + + assertEquals(usage2.getEvents().size(), 2, "usage="+usage2); + } + + public static class DynamicLocalhostMachineProvisioningLocation extends LocalhostMachineProvisioningLocation { + private static final long serialVersionUID = 4822009936654077946L; + + @Override + public SshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException { + System.out.println("called DynamicLocalhostMachineProvisioningLocation.obtain"); + return super.obtain(flags); + } + + @Override + public void release(SshMachineLocation machine) { + System.out.println("called DynamicLocalhostMachineProvisioningLocation.release"); + super.release(machine); + super.machines.remove(machine); + super.removeChild(machine); + } + } + + private void assertLocationUsage(LocationUsage usage, Location expectedLoc) { + assertEquals(usage.getLocationId(), expectedLoc.getId(), "usage="+usage); + assertNotNull(usage.getMetadata(), "usage="+usage); + } + + private void assertLocationEvent(LocationEvent event, Entity expectedEntity, Lifecycle expectedState, long preEvent, long postEvent) { + assertLocationEvent(event, expectedEntity.getApplicationId(), expectedEntity.getId(), expectedEntity.getEntityType().getName(), expectedState, preEvent, postEvent); + } + + private void assertLocationEvent(LocationEvent event, String expectedAppId, String expectedEntityId, String expectedEntityType, Lifecycle expectedState, long preEvent, long postEvent) { + // Saw times differ by 1ms - perhaps different threads calling currentTimeMillis() can get out-of-order times?! + final int TIMING_GRACE = 5; + + assertEquals(event.getApplicationId(), expectedAppId); + assertEquals(event.getEntityId(), expectedEntityId); + assertEquals(event.getEntityType(), expectedEntityType); + assertEquals(event.getState(), expectedState); + long eventTime = event.getDate().getTime(); + if (eventTime < (preEvent - TIMING_GRACE) || eventTime > (postEvent + TIMING_GRACE)) { + fail("for "+expectedState+": event=" + Time.makeDateString(eventTime) + "("+eventTime + "); " + + "pre=" + Time.makeDateString(preEvent) + " ("+preEvent+ "); " + + "post=" + Time.makeDateString(postEvent) + " ("+postEvent + ")"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingLegacyUsageListener.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingLegacyUsageListener.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingLegacyUsageListener.java new file mode 100644 index 0000000..8e2e489 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingLegacyUsageListener.java @@ -0,0 +1,70 @@ +/* + * 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.entity.software.base.test.core.mgmt.usage; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent; +import org.apache.brooklyn.core.mgmt.usage.LocationUsage.LocationEvent; +import org.apache.brooklyn.util.collections.MutableList; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +@Deprecated +public class RecordingLegacyUsageListener implements org.apache.brooklyn.core.mgmt.internal.UsageManager.UsageListener { + + private final List<List<?>> events = Lists.newCopyOnWriteArrayList(); + + @Override + public void onApplicationEvent(String applicationId, String applicationName, String entityType, + String catalogItemId, Map<String, String> metadata, ApplicationEvent event) { + events.add(MutableList.of("application", applicationId, applicationName, entityType, catalogItemId, metadata, event)); + } + + @Override + public void onLocationEvent(String locationId, Map<String, String> metadata, LocationEvent event) { + events.add(MutableList.of("location", locationId, metadata, event)); + } + + public void clearEvents() { + events.clear(); + } + + public List<List<?>> getEvents() { + return ImmutableList.copyOf(events); + } + + public List<List<?>> getLocationEvents() { + List<List<?>> result = Lists.newArrayList(); + for (List<?> event : events) { + if (event.get(0).equals("location")) result.add(event); + } + return ImmutableList.copyOf(result); + } + + public List<List<?>> getApplicationEvents() { + List<List<?>> result = Lists.newArrayList(); + for (List<?> event : events) { + if (event.get(0).equals("application")) result.add(event); + } + return ImmutableList.copyOf(result); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingUsageListener.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingUsageListener.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingUsageListener.java new file mode 100644 index 0000000..399b575 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/RecordingUsageListener.java @@ -0,0 +1,67 @@ +/* + * 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.entity.software.base.test.core.mgmt.usage; + +import java.util.List; + +import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent; +import org.apache.brooklyn.core.mgmt.usage.LocationUsage.LocationEvent; +import org.apache.brooklyn.util.collections.MutableList; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class RecordingUsageListener implements org.apache.brooklyn.core.mgmt.internal.UsageListener { + + private final List<List<?>> events = Lists.newCopyOnWriteArrayList(); + + @Override + public void onApplicationEvent(ApplicationMetadata app, ApplicationEvent event) { + events.add(MutableList.of("application", app, event)); + } + + @Override + public void onLocationEvent(LocationMetadata loc, LocationEvent event) { + events.add(MutableList.of("location", loc, event)); + } + + public void clearEvents() { + events.clear(); + } + + public List<List<?>> getEvents() { + return ImmutableList.copyOf(events); + } + + public List<List<?>> getLocationEvents() { + List<List<?>> result = Lists.newArrayList(); + for (List<?> event : events) { + if (event.get(0).equals("location")) result.add(event); + } + return ImmutableList.copyOf(result); + } + + public List<List<?>> getApplicationEvents() { + List<List<?>> result = Lists.newArrayList(); + for (List<?> event : events) { + if (event.get(0).equals("application")) result.add(event); + } + return ImmutableList.copyOf(result); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/UsageListenerTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/UsageListenerTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/UsageListenerTest.java new file mode 100644 index 0000000..518f975 --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/core/mgmt/usage/UsageListenerTest.java @@ -0,0 +1,142 @@ +/* + * 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.entity.software.base.test.core.mgmt.usage; + +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.apache.brooklyn.test.Asserts; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.mgmt.internal.UsageManager; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Entities; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +public class UsageListenerTest { + + // Also see {Application|Location}UsageTrackingTest for listener functionality + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationUsageTrackingTest.class); + + protected TestApplication app; + protected ManagementContextInternal mgmt; + + protected boolean shouldSkipOnBoxBaseDirResolution() { + return true; + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + RecordingStaticLegacyUsageListener.clearInstances(); + RecordingStaticUsageListener.clearInstances(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + try { + if (mgmt != null) Entities.destroyAll(mgmt); + } catch (Throwable t) { + LOG.error("Caught exception in tearDown method", t); + } finally { + mgmt = null; + RecordingStaticLegacyUsageListener.clearInstances(); + RecordingStaticUsageListener.clearInstances(); + } + } + + @Test + public void testAddLegacyUsageListenerViaProperties() throws Exception { + BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty(); + brooklynProperties.put(UsageManager.USAGE_LISTENERS, RecordingStaticLegacyUsageListener.class.getName()); + mgmt = LocalManagementContextForTests.newInstance(brooklynProperties); + + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + app.start(ImmutableList.<Location>of()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = RecordingStaticLegacyUsageListener.getInstance().getApplicationEvents(); + assertTrue(events.size() > 0, "events="+events); // expect some events + }}); + } + + @Test + public void testAddUsageListenerViaProperties() throws Exception { + BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty(); + brooklynProperties.put(UsageManager.USAGE_LISTENERS, RecordingStaticUsageListener.class.getName()); + mgmt = LocalManagementContextForTests.newInstance(brooklynProperties); + + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + app.start(ImmutableList.<Location>of()); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + List<List<?>> events = RecordingStaticUsageListener.getInstance().getApplicationEvents(); + assertTrue(events.size() > 0, "events="+events); // expect some events + }}); + } + + public static class RecordingStaticLegacyUsageListener extends RecordingLegacyUsageListener implements org.apache.brooklyn.core.mgmt.internal.UsageManager.UsageListener { + private static final List<RecordingStaticLegacyUsageListener> STATIC_INSTANCES = Lists.newCopyOnWriteArrayList(); + + public static RecordingStaticLegacyUsageListener getInstance() { + return Iterables.getOnlyElement(STATIC_INSTANCES); + } + + public static void clearInstances() { + STATIC_INSTANCES.clear(); + } + + public RecordingStaticLegacyUsageListener() { + // Bad to leak a ref to this before constructor finished, but we'll live with it because + // it's just test code! + STATIC_INSTANCES.add(this); + } + } + + public static class RecordingStaticUsageListener extends RecordingUsageListener implements org.apache.brooklyn.core.mgmt.internal.UsageListener { + private static final List<RecordingStaticUsageListener> STATIC_INSTANCES = Lists.newCopyOnWriteArrayList(); + + public static RecordingStaticUsageListener getInstance() { + return Iterables.getOnlyElement(STATIC_INSTANCES); + } + + public static void clearInstances() { + STATIC_INSTANCES.clear(); + } + + public RecordingStaticUsageListener() { + // Bad to leak a ref to this before constructor finished, but we'll live with it because + // it's just test code! + STATIC_INSTANCES.add(this); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/driver/MockSshDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/driver/MockSshDriver.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/driver/MockSshDriver.java new file mode 100644 index 0000000..9c92b5b --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/driver/MockSshDriver.java @@ -0,0 +1,72 @@ +/* + * 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.entity.software.base.test.driver; + +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.entity.software.base.SoftwareProcessDriver; +import org.apache.brooklyn.location.basic.SshMachineLocation; + +public class MockSshDriver implements SoftwareProcessDriver { + + public int numCallsToRunApp = 0; + private final EntityLocal entity; + private final SshMachineLocation machine; + + public MockSshDriver(EntityLocal entity, SshMachineLocation machine) { + this.entity = entity; + this.machine = machine; + } + + @Override + public void start() { + numCallsToRunApp++; + } + + @Override + public boolean isRunning() { + return numCallsToRunApp>0; + } + + @Override + public EntityLocal getEntity() { + return entity; + } + + @Override + public Location getLocation() { + return machine; + } + + @Override + public void rebind() { + } + + @Override + public void stop() { + } + + @Override + public void restart() { + } + + @Override + public void kill() { + } +}
