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:27 UTC

[09/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/java/VanillaJavaAppTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java
new file mode 100644
index 0000000..e4d30c3
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/java/VanillaJavaAppTest.java
@@ -0,0 +1,352 @@
+/*
+ * 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.java;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.MalformedURLException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.java.JavaAppUtils;
+import org.apache.brooklyn.entity.java.UsesJava;
+import org.apache.brooklyn.entity.java.UsesJmx;
+import org.apache.brooklyn.entity.java.VanillaJavaApp;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.sensor.feed.jmx.JmxHelper;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.crypto.FluentKeySigner;
+import org.apache.brooklyn.util.core.crypto.SecureKeys;
+import org.apache.brooklyn.util.crypto.SslTrustUtils;
+import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent;
+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.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.PortRanges;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+public class VanillaJavaAppTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(VanillaJavaAppTest.class);
+    
+    private static final long TIMEOUT_MS = 10*1000;
+
+    // Static attributes such as number of processors and start time are only polled every 60 seconds
+    // so if they are not immediately available, it will be 60 seconds before they are polled again
+    private static final Object LONG_TIMEOUT_MS = 61*1000;
+
+    private static String BROOKLYN_THIS_CLASSPATH = null;
+    private static Class<?> MAIN_CLASS = ExampleVanillaMain.class;
+    private static Class<?> MAIN_CPU_HUNGRY_CLASS = ExampleVanillaMainCpuHungry.class;
+    
+    private TestApplication app;
+    private LocalhostMachineProvisioningLocation loc;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        if (BROOKLYN_THIS_CLASSPATH==null) {
+            BROOKLYN_THIS_CLASSPATH = ResourceUtils.create(MAIN_CLASS).getClassLoaderDir();
+        }
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        loc = app.newLocalhostProvisioningLocation(MutableMap.of("address", "localhost"));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testReadsConfigFromFlags() throws Exception {
+        final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", "my.Main").configure("classpath", ImmutableList.of("c1", "c2"))
+            .configure("args", ImmutableList.of("a1", "a2")));
+
+        assertEquals(javaProcess.getMainClass(), "my.Main");
+        assertEquals(javaProcess.getClasspath(), ImmutableList.of("c1","c2"));
+        assertEquals(javaProcess.getConfig(VanillaJavaApp.ARGS), ImmutableList.of("a1", "a2"));
+    }
+
+    @Test(groups={"WIP", "Integration"})
+    public void testJavaSystemProperties() throws Exception {
+        final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", "my.Main").configure("classpath", ImmutableList.of("c1", "c2"))
+            .configure("args", ImmutableList.of("a1", "a2")));
+        ((EntityLocal)javaProcess).setConfig(UsesJava.JAVA_SYSPROPS, ImmutableMap.of("fooKey", "fooValue", "barKey", "barValue"));
+        // TODO: how to test: launch standalone app that outputs system properties to stdout? Probe via JMX?
+    }
+
+    @Test(groups={"Integration"})
+    public void testStartsAndStops() throws Exception {
+        String main = MAIN_CLASS.getCanonicalName();
+        final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH))
+            .configure("args", ImmutableList.of()));
+        app.start(ImmutableList.of(loc));
+        assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+
+        javaProcess.stop();
+        assertEquals(javaProcess.getAttribute(VanillaJavaApp.SERVICE_STATE_ACTUAL), Lifecycle.STOPPED);
+    }
+
+    @Test(groups={"Integration"})
+    public void testHasJvmMXBeanSensorVals() throws Exception {
+        String main = MAIN_CLASS.getCanonicalName();
+        final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH))
+            .configure("args", ImmutableList.of()));
+        app.start(ImmutableList.of(loc));
+        
+        // Memory MXBean
+        Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+            public void run() {
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.NON_HEAP_MEMORY_USAGE));
+                long init = javaProcess.getAttribute(VanillaJavaApp.INIT_HEAP_MEMORY);
+                long used = javaProcess.getAttribute(VanillaJavaApp.USED_HEAP_MEMORY);
+                long committed = javaProcess.getAttribute(VanillaJavaApp.COMMITTED_HEAP_MEMORY);
+                long max = javaProcess.getAttribute(VanillaJavaApp.MAX_HEAP_MEMORY);
+    
+                assertNotNull(used);
+                assertNotNull(init);
+                assertNotNull(committed);
+                assertNotNull(max);
+                assertTrue(init <= max, String.format("init %d > max %d heap memory", init, max));
+                assertTrue(used <= committed, String.format("used %d > committed %d heap memory", used, committed));
+                assertTrue(committed <= max, String.format("committed %d > max %d heap memory", committed, max));
+            }});
+        
+        // Threads MX Bean
+        Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+            public void run() {
+                long current = javaProcess.getAttribute(VanillaJavaApp.CURRENT_THREAD_COUNT);
+                long peak = javaProcess.getAttribute(VanillaJavaApp.PEAK_THREAD_COUNT);
+    
+                assertNotNull(current);
+                assertNotNull(peak);
+                assertTrue(current <= peak, String.format("current %d > peak %d thread count", current, peak));
+            }});
+
+        // Runtime MX Bean
+        Asserts.succeedsEventually(MutableMap.of("timeout", LONG_TIMEOUT_MS), new Runnable() {
+            public void run() {
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.START_TIME));
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.UP_TIME));
+            }});
+        
+        // Operating System MX Bean
+        Asserts.succeedsEventually(MutableMap.of("timeout", LONG_TIMEOUT_MS), new Runnable() {
+            public void run() {
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.PROCESS_CPU_TIME));
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.SYSTEM_LOAD_AVERAGE));
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.AVAILABLE_PROCESSORS));
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.TOTAL_PHYSICAL_MEMORY_SIZE));
+                assertNotNull(javaProcess.getAttribute(VanillaJavaApp.FREE_PHYSICAL_MEMORY_SIZE));
+            }});
+        // TODO work on providing useful metrics from garbage collector MX Bean
+        // assertNotNull(javaProcess.getAttribute(VanillaJavaApp.GARBAGE_COLLECTION_TIME)) TODO: work on providing this
+    }
+    
+    @Test(groups={"Integration"})
+    public void testJvmMXBeanProcessCpuTimeGivesNonZeroPercentage() throws Exception {
+        String main = MAIN_CPU_HUNGRY_CLASS.getCanonicalName();
+        final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH))
+            .configure("args", ImmutableList.of()));
+        app.start(ImmutableList.of(loc));
+
+        JavaAppUtils.connectJavaAppServerPolicies((EntityLocal)javaProcess);
+        
+        final List<Double> fractions = new CopyOnWriteArrayList<Double>();
+        app.getManagementContext().getSubscriptionManager().subscribe(javaProcess, VanillaJavaApp.PROCESS_CPU_TIME_FRACTION_LAST, new SensorEventListener<Double>() {
+                public void onEvent(SensorEvent<Double> event) {
+                    fractions.add(event.getValue());
+                }});
+        
+        // Expect non-trivial load to be generated by the process.
+        // Expect load to be in the right order of magnitude (to ensure we haven't got a decimal point in the wrong place etc);
+        // But with multi-core could get big number; and on jenkins@releng3 we once saw [11.9, 0.6, 0.5]!
+        Asserts.succeedsEventually(new Runnable() {
+            public void run() {
+                Iterable<Double> nonTrivialFractions = Iterables.filter(fractions, new Predicate<Double>() {
+                        public boolean apply(Double input) {
+                            return input > 0.01;
+                        }});
+                assertTrue(Iterables.size(nonTrivialFractions) > 3, "fractions="+fractions); 
+            }});
+
+        Iterable<Double> tooBigFractions = Iterables.filter(fractions, new Predicate<Double>() {
+                public boolean apply(Double input) {
+                    return input > 50;
+                }});
+        assertTrue(Iterables.isEmpty(tooBigFractions), "fractions="+fractions); 
+        
+        Iterable<Double> ballparkRightFractions = Iterables.filter(fractions, new Predicate<Double>() {
+                public boolean apply(Double input) {
+                    return input > 0.01 && input < 4;
+                }});
+        assertTrue(Iterables.size(ballparkRightFractions) >= (fractions.size() / 2), "fractions="+fractions);
+        
+        LOG.info("VanillaJavaApp->ExampleVanillaMainCpuHuntry: ProcessCpuTime fractions="+fractions);
+    }
+
+    @Test(groups={"Integration"})
+    public void testStartsWithJmxPortSpecifiedInConfig() throws Exception {
+        int port = 53405;
+        String main = MAIN_CLASS.getCanonicalName();
+        VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH))
+            .configure("args", ImmutableList.of()));
+        ((EntityLocal)javaProcess).setConfig(UsesJmx.JMX_PORT, PortRanges.fromInteger(port));
+        app.start(ImmutableList.of(loc));
+
+        assertEquals(javaProcess.getAttribute(UsesJmx.JMX_PORT), (Integer)port);
+    }
+
+    // FIXME Way test was written requires JmxSensorAdapter; need to rewrite...  
+    @Test(groups={"Integration", "WIP"})
+    public void testStartsWithSecureJmxPortSpecifiedInConfig() throws Exception {
+        int port = 53406;
+        String main = MAIN_CLASS.getCanonicalName();
+        final VanillaJavaApp javaProcess = app.createAndManageChild(EntitySpec.create(VanillaJavaApp.class)
+            .configure("main", main).configure("classpath", ImmutableList.of(BROOKLYN_THIS_CLASSPATH))
+            .configure("args", ImmutableList.of()));
+        ((EntityLocal)javaProcess).setConfig(UsesJmx.JMX_PORT, PortRanges.fromInteger(port));
+        ((EntityLocal)javaProcess).setConfig(UsesJmx.JMX_SSL_ENABLED, true);
+        
+        app.start(ImmutableList.of(loc));
+        // will fail above if JMX can't connect, but also do some add'l checks
+        
+        assertEquals(javaProcess.getAttribute(UsesJmx.JMX_PORT), (Integer)port);
+
+        // good key+cert succeeds
+        new AsserterForJmxConnection(javaProcess)
+                .customizeSocketFactory(null, null)
+                .connect();
+        
+        // bad cert fails
+        Asserts.assertFails(new Callable<Void>() {
+            public Void call() throws Exception {
+                new AsserterForJmxConnection(javaProcess)
+                        .customizeSocketFactory(null, new FluentKeySigner("cheater").newCertificateFor("jmx-access-key", SecureKeys.newKeyPair()))
+                        .connect();
+                return null;
+            }});
+
+        // bad key fails
+        Asserts.assertFails(new Callable<Void>() {
+            public Void call() throws Exception {
+                new AsserterForJmxConnection(javaProcess)
+                        .customizeSocketFactory(SecureKeys.newKeyPair().getPrivate(), null)
+                        .connect();
+                return null;
+            }});
+        
+        // bad profile fails
+        Asserts.assertFails(new Callable<Void>() {
+            public Void call() throws Exception {
+                AsserterForJmxConnection asserter = new AsserterForJmxConnection(javaProcess);
+                asserter.putEnv("jmx.remote.profiles", JmxmpAgent.TLS_JMX_REMOTE_PROFILES);
+                asserter.customizeSocketFactory(SecureKeys.newKeyPair().getPrivate(), null)
+                        .connect();
+                return null;
+            }});
+    }
+
+    private static class AsserterForJmxConnection {
+        final VanillaJavaApp entity;
+        final JMXServiceURL url;
+        final Map<String,Object> env;
+        
+        @SuppressWarnings("unchecked")
+        public AsserterForJmxConnection(VanillaJavaApp e) throws MalformedURLException {
+            this.entity = e;
+            
+            JmxHelper jmxHelper = new JmxHelper((EntityLocal)entity);
+            this.url = new JMXServiceURL(jmxHelper.getUrl());
+            this.env = Maps.newLinkedHashMap(jmxHelper.getConnectionEnvVars());
+        }
+        
+        public JMXServiceURL getJmxUrl() throws MalformedURLException {
+            return url;
+        }
+        
+        public void putEnv(String key, Object val) {
+            env.put(key, val);
+        }
+        
+        public AsserterForJmxConnection customizeSocketFactory(PrivateKey customKey, Certificate customCert) throws Exception {
+            PrivateKey key = (customKey == null) ? entity.getConfig(UsesJmx.JMX_SSL_ACCESS_KEY) : customKey;
+            Certificate cert = (customCert == null) ? entity.getConfig(UsesJmx.JMX_SSL_ACCESS_CERT) : customCert;
+            
+            KeyStore ks = SecureKeys.newKeyStore();
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            if (key!=null) {
+                ks.setKeyEntry("brooklyn-jmx-access", key, "".toCharArray(), new Certificate[] {cert});
+            }
+            kmf.init(ks, "".toCharArray());
+
+            TrustManager tms =
+            // TODO use root cert for trusting server
+            //trustStore!=null ? SecureKeys.getTrustManager(trustStore) :
+                SslTrustUtils.TRUST_ALL;
+
+            SSLContext ctx = SSLContext.getInstance("TLSv1");
+            ctx.init(kmf.getKeyManagers(), new TrustManager[] {tms}, null);
+            SSLSocketFactory ssf = ctx.getSocketFactory();
+            env.put(JmxmpAgent.TLS_SOCKET_FACTORY_PROPERTY, ssf);
+            
+            return this;
+        }
+        
+        public JMXConnector connect() throws Exception {
+            return JMXConnectorFactory.connect(getJmxUrl(), env);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java
new file mode 100644
index 0000000..9c4e571
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityEc2LiveTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.machine;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.entity.AbstractEc2LiveTest;
+import org.apache.brooklyn.entity.machine.MachineEntity;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class MachineEntityEc2LiveTest extends AbstractEc2LiveTest {
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        final MachineEntity server = app.createAndManageChild(EntitySpec.create(MachineEntity.class));
+        
+        app.start(ImmutableList.of(loc));
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertNotNull(server.getAttribute(MachineEntity.UPTIME));
+                assertNotNull(server.getAttribute(MachineEntity.LOAD_AVERAGE));
+                assertNotNull(server.getAttribute(MachineEntity.CPU_USAGE));
+                assertNotNull(server.getAttribute(MachineEntity.FREE_MEMORY));
+                assertNotNull(server.getAttribute(MachineEntity.TOTAL_MEMORY));
+                assertNotNull(server.getAttribute(MachineEntity.USED_MEMORY));
+            }});
+        
+        String result = server.execCommand("MY_ENV=myval && echo start $MY_ENV");
+        assertTrue(result.contains("start myval"), "result="+result);
+    }
+    
+    @Test(enabled=false)
+    public void testDummy() {} // Convince testng IDE integration that this really does have test methods
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java
new file mode 100644
index 0000000..cda1309
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/MachineEntityRebindTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.machine;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class MachineEntityRebindTest extends RebindTestFixtureWithApp {
+
+    @Test(groups = "Integration")
+    public void testRebindToMachineEntity() throws Exception {
+        EmptySoftwareProcess machine = origApp.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class));
+        origApp.start(ImmutableList.of(origManagementContext.getLocationRegistry().resolve("localhost")));
+        EntityTestUtils.assertAttributeEqualsEventually(machine, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        rebind(false);
+        Entity machine2 = newManagementContext.getEntityManager().getEntity(machine.getId());
+        EntityTestUtils.assertAttributeEqualsEventually(machine2, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java
new file mode 100644
index 0000000..c31e458
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/AbstractServerPoolTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.machine.pool;
+
+import static org.testng.Assert.fail;
+
+import java.util.List;
+
+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.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+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.factory.ApplicationBuilder;
+import org.apache.brooklyn.entity.machine.pool.ServerPool;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+public abstract class AbstractServerPoolTest {
+
+    // Note not extending BrooklynAppUnitTestSupport because sub-classes of this are for live and for unit tests.
+    // Instead, we have to repeat that logic for setting SKIP_ON_BOX_BASE_DIR_RESOLUTION
+    
+    private static final int DEFAULT_POOL_SIZE = 3;
+
+    protected Location location;
+    protected ManagementContext mgmt;
+    protected TestApplication poolApp;
+    protected ServerPool pool;
+    private List<TestApplication> createdApps = Lists.newLinkedList();
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        createdApps.clear();
+        mgmt = createManagementContext();
+        location = createLocation();
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution());
+        poolApp = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+
+        pool = poolApp.createAndManageChild(EntitySpec.create(ServerPool.class)
+                .configure(ServerPool.INITIAL_SIZE, getInitialPoolSize())
+                .configure(ServerPool.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
+        poolApp.start(ImmutableList.of(location));
+        EntityTestUtils.assertAttributeEqualsEventually(pool, Attributes.SERVICE_UP, true);
+        assertAvailableCountEventuallyEquals(getInitialPoolSize());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        // Kills the apps before terminating the pool
+        for (TestApplication app : createdApps) {
+            Entities.destroy(app);
+        }
+        if (mgmt != null) {
+            Entities.destroyAll(mgmt);
+            mgmt = null;
+        }
+    }
+
+    protected int getInitialPoolSize() {
+        return DEFAULT_POOL_SIZE;
+    }
+
+    protected ManagementContext createManagementContext() {
+        return new LocalManagementContextForTests();
+    }
+    
+    protected boolean shouldSkipOnBoxBaseDirResolution() {
+        return true;
+    }
+
+    /** @return Creates a LocalhostMachineProvisioningLocation */
+    protected Location createLocation() {
+        return mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+    }
+
+    protected void assertNoMachinesAvailableForApp(TestApplication app) {
+        try {
+            app.start(ImmutableList.of(pool.getDynamicLocation()));
+            fail("Expected exception when starting app with too many entities for pool");
+        } catch (Exception e) {
+            Throwable t = Exceptions.getFirstThrowableOfType(e, NoMachinesAvailableException.class);
+            if (t == null) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    protected void assertAvailableCountEventuallyEquals(int count) {
+        assertAvailableCountEventuallyEquals(pool, count);
+    }
+
+    protected void assertAvailableCountEventuallyEquals(ServerPool pool, int count) {
+        EntityTestUtils.assertAttributeEqualsEventually(pool, ServerPool.AVAILABLE_COUNT, count);
+    }
+
+    protected void assertClaimedCountEventuallyEquals(int count) {
+        assertClaimedCountEventuallyEquals(pool, count);
+    }
+
+    protected void assertClaimedCountEventuallyEquals(ServerPool pool, Integer count) {
+        EntityTestUtils.assertAttributeEqualsEventually(pool, ServerPool.CLAIMED_COUNT, count);
+    }
+
+    protected TestApplication createAppWithChildren(int numChildren) {
+        if (numChildren < 0) fail("Invalid number of children for app: " + numChildren);
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution());
+        TestApplication app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+        while (numChildren-- > 0) {
+            app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class));
+        }
+        createdApps.add(app);
+        return app;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java
new file mode 100644
index 0000000..c1e1f18
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.machine.pool;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.annotations.Test;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableList;
+
+public class ServerPoolLiveTest extends AbstractServerPoolTest {
+
+    public static final String PROVIDER = "softlayer";
+
+    protected BrooklynProperties brooklynProperties;
+
+    @Override
+    protected Location createLocation() {
+        // Image: {id=CENTOS_6_64, providerId=CENTOS_6_64, os={family=centos, version=6.5, description=CentOS / CentOS / 6.5-64 LAMP for Bare Metal, is64Bit=true}, description=CENTOS_6_64, status=AVAILABLE, loginUser=root}
+        Map<String, ?> allFlags = MutableMap.<String, Object>builder()
+                .put("provider", PROVIDER)
+                .put("tags", ImmutableList.of(getClass().getName()))
+                .put("vmNameMaxLength", 30)
+                .put("imageId", "CENTOS_6_64")
+                .build();
+        return mgmt.getLocationRegistry().resolve(PROVIDER, allFlags);
+    }
+
+    @Override
+    protected ManagementContext createManagementContext() {
+        String[] propsToRemove = new String[]{"imageId", "imageDescriptionRegex", "imageNameRegex", "inboundPorts", "hardwareId", "minRam"};
+
+        // Don't let any defaults from brooklyn.properties (except credentials) interfere with test
+        brooklynProperties = BrooklynProperties.Factory.newDefault();
+        for (String propToRemove : propsToRemove) {
+            for (String propVariant : ImmutableList.of(propToRemove, CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, propToRemove))) {
+                brooklynProperties.remove("brooklyn.locations.jclouds." + PROVIDER + "." + propVariant);
+                brooklynProperties.remove("brooklyn.locations." + propVariant);
+                brooklynProperties.remove("brooklyn.jclouds." + PROVIDER + "." + propVariant);
+                brooklynProperties.remove("brooklyn.jclouds." + propVariant);
+            }
+        }
+
+        // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
+        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        return new LocalManagementContextForTests(brooklynProperties);
+    }
+
+    protected boolean shouldSkipOnBoxBaseDirResolution() {
+        return false;
+    }
+
+    @Override
+    protected int getInitialPoolSize() {
+        return 1;
+    }
+
+    @Test(groups = "Live")
+    public void testAppCanBeDeployedToPool() {
+        TestApplication app = createAppWithChildren(1);
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertTrue(app.getAttribute(Attributes.SERVICE_UP));
+        for (Entity child : app.getChildren()) {
+            assertTrue(child.getAttribute(Attributes.SERVICE_UP));
+        }
+        TestApplication app2 = createAppWithChildren(1);
+        assertNoMachinesAvailableForApp(app2);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java
new file mode 100644
index 0000000..fb3974b
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLocationResolverTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.machine.pool;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+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.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+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.factory.ApplicationBuilder;
+import org.apache.brooklyn.entity.machine.pool.ServerPool;
+import org.apache.brooklyn.entity.machine.pool.ServerPoolLocation;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.dynamic.DynamicLocation;
+
+public class ServerPoolLocationResolverTest {
+
+    private LocalManagementContext managementContext;
+    private Entity locationOwner;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = new LocalManagementContextForTests(BrooklynProperties.Factory.newEmpty());
+        TestApplication t = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+        locationOwner = t.createAndManageChild(EntitySpec.create(ServerPool.class)
+                .configure(ServerPool.INITIAL_SIZE, 0)
+                .configure(ServerPool.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
+        Location poolLocation = managementContext.getLocationManager()
+                .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+        t.start(ImmutableList.of(poolLocation));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+
+    @Test
+    public void testResolve() {
+        ServerPoolLocation location = resolve("pool:" + locationOwner.getId());
+        assertEquals(location.getOwner().getId(), locationOwner.getId());
+    }
+
+    @Test
+    public void testSetsDisplayName() {
+        ServerPoolLocation location = resolve("pool:" + locationOwner.getId() + ":(displayName=xyz)");
+        assertEquals(location.getDisplayName(), "xyz");
+    }
+
+    private ServerPoolLocation resolve(String val) {
+        Map<String, Object> flags = MutableMap.<String, Object>of(DynamicLocation.OWNER.getName(), locationOwner);
+        Location l = managementContext.getLocationRegistry().resolve(val, flags);
+        Assert.assertNotNull(l);
+        return (ServerPoolLocation) l;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java
new file mode 100644
index 0000000..2de22bb
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolRebindTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.machine.pool;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.rebind.RebindOptions;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.machine.pool.ServerPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class ServerPoolRebindTest extends AbstractServerPoolTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ServerPoolRebindTest.class);
+    private ClassLoader classLoader = getClass().getClassLoader();
+    private File mementoDir;
+
+    @Override
+    protected ManagementContext createManagementContext() {
+        mementoDir = Files.createTempDir();
+        return RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
+    }
+
+    @Override
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+    }
+
+    private Collection<Application> rebind(TestApplication app) throws Exception {
+        LOG.info("Rebind start");
+        RebindTestUtils.waitForPersisted(app);
+        ((LocalManagementContext) app.getManagementContext()).terminate();
+        Collection<Application> r = RebindTestUtils.rebindAll(RebindOptions.create().mementoDir(mementoDir).classLoader(classLoader));
+        LOG.info("Rebind complete");
+        return r;
+    }
+
+    @Test(enabled = false)
+    public void testRebindingToPool() throws Exception {
+        TestApplication app = createAppWithChildren(1);
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertTrue(app.getAttribute(Attributes.SERVICE_UP));
+        assertAvailableCountEventuallyEquals(pool, getInitialPoolSize() - 1);
+        assertClaimedCountEventuallyEquals(pool, 1);
+
+        Collection<Application> reboundApps = rebind(poolApp);
+        ServerPool reboundPool = null;
+        for (Application reboundApp : reboundApps) {
+            Optional<Entity> np = Iterables.tryFind(reboundApp.getChildren(), Predicates.instanceOf(ServerPool.class));
+            if (np.isPresent()) {
+                mgmt = reboundApp.getManagementContext();
+                reboundPool = (ServerPool) np.get();
+                break;
+            }
+        }
+
+        assertNotNull(reboundPool, "No app in rebound context has " + ServerPool.class.getName() +
+                " child. Apps: " + reboundApps);
+        assertNotNull(reboundPool.getDynamicLocation());
+        assertTrue(reboundPool.getAttribute(Attributes.SERVICE_UP));
+        assertAvailableCountEventuallyEquals(reboundPool, getInitialPoolSize() - 1);
+        assertClaimedCountEventuallyEquals(reboundPool, 1);
+
+        TestApplication app2 = createAppWithChildren(1);
+        app2.start(ImmutableList.of(reboundPool.getDynamicLocation()));
+        assertTrue(app2.getAttribute(Attributes.SERVICE_UP));
+        assertAvailableCountEventuallyEquals(reboundPool, getInitialPoolSize() - 2);
+        assertClaimedCountEventuallyEquals(reboundPool, 2);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java
new file mode 100644
index 0000000..9d9b3a2
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.machine.pool;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.machine.pool.ServerPoolImpl;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class ServerPoolTest extends AbstractServerPoolTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ServerPoolTest.class);
+
+    @Test
+    public void testAppCanBeDeployedToServerPool() {
+        TestApplication app = createAppWithChildren(1);
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertTrue(app.getAttribute(Attributes.SERVICE_UP));
+        for (Entity child : app.getChildren()) {
+            assertTrue(child.getAttribute(Attributes.SERVICE_UP));
+        }
+    }
+
+    @Test
+    public void testFailureWhenNotEnoughServersAvailable() {
+        TestApplication app = createAppWithChildren(getInitialPoolSize() + 1);
+        assertNoMachinesAvailableForApp(app);
+        EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+    }
+
+    @Test
+    public void testDeployReleaseDeploy() {
+        TestApplication app = createAppWithChildren(getInitialPoolSize());
+        TestApplication app2 = createAppWithChildren(1);
+
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        assertAvailableCountEventuallyEquals(0);
+        assertNoMachinesAvailableForApp(app2);
+
+        app.stop();
+        assertFalse(app.getAttribute(Attributes.SERVICE_UP));
+        assertAvailableCountEventuallyEquals(getInitialPoolSize());
+
+        app2.start(ImmutableList.of(pool.getDynamicLocation()));
+        EntityTestUtils.assertAttributeEqualsEventually(app2, Attributes.SERVICE_UP, true);
+        
+        assertAvailableCountEventuallyEquals(getInitialPoolSize() - 1);
+        assertClaimedCountEventuallyEquals(1);
+    }
+
+    @Test
+    public void testResizingPoolUp() {
+        TestApplication app = createAppWithChildren(getInitialPoolSize());
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertTrue(app.getAttribute(Attributes.SERVICE_UP));
+
+        TestApplication app2 = createAppWithChildren(1);
+        assertNoMachinesAvailableForApp(app2);
+
+        pool.resizeByDelta(1);
+        
+        assertAvailableCountEventuallyEquals(1);
+
+        assertEquals((int) pool.getCurrentSize(), getInitialPoolSize() + 1);
+        app2.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertTrue(app2.getAttribute(Attributes.SERVICE_UP));
+    }
+
+    @Test
+    public void testResizePoolDownSucceedsWhenEnoughMachinesAreFree() {
+        TestApplication app = createAppWithChildren(1);
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertAvailableCountEventuallyEquals(getInitialPoolSize() - 1);
+
+        pool.resize(1);
+
+        assertAvailableCountEventuallyEquals(0);
+    }
+
+    @Test
+    public void testResizeDownDoesNotReleaseClaimedMachines() {
+        TestApplication app = createAppWithChildren(getInitialPoolSize() - 1);
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertAvailableCountEventuallyEquals(1);
+        assertClaimedCountEventuallyEquals(getInitialPoolSize() - 1);
+
+        LOG.info("Test attempting to resize to 0 members. Should only drop the one available machine.");
+        pool.resize(0);
+
+        assertAvailableCountEventuallyEquals(0);
+        assertEquals(Iterables.size(pool.getMembers()), getInitialPoolSize() - 1);
+        assertAvailableCountEventuallyEquals(0);
+        assertClaimedCountEventuallyEquals(getInitialPoolSize() - 1);
+    }
+
+    @Test
+    public void testCanAddExistingMachinesToPool() {
+        TestApplication app = createAppWithChildren(getInitialPoolSize());
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertAvailableCountEventuallyEquals(0);
+
+        LocalhostMachine loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachine.class));
+        Entity added = pool.addExistingMachine(loc);
+        assertFalse(added.getConfig(ServerPoolImpl.REMOVABLE));
+        assertAvailableCountEventuallyEquals(1);
+
+        TestApplication app2 = createAppWithChildren(1);
+        app2.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertAvailableCountEventuallyEquals(0);
+    }
+
+    @Test
+    public void testExistingMachinesAreNotRemovedFromThePoolOnShrinkButAreOnStop() {
+        LocalhostMachine loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachine.class));
+        pool.addExistingMachine(loc);
+        assertAvailableCountEventuallyEquals(getInitialPoolSize() + 1);
+        pool.resize(0);
+        assertAvailableCountEventuallyEquals(1);
+        pool.stop();
+        assertAvailableCountEventuallyEquals(0);
+    }
+
+    @Test
+    public void testAddExistingMachineFromSpec() {
+        TestApplication app = createAppWithChildren(getInitialPoolSize());
+        app.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertAvailableCountEventuallyEquals(0);
+
+        Collection<Entity> added = pool.addExistingMachinesFromSpec("byon:(hosts=\"localhost,localhost\")");
+        assertEquals(added.size(), 2, "Added: " + Joiner.on(", ").join(added));
+        Iterator<Entity> it = added.iterator();
+        assertFalse(it.next().getConfig(ServerPoolImpl.REMOVABLE));
+        assertFalse(it.next().getConfig(ServerPoolImpl.REMOVABLE));
+        assertAvailableCountEventuallyEquals(2);
+
+        TestApplication app2 = createAppWithChildren(2);
+        app2.start(ImmutableList.of(pool.getDynamicLocation()));
+        assertAvailableCountEventuallyEquals(0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java
new file mode 100644
index 0000000..7a9fd6d
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Runs a test with many different distros and versions.
+ */
+public abstract class AbstractDockerLiveTest {
+    
+    public static final String PROVIDER = "docker";
+
+    protected BrooklynProperties brooklynProperties;
+    protected ManagementContext ctx;
+    
+    protected TestApplication app;
+    protected Location jcloudsLocation;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        List<String> propsToRemove = ImmutableList.of("imageDescriptionRegex", "imageNameRegex", "inboundPorts",
+                "hardwareId", "minRam");
+        
+     // Don't let any defaults from brooklyn.properties (except credentials) interfere with test
+        brooklynProperties = BrooklynProperties.Factory.newDefault();
+        for (String propToRemove : propsToRemove) {
+            for (String propVariant : ImmutableList.of(propToRemove, CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, propToRemove))) {
+                brooklynProperties.remove("brooklyn.locations.jclouds."+PROVIDER+"."+propVariant);
+                brooklynProperties.remove("brooklyn.locations."+propVariant);
+                brooklynProperties.remove("brooklyn.jclouds."+PROVIDER+"."+propVariant);
+                brooklynProperties.remove("brooklyn.jclouds."+propVariant);
+            }
+        }
+
+        // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
+        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        
+        ctx = new LocalManagementContext(brooklynProperties);
+        app = ApplicationBuilder.newManagedApp(TestApplication.class, ctx);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAllCatching(app.getManagementContext());
+    }
+
+    @Test(groups={"Live", "WIP"})
+    public void test_Ubuntu_13_10() throws Exception {
+          runTest(ImmutableMap.of("imageId", "7fe2ec2ff748c411cf0d6833120741778c00e1b07a83c4104296b6258b5331c4",
+              "loginUser", "root",
+              "loginUser.password", "password"));
+     }
+
+    protected void runTest(Map<String,?> flags) throws Exception {
+        String tag = getClass().getSimpleName().toLowerCase();
+        Map<String,?> allFlags = MutableMap.<String,Object>builder()
+                .put("tags", ImmutableList.of(tag))
+                .putAll(flags)
+                .build();
+        jcloudsLocation = ctx.getLocationRegistry().resolve(PROVIDER, allFlags);
+        doTest(jcloudsLocation);
+    }
+
+    protected abstract void doTest(Location loc) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java
new file mode 100644
index 0000000..67f5fc4
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessRestartIntegrationTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+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.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess.RestartSoftwareParameters;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests restart of the software *process* (as opposed to the VM).
+ */
+public abstract class AbstractSoftwareProcessRestartIntegrationTest extends BrooklynAppLiveTestSupport {
+    
+    // TODO Remove duplication from TomcatServerRestartIntegrationTest and MySqlRestartIntegrationTest
+    
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractSoftwareProcessRestartIntegrationTest.class);
+
+    protected abstract EntitySpec<? extends SoftwareProcess> newEntitySpec();
+    
+    @Test(groups="Integration")
+    public void testStopProcessAndRestart() throws Exception {
+        runStopProcessAndRestart(
+                SoftwareProcess.RESTART, 
+                ImmutableMap.of(RestartSoftwareParameters.RESTART_MACHINE.getName(), RestartSoftwareParameters.RestartMachineMode.FALSE));
+    }
+    
+    @Test(groups="Integration")
+    public void testStopProcessAndStart() throws Exception {
+        runStopProcessAndRestart(
+                SoftwareProcess.START, 
+                ImmutableMap.of("locations", ImmutableList.of()));
+    }
+    
+    protected void runStopProcessAndRestart(Effector<?> restartEffector, Map<String, ?> args) throws Exception {
+        LocalhostMachineProvisioningLocation loc = app.newLocalhostProvisioningLocation();
+        SoftwareProcess entity = app.createAndManageChild(newEntitySpec());
+        
+        // Start the app
+        app.start(ImmutableList.of(loc));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(app, SoftwareProcess.SERVICE_UP, true);
+
+        // Stop the process
+        Entities.invokeEffector(app, entity, SoftwareProcess.STOP, ImmutableMap.of(
+                StopSoftwareParameters.STOP_MACHINE_MODE.getName(), StopSoftwareParameters.StopMode.NEVER))
+                .get();
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_UP, false);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, false);
+        EntityTestUtils.assertAttributeEventually(entity, ServiceStateLogic.SERVICE_NOT_UP_INDICATORS, CollectionFunctionals.<String>mapSizeEquals(1));
+        
+        // Restart the process
+        Entities.invokeEffector(app, entity, restartEffector, args).get();
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, true);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, ServiceStateLogic.SERVICE_NOT_UP_INDICATORS, ImmutableMap.<String, Object>of());
+
+        EntityTestUtils.assertAttributeEqualsEventually(app, SoftwareProcess.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(app, SoftwareProcess.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java
new file mode 100644
index 0000000..4459db4
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcess.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.entity.core.BrooklynConfigKeys;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+
+@ImplementedBy(DoNothingSoftwareProcessImpl.class)
+public interface DoNothingSoftwareProcess extends SoftwareProcess {
+    
+    public static final ConfigKey<Boolean> SKIP_ON_BOX_BASE_DIR_RESOLUTION = ConfigKeys.newConfigKeyWithDefault(
+            BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION,
+            true);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java
new file mode 100644
index 0000000..2e0ea00
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessDriver.java
@@ -0,0 +1,69 @@
+/*
+ * 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 org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+/**
+ * Implements methods in {@link org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver}
+ * such that no actions are performed.
+ * <p>
+ * {@link #isRunning()} returns true.
+ */
+public class DoNothingSoftwareProcessDriver extends AbstractSoftwareProcessSshDriver {
+
+    public DoNothingSoftwareProcessDriver(EntityLocal entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public boolean isRunning() {
+        return true;
+    }
+
+    @Override
+    public void copyPreInstallResources() {
+    }
+
+    @Override
+    public void copyInstallResources() {
+    }
+
+    @Override
+    public void copyRuntimeResources() {
+    }
+
+    @Override
+    public void install() {
+    }
+
+    @Override
+    public void customize() {
+    }
+
+    @Override
+    public void launch() {
+    }
+
+    @Override
+    public void stop() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java
new file mode 100644
index 0000000..a04c660
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/DoNothingSoftwareProcessImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
+
+public class DoNothingSoftwareProcessImpl extends SoftwareProcessImpl implements DoNothingSoftwareProcess {
+
+    @Override
+    public Class getDriverInterface() {
+        return DoNothingSoftwareProcessDriver.class;
+    }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) {
+            setAttribute(SERVICE_UP, true);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java
new file mode 100644
index 0000000..3d87dbb
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/MachineLifecycleEffectorTasksTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.mgmt.Task;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+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.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters.StopMode;
+import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.entity.stock.BasicEntityImpl;
+import org.apache.brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.sensor.core.DependentConfiguration;
+import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.core.task.TaskInternal;
+import org.apache.brooklyn.util.time.Duration;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.apache.brooklyn.location.jclouds.BailOutJcloudsLocation;
+
+public class MachineLifecycleEffectorTasksTest {
+    public static boolean canStop(StopMode stopMode, boolean isEntityStopped) {
+        BasicEntityImpl entity = new BasicEntityImpl();
+        Lifecycle state = isEntityStopped ? Lifecycle.STOPPED : Lifecycle.RUNNING;
+        entity.setAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL, state);
+        return MachineLifecycleEffectorTasks.canStop(stopMode, entity);
+    }
+    
+    @DataProvider(name = "canStopStates")
+    public Object[][] canStopStates() {
+        return new Object[][] {
+            { StopMode.ALWAYS, true, true },
+            { StopMode.ALWAYS, false, true },
+            { StopMode.IF_NOT_STOPPED, true, false },
+            { StopMode.IF_NOT_STOPPED, false, true },
+            { StopMode.NEVER, true, false },
+            { StopMode.NEVER, false, false },
+        };
+    }
+
+    @Test(dataProvider = "canStopStates")
+    public void testBasicSonftwareProcessCanStop(StopMode mode, boolean isEntityStopped, boolean expected) {
+        boolean canStop = canStop(mode, isEntityStopped);
+        assertEquals(canStop, expected);
+    }
+
+    @Test
+    public void testProvisionLatchObeyed() throws Exception {
+
+        AttributeSensor<Boolean> ready = Sensors.newBooleanSensor("readiness");
+
+        TestApplication app = TestApplication.Factory.newManagedInstanceForTests();
+        BasicEntity triggerEntity = app.createAndManageChild(EntitySpec.create(BasicEntity.class));
+
+        EmptySoftwareProcess entity = app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class)
+                .configure(BrooklynConfigKeys.PROVISION_LATCH, DependentConfiguration.attributeWhenReady(triggerEntity, ready)));
+
+        final Task<Void> task = Entities.invokeEffector(app, app, Startable.START, ImmutableMap.of(
+                "locations", ImmutableList.of(BailOutJcloudsLocation.newBailOutJcloudsLocation(app.getManagementContext()))));
+
+        assertEffectorBlockingDetailsEventually(entity, "Waiting for config " + BrooklynConfigKeys.PROVISION_LATCH.getName());
+
+        Asserts.succeedsContinually(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(task.isDone());
+            }
+        });
+        try {
+            ((EntityLocal) triggerEntity).setAttribute(ready, true);
+            task.get(Duration.THIRTY_SECONDS);
+        } catch (Throwable t) {
+            // BailOut location throws but we don't care.
+        } finally {
+            Entities.destroyAll(app.getManagementContext());
+        }
+    }
+
+    private void assertEffectorBlockingDetailsEventually(final Entity entity, final String blockingDetailsSnippet) {
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                Task<?> entityTask = Iterables.getOnlyElement(entity.getApplication().getManagementContext().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/SameServerEntityTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java
new file mode 100644
index 0000000..937c87d
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerEntityTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.assertSame;
+import static org.testng.Assert.assertTrue;
+
+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.mgmt.ManagementContext;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.software.base.SameServerEntity;
+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.LocalhostMachineProvisioningLocation.LocalhostMachine;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class SameServerEntityTest {
+
+    private LocalhostMachineProvisioningLocation loc;
+    private ManagementContext mgmt;
+    private TestApplication app;
+    private SameServerEntity entity;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        loc = new LocalhostMachineProvisioningLocation();
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        mgmt = app.getManagementContext();
+        entity = app.createAndManageChild(EntitySpec.create(SameServerEntity.class));
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        if (app != null) Entities.destroyAll(mgmt);
+    }
+    
+    @Test
+    public void testUsesSameMachineLocationForEachChild() throws Exception {
+        Entity child1 = entity.addChild(EntitySpec.create(TestEntity.class));
+        Entity child2 = entity.addChild(EntitySpec.create(TestEntity.class));
+        Entities.manage(child1);
+        Entities.manage(child2);
+        
+        app.start(ImmutableList.of(loc));
+        
+        Location child1Loc = Iterables.getOnlyElement(child1.getLocations());
+        Location child2Loc = Iterables.getOnlyElement(child2.getLocations());
+        
+        assertSame(child1Loc, child2Loc);
+        assertTrue(child1Loc instanceof LocalhostMachine, "loc="+child1Loc);
+        
+        assertEquals(ImmutableSet.of(child1Loc), ImmutableSet.copyOf(loc.getInUse()));
+
+        app.stop();
+        
+        assertEquals(ImmutableSet.of(), ImmutableSet.copyOf(loc.getInUse()));
+    }
+}