http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerRebindTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerRebindTest.java
 
b/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerRebindTest.java
new file mode 100644
index 0000000..6645217
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerRebindTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.access;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+
+import java.io.File;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.management.ha.MementoCopyMode;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.apache.brooklyn.test.entity.TestEntityImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.rebind.RebindOptions;
+import brooklyn.entity.rebind.RebindTestFixtureWithApp;
+import brooklyn.entity.rebind.RebindTestUtils;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.FileBasedObjectStore;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.event.basic.Sensors;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.net.Networking;
+import brooklyn.util.os.Os;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.net.HostAndPort;
+
+public class PortForwardManagerRebindTest extends RebindTestFixtureWithApp {
+
+    private static final Logger log = 
LoggerFactory.getLogger(PortForwardManagerRebindTest.class);
+
+    private String machineAddress = "1.2.3.4";
+    private SshMachineLocation origSimulatedMachine;
+
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+
+        origSimulatedMachine = 
origManagementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", 
Networking.getInetAddressWithFixedName(machineAddress))
+                .configure("port", 1234)
+                .configure("user", "myuser"));
+    }
+    
+    @Test
+    public void testAssociationPreservedOnRebind() throws Exception {
+        String publicIpId = "5.6.7.8";
+        String publicAddress = "5.6.7.8";
+
+        TestEntity origEntity = 
origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        PortForwardManager origPortForwardManager = 
origEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+
+        // We first wait for persisted, to ensure that it is the 
PortForwardManager.onChanged that is causing persistence.
+        RebindTestUtils.waitForPersisted(origApp);
+        origPortForwardManager.associate(publicIpId, 
HostAndPort.fromParts(publicAddress, 40080), origSimulatedMachine, 80);
+     
+        newApp = rebind();
+        
+        // After rebind, confirm that lookups still work
+        TestEntity newEntity = (TestEntity) 
Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+        Location newSimulatedMachine = 
newApp.getManagementContext().getLocationManager().getLocation(origSimulatedMachine.getId());
+        PortForwardManager newPortForwardManager = 
newEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+        
+        assertEquals(newPortForwardManager.lookup(newSimulatedMachine, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        assertEquals(newPortForwardManager.lookup(publicIpId, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+    }
+    
+    @Test
+    public void testAssociationPreservedOnStateExport() throws Exception {
+        String publicIpId = "5.6.7.8";
+        String publicAddress = "5.6.7.8";
+
+        TestEntity origEntity = 
origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        PortForwardManager origPortForwardManager = 
origEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+
+        origPortForwardManager.associate(publicIpId, 
HostAndPort.fromParts(publicAddress, 40080), origSimulatedMachine, 80);
+
+        String label = 
origManagementContext.getManagementNodeId()+"-"+Time.makeDateSimpleStampString();
+        PersistenceObjectStore targetStore = 
BrooklynPersistenceUtils.newPersistenceObjectStore(origManagementContext, null, 
+            "tmp/web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
+        File dir = ((FileBasedObjectStore)targetStore).getBaseDir();
+        // only register the parent dir because that will prevent leaks for 
the random ID
+        Os.deleteOnExitEmptyParentsUpTo(dir.getParentFile(), 
dir.getParentFile());
+        BrooklynPersistenceUtils.writeMemento(origManagementContext, 
targetStore, MementoCopyMode.LOCAL);            
+
+        RebindTestUtils.waitForPersisted(origApp);
+        log.info("Using manual export dir "+dir+" for rebind instead of 
"+mementoDir);
+        newApp = rebind(RebindOptions.create().mementoDir(dir));
+        
+        // After rebind, confirm that lookups still work
+        TestEntity newEntity = (TestEntity) 
Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+        Location newSimulatedMachine = 
newApp.getManagementContext().getLocationManager().getLocation(origSimulatedMachine.getId());
+        PortForwardManager newPortForwardManager = 
newEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+        
+        assertEquals(newPortForwardManager.lookup(newSimulatedMachine, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        assertEquals(newPortForwardManager.lookup(publicIpId, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        
+        // delete the dir here, to be more likely not to leak it on failure
+        newManagementContext.getRebindManager().stop();
+        Os.deleteRecursively(dir);
+    }
+    
+    @Test
+    public void testAssociationPreservedOnRebindLegacy() throws Exception {
+        String publicIpId = "5.6.7.8";
+        String publicAddress = "5.6.7.8";
+
+        TestEntity origEntity = 
origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        PortForwardManager origPortForwardManager = 
origEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+
+        // We first wait for persisted, to ensure that it is the 
PortForwardManager.onChanged that is causing persistence.
+        RebindTestUtils.waitForPersisted(origApp);
+        origPortForwardManager.recordPublicIpHostname(publicIpId, 
publicAddress);
+        origPortForwardManager.acquirePublicPortExplicit(publicIpId, 40080);
+        origPortForwardManager.associate(publicIpId, 40080, 
origSimulatedMachine, 80);
+     
+        newApp = rebind();
+        
+        // After rebind, confirm that lookups still work
+        TestEntity newEntity = (TestEntity) 
Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+        Location newSimulatedMachine = 
newApp.getManagementContext().getLocationManager().getLocation(origSimulatedMachine.getId());
+        PortForwardManager newPortForwardManager = 
newEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+        
+        assertEquals(newPortForwardManager.getPublicIpHostname(publicIpId), 
publicAddress);
+        assertEquals(newPortForwardManager.lookup(newSimulatedMachine, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        assertEquals(newPortForwardManager.lookup(publicIpId, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+    }
+    
+    @Test
+    public void testAcquirePortCounterPreservedOnRebindLegacy() throws 
Exception {
+        String publicIpId = "5.6.7.8";
+
+        TestEntity origEntity = 
origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        PortForwardManager origPortForwardManager = 
origEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+
+        // We first wait for persisted, to ensure that it is the 
PortForwardManager.onChanged that is causing persistence.
+        RebindTestUtils.waitForPersisted(origApp);
+        int acquiredPort = 
origPortForwardManager.acquirePublicPort(publicIpId);
+     
+        newApp = rebind();
+        
+        // After rebind, confirm that lookups still work
+        TestEntity newEntity = (TestEntity) 
Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+        PortForwardManager newPortForwardManager = 
newEntity.getConfig(MyEntity.PORT_FORWARD_MANAGER);
+        
+        int acquiredPort2 = 
newPortForwardManager.acquirePublicPort(publicIpId);
+        assertNotEquals(acquiredPort, acquiredPort2);
+    }
+    
+    public static class MyEntity extends TestEntityImpl {
+        public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER 
= ConfigKeys.newConfigKey(PortForwardManager.class, 
"myentity.portForwardManager");
+        public static final AttributeSensor<PortForwardManager> 
PORT_FORWARD_MANAGER_LIVE = Sensors.newSensor(PortForwardManager.class, 
"myentity.portForwardManager.live");
+
+        @Override
+        public void init() {
+            super.init();
+            
+            if (getConfig(PORT_FORWARD_MANAGER) == null) {
+                PortForwardManager pfm = (PortForwardManager) 
getManagementContext().getLocationRegistry().resolve("portForwardManager(scope=global)");
+                setAttribute(PORT_FORWARD_MANAGER_LIVE, pfm);
+                setConfig(PORT_FORWARD_MANAGER, pfm);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerTest.java
 
b/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerTest.java
new file mode 100644
index 0000000..904e9dc
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/location/access/PortForwardManagerTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.access;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNull;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.net.Networking;
+
+import com.google.common.base.Predicate;
+import com.google.common.net.HostAndPort;
+
+public class PortForwardManagerTest extends BrooklynAppUnitTestSupport {
+
+    private static final Logger log = 
LoggerFactory.getLogger(PortForwardManagerTest.class);
+
+    private Map<HostAndPort, HostAndPort> portMapping;
+    private SshMachineLocation machine1;
+    private SshMachineLocation machine2;
+    private PortForwardManager pfm;
+    
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+
+        pfm = (PortForwardManager) 
mgmt.getLocationRegistry().resolve("portForwardManager(scope=global)");
+
+        machine1 = 
mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", 
Networking.getInetAddressWithFixedName("1.2.3.4"))
+                .configure("port", 1234)
+                .configure("user", "myuser"));
+        machine2 = 
mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", 
Networking.getInetAddressWithFixedName("1.2.3.5"))
+                .configure("port", 1234)
+                .configure("user", "myuser"));
+    }
+    
+    @Test
+    public void testAssociateWithLocation() throws Exception {
+        String publicIpId = "myipid";
+        String publicAddress = "5.6.7.8";
+
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicAddress, 40080), 
machine1, 80);
+     
+        assertEquals(pfm.lookup(machine1, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        assertEquals(pfm.lookup(publicIpId, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+    }
+    
+    @Test
+    public void testAssociateWithoutLocation() throws Exception {
+        String publicIpId = "myipid";
+        String publicAddress = "5.6.7.8";
+
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicAddress, 40080), 
80);
+     
+        assertEquals(pfm.lookup(publicIpId, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        assertNull(pfm.lookup(machine1, 80));
+    }
+    
+    @Test
+    public void testAcquirePortDoesNotReturnDuplicate() throws Exception {
+        String publicIpId = "myipid";
+
+        int port1 = pfm.acquirePublicPort(publicIpId);
+        int port2 = pfm.acquirePublicPort(publicIpId);
+        assertNotEquals(port1, port2);
+    }
+    
+    @Test
+    public void testAcquirePortRespectsStartingPortNumber() throws Exception {
+        BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
+        props.put(PortForwardManager.PORT_FORWARD_MANAGER_STARTING_PORT, 1234);
+        LocalManagementContextForTests mgmt2 = new 
LocalManagementContextForTests(props);
+        try {
+            PortForwardManager pfm2 = (PortForwardManager) 
mgmt2.getLocationRegistry().resolve("portForwardManager(scope=global)");
+            int port = pfm2.acquirePublicPort("myipid");
+            assertEquals(port, 1234);
+        } finally {
+            Entities.destroyAll(mgmt2);
+        }
+    }
+    
+    @Test
+    public void testForgetPortMapping() throws Exception {
+        String publicIpId = "myipid";
+        String publicAddress = "5.6.7.8";
+
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicAddress, 40080), 
machine1, 80);
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicAddress, 40081), 
machine1, 81);
+        pfm.forgetPortMapping(publicIpId, 40080);
+        
+        assertNull(pfm.lookup(publicIpId, 80));
+        assertNull(pfm.lookup(machine1, 80));
+        assertEquals(pfm.lookup(publicIpId, 81), 
HostAndPort.fromParts(publicAddress, 40081));
+        assertEquals(pfm.lookup(publicIpId, 81), 
HostAndPort.fromParts(publicAddress, 40081));
+    }
+    
+    @Test
+    public void testForgetPortMappingsOfMachine() throws Exception {
+        String publicIpId = "myipid";
+        String publicIpId2 = "myipid2";
+        String publicAddress = "5.6.7.8";
+
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicAddress, 40080), 
machine1, 80);
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicAddress, 40081), 
machine1, 81);
+        pfm.associate(publicIpId2, HostAndPort.fromParts(publicAddress, 
40082), machine2, 80);
+        pfm.forgetPortMappings(machine1);
+        
+        assertNull(pfm.lookup(machine1, 80));
+        assertNull(pfm.lookup(machine1, 81));
+        assertNull(pfm.lookup(publicIpId, 80));
+        assertEquals(pfm.lookup(machine2, 80), 
HostAndPort.fromParts(publicAddress, 40082));
+    }
+    
+    @Test
+    public void testAssociateLegacy() throws Exception {
+        String publicIpId = "myipid";
+        String publicAddress = "5.6.7.8";
+
+        pfm.acquirePublicPortExplicit(publicIpId, 40080);
+        pfm.recordPublicIpHostname(publicIpId, publicAddress);
+        pfm.associate(publicIpId, 40080, machine1, 80);
+        
+        assertEquals(pfm.lookup(publicIpId, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+        assertEquals(pfm.lookup(machine1, 80), 
HostAndPort.fromParts(publicAddress, 40080));
+    }
+
+    @Test
+    public void testAssociationListeners() throws Exception {
+        final AtomicInteger associationCreatedCount = new AtomicInteger(0);
+        final AtomicInteger associationDeletedCount = new AtomicInteger(0);
+
+        final String publicIpId = "myipid";
+        final String anotherIpId = "anotherIpId";
+
+        pfm.addAssociationListener(new 
PortForwardManager.AssociationListener() {
+            @Override
+            public void 
onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
+                associationCreatedCount.incrementAndGet();
+            }
+
+            @Override
+            public void 
onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
+                associationDeletedCount.incrementAndGet();
+            }
+        }, new Predicate<PortForwardManager.AssociationMetadata>() {
+            @Override
+            public boolean apply(PortForwardManager.AssociationMetadata 
metadata) {
+                return publicIpId.equals(metadata.getPublicIpId());
+            }
+        });
+
+        pfm.associate(publicIpId, HostAndPort.fromParts(publicIpId, 40080), 
machine1, 80);
+        pfm.associate(anotherIpId, HostAndPort.fromParts(anotherIpId, 40081), 
machine1, 80);
+        pfm.forgetPortMapping(publicIpId, 40080);
+        pfm.forgetPortMapping(anotherIpId, 40081);
+
+        assertEquals(associationCreatedCount.get(), 1);
+        assertEquals(associationDeletedCount.get(), 1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/AbstractLocationTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/location/basic/AbstractLocationTest.java
 
b/core/src/test/java/org/apache/brooklyn/location/basic/AbstractLocationTest.java
new file mode 100644
index 0000000..4977195
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/location/basic/AbstractLocationTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+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.collections.MutableSet;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class AbstractLocationTest {
+
+    public static class ConcreteLocation extends AbstractLocation {
+        private static final long serialVersionUID = 3954199300889119970L;
+        @SetFromFlag(defaultVal="mydefault")
+        String myfield;
+
+        public ConcreteLocation() {
+            super();
+        }
+
+        public ConcreteLocation(Map<?,?> properties) {
+            super(properties);
+        }
+    }
+
+    private ManagementContext mgmt;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        mgmt = LocalManagementContextForTests.newInstance();
+    }
+    
+    @AfterMethod(alwaysRun = true)
+    public void tearDown(){
+        if (mgmt!=null) Entities.destroyAll(mgmt);
+    }
+
+    private ConcreteLocation createConcrete() {
+        return createConcrete(MutableMap.<String,Object>of());
+    }
+    private ConcreteLocation createConcrete(Map<String,?> flags) {
+        return createConcrete(null, flags);
+    }
+    @SuppressWarnings("deprecation")
+    private ConcreteLocation createConcrete(String id, Map<String,?> flags) {
+        return mgmt.getLocationManager().createLocation( 
LocationSpec.create(ConcreteLocation.class).id(id).configure(flags) );
+    }
+    
+    @Test
+    public void testEqualsUsesId() {
+        Location l1 = createConcrete("1", MutableMap.of("name", "bob"));
+        Location l1b = new ConcreteLocation(ImmutableMap.of("id", 1));
+        Location l2 = createConcrete("2", MutableMap.of("name", "frank"));
+        assertEquals(l1, l1b);
+        assertNotEquals(l1, l2);
+    }
+
+    @Test
+    public void nullNameAndParentLocationIsAcceptable() {
+        Location location = createConcrete(MutableMap.of("name", null, 
"parentLocation", null));
+        assertEquals(location.getDisplayName(), null);
+        assertEquals(location.getParent(), null);
+    }
+
+    @Test
+    public void testSettingParentLocation() {
+        Location location = createConcrete();
+        Location locationSub = createConcrete();
+        locationSub.setParent(location);
+        
+        assertEquals(ImmutableList.copyOf(location.getChildren()), 
ImmutableList.of(locationSub));
+        assertEquals(locationSub.getParent(), location);
+    }
+
+    @Test
+    public void testClearingParentLocation() {
+        Location location = createConcrete();
+        Location locationSub = createConcrete();
+        locationSub.setParent(location);
+        
+        locationSub.setParent(null);
+        assertEquals(ImmutableList.copyOf(location.getChildren()), 
Collections.emptyList());
+        assertEquals(locationSub.getParent(), null);
+    }
+    
+    @Test
+    public void testContainsLocation() {
+        Location location = createConcrete();
+        Location locationSub = createConcrete();
+        locationSub.setParent(location);
+        
+        assertTrue(location.containsLocation(location));
+        assertTrue(location.containsLocation(locationSub));
+        assertFalse(locationSub.containsLocation(location));
+    }
+
+
+    @Test
+    public void queryingNameReturnsNameGivenInConstructor() {
+        String name = "Outer Mongolia";
+        Location location = createConcrete(MutableMap.of("name", "Outer 
Mongolia"));
+        assertEquals(location.getDisplayName(), name);;
+    }
+
+    @Test
+    public void constructorParentLocationReturnsExpectedLocation() {
+        Location parent = createConcrete(MutableMap.of("name", "Middle 
Earth"));
+        Location child = createConcrete(MutableMap.of("name", "The Shire", 
"parentLocation", parent));
+        assertEquals(child.getParent(), parent);
+        assertEquals(ImmutableList.copyOf(parent.getChildren()), 
ImmutableList.of(child));
+    }
+
+    @Test
+    public void setParentLocationReturnsExpectedLocation() {
+        Location parent = createConcrete(MutableMap.of("name", "Middle 
Earth"));
+        Location child = createConcrete(MutableMap.of("name", "The Shire"));
+        child.setParent(parent);
+        assertEquals(child.getParent(), parent);
+        assertEquals(ImmutableList.copyOf(parent.getChildren()), 
ImmutableList.of(child));
+    }
+    
+    @Test
+    public void testAddChildToParentLocationReturnsExpectedLocation() {
+        ConcreteLocation parent = createConcrete();
+        Location child = createConcrete();
+        parent.addChild(child);
+        assertEquals(child.getParent(), parent);
+        assertEquals(ImmutableList.copyOf(parent.getChildren()), 
ImmutableList.of(child));
+    }
+
+    @Test
+    public void testFieldSetFromFlag() {
+        ConcreteLocation loc = createConcrete(MutableMap.of("myfield", 
"myval"));
+        assertEquals(loc.myfield, "myval");
+    }
+    
+    @Test
+    public void testFieldSetFromFlagUsesDefault() {
+        ConcreteLocation loc = createConcrete();
+        assertEquals(loc.myfield, "mydefault");
+    }
+
+    @Test
+    public void testLocationTags() throws Exception {
+        LocationInternal loc = 
mgmt.getLocationManager().createLocation(LocationSpec.create(ConcreteLocation.class).tag("x"));
+        assertEquals(loc.tags().getTags(), MutableSet.of("x"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocationTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocationTest.java
 
b/core/src/test/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocationTest.java
new file mode 100644
index 0000000..05b53b2
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocationTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.util.Map;
+
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+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.MachineProvisioningLocation;
+import 
org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine;
+import brooklyn.management.internal.LocalManagementContext;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class AggregatingMachineProvisioningLocationTest {
+
+    
+    private LocalManagementContext managementContext;
+    private AggregatingMachineProvisioningLocation<LocalhostMachine> 
aggregator;
+    private LocalhostMachine machine1a;
+    private LocalhostMachine machine1b;
+    private LocalhostMachine machine2a;
+    private LocalhostMachine machine2b;
+    private MachineProvisioningLocation<LocalhostMachine> provisioner1;
+    private MachineProvisioningLocation<LocalhostMachine> provisioner2;
+    
+    @BeforeMethod(alwaysRun=true)
+    @SuppressWarnings("unchecked")
+    public void setUp() {
+        managementContext = new LocalManagementContextForTests();
+        machine1a = newLocation(LocalhostMachine.class, "1a");
+        machine1b = newLocation(LocalhostMachine.class, "1b");
+        machine2a = newLocation(LocalhostMachine.class, "2a");
+        machine2b = newLocation(LocalhostMachine.class, "2b");
+        provisioner1 = newLocation(FixedListMachineProvisioningLocation.class, 
ImmutableMap.of("machines", ImmutableList.of(machine1a, machine1b)));
+        provisioner2 = newLocation(FixedListMachineProvisioningLocation.class, 
ImmutableMap.of("machines", ImmutableList.of(machine2a, machine2b)));
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testObtainAndRelease() throws Exception {
+        aggregator = newLocation(AggregatingMachineProvisioningLocation.class, 
ImmutableMap.of("provisioners", ImmutableList.of(provisioner1, provisioner2)));
+        assertEquals(aggregator.obtain(), machine1a);
+        assertEquals(aggregator.obtain(), machine2a);
+        assertEquals(aggregator.obtain(), machine1b);
+        assertEquals(aggregator.obtain(), machine2b);
+        
+        try {
+            aggregator.obtain();
+            fail();
+        } catch (NoMachinesAvailableException e) {
+            // success
+        }
+        
+        aggregator.release(machine2b);
+        assertEquals(aggregator.obtain(), machine2b);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testReleaseWhenNotHeldThrows() throws Exception {
+        aggregator = newLocation(AggregatingMachineProvisioningLocation.class, 
ImmutableMap.of("provisioners", ImmutableList.of(provisioner1, provisioner2)));
+        try {
+            aggregator.release(machine1a);
+            fail();
+        } catch (IllegalStateException e) {
+            if (!e.toString().contains("machine is not currently allocated")) 
throw e;
+        }
+    }
+
+    private <T extends Location> T newLocation(Class<T> clazz, String 
displayName) {
+        return newLocation(clazz, displayName, ImmutableMap.of());
+    }
+
+    private <T extends Location> T newLocation(Class<T> clazz, Map<?,?> 
config) {
+        return newLocation(clazz, "mydisplayname", config);
+    }
+    
+    private <T extends Location> T newLocation(Class<T> clazz, String 
displayName, Map<?,?> config) {
+        return 
managementContext.getLocationManager().createLocation(LocationSpec.create(clazz)
+                .displayName(displayName)
+                .configure(config));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/ByonLocationResolverTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/location/basic/ByonLocationResolverTest.java
 
b/core/src/test/java/org/apache/brooklyn/location/basic/ByonLocationResolverTest.java
new file mode 100644
index 0000000..141ab5d
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/location/basic/ByonLocationResolverTest.java
@@ -0,0 +1,423 @@
+/*
+ * 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.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.MachineProvisioningLocation;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+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.config.BrooklynProperties;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Networking;
+import brooklyn.util.net.UserAndHostAndPort;
+import brooklyn.util.os.Os;
+import brooklyn.util.text.StringPredicates;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+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.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class ByonLocationResolverTest {
+
+    private static final Logger log = 
LoggerFactory.getLogger(ByonLocationResolverTest.class);
+    
+    private BrooklynProperties brooklynProperties;
+    private LocalManagementContext managementContext;
+    private Predicate<CharSequence> defaultNamePredicate;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        brooklynProperties = managementContext.getBrooklynProperties();
+        defaultNamePredicate = 
StringPredicates.startsWith(FixedListMachineProvisioningLocation.class.getSimpleName());
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+    
+    @Test
+    public void testTakesByonScopedProperties() {
+        brooklynProperties.put("brooklyn.location.byon.privateKeyFile", 
"myprivatekeyfile");
+        brooklynProperties.put("brooklyn.location.byon.publicKeyFile", 
"mypublickeyfile");
+        brooklynProperties.put("brooklyn.location.byon.privateKeyData", 
"myprivateKeyData");
+        brooklynProperties.put("brooklyn.location.byon.publicKeyData", 
"myPublicKeyData");
+        brooklynProperties.put("brooklyn.location.byon.privateKeyPassphrase", 
"myprivateKeyPassphrase");
+
+        Map<String, Object> conf = 
resolve("byon(hosts=\"1.1.1.1\")").config().getBag().getAllConfig();
+        
+        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
+        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
+        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
+        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
+        assertEquals(conf.get("privateKeyPassphrase"), 
"myprivateKeyPassphrase");
+    }
+
+    @Test
+    public void testNamedByonLocation() throws Exception {
+        brooklynProperties.put("brooklyn.location.named.mynamed", 
"byon(hosts=\"1.1.1.1\")");
+        
+        FixedListMachineProvisioningLocation<MachineLocation> loc = 
resolve("named:mynamed");
+        assertEquals(loc.obtain().getAddress(), 
InetAddress.getByName("1.1.1.1"));
+    }
+
+    @Test
+    public void testPropertiesInSpec() throws Exception {
+        FixedListMachineProvisioningLocation<MachineLocation> loc = 
resolve("byon(privateKeyFile=myprivatekeyfile,hosts=\"1.1.1.1\")");
+        SshMachineLocation machine = (SshMachineLocation)loc.obtain();
+        
+        assertEquals(machine.config().getBag().getStringKey("privateKeyFile"), 
"myprivatekeyfile");
+        assertEquals(machine.getAddress(), 
Networking.getInetAddressWithFixedName("1.1.1.1"));
+    }
+
+    @Test
+    public void testPropertyScopePrecedence() throws Exception {
+        brooklynProperties.put("brooklyn.location.named.mynamed", 
"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.location.byon.privateKeyFile", 
"privateKeyFile-inProviderSpecific");
+        brooklynProperties.put("brooklyn.localhost.privateKeyFile", 
"privateKeyFile-inGeneric");
+
+        // prefer those in provider-specific over generic
+        brooklynProperties.put("brooklyn.location.byon.publicKeyFile", 
"publicKeyFile-inProviderSpecific");
+        brooklynProperties.put("brooklyn.location.publicKeyFile", 
"publicKeyFile-inGeneric");
+
+        // prefer location-generic if nothing else
+        brooklynProperties.put("brooklyn.location.privateKeyData", 
"privateKeyData-inGeneric");
+
+        Map<String, Object> conf = 
resolve("named:mynamed").config().getBag().getAllConfig();
+        
+        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
+        assertEquals(conf.get("publicKeyFile"), 
"publicKeyFile-inProviderSpecific");
+        assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric");
+    }
+
+    @Test
+    public void testThrowsOnInvalid() throws Exception {
+        assertThrowsNoSuchElement("wrongprefix:(hosts=\"1.1.1.1\")");
+        assertThrowsIllegalArgument("byon"); // no hosts
+        assertThrowsIllegalArgument("byon()"); // no hosts
+        assertThrowsIllegalArgument("byon(hosts=\"\")"); // empty hosts
+        assertThrowsIllegalArgument("byon(hosts=\"1.1.1.1\""); // no closing 
bracket
+        assertThrowsIllegalArgument("byon(hosts=\"1.1.1.1\", name)"); // no 
value for name
+        assertThrowsIllegalArgument("byon(hosts=\"1.1.1.1\", name=)"); // no 
value for name
+    }
+    
+    @Test(expectedExceptions={IllegalArgumentException.class})
+    public void testRegistryCommaResolutionInListNotAllowed() throws 
NoMachinesAvailableException {
+        // disallowed since 0.7.0
+        // fails because it interprets the entire string as a single byon 
spec, which does not parse
+        
managementContext.getLocationRegistry().resolve(ImmutableList.of("byon(hosts=\"192.168.0.1\",user=bob),byon(hosts=\"192.168.0.2\",user=bob2)"));
+    }
+
+    @Test
+    public void testResolvesHosts() throws Exception {
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\")"), 
ImmutableSet.of("1.1.1.1"));
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\")"), 
ImmutableSet.of("1.1.1.1"));
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1,1.1.1.2\")"), 
ImmutableSet.of("1.1.1.1","1.1.1.2"));
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1, 1.1.1.2\")"), 
ImmutableSet.of("1.1.1.1","1.1.1.2"));
+    }
+
+    @Test
+    public void testWithOldStyleColon() throws Exception {
+        assertByonClusterEquals(resolve("byon:(hosts=\"1.1.1.1\")"), 
ImmutableSet.of("1.1.1.1"));
+        assertByonClusterEquals(resolve("byon:(hosts=\"1.1.1.1\", 
name=myname)"), ImmutableSet.of("1.1.1.1"), "myname");
+    }
+
+    @Test
+    public void testUsesDisplayName() throws Exception {
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\", 
name=myname)"), ImmutableSet.of("1.1.1.1"), "myname");
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\", 
name=\"myname\")"), ImmutableSet.of("1.1.1.1"), "myname");
+    }
+
+    @Test
+    public void testResolvesHostsGlobExpansion() throws Exception {
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.{1,2}\")"), 
ImmutableSet.of("1.1.1.1","1.1.1.2"));
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.{1.1,2.{1,2}}\")"), 
+                ImmutableSet.of("1.1.1.1","1.1.2.1","1.1.2.2"));
+        assertByonClusterEquals(resolve("byon(hosts=\"1.1.{1,2}.{1,2}\")"), 
+                ImmutableSet.of("1.1.1.1","1.1.1.2","1.1.2.1","1.1.2.2"));
+    }
+
+    @Test(groups="Integration")
+    public void testNiceError() throws Exception {
+        Asserts.assertFailsWith(new Runnable() {
+            @Override public void run() {
+                FixedListMachineProvisioningLocation<MachineLocation> x =
+                        resolve("byon(hosts=\"1.1.1.{1,2}}\")");
+                log.error("got "+x+" but should have failed (your DNS is 
giving an IP for hostname '1.1.1.1}' (with the extra '}')");
+            }
+        }, new Predicate<Throwable>() {
+            @Override
+            public boolean apply(@Nullable Throwable input) {
+                String s = input.toString();
+                // words
+                if (!s.contains("Invalid host")) return false;
+                // problematic entry
+                if (!s.contains("1.1.1.1}")) return false;
+                // original spec
+                if (!s.contains("1.1.1.{1,2}}")) return false;
+                return true;
+            }
+        });
+    }
+
+    @Test
+    public void testResolvesUsernameAtHost() throws Exception {
+        
assertByonClusterWithUsersEquals(resolve("byon(hosts=\"[email protected]\")"), 
+                ImmutableSet.of(UserAndHostAndPort.fromParts("myuser", 
"1.1.1.1", 22)));
+        
assertByonClusterWithUsersEquals(resolve("byon(hosts=\"[email protected],[email protected]\")"),
 ImmutableSet.of(
+                UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22), 
UserAndHostAndPort.fromParts("myuser2", "1.1.1.1", 22)));
+        
assertByonClusterWithUsersEquals(resolve("byon(hosts=\"[email protected],[email protected]\")"),
 ImmutableSet.of(
+                UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22), 
UserAndHostAndPort.fromParts("myuser2", "1.1.1.2", 22)));
+    }
+
+    @Test
+    public void testResolvesUserArg() throws Exception {
+        
assertByonClusterWithUsersEquals(resolve("byon(hosts=\"1.1.1.1\",user=bob)"), 
+                ImmutableSet.of(UserAndHostAndPort.fromParts("bob", "1.1.1.1", 
22)));
+        
assertByonClusterWithUsersEquals(resolve("byon(user=\"bob\",hosts=\"[email protected],1.1.1.1\")"),
 
+                ImmutableSet.of(UserAndHostAndPort.fromParts("myuser", 
"1.1.1.1", 22), UserAndHostAndPort.fromParts("bob", "1.1.1.1", 22)));
+    }
+
+    @Test
+    public void testResolvesUserArg2() throws Exception {
+        String spec = "byon(hosts=\"1.1.1.1\",user=bob)";
+        FixedListMachineProvisioningLocation<MachineLocation> ll = 
resolve(spec);
+        SshMachineLocation l = (SshMachineLocation)ll.obtain();
+        Assert.assertEquals("bob", l.getUser());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testResolvesUserArg3() throws Exception {
+        String spec = "byon(hosts=\"1.1.1.1\")";
+        
managementContext.getLocationRegistry().getProperties().putAll(MutableMap.of(
+                "brooklyn.location.named.foo", spec,
+                "brooklyn.location.named.foo.user", "bob"));
+        
((BasicLocationRegistry)managementContext.getLocationRegistry()).updateDefinedLocations();
+        
+        MachineProvisioningLocation<SshMachineLocation> ll = 
(MachineProvisioningLocation<SshMachineLocation>)
+                new 
NamedLocationResolver().newLocationFromString(MutableMap.of(), "named:foo", 
managementContext.getLocationRegistry());
+        SshMachineLocation l = ll.obtain(MutableMap.of());
+        Assert.assertEquals("bob", l.getUser());
+    }
+
+    @Test
+    public void testResolvesPortArg() throws Exception {
+        
assertByonClusterWithUsersEquals(resolve("byon(user=bob,port=8022,hosts=\"1.1.1.1\")"),
 
+                ImmutableSet.of(UserAndHostAndPort.fromParts("bob", "1.1.1.1", 
8022)));
+        
assertByonClusterWithUsersEquals(resolve("byon(user=bob,port=8022,hosts=\"[email protected],1.1.1.2:8901\")"),
 
+                ImmutableSet.of(UserAndHostAndPort.fromParts("myuser", 
"1.1.1.1", 8022), UserAndHostAndPort.fromParts("bob", "1.1.1.2", 8901)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    /** private key should be inherited, so confirm that happens correctly */
+    public void testResolvesPrivateKeyArgInheritance() throws Exception {
+        String spec = "byon(hosts=\"1.1.1.1\")";
+        
managementContext.getLocationRegistry().getProperties().putAll(MutableMap.of(
+                "brooklyn.location.named.foo", spec,
+                "brooklyn.location.named.foo.user", "bob",
+                "brooklyn.location.named.foo.privateKeyFile", "/tmp/x"));
+        
((BasicLocationRegistry)managementContext.getLocationRegistry()).updateDefinedLocations();
+        
+        MachineProvisioningLocation<SshMachineLocation> ll = 
(MachineProvisioningLocation<SshMachineLocation>) 
+                new 
NamedLocationResolver().newLocationFromString(MutableMap.of(), "named:foo", 
managementContext.getLocationRegistry());
+        
+        Assert.assertEquals("/tmp/x", 
ll.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE));
+        
Assert.assertTrue(((LocationInternal)ll).config().getLocalRaw(LocationConfigKeys.PRIVATE_KEY_FILE).isPresent());
+        Assert.assertEquals("/tmp/x", 
((LocationInternal)ll).config().getLocalBag().getStringKey(LocationConfigKeys.PRIVATE_KEY_FILE.getName()));
+        Assert.assertEquals("/tmp/x", 
((LocationInternal)ll).config().getBag().get(LocationConfigKeys.PRIVATE_KEY_FILE));
+
+        SshMachineLocation l = ll.obtain(MutableMap.of());
+        
+        Assert.assertEquals("/tmp/x", 
l.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE));
+        
+        
Assert.assertTrue(l.config().getRaw(LocationConfigKeys.PRIVATE_KEY_FILE).isPresent());
+        
Assert.assertTrue(l.config().getLocalRaw(LocationConfigKeys.PRIVATE_KEY_FILE).isAbsent());
+
+        Assert.assertEquals("/tmp/x", 
l.config().getBag().getStringKey(LocationConfigKeys.PRIVATE_KEY_FILE.getName()));
+        Assert.assertEquals("/tmp/x", 
l.config().getBag().getStringKey(LocationConfigKeys.PRIVATE_KEY_FILE.getName()));
+
+        Assert.assertEquals("/tmp/x", 
l.config().getBag().get(LocationConfigKeys.PRIVATE_KEY_FILE));
+    }
+
+    @Test
+    public void testResolvesLocalTempDir() throws Exception {
+        String localTempDir = Os.mergePaths(Os.tmp(), 
"testResolvesUsernameAtHost");
+        brooklynProperties.put("brooklyn.location.byon.localTempDir", 
localTempDir);
+
+        FixedListMachineProvisioningLocation<MachineLocation> byon = 
resolve("byon(hosts=\"1.1.1.1\",osFamily=\"windows\")");
+        MachineLocation machine = byon.obtain();
+        assertEquals(machine.getConfig(SshMachineLocation.LOCAL_TEMP_DIR), 
localTempDir);
+    }
+
+    @Test
+    public void testMachinesObtainedInOrder() throws Exception {
+        List<String> ips = ImmutableList.of("1.1.1.1", "1.1.1.6", "1.1.1.3", 
"1.1.1.4", "1.1.1.5");
+        String spec = "byon(hosts=\""+Joiner.on(",").join(ips)+"\")";
+        
+        MachineProvisioningLocation<MachineLocation> ll = resolve(spec);
+
+        for (String expected : ips) {
+            MachineLocation obtained = ll.obtain(ImmutableMap.of());
+            assertEquals(obtained.getAddress().getHostAddress(), expected);
+        }
+    }
+    
+    @Test
+    public void testEmptySpec() throws Exception {
+        String spec = "byon";
+        Map<String, ?> flags = ImmutableMap.of(
+                "hosts", ImmutableList.of("1.1.1.1", "2.2.2.22"),
+                "name", "foo",
+                "user", "myuser"
+        );
+        MachineProvisioningLocation<MachineLocation> provisioner = 
resolve(spec, flags);
+        SshMachineLocation location1 = 
(SshMachineLocation)provisioner.obtain(ImmutableMap.of());
+        Assert.assertEquals("myuser", location1.getUser());
+        Assert.assertEquals("1.1.1.1", 
location1.getAddress().getHostAddress());
+    }
+
+    @Test
+    public void testWindowsMachines() throws Exception {
+        brooklynProperties.put("brooklyn.location.byon.user", "myuser");
+        brooklynProperties.put("brooklyn.location.byon.password", 
"mypassword");
+        String spec = "byon";
+        Map<String, ?> flags = ImmutableMap.of(
+                "hosts", ImmutableList.of("1.1.1.1", "2.2.2.2"),
+                "osfamily", "windows"
+        );
+        MachineProvisioningLocation<MachineLocation> provisioner = 
resolve(spec, flags);
+        WinRmMachineLocation location = (WinRmMachineLocation) 
provisioner.obtain(ImmutableMap.of());
+
+        assertEquals(location.config().get(WinRmMachineLocation.USER), 
"myuser");
+        assertEquals(location.config().get(WinRmMachineLocation.PASSWORD), 
"mypassword");
+        
assertEquals(location.config().get(WinRmMachineLocation.ADDRESS).getHostAddress(),
 "1.1.1.1");
+    }
+
+    @Test
+    public void testNoneWindowsMachines() throws Exception {
+        String spec = "byon";
+        Map<String, ?> flags = ImmutableMap.of(
+                "hosts", ImmutableList.of("1.1.1.1", "2.2.2.2"),
+                "osfamily", "linux"
+        );
+        MachineProvisioningLocation<MachineLocation> provisioner = 
resolve(spec, flags);
+        MachineLocation location = provisioner.obtain(ImmutableMap.of());
+        assertTrue(location instanceof SshMachineLocation, "Expected location 
to be SshMachineLocation, found " + location);
+    }
+
+    @Test
+    public void testAdditionalConfig() throws Exception {
+        FixedListMachineProvisioningLocation<MachineLocation> loc = 
resolve("byon(mykey=myval,hosts=\"1.1.1.1\")");
+        MachineLocation machine = loc.obtain(ImmutableMap.of());
+        assertEquals(machine.getConfig(ConfigKeys.newConfigKey(String.class, 
"mykey")), "myval");
+    }
+
+    private void 
assertByonClusterEquals(FixedListMachineProvisioningLocation<? extends 
MachineLocation> cluster, Set<String> expectedHosts) {
+        assertByonClusterEquals(cluster, expectedHosts, defaultNamePredicate);
+    }
+    
+    private void 
assertByonClusterEquals(FixedListMachineProvisioningLocation<? extends 
MachineLocation> cluster, Set<String> expectedHosts, String expectedName) {
+        assertByonClusterEquals(cluster, expectedHosts, 
Predicates.equalTo(expectedName));
+    }
+    
+    private void 
assertByonClusterEquals(FixedListMachineProvisioningLocation<? extends 
MachineLocation> cluster, Set<String> expectedHosts, Predicate<? super String> 
expectedName) {
+        Set<String> actualHosts = 
ImmutableSet.copyOf(Iterables.transform(cluster.getMachines(), new 
Function<MachineLocation, String>() {
+            @Override public String apply(MachineLocation input) {
+                return input.getAddress().getHostName();
+            }}));
+        assertEquals(actualHosts, expectedHosts);
+        assertTrue(expectedName.apply(cluster.getDisplayName()), 
"name="+cluster.getDisplayName());
+    }
+
+    private void 
assertByonClusterWithUsersEquals(FixedListMachineProvisioningLocation<? extends 
MachineLocation> cluster, Set<UserAndHostAndPort> expectedHosts) {
+        assertByonClusterWithUsersEquals(cluster, expectedHosts, 
defaultNamePredicate);
+    }
+    
+    private void 
assertByonClusterWithUsersEquals(FixedListMachineProvisioningLocation<? extends 
MachineLocation> cluster, Set<UserAndHostAndPort> expectedHosts, Predicate<? 
super String> expectedName) {
+        Set<UserAndHostAndPort> actualHosts = 
ImmutableSet.copyOf(Iterables.transform(cluster.getMachines(), new 
Function<MachineLocation, UserAndHostAndPort>() {
+            @Override public UserAndHostAndPort apply(MachineLocation input) {
+                SshMachineLocation machine = (SshMachineLocation) input;
+                return UserAndHostAndPort.fromParts(machine.getUser(), 
machine.getAddress().getHostName(), machine.getPort());
+            }}));
+        assertEquals(actualHosts, expectedHosts);
+        assertTrue(expectedName.apply(cluster.getDisplayName()), 
"name="+cluster.getDisplayName());
+    }
+
+    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 FixedListMachineProvisioningLocation<MachineLocation> 
resolve(String val) {
+        return (FixedListMachineProvisioningLocation<MachineLocation>) 
managementContext.getLocationRegistry().resolve(val);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private FixedListMachineProvisioningLocation<MachineLocation> 
resolve(String val, Map<?, ?> locationFlags) {
+        return (FixedListMachineProvisioningLocation<MachineLocation>) 
managementContext.getLocationRegistry().resolve(val, locationFlags);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocationRebindTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocationRebindTest.java
 
b/core/src/test/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocationRebindTest.java
new file mode 100644
index 0000000..c4df75f
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocationRebindTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.io.File;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.location.Location;
+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.util.collections.MutableSet;
+import brooklyn.util.os.Os;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class FixedListMachineProvisioningLocationRebindTest {
+
+    private FixedListMachineProvisioningLocation<SshMachineLocation> origLoc;
+    private ClassLoader classLoader = getClass().getClassLoader();
+    private ManagementContext origManagementContext;
+    private TestApplication origApp;
+    private TestApplication newApp;
+    private File mementoDir;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        mementoDir = Os.newTempDir(getClass());
+        origManagementContext = 
RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+        
+        origLoc = new 
FixedListMachineProvisioningLocation.Builder(origManagementContext.getLocationManager())
+                .addAddresses("localhost", "127.0.0.1")
+                .user("myuser")
+                .keyFile("/path/to/myPrivateKeyFile")
+                .keyData("myKeyData")
+                .keyPassphrase("myKeyPassphrase")
+                .build();
+        origApp = ApplicationBuilder.newManagedApp(TestApplication.class, 
origManagementContext);
+        origApp.start(ImmutableList.of(origLoc));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (origManagementContext != null) 
Entities.destroyAll(origManagementContext);
+        if (newApp != null) Entities.destroyAll(newApp.getManagementContext());
+        if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+    }
+    
+    @Test
+    public void testRebindPreservesConfig() throws Exception {
+        newApp = rebind();
+        FixedListMachineProvisioningLocation<SshMachineLocation> newLoc = 
(FixedListMachineProvisioningLocation<SshMachineLocation>) 
Iterables.get(newApp.getLocations(), 0);
+        
+        assertEquals(newLoc.getId(), origLoc.getId());
+        assertEquals(newLoc.getDisplayName(), origLoc.getDisplayName());
+        assertEquals(newLoc.getHostGeoInfo(), origLoc.getHostGeoInfo());
+        assertEquals(newLoc.getConfig(LocationConfigKeys.USER), 
origLoc.getConfig(LocationConfigKeys.USER));
+        
assertEquals(newLoc.getConfig(LocationConfigKeys.PRIVATE_KEY_PASSPHRASE), 
origLoc.getConfig(LocationConfigKeys.PRIVATE_KEY_PASSPHRASE));
+        assertEquals(newLoc.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE), 
origLoc.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE));
+        assertEquals(newLoc.getConfig(LocationConfigKeys.PRIVATE_KEY_DATA), 
origLoc.getConfig(LocationConfigKeys.PRIVATE_KEY_DATA));
+    }
+
+    @Test
+    public void testRebindParentRelationship() throws Exception {
+        newApp = rebind();
+        FixedListMachineProvisioningLocation<SshMachineLocation> newLoc = 
(FixedListMachineProvisioningLocation<SshMachineLocation>) 
Iterables.get(newApp.getLocations(), 0);
+        
+        assertLocationIdsEqual(newLoc.getChildren(), origLoc.getChildren());
+        assertEquals(Iterables.get(newLoc.getChildren(), 0).getParent(), 
newLoc);
+        assertEquals(Iterables.get(newLoc.getChildren(), 1).getParent(), 
newLoc);
+    }
+
+    @Test
+    public void testRebindPreservesInUseMachines() throws Exception {
+        SshMachineLocation inuseMachine = origLoc.obtain();
+        origApp.setAttribute(TestApplication.SERVICE_UP, true); // to force 
persist, and thus avoid race
+        
+        newApp = rebind();
+        FixedListMachineProvisioningLocation<SshMachineLocation> newLoc = 
(FixedListMachineProvisioningLocation<SshMachineLocation>) 
Iterables.get(newApp.getLocations(), 0);
+        
+        assertLocationIdsEqual(newLoc.getInUse(), origLoc.getInUse());
+        assertLocationIdsEqual(newLoc.getAvailable(), origLoc.getAvailable());
+    }
+
+    private TestApplication rebind() throws Exception {
+        RebindTestUtils.waitForPersisted(origApp);
+        return (TestApplication) RebindTestUtils.rebind(mementoDir, 
getClass().getClassLoader());
+    }
+    
+    private void assertLocationIdsEqual(Iterable<? extends Location> actual, 
Iterable<? extends Location> expected) {
+        Function<Location, String> locationIdFunction = new Function<Location, 
String>() {
+            @Override public String apply(@Nullable Location input) {
+                return (input != null) ? input.getId() : null;
+            }
+        };
+        Set<String> actualIds = MutableSet.copyOf(Iterables.transform(actual, 
locationIdFunction));
+        Set<String> expectedIds = 
MutableSet.copyOf(Iterables.transform(expected, locationIdFunction));
+        
+        assertEquals(actualIds, expectedIds);
+    }
+}

Reply via email to