You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/08/19 13:09:26 UTC
[08/72] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply
org.apache package prefix to software-base, tidying package names,
and moving a few sensory things to core
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareEffectorTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareEffectorTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareEffectorTest.java
new file mode 100644
index 0000000..b341b6e
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareEffectorTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.Arrays;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.effector.core.Effectors;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks.SshEffectorBody;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+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 org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import com.google.common.base.Throwables;
+
+public class SoftwareEffectorTest {
+
+ private static final Logger log = LoggerFactory.getLogger(SoftwareEffectorTest.class);
+
+ TestApplication app;
+ ManagementContext mgmt;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setup() throws Exception {
+ app = TestApplication.Factory.newManagedInstanceForTests();
+ mgmt = app.getManagementContext();
+
+ LocalhostMachineProvisioningLocation lhc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+ SshMachineLocation lh = lhc.obtain();
+ app.start(Arrays.asList(lh));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (mgmt != null) Entities.destroyAll(mgmt);
+ mgmt = null;
+ }
+
+ public static final Effector<String> GET_REMOTE_DATE_1 = Effectors.effector(String.class, "getRemoteDate")
+ .description("retrieves the date from the remote machine")
+ .impl(new SshEffectorBody<String>() {
+ public String call(ConfigBag parameters) {
+ queue( ssh("date").requiringZeroAndReturningStdout() );
+ return last(String.class);
+ }
+ })
+ .build();
+
+ public static final Effector<String> GET_REMOTE_DATE_2 = Effectors.effector(GET_REMOTE_DATE_1)
+ // Just get year to confirm implementation is different
+ .description("retrieves the year from the remote machine")
+ .impl(SshEffectorTasks.ssh("date +%Y").requiringZeroAndReturningStdout())
+ .build();
+
+ // TODO revisit next two tests before end 2019 ;)
+
+ @Test(groups="Integration")
+ public void testSshDateEffector1() {
+ Task<String> call = Entities.invokeEffector(app, app, GET_REMOTE_DATE_1);
+ log.info("ssh date 1 gives: "+call.getUnchecked());
+ Assert.assertTrue(call.getUnchecked().indexOf("201") > 0);
+ }
+
+ @Test(groups="Integration")
+ public void testSshDateEffector2() {
+ Task<String> call = Entities.invokeEffector(app, app, GET_REMOTE_DATE_2);
+ log.info("ssh date 2 gives: "+call.getUnchecked());
+ Assert.assertTrue(call.getUnchecked().indexOf("201") == 0);
+ }
+
+ public static final String COMMAND_THAT_DOES_NOT_EXIST = "blah_blah_blah_command_DOES_NOT_EXIST";
+
+ @Test(groups="Integration")
+ public void testBadExitCodeCaught() {
+ Task<Void> call = Entities.invokeEffector(app, app, Effectors.effector(Void.class, "badExitCode")
+ .impl(new SshEffectorBody<Void>() {
+ public Void call(ConfigBag parameters) {
+ queue( ssh(COMMAND_THAT_DOES_NOT_EXIST).requiringZeroAndReturningStdout() );
+ return null;
+ }
+ }).build() );
+ try {
+ Object result = call.getUnchecked();
+ Assert.fail("ERROR: should have failed earlier in this test, instead got successful task result "+result+" from "+call);
+ } catch (Exception e) {
+ Throwable root = Throwables.getRootCause(e);
+ if (!(root instanceof IllegalStateException)) Assert.fail("Should have failed with IAE, but got: "+root);
+ if (root.getMessage()==null || root.getMessage().indexOf("exit code")<=0)
+ Assert.fail("Should have failed with 'exit code' message, but got: "+root);
+ // test passed
+ return;
+ }
+ }
+
+ @Test(groups="Integration")
+ public void testBadExitCodeCaughtAndStdErrAvailable() {
+ final ProcessTaskWrapper<?>[] sshTasks = new ProcessTaskWrapper[1];
+
+ Task<Void> call = Entities.invokeEffector(app, app, Effectors.effector(Void.class, "badExitCode")
+ .impl(new SshEffectorBody<Void>() {
+ public Void call(ConfigBag parameters) {
+ sshTasks[0] = queue( ssh(COMMAND_THAT_DOES_NOT_EXIST).requiringExitCodeZero() );
+ return null;
+ }
+ }).build() );
+ call.blockUntilEnded();
+ Assert.assertTrue(call.isError());
+ log.info("stderr gives: "+new String(sshTasks[0].getStderr()));
+ Assert.assertTrue(new String(sshTasks[0].getStderr()).indexOf(COMMAND_THAT_DOES_NOT_EXIST) >= 0);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityLatchTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityLatchTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityLatchTest.java
new file mode 100644
index 0000000..e743b24
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityLatchTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Entity;
+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.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+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.SoftwareProcessEntityTest.MyService;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.SimulatedDriver;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 org.apache.brooklyn.sensor.core.DependentConfiguration;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.core.task.TaskInternal;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+
+public class SoftwareProcessEntityLatchTest extends BrooklynAppUnitTestSupport {
+
+ // NB: These tests don't actually require ssh to localhost -- only that 'localhost' resolves.
+
+ @SuppressWarnings("unused")
+ private static final Logger LOG = LoggerFactory.getLogger(SoftwareProcessEntityLatchTest.class);
+
+ private SshMachineLocation machine;
+ private FixedListMachineProvisioningLocation<SshMachineLocation> loc;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ loc = getLocation();
+ }
+
+ @SuppressWarnings("unchecked")
+ private FixedListMachineProvisioningLocation<SshMachineLocation> getLocation() {
+ FixedListMachineProvisioningLocation<SshMachineLocation> loc = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class));
+ machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure("address", "localhost"));
+ loc.addMachine(machine);
+ return loc;
+ }
+
+ @Test
+ public void testStartLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.START_LATCH, ImmutableList.<String>of());
+ }
+
+ @Test
+ public void testSetupLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.SETUP_LATCH, ImmutableList.<String>of());
+ }
+
+ @Test
+ public void testIntallResourcesLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.INSTALL_RESOURCES_LATCH, ImmutableList.of("setup"));
+ }
+
+ @Test
+ public void testInstallLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.INSTALL_LATCH, ImmutableList.of("setup", "copyInstallResources"));
+ }
+
+ @Test
+ public void testCustomizeLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.CUSTOMIZE_LATCH, ImmutableList.of("setup", "copyInstallResources", "install"));
+ }
+
+ @Test
+ public void testRuntimeResourcesLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.RUNTIME_RESOURCES_LATCH, ImmutableList.of("setup", "copyInstallResources", "install", "customize"));
+ }
+
+ @Test
+ public void testLaunchLatchBlocks() throws Exception {
+ runTestLatchBlocks(SoftwareProcess.LAUNCH_LATCH, ImmutableList.of("setup", "copyInstallResources", "install", "customize", "copyRuntimeResources"));
+ }
+
+ protected void runTestLatchBlocks(final ConfigKey<Boolean> latch, List<String> preLatchEvents) throws Exception {
+ final BasicEntity triggerEntity = app.createAndManageChild(EntitySpec.create(BasicEntity.class));
+ final MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class)
+ .configure(latch, DependentConfiguration.attributeWhenReady(triggerEntity, Attributes.SERVICE_UP)));
+
+ final Task<Void> task = Entities.invokeEffector(app, app, MyService.START, ImmutableMap.of("locations", ImmutableList.of(loc)));
+
+ assertEffectorBlockingDetailsEventually(entity, "Waiting for config "+latch.getName());
+ assertDriverEventsEquals(entity, preLatchEvents);
+
+ assertFalse(task.isDone());
+ ((EntityLocal)triggerEntity).setAttribute(Attributes.SERVICE_UP, true);
+ task.get(Duration.THIRTY_SECONDS);
+ assertDriverEventsEquals(entity, ImmutableList.of("setup", "copyInstallResources", "install", "customize", "copyRuntimeResources", "launch"));
+ }
+
+ private void assertDriverEventsEquals(MyService entity, List<String> expectedEvents) {
+ List<String> events = ((SimulatedDriver)entity.getDriver()).events;
+ assertEquals(events, expectedEvents, "events="+events);
+ }
+
+ private void assertEffectorBlockingDetailsEventually(final Entity entity, final String blockingDetailsSnippet) {
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ Task<?> entityTask = Iterables.getOnlyElement(mgmt.getExecutionManager().getTasksWithAllTags(ImmutableList.of(BrooklynTaskTags.EFFECTOR_TAG, BrooklynTaskTags.tagForContextEntity(entity))));
+ String blockingDetails = getBlockingDetails(entityTask);
+ assertTrue(blockingDetails.contains(blockingDetailsSnippet));
+ }});
+ }
+
+ private String getBlockingDetails(Task<?> task) {
+ List<TaskInternal<?>> taskChain = Lists.newArrayList();
+ TaskInternal<?> taskI = (TaskInternal<?>) task;
+ while (taskI != null) {
+ taskChain.add(taskI);
+ if (taskI.getBlockingDetails() != null) {
+ return taskI.getBlockingDetails();
+ }
+ taskI = (TaskInternal<?>) taskI.getBlockingTask();
+ }
+ throw new IllegalStateException("No blocking details for "+task+" (walked task chain "+taskChain+")");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityRebindTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityRebindTest.java
new file mode 100644
index 0000000..475cb09
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityRebindTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+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.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic.ServiceProblemsLogic;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.MyService;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.AbstractLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class SoftwareProcessEntityRebindTest extends BrooklynAppUnitTestSupport {
+
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private TestApplication newApp;
+ private ManagementContext newManagementContext;
+ private MyService origE;
+ private File mementoDir;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ mementoDir = Files.createTempDir();
+ mgmt = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
+ super.setUp();
+ }
+
+ @AfterMethod(alwaysRun=true)
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ if (newApp != null) Entities.destroyAll(newApp.getManagementContext());
+ if (newManagementContext != null) Entities.destroyAll(newManagementContext);
+ if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+ }
+
+ @Test
+ public void testReleasesLocationOnStopAfterRebinding() throws Exception {
+ origE = app.createAndManageChild(EntitySpec.create(MyService.class));
+
+ MyProvisioningLocation origLoc = mgmt.getLocationManager().createLocation(LocationSpec.create(MyProvisioningLocation.class)
+ .displayName("mylocname"));
+ app.start(ImmutableList.of(origLoc));
+ assertEquals(origLoc.inUseCount.get(), 1);
+
+ newApp = (TestApplication) rebind();
+ MyProvisioningLocation newLoc = (MyProvisioningLocation) Iterables.getOnlyElement(newApp.getLocations());
+ assertEquals(newLoc.inUseCount.get(), 1);
+
+ newApp.stop();
+ assertEquals(newLoc.inUseCount.get(), 0);
+ }
+
+ @Test
+ public void testCreatesDriverAfterRebind() throws Exception {
+ origE = app.createAndManageChild(EntitySpec.create(MyService.class));
+ //the entity skips enricher initialization, do it explicitly
+ origE.addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
+
+ MyProvisioningLocation origLoc = mgmt.getLocationManager().createLocation(LocationSpec.create(MyProvisioningLocation.class)
+ .displayName("mylocname"));
+ app.start(ImmutableList.of(origLoc));
+ assertEquals(origE.getAttribute(Attributes.SERVICE_STATE_EXPECTED).getState(), Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(origE, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ ServiceProblemsLogic.updateProblemsIndicator((EntityLocal)origE, "test", "fire");
+ EntityTestUtils.assertAttributeEqualsEventually(origE, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+ newApp = (TestApplication) rebind();
+ MyService newE = (MyService) Iterables.getOnlyElement(newApp.getChildren());
+ assertTrue(newE.getDriver() != null, "driver should be initialized");
+ }
+
+ @Test
+ public void testDoesNotCreateDriverAfterRebind() throws Exception {
+ origE = app.createAndManageChild(EntitySpec.create(MyService.class));
+ //the entity skips enricher initialization, do it explicitly
+ origE.addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
+
+ MyProvisioningLocation origLoc = mgmt.getLocationManager().createLocation(LocationSpec.create(MyProvisioningLocation.class)
+ .displayName("mylocname"));
+ app.start(ImmutableList.of(origLoc));
+ assertEquals(origE.getAttribute(Attributes.SERVICE_STATE_EXPECTED).getState(), Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(origE, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ ServiceStateLogic.setExpectedState(origE, Lifecycle.ON_FIRE);
+ EntityTestUtils.assertAttributeEqualsEventually(origE, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+ newApp = (TestApplication) rebind();
+ MyService newE = (MyService) Iterables.getOnlyElement(newApp.getChildren());
+ assertNull(newE.getDriver(), "driver should not be initialized because entity is in a permanent failure");
+ }
+
+ private TestApplication rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(app);
+ TestApplication result = (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ newManagementContext = result.getManagementContext();
+ return result;
+ }
+
+ public static class MyProvisioningLocation extends AbstractLocation implements MachineProvisioningLocation<SshMachineLocation> {
+ private static final long serialVersionUID = 1L;
+
+ @SetFromFlag(defaultVal="0")
+ AtomicInteger inUseCount;
+
+ public MyProvisioningLocation() {
+ }
+
+ @Override
+ public MachineProvisioningLocation<SshMachineLocation> newSubLocation(Map<?, ?> newFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SshMachineLocation obtain(Map flags) throws NoMachinesAvailableException {
+ inUseCount.incrementAndGet();
+ return getManagementContext().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .parent(this)
+ .configure("address","localhost"));
+ }
+
+ @Override
+ public void release(SshMachineLocation machine) {
+ inUseCount.decrementAndGet();
+ }
+
+ @Override
+ public Map getProvisioningFlags(Collection tags) {
+ return Collections.emptyMap();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityTest.java
new file mode 100644
index 0000000..0a6bd26
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityTest.java
@@ -0,0 +1,798 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.effector.core.Effectors;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.core.BrooklynConfigKeys;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.core.EntityInternal;
+import org.apache.brooklyn.entity.drivers.BasicEntityDriverManager;
+import org.apache.brooklyn.entity.drivers.ReflectiveEntityDriverFactory;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+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.entity.software.base.SoftwareProcess.RestartSoftwareParameters;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess.RestartSoftwareParameters.RestartMachineMode;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters.StopMode;
+import org.apache.brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.sensor.core.PortAttributeSensorAndConfigKey;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
+import org.apache.brooklyn.util.net.UserAndHostAndPort;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.jclouds.util.Throwables2;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import org.apache.brooklyn.location.basic.FixedListMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.Locations;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+
+public class SoftwareProcessEntityTest extends BrooklynAppUnitTestSupport {
+
+ // NB: These tests don't actually require ssh to localhost -- only that 'localhost' resolves.
+
+ private static final Logger LOG = LoggerFactory.getLogger(SoftwareProcessEntityTest.class);
+
+ private SshMachineLocation machine;
+ private FixedListMachineProvisioningLocation<SshMachineLocation> loc;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ loc = getLocation();
+ }
+
+ @SuppressWarnings("unchecked")
+ private FixedListMachineProvisioningLocation<SshMachineLocation> getLocation() {
+ FixedListMachineProvisioningLocation<SshMachineLocation> loc = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class));
+ machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure("address", "localhost"));
+ loc.addMachine(machine);
+ return loc;
+ }
+
+ @Test
+ public void testSetsMachineAttributes() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+
+ assertEquals(entity.getAttribute(SoftwareProcess.HOSTNAME), machine.getAddress().getHostName());
+ assertEquals(entity.getAttribute(SoftwareProcess.ADDRESS), machine.getAddress().getHostAddress());
+ assertEquals(entity.getAttribute(Attributes.SSH_ADDRESS), UserAndHostAndPort.fromParts(machine.getUser(), machine.getAddress().getHostName(), machine.getPort()));
+ assertEquals(entity.getAttribute(SoftwareProcess.PROVISIONING_LOCATION), loc);
+ }
+
+ @Test
+ public void testProcessTemplateWithExtraSubstitutions() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ SimulatedDriver driver = (SimulatedDriver) entity.getDriver();
+ Map<String,String> substitutions = MutableMap.of("myname","peter");
+ String result = driver.processTemplate("/org/apache/brooklyn/entity/software/base/template_with_extra_substitutions.txt",substitutions);
+ Assert.assertTrue(result.contains("peter"));
+ }
+
+ @Test
+ public void testInstallDirAndRunDir() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class)
+ .configure(BrooklynConfigKeys.ONBOX_BASE_DIR, "/tmp/brooklyn-foo"));
+
+ entity.start(ImmutableList.of(loc));
+
+ Assert.assertEquals(entity.getAttribute(SoftwareProcess.INSTALL_DIR), "/tmp/brooklyn-foo/installs/MyService");
+ Assert.assertEquals(entity.getAttribute(SoftwareProcess.RUN_DIR), "/tmp/brooklyn-foo/apps/"+entity.getApplicationId()+"/entities/MyService_"+entity.getId());
+ }
+
+ @Test
+ public void testInstallDirAndRunDirUsingTilde() throws Exception {
+ String dataDirName = ".brooklyn-foo"+Strings.makeRandomId(4);
+ String dataDir = "~/"+dataDirName;
+ String resolvedDataDir = Os.mergePaths(Os.home(), dataDirName);
+
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class)
+ .configure(BrooklynConfigKeys.ONBOX_BASE_DIR, dataDir));
+
+ entity.start(ImmutableList.of(loc));
+
+ Assert.assertEquals(Os.nativePath(entity.getAttribute(SoftwareProcess.INSTALL_DIR)),
+ Os.nativePath(Os.mergePaths(resolvedDataDir, "installs/MyService")));
+ Assert.assertEquals(Os.nativePath(entity.getAttribute(SoftwareProcess.RUN_DIR)),
+ Os.nativePath(Os.mergePaths(resolvedDataDir, "apps/"+entity.getApplicationId()+"/entities/MyService_"+entity.getId())));
+ }
+
+ protected <T extends MyService> void doStartAndCheckVersion(Class<T> type, String expectedLabel, ConfigBag config) {
+ MyService entity = app.createAndManageChild(EntitySpec.create(type)
+ .configure(BrooklynConfigKeys.ONBOX_BASE_DIR, "/tmp/brooklyn-foo")
+ .configure(config.getAllConfigAsConfigKeyMap()));
+ entity.start(ImmutableList.of(loc));
+ Assert.assertEquals(entity.getAttribute(SoftwareProcess.INSTALL_DIR), "/tmp/brooklyn-foo/installs/"
+ + expectedLabel);
+ }
+
+ @Test
+ public void testCustomInstallDir0() throws Exception {
+ doStartAndCheckVersion(MyService.class, "MyService", ConfigBag.newInstance());
+ }
+ @Test
+ public void testCustomInstallDir1() throws Exception {
+ doStartAndCheckVersion(MyService.class, "MyService_9.9.8", ConfigBag.newInstance()
+ .configure(SoftwareProcess.SUGGESTED_VERSION, "9.9.8"));
+ }
+ @Test
+ public void testCustomInstallDir2() throws Exception {
+ doStartAndCheckVersion(MyService.class, "MySvc_998", ConfigBag.newInstance()
+ .configure(SoftwareProcess.INSTALL_UNIQUE_LABEL, "MySvc_998"));
+ }
+ @Test
+ public void testCustomInstallDir3() throws Exception {
+ doStartAndCheckVersion(MyServiceWithVersion.class, "MyServiceWithVersion_9.9.9", ConfigBag.newInstance());
+ }
+ @Test
+ public void testCustomInstallDir4() throws Exception {
+ doStartAndCheckVersion(MyServiceWithVersion.class, "MyServiceWithVersion_9.9.7", ConfigBag.newInstance()
+ .configure(SoftwareProcess.SUGGESTED_VERSION, "9.9.7"));
+ }
+ @Test
+ public void testCustomInstallDir5() throws Exception {
+ doStartAndCheckVersion(MyServiceWithVersion.class, "MyServiceWithVersion_9.9.9_NaCl", ConfigBag.newInstance()
+ .configure(ConfigKeys.newStringConfigKey("salt"), "NaCl"));
+ }
+
+ @Test
+ public void testBasicSoftwareProcessEntityLifecycle() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+ Assert.assertTrue(d.isRunning());
+ entity.stop();
+ Assert.assertEquals(d.events, ImmutableList.of("setup", "copyInstallResources", "install", "customize", "copyRuntimeResources", "launch", "stop"));
+ assertFalse(d.isRunning());
+ }
+
+ @Test
+ public void testBasicSoftwareProcessRestarts() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+ Assert.assertTrue(d.isRunning());
+
+ // this will cause restart to fail if it attempts to replace the machine
+ loc.removeMachine(Locations.findUniqueSshMachineLocation(entity.getLocations()).get());
+
+ // with defaults, it won't reboot machine
+ d.events.clear();
+ entity.restart();
+ assertEquals(d.events, ImmutableList.of("stop", "launch"));
+
+ // but here, it will try to reboot, and fail because there is no machine available
+ TaskAdaptable<Void> t1 = Entities.submit(entity, Effectors.invocation(entity, Startable.RESTART,
+ ConfigBag.newInstance().configure(RestartSoftwareParameters.RESTART_MACHINE_TYPED, RestartMachineMode.TRUE)));
+ t1.asTask().blockUntilEnded(Duration.TEN_SECONDS);
+ if (!t1.asTask().isError()) {
+ Assert.fail("Should have thrown error during "+t1+" because no more machines available at "+loc);
+ }
+
+ // now it has a machine, so reboot should succeed
+ SshMachineLocation machine2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure("address", "localhost"));
+ loc.addMachine(machine2);
+ TaskAdaptable<Void> t2 = Entities.submit(entity, Effectors.invocation(entity, Startable.RESTART,
+ ConfigBag.newInstance().configure(RestartSoftwareParameters.RESTART_MACHINE_TYPED, RestartMachineMode.TRUE)));
+ t2.asTask().get();
+
+ assertFalse(d.isRunning());
+ }
+
+ @Test
+ public void testBasicSoftwareProcessStopsEverything() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+ Location machine = Iterables.getOnlyElement(entity.getLocations());
+
+ d.events.clear();
+ entity.stop();
+ assertEquals(d.events, ImmutableList.of("stop"));
+ assertEquals(entity.getLocations().size(), 0);
+ assertTrue(loc.getAvailable().contains(machine));
+ }
+
+ @Test
+ public void testBasicSoftwareProcessStopEverythingExplicitly() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+ Location machine = Iterables.getOnlyElement(entity.getLocations());
+ d.events.clear();
+
+ TaskAdaptable<Void> t1 = Entities.submit(entity, Effectors.invocation(entity, Startable.STOP,
+ ConfigBag.newInstance().configure(StopSoftwareParameters.STOP_MACHINE_MODE, StopSoftwareParameters.StopMode.IF_NOT_STOPPED)));
+ t1.asTask().get();
+
+ assertEquals(d.events, ImmutableList.of("stop"));
+ assertEquals(entity.getLocations().size(), 0);
+ assertTrue(loc.getAvailable().contains(machine));
+ }
+
+ @Test
+ public void testBasicSoftwareProcessStopsProcess() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+ Location machine = Iterables.getOnlyElement(entity.getLocations());
+ d.events.clear();
+
+ TaskAdaptable<Void> t1 = Entities.submit(entity, Effectors.invocation(entity, Startable.STOP,
+ ConfigBag.newInstance().configure(StopSoftwareParameters.STOP_MACHINE_MODE, StopSoftwareParameters.StopMode.NEVER)));
+ t1.asTask().get(10, TimeUnit.SECONDS);
+
+ assertEquals(d.events, ImmutableList.of("stop"));
+ assertEquals(ImmutableList.copyOf(entity.getLocations()), ImmutableList.of(machine));
+ assertFalse(loc.getAvailable().contains(machine));
+ }
+
+ @Test(groups = "Integration")
+ public void testBasicSoftwareProcessStopAllModes() throws Exception {
+ for (boolean isEntityStopped : new boolean[] {true, false}) {
+ for (StopMode stopProcessMode : StopMode.values()) {
+ for (StopMode stopMachineMode : StopMode.values()) {
+ try {
+ testBasicSoftwareProcessStopModes(stopProcessMode, stopMachineMode, isEntityStopped);
+ } catch (Exception e) {
+ String msg = "stopProcessMode: " + stopProcessMode + ", stopMachineMode: " + stopMachineMode + ", isEntityStopped: " + isEntityStopped;
+ throw new PropagatedRuntimeException(msg, e);
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testBasicSoftwareProcessStopSomeModes() throws Exception {
+ for (boolean isEntityStopped : new boolean[] {true, false}) {
+ StopMode stopProcessMode = StopMode.IF_NOT_STOPPED;
+ StopMode stopMachineMode = StopMode.IF_NOT_STOPPED;
+ try {
+ testBasicSoftwareProcessStopModes(stopProcessMode, stopMachineMode, isEntityStopped);
+ } catch (Exception e) {
+ String msg = "stopProcessMode: " + stopProcessMode + ", stopMachineMode: " + stopMachineMode + ", isEntityStopped: " + isEntityStopped;
+ throw new PropagatedRuntimeException(msg, e);
+ }
+ }
+ }
+
+ private void testBasicSoftwareProcessStopModes(StopMode stopProcessMode, StopMode stopMachineMode, boolean isEntityStopped) throws Exception {
+ FixedListMachineProvisioningLocation<SshMachineLocation> l = getLocation();
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(l));
+ SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+ Location machine = Iterables.getOnlyElement(entity.getLocations());
+ d.events.clear();
+
+ if (isEntityStopped) {
+ ((EntityInternal)entity).setAttribute(ServiceStateLogic.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+ }
+
+ TaskAdaptable<Void> t1 = Entities.submit(entity, Effectors.invocation(entity, Startable.STOP,
+ ConfigBag.newInstance()
+ .configure(StopSoftwareParameters.STOP_PROCESS_MODE, stopProcessMode)
+ .configure(StopSoftwareParameters.STOP_MACHINE_MODE, stopMachineMode)));
+ t1.asTask().get(10, TimeUnit.SECONDS);
+
+ if (MachineLifecycleEffectorTasksTest.canStop(stopProcessMode, isEntityStopped)) {
+ assertEquals(d.events, ImmutableList.of("stop"));
+ } else {
+ assertTrue(d.events.isEmpty());
+ }
+ if (MachineLifecycleEffectorTasksTest.canStop(stopMachineMode, machine == null)) {
+ assertTrue(entity.getLocations().isEmpty());
+ assertTrue(l.getAvailable().contains(machine));
+ } else {
+ assertEquals(ImmutableList.copyOf(entity.getLocations()), ImmutableList.of(machine));
+ assertFalse(l.getAvailable().contains(machine));
+ }
+ }
+
+ @Test
+ public void testShutdownIsIdempotent() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ entity.start(ImmutableList.of(loc));
+ entity.stop();
+
+ entity.stop();
+ }
+
+ @Test
+ public void testReleaseEvenIfErrorDuringStart() throws Exception {
+ MyServiceImpl entity = new MyServiceImpl(app) {
+ @Override public Class<?> getDriverInterface() {
+ return SimulatedFailOnStartDriver.class;
+ }
+ };
+ Entities.manage(entity);
+
+ try {
+ entity.start(ImmutableList.of(loc));
+ Assert.fail();
+ } catch (Exception e) {
+ IllegalStateException cause = Throwables2.getFirstThrowableOfType(e, IllegalStateException.class);
+ if (cause == null || !cause.toString().contains("Simulating start error")) throw e;
+ }
+
+ try {
+ entity.stop();
+ } catch (Exception e) {
+ // Keep going
+ LOG.info("Error during stop, after simulating error during start", e);
+ }
+ Assert.assertEquals(loc.getAvailable(), ImmutableSet.of(machine));
+ Entities.unmanage(entity);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void doTestReleaseEvenIfErrorDuringStop(final Class driver) throws Exception {
+ MyServiceImpl entity = new MyServiceImpl(app) {
+ @Override public Class<?> getDriverInterface() {
+ return driver;
+ }
+ };
+ Entities.manage(entity);
+
+ entity.start(ImmutableList.of(loc));
+ Task<Void> t = entity.invoke(Startable.STOP);
+ t.blockUntilEnded();
+
+ assertFalse(t.isError(), "Expected parent to succeed, not fail with " + Tasks.getError(t));
+ Iterator<Task<?>> failures;
+ failures = Tasks.failed(Tasks.descendants(t, true)).iterator();
+ Assert.assertTrue(failures.hasNext(), "Expected error in descendants");
+ failures = Tasks.failed(Tasks.children(t)).iterator();
+ Assert.assertTrue(failures.hasNext(), "Expected error in child");
+ Throwable e = Tasks.getError(failures.next());
+ if (e == null || !e.toString().contains("Simulating stop error"))
+ Assert.fail("Wrong error", e);
+
+ Assert.assertEquals(loc.getAvailable(), ImmutableSet.of(machine), "Expected location to be available again");
+
+ Entities.unmanage(entity);
+ }
+
+ @Test
+ public void testReleaseEvenIfErrorDuringStop() throws Exception {
+ doTestReleaseEvenIfErrorDuringStop(SimulatedFailOnStopDriver.class);
+ }
+
+ @Test
+ public void testReleaseEvenIfChildErrorDuringStop() throws Exception {
+ doTestReleaseEvenIfErrorDuringStop(SimulatedFailInChildOnStopDriver.class);
+ }
+
+ @Test
+ public void testDoubleStopEntity() {
+ ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)mgmt.getEntityDriverManager()).getReflectiveDriverFactory();
+ f.addClassFullNameMapping(EmptySoftwareProcessDriver.class.getName(), MinimalEmptySoftwareProcessTestDriver.class.getName());
+
+ // Second stop on SoftwareProcess will return early, while the first stop is still in progress
+ // This causes the app to shutdown prematurely, leaking machines.
+ EntityManager emgr = mgmt.getEntityManager();
+ EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class);
+ TestApplication app = emgr.createEntity(appSpec);
+ emgr.manage(app);
+ EntitySpec<?> latchEntitySpec = EntitySpec.create(EmptySoftwareProcess.class);
+ Entity entity = app.createAndManageChild(latchEntitySpec);
+
+ final ReleaseLatchLocation loc = mgmt.getLocationManager().createLocation(LocationSpec.create(ReleaseLatchLocation.class));
+ try {
+ app.start(ImmutableSet.of(loc));
+ EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ final Task<Void> firstStop = entity.invoke(Startable.STOP, ImmutableMap.<String, Object>of());
+ // Wait until first task tries to release the location, at this point the entity's reference
+ // to the location is already cleared.
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(loc.isBlocked());
+ }
+ });
+
+ // Subsequent stops will end quickly - no location to release,
+ // while the first one is still releasing the machine.
+ final Task<Void> secondStop = entity.invoke(Startable.STOP, ImmutableMap.<String, Object>of());;
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(secondStop.isDone());
+ }
+ });
+
+ // Entity state is STOPPED even though first location is still releasing
+ EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+ Asserts.succeedsContinually(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(firstStop.isDone());
+ }
+ });
+
+ loc.unblock();
+
+ // After the location is released, first task ends as well.
+ EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(firstStop.isDone());
+ }
+ });
+
+ } finally {
+ loc.unblock();
+ }
+
+ }
+
+ @Test
+ public void testDoubleStopApp() {
+ ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)mgmt.getEntityDriverManager()).getReflectiveDriverFactory();
+ f.addClassFullNameMapping(EmptySoftwareProcessDriver.class.getName(), MinimalEmptySoftwareProcessTestDriver.class.getName());
+
+ // Second stop on SoftwareProcess will return early, while the first stop is still in progress
+ // This causes the app to shutdown prematurely, leaking machines.
+ EntityManager emgr = mgmt.getEntityManager();
+ EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class);
+ final TestApplication app = emgr.createEntity(appSpec);
+ emgr.manage(app);
+ EntitySpec<?> latchEntitySpec = EntitySpec.create(EmptySoftwareProcess.class);
+ final Entity entity = app.createAndManageChild(latchEntitySpec);
+
+ final ReleaseLatchLocation loc = mgmt.getLocationManager().createLocation(LocationSpec.create(ReleaseLatchLocation.class));
+ try {
+ app.start(ImmutableSet.of(loc));
+ EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ final Task<Void> firstStop = app.invoke(Startable.STOP, ImmutableMap.<String, Object>of());
+ // Wait until first task tries to release the location, at this point the entity's reference
+ // to the location is already cleared.
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(loc.isBlocked());
+ }
+ });
+
+ // Subsequent stops will end quickly - no location to release,
+ // while the first one is still releasing the machine.
+ final Task<Void> secondStop = app.invoke(Startable.STOP, ImmutableMap.<String, Object>of());;
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(secondStop.isDone());
+ }
+ });
+
+ // Since second stop succeeded the app will get unmanaged.
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(!Entities.isManaged(entity));
+ assertTrue(!Entities.isManaged(app));
+ }
+ });
+
+ // Unmanage will cancel the first task
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(firstStop.isDone());
+ }
+ });
+ } finally {
+ // We still haven't unblocked the location release, but entity is already unmanaged.
+ // Double STOP on an application could leak locations!!!
+ loc.unblock();
+ }
+ }
+
+ @Test
+ public void testOpenPortsWithPortRangeConfig() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class)
+ .configure("http.port", "9999+"));
+ Assert.assertTrue(entity.getRequiredOpenPorts().contains(9999));
+ }
+
+ @ImplementedBy(MyServiceImpl.class)
+ public interface MyService extends SoftwareProcess {
+ PortAttributeSensorAndConfigKey HTTP_PORT = Attributes.HTTP_PORT;
+ public SoftwareProcessDriver getDriver();
+ public Collection<Integer> getRequiredOpenPorts();
+ }
+
+ public static class MyServiceImpl extends SoftwareProcessImpl implements MyService {
+ public MyServiceImpl() {}
+ public MyServiceImpl(Entity parent) { super(parent); }
+
+ @Override
+ protected void initEnrichers() {
+ // Don't add enrichers messing with the SERVICE_UP state - we are setting it manually
+ }
+
+ @Override
+ public Class<?> getDriverInterface() {
+ return SimulatedDriver.class;
+ }
+
+ @Override
+ public Collection<Integer> getRequiredOpenPorts() {
+ return super.getRequiredOpenPorts();
+ }
+ }
+
+ @ImplementedBy(MyServiceWithVersionImpl.class)
+ public interface MyServiceWithVersion extends MyService {
+ public static ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "9.9.9");
+ }
+
+ public static class MyServiceWithVersionImpl extends MyServiceImpl implements MyServiceWithVersion {
+ public MyServiceWithVersionImpl() {}
+ public MyServiceWithVersionImpl(Entity parent) { super(parent); }
+ }
+
+ public static class SimulatedFailOnStartDriver extends SimulatedDriver {
+ public SimulatedFailOnStartDriver(EntityLocal entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ @Override
+ public void install() {
+ throw new IllegalStateException("Simulating start error");
+ }
+ }
+
+ public static class SimulatedFailOnStopDriver extends SimulatedDriver {
+ public SimulatedFailOnStopDriver(EntityLocal entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ @Override
+ public void stop() {
+ throw new IllegalStateException("Simulating stop error");
+ }
+ }
+
+ public static class SimulatedFailInChildOnStopDriver extends SimulatedDriver {
+ public SimulatedFailInChildOnStopDriver(EntityLocal entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ @Override
+ public void stop() {
+ DynamicTasks.queue(Tasks.fail("Simulating stop error in child", null));
+ }
+ }
+
+ public static class SimulatedDriver extends AbstractSoftwareProcessSshDriver {
+ public List<String> events = new ArrayList<String>();
+ private volatile boolean launched = false;
+
+ public SimulatedDriver(EntityLocal entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return launched;
+ }
+
+ @Override
+ public void stop() {
+ events.add("stop");
+ launched = false;
+ entity.setAttribute(Startable.SERVICE_UP, false);
+ entity.setAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+ }
+
+ @Override
+ public void kill() {
+ events.add("kill");
+ launched = false;
+ entity.setAttribute(Startable.SERVICE_UP, false);
+ }
+
+ @Override
+ public void install() {
+ events.add("install");
+ entity.setAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
+ }
+
+ @Override
+ public void customize() {
+ events.add("customize");
+ }
+
+ @Override
+ public void launch() {
+ events.add("launch");
+ launched = true;
+ entity.setAttribute(Startable.SERVICE_UP, true);
+ entity.setAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+ }
+
+ @Override
+ public void setup() {
+ events.add("setup");
+ }
+
+ @Override
+ public void copyInstallResources() {
+ events.add("copyInstallResources");
+ }
+
+ @Override
+ public void copyRuntimeResources() {
+ events.add("copyRuntimeResources");
+ }
+
+ @Override
+ public void runPreInstallCommand(String command) { }
+
+ @Override
+ public void runPostInstallCommand(String command) { }
+
+ @Override
+ public void runPreLaunchCommand(String command) { }
+
+ @Override
+ public void runPostLaunchCommand(String command) { }
+
+ @Override
+ protected String getInstallLabelExtraSalt() {
+ return (String)getEntity().getConfigRaw(ConfigKeys.newStringConfigKey("salt"), true).or((String)null);
+ }
+ }
+
+ public static class ReleaseLatchLocation extends SimulatedLocation {
+ private static final long serialVersionUID = 1L;
+
+ private CountDownLatch lock = new CountDownLatch(1);
+ private volatile boolean isBlocked;
+
+ public void unblock() {
+ lock.countDown();
+ }
+ @Override
+ public void release(MachineLocation machine) {
+ super.release(machine);
+ try {
+ isBlocked = true;
+ lock.await();
+ isBlocked = false;
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ public boolean isBlocked() {
+ return isBlocked;
+ }
+
+ }
+
+ public static class MinimalEmptySoftwareProcessTestDriver implements EmptySoftwareProcessDriver {
+
+ private EmptySoftwareProcessImpl entity;
+ private Location location;
+
+ public MinimalEmptySoftwareProcessTestDriver(EmptySoftwareProcessImpl entity, Location location) {
+ this.entity = entity;
+ this.location = location;
+ }
+
+ @Override
+ public Location getLocation() {
+ return location;
+ }
+
+ @Override
+ public EntityLocal getEntity() {
+ return entity;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return true;
+ }
+
+ @Override
+ public void rebind() {
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void restart() {
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public void kill() {
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSshDriverIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSshDriverIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSshDriverIntegrationTest.java
new file mode 100644
index 0000000..1b270b9
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSshDriverIntegrationTest.java
@@ -0,0 +1,389 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.core.BrooklynConfigKeys;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
+import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
+import org.apache.brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.BrooklynNetworkUtils;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.stream.KnownSizeInputStream;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.yaml.Yamls;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+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 com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteSource;
+import com.google.common.io.Files;
+
+
+public class SoftwareProcessSshDriverIntegrationTest {
+
+ private LocalManagementContext managementContext;
+ private LocalhostMachineProvisioningLocation localhost;
+ private SshMachineLocation machine127;
+ private TestApplication app;
+ private File tempDataDir;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ tempDataDir = Files.createTempDir();
+ managementContext = new LocalManagementContext();
+
+ localhost = managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+ machine127 = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure("address", "localhost"));
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ if (tempDataDir != null) Os.deleteRecursively(tempDataDir);
+ }
+
+ // Integration test because requires ssh'ing (and takes about 5 seconds)
+ // See also SoftwareProcessEntityTest.testCustomInstallDirX for a lot more mocked variants
+ @Test(groups="Integration")
+ public void testCanInstallMultipleVersionsOnSameMachine() throws Exception {
+ managementContext.getBrooklynProperties().put(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class)
+ .configure(SoftwareProcess.SUGGESTED_VERSION, "0.1.0"));
+ MyService entity2 = app.createAndManageChild(EntitySpec.create(MyService.class)
+ .configure(SoftwareProcess.SUGGESTED_VERSION, "0.2.0"));
+ app.start(ImmutableList.of(machine127));
+
+ String installDir1 = entity.getAttribute(SoftwareProcess.INSTALL_DIR);
+ String installDir2 = entity2.getAttribute(SoftwareProcess.INSTALL_DIR);
+
+ assertNotEquals(installDir1, installDir2);
+ assertTrue(installDir1.contains("0.1.0"), "installDir1="+installDir1);
+ assertTrue(installDir2.contains("0.2.0"), "installDir2="+installDir2);
+ assertTrue(new File(new File(installDir1), "myfile").isFile());
+ assertTrue(new File(new File(installDir2), "myfile").isFile());
+ }
+
+ @Test(groups="Integration")
+ public void testLocalhostInTmp() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ app.start(ImmutableList.of(localhost));
+
+ String installDir = entity.getAttribute(SoftwareProcess.INSTALL_DIR);
+ assertTrue(installDir.startsWith("/tmp/brooklyn-"+Os.user()+"/installs/"), "installed in "+installDir);
+ }
+
+ @Test(groups="Integration")
+ public void testMachine127InHome() throws Exception {
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ app.start(ImmutableList.of(machine127));
+
+ String installDir = entity.getAttribute(SoftwareProcess.INSTALL_DIR);
+ assertTrue(installDir.startsWith(Os.home()+"/brooklyn-managed-processes/installs/"), "installed in "+installDir);
+ }
+
+ @Test(groups="Integration")
+ public void testLocalhostInCustom() throws Exception {
+ localhost.setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ app.start(ImmutableList.of(localhost));
+
+ String installDir = entity.getAttribute(SoftwareProcess.INSTALL_DIR);
+ assertTrue(installDir.startsWith(tempDataDir.getAbsolutePath()+"/installs/"), "installed in "+installDir);
+ }
+
+ @Test(groups="Integration")
+ @Deprecated
+ public void testMachineInCustomFromDataDir() throws Exception {
+ managementContext.getBrooklynProperties().put(BrooklynConfigKeys.BROOKLYN_DATA_DIR, tempDataDir.getAbsolutePath());
+
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ app.start(ImmutableList.of(machine127));
+
+ String installDir = entity.getAttribute(SoftwareProcess.INSTALL_DIR);
+ assertTrue(installDir.startsWith(tempDataDir.getAbsolutePath()+"/installs/"), "installed in "+installDir);
+ }
+
+ @Test(groups="Integration")
+ public void testCopyResource() throws Exception {
+ File tempDest = new File(tempDataDir, "tempDest.txt");
+ String tempLocalContent = "abc";
+ File tempLocal = new File(tempDataDir, "tempLocal.txt");
+ Files.write(tempLocalContent, tempLocal, Charsets.UTF_8);
+
+ localhost.setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ app.start(ImmutableList.of(localhost));
+
+ // Copy local file
+ entity.getDriver().copyResource(tempLocal, tempDest.getAbsolutePath());
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ tempDest.delete();
+
+ // Copy local file using url
+ entity.getDriver().copyResource(tempLocal.toURI().toString(), tempDest.getAbsolutePath());
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ tempDest.delete();
+
+ // Copy reader
+ entity.getDriver().copyResource(new StringReader(tempLocalContent), tempDest.getAbsolutePath());
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ tempDest.delete();
+
+ // Copy stream
+ entity.getDriver().copyResource(ByteSource.wrap(tempLocalContent.getBytes()).openStream(), tempDest.getAbsolutePath());
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ tempDest.delete();
+
+ // Copy known-size stream
+ entity.getDriver().copyResource(new KnownSizeInputStream(Streams.newInputStreamWithContents(tempLocalContent), tempLocalContent.length()), tempDest.getAbsolutePath());
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ tempDest.delete();
+ }
+
+ @Test(groups="Integration")
+ public void testCopyResourceCreatingParentDir() throws Exception {
+ /*
+ * TODO copyResource will now always create the parent dir, irrespective of the createParentDir value!
+ * In SshMachineLocation on 2014-05-29, Alex added: mkdir -p `dirname '$DEST'`
+ *
+ * Changing this test to assert that parent dir always created; should we delete boolean createParentDir
+ * from the copyResource method?
+ *
+ * TODO Have also deleted test that if relative path is given it will write that relative to $RUN_DIR.
+ * That is not the case: it is relative to $HOME, which seems fine. For example, if copyResource
+ * is used during install phase then $RUN_DIR would be the wrong default.
+ * Is there any code that relies on this behaviour?
+ */
+ File tempDataDirSub = new File(tempDataDir, "subdir");
+ File tempDest = new File(tempDataDirSub, "tempDest.txt");
+ String tempLocalContent = "abc";
+ File tempLocal = new File(tempDataDir, "tempLocal.txt");
+ Files.write(tempLocalContent, tempLocal, Charsets.UTF_8);
+
+ localhost.setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+
+ MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+ app.start(ImmutableList.of(localhost));
+
+ // First confirm that even if createParentDir==false that it still gets created!
+ try {
+ entity.getDriver().copyResource(tempLocal.toURI().toString(), tempDest.getAbsolutePath(), false);
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ } finally {
+ Os.deleteRecursively(tempDataDirSub);
+ }
+
+ // Copy to absolute path
+ try {
+ entity.getDriver().copyResource(tempLocal.toURI().toString(), tempDest.getAbsolutePath(), true);
+ assertEquals(Files.readLines(tempDest, Charsets.UTF_8), ImmutableList.of(tempLocalContent));
+ } finally {
+ Os.deleteRecursively(tempDataDirSub);
+ }
+ }
+
+ @Test(groups="Integration")
+ public void testPreAndPostLaunchCommands() throws IOException {
+ File tempFile = new File(tempDataDir, "tempFile.txt");
+ localhost.setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+ app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+ .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "")
+ .configure(SoftwareProcess.PRE_LAUNCH_COMMAND, String.format("echo inPreLaunch >> %s", tempFile.getAbsoluteFile()))
+ .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, String.format("echo inLaunch >> %s", tempFile.getAbsoluteFile()))
+ .configure(SoftwareProcess.POST_LAUNCH_COMMAND, String.format("echo inPostLaunch >> %s", tempFile.getAbsoluteFile())));
+ app.start(ImmutableList.of(localhost));
+
+ List<String> output = Files.readLines(tempFile, Charsets.UTF_8);
+ assertEquals(output.size(), 3);
+ assertEquals(output.get(0), "inPreLaunch");
+ assertEquals(output.get(1), "inLaunch");
+ assertEquals(output.get(2), "inPostLaunch");
+ tempFile.delete();
+ }
+
+ @Test(groups="Integration")
+ public void testInstallResourcesCopy() throws IOException {
+ localhost.setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+ File template = new File(Os.tmp(), "template.yaml");
+ VanillaSoftwareProcess entity = app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+ .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "")
+ .configure(SoftwareProcess.INSTALL_FILES, MutableMap.of("classpath://org/apache/brooklyn/entity/software/base/frogs.txt", "frogs.txt"))
+ .configure(SoftwareProcess.INSTALL_TEMPLATES, MutableMap.of("classpath://org/apache/brooklyn/entity/software/base/template.yaml", template.getAbsolutePath()))
+ .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "date"));
+ app.start(ImmutableList.of(localhost));
+
+ File frogs = new File(entity.getAttribute(SoftwareProcess.INSTALL_DIR), "frogs.txt");
+ try {
+ Assert.assertTrue(frogs.canRead(), "File not readable: " + frogs);
+ String output = Files.toString(frogs, Charsets.UTF_8);
+ Assert.assertTrue(output.contains("Brekekekex"), "File content not found: " + output);
+ } finally {
+ frogs.delete();
+ }
+
+ try {
+ String expectedHostname = BrooklynNetworkUtils.getLocalhostInetAddress().getHostName();
+ String expectedIp = BrooklynNetworkUtils.getLocalhostInetAddress().getHostAddress();
+
+ Map<?,?> data = (Map) Iterables.getOnlyElement(Yamls.parseAll(Files.toString(template, Charsets.UTF_8)));
+ Assert.assertEquals(data.size(), 3);
+ Assert.assertEquals(data.get("entity.hostname"), expectedHostname);
+ Assert.assertEquals(data.get("entity.address"), expectedIp);
+ Assert.assertEquals(data.get("frogs"), Integer.valueOf(12));
+ } finally {
+ template.delete();
+ }
+ }
+
+ @Test(groups="Integration")
+ public void testRuntimeResourcesCopy() throws IOException {
+ localhost.setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, tempDataDir.getAbsolutePath());
+ File template = new File(Os.tmp(), "template.yaml");
+ VanillaSoftwareProcess entity = app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+ .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "")
+ .configure(SoftwareProcess.RUNTIME_FILES, MutableMap.of("classpath://org/apache/brooklyn/entity/software/base/frogs.txt", "frogs.txt"))
+ .configure(SoftwareProcess.RUNTIME_TEMPLATES, MutableMap.of("classpath://org/apache/brooklyn/entity/software/base/template.yaml", template.getAbsolutePath()))
+ .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "date"));
+ app.start(ImmutableList.of(localhost));
+
+ File frogs = new File(entity.getAttribute(SoftwareProcess.RUN_DIR), "frogs.txt");
+ try {
+ Assert.assertTrue(frogs.canRead(), "File not readable: " + frogs);
+ String output = Files.toString(frogs, Charsets.UTF_8);
+ Assert.assertTrue(output.contains("Brekekekex"), "File content not found: " + output);
+ } finally {
+ frogs.delete();
+ }
+
+ try {
+ String expectedHostname = BrooklynNetworkUtils.getLocalhostInetAddress().getHostName();
+ String expectedIp = BrooklynNetworkUtils.getLocalhostInetAddress().getHostAddress();
+
+ Map<?,?> data = (Map) Iterables.getOnlyElement(Yamls.parseAll(Files.toString(template, Charsets.UTF_8)));
+ Assert.assertEquals(data.size(), 3);
+ Assert.assertEquals(data.get("entity.hostname"), expectedHostname);
+ Assert.assertEquals(data.get("entity.address"), expectedIp);
+ Assert.assertEquals(data.get("frogs"), Integer.valueOf(12));
+ } finally {
+ template.delete();
+ }
+ }
+
+ @ImplementedBy(MyServiceImpl.class)
+ public interface MyService extends SoftwareProcess {
+ public SimulatedDriver getDriver();
+ }
+
+ public static class MyServiceImpl extends SoftwareProcessImpl implements MyService {
+ public MyServiceImpl() {
+ }
+
+ @Override
+ public Class<?> getDriverInterface() {
+ return SimulatedDriver.class;
+ }
+
+ @Override
+ public SimulatedDriver getDriver() {
+ return (SimulatedDriver) super.getDriver();
+ }
+ }
+
+ public static class SimulatedDriver extends AbstractSoftwareProcessSshDriver {
+ public List<String> events = new ArrayList<String>();
+ private volatile boolean launched = false;
+
+ public SimulatedDriver(EntityLocal entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ @Override
+ public void install() {
+ events.add("install");
+ newScript(INSTALLING)
+ .failOnNonZeroResultCode()
+ .body.append("touch myfile")
+ .execute();
+ }
+
+ @Override
+ public void customize() {
+ events.add("customize");
+ }
+
+ @Override
+ public void launch() {
+ events.add("launch");
+ launched = true;
+ entity.setAttribute(Startable.SERVICE_UP, true);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return launched;
+ }
+
+ @Override
+ public void stop() {
+ events.add("stop");
+ launched = false;
+ entity.setAttribute(Startable.SERVICE_UP, false);
+ }
+
+ @Override
+ public void kill() {
+ events.add("kill");
+ launched = false;
+ entity.setAttribute(Startable.SERVICE_UP, false);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSubclassTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSubclassTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSubclassTest.java
new file mode 100644
index 0000000..338b1f3
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessSubclassTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.effector.core.EffectorAndBody;
+import org.apache.brooklyn.effector.core.MethodEffector;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+
+public class SoftwareProcessSubclassTest extends BrooklynAppUnitTestSupport {
+
+// NB: These tests don't actually require ssh to localhost -- only that 'localhost' resolves.
+
+ @SuppressWarnings("unused")
+ private static final Logger LOG = LoggerFactory.getLogger(SoftwareProcessSubclassTest.class);
+
+ @ImplementedBy(SubSoftwareProcessImpl.class)
+ public static interface SubSoftwareProcess extends EmptySoftwareProcess {
+ public List<String> getCallHistory();
+ public void triggerStopOutsideOfEffector();
+ public void customRestart();
+ }
+
+ public static class SubSoftwareProcessImpl extends EmptySoftwareProcessImpl implements SubSoftwareProcess {
+
+ protected List<String> callHistory = Collections.synchronizedList(Lists.<String>newArrayList());
+
+ @Override
+ public void init() {
+ super.init();
+ getMutableEntityType().addEffector(new EffectorAndBody<Void>(SoftwareProcess.RESTART, new MethodEffector<Void>(SubSoftwareProcess.class, "customRestart").getBody()));
+ }
+
+ @Override
+ public List<String> getCallHistory() {
+ return callHistory;
+ }
+
+ @Override
+ public void preStart() {
+ callHistory.add("doStart");
+ super.preStart();
+ }
+
+ @Override
+ public void preStop() {
+ callHistory.add("doStop");
+ super.preStop();
+ }
+
+ @Override
+ public void customRestart() {
+ callHistory.add("doRestart");
+ }
+
+ @Override
+ public void triggerStopOutsideOfEffector() {
+ stop();
+ }
+
+ }
+
+ private Location loc;
+ private List<Location> locs;
+ private SubSoftwareProcess entity;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ loc = mgmt.getLocationRegistry().resolve("localhost");
+ locs = ImmutableList.of(loc);
+ entity = app.createAndManageChild(EntitySpec.create(SubSoftwareProcess.class));
+ }
+
+ @Test
+ public void testStartCalledViaMethod() throws Exception {
+ entity.start(locs);
+
+ assertCallHistory(ImmutableList.of("doStart"));
+ }
+
+ @Test
+ public void testStopCalledViaMethod() throws Exception {
+ app.start(locs);
+ entity.stop();
+
+ assertCallHistory(ImmutableList.of("doStart", "doStop"));
+ }
+
+ @Test
+ public void testRestartCalledViaMethod() throws Exception {
+ app.start(locs);
+ entity.restart();
+
+ assertCallHistory(ImmutableList.of("doStart", "doRestart"));
+ }
+
+ @Test
+ public void testStopCalledWithoutEffector() throws Exception {
+ app.start(locs);
+ entity.triggerStopOutsideOfEffector();
+
+ assertCallHistory(ImmutableList.of("doStart", "doStop"));
+ }
+
+ @Test
+ public void testStartCalledViaInvokeEffector() throws Exception {
+ entity.invoke(SubSoftwareProcess.START, ImmutableMap.<String,Object>of("locations", locs)).get();
+
+ assertCallHistory(ImmutableList.of("doStart"));
+ }
+
+ @Test
+ public void testStopCalledViaInvokeEffector() throws Exception {
+ app.start(locs);
+ entity.invoke(SubSoftwareProcess.STOP, ImmutableMap.<String,Object>of()).get();
+
+ assertCallHistory(ImmutableList.of("doStart", "doStop"));
+ }
+
+ @Test
+ public void testRestartCalledViaInvokeEffector() throws Exception {
+ app.start(locs);
+ entity.invoke(SubSoftwareProcess.RESTART, ImmutableMap.<String,Object>of()).get();
+
+ assertCallHistory(ImmutableList.of("doStart", "doRestart"));
+ }
+
+ private void assertCallHistory(Iterable<String> expected) {
+ List<String> actual = entity.getCallHistory();
+ assertEquals(actual, ImmutableList.copyOf(expected), "actual="+actual);
+ }
+}