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()));
+    }
+}

Reply via email to