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

[06/72] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply org.apache package prefix to software-base, tidying package names, and moving a few sensory things to core

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java
new file mode 100644
index 0000000..474ce0f
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.group;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.entity.core.EntityPredicates;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.group.DynamicClusterWithAvailabilityZonesTest;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.MultiLocation;
+import org.apache.brooklyn.test.Asserts;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * Uses {@link SoftwareProcess}, so test can't be in core project.
+ * 
+ * Different from {@link DynamicClusterWithAvailabilityZonesTest} in the use of {@link MultiLocation}.
+ * However, the difference is important: the {@link SoftwareProcess} entity has two locations
+ * (the {@link MachineProvisioningLocation} and the {@link MachineLocation}, which was previously
+ * causing a failure - now fixed and tested here.
+ */
+public class DynamicClusterWithAvailabilityZonesMultiLocationTest extends BrooklynAppUnitTestSupport {
+    
+    private DynamicCluster cluster;
+    
+    private LocalhostMachineProvisioningLocation subLoc1;
+    private LocalhostMachineProvisioningLocation subLoc2;
+    private MultiLocation<?> multiLoc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure(DynamicCluster.ENABLE_AVAILABILITY_ZONES, true)
+                .configure(DynamicCluster.INITIAL_SIZE, 0)
+                .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
+        
+        subLoc1 = app.newLocalhostProvisioningLocation(ImmutableMap.of("displayName", "loc1"));
+        subLoc2 = app.newLocalhostProvisioningLocation(ImmutableMap.of("displayName", "loc2"));
+        multiLoc = mgmt.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class)
+                .configure(MultiLocation.SUB_LOCATIONS, ImmutableList.<MachineProvisioningLocation<?>>of(subLoc1, subLoc2)));
+    }
+
+    @Test
+    public void testReplacesEntityInSameZone() throws Exception {
+        ((EntityLocal)cluster).config().set(DynamicCluster.ENABLE_AVAILABILITY_ZONES, true);
+        cluster.start(ImmutableList.of(multiLoc));
+        
+        cluster.resize(4);
+        List<String> locsUsed = getLocationNames(getLocationsOf(cluster.getMembers(), Predicates.instanceOf(MachineProvisioningLocation.class)));
+        Asserts.assertEqualsIgnoringOrder(locsUsed, ImmutableList.of("loc1", "loc1", "loc2", "loc2"));
+
+        String idToRemove = Iterables.getFirst(cluster.getMembers(), null).getId();
+        String idAdded = cluster.replaceMember(idToRemove);
+        locsUsed = getLocationNames(getLocationsOf(cluster.getMembers(), Predicates.instanceOf(MachineProvisioningLocation.class)));
+        Asserts.assertEqualsIgnoringOrder(locsUsed, ImmutableList.of("loc1", "loc1", "loc2", "loc2"));
+        assertNull(Iterables.find(cluster.getMembers(), EntityPredicates.idEqualTo(idToRemove), null));
+        assertNotNull(Iterables.find(cluster.getMembers(), EntityPredicates.idEqualTo(idAdded), null));
+    }
+    
+    protected List<Location> getLocationsOf(Iterable<? extends Entity> entities, Predicate<? super Location> filter) {
+        List<Location> result = Lists.newArrayList();
+        for (Entity entity : entities) {
+            Iterables.addAll(result, Iterables.filter(entity.getLocations(), filter));
+        }
+        return result;
+    }
+
+    protected List<String> getLocationNames(Iterable<? extends Location> locs) {
+        List<String> result = Lists.newArrayList();
+        for (Location subLoc : locs) {
+            result.add(subLoc.getDisplayName());
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/GeneralisedDynamicMBean.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/GeneralisedDynamicMBean.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/GeneralisedDynamicMBean.java
new file mode 100644
index 0000000..9c6c551
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/GeneralisedDynamicMBean.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.jmx;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.DynamicMBean;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+/**
+ * A quick-and-simple general-purpose implementation of DynamicMBean.
+ *
+ * This class provides an implementation of {@link DynamicMBean}. Its initial set of attribute names and values are
+ * provided to the constructor; from this it figures an {@link MBeanInfo}.
+ * <p>
+ * It presently assumes that all attributes are read-only; operations and notifications are not currently supported.
+ * Choosing the descriptions is not supported - they are set to be the same as the name.
+ * <p>
+ * Getting a valid dynamic MBean (in Groovy) is as simple as:
+ * <pre>
+ * new GeneralisedDynamicMBean(meaning: 42, advice: "Don't panic")
+ * </pre>
+ */
+public class GeneralisedDynamicMBean implements DynamicMBean {
+    private final MBeanInfo mBeanInfo;
+    private final Map<String,Object> attributes = Maps.newLinkedHashMap();
+    private final Map<String,Function> operations = Maps.newLinkedHashMap();
+    
+    public GeneralisedDynamicMBean(Map<String,?> initialAttributes, Map<?,?> initialOperations) {
+        attributes.putAll(initialAttributes);
+
+        for (Entry<?,?> entry : initialOperations.entrySet()) {
+            checkArgument(entry.getKey() instanceof String || entry.getKey() instanceof MBeanOperationInfo, "entry.key=%s", entry.getKey());
+            String opName = (entry.getKey() instanceof String) ? (String)entry.getKey() : ((MBeanOperationInfo)entry.getKey()).getName();
+            operations.put(opName, (Function) entry.getValue());
+        }
+        
+        Iterable<MBeanAttributeInfo> attrInfo = Iterables.transform(initialAttributes.entrySet(), new Function<Map.Entry<String,?>, MBeanAttributeInfo>() {
+            @Override public MBeanAttributeInfo apply(Map.Entry<String,?> entry) {
+                return new MBeanAttributeInfo(entry.getKey(), entry.getValue().getClass().getName(), entry.getKey(), true, false, false);
+            }
+        });
+        
+        Iterable<MBeanOperationInfo> opInfo = Iterables.transform(initialOperations.keySet(), new Function<Object, MBeanOperationInfo>() {
+            public MBeanOperationInfo apply(Object it) {
+                if (it instanceof MBeanOperationInfo) {
+                    return (MBeanOperationInfo) it;
+                } else if (it instanceof CharSequence) {
+                    return new MBeanOperationInfo(
+                            it.toString(),
+                            "my descr", 
+                            new MBeanParameterInfo[0], 
+                            "void", 
+                            MBeanOperationInfo.ACTION_INFO);
+                } else {
+                    throw new IllegalArgumentException("Cannot convert "+it+" to MBeanOperationInfo");
+                }
+            }});
+        
+        mBeanInfo = new MBeanInfo(
+                GeneralisedDynamicMBean.class.getName(), 
+                GeneralisedDynamicMBean.class.getName(), 
+                Iterables.toArray(attrInfo, MBeanAttributeInfo.class),
+                new MBeanConstructorInfo[0], 
+                Iterables.toArray(opInfo, MBeanOperationInfo.class),
+                new MBeanNotificationInfo[0]);
+    }
+
+    public void updateAttributeValue(String name, Object value) {
+        attributes.put(name, value);
+    }
+
+    @Override
+    public Object getAttribute(String s) {
+        return attributes.get(s);
+    }
+
+    @Override
+    public void setAttribute(Attribute attribute) {
+        attributes.put(attribute.getName(), attribute.getValue());
+    }
+
+    @Override
+    public AttributeList getAttributes(String[] strings) {
+        AttributeList result = new AttributeList();
+        for (Object obj : mBeanInfo.getAttributes()) {
+            Attribute attrib = (Attribute) obj;
+            result.add(new Attribute(attrib.getName(), attributes.get(attrib.getName())));
+        }
+        return result;
+    }
+
+    @Override
+    public AttributeList setAttributes(AttributeList attributeList) {
+        for (Object element : attributeList) {
+            Attribute attrib = (Attribute) element;
+            attributes.put(attrib.getName(), attrib.getValue());
+        }
+        return attributeList;
+    }
+
+    @Override
+    public Object invoke(String s, Object[] objects, String[] strings) {
+        Function op = operations.get(s);
+        if (op != null) {
+            return op.apply(objects);
+        } else {
+            throw new RuntimeException("Unknown operation "+s);
+        }
+    }
+
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        return mBeanInfo;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/JmxService.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/JmxService.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/JmxService.java
new file mode 100644
index 0000000..bb7b2a5
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/jmx/JmxService.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.jmx;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationEmitter;
+import javax.management.ObjectName;
+import javax.management.StandardEmitterMBean;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+import mx4j.tools.naming.NamingService;
+import mx4j.tools.naming.NamingServiceMBean;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.java.UsesJmx;
+import org.apache.brooklyn.sensor.feed.jmx.JmxHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Set up a JMX service ready for clients to connect. This consists of an MBean server, a connector server and a naming
+ * service.
+ */
+public class JmxService {
+    private static final Logger logger = LoggerFactory.getLogger(JmxService.class);
+
+    private MBeanServer server;
+    private NamingServiceMBean namingServiceMBean;
+    private JMXConnectorServer connectorServer;
+    private String jmxHost;
+    private int jmxPort;
+    private String url;
+
+    public JmxService() throws Exception {
+        this("localhost", 28000 + (int)Math.floor(new Random().nextDouble() * 1000));
+        logger.warn("use of deprecated default host and port in JmxService");
+    }
+    
+    /**
+     * @deprecated since 0.6.0; either needs abandoning, or updating to support JmxSupport (and JmxmpAgent, etc) */
+    public JmxService(Entity e) throws Exception {
+        this(e.getAttribute(Attributes.HOSTNAME) != null ? e.getAttribute(Attributes.HOSTNAME) : "localhost", 
+                 e.getAttribute(UsesJmx.JMX_PORT) != null ? e.getAttribute(UsesJmx.JMX_PORT) : null);
+    }
+    
+    public JmxService(String jmxHost, Integer jmxPort) throws Exception {
+        this.jmxHost = jmxHost;
+        Preconditions.checkNotNull(jmxPort, "JMX_PORT must be set when starting JmxService"); 
+        this.jmxPort = jmxPort;
+        url = JmxHelper.toRmiJmxUrl(jmxHost, jmxPort, jmxPort, "jmxrmi");
+
+        try {
+            JMXServiceURL address = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + jmxHost + ":" + jmxPort + "/jmxrmi");
+            connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, null, null);
+            server = MBeanServerFactory.createMBeanServer();
+            ObjectName cntorServerName = ObjectName.getInstance("connectors:protocol=rmi");
+            server.registerMBean(connectorServer, cntorServerName);
+    
+            ObjectName naming = new ObjectName("Naming:type=registry");
+            server.registerMBean(new NamingService(jmxPort), naming);
+            Object proxy = MBeanServerInvocationHandler.newProxyInstance(server, naming, NamingServiceMBean.class, false);
+            namingServiceMBean = (NamingServiceMBean) proxy;
+            try {
+                namingServiceMBean.start();
+            } catch (Exception e) {
+                // may take a bit of time for port to be available, if it had just been used
+                logger.warn("JmxService couldn't start test mbean ("+e+"); will delay then retry once");
+                Thread.sleep(1000);
+                namingServiceMBean.start();
+            }
+    
+            connectorServer.start();
+            logger.info("JMX tester service started at URL {}", address);
+        } catch (Exception e) {
+            try {
+                shutdown();
+            } catch (Exception e2) {
+                logger.warn("Error shutting down JmxService, after error during startup; rethrowing original error", e2);
+            }
+            throw e;
+        }
+    }
+
+    public int getJmxPort() {
+        return jmxPort;
+    }
+    
+    public void shutdown() throws IOException {
+        if (connectorServer != null) connectorServer.stop();
+        if (namingServiceMBean != null) namingServiceMBean.stop();
+        if (server != null) MBeanServerFactory.releaseMBeanServer(server);
+        connectorServer = null;
+        namingServiceMBean = null;
+        server = null;
+        logger.info("JMX tester service stopped ({}:{})", jmxHost, jmxPort);
+    }
+
+    public String getUrl() {
+        return url;
+    }
+    
+    public GeneralisedDynamicMBean registerMBean(String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
+        return registerMBean(ImmutableMap.of(), ImmutableMap.of(), name);
+    }
+
+    /**
+     * Construct a {@link GeneralisedDynamicMBean} and register it with this MBean server.
+     *
+     * @param initialAttributes a {@link Map} of attributes that make up the MBean's initial set of attributes and their * values
+     * @param name the name of the MBean
+     * @return the newly created and registered MBean
+     * @throws NullPointerException 
+     * @throws MalformedObjectNameException 
+     * @throws NotCompliantMBeanException 
+     * @throws MBeanRegistrationException 
+     * @throws InstanceAlreadyExistsException 
+     */
+    public GeneralisedDynamicMBean registerMBean(Map initialAttributes, String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
+        return registerMBean(initialAttributes, ImmutableMap.of(), name);
+    }
+    
+    public GeneralisedDynamicMBean registerMBean(Map initialAttributes, Map operations, String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
+        GeneralisedDynamicMBean mbean = new GeneralisedDynamicMBean(initialAttributes, operations);
+        server.registerMBean(mbean, new ObjectName(name));
+        return mbean;
+    }
+    
+    public StandardEmitterMBean registerMBean(List<String> notifications, String name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException {
+        String[] types = (String[]) notifications.toArray(new String[0]);
+        MBeanNotificationInfo info = new MBeanNotificationInfo(types, Notification.class.getName(), "Notification");
+        NotificationEmitter emitter = new NotificationBroadcasterSupport(info);
+        StandardEmitterMBean mbean = new StandardEmitterMBean(emitter, NotificationEmitter.class, emitter);
+        server.registerMBean(mbean, new ObjectName(name));
+        return mbean;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsEc2LiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsEc2LiveTest.java
new file mode 100644
index 0000000..b9e3d68
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsEc2LiveTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.location;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+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.MachineDetails;
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.entity.AbstractEc2LiveTest;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.location.basic.BasicMachineDetails;
+import org.apache.brooklyn.location.basic.Locations;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+// This test really belongs in brooklyn-location but depends on AbstractEc2LiveTest in brooklyn-software-base
+public class MachineDetailsEc2LiveTest extends AbstractEc2LiveTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MachineDetailsEc2LiveTest.class);
+    private static final int TIMEOUT_MS = 1000 * 60 * 10; // ten minutes
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        Entity testEntity = app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class));
+        app.start(ImmutableList.of(loc));
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS),
+                testEntity, Startable.SERVICE_UP, true);
+
+        SshMachineLocation sshLoc = Locations.findUniqueSshMachineLocation(testEntity.getLocations()).get();
+        MachineDetails machine = app.getExecutionContext()
+                .submit(BasicMachineDetails.taskForSshMachineLocation(sshLoc))
+                .getUnchecked();
+        LOG.info("Found the following at {}: {}", loc, machine);
+        assertNotNull(machine);
+        OsDetails details = machine.getOsDetails();
+        assertNotNull(details);
+        assertNotNull(details.getArch());
+        assertNotNull(details.getName());
+        assertNotNull(details.getVersion());
+        assertFalse(details.getArch().startsWith("architecture:"));
+        assertFalse(details.getName().startsWith("name:"));
+        assertFalse(details.getVersion().startsWith("version:"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsGoogleComputeLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsGoogleComputeLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsGoogleComputeLiveTest.java
new file mode 100644
index 0000000..1234f41
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/MachineDetailsGoogleComputeLiveTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.location;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+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.MachineDetails;
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.entity.AbstractGoogleComputeLiveTest;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.location.basic.BasicMachineDetails;
+import org.apache.brooklyn.location.basic.Locations;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+// This test really belongs in brooklyn-location but depends on AbstractGoogleComputeLiveTest in brooklyn-software-base
+public class MachineDetailsGoogleComputeLiveTest extends AbstractGoogleComputeLiveTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MachineDetailsGoogleComputeLiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        Entity testEntity = app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class));
+        app.start(ImmutableList.of(loc));
+        EntityTestUtils.assertAttributeEqualsEventually(testEntity, Startable.SERVICE_UP, true);
+
+        SshMachineLocation sshLoc = Locations.findUniqueSshMachineLocation(testEntity.getLocations()).get();
+        MachineDetails machine = app.getExecutionContext()
+                .submit(BasicMachineDetails.taskForSshMachineLocation(sshLoc))
+                .getUnchecked();
+        LOG.info("Found the following at {}: {}", loc, machine);
+        assertNotNull(machine);
+        OsDetails details = machine.getOsDetails();
+        assertNotNull(details);
+        assertNotNull(details.getArch());
+        assertNotNull(details.getName());
+        assertNotNull(details.getVersion());
+        assertFalse(details.getArch().startsWith("architecture:"));
+        assertFalse(details.getName().startsWith("name:"));
+        assertFalse(details.getVersion().startsWith("version:"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
new file mode 100644
index 0000000..12727fa
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.location;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+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.location.jclouds.JcloudsLocation;
+
+import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.jclouds.JcloudsWinRmMachineLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class WinRmMachineLocationLiveTest {
+    
+    // FIXME failing locally with:
+    //   Caused by: Traceback (most recent call last):
+    //     File "__pyclasspath__/winrm/__init__.py", line 40, in run_cmd
+    //     File "__pyclasspath__/winrm/protocol.py", line 118, in open_shell
+    //     File "__pyclasspath__/winrm/protocol.py", line 190, in send_message
+    //     File "__pyclasspath__/winrm/transport.py", line 112, in send_message
+    //     winrm.exceptions.WinRMTransportError: 500 WinRMTransport. [Errno 20001] getaddrinfo failed
+    //     at org.python.core.PyException.doRaise(PyException.java:226)
+
+    private static final Logger LOG = LoggerFactory.getLogger(BrooklynAppLiveTestSupport.class);
+
+    protected JcloudsLocation loc;
+    protected TestApplication app;
+    protected ManagementContextInternal mgmt;
+
+    private JcloudsWinRmMachineLocation machine;
+    
+    @BeforeClass(alwaysRun=true)
+    public void setUpClass() throws Exception {
+        mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
+        JcloudsLocation loc = (JcloudsLocation) mgmt.getLocationRegistry().resolve("jclouds:aws-ec2:us-west-2", ImmutableMap.of(
+                "inboundPorts", ImmutableList.of(5985, 3389),
+                "displayName", "AWS Oregon (Windows)",
+                "imageId", "us-west-2/ami-8fd3f9bf",
+                "hardwareId", "m3.medium",
+                "useJcloudsSshInit", false));
+        machine = (JcloudsWinRmMachineLocation) loc.obtain();
+    }
+
+    @AfterClass(alwaysRun=true)
+    public void tearDownClass() throws Exception {
+        try {
+            if (machine != null) loc.release(machine);
+            if (mgmt != null) Entities.destroyAll(mgmt);
+        } catch (Throwable t) {
+            LOG.error("Caught exception in tearDown method", t);
+        } finally {
+            mgmt = null;
+        }
+    }
+
+    @Test(groups="Live")
+    public void testExecScript() throws Exception {
+        WinRmToolResponse response = machine.executeScript("echo true");
+        assertEquals(response.getStatusCode(), 0);
+        assertEquals(response.getStdErr(), "");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationTest.java
new file mode 100644
index 0000000..8880d88
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationTest.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.software.base.test.location;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.entity.core.BrooklynConfigKeys;
+import org.apache.brooklyn.location.basic.WinRmMachineLocation;
+import org.apache.brooklyn.util.net.Networking;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class WinRmMachineLocationTest extends BrooklynAppUnitTestSupport {
+
+    @Test
+    public void testConfigurePrivateAddresses() throws Exception {
+        WinRmMachineLocation host = mgmt.getLocationManager().createLocation(LocationSpec.create(WinRmMachineLocation.class)
+                .configure("address", Networking.getLocalHost())
+                .configure(WinRmMachineLocation.PRIVATE_ADDRESSES, ImmutableList.of("1.2.3.4"))
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true));
+
+        assertEquals(host.getPrivateAddresses(), ImmutableSet.of("1.2.3.4"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/AbstractToyMySqlEntityTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/AbstractToyMySqlEntityTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/AbstractToyMySqlEntityTest.java
new file mode 100644
index 0000000..e7bc7d8
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/AbstractToyMySqlEntityTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.mysql;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+
+public abstract class AbstractToyMySqlEntityTest extends BrooklynAppLiveTestSupport {
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractToyMySqlEntityTest.class);
+    
+    protected MachineProvisioningLocation<? extends SshMachineLocation> targetLocation;
+
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        
+        targetLocation = createLocation();
+    }
+
+    protected MachineProvisioningLocation<? extends SshMachineLocation> createLocation() {
+        return mgmt.getLocationManager().createLocation(LocationSpec.create(
+                LocalhostMachineProvisioningLocation.class));
+    }
+    
+    protected abstract Entity createMysql();
+
+    // deliberately not marked as a test here so that subclasses mark it correctly (Live v Integration)
+    public void testMySqlOnProvisioningLocation() throws Exception {
+        Entity mysql = createMysql();
+        app.start(MutableList.of(targetLocation));
+        checkStartsRunning(mysql);
+        checkIsRunningAndStops(mysql, (SshMachineLocation) Iterables.getOnlyElement( mysql.getLocations() ));
+    }
+
+    protected Integer getPid(Entity mysql) {
+        return mysql.getAttribute(Attributes.PID);
+    }
+
+    protected void checkStartsRunning(Entity mysql) {
+        // should be starting within a few seconds (and almost certainly won't complete in that time)
+        EntityTestUtils.assertAttributeEventually(
+                mysql, 
+                Attributes.SERVICE_STATE_ACTUAL,
+                Predicates.or(Predicates.equalTo(Lifecycle.STARTING), Predicates.equalTo(Lifecycle.RUNNING)));
+        // should be up and running within 5m 
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", Duration.FIVE_MINUTES),
+                mysql, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
+    protected void checkIsRunningAndStops(Entity mysql, SshMachineLocation lh) {
+        Integer pid = getPid(mysql);
+        Assert.assertNotNull(pid, "PID should be set as an attribute (or getPid() overridden to supply)");
+        Entities.submit(app, SshEffectorTasks.requirePidRunning(pid).machine(lh).newTask() ).get();
+        
+        app.stop();
+
+        // let the kill -1 take effect 
+        Time.sleep(Duration.ONE_SECOND);
+        
+        // and assert it has died
+        log.info("mysql in pid "+pid+" should be dead now");
+        // (app has stopped, so submit on mgmt context)
+        ProcessTaskWrapper<Integer> t = SshEffectorTasks.codePidRunning(pid).machine(lh).newTask();
+        mgmt.getExecutionManager().submit(t);
+        Assert.assertNotEquals(t.block().getExitCode(), (Integer)0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
new file mode 100644
index 0000000..650c28c
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.mysql;
+
+import java.io.File;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityInitializer;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks;
+import org.apache.brooklyn.entity.stock.BasicStartable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.basic.BasicOsDetails.OsVersions;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.ssh.SshTasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.text.ComparableVersion;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Iterables;
+
+public class DynamicToyMySqlEntityBuilder {
+
+    private static final Logger log = LoggerFactory.getLogger(DynamicToyMySqlEntityBuilder.class);
+
+    public static EntitySpec<? extends Entity> spec() {
+        return EntitySpec.create(BasicStartable.class).addInitializer(MySqlEntityInitializer.class);
+    }
+
+    public static final String downloadUrl(Entity e, boolean isLocalhost) {
+        if (isLocalhost) {
+            for (int i=50; i>20; i--) {
+                String f = System.getProperty("user.home")+"/.brooklyn/repository/MySqlNode/5.5."+i+"/mysql-5.5."+i+"-osx10.6-x86_64.tar.gz";
+                if (new File(f).exists())
+                    return "file://"+f;
+            }
+        }
+        // download
+        String version = "5.5.37";
+        String osTag = getOsTag(e);
+        String mirrorUrl = "http://www.mirrorservice.org/sites/ftp.mysql.com/";
+        return "http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-"+version+"-"+osTag+".tar.gz/from/"+mirrorUrl;
+    }
+
+    public static final String installDir(Entity e, boolean isLocalhost) {
+        String url = downloadUrl(e, isLocalhost);
+        String archive = Iterables.find(Splitter.on('/').omitEmptyStrings().split(url), Predicates.containsPattern(".tar.gz"));
+        return archive.replace(".tar.gz", "");
+    }
+
+    public static final String dir(Entity e) {
+        return "/tmp/brooklyn-mysql-"+e.getId();
+    }
+
+    // copied from MySqlSshDriver
+    public static String getOsTag(Entity e) {
+        // e.g. "osx10.6-x86_64"; see http://www.mysql.com/downloads/mysql/#downloads
+        OsDetails os = ((SshMachineLocation)Iterables.getOnlyElement(e.getLocations())).getOsDetails();
+        if (os == null) return "linux-glibc2.5-x86_64";
+        if (os.isMac()) {
+            String osp1 = os.getVersion()==null ? "osx10.8" //lowest common denominator
+                : new ComparableVersion(os.getVersion()).isGreaterThanOrEqualTo(OsVersions.MAC_10_9) ? "osx10.9"
+                : "osx10.8";  //lowest common denominator
+            if (!os.is64bit()) {
+                throw new IllegalStateException("Only 64 bit MySQL build is available for OS X");
+            }
+            return osp1+"-x86_64";
+        }
+        //assume generic linux
+        String osp1 = "linux-glibc2.5";
+        String osp2 = os.is64bit() ? "x86_64" : "i686";
+        return osp1+"-"+osp2;
+    }
+
+    public static class MySqlEntityInitializer implements EntityInitializer {
+        public void apply(final EntityLocal entity) {
+          new MachineLifecycleEffectorTasks() {
+            @Override
+            protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) {
+                DynamicTasks.queue(
+                        SshEffectorTasks.ssh(
+                            "mkdir "+dir(entity),
+                            "cd "+dir(entity),
+                            BashCommands.downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
+                        ).summary("download mysql").returning(SshTasks.returningStdoutLoggingInfo(log, true)));
+                if (isLinux(machineS)) {
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.installPackage("libaio1")));
+                }
+                DynamicTasks.queue(
+                        SshEffectorTasks.put(".my.cnf")
+                            .contents(String.format("[mysqld]\nbasedir=%s/%s\n", dir(entity), installDir(entity, isLocalhost(machineS)))),
+                        SshEffectorTasks.ssh(
+                            "cd "+dir(entity)+"/*",
+                            "./scripts/mysql_install_db",
+                            "./support-files/mysql.server start > out.log 2> err.log < /dev/null"
+                        ).summary("setup and run mysql").returning(SshTasks.returningStdoutLoggingInfo(log, true)));
+                return "submitted start";
+            }
+            protected void postStartCustom() {
+                // if it's still up after 5s assume we are good
+                Time.sleep(Duration.FIVE_SECONDS);
+                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(dir(entity)+"/*/data/*.pid")).get()) {
+                    // but if it's not up add a bunch of other info
+                    log.warn("MySQL did not start: "+dir(entity));
+                    ProcessTaskWrapper<Integer> info = DynamicTasks.queue(SshEffectorTasks.ssh(
+                            "cd "+dir(entity)+"/*",
+                            "cat out.log",
+                            "cat err.log > /dev/stderr")).block();
+                    log.info("STDOUT:\n"+info.getStdout());
+                    log.info("STDERR:\n"+info.getStderr());
+                    BrooklynTaskTags.addTagsDynamically(Tasks.current(), 
+                        BrooklynTaskTags.tagForStream("console (nohup stdout)", Suppliers.ofInstance(info.getStdout()), null),
+                        BrooklynTaskTags.tagForStream("console (nohup stderr)", Suppliers.ofInstance(info.getStderr()), null));
+                    throw new IllegalStateException("MySQL appears not to be running");
+                }
+
+                // and set the PID
+                entity().setAttribute(Attributes.PID, 
+                        Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat "+dir(entity)+"/*/data/*.pid")).block().getStdout().trim()));
+                
+                // TODO Without this, tests fail because nothing else sets serviceUp!
+                // Really should set this with a Feed that checks pid periodically.
+                // Should this instead be using SERVICE_NOT_UP_INDICATORS?
+                entity().setAttribute(Attributes.SERVICE_UP, true);
+            }
+
+            @Override
+            protected String stopProcessesAtMachine() {
+                // TODO Where is best place to set? 
+                // Really should set this with a Feed that checks pid periodically.
+                entity().setAttribute(Attributes.SERVICE_UP, false);
+                
+                Integer pid = entity().getAttribute(Attributes.PID);
+                if (pid==null) {
+                    log.info("mysql not running");
+                    return "No pid -- is it running?";
+                }
+
+                DynamicTasks.queue(SshEffectorTasks.ssh(
+                        "cd "+dir(entity)+"/*",
+                        "./support-files/mysql.server stop"
+                    ).summary("stop mysql"));
+                return "submitted stop";
+            }
+          }.attachLifecycleEffectors(entity);
+      }
+    }
+
+    private static boolean isLocalhost(Supplier<MachineLocation> machineS) {
+        return machineS.get() instanceof LocalhostMachine;
+    }
+
+    private static boolean isLinux(Supplier<MachineLocation> machineS) {
+        return machineS.get().getMachineDetails().getOsDetails().isLinux();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityTest.java
new file mode 100644
index 0000000..d676c00
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.mysql;
+
+import java.util.Arrays;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+
+public class DynamicToyMySqlEntityTest extends AbstractToyMySqlEntityTest {
+
+    private static final Logger log = LoggerFactory.getLogger(DynamicToyMySqlEntityTest.class);
+    
+    protected Entity createMysql() {
+        Entity mysql = app.createAndManageChild(DynamicToyMySqlEntityBuilder.spec());
+        log.debug("created "+mysql);
+        return mysql;
+    }
+
+    // put right group on test (also help Eclipse IDE pick it up)
+    @Override
+    @Test(groups = "Integration")
+    public void testMySqlOnProvisioningLocation() throws Exception {
+        super.testMySqlOnProvisioningLocation();
+    }
+    
+    @Test(groups="Integration")
+    public void testMySqlOnMachineLocation() throws Exception {
+        Entity mysql = createMysql();
+        SshMachineLocation lh = targetLocation.obtain(MutableMap.of());
+        
+        app.start(Arrays.asList(lh));
+        checkStartsRunning(mysql);
+        checkIsRunningAndStops(mysql, lh);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/sensor/core/PortAttributeSensorAndConfigKeyTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/sensor/core/PortAttributeSensorAndConfigKeyTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/sensor/core/PortAttributeSensorAndConfigKeyTest.java
new file mode 100644
index 0000000..66049b5
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/sensor/core/PortAttributeSensorAndConfigKeyTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.sensor.core;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.sensor.core.PortAttributeSensorAndConfigKey;
+
+import com.google.common.collect.ImmutableList;
+
+public class PortAttributeSensorAndConfigKeyTest extends BrooklynAppUnitTestSupport {
+
+    /*
+     * FIXME Fails because port is never released. Nothing calls PortSupplier.releasePort(int).
+     * The stacktrace below shows where it is obtained:
+     * 
+        Daemon Thread [brooklyn-execmanager-XwLLLdS4-5] (Suspended (breakpoint at line 244 in LocalhostMachineProvisioningLocation$LocalhostMachine))   
+            LocalhostMachineProvisioningLocation$LocalhostMachine.obtainPort(PortRange) line: 244   
+            PortAttributeSensorAndConfigKey.convertConfigToSensor(PortRange, Entity) line: 78   
+            PortAttributeSensorAndConfigKey.convertConfigToSensor(Object, Entity) line: 1   
+            PortAttributeSensorAndConfigKey(AttributeSensorAndConfigKey<ConfigType,SensorType>).getAsSensorValue(Entity) line: 93   
+            ConfigToAttributes.apply(EntityLocal, AttributeSensorAndConfigKey<?,T>) line: 28    
+            ConfigToAttributes.apply(EntityLocal) line: 17  
+            SoftwareProcessDriverLifecycleEffectorTasks(MachineLifecycleEffectorTasks).preStartCustom(MachineLocation) line: 343    
+            SoftwareProcessDriverLifecycleEffectorTasks.preStartCustom(MachineLocation) line: 69    
+            MachineLifecycleEffectorTasks$6.run() line: 283 
+     */
+    @Test(enabled=false, groups="Integration") // test is slow (for some reason - why?)
+    public void testStoppingEntityReleasesPortFromMachineForReuse() throws Exception {
+        LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost");
+        SshMachineLocation machine = loc.obtain();
+        runStoppingEntityReleasesPortFromLocalhostForReuse(machine);
+    }
+
+    @Test(groups="Integration") // test is slow (for some reason - why?)
+    public void testStoppingEntityReleasesPortFromLocalhostProvisioningLocationForReuse() throws Exception {
+        LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost");
+        runStoppingEntityReleasesPortFromLocalhostForReuse(loc);
+    }
+    
+    protected void runStoppingEntityReleasesPortFromLocalhostForReuse(Location loc) throws Exception {
+        MyEntity e1 = app.createAndManageChild(EntitySpec.create(MyEntity.class));
+        e1.start(ImmutableList.of(loc));
+        assertEquals(e1.getAttribute(MyEntity.MY_PORT), (Integer)47653);
+        
+        e1.stop();
+        Entities.unmanage(e1);
+        MyEntity e2 = app.createAndManageChild(EntitySpec.create(MyEntity.class));
+        e2.start(ImmutableList.of(loc));
+        assertEquals(e2.getAttribute(MyEntity.MY_PORT), (Integer)47653);
+    }
+
+    @ImplementedBy(MyEntityImpl.class)
+    public interface MyEntity extends EmptySoftwareProcess {
+        PortAttributeSensorAndConfigKey MY_PORT = new PortAttributeSensorAndConfigKey("myport", "", "47653");
+    }
+    
+    public static class MyEntityImpl extends EmptySoftwareProcessImpl implements MyEntity {
+    }        
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
new file mode 100644
index 0000000..c51725c
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.ssh;
+
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.effector.core.Effectors;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.sensor.ssh.SshCommandEffector;
+import org.apache.brooklyn.sensor.ssh.SshCommandSensor;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.time.Duration;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import com.google.common.collect.ImmutableList;
+
+public class SshCommandIntegrationTest {
+
+    final static AttributeSensor<String> SENSOR_STRING = Sensors.newStringSensor("aString", "");
+    final static AttributeSensor<Integer> SENSOR_INT = Sensors.newIntegerSensor("aLong", "");
+    final static Effector<String> EFFECTOR_SAY_HI = Effectors.effector(String.class, "sayHi").buildAbstract();
+
+    private TestApplication app;
+    private SshMachineLocation machine;
+    private EntityLocal entity;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        machine = app.newLocalhostProvisioningLocation().obtain();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
+        app.start(ImmutableList.<Location>of());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test(groups="Integration")
+    public void testSshSensor() throws Exception {
+        File tempFile = File.createTempFile("testSshCommand", "txt");
+        tempFile.deleteOnExit();
+        new SshCommandSensor<String>(ConfigBag.newInstance()
+                .configure(SshCommandSensor.SENSOR_PERIOD, Duration.millis(100))
+                .configure(SshCommandSensor.SENSOR_NAME, SENSOR_STRING.getName())
+                .configure(SshCommandSensor.SENSOR_COMMAND, "echo foo > "+tempFile.getAbsolutePath()+"\n"
+                    + "wc "+tempFile.getAbsolutePath()))
+            .apply(entity);
+        entity.setAttribute(Attributes.SERVICE_UP, true);
+
+        String val = EntityTestUtils.assertAttributeEventuallyNonNull(entity, SENSOR_STRING);
+        assertTrue(val.contains("1"), "val="+val);
+        String[] counts = val.trim().split("\\s+");
+        Assert.assertEquals(counts.length, 4, "val="+val);
+        Assert.assertEquals(counts[0], "1", "val="+val);
+    }
+
+    @Test(groups="Integration")
+    public void testSshEffector() throws Exception {
+        File tempFile = File.createTempFile("testSshCommand", "txt");
+        tempFile.deleteOnExit();
+        new SshCommandEffector(ConfigBag.newInstance()
+                .configure(SshCommandEffector.EFFECTOR_NAME, "sayHi")
+                .configure(SshCommandEffector.EFFECTOR_COMMAND, "echo hi"))
+            .apply(entity);
+        
+        String val = entity.invoke(EFFECTOR_SAY_HI, MutableMap.<String,String>of()).get();
+        Assert.assertEquals(val.trim(), "hi", "val="+val);
+    }
+
+    @Test(groups="Integration")
+    public void testSshEffectorWithParameters() throws Exception {
+        File tempFile = File.createTempFile("testSshCommand", "txt");
+        tempFile.deleteOnExit();
+        new SshCommandEffector(ConfigBag.newInstance()
+                .configure(SshCommandEffector.EFFECTOR_NAME, "sayHi")
+                .configure(SshCommandEffector.EFFECTOR_COMMAND, "echo $foo")
+                .configure(SshCommandEffector.EFFECTOR_PARAMETER_DEFS, 
+                    MutableMap.<String,Object>of("foo", MutableMap.of("defaultValue", "hi"))))
+            .apply(entity);
+        
+        String val;
+        // explicit value
+        val = entity.invoke(EFFECTOR_SAY_HI, MutableMap.<String,String>of("foo", "bar")).get();
+        Assert.assertEquals(val.trim(), "bar", "val="+val);
+        
+        // default value
+        val = entity.invoke(EFFECTOR_SAY_HI, MutableMap.<String,String>of()).get();
+        Assert.assertEquals(val.trim(), "hi", "val="+val);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
new file mode 100644
index 0000000..87ad639
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.system_service;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+import org.apache.brooklyn.effector.core.EffectorTasks;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.core.EntityPredicates;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
+import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcessImpl;
+import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcessSshDriver;
+import org.apache.brooklyn.entity.system_service.SystemServiceEnricher;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class SystemServiceEnricherTest extends BrooklynAppLiveTestSupport {
+    //requires /etc/init.d OS, for example CentOS 6.5
+    private static final String LOCATION_SPEC = "named:service-live-test-location";
+    private JcloudsLocation location;
+
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        location = (JcloudsLocation) mgmt.getLocationRegistry().resolve(LOCATION_SPEC);
+    }
+
+    @Test(groups = "Live")
+    public void testRestartLaunchesService() {
+        String launchCmd = "nohup bash -c \"echo \\$\\$ > $PID_FILE; while true; do sleep 1000; done\" &";
+        EntitySpec<VanillaSoftwareProcess> procSpec = EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, launchCmd)
+                .enricher(EnricherSpec.create(SystemServiceEnricher.class));
+        VanillaSoftwareProcess proc = app.createAndManageChild(procSpec);
+        app.start(ImmutableList.of(location));
+
+        waitHealthy(proc);
+
+        SshMachineLocation machine = EffectorTasks.getSshMachine(proc);
+        String pidFile = getPidFile(proc);
+        String killCmd = "kill -9 `cat " + pidFile + "`";
+        machine.execCommands("kill process", ImmutableList.of(killCmd));
+
+        waitFailed(proc);
+
+        int restartCode = machine.execCommands("restart machine", ImmutableList.of(BashCommands.sudo("/sbin/shutdown -r now")));
+        assertEquals(restartCode, 0);
+
+        waitHealthy(proc);
+    }
+
+    private String getPidFile(VanillaSoftwareProcess proc) {
+        VanillaSoftwareProcessImpl impl = (VanillaSoftwareProcessImpl)Entities.deproxy(proc);
+        return ((VanillaSoftwareProcessSshDriver)impl.getDriver()).getPidFile();
+    }
+
+    private void waitFailed(VanillaSoftwareProcess proc) {
+        Asserts.eventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), Suppliers.ofInstance(proc), EntityPredicates.attributeEqualTo(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE));
+    }
+
+    private void waitHealthy(VanillaSoftwareProcess proc) {
+        Asserts.eventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), Suppliers.ofInstance(proc), EntityPredicates.attributeEqualTo(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/location/basic/MachineDetailsEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsEc2LiveTest.java b/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsEc2LiveTest.java
deleted file mode 100644
index 39f787f..0000000
--- a/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsEc2LiveTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.location.basic;
-
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-
-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.MachineDetails;
-import org.apache.brooklyn.api.location.OsDetails;
-import org.apache.brooklyn.entity.trait.Startable;
-import org.apache.brooklyn.test.EntityTestUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.ImmutableList;
-
-import brooklyn.entity.AbstractEc2LiveTest;
-import brooklyn.entity.basic.EmptySoftwareProcess;
-
-// This test really belongs in brooklyn-location but depends on AbstractEc2LiveTest in brooklyn-software-base
-public class MachineDetailsEc2LiveTest extends AbstractEc2LiveTest {
-
-    private static final Logger LOG = LoggerFactory.getLogger(MachineDetailsEc2LiveTest.class);
-    private static final int TIMEOUT_MS = 1000 * 60 * 10; // ten minutes
-
-    @Override
-    protected void doTest(Location loc) throws Exception {
-        Entity testEntity = app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class));
-        app.start(ImmutableList.of(loc));
-        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS),
-                testEntity, Startable.SERVICE_UP, true);
-
-        SshMachineLocation sshLoc = Locations.findUniqueSshMachineLocation(testEntity.getLocations()).get();
-        MachineDetails machine = app.getExecutionContext()
-                .submit(BasicMachineDetails.taskForSshMachineLocation(sshLoc))
-                .getUnchecked();
-        LOG.info("Found the following at {}: {}", loc, machine);
-        assertNotNull(machine);
-        OsDetails details = machine.getOsDetails();
-        assertNotNull(details);
-        assertNotNull(details.getArch());
-        assertNotNull(details.getName());
-        assertNotNull(details.getVersion());
-        assertFalse(details.getArch().startsWith("architecture:"));
-        assertFalse(details.getName().startsWith("name:"));
-        assertFalse(details.getVersion().startsWith("version:"));
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsGoogleComputeLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsGoogleComputeLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsGoogleComputeLiveTest.java
deleted file mode 100644
index 6f2a06a..0000000
--- a/software/base/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsGoogleComputeLiveTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.location.basic;
-
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-
-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.MachineDetails;
-import org.apache.brooklyn.api.location.OsDetails;
-import org.apache.brooklyn.entity.trait.Startable;
-import org.apache.brooklyn.test.EntityTestUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.ImmutableList;
-
-import brooklyn.entity.AbstractGoogleComputeLiveTest;
-import brooklyn.entity.basic.EmptySoftwareProcess;
-
-// This test really belongs in brooklyn-location but depends on AbstractGoogleComputeLiveTest in brooklyn-software-base
-public class MachineDetailsGoogleComputeLiveTest extends AbstractGoogleComputeLiveTest {
-
-    private static final Logger LOG = LoggerFactory.getLogger(MachineDetailsGoogleComputeLiveTest.class);
-
-    @Override
-    protected void doTest(Location loc) throws Exception {
-        Entity testEntity = app.createAndManageChild(EntitySpec.create(EmptySoftwareProcess.class));
-        app.start(ImmutableList.of(loc));
-        EntityTestUtils.assertAttributeEqualsEventually(testEntity, Startable.SERVICE_UP, true);
-
-        SshMachineLocation sshLoc = Locations.findUniqueSshMachineLocation(testEntity.getLocations()).get();
-        MachineDetails machine = app.getExecutionContext()
-                .submit(BasicMachineDetails.taskForSshMachineLocation(sshLoc))
-                .getUnchecked();
-        LOG.info("Found the following at {}: {}", loc, machine);
-        assertNotNull(machine);
-        OsDetails details = machine.getOsDetails();
-        assertNotNull(details);
-        assertNotNull(details.getArch());
-        assertNotNull(details.getName());
-        assertNotNull(details.getVersion());
-        assertFalse(details.getArch().startsWith("architecture:"));
-        assertFalse(details.getName().startsWith("name:"));
-        assertFalse(details.getVersion().startsWith("version:"));
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationLiveTest.java
deleted file mode 100644
index 1651f15..0000000
--- a/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationLiveTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.location.basic;
-
-import static org.testng.Assert.assertEquals;
-
-import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
-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.location.jclouds.JcloudsLocation;
-
-import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.location.jclouds.JcloudsWinRmMachineLocation;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-public class WinRmMachineLocationLiveTest {
-    
-    // FIXME failing locally with:
-    //   Caused by: Traceback (most recent call last):
-    //     File "__pyclasspath__/winrm/__init__.py", line 40, in run_cmd
-    //     File "__pyclasspath__/winrm/protocol.py", line 118, in open_shell
-    //     File "__pyclasspath__/winrm/protocol.py", line 190, in send_message
-    //     File "__pyclasspath__/winrm/transport.py", line 112, in send_message
-    //     winrm.exceptions.WinRMTransportError: 500 WinRMTransport. [Errno 20001] getaddrinfo failed
-    //     at org.python.core.PyException.doRaise(PyException.java:226)
-
-    private static final Logger LOG = LoggerFactory.getLogger(BrooklynAppLiveTestSupport.class);
-
-    protected JcloudsLocation loc;
-    protected TestApplication app;
-    protected ManagementContextInternal mgmt;
-
-    private JcloudsWinRmMachineLocation machine;
-    
-    @BeforeClass(alwaysRun=true)
-    public void setUpClass() throws Exception {
-        mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
-        JcloudsLocation loc = (JcloudsLocation) mgmt.getLocationRegistry().resolve("jclouds:aws-ec2:us-west-2", ImmutableMap.of(
-                "inboundPorts", ImmutableList.of(5985, 3389),
-                "displayName", "AWS Oregon (Windows)",
-                "imageId", "us-west-2/ami-8fd3f9bf",
-                "hardwareId", "m3.medium",
-                "useJcloudsSshInit", false));
-        machine = (JcloudsWinRmMachineLocation) loc.obtain();
-    }
-
-    @AfterClass(alwaysRun=true)
-    public void tearDownClass() throws Exception {
-        try {
-            if (machine != null) loc.release(machine);
-            if (mgmt != null) Entities.destroyAll(mgmt);
-        } catch (Throwable t) {
-            LOG.error("Caught exception in tearDown method", t);
-        } finally {
-            mgmt = null;
-        }
-    }
-
-    @Test(groups="Live")
-    public void testExecScript() throws Exception {
-        WinRmToolResponse response = machine.executeScript("echo true");
-        assertEquals(response.getStatusCode(), 0);
-        assertEquals(response.getStdErr(), "");
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationTest.java b/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationTest.java
deleted file mode 100644
index ea00d15..0000000
--- a/software/base/src/test/java/org/apache/brooklyn/location/basic/WinRmMachineLocationTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.location.basic;
-
-import static org.testng.Assert.assertEquals;
-
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
-import org.apache.brooklyn.entity.core.BrooklynConfigKeys;
-import org.apache.brooklyn.util.net.Networking;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-public class WinRmMachineLocationTest extends BrooklynAppUnitTestSupport {
-
-    @Test
-    public void testConfigurePrivateAddresses() throws Exception {
-        WinRmMachineLocation host = mgmt.getLocationManager().createLocation(LocationSpec.create(WinRmMachineLocation.class)
-                .configure("address", Networking.getLocalHost())
-                .configure(WinRmMachineLocation.PRIVATE_ADDRESSES, ImmutableList.of("1.2.3.4"))
-                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true));
-
-        assertEquals(host.getPrivateAddresses(), ImmutableSet.of("1.2.3.4"));
-    }
-}