You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/14 05:42:52 UTC

[23/54] incubator-brooklyn git commit: [BROOKLYN-162] Renaming package brooklyn.location

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/LocationExtensionsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/LocationExtensionsTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/LocationExtensionsTest.java
new file mode 100644
index 0000000..1b5b743
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/LocationExtensionsTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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 static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationSpec;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class LocationExtensionsTest {
+
+    public static class ConcreteLocation extends AbstractLocation {
+        private static final long serialVersionUID = 2407231019435442876L;
+
+        public ConcreteLocation() {
+            super();
+        }
+    }
+
+    public interface MyExtension {
+    }
+    
+    public static class MyExtensionImpl implements MyExtension {
+    }
+    
+    private ManagementContext mgmt;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        mgmt = new LocalManagementContextForTests();
+    }
+    
+    @AfterMethod(alwaysRun = true)
+    public void tearDown(){
+        if (mgmt!=null) Entities.destroyAll(mgmt);
+    }
+
+    private ConcreteLocation createConcrete() {
+        return mgmt.getLocationManager().createLocation(LocationSpec.create(ConcreteLocation.class));
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private ConcreteLocation createConcrete(Class<?> extensionType, Object extension) {
+        // this cast is needed to make IntelliJ happy
+        return (ConcreteLocation) mgmt.getLocationManager().createLocation(LocationSpec.create(ConcreteLocation.class).extension((Class)extensionType, extension));
+    }
+    
+    @Test
+    public void testHasExtensionWhenMissing() {
+        Location loc = createConcrete();
+        assertFalse(loc.hasExtension(MyExtension.class));
+    }
+
+    @Test
+    public void testWhenExtensionPresent() {
+        MyExtension extension = new MyExtensionImpl();
+        ConcreteLocation loc = createConcrete();
+        loc.addExtension(MyExtension.class, extension);
+        
+        assertTrue(loc.hasExtension(MyExtension.class));
+        assertEquals(loc.getExtension(MyExtension.class), extension);
+    }
+
+    @Test
+    public void testAddExtensionThroughLocationSpec() {
+        MyExtension extension = new MyExtensionImpl();
+        Location loc = createConcrete(MyExtension.class, extension);
+        
+        assertTrue(loc.hasExtension(MyExtension.class));
+        assertEquals(loc.getExtension(MyExtension.class), extension);
+    }
+
+    @Test
+    public void testGetExtensionWhenMissing() {
+        Location loc = createConcrete();
+
+        try {
+            loc.getExtension(MyExtension.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+        
+        try {
+            loc.getExtension(null);
+            fail();
+        } catch (NullPointerException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testWhenExtensionDifferent() {
+        MyExtension extension = new MyExtensionImpl();
+        ConcreteLocation loc = createConcrete();
+        loc.addExtension(MyExtension.class, extension);
+        
+        assertFalse(loc.hasExtension(Object.class));
+        
+        try {
+            loc.getExtension(Object.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+
+    @Test
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public void testAddExtensionIllegally() {
+        MyExtension extension = new MyExtensionImpl();
+        ConcreteLocation loc = createConcrete();
+        
+        try {
+            loc.addExtension((Class)MyExtension.class, "not an extension");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+        
+        try {
+            loc.addExtension(MyExtension.class, null);
+            fail();
+        } catch (NullPointerException e) {
+            // success
+        }
+        
+        try {
+            loc.addExtension(null, extension);
+            fail();
+        } catch (NullPointerException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testAddExtensionThroughLocationSpecIllegally() {
+        MyExtension extension = new MyExtensionImpl();
+        
+        try {
+            Location loc = createConcrete(MyExtension.class, "not an extension");
+            fail("loc="+loc);
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+        
+        try {
+            Location loc = createConcrete(MyExtension.class, null);
+            fail("loc="+loc);
+        } catch (NullPointerException e) {
+            // success
+        }
+        
+        try {
+            Location loc = createConcrete(null, extension);
+            fail("loc="+loc);
+        } catch (NullPointerException e) {
+            // success
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/LocationManagementTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/LocationManagementTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/LocationManagementTest.java
new file mode 100644
index 0000000..be68557
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/LocationManagementTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.management.LocationManager;
+import org.apache.brooklyn.location.LocationSpec;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class LocationManagementTest extends BrooklynAppUnitTestSupport {
+
+    private LocationManager locationManager;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        locationManager = mgmt.getLocationManager();
+    }
+    
+    @Test
+    public void testCreateLocationUsingSpec() {
+        SshMachineLocation loc = locationManager.createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", "1.2.3.4"));
+        
+        assertEquals(loc.getAddress().getHostAddress(), "1.2.3.4");
+        assertSame(locationManager.getLocation(loc.getId()), loc);
+    }
+    
+    @Test
+    public void testCreateLocationUsingResolver() {
+        String spec = "byon:(hosts=\"1.1.1.1\")";
+        FixedListMachineProvisioningLocation<SshMachineLocation> loc = (FixedListMachineProvisioningLocation<SshMachineLocation>) mgmt.getLocationRegistry().resolve(spec);
+        SshMachineLocation machine = Iterables.getOnlyElement(loc.getAllMachines());
+        
+        assertSame(locationManager.getLocation(loc.getId()), loc);
+        assertSame(locationManager.getLocation(machine.getId()), machine);
+    }
+    
+    @Test
+    public void testChildrenOfManagedLocationAutoManaged() {
+        String spec = "byon:(hosts=\"1.1.1.1\")";
+        FixedListMachineProvisioningLocation<SshMachineLocation> loc = (FixedListMachineProvisioningLocation<SshMachineLocation>) mgmt.getLocationRegistry().resolve(spec);
+        SshMachineLocation machine = new SshMachineLocation(ImmutableMap.of("address", "1.2.3.4"));
+
+        loc.addChild(machine);
+        assertSame(locationManager.getLocation(machine.getId()), machine);
+        assertTrue(machine.isManaged());
+        
+        loc.removeChild(machine);
+        assertNull(locationManager.getLocation(machine.getId()));
+        assertFalse(machine.isManaged());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/LocationPredicatesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/LocationPredicatesTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/LocationPredicatesTest.java
new file mode 100644
index 0000000..3324477
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/LocationPredicatesTest.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.location.basic;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.internal.LocalManagementContext;
+
+public class LocationPredicatesTest {
+
+    private LocalManagementContext managementContext;
+    private LocalhostMachineProvisioningLocation loc;
+    private SshMachineLocation childLoc;
+    private Location grandchildLoc;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        loc = (LocalhostMachineProvisioningLocation) managementContext.getLocationRegistry().resolve("localhost:(name=mydisplayname)");
+        childLoc = loc.obtain();
+        grandchildLoc = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).parent(childLoc));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+    
+    @Test
+    public void testIdEqualTo() throws Exception {
+        assertTrue(LocationPredicates.idEqualTo(loc.getId()).apply(loc));
+        assertFalse(LocationPredicates.idEqualTo("wrongid").apply(loc));
+    }
+    
+    @Test
+    public void testConfigEqualTo() throws Exception {
+        loc.setConfig(TestEntity.CONF_NAME, "myname");
+        assertTrue(LocationPredicates.configEqualTo(TestEntity.CONF_NAME, "myname").apply(loc));
+        assertFalse(LocationPredicates.configEqualTo(TestEntity.CONF_NAME, "wrongname").apply(loc));
+    }
+    
+    @Test
+    public void testDisplayNameEqualTo() throws Exception {
+        assertTrue(LocationPredicates.displayNameEqualTo("mydisplayname").apply(loc));
+        assertFalse(LocationPredicates.displayNameEqualTo("wrongname").apply(loc));
+    }
+    
+    @Test
+    public void testIsChildOf() throws Exception {
+        assertTrue(LocationPredicates.isChildOf(loc).apply(childLoc));
+        assertFalse(LocationPredicates.isChildOf(loc).apply(loc));
+        assertFalse(LocationPredicates.isChildOf(childLoc).apply(loc));
+    }
+    
+    @Test
+    public void testIsDescendantOf() throws Exception {
+        assertTrue(LocationPredicates.isDescendantOf(loc).apply(grandchildLoc));
+        assertTrue(LocationPredicates.isDescendantOf(loc).apply(childLoc));
+        assertFalse(LocationPredicates.isDescendantOf(loc).apply(loc));
+        assertFalse(LocationPredicates.isDescendantOf(childLoc).apply(loc));
+    }
+    
+    @Test
+    public void testManaged() throws Exception {
+        // TODO get exception in LocalhostMachineProvisioningLocation.removeChild because childLoc is "in use";
+        // this happens from the call to unmanage(loc), which first unmanaged the children.
+        loc.release(childLoc);
+        
+        assertTrue(LocationPredicates.managed().apply(loc));
+        Locations.unmanage(loc);
+        assertFalse(LocationPredicates.managed().apply(loc));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynPropertiesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynPropertiesTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynPropertiesTest.java
new file mode 100644
index 0000000..8be1689
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynPropertiesTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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 java.util.Map;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Maps;
+
+public class LocationPropertiesFromBrooklynPropertiesTest {
+
+    private LocationPropertiesFromBrooklynProperties parser;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        parser = new LocationPropertiesFromBrooklynProperties();
+    }
+    
+    @Test
+    public void testExtractProviderProperties() throws Exception {
+        String provider = "myprovider";
+        String namedLocation = null;
+        
+        Map<String, String> properties = Maps.newLinkedHashMap();
+        
+        // prefer those in "named" over everything else
+        properties.put("brooklyn.location.myprovider.privateKeyFile", "privateKeyFile-inProviderSpecific");
+        properties.put("brooklyn.location.privateKeyFile", "privateKeyFile-inLocationGeneric");
+
+        // prefer location-generic if nothing else
+        properties.put("brooklyn.location.publicKeyFile", "publicKeyFile-inLocationGeneric");
+        
+        Map<String, Object> conf = parser.getLocationProperties(provider, namedLocation, properties);
+        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inProviderSpecific");
+        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inLocationGeneric");
+    }
+
+    @Test
+    public void testExtractNamedLocationProperties() throws Exception {
+        String provider = "myprovider";
+        String namedLocation = "mynamed";
+        
+        Map<String, String> properties = Maps.newLinkedHashMap();
+        
+        properties.put("brooklyn.location.named.mynamed", "myprovider");
+        
+        // prefer those in "named" over everything else
+        properties.put("brooklyn.location.named.mynamed.privateKeyFile", "privateKeyFile-inNamed");
+        properties.put("brooklyn.location.myprovider.privateKeyFile", "privateKeyFile-inProviderSpecific");
+        properties.put("brooklyn.location.privateKeyFile", "privateKeyFile-inGeneric");
+
+        // prefer those in provider-specific over generic
+        properties.put("brooklyn.location.myprovider.publicKeyFile", "publicKeyFile-inProviderSpecific");
+        properties.put("brooklyn.location.publicKeyFile", "publicKeyFile-inGeneric");
+
+        // prefer location-generic if nothing else
+        properties.put("brooklyn.location.privateKeyData", "privateKeyData-inGeneric");
+
+        Map<String, Object> conf = parser.getLocationProperties(provider, namedLocation, properties);
+        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
+        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inProviderSpecific");
+        assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric");
+    }
+
+    @Test
+    public void testConvertsDeprecatedFormats() throws Exception {
+        String provider = "myprovider";
+        String namedLocation = "mynamed";
+        
+        Map<String, String> properties = Maps.newLinkedHashMap();
+        
+        properties.put("brooklyn.location.named.mynamed", "myprovider");
+        
+        // prefer those in "named" over everything else
+        properties.put("brooklyn.location.named.mynamed.private-key-file", "privateKeyFile-inNamed");
+        properties.put("brooklyn.location.myprovider.public-key-file", "publicKeyFile-inProviderSpecific");
+        properties.put("brooklyn.location.private-key-data", "privateKeyData-inGeneric");
+
+        Map<String, Object> conf = parser.getLocationProperties(provider, namedLocation, properties);
+        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
+        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inProviderSpecific");
+        assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric");
+    }
+    
+
+    @Test
+    public void testThrowsIfProviderDoesNotMatchNamed() throws Exception {
+        String provider = "myprovider";
+        String namedLocation = "mynamed";
+        
+        Map<String, String> properties = Maps.newLinkedHashMap();
+        
+        properties.put("brooklyn.location.named.mynamed", "completelydifferent");
+
+        try {
+            Map<String, Object> conf = parser.getLocationProperties(provider, namedLocation, properties);
+        } catch (IllegalStateException e) {
+            if (!e.toString().contains("Conflicting configuration")) throw e;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/LocationRegistryTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/LocationRegistryTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/LocationRegistryTest.java
new file mode 100644
index 0000000..ed8a96d
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/LocationRegistryTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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 org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.LocationDefinition;
+import brooklyn.management.internal.LocalManagementContext;
+
+public class LocationRegistryTest {
+    
+    private static final Logger log = LoggerFactory.getLogger(LocationRegistryTest.class);
+    
+    private LocalManagementContext mgmt;
+    private LocationDefinition locdef;
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown(){
+        if (mgmt != null) Entities.destroyAll(mgmt);
+    }
+
+    @Test
+    public void testNamedLocationsPropertyDefinedLocations() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.named.foo", "byon:(hosts=\"root@192.168.1.{1,2,3,4}\")");
+        properties.put("brooklyn.location.named.foo.privateKeyFile", "~/.ssh/foo.id_rsa");
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+        log.info("foo properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations());
+        locdef = mgmt.getLocationRegistry().getDefinedLocationByName("foo");
+        Assert.assertNotNull(locdef, "Expected 'foo' location; but had "+mgmt.getLocationRegistry().getDefinedLocations());
+        Assert.assertEquals(locdef.getConfig().get("privateKeyFile"), "~/.ssh/foo.id_rsa");
+    }
+    
+    @Test(dependsOnMethods="testNamedLocationsPropertyDefinedLocations")
+    public void testResolvesByNamedAndId() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.named.foo", "byon:(hosts=\"root@192.168.1.{1,2,3,4}\")");
+        properties.put("brooklyn.location.named.foo.privateKeyFile", "~/.ssh/foo.id_rsa");
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+
+        locdef = mgmt.getLocationRegistry().getDefinedLocationByName("foo");
+        log.info("testResovlesBy has defined locations: "+mgmt.getLocationRegistry().getDefinedLocations());
+        
+        Location l = mgmt.getLocationRegistry().resolve("named:foo");
+        Assert.assertNotNull(l);
+        Assert.assertEquals(l.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE), "~/.ssh/foo.id_rsa");
+        
+        l = mgmt.getLocationRegistry().resolve("foo");
+        Assert.assertNotNull(l);
+        Assert.assertEquals(l.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE), "~/.ssh/foo.id_rsa");
+        
+        l = mgmt.getLocationRegistry().resolve("id:"+locdef.getId());
+        Assert.assertNotNull(l);
+        Assert.assertEquals(l.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE), "~/.ssh/foo.id_rsa");
+        
+        l = mgmt.getLocationRegistry().resolve(locdef.getId());
+        Assert.assertNotNull(l);
+        Assert.assertEquals(l.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE), "~/.ssh/foo.id_rsa");
+    }
+
+    @Test
+    public void testLocationGetsDisplayName() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.named.foo", "byon:(hosts=\"root@192.168.1.{1,2,3,4}\")");
+        properties.put("brooklyn.location.named.foo.displayName", "My Foo");
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+        Location l = mgmt.getLocationRegistry().resolve("foo");
+        Assert.assertEquals(l.getDisplayName(), "My Foo");
+    }
+    
+    @Test
+    public void testLocationGetsDefaultDisplayName() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.named.foo", "byon:(hosts=\"root@192.168.1.{1,2,3,4}\")");
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+        Location l = mgmt.getLocationRegistry().resolve("foo");
+        Assert.assertNotNull(l.getDisplayName());
+        Assert.assertTrue(l.getDisplayName().startsWith(FixedListMachineProvisioningLocation.class.getSimpleName()), "name="+l.getDisplayName());
+        // TODO currently it gives default name; it would be nice to use 'foo', 
+        // or at least to have access to the spec (and use it e.g. in places such as DynamicFabric)
+        // Assert.assertEquals(l.getDisplayName(), "foo");
+    }
+    
+    @Test
+    public void testSetupForTesting() {
+        mgmt = LocalManagementContextForTests.newInstance();
+        BasicLocationRegistry.setupLocationRegistryForTesting(mgmt);
+        Assert.assertNotNull(mgmt.getLocationRegistry().getDefinedLocationByName("localhost"));
+    }
+
+    @Test
+    public void testCircularReference() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.named.bar", "named:bar");
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+        log.info("bar properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations());
+        boolean resolved = false;
+        try {
+            mgmt.getLocationRegistry().resolve("bar");
+            resolved = true;
+        } catch (IllegalStateException e) {
+            //expected
+            log.info("bar properties correctly caught circular reference: "+e);
+        }
+        if (resolved)
+            // probably won't happen, if test fails will loop endlessly above
+            Assert.fail("Circular reference resolved location");
+    }
+
+    protected boolean findLocationMatching(String regex) {
+        for (LocationDefinition d: mgmt.getLocationRegistry().getDefinedLocations().values()) {
+            if (d.getName()!=null && d.getName().matches(regex)) return true;
+        }
+        return false;
+    }
+    
+    @Test
+    public void testLocalhostEnabled() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.localhost.enabled", true);
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+        Assert.assertTrue( findLocationMatching("localhost") );
+    }
+
+    @Test
+    public void testLocalhostDisabled() {
+        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
+        properties.put("brooklyn.location.localhost.enabled", false);
+        mgmt = LocalManagementContextForTests.newInstance(properties);
+        log.info("RESOLVERS: "+mgmt.getLocationRegistry().getDefinedLocations());
+        log.info("DEFINED LOCATIONS: "+mgmt.getLocationRegistry().getDefinedLocations());
+        Assert.assertFalse( findLocationMatching("localhost") );
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsTest.java
new file mode 100644
index 0000000..7b36172
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/MachineDetailsTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.util.Arrays;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.Task;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.MachineDetails;
+import org.apache.brooklyn.location.OsDetails;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+
+public class MachineDetailsTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SshMachineLocationTest.class);
+
+    TestApplication app;
+    ManagementContext mgmt;
+    SshMachineLocation host;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setup() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        mgmt = app.getManagementContext();
+
+        LocalhostMachineProvisioningLocation localhost = mgmt.getLocationManager().createLocation(
+                LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+        host = localhost.obtain();
+        app.start(Arrays.asList(host));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (mgmt != null) Entities.destroyAll(mgmt);
+        mgmt = null;
+    }
+
+    @Test(groups = "Integration")
+    public void testGetMachineDetails() {
+        Task<BasicMachineDetails> detailsTask = app.getExecutionContext().submit(
+                BasicMachineDetails.taskForSshMachineLocation(host));
+        MachineDetails machine = detailsTask.getUnchecked();
+        LOG.info("Found the following on localhost: {}", machine);
+        assertNotNull(machine);
+        OsDetails details = machine.getOsDetails();
+        assertNotNull(details);
+        assertNotNull(details.getArch());
+        assertNotNull(details.getName());
+        assertNotNull(details.getVersion());
+        assertFalse(details.getArch().startsWith("architecture:"), "architecture prefix not removed from details");
+        assertFalse(details.getName().startsWith("name:"), "name prefix not removed from details");
+        assertFalse(details.getVersion().startsWith("version:"), "version prefix not removed from details");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationRebindTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationRebindTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationRebindTest.java
new file mode 100644
index 0000000..25c2e9e
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationRebindTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 java.io.File;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.rebind.RebindTestUtils;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.net.Networking;
+import brooklyn.util.os.Os;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class MultiLocationRebindTest {
+
+    private ClassLoader classLoader = getClass().getClassLoader();
+    private ManagementContext origManagementContext;
+    private ManagementContext newManagementContext;
+    private File mementoDir;
+    
+    private TestApplication origApp;
+    private TestApplication newApp;
+    private SshMachineLocation mac1a;
+    private SshMachineLocation mac2a;
+    private FixedListMachineProvisioningLocation<SshMachineLocation> loc1;
+    private FixedListMachineProvisioningLocation<SshMachineLocation> loc2;
+    private MultiLocation<SshMachineLocation> multiLoc;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        mementoDir = Os.newTempDir(getClass());
+        origManagementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+        origApp = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class), origManagementContext);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (origManagementContext != null) Entities.destroyAll(origManagementContext);
+        if (newApp != null) Entities.destroyAll(newApp.getManagementContext());
+        if (newManagementContext != null) Entities.destroyAll(newManagementContext);
+        if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testRebindsMultiLocation() throws Exception {
+        mac1a = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .displayName("mac1a")
+                .configure("address", Networking.getInetAddressWithFixedName("1.1.1.1")));
+        mac2a = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .displayName("mac2a")
+                .configure("address", Networking.getInetAddressWithFixedName("1.1.1.3")));
+        loc1 = origManagementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .displayName("loc1")
+                .configure("machines", MutableSet.of(mac1a)));
+        loc2 = origManagementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .displayName("loc2")
+                .configure("machines", MutableSet.of(mac2a)));
+        multiLoc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class)
+                        .displayName("multiLoc")
+                        .configure("subLocations", ImmutableList.of(loc1, loc2)));
+        
+        newApp = rebind();
+        newManagementContext = newApp.getManagementContext();
+        
+        MultiLocation newMultiLoc = (MultiLocation) Iterables.find(newManagementContext.getLocationManager().getLocations(), Predicates.instanceOf(MultiLocation.class));
+        AvailabilityZoneExtension azExtension = newMultiLoc.getExtension(AvailabilityZoneExtension.class);
+        List<Location> newSublLocs = azExtension.getAllSubLocations();
+        Iterable<String> newSubLocNames = Iterables.transform(newSublLocs, new Function<Location, String>() {
+            @Override public String apply(Location input) {
+                return (input == null) ? null : input.getDisplayName();
+            }});
+        Asserts.assertEqualsIgnoringOrder(newSubLocNames, ImmutableList.of("loc1", "loc2"));
+    }
+    
+    private TestApplication rebind() throws Exception {
+        return rebind(true);
+    }
+    
+    private TestApplication rebind(boolean checkSerializable) throws Exception {
+        RebindTestUtils.waitForPersisted(origApp);
+        if (checkSerializable) {
+            RebindTestUtils.checkCurrentMementoSerializable(origApp);
+        }
+        return (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationResolverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationResolverTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationResolverTest.java
new file mode 100644
index 0000000..6fbf54e
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationResolverTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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 static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.MachineProvisioningLocation;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+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;
+
+public class MultiLocationResolverTest {
+
+    private BrooklynProperties brooklynProperties;
+    private LocalManagementContext managementContext;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        brooklynProperties = managementContext.getBrooklynProperties();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+    
+    @Test
+    public void testThrowsOnInvalid() throws Exception {
+        assertThrowsNoSuchElement("wrongprefix:(hosts=\"1.1.1.1\")");
+        assertThrowsIllegalArgument("single");
+    }
+    
+    @Test
+    public void testThrowsOnInvalidTarget() throws Exception {
+        assertThrowsIllegalArgument("multi:()");
+        assertThrowsIllegalArgument("multi:(wrongprefix:(hosts=\"1.1.1.1\"))");
+        assertThrowsIllegalArgument("multi:(foo:bar)");
+    }
+
+    @Test
+    public void testCleansUpOnInvalidTarget() {
+        assertThrowsNoSuchElement("multi:(targets=\"localhost:(name=testCleansUpOnInvalidTarget),thisNamedLocationDoesNotExist\")");
+        Optional<Location> subtarget = Iterables.tryFind(managementContext.getLocationManager().getLocations(), LocationPredicates.displayNameEqualTo("testCleansUpOnInvalidTarget"));
+        assertFalse(subtarget.isPresent(), "subtarget="+subtarget);
+    }
+
+
+    @Test
+    public void testResolvesSubLocs() {
+        assertMultiLocation(resolve("multi:(targets=localhost)"), 1, ImmutableList.of(Predicates.instanceOf(LocalhostMachineProvisioningLocation.class)));
+        assertMultiLocation(resolve("multi:(targets=\"localhost,localhost\")"), 2, Collections.nCopies(2, Predicates.instanceOf(LocalhostMachineProvisioningLocation.class)));
+        assertMultiLocation(resolve("multi:(targets=\"localhost,localhost,localhost\")"), 3, Collections.nCopies(3, Predicates.instanceOf(LocalhostMachineProvisioningLocation.class)));
+        assertMultiLocation(resolve("multi:(targets=\"localhost:(name=mysubname)\")"), 1, ImmutableList.of(displayNameEqualTo("mysubname")));
+        assertMultiLocation(resolve("multi:(targets=byon:(hosts=\"1.1.1.1\"))"), 1, ImmutableList.of(Predicates.and(
+                Predicates.instanceOf(FixedListMachineProvisioningLocation.class),
+                new Predicate<MachineProvisioningLocation>() {
+                    @Override public boolean apply(MachineProvisioningLocation input) {
+                        SshMachineLocation machine;
+                        try {
+                            machine = (SshMachineLocation) input.obtain(ImmutableMap.of());
+                        } catch (NoMachinesAvailableException e) {
+                            throw Exceptions.propagate(e);
+                        }
+                        try {
+                            String addr = ((SshMachineLocation)machine).getAddress().getHostAddress();
+                            return addr != null && addr.equals("1.1.1.1");
+                        } finally {
+                            input.release(machine);
+                        }
+                    }
+                })));
+        assertMultiLocation(resolve("multi:(targets=\"byon:(hosts=1.1.1.1),byon:(hosts=1.1.1.2)\")"), 2, Collections.nCopies(2, Predicates.instanceOf(FixedListMachineProvisioningLocation.class)));
+    }
+
+    @Test
+    public void testResolvesName() {
+        MultiLocation<SshMachineLocation> multiLoc = resolve("multi:(name=myname,targets=localhost)");
+        assertEquals(multiLoc.getDisplayName(), "myname");
+    }
+    
+    @Test
+    public void testNamedByonLocation() throws Exception {
+        brooklynProperties.put("brooklyn.location.named.mynamed", "multi:(targets=byon:(hosts=\"1.1.1.1\"))");
+        
+        MultiLocation<SshMachineLocation> loc = resolve("named:mynamed");
+        assertEquals(loc.obtain(ImmutableMap.of()).getAddress(), InetAddress.getByName("1.1.1.1"));
+    }
+
+    @Test
+    public void testResolvesFromMap() throws NoMachinesAvailableException {
+        Location l = managementContext.getLocationRegistry().resolve("multi", MutableMap.of("targets", 
+            MutableList.of("localhost", MutableMap.of("byon", MutableMap.of("hosts", "127.0.0.127")))));
+        MultiLocation<?> ml = (MultiLocation<?>)l;
+        Iterator<MachineProvisioningLocation<?>> ci = ml.getSubLocations().iterator();
+        
+        l = ci.next();
+        Assert.assertTrue(l instanceof LocalhostMachineProvisioningLocation, "Expected localhost, got "+l);
+        
+        l = ci.next();
+        Assert.assertTrue(l instanceof FixedListMachineProvisioningLocation, "Expected fixed, got "+l);
+        MachineLocation sl = ((FixedListMachineProvisioningLocation<?>)l).obtain();
+        Assert.assertEquals(sl.getAddress().getHostAddress(), "127.0.0.127");
+        
+        Assert.assertFalse(ci.hasNext());
+    }
+
+
+    private void assertThrowsNoSuchElement(String val) {
+        try {
+            resolve(val);
+            fail();
+        } catch (NoSuchElementException e) {
+            // success
+        }
+    }
+    
+    private void assertThrowsIllegalArgument(String val) {
+        try {
+            resolve(val);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private MultiLocation<SshMachineLocation> resolve(String val) {
+        return (MultiLocation<SshMachineLocation>) managementContext.getLocationRegistry().resolve(val);
+    }
+    
+    @SuppressWarnings("rawtypes")
+    private void assertMultiLocation(MultiLocation<?> multiLoc, int expectedSize, List<? extends Predicate> expectedSubLocationPredicates) {
+        AvailabilityZoneExtension zones = multiLoc.getExtension(AvailabilityZoneExtension.class);
+        List<Location> subLocs = zones.getAllSubLocations();
+        assertEquals(subLocs.size(), expectedSize, "zones="+subLocs);
+        for (int i = 0; i < subLocs.size(); i++) {
+            MachineProvisioningLocation subLoc = (MachineProvisioningLocation) subLocs.get(i);
+            assertTrue(expectedSubLocationPredicates.get(i).apply(subLoc), "index="+i+"; subLocs="+subLocs);
+        }
+    }
+    
+    public static <T> Predicate<Location> displayNameEqualTo(final T val) {
+        return new Predicate<Location>() {
+            @Override
+            public boolean apply(@Nullable Location input) {
+                return Objects.equal(input.getDisplayName(), val);
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationTest.java
new file mode 100644
index 0000000..57d6c79
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/MultiLocationTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.assertTrue;
+
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.net.Networking;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class MultiLocationTest {
+
+    private static final Logger log = LoggerFactory.getLogger(MultiLocationTest.class);
+    
+    private LocalManagementContext managementContext;
+    private SshMachineLocation mac1a;
+    private SshMachineLocation mac1b;
+    private SshMachineLocation mac2a;
+    private SshMachineLocation mac2b;
+    private FixedListMachineProvisioningLocation<SshMachineLocation> loc1;
+    private FixedListMachineProvisioningLocation<SshMachineLocation> loc2;
+    private MultiLocation<SshMachineLocation> multiLoc;
+    
+    @SuppressWarnings("unchecked")
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        mac1a = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .displayName("mac1a")
+                .configure("address", Networking.getInetAddressWithFixedName("1.1.1.1")));
+        mac1b = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .displayName("mac1b")
+                .configure("address", Networking.getInetAddressWithFixedName("1.1.1.2")));
+        mac2a = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .displayName("mac2a")
+                .configure("address", Networking.getInetAddressWithFixedName("1.1.1.3")));
+        mac2b = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .displayName("mac2b")
+                .configure("address", Networking.getInetAddressWithFixedName("1.1.1.4")));
+        loc1 = managementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .displayName("loc1")
+                .configure("machines", MutableSet.of(mac1a, mac1b)));
+        loc2 = managementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .displayName("loc2")
+                .configure("machines", MutableSet.of(mac2a, mac2b)));
+        multiLoc = managementContext.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class)
+                        .displayName("multiLoc")
+                        .configure("subLocations", ImmutableList.of(loc1, loc2)));
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+    
+    @Test
+    public void testHasAvailabilityZonesAsSubLocations() throws Exception {
+        multiLoc.hasExtension(AvailabilityZoneExtension.class);
+        AvailabilityZoneExtension extension = multiLoc.getExtension(AvailabilityZoneExtension.class);
+        Asserts.assertEqualsIgnoringOrder(extension.getAllSubLocations(), ImmutableList.of(loc1, loc2));
+        Asserts.assertEqualsIgnoringOrder(extension.getSubLocations(2), ImmutableList.of(loc1, loc2));
+        assertTrue(ImmutableList.of(loc1, loc2).containsAll(extension.getSubLocations(1)));
+    }
+    
+    @Test
+    public void testObtainAndReleaseDelegateToSubLocation() throws Exception {
+        SshMachineLocation obtained = multiLoc.obtain(ImmutableMap.of());
+        assertTrue(ImmutableList.of(mac1a, mac1b, mac2a, mac2b).contains(obtained));
+        multiLoc.release(obtained);
+    }
+    
+    @Test
+    public void testObtainsMovesThroughSubLocations() throws Exception {
+        Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.1");
+        Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.2");
+        Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.3");
+        Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.4");
+        try {
+            multiLoc.obtain();
+            Assert.fail();
+        } catch (NoMachinesAvailableException e) {
+            log.info("Error when no machines available across locations is: "+e);
+            Assert.assertTrue(e.toString().contains("loc1"), "Message should have referred to sub-location message: "+e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/PaasLocationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/PaasLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/PaasLocationTest.java
new file mode 100644
index 0000000..1b841d3
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/PaasLocationTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 org.apache.brooklyn.location.paas.PaasLocation;
+import brooklyn.test.location.TestPaasLocation;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class PaasLocationTest {
+
+    private PaasLocation location;
+    
+    @Test
+    public void testProviderName(){
+        location = new TestPaasLocation();
+        Assert.assertEquals(location.getPaasProviderName(), "TestPaas");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/PortRangesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/PortRangesTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/PortRangesTest.java
new file mode 100644
index 0000000..3e4812f
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/PortRangesTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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 java.util.Iterator;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.location.PortRange;
+import brooklyn.util.flags.TypeCoercions;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+public class PortRangesTest {
+
+    @Test
+    public void testSingleRange() {
+        PortRange r = PortRanges.fromInteger(1234);
+        assertContents(r, 1234);
+    }
+
+    @Test
+    public void testFromCollection() {
+        PortRange r = PortRanges.fromCollection(ImmutableList.of(1234, 2345));
+        assertContents(r, 1234, 2345);
+    }
+
+    @Test
+    public void testFromString() {
+        PortRange r = PortRanges.fromString("80,8080,8000,8080-8099");
+        assertContents(r, 80, 8080, 8000, 
+                8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089,
+                8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099);
+    }
+
+    @Test
+    public void testFromStringWithSpaces() {
+        PortRange r = PortRanges.fromString(" 80 , 8080  , 8000 , 8080  - 8099 ");
+        assertContents(r, 80, 8080, 8000, 
+                8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089,
+                8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099);
+    }
+
+    @Test
+    public void testFromStringWithSpacesToString() {
+        PortRange r = PortRanges.fromString(" 80 , 8080  , 8000 , 8080  - 8099 ");
+        Assert.assertEquals(r.toString(), "80,8080,8000,8080-8099");
+    }
+    
+    @Test
+    public void testFromStringThrowsIllegalArgumentException() {
+        assertFromStringThrowsIllegalArgumentException("80-100000");
+        assertFromStringThrowsIllegalArgumentException("0-80");
+    }
+
+    @Test
+    public void testCoercion() {
+        PortRanges.init();
+        PortRange r = TypeCoercions.coerce("80", PortRange.class);
+        assertContents(r, 80);
+    }
+
+    @Test
+    public void testCoercionInt() {
+        PortRanges.init();
+        PortRange r = TypeCoercions.coerce(80, PortRange.class);
+        assertContents(r, 80);
+    }
+    
+    @Test
+    public void testLinearRangeOfSizeOne() throws Exception {
+        PortRanges.LinearPortRange range = new PortRanges.LinearPortRange(80, 80);
+        assertEquals(Lists.newArrayList(range), ImmutableList.of(80));
+    }
+
+    @Test
+    public void testLinearRangeCountingUpwards() throws Exception {
+        PortRanges.LinearPortRange range = new PortRanges.LinearPortRange(80, 81);
+        assertEquals(Lists.newArrayList(range), ImmutableList.of(80, 81));
+    }
+    
+    @Test
+    public void testLinearRangeCountingDownwards() throws Exception {
+        PortRanges.LinearPortRange range = new PortRanges.LinearPortRange(80, 79);
+        assertEquals(Lists.newArrayList(range), ImmutableList.of(80, 79));
+    }
+    
+    protected void assertFromStringThrowsIllegalArgumentException(String range) {
+        try {
+            PortRanges.fromString(range);
+            Assert.fail();
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+
+    private static <T> void assertContents(Iterable<T> actual, T ...expected) {
+        Iterator<T> i = actual.iterator();
+        int c = 0;
+        while (i.hasNext()) {
+            if (expected.length<=c) {
+                Assert.fail("Iterable contained more than the "+c+" expected elements");
+            }
+            Assert.assertEquals(i.next(), expected[c++]);
+        }
+        if (expected.length>c) {
+            Assert.fail("Iterable contained only "+c+" elements, "+expected.length+" expected");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/RecordingMachineLocationCustomizer.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/RecordingMachineLocationCustomizer.java b/core/src/test/java/org/apache/brooklyn/location/basic/RecordingMachineLocationCustomizer.java
new file mode 100644
index 0000000..a30d6e1
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/RecordingMachineLocationCustomizer.java
@@ -0,0 +1,71 @@
+/*
+ * 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 com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.MachineLocationCustomizer;
+
+public class RecordingMachineLocationCustomizer implements MachineLocationCustomizer {
+    public static class Call {
+        public final String methodName;
+        public final List<?> args;
+        
+        Call(String methodName, List<?> args) {
+            this.methodName = checkNotNull(methodName);
+            this.args = checkNotNull(args);
+        }
+        
+        @Override
+        public String toString() {
+            return methodName+args;
+        }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(methodName, args);
+        }
+        
+        @Override
+        public boolean equals(Object other) {
+            return (other instanceof RecordingMachineLocationCustomizer.Call) && 
+                    methodName.equals(((RecordingMachineLocationCustomizer.Call)other).methodName) && 
+                    args.equals(((RecordingMachineLocationCustomizer.Call)other).args);
+        }
+    }
+    
+    public final List<RecordingMachineLocationCustomizer.Call> calls = Lists.newCopyOnWriteArrayList();
+    
+    @Override
+    public void customize(MachineLocation machine) {
+        calls.add(new Call("customize", ImmutableList.of(machine)));
+    }
+
+    @Override
+    public void preRelease(MachineLocation machine) {
+        calls.add(new Call("preRelease", ImmutableList.of(machine)));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/SimulatedLocation.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SimulatedLocation.java b/core/src/test/java/org/apache/brooklyn/location/basic/SimulatedLocation.java
new file mode 100644
index 0000000..c904619
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SimulatedLocation.java
@@ -0,0 +1,136 @@
+/*
+ * 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 java.net.InetAddress;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.location.HardwareDetails;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.MachineDetails;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.MachineProvisioningLocation;
+import org.apache.brooklyn.location.OsDetails;
+import org.apache.brooklyn.location.PortRange;
+import org.apache.brooklyn.location.PortSupplier;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Networking;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+
+/** Location for use in dev/test, defining custom start/stop support, and/or tweaking the ports which are permitted to be available
+ * (using setPermittedPorts(Iterable))
+ */
+public class SimulatedLocation extends AbstractLocation implements MachineProvisioningLocation<MachineLocation>, MachineLocation, PortSupplier {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final InetAddress address;
+    static {
+        address = Networking.getLocalHost();
+    }
+
+    Iterable<Integer> permittedPorts = PortRanges.fromString("1+");
+    Set<Integer> usedPorts = Sets.newLinkedHashSet();
+
+    public SimulatedLocation() {
+        this(MutableMap.<String,Object>of());
+    }
+    public SimulatedLocation(Map<String,? extends Object> flags) {
+        super(flags);
+    }
+    
+    @Override
+    public SimulatedLocation newSubLocation(Map<?,?> newFlags) {
+        // TODO shouldn't have to copy config bag as it should be inherited (but currently it is not used inherited everywhere; just most places)
+        return getManagementContext().getLocationManager().createLocation(LocationSpec.create(getClass())
+                .parent(this)
+                .configure(config().getLocalBag().getAllConfig())  // FIXME Should this just be inherited?
+                .configure(newFlags));
+    }
+
+    public MachineLocation obtain(Map<?,?> flags) {
+        return this;
+    }
+
+    public void release(MachineLocation machine) {
+    }
+
+    public Map<String,Object> getProvisioningFlags(Collection<String> tags) {
+        return MutableMap.<String,Object>of();
+    }
+    
+    public InetAddress getAddress() {
+        return address;
+    }
+
+    @Override
+    public String getHostname() {
+        String hostname = address.getHostName();
+        return (hostname == null || hostname.equals(address.getHostAddress())) ? null : hostname;
+    }
+    
+    @Override
+    public Set<String> getPublicAddresses() {
+        return ImmutableSet.of(address.getHostAddress());
+    }
+    
+    @Override
+    public Set<String> getPrivateAddresses() {
+        return ImmutableSet.of();
+    }
+
+    public synchronized boolean obtainSpecificPort(int portNumber) {
+        if (!Iterables.contains(permittedPorts, portNumber)) return false;
+        if (usedPorts.contains(portNumber)) return false;
+        usedPorts.add(portNumber);
+        return true;
+    }
+
+    public synchronized int obtainPort(PortRange range) {
+        for (int p: range)
+            if (obtainSpecificPort(p)) return p;
+        return -1;
+    }
+
+    public synchronized void releasePort(int portNumber) {
+        usedPorts.remove(portNumber);
+    }
+    
+    public synchronized void setPermittedPorts(Iterable<Integer> ports) {
+        permittedPorts  = ports;
+    }
+
+    @Override
+    public OsDetails getOsDetails() {
+        return getMachineDetails().getOsDetails();
+    }
+
+    @Override
+    public MachineDetails getMachineDetails() {
+        HardwareDetails hardwareDetails = new BasicHardwareDetails(null, null);
+        OsDetails osDetails = BasicOsDetails.Factory.ANONYMOUS_LINUX;
+        return new BasicMachineDetails(hardwareDetails, osDetails);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineLocationResolverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineLocationResolverTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineLocationResolverTest.java
new file mode 100644
index 0000000..a9da0dd
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineLocationResolverTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 static org.testng.Assert.fail;
+
+import java.net.InetAddress;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.internal.LocalManagementContext;
+
+import com.google.common.collect.ImmutableMap;
+
+public class SingleMachineLocationResolverTest {
+
+    private BrooklynProperties brooklynProperties;
+    private LocalManagementContext managementContext;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        brooklynProperties = managementContext.getBrooklynProperties();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+    
+    @Test
+    public void testThrowsOnInvalid() throws Exception {
+        assertThrowsNoSuchElement("wrongprefix:(hosts=\"1.1.1.1\")");
+        assertThrowsIllegalArgument("single");
+    }
+    
+    @Test
+    public void testThrowsOnInvalidTarget() throws Exception {
+        assertThrowsIllegalArgument("single()");
+        assertThrowsIllegalArgument("single(wrongprefix:(hosts=\"1.1.1.1\"))");
+        assertThrowsIllegalArgument("single(foo:bar)");
+    }
+
+    @Test
+    public void resolveHosts() {
+        resolve("single(target=localhost)");
+        resolve("single(target=byon(hosts=\"1.1.1.1\"))");
+
+        brooklynProperties.put("brooklyn.location.named.mynamed", "single(target=byon:(hosts=\"1.1.1.1\"))");
+        managementContext.clearLocationRegistry();
+        resolve("single(target=named:mynamed)");
+    }
+    
+    @Test
+    public void resolveWithOldColonFormat() {
+        resolve("single:(target=localhost)");
+    }
+    
+    @Test
+    public void testNamedByonLocation() throws Exception {
+        brooklynProperties.put("brooklyn.location.named.mynamed", "single(target=byon:(hosts=\"1.1.1.1\"))");
+        
+        SingleMachineProvisioningLocation<SshMachineLocation> loc = resolve("named:mynamed");
+        assertEquals(loc.obtain(ImmutableMap.of()).getAddress(), InetAddress.getByName("1.1.1.1"));
+    }
+
+    @Test
+    public void testPropertyScopePrescedence() throws Exception {
+        brooklynProperties.put("brooklyn.location.named.mynamed", "single(target=byon:(hosts=\"1.1.1.1\"))");
+        
+        // prefer those in "named" over everything else
+        brooklynProperties.put("brooklyn.location.named.mynamed.privateKeyFile", "privateKeyFile-inNamed");
+        brooklynProperties.put("brooklyn.localhost.privateKeyFile", "privateKeyFile-inGeneric");
+
+        // prefer location-generic if nothing else
+        brooklynProperties.put("brooklyn.location.privateKeyData", "privateKeyData-inGeneric");
+
+        Map<String, Object> conf = resolve("named:mynamed").obtain(ImmutableMap.of()).config().getBag().getAllConfig();
+        
+        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
+        assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric");
+    }
+
+    private void assertThrowsNoSuchElement(String val) {
+        try {
+            resolve(val);
+            fail();
+        } catch (NoSuchElementException e) {
+            // success
+        }
+    }
+    
+    private void assertThrowsIllegalArgument(String val) {
+        try {
+            resolve(val);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private SingleMachineProvisioningLocation<SshMachineLocation> resolve(String val) {
+        return (SingleMachineProvisioningLocation<SshMachineLocation>) managementContext.getLocationRegistry().resolve(val);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineProvisioningLocationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineProvisioningLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineProvisioningLocationTest.java
new file mode 100644
index 0000000..7d42e88
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SingleMachineProvisioningLocationTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.assertNotNull;
+
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.management.internal.LocalManagementContext;
+
+public class SingleMachineProvisioningLocationTest {
+    
+    private static final Logger log = LoggerFactory.getLogger(SingleMachineProvisioningLocation.class);
+    
+    private LocalManagementContext managementContext;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) managementContext.terminate();
+    }
+    
+    @SuppressWarnings("unchecked") 
+    @Test
+    public void testLocalhostSingle() throws Exception {
+        SingleMachineProvisioningLocation<SshMachineLocation> l = (SingleMachineProvisioningLocation<SshMachineLocation>) 
+            managementContext.getLocationRegistry().resolve("single:(target='localhost')");
+        l.setManagementContext(managementContext);
+        
+        SshMachineLocation m1 = l.obtain();
+        
+        assertNotNull(m1);
+
+        log.info("GOT "+m1);
+        
+        l.release(m1);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationIntegrationTest.java
new file mode 100644
index 0000000..68a53c5
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationIntegrationTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import java.io.ByteArrayOutputStream;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Map;
+
+import brooklyn.util.internal.ssh.SshTool;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.crypto.SecureKeys;
+import brooklyn.util.internal.ssh.sshj.SshjTool;
+import brooklyn.util.internal.ssh.sshj.SshjTool.SshjToolBuilder;
+
+import com.google.common.base.Preconditions;
+
+import static org.testng.Assert.assertEquals;
+
+public class SshMachineLocationIntegrationTest {
+
+    protected TestApplication app;
+    protected ManagementContext mgmt;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setup() throws Exception {
+        mgmt = LocalManagementContextForTests.builder(true)
+            .useDefaultProperties()
+            .build();
+        app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (mgmt != null) Entities.destroyAll(mgmt);
+        mgmt = null;
+    }
+
+    // Note: requires `named:localhost-passphrase` set up with a key whose passphrase is "localhost"
+    // * create the key with:
+    //      ssh-keygen -t rsa -N "brooklyn" -f ~/.ssh/id_rsa_passphrase
+    //      ssh-copy-id localhost
+    // * create brooklyn.properties, containing:
+    //      brooklyn.location.named.localhost-passphrase=localhost
+    //      brooklyn.location.named.localhost-passphrase.privateKeyFile=~/.ssh/id_rsa_passphrase
+    //      brooklyn.location.named.localhost-passphrase.privateKeyPassphrase=brooklyn
+    @Test(groups = "Integration")
+    public void testExtractingConnectablePassphraselessKey() throws Exception {
+        LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("named:localhost-passphrase", true, null).orNull();
+        Preconditions.checkNotNull(lhp, "This test requires a localhost named location called 'localhost-passphrase' (which should have a passphrase set)");
+        SshMachineLocation sm = lhp.obtain();
+        
+        SshjToolBuilder builder = SshjTool.builder().host(sm.getAddress().getHostName()).user(sm.getUser());
+        
+        KeyPair data = sm.findKeyPair();
+        if (data!=null) builder.privateKeyData(SecureKeys.toPem(data));
+        String password = sm.findPassword();
+        if (password!=null) builder.password(password);
+        SshjTool tool = builder.build();
+        tool.connect();
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int result = tool.execCommands(MutableMap.<String,Object>of("out", out), Arrays.asList("date"));
+        Assert.assertTrue(out.toString().contains(" 20"), "out="+out);
+        assertEquals(result, 0);
+    }
+
+    @Test(groups = "Integration")
+    public void testExecScriptScriptDirFlagIsRespected() throws Exception {
+        // For explanation of (some of) the magic behind this command, see http://stackoverflow.com/a/229606/68898
+        final String command = "if [[ \"$0\" == \"/var/tmp/\"* ]]; then true; else false; fi";
+
+        LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost", true, null).orNull();
+        SshMachineLocation sm = lhp.obtain();
+
+        Map<String, Object> props = ImmutableMap.<String, Object>builder()
+                .put(SshTool.PROP_SCRIPT_DIR.getName(), "/var/tmp")
+                .build();
+        int rc = sm.execScript(props, "Test script directory execution", ImmutableList.of(command));
+        assertEquals(rc, 0);
+    }
+
+    @Test(groups = "Integration")
+    public void testLocationScriptDirConfigIsRespected() throws Exception {
+        // For explanation of (some of) the magic behind this command, see http://stackoverflow.com/a/229606/68898
+        final String command = "if [[ \"$0\" == \"/var/tmp/\"* ]]; then true; else false; fi";
+
+        Map<String, Object> locationConfig = ImmutableMap.<String, Object>builder()
+                .put(SshMachineLocation.SCRIPT_DIR.getName(), "/var/tmp")
+                .build();
+
+        LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost", locationConfig);
+        SshMachineLocation sm = lhp.obtain();
+
+        int rc = sm.execScript("Test script directory execution", ImmutableList.of(command));
+        assertEquals(rc, 0);
+    }
+    
+    @Test(groups = "Integration")
+    public void testMissingLocationScriptDirIsAlsoOkay() throws Exception {
+        final String command = "echo hello";
+
+        Map<String, Object> locationConfig = ImmutableMap.<String, Object>builder()
+//                .put(SshMachineLocation.SCRIPT_DIR.getName(), "/var/tmp")
+                .build();
+
+        LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost", locationConfig);
+        SshMachineLocation sm = lhp.obtain();
+
+        int rc = sm.execScript("Test script directory execution", ImmutableList.of(command));
+        assertEquals(rc, 0);
+    }
+}