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

[22/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/SshMachineLocationPerformanceTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationPerformanceTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationPerformanceTest.java
new file mode 100644
index 0000000..93e33a1
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationPerformanceTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.test.PerformanceTestUtils;
+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.util.collections.MutableMap;
+import brooklyn.util.internal.ssh.SshTool;
+import brooklyn.util.net.Networking;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ * Test the performance of different variants of invoking the sshj tool.
+ * 
+ * Intended for human-invocation and inspection, to see which parts are most expensive.
+ */
+public class SshMachineLocationPerformanceTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SshMachineLocationPerformanceTest.class);
+    
+    private SshMachineLocation machine;
+    private ListeningExecutorService executor;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        machine = new SshMachineLocation(MutableMap.of("address", "localhost"));
+        executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void afterMethod() throws Exception {
+        if (executor != null) executor.shutdownNow();
+        Streams.closeQuietly(machine);
+    }
+
+    @Test(groups = {"Integration"})
+    public void testConsecutiveSmallCommands() throws Exception {
+        runExecManyCommands(ImmutableList.of("true"), "small-cmd", 10);
+    }
+
+    // Mimics SshSensorAdapter's polling
+    @Test(groups = {"Integration"})
+    public void testConsecutiveSmallCommandsWithCustomStdoutAndErr() throws Exception {
+        final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+        final ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+        
+        Runnable task = new Runnable() {
+            @Override public void run() {
+                machine.execScript(ImmutableMap.of("out", stdout, "err", stderr), "test", ImmutableList.of("true"));
+            }};
+        runMany(task, "small-cmd-custom-stdout", 1, 10);
+    }
+
+    @Test(groups = {"Integration"})
+    public void testConcurrentSmallCommands() throws Exception {
+        runExecManyCommands(ImmutableList.of("true"), "small-cmd", 10, 10);
+    }
+
+    @Test(groups = {"Integration"})
+    public void testConsecutiveBigStdoutCommands() throws Exception {
+        runExecManyCommands(ImmutableList.of("head -c 100000 /dev/urandom"), "big-stdout", 10);
+    }
+
+    @Test(groups = {"Integration"})
+    public void testConsecutiveBigStdinCommands() throws Exception {
+        String bigstr = Identifiers.makeRandomId(100000);
+        runExecManyCommands(ImmutableList.of("echo "+bigstr+" | wc -c"), "big-stdin", 10);
+    }
+
+    @Test(groups = {"Integration"})
+    public void testConsecutiveSmallCommandsWithDifferentProperties() throws Exception {
+        final Map<String, ?> emptyProperties = Collections.emptyMap();
+        final Map<String, ?> customProperties = MutableMap.of(
+                "address", Networking.getLocalHost(),
+                SshTool.PROP_SESSION_TIMEOUT.getName(), 20000,
+                SshTool.PROP_CONNECT_TIMEOUT.getName(), 50000,
+                SshTool.PROP_SCRIPT_HEADER.getName(), "#!/bin/bash");
+
+        Runnable task = new Runnable() {
+            @Override public void run() {
+                if (Math.random() < 0.5) {
+                    machine.execScript(emptyProperties, "test", ImmutableList.of("true"));
+                } else {
+                    machine.execScript(customProperties, "test", ImmutableList.of("true"));
+                }
+            }};
+        runMany(task, "small-cmd-custom-ssh-properties", 1, 10);
+    }
+
+    private void runExecManyCommands(final List<String> cmds, String context, int iterations) throws Exception {
+        runExecManyCommands(cmds, context, 1, iterations);
+    }
+    
+    private void runExecManyCommands(final List<String> cmds, String context, int concurrentRuns, int iterations) throws Exception {
+        Runnable task = new Runnable() {
+                @Override public void run() {
+                    execScript(cmds);
+                }};
+        runMany(task, context, concurrentRuns, iterations);
+    }
+    
+    private void runMany(final Runnable task, final String context, int concurrentRuns, int iterations) throws Exception {
+        long preCpuTime = PerformanceTestUtils.getProcessCpuTime();
+        Stopwatch stopwatch = Stopwatch.createStarted();
+
+        for (int i = 0; i < iterations; i++) {
+            List<ListenableFuture<?>> futures = Lists.newArrayList();
+            for (int j = 0; j < concurrentRuns; j++) {
+                futures.add(executor.submit(new Runnable() {
+                    public void run() {
+                        try {
+                            task.run();
+                        } catch (Exception e) {
+                            LOG.error("Error for "+context+", executing "+task, e);
+                            throw Throwables.propagate(e);
+                        }
+                    }}));
+            }
+            Futures.allAsList(futures).get();
+            
+            long postCpuTime = PerformanceTestUtils.getProcessCpuTime();
+            long elapsedTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+            double fractionCpu = (elapsedTime > 0) ? ((double)postCpuTime-preCpuTime) / TimeUnit.MILLISECONDS.toNanos(elapsedTime) : -1;
+            LOG.info("Executing {}; completed {}; took {}; fraction cpu {}",
+                    new Object[] {context, (i+1), Time.makeTimeStringRounded(elapsedTime), fractionCpu});
+        }
+    }
+
+    private int execScript(List<String> cmds) {
+        return machine.execScript("mysummary", cmds);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationReuseIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationReuseIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationReuseIntegrationTest.java
new file mode 100644
index 0000000..27731e5
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationReuseIntegrationTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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 brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.internal.ssh.SshTool;
+import brooklyn.util.internal.ssh.sshj.SshjTool;
+import brooklyn.util.net.Networking;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Tests the re-use of SshTools in SshMachineLocation
+ */
+public class SshMachineLocationReuseIntegrationTest {
+
+    public static class RecordingSshjTool extends SshjTool {
+        public static final AtomicBoolean forbidden = new AtomicBoolean(false); 
+        public static final AtomicInteger connectionCount = new AtomicInteger(0);
+        public static final AtomicInteger disconnectionCount = new AtomicInteger();
+        
+        public RecordingSshjTool(Map<String, ?> map) {
+            super(map);
+        }
+
+        @Override
+        public void connect() {
+            if (forbidden.get()) throw new IllegalStateException("forbidden at this time");
+            connectionCount.incrementAndGet();
+            super.connect();
+        }
+
+        @Override
+        public void disconnect() {
+            disconnectionCount.incrementAndGet();
+            super.disconnect();
+        }
+
+        public static void reset() {
+            forbidden.set(false);
+            connectionCount.set(0);
+            disconnectionCount.set(0);
+        }
+
+        @Override
+        public int execCommands(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+            if (forbidden.get()) throw new IllegalStateException("forbidden at this time");
+            return super.execCommands(props, commands, env);
+        }
+        
+        @Override
+        public int execScript(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+            if (forbidden.get()) throw new IllegalStateException("forbidden at this time");
+            return super.execScript(props, commands, env);
+        }
+        
+        @Override
+        public int execShellDirect(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+            if (forbidden.get()) throw new IllegalStateException("forbidden at this time");
+            return super.execShellDirect(props, commands, env);
+        }
+    }
+
+    private SshMachineLocation host;
+    private LocalManagementContext managementContext;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = new LocalManagementContext();
+        host = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", Networking.getLocalHost())
+                .configure(SshTool.PROP_TOOL_CLASS, RecordingSshjTool.class.getName()));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (host != null) Streams.closeQuietly(host);
+        if (managementContext != null) Entities.destroyAll(managementContext);
+        RecordingSshjTool.reset();
+    }
+
+    @Test(groups = "Integration")
+    public void testBasicReuse() throws Exception {
+        host.execScript("mysummary", ImmutableList.of("exit"));
+        host.execScript("mysummary", ImmutableList.of("exit"));
+        assertEquals(RecordingSshjTool.connectionCount.get(), 1, "Expected one SSH connection to have been recorded");
+    }
+
+    @Test(groups = "Integration")
+    public void testReuseWithInterestingProps() throws Exception {
+        host.execScript(customSshConfigKeys(), "mysummary", ImmutableList.of("exit"));
+        host.execScript(customSshConfigKeys(), "mysummary", ImmutableList.of("exit"));
+        assertEquals(RecordingSshjTool.connectionCount.get(), 1, "Expected one SSH connection to have been recorded");
+    }
+
+    @Test(groups = "Integration")
+    public void testNewConnectionForDifferentProps() throws Exception {
+        host.execScript("mysummary", ImmutableList.of("exit"));
+        host.execScript(customSshConfigKeys(), "mysummary", ImmutableList.of("exit"));
+        assertEquals(RecordingSshjTool.connectionCount.get(), 2, "Expected two SSH connections to have been recorded");
+    }
+
+    @Test(groups = "Integration")
+    public void testSshToolReusedWhenConfigDiffers() throws Exception {
+        Map<String, Object> props = customSshConfigKeys();
+        host.execScript(props, "mysummary", ImmutableList.of("exit"));
+
+        // Use another output stream for second request
+        props.put(SshTool.PROP_SCRIPT_HEADER.getName(), "#!/bin/bash -e\n");
+        host.execScript(props, "mysummary", ImmutableList.of("exit"));
+        assertEquals(RecordingSshjTool.connectionCount.get(), 1, "Expected one SSH connection to have been recorded even though out script header differed.");
+    }
+
+    @Test(groups = "Integration")
+    public void testSshCacheExpiresEvenIfNotUsed() throws Exception {
+        SshMachineLocation host2 = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", InetAddress.getLocalHost())
+                .configure(SshMachineLocation.SSH_CACHE_EXPIRY_DURATION, Duration.ONE_SECOND)
+                .configure(SshTool.PROP_TOOL_CLASS, RecordingSshjTool.class.getName()));
+        
+        Map<String, Object> props = customSshConfigKeys();
+        host2.execScript(props, "mysummary", ImmutableList.of("exit"));
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertEquals(RecordingSshjTool.disconnectionCount.get(), 1);
+            }});
+    }
+
+    public Map<String, Object> customSshConfigKeys() throws UnknownHostException {
+        return MutableMap.<String, Object>of(
+                "address", Networking.getLocalHost(),
+                SshTool.PROP_SESSION_TIMEOUT.getName(), 20000,
+                SshTool.PROP_CONNECT_TIMEOUT.getName(), 50000,
+                SshTool.PROP_SCRIPT_HEADER.getName(), "#!/bin/bash");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationTest.java
new file mode 100644
index 0000000..37f3b32
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/SshMachineLocationTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.entity.proxying.EntityInitializer;
+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.MachineDetails;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.PortRange;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+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.BrooklynConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.EffectorTaskTest;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.file.ArchiveUtils;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.internal.ssh.RecordingSshTool;
+import brooklyn.util.internal.ssh.SshException;
+import brooklyn.util.internal.ssh.SshTool;
+import brooklyn.util.net.Networking;
+import brooklyn.util.net.Urls;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.task.BasicExecutionContext;
+import brooklyn.util.task.BasicExecutionManager;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+/**
+ * Test the {@link SshMachineLocation} implementation of the {@link Location} interface.
+ */
+public class SshMachineLocationTest {
+
+    private SshMachineLocation host;
+    private ManagementContext mgmt;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        mgmt = LocalManagementContextForTests.newInstance();
+        host = new SshMachineLocation(MutableMap.of("address", Networking.getLocalHost()));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (host != null) Streams.closeQuietly(host);
+        if (mgmt != null) Entities.destroyAll(mgmt);
+    }
+    
+    @Test(groups = "Integration")
+    public void testGetMachineDetails() throws Exception {
+        BasicExecutionManager execManager = new BasicExecutionManager("mycontextid");
+        BasicExecutionContext execContext = new BasicExecutionContext(execManager);
+        try {
+            MachineDetails details = execContext.submit(new Callable<MachineDetails>() {
+                public MachineDetails call() {
+                    return host.getMachineDetails();
+                }}).get();
+            assertNotNull(details);
+        } finally {
+            execManager.shutdownNow();
+        }
+    }
+    
+    @Test
+    public void testSupplyingMachineDetails() throws Exception {
+        MachineDetails machineDetails = new BasicMachineDetails(new BasicHardwareDetails(1, 1024), new BasicOsDetails("myname", "myarch", "myversion"));
+        SshMachineLocation host2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure(SshMachineLocation.MACHINE_DETAILS, machineDetails));
+        
+        assertSame(host2.getMachineDetails(), machineDetails);
+    }
+    
+    @Test
+    public void testConfigurePrivateAddresses() throws Exception {
+        SshMachineLocation host2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", Networking.getLocalHost())
+                .configure(SshMachineLocation.PRIVATE_ADDRESSES, ImmutableList.of("1.2.3.4"))
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true));
+
+        assertEquals(host2.getPrivateAddresses(), ImmutableSet.of("1.2.3.4"));
+    }
+    
+    // Wow, this is hard to test (until I accepted creating the entity + effector)! Code smell?
+    // Need to call getMachineDetails in a DynamicSequentialTask so that the "innessential" takes effect,
+    // to not fail its caller. But to get one of those outside of an effector is non-obvious.
+    @Test(groups = "Integration")
+    public void testGetMachineIsInessentialOnFailure() throws Exception {
+        SshMachineLocation host2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", Networking.getLocalHost())
+                .configure(SshTool.PROP_TOOL_CLASS, FailingSshTool.class.getName()));
+
+        final Effector<MachineDetails> GET_MACHINE_DETAILS = Effectors.effector(MachineDetails.class, "getMachineDetails")
+                .impl(new EffectorBody<MachineDetails>() {
+                    public MachineDetails call(ConfigBag parameters) {
+                        Maybe<MachineLocation> machine = Machines.findUniqueMachineLocation(entity().getLocations());
+                        try {
+                            machine.get().getMachineDetails();
+                            throw new IllegalStateException("Expected failure in ssh");
+                        } catch (RuntimeException e) {
+                            return null;
+                        }
+                    }})
+                .build();
+
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
+                .addInitializer(new EntityInitializer() {
+                        public void apply(EntityLocal entity) {
+                            ((EntityInternal)entity).getMutableEntityType().addEffector(EffectorTaskTest.DOUBLE_1);
+                        }});
+
+        TestApplication app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+
+        app.start(ImmutableList.of(host2));
+        
+        MachineDetails details = app.invoke(GET_MACHINE_DETAILS, ImmutableMap.<String, Object>of()).get();
+        assertNull(details);
+    }
+    public static class FailingSshTool extends RecordingSshTool {
+        public FailingSshTool(Map<?, ?> props) {
+            super(props);
+        }
+        @Override public int execScript(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+            throw new RuntimeException("Simulating failure of ssh: cmds="+commands);
+        }
+        @Override public int execCommands(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+            throw new RuntimeException("Simulating failure of ssh: cmds="+commands);
+        }
+    }
+    
+    // Note: requires `ssh localhost` to be setup such that no password is required    
+    @Test(groups = "Integration")
+    public void testSshExecScript() throws Exception {
+        OutputStream outStream = new ByteArrayOutputStream();
+        String expectedName = Os.user();
+        host.execScript(MutableMap.of("out", outStream), "mysummary", ImmutableList.of("whoami; exit"));
+        String outString = outStream.toString();
+        
+        assertTrue(outString.contains(expectedName), outString);
+    }
+    
+    // Note: requires `ssh localhost` to be setup such that no password is required    
+    @Test(groups = "Integration")
+    public void testSshExecCommands() throws Exception {
+        OutputStream outStream = new ByteArrayOutputStream();
+        String expectedName = Os.user();
+        host.execCommands(MutableMap.of("out", outStream), "mysummary", ImmutableList.of("whoami; exit"));
+        String outString = outStream.toString();
+        
+        assertTrue(outString.contains(expectedName), outString);
+    }
+    
+    // For issue #230
+    @Test(groups = "Integration")
+    public void testOverridingPropertyOnExec() throws Exception {
+        SshMachineLocation host = new SshMachineLocation(MutableMap.of("address", Networking.getLocalHost(), "sshPrivateKeyData", "wrongdata"));
+        
+        OutputStream outStream = new ByteArrayOutputStream();
+        String expectedName = Os.user();
+        host.execCommands(MutableMap.of("sshPrivateKeyData", null, "out", outStream), "my summary", ImmutableList.of("whoami"));
+        String outString = outStream.toString();
+        
+        assertTrue(outString.contains(expectedName), "outString="+outString);
+    }
+
+    @Test(groups = "Integration", expectedExceptions={IllegalStateException.class, SshException.class})
+    public void testSshRunWithInvalidUserFails() throws Exception {
+        SshMachineLocation badHost = new SshMachineLocation(MutableMap.of("user", "doesnotexist", "address", Networking.getLocalHost()));
+        badHost.execScript("mysummary", ImmutableList.of("whoami; exit"));
+    }
+    
+    // Note: requires `ssh localhost` to be setup such that no password is required    
+    @Test(groups = "Integration")
+    public void testCopyFileTo() throws Exception {
+        File dest = Os.newTempFile(getClass(), ".dest.tmp");
+        File src = Os.newTempFile(getClass(), ".src.tmp");
+        try {
+            Files.write("abc", src, Charsets.UTF_8);
+            host.copyTo(src, dest);
+            assertEquals("abc", Files.readFirstLine(dest, Charsets.UTF_8));
+        } finally {
+            src.delete();
+            dest.delete();
+        }
+    }
+
+    // Note: requires `ssh localhost` to be setup such that no password is required    
+    @Test(groups = "Integration")
+    public void testCopyStreamTo() throws Exception {
+        String contents = "abc";
+        File dest = new File(Os.tmp(), "sssMachineLocationTest_dest.tmp");
+        try {
+            host.copyTo(Streams.newInputStreamWithContents(contents), dest.getAbsolutePath());
+            assertEquals("abc", Files.readFirstLine(dest, Charsets.UTF_8));
+        } finally {
+            dest.delete();
+        }
+    }
+
+    @Test(groups = "Integration")
+    public void testInstallUrlTo() throws Exception {
+        File dest = new File(Os.tmp(), "sssMachineLocationTest_dir/");
+        dest.mkdir();
+        try {
+            int result = host.installTo("https://raw.github.com/brooklyncentral/brooklyn/master/README.md", Urls.mergePaths(dest.getAbsolutePath(), "README.md"));
+            assertEquals(result, 0);
+            String contents = ArchiveUtils.readFullyString(new File(dest, "README.md"));
+            assertTrue(contents.contains("http://brooklyncentral.github.com"), "contents missing expected phrase; contains:\n"+contents);
+        } finally {
+            dest.delete();
+        }
+    }
+    
+    @Test(groups = "Integration")
+    public void testInstallClasspathCopyTo() throws Exception {
+        File dest = new File(Os.tmp(), "sssMachineLocationTest_dir/");
+        dest.mkdir();
+        try {
+            int result = host.installTo("classpath://brooklyn/config/sample.properties", Urls.mergePaths(dest.getAbsolutePath(), "sample.properties"));
+            assertEquals(result, 0);
+            String contents = ArchiveUtils.readFullyString(new File(dest, "sample.properties"));
+            assertTrue(contents.contains("Property 1"), "contents missing expected phrase; contains:\n"+contents);
+        } finally {
+            dest.delete();
+        }
+    }
+
+    // Note: requires `ssh localhost` to be setup such that no password is required    
+    @Test(groups = "Integration")
+    public void testIsSshableWhenTrue() throws Exception {
+        assertTrue(host.isSshable());
+    }
+    
+    // Note: on some (home/airport) networks, `ssh 123.123.123.123` hangs seemingly forever.
+    // Make sure we fail, waiting for longer than the 70 second TCP timeout.
+    //
+    // Times out in 2m7s on Ubuntu Vivid (syn retries set to 6)
+    @Test(groups = "Integration")
+    public void testIsSshableWhenFalse() throws Exception {
+        byte[] unreachableIp = new byte[] {123,123,123,123};
+        final SshMachineLocation unreachableHost = new SshMachineLocation(MutableMap.of("address", InetAddress.getByAddress("unreachablename", unreachableIp)));
+        Asserts.assertReturnsEventually(new Runnable() {
+            public void run() {
+                assertFalse(unreachableHost.isSshable());
+            }},
+            Duration.minutes(3));
+    }
+    
+    @Test
+    public void obtainSpecificPortGivesOutPortOnlyOnce() {
+        int port = 2345;
+        assertTrue(host.obtainSpecificPort(port));
+        assertFalse(host.obtainSpecificPort(port));
+        host.releasePort(port);
+        assertTrue(host.obtainSpecificPort(port));
+    }
+    
+    @Test
+    public void obtainPortInRangeGivesBackRequiredPortOnlyIfAvailable() {
+        int port = 2345;
+        assertEquals(host.obtainPort(new PortRanges.LinearPortRange(port, port)), port);
+        assertEquals(host.obtainPort(new PortRanges.LinearPortRange(port, port)), -1);
+        host.releasePort(port);
+        assertEquals(host.obtainPort(new PortRanges.LinearPortRange(port, port)), port);
+    }
+    
+    @Test
+    public void obtainPortInWideRange() {
+        int lowerPort = 2345;
+        int upperPort = 2350;
+        PortRange range = new PortRanges.LinearPortRange(lowerPort, upperPort);
+        for (int i = lowerPort; i <= upperPort; i++) {
+            assertEquals(host.obtainPort(range), i);
+        }
+        assertEquals(host.obtainPort(range), -1);
+        
+        host.releasePort(lowerPort);
+        assertEquals(host.obtainPort(range), lowerPort);
+        assertEquals(host.obtainPort(range), -1);
+    }
+    
+    @Test
+    public void testObtainPortDoesNotUsePreReservedPorts() {
+        host = new SshMachineLocation(MutableMap.of("address", Networking.getLocalHost(), "usedPorts", ImmutableSet.of(8000)));
+        assertEquals(host.obtainPort(PortRanges.fromString("8000")), -1);
+        assertEquals(host.obtainPort(PortRanges.fromString("8000+")), 8001);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/basic/TestPortSupplierLocation.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/basic/TestPortSupplierLocation.java b/core/src/test/java/org/apache/brooklyn/location/basic/TestPortSupplierLocation.java
new file mode 100644
index 0000000..a8b9354
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/basic/TestPortSupplierLocation.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.event.feed.ConfigToAttributes;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestPortSupplierLocation extends BrooklynAppUnitTestSupport {
+
+    SimulatedLocation loc;
+    PortAttributeSensorAndConfigKey ps;
+    TestEntity entity;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = app.newSimulatedLocation();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        app.start(ImmutableList.of(loc));
+        
+        ps = new PortAttributeSensorAndConfigKey("some.port", "for testing", "1234+");
+    }
+
+    @Test
+    public void testObtainsPort() throws Exception {
+        ConfigToAttributes.apply(entity, ps);
+        
+        int p = entity.getAttribute(ps);
+        assertEquals(p, 1234);
+        
+        //sensor access should keep the same value
+        p = entity.getAttribute(ps);
+        assertEquals(p, 1234);
+    }
+    
+    @Test
+    public void testRepeatedConvertAccessIncrements() throws Exception {
+        int p = ps.getAsSensorValue(entity);
+        assertEquals(p, 1234);
+
+        //but direct access should see it as being reserved (not required behaviour, but it is the current behaviour)
+        int p2 = ps.getAsSensorValue(entity);
+        assertEquals(p2, 1235);
+    }
+
+    @Test
+    public void testNullBeforeSetting() throws Exception {
+        // currently getting the attribute before explicitly setting return null; i.e. no "auto-set" -- 
+        // but this behaviour may be changed
+        Integer p = entity.getAttribute(ps);
+        assertEquals(p, null);
+    }
+
+    @Test
+    public void testSimulatedRestrictedPermitted() throws Exception {
+        loc.setPermittedPorts(PortRanges.fromString("1240+"));
+        
+        ConfigToAttributes.apply(entity, ps);
+        int p = entity.getAttribute(ps);
+        assertEquals((int)p, 1240);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/cloud/CloudMachineNamerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/cloud/CloudMachineNamerTest.java b/core/src/test/java/org/apache/brooklyn/location/cloud/CloudMachineNamerTest.java
new file mode 100644
index 0000000..9d4acad
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/cloud/CloudMachineNamerTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.cloud;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.location.cloud.names.AbstractCloudMachineNamer;
+import org.apache.brooklyn.location.cloud.names.CloudMachineNamer;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.cloud.names.BasicCloudMachineNamer;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.text.Strings;
+public class CloudMachineNamerTest {
+
+    private static final Logger log = LoggerFactory.getLogger(CloudMachineNamerTest.class);
+    
+    private TestApplication app;
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testGenerateGroupIdWithEntity() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TistApp"), LocalManagementContextForTests.newInstance());
+        TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
+
+        ConfigBag cfg = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+
+        String result = new BasicCloudMachineNamer().generateNewGroupId(cfg);
+
+        log.info("test entity child group id gives: "+result);
+        // e.g. brooklyn-alex-tistapp-uube-testent-xisg-rwad
+        Assert.assertTrue(result.length() <= 60);
+
+        String user = Strings.maxlen(System.getProperty("user.name"), 4).toLowerCase();
+        Assert.assertTrue(result.indexOf(user) >= 0);
+        Assert.assertTrue(result.indexOf("-tistapp-") >= 0);
+        Assert.assertTrue(result.indexOf("-testent-") >= 0);
+        Assert.assertTrue(result.indexOf("-"+Strings.maxlen(app.getId(), 4).toLowerCase()) >= 0);
+        Assert.assertTrue(result.indexOf("-"+Strings.maxlen(child.getId(), 4).toLowerCase()) >= 0);
+    }
+    
+    @Test
+    public void testGenerateNewMachineName() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TistApp"), LocalManagementContextForTests.newInstance());
+        TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
+
+        ConfigBag cfg = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+        BasicCloudMachineNamer namer = new BasicCloudMachineNamer();
+        
+        String result = namer.generateNewMachineUniqueName(cfg);
+        Assert.assertTrue(result.length() <= namer.getMaxNameLength(cfg));
+        String user = Strings.maxlen(System.getProperty("user.name"), 4).toLowerCase();
+        Assert.assertTrue(result.indexOf(user) >= 0);
+        Assert.assertTrue(result.indexOf("-tistapp-") >= 0);
+        Assert.assertTrue(result.indexOf("-testent-") >= 0);
+        Assert.assertTrue(result.indexOf("-"+Strings.maxlen(app.getId(), 4).toLowerCase()) >= 0);
+        Assert.assertTrue(result.indexOf("-"+Strings.maxlen(child.getId(), 4).toLowerCase()) >= 0);
+    }
+    
+    @Test
+    public void testGenerateNewMachineUniqueNameFromGroupId() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TistApp"), LocalManagementContextForTests.newInstance());
+        TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
+
+        ConfigBag cfg = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+        CloudMachineNamer namer = new BasicCloudMachineNamer();
+        
+        String groupId = namer.generateNewGroupId(cfg);
+        String result = namer.generateNewMachineUniqueNameFromGroupId(cfg, groupId);
+        Assert.assertTrue(result.startsWith(groupId));
+        Assert.assertTrue(result.length() == groupId.length() + 5);
+    }
+    
+    @Test
+    public void testLengthMaxPermittedForMachineName() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TistApp"), LocalManagementContextForTests.newInstance());
+        TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
+        
+        ConfigBag cfg = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+        BasicCloudMachineNamer namer = new BasicCloudMachineNamer();
+        namer.setDefaultMachineNameMaxLength(10);
+        String result = namer.generateNewMachineUniqueName(cfg);
+        Assert.assertEquals(result.length(), 10);
+    }
+    
+    @Test
+    public void testLengthReservedForNameInGroup() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TistApp"), LocalManagementContextForTests.newInstance());
+        TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
+        
+        ConfigBag cfg = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+        BasicCloudMachineNamer namer = new BasicCloudMachineNamer();
+        namer.setDefaultMachineNameMaxLength(20);
+        namer.setDefaultMachineNameSeparatorAndSaltLength(":I", 5);
+        String groupId = namer.generateNewGroupId(cfg);
+        Assert.assertEquals(13, groupId.length(), "groupId="+groupId);
+        String machineId = namer.generateNewMachineUniqueNameFromGroupId(cfg, groupId);
+        Assert.assertEquals(20, machineId.length(), "machineId="+machineId);
+        // separator is not sanitized -- caller should know what they are doing there!
+        Assert.assertTrue(machineId.startsWith(groupId+"-i"), "machineId="+machineId);
+    }
+
+    @Test
+    public void testSanitizesNewMachineName() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("T_%$()\r\n\t[]*.!App"), LocalManagementContextForTests.newInstance());
+        TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("ent"));
+
+        ConfigBag cfg = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+        CloudMachineNamer namer = new BasicCloudMachineNamer();
+        
+        String result = namer.generateNewMachineUniqueName(cfg);
+        assertTrue(result.indexOf("t-ap") >= 0, "result="+result);
+        for (int c : "_%$()\r\n\t[]*.!".getBytes()) {
+            assertFalse(result.contains(new String(new char [] {(char)c})), "result="+result);
+        }
+    }
+    
+    @Test
+    public void testSanitize() {
+        Assert.assertEquals(AbstractCloudMachineNamer.sanitize(
+                        "me & you like alphanumeric but not _ or !!! or dots...dots...dots %$()\r\n\t[]*etc"),
+                "me-you-like-alphanumeric-but-not-or-or-dots-dots-dots-etc");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/cloud/CustomMachineNamerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/cloud/CustomMachineNamerTest.java b/core/src/test/java/org/apache/brooklyn/location/cloud/CustomMachineNamerTest.java
new file mode 100644
index 0000000..f0e7d11
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/cloud/CustomMachineNamerTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.cloud;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.Assert;
+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 org.apache.brooklyn.location.cloud.names.CustomMachineNamer;
+import brooklyn.util.config.ConfigBag;
+
+import com.google.common.collect.ImmutableMap;
+
+public class CustomMachineNamerTest {
+    
+    private TestApplication app;
+    private TestEntity child;
+    private ConfigBag config;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TestApp"), LocalManagementContextForTests.newInstance());
+        child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
+        config = new ConfigBag()
+            .configure(CloudLocationConfig.CALLER_CONTEXT, child);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testMachineNameNoConfig() {
+        config.configure(CloudLocationConfig.CALLER_CONTEXT, child);
+        Assert.assertEquals(new CustomMachineNamer().generateNewMachineUniqueName(config), "TestEnt");
+    }
+    
+    @Test
+    public void testMachineNameWithConfig() {
+        child.setSequenceValue(999);
+        config.configure(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "number${entity.sequenceValue}");
+        Assert.assertEquals(new CustomMachineNamer().generateNewMachineUniqueName(config), "number999");
+    }
+    
+    @Test
+    public void testMachineNameWithExtraSubstitutions() {
+        config.configure(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "foo-${fooName}-bar-${barName}-baz-${bazName.substitution}")
+            .configure(CustomMachineNamer.EXTRA_SUBSTITUTIONS, ImmutableMap.of("fooName", "foo", "barName", "bar", "bazName", this));
+        Assert.assertEquals(new CustomMachineNamer().generateNewMachineUniqueName(config), "foo-foo-bar-bar-baz-baz");
+    }
+    
+    public String getSubstitution() {
+        return "baz";
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java
new file mode 100644
index 0000000..f288bee
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.geo;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNotNull;
+
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+import org.testng.annotations.Test;
+
+import brooklyn.util.collections.MutableMap;
+
+public class HostGeoInfoTest {
+    private static final String IP = "192.168.0.1";
+    
+    private static final Location CUSTOM_LOCATION = new SimulatedLocation(MutableMap.of("name", "custom", "latitude", 50d, "longitude", 0d));
+    private static final Location CUSTOM_LOCATION_CHILD = new SimulatedLocation(MutableMap.of("name", "custom-child", "address", IP, "parentLocation", CUSTOM_LOCATION));
+        
+    @Test
+    public void testCustomLocationCoordinates() {
+        HostGeoInfo hgi = HostGeoInfo.fromLocation(CUSTOM_LOCATION);
+        assertNotNull(hgi);
+        assertEquals(50.0d, hgi.latitude);
+        assertEquals(0.0d, hgi.longitude);
+    }
+    
+    @Test
+    public void testCustomLocationChildCoordinates() {
+        HostGeoInfo hgi = HostGeoInfo.fromLocation(CUSTOM_LOCATION_CHILD);
+        assertNotNull(hgi);
+        assertEquals(50.0d, hgi.latitude);
+        assertEquals(0.0d, hgi.longitude);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java
new file mode 100644
index 0000000..129a46d
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.geo;
+
+import java.net.InetAddress;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Objects;
+
+public class HostGeoLookupIntegrationTest {
+
+    public static final Logger log = LoggerFactory.getLogger(HostGeoLookupIntegrationTest.class);
+    
+    // Needs fast network connectivity to figure out the external IP. If response not returned in 2s fails.
+    @Test(groups = "Integration")
+    public void testLocalhostGetsLocation() throws Exception {
+        LocalhostMachineProvisioningLocation ll = new LocalhostMachineProvisioningLocation();
+        SshMachineLocation l = ll.obtain();
+        HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+        Assert.assertNotNull(geo, "host lookup unavailable - is the maxmind database installed? or else network unavailable or too slow?");
+        log.info("localhost is in "+geo);
+        Assert.assertNotNull(geo, "couldn't load data; must have a valid HostGeoLookup impl (e.g. MaxMind installed, or online and with Utrace credit)");
+        Assert.assertTrue(-90 <= geo.latitude && geo.latitude <= 90); 
+        ll.close();
+    }
+
+    @Deprecated // see GeoBytesHostGeoLookup - their API changed
+    @Test(groups = "Integration", enabled=false)
+    public void testGeobytesLookup() throws Exception {
+        HostGeoInfo geo = new GeoBytesHostGeoLookup().getHostGeoInfo(InetAddress.getByName("geobytes.com"));
+        Assert.assertNotNull(geo, "host lookup unavailable");
+        Assert.assertEquals(geo.displayName, "Baltimore (US)");
+        Assert.assertEquals(geo.latitude, 39.2894, 0.1);
+        Assert.assertEquals(geo.longitude, -76.6384, 0.1);
+    }
+
+    @Test(groups = "Integration")
+    public void testUtraceLookup() throws Exception {
+        // The test times out in a VM - VirtualBox + Ubuntu Vivid, possibly due to proxy usage?
+        // Increase the timeout so we can actually test it's working correctly, regardless of test environment.
+        HostGeoInfo geo = new UtraceHostGeoLookup().getHostGeoInfo(InetAddress.getByName("utrace.de"), Duration.THIRTY_SECONDS);
+        Assert.assertNotNull(geo, "host lookup unavailable - maybe network not available ");
+        Assert.assertTrue(geo.displayName.contains("(DE)"));
+        Assert.assertEquals(geo.latitude, 51, 2);
+        Assert.assertEquals(geo.longitude, 9, 5);
+    }
+
+    @Test(groups = "Integration")  // only works if maxmind database is installed to ~/.brooklyn/
+    public void testMaxmindLookup() throws Exception {
+        HostGeoInfo geo = new MaxMind2HostGeoLookup().getHostGeoInfo(InetAddress.getByName("maxmind.com"));
+        Assert.assertNotNull(geo, "host lookup unavailable - is the maxmind database installed?");
+        log.info("maxmind.com at "+geo);
+        
+        // used to be Washington; now Dallas - in case this changes again, we will accept either!
+        // also have seen variation in lat/lon reported, so happy to within one degree now
+        Assert.assertTrue(Objects.equal(geo.displayName, "Washington, DC (US)") || Objects.equal(geo.displayName, "Dallas, TX (US)"), "name="+geo.displayName);
+        Assert.assertTrue(Math.abs(geo.latitude - 38.90) <= 1 || Math.abs(geo.latitude - 32.78) <= 1, "lat="+geo.latitude);
+        Assert.assertTrue(Math.abs(geo.longitude - -77.02) <= 1 || Math.abs(geo.longitude - -96.82) <= 1, "lon="+geo.longitude);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/location/geo/LocalhostExternalIpLoaderIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/geo/LocalhostExternalIpLoaderIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/geo/LocalhostExternalIpLoaderIntegrationTest.java
new file mode 100644
index 0000000..e686985
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/geo/LocalhostExternalIpLoaderIntegrationTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.geo;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Sets;
+
+public class LocalhostExternalIpLoaderIntegrationTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LocalhostExternalIpLoaderIntegrationTest.class);
+
+    @Test(groups = "Integration")
+    public void testHostsAgreeOnExternalIp() {
+        Set<String> ips = Sets.newHashSet();
+        for (String url : LocalhostExternalIpLoader.getIpAddressWebsites()) {
+            String ip = LocalhostExternalIpLoader.getIpAddressFrom(url);
+            LOG.debug("IP from {}: {}", url, ip);
+            ips.add(ip);
+        }
+        assertEquals(ips.size(), 1, "Expected all IP suppliers to agree on the external IP address of Brooklyn. " +
+                "Check logs for source responses. ips=" + ips);
+    }
+
+    @Test(groups = "Integration")
+    public void testLoadExternalIp() {
+        assertNotNull(LocalhostExternalIpLoader.getLocalhostIpWaiting());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/test/entity/BlockingEntityImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/test/entity/BlockingEntityImpl.java b/core/src/test/java/org/apache/brooklyn/test/entity/BlockingEntityImpl.java
index cd94a22..8dbb21d 100644
--- a/core/src/test/java/org/apache/brooklyn/test/entity/BlockingEntityImpl.java
+++ b/core/src/test/java/org/apache/brooklyn/test/entity/BlockingEntityImpl.java
@@ -21,7 +21,7 @@ package org.apache.brooklyn.test.entity;
 import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 
-import brooklyn.location.Location;
+import org.apache.brooklyn.location.Location;
 
 import com.google.common.base.Throwables;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/test/entity/TestApplication.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/test/entity/TestApplication.java b/core/src/test/java/org/apache/brooklyn/test/entity/TestApplication.java
index 545094c..4a4cb94 100644
--- a/core/src/test/java/org/apache/brooklyn/test/entity/TestApplication.java
+++ b/core/src/test/java/org/apache/brooklyn/test/entity/TestApplication.java
@@ -30,8 +30,8 @@ import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.StartableApplication;
 import brooklyn.event.basic.Sensors;
-import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
-import brooklyn.location.basic.SimulatedLocation;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
 
 /**
  * Mock application for testing.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/test/entity/TestApplicationImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/test/entity/TestApplicationImpl.java b/core/src/test/java/org/apache/brooklyn/test/entity/TestApplicationImpl.java
index 70904e6..33c52ed 100644
--- a/core/src/test/java/org/apache/brooklyn/test/entity/TestApplicationImpl.java
+++ b/core/src/test/java/org/apache/brooklyn/test/entity/TestApplicationImpl.java
@@ -30,9 +30,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.basic.AbstractApplication;
-import brooklyn.location.LocationSpec;
-import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
-import brooklyn.location.basic.SimulatedLocation;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
 import brooklyn.util.logging.LoggingSetup;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/java/org/apache/brooklyn/test/entity/TestEntityImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/test/entity/TestEntityImpl.java b/core/src/test/java/org/apache/brooklyn/test/entity/TestEntityImpl.java
index 0cb2dd5..c444b51 100644
--- a/core/src/test/java/org/apache/brooklyn/test/entity/TestEntityImpl.java
+++ b/core/src/test/java/org/apache/brooklyn/test/entity/TestEntityImpl.java
@@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.location.Location;
+import org.apache.brooklyn.location.Location;
 import brooklyn.util.collections.MutableMap;
 
 import com.google.common.collect.Lists;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/resources/brooklyn/location/basic/sample_id_rsa
----------------------------------------------------------------------
diff --git a/core/src/test/resources/brooklyn/location/basic/sample_id_rsa b/core/src/test/resources/brooklyn/location/basic/sample_id_rsa
deleted file mode 100644
index bfeef4a..0000000
--- a/core/src/test/resources/brooklyn/location/basic/sample_id_rsa
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEA0rPOuv1rVXhrSaZSnA2fShSFp8/3knG/Uuss8ZojUW4k+ETj
-dVuYzoH/tvAaYqTUuxqB5OpNgOgUpS02hqOWtN/3iYcfJtBHe4eu/dMSfcQXKasU
-ZalQtv2zF955EMH7jZ0q4TPikOS/tR/gPBDe6xHGjwPpB7Jd2daU0VgJD7M9SOIL
-bRDbilw1XpgyOs4C6I3ftlcH1nA9tQKcdFQ0wmWAXr0ulizYMoAy3VOm34XAwT/o
-w1sgpGK7YwPLbBv1QI4SKfvHi9OhcNBBOoXTE3G3vg6EiKGaam3Kp17EO60iq4CE
-Ec+3FBk5JQ0pAX2SsfxqASHY7QcpbQphF+jlRwIDAQABAoIBAQCqjnZXku+hfhqK
-waG5RKWeV8JhNs0WtBDFVC1LXRQdxGUUut7MjtrAvyZ5tR4Gn5q74hcncCpQoIyl
-sFWk4yMJQwqjPseOqaZTbl/Og19CgsqlJiEasdXuaqrgNWwWjo/L8F9XcKKD20b7
-nNPsi1OHQRpThjzJyC6EOVi5pOOg2mL+cWBJEJy6AIOWVkidw/x+IX6P/6eppc+C
-JM9AVRvSkCYBmTXTCM4OZq6WV53V/m/SaAlG/LcC4ykZBDdhq8T3lNp/sACvWVzL
-JKVcQuBAv7ABBbMcyTgIFpKrx7siUTA+7QGkdG1IbR55S57iSgGGytUV4qv02pBr
-bCJUokZRAoGBAPx/XZ0ig9lYVF7Z4ayASS7j5na4A/MZyHeBZ8hPynIHpjbGvRrG
-+r61XEcSsS4vj6ptCBhuxzYRq6t1yX/6wecFmWdSxcjoS4grMKdGPoWSoOBnV85I
-4/Ctd+NiFtXDqd1abGH3NKtkSwwKr/FX3IK2ePiGpoBNYWbGFMRO2LJrAoGBANWg
-BlLjnZwZ71+8cKb3ozCaLmE8fMG/vw6idTEPRk6iyvto9rpjnpz7Sd12z/d35U32
-6QM+3MsGZ3B1NTJoba7l7cuVtwfIHBc0Q3VQSHBp5PDuM5bNhDb38jt0/Dtv/bMD
-NMVletmrc2Hfx8uqUdr21LKrWO42AwYsLFsMAGeVAoGAObXWrKqN3ihdKEy+UtID
-aA84xpuqc27KLd5K3TK3f7aV2+EyqaMe/mWvUKNKEddXC8nd1s/DAm2pggfq5TBo
-Dyhtdnspr5DAasAMX78jXR41XPTh0clBJ+pOA4+QzozpDymyqfV5eU70BC2RJyVA
-xjN0lMEZ3ytQfs/5QSEQUD8CgYBwipOKS3uW51riVsYKUF/alP9mHpWjBL9EmHWg
-2OkzODQzasLAwwamsQPi9lrthm55OmDbYtyy4LbR2g2idr2B7IPwQvlf0h5qYxA+
-14KyJjeEbhkjkzXaN5mXlTPkpEVFb6T3cVTdI6PvphL9ysbA0lSPpBF/vViugcsE
-VDhKWQKBgQCwaNQ07sHKRqc22SFNgcOv0KOMx7CbaSRxGoEwtpvvdAPlUXuIYMs+
-pueK5scHKJvtcHZQb82j6o84OC37ut+8tUe2XkasyAbONZqqm+Oes1xseKrNcF8J
-+thFmGvZc8O3xdflT87OXDLGzufGJXUDDvZctJwrC5AYPClwj6wd0A==
------END RSA PRIVATE KEY-----

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/resources/brooklyn/location/basic/sample_id_rsa.pub
----------------------------------------------------------------------
diff --git a/core/src/test/resources/brooklyn/location/basic/sample_id_rsa.pub b/core/src/test/resources/brooklyn/location/basic/sample_id_rsa.pub
deleted file mode 100644
index f137be8..0000000
--- a/core/src/test/resources/brooklyn/location/basic/sample_id_rsa.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSs866/WtVeGtJplKcDZ9KFIWnz/eScb9S6yzxmiNRbiT4RON1W5jOgf+28BpipNS7GoHk6k2A6BSlLTaGo5a03/eJhx8m0Ed7h6790xJ9xBcpqxRlqVC2/bMX3nkQwfuNnSrhM+KQ5L+1H+A8EN7rEcaPA+kHsl3Z1pTRWAkPsz1I4gttENuKXDVemDI6zgLojd+2VwfWcD21Apx0VDTCZYBevS6WLNgygDLdU6bfhcDBP+jDWyCkYrtjA8tsG/VAjhIp+8eL06Fw0EE6hdMTcbe+DoSIoZpqbcqnXsQ7rSKrgIQRz7cUGTklDSkBfZKx/GoBIdjtByltCmEX6OVH alex@almacretin.local

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa b/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa
new file mode 100644
index 0000000..bfeef4a
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0rPOuv1rVXhrSaZSnA2fShSFp8/3knG/Uuss8ZojUW4k+ETj
+dVuYzoH/tvAaYqTUuxqB5OpNgOgUpS02hqOWtN/3iYcfJtBHe4eu/dMSfcQXKasU
+ZalQtv2zF955EMH7jZ0q4TPikOS/tR/gPBDe6xHGjwPpB7Jd2daU0VgJD7M9SOIL
+bRDbilw1XpgyOs4C6I3ftlcH1nA9tQKcdFQ0wmWAXr0ulizYMoAy3VOm34XAwT/o
+w1sgpGK7YwPLbBv1QI4SKfvHi9OhcNBBOoXTE3G3vg6EiKGaam3Kp17EO60iq4CE
+Ec+3FBk5JQ0pAX2SsfxqASHY7QcpbQphF+jlRwIDAQABAoIBAQCqjnZXku+hfhqK
+waG5RKWeV8JhNs0WtBDFVC1LXRQdxGUUut7MjtrAvyZ5tR4Gn5q74hcncCpQoIyl
+sFWk4yMJQwqjPseOqaZTbl/Og19CgsqlJiEasdXuaqrgNWwWjo/L8F9XcKKD20b7
+nNPsi1OHQRpThjzJyC6EOVi5pOOg2mL+cWBJEJy6AIOWVkidw/x+IX6P/6eppc+C
+JM9AVRvSkCYBmTXTCM4OZq6WV53V/m/SaAlG/LcC4ykZBDdhq8T3lNp/sACvWVzL
+JKVcQuBAv7ABBbMcyTgIFpKrx7siUTA+7QGkdG1IbR55S57iSgGGytUV4qv02pBr
+bCJUokZRAoGBAPx/XZ0ig9lYVF7Z4ayASS7j5na4A/MZyHeBZ8hPynIHpjbGvRrG
++r61XEcSsS4vj6ptCBhuxzYRq6t1yX/6wecFmWdSxcjoS4grMKdGPoWSoOBnV85I
+4/Ctd+NiFtXDqd1abGH3NKtkSwwKr/FX3IK2ePiGpoBNYWbGFMRO2LJrAoGBANWg
+BlLjnZwZ71+8cKb3ozCaLmE8fMG/vw6idTEPRk6iyvto9rpjnpz7Sd12z/d35U32
+6QM+3MsGZ3B1NTJoba7l7cuVtwfIHBc0Q3VQSHBp5PDuM5bNhDb38jt0/Dtv/bMD
+NMVletmrc2Hfx8uqUdr21LKrWO42AwYsLFsMAGeVAoGAObXWrKqN3ihdKEy+UtID
+aA84xpuqc27KLd5K3TK3f7aV2+EyqaMe/mWvUKNKEddXC8nd1s/DAm2pggfq5TBo
+Dyhtdnspr5DAasAMX78jXR41XPTh0clBJ+pOA4+QzozpDymyqfV5eU70BC2RJyVA
+xjN0lMEZ3ytQfs/5QSEQUD8CgYBwipOKS3uW51riVsYKUF/alP9mHpWjBL9EmHWg
+2OkzODQzasLAwwamsQPi9lrthm55OmDbYtyy4LbR2g2idr2B7IPwQvlf0h5qYxA+
+14KyJjeEbhkjkzXaN5mXlTPkpEVFb6T3cVTdI6PvphL9ysbA0lSPpBF/vViugcsE
+VDhKWQKBgQCwaNQ07sHKRqc22SFNgcOv0KOMx7CbaSRxGoEwtpvvdAPlUXuIYMs+
+pueK5scHKJvtcHZQb82j6o84OC37ut+8tUe2XkasyAbONZqqm+Oes1xseKrNcF8J
++thFmGvZc8O3xdflT87OXDLGzufGJXUDDvZctJwrC5AYPClwj6wd0A==
+-----END RSA PRIVATE KEY-----

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa.pub
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa.pub b/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa.pub
new file mode 100644
index 0000000..f137be8
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/location/basic/sample_id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSs866/WtVeGtJplKcDZ9KFIWnz/eScb9S6yzxmiNRbiT4RON1W5jOgf+28BpipNS7GoHk6k2A6BSlLTaGo5a03/eJhx8m0Ed7h6790xJ9xBcpqxRlqVC2/bMX3nkQwfuNnSrhM+KQ5L+1H+A8EN7rEcaPA+kHsl3Z1pTRWAkPsz1I4gttENuKXDVemDI6zgLojd+2VwfWcD21Apx0VDTCZYBevS6WLNgygDLdU6bfhcDBP+jDWyCkYrtjA8tsG/VAjhIp+8eL06Fw0EE6hdMTcbe+DoSIoZpqbcqnXsQ7rSKrgIQRz7cUGTklDSkBfZKx/GoBIdjtByltCmEX6OVH alex@almacretin.local

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/_extra/big_examples/global-web-fabric/index.md
----------------------------------------------------------------------
diff --git a/docs/_extra/big_examples/global-web-fabric/index.md b/docs/_extra/big_examples/global-web-fabric/index.md
index cd9a29b..d163611 100644
--- a/docs/_extra/big_examples/global-web-fabric/index.md
+++ b/docs/_extra/big_examples/global-web-fabric/index.md
@@ -73,7 +73,7 @@ unpack it, and copy it to `~/.brooklyn/GeoLite2-City.mmdb`.
 This will be picked up automatically if it is installed.
 You can instead specify to use an online lookup service, such as 
 [utrace.de](http://www.utrace.de) by specifying
-`-Dbrooklyn.location.geo.HostGeoLookup=brooklyn.location.geo.UtraceHostGeoLookup`;
+`-Dbrooklyn.location.geo.HostGeoLookup=UtraceHostGeoLookup`;
 but note this has a cap of 100 per day.
 
 This information is also used to display locations on the map
@@ -210,7 +210,7 @@ import brooklyn.entity.group.DynamicFabric;
 import brooklyn.entity.proxy.AbstractController;
 import brooklyn.entity.proxying.EntitySpec;
 import org.apache.brooklyn.entity.webapp.ElasticJavaWebAppService;
-import brooklyn.location.basic.PortRanges;
+import PortRanges;
 {% endhighlight %}
 
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/guide/ops/locations/index.md
----------------------------------------------------------------------
diff --git a/docs/guide/ops/locations/index.md b/docs/guide/ops/locations/index.md
index 767ca72..c9b41db 100644
--- a/docs/guide/ops/locations/index.md
+++ b/docs/guide/ops/locations/index.md
@@ -133,7 +133,7 @@ For more keys and more detail on the keys below, see
   For more sophisticated control over host naming, you can supply a custom 
   {% include java_link.html class_name="CloudMachineNamer" package_path="brooklyn/location/cloud/names" project_subpath="core" %},
   for example
-  `cloudMachineNamer: brooklyn.location.cloud.names.CustomMachineNamer`.
+  `cloudMachineNamer: CustomMachineNamer`.
   {% include java_link.html class_name="CustomMachineNamer" package_path="brooklyn/location/cloud/names" project_subpath="core" %}
   will use the entity's name or following a template you supply.
   On many clouds, a random suffix will be appended to help guarantee uniqueness;
@@ -425,7 +425,7 @@ The BYON location also supports a machine chooser, using the config key `byon.ma
 This allows one to plugin logic to choose from the set of available machines in the pool. For
 example, additional config could be supplied for each machine. This could be used (during the call
 to `location.obtain()`) to find the config that matches the requirements of the entity being
-provisioned. See `brooklyn.location.basic.FixedListMachineProvisioningLocation.MACHINE_CHOOSER`.
+provisioned. See `FixedListMachineProvisioningLocation.MACHINE_CHOOSER`.
 
 
 ### Other Location Topics

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.AddressableLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.AddressableLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.AddressableLocation.html
index 857a635..bb484c2 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.AddressableLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.AddressableLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.AddressableLocation</title>
+    <title>Brooklyn Location - AddressableLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.AddressableLocation</h1>
+        <h1>AddressableLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.AddressableLocation";
+            return entity.type == "AddressableLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.MachineLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.MachineLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.MachineLocation.html
index dbd9455..edb16fd 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.MachineLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.MachineLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.MachineLocation</title>
+    <title>Brooklyn Location - MachineLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.MachineLocation</h1>
+        <h1>MachineLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.MachineLocation";
+            return entity.type == "MachineLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation.html
index fdf531b..09ffd3f 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation</title>
+    <title>Brooklyn Location - MachineManagementMixins$RichMachineProvisioningLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation</h1>
+        <h1>MachineManagementMixins$RichMachineProvisioningLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.MachineManagementMixins$RichMachineProvisioningLocation";
+            return entity.type == "MachineManagementMixins$RichMachineProvisioningLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.MachineProvisioningLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.MachineProvisioningLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.MachineProvisioningLocation.html
index d63dc8b..f0a8282 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.MachineProvisioningLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.MachineProvisioningLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.MachineProvisioningLocation</title>
+    <title>Brooklyn Location - MachineProvisioningLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.MachineProvisioningLocation</h1>
+        <h1>MachineProvisioningLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.MachineProvisioningLocation";
+            return entity.type == "MachineProvisioningLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AbstractLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AbstractLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AbstractLocation.html
index 6861236..05ad96f 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AbstractLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AbstractLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.basic.AbstractLocation</title>
+    <title>Brooklyn Location - AbstractLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.basic.AbstractLocation</h1>
+        <h1>AbstractLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.basic.AbstractLocation";
+            return entity.type == "AbstractLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AggregatingMachineProvisioningLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AggregatingMachineProvisioningLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AggregatingMachineProvisioningLocation.html
index 790d261..75c4101 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AggregatingMachineProvisioningLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.AggregatingMachineProvisioningLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.basic.AggregatingMachineProvisioningLocation</title>
+    <title>Brooklyn Location - AggregatingMachineProvisioningLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.basic.AggregatingMachineProvisioningLocation</h1>
+        <h1>AggregatingMachineProvisioningLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.basic.AggregatingMachineProvisioningLocation";
+            return entity.type == "AggregatingMachineProvisioningLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.basic.FixedListMachineProvisioningLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.FixedListMachineProvisioningLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.FixedListMachineProvisioningLocation.html
index 989c8ee..bfedb11 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.FixedListMachineProvisioningLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.FixedListMachineProvisioningLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.basic.FixedListMachineProvisioningLocation</title>
+    <title>Brooklyn Location - FixedListMachineProvisioningLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.basic.FixedListMachineProvisioningLocation</h1>
+        <h1>FixedListMachineProvisioningLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.basic.FixedListMachineProvisioningLocation";
+            return entity.type == "FixedListMachineProvisioningLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine.html b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine.html
index 2a26d53..b39bc4c 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine</title>
+    <title>Brooklyn Location - LocalhostMachineProvisioningLocation$LocalhostMachine</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine</h1>
+        <h1>LocalhostMachineProvisioningLocation$LocalhostMachine</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.basic.LocalhostMachineProvisioningLocation$LocalhostMachine";
+            return entity.type == "LocalhostMachineProvisioningLocation$LocalhostMachine";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation.html b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation.html
index 47af495..97f25e8 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.location.basic.LocalhostMachineProvisioningLocation.html
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-    <title>Brooklyn Location - brooklyn.location.basic.LocalhostMachineProvisioningLocation</title>
+    <title>Brooklyn Location - LocalhostMachineProvisioningLocation</title>
     <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
     <link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
 </head>
@@ -30,7 +30,7 @@ under the License.
         </div>
     </div>
     <div id="content" class="objectContent">
-        <h1>brooklyn.location.basic.LocalhostMachineProvisioningLocation</h1>
+        <h1>LocalhostMachineProvisioningLocation</h1>
         <h2 class="typeLabel">Type:</h2><span id="type"></span>
 
         <h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
 <script type="text/javascript">
     $(document).ready(function () {
         var item = $.grep((items.locations), function (entity) {
-            return entity.type == "brooklyn.location.basic.LocalhostMachineProvisioningLocation";
+            return entity.type == "LocalhostMachineProvisioningLocation";
         })[0];
         $("#type").html(item.type);
         item.config.forEach(function (element) {