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

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

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationLiveTest.java
new file mode 100644
index 0000000..535b6a8
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationLiveTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.jclouds;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+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 org.apache.brooklyn.location.OsDetails;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.util.config.ConfigBag;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class RebindJcloudsLocationLiveTest extends AbstractJcloudsLiveTest {
+
+    public static final String AWS_EC2_REGION_NAME = AWS_EC2_USEAST_REGION_NAME;
+    public static final String AWS_EC2_LOCATION_SPEC = "jclouds:" + AWS_EC2_PROVIDER + ":" + AWS_EC2_REGION_NAME;
+
+    private ClassLoader classLoader = getClass().getClassLoader();
+    private TestApplication origApp;
+    private LiveTestEntity origEntity;
+    private File mementoDir;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        origApp = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class), managementContext);
+        origEntity = origApp.createAndManageChild(EntitySpec.create(LiveTestEntity.class));
+
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        jcloudsLocation.setConfig(JcloudsLocation.HARDWARE_ID, AWS_EC2_SMALL_HARDWARE_ID);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (origApp != null) Entities.destroyAll(origApp.getManagementContext());
+        if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+    }
+    
+    @Override
+    protected LocalManagementContext newManagementContext() {
+        mementoDir = Files.createTempDir();
+        return RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+    }
+    
+    @Test(groups="Live")
+    public void testRebindsToJcloudsMachine() throws Exception {
+        origApp.start(ImmutableList.of(jcloudsLocation));
+        JcloudsLocation origJcloudsLocation = jcloudsLocation;
+        System.out.println("orig locations: " + origEntity.getLocations());
+        JcloudsSshMachineLocation origMachine = (JcloudsSshMachineLocation) Iterables.find(origEntity.getLocations(), Predicates.instanceOf(JcloudsSshMachineLocation.class));
+
+        TestApplication newApp = rebind();
+        LiveTestEntity newEntity = (LiveTestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(LiveTestEntity.class));
+        JcloudsSshMachineLocation newMachine = (JcloudsSshMachineLocation) Iterables.find(newEntity.getLocations(), Predicates.instanceOf(JcloudsSshMachineLocation.class));
+
+        assertMachineEquals(newMachine, origMachine);
+        assertTrue(newMachine.isSshable());
+
+        JcloudsLocation newJcloudsLoction = newMachine.getParent();
+        assertJcloudsLocationEquals(newJcloudsLoction, origJcloudsLocation);
+    }
+
+    private void assertMachineEquals(JcloudsSshMachineLocation actual, JcloudsSshMachineLocation expected) {
+        String errmsg = "actual="+actual.toVerboseString()+"; expected="+expected.toVerboseString();
+        assertEquals(actual.getId(), expected.getId(), errmsg);
+        assertEquals(actual.getJcloudsId(), expected.getJcloudsId(), errmsg);
+        assertOsDetailEquals(actual.getOsDetails(), expected.getOsDetails());
+        assertEquals(actual.getSshHostAndPort(), expected.getSshHostAndPort());
+        assertEquals(actual.getPrivateAddress(), expected.getPrivateAddress());
+        assertConfigBagEquals(actual.config().getBag(), expected.config().getBag(), errmsg);
+    }
+
+    private void assertOsDetailEquals(OsDetails actual, OsDetails expected) {
+        String errmsg = "actual="+actual+"; expected="+expected;
+        if (actual == null) assertNull(expected, errmsg);
+        assertEquals(actual.isWindows(), expected.isWindows());
+        assertEquals(actual.isLinux(), expected.isLinux());
+        assertEquals(actual.isMac(), expected.isMac());
+        assertEquals(actual.getName(), expected.getName());
+        assertEquals(actual.getArch(), expected.getArch());
+        assertEquals(actual.getVersion(), expected.getVersion());
+        assertEquals(actual.is64bit(), expected.is64bit());
+    }
+
+    private void assertJcloudsLocationEquals(JcloudsLocation actual, JcloudsLocation expected) {
+        String errmsg = "actual="+actual.toVerboseString()+"; expected="+expected.toVerboseString();
+        assertEquals(actual.getId(), expected.getId(), errmsg);
+        assertEquals(actual.getProvider(), expected.getProvider(), errmsg);
+        assertEquals(actual.getRegion(), expected.getRegion(), errmsg);
+        assertEquals(actual.getIdentity(), expected.getIdentity(), errmsg);
+        assertEquals(actual.getCredential(), expected.getCredential(), errmsg);
+        assertEquals(actual.getHostGeoInfo(), expected.getHostGeoInfo(), errmsg);
+        assertConfigBagEquals(actual.config().getBag(), expected.config().getBag(), errmsg);
+    }
+
+    private void assertConfigBagEquals(ConfigBag actual, ConfigBag expected, String errmsg) {
+        // TODO revisit the strong assertion that configBags are equal
+        
+//        // TODO Can we include all of these things (e.g. when locations are entities, so flagged fields not treated special)?
+//        List<String> configToIgnore = ImmutableList.of("id", "template", "usedPorts", "machineCreationSemaphore", "config");
+//        MutableMap<Object, Object> actualMap = MutableMap.builder().putAll(actual.getAllConfig())
+//                .removeAll(configToIgnore)
+//                .build();
+//        MutableMap<Object, Object> expectedMap = MutableMap.builder().putAll(expected.getAllConfig())
+//                .removeAll(configToIgnore)
+//                .build();
+//        
+//        assertEquals(actualMap, expectedMap, errmsg+"; actualBag="+actualMap+"; expectedBag="+expectedMap);
+    }
+    
+    private TestApplication rebind() throws Exception {
+        RebindTestUtils.waitForPersisted(origApp);
+        return (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationTest.java
new file mode 100644
index 0000000..cc52ef2
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/RebindJcloudsLocationTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.jclouds;
+
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNull;
+
+import org.jclouds.domain.LoginCredentials;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.rebind.RebindTestFixtureWithApp;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.config.ConfigBag;
+
+import com.google.common.net.HostAndPort;
+
+public class RebindJcloudsLocationTest extends RebindTestFixtureWithApp {
+
+    public static final String LOC_SPEC = "jclouds:aws-ec2:us-east-1";
+
+    private JcloudsLocation origLoc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        origLoc = (JcloudsLocation) origManagementContext.getLocationRegistry().resolve(LOC_SPEC);
+    }
+
+    // Previously, the rebound config contained "id" which was then passed to createTemporarySshMachineLocation, causing
+    // that to fail (because the LocationSpec should not have had "id" in its config)
+    @Test
+    public void testReboundConfigDoesNotContainId() throws Exception {
+        rebind();
+        
+        JcloudsLocation newLoc = (JcloudsLocation) newManagementContext.getLocationManager().getLocation(origLoc.getId());
+        
+        ConfigBag newLocConfig = newLoc.config().getBag();
+        ConfigBag config = ConfigBag.newInstanceCopying(newLocConfig);
+        
+        assertNull(newLocConfig.getStringKey(("id")));
+        
+        SshMachineLocation tempMachine = newLoc.createTemporarySshMachineLocation(
+                HostAndPort.fromParts("localhost", 1234), 
+                LoginCredentials.builder().identity("myuser").password("mypass").noPrivateKey().build(), 
+                config);
+        assertNotEquals(tempMachine.getId(), newLoc.getId());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
new file mode 100644
index 0000000..3e33ce8
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.jclouds;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+public class SimpleJcloudsLocationUserLoginAndConfigLiveTest extends AbstractJcloudsLiveTest {
+
+    // FIXME And tidy up this one
+    
+    private static final String LOCATION_SPEC = AWS_EC2_PROVIDER + ":" + AWS_EC2_USEAST_REGION_NAME;
+    
+    private static final Logger log = LoggerFactory.getLogger(SimpleJcloudsLocationUserLoginAndConfigLiveTest.class);
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        jcloudsLocation = resolve(LOCATION_SPEC);
+    }
+    
+    @SuppressWarnings("rawtypes")
+    @Test(groups="Live")
+    public void testJcloudsCreateBogStandard() throws Exception {
+        log.info("TEST testJcloudsCreateBogStandard");
+        JcloudsSshMachineLocation m1 = obtainMachine(ImmutableMap.of());
+
+        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
+        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
+        String result;
+        // echo conflates spaces of arguments
+        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m1"));
+        
+        log.info("now trying rebind "+m1);
+        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
+        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m2"));
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test(groups="Live")
+    public void testJcloudsCreateBogStandardWithUserBrooklyn() throws Exception {
+        log.info("TEST testJcloudsCreateBogStandardWithUserBrooklyn");
+        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of("user", "brooklyn"));
+
+        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
+        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
+        String result;
+        // echo conflates spaces of arguments
+        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m1"));
+        
+        log.info("now trying rebind "+m1);
+        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
+        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m2"));
+        
+        Assert.assertEquals(m2.getUser(), "brooklyn");
+    }
+    
+    @SuppressWarnings("rawtypes")
+    @Test(groups="Live")
+    public void testJcloudsCreateUserMetadata() throws Exception {
+        log.info("TEST testJcloudsCreateBogStandard");
+        String key = "brooklyn-test-user-data";
+        String value = "test-"+Identifiers.makeRandomId(4);
+        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of("userMetadata", key+"="+value));
+
+        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser(),
+                "userMetadata", key+"="+value);
+        Assert.assertEquals(m1.node.getUserMetadata().get(key), value);
+        
+        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
+        String result;
+        // echo conflates spaces of arguments
+        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m1"));
+        
+        log.info("now trying rebind "+m1);
+        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
+        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m2"));
+        Assert.assertEquals(m2.node.getUserMetadata().get(key), value);
+    }
+
+    // a curious image, centos, but user is ec2-user, and handily not correctly auto-detected
+    // test we can specify a loginUser different from user, and that user is created etc...
+    // imageId=us-east-1/ami-f95cf390
+    public static final String EC2_CENTOS_IMAGE = "us-east-1/ami-f95cf390";
+    
+    @Test(groups="Live")
+    public void testJcloudsMissingUser() throws Exception {
+        log.info("TEST testJcloudsMissingUser");
+        try {
+            // wait up to 30s for login (override default of 5m so test runs faster)
+            obtainMachine(MutableMap.of("imageId", EC2_CENTOS_IMAGE, "waitForSshable", 30*1000));
+            log.info("whoops we logged in");
+        } catch (NoMachinesAvailableException e) {
+            log.info("got error as expected, for missing user: "+e); // success
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test(groups="Live")
+    public void testJcloudsWithSpecificLoginUserAndSameUser() throws Exception {
+        log.info("TEST testJcloudsWithSpecificLoginUserAndSameUser");
+        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of(
+                "imageId", EC2_CENTOS_IMAGE,
+                "loginUser", "ec2-user",
+                "user", "ec2-user",
+                "waitForSshable", 30*1000));
+
+        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
+        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
+        String result;
+        // echo conflates spaces of arguments
+        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m1"));
+        
+        log.info("now trying rebind "+m1);
+        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
+        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m2"));
+        
+        Assert.assertEquals(m2.getUser(), "ec2-user");
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test(groups="Live")
+    public void testJcloudsWithSpecificLoginUserAndNewUser() throws Exception {
+        log.info("TEST testJcloudsWithSpecificLoginUserAndNewUser");
+        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of(
+                "imageId", EC2_CENTOS_IMAGE,
+                "loginUser", "ec2-user",
+                "user", "newbob",
+                "waitForSshable", 30*1000));
+
+        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
+        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
+        String result;
+        // echo conflates spaces of arguments
+        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m1"));
+        
+        log.info("now trying rebind "+m1);
+        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
+        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m2"));
+        
+        Assert.assertEquals(m2.getUser(), "newbob");
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test(groups="Live")
+    public void testJcloudsWithSpecificLoginUserAndDefaultUser() throws Exception {
+        log.info("TEST testJcloudsWithSpecificLoginUserAndDefaultUser");
+        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of(
+                "imageId", EC2_CENTOS_IMAGE,
+                "loginUser", "ec2-user",
+                "waitForSshable", 30*1000));
+
+        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
+        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
+        String result;
+        // echo conflates spaces of arguments
+        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m1"));
+        
+        log.info("now trying rebind "+m1);
+        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
+        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
+        Assert.assertTrue(result.contains("trying m2"));
+    }
+
+    @Test(groups="Live")
+    public void testJcloudsCreateWithNoSudoGranted() throws Exception {
+        log.info("TEST testJcloudsCreateWithNoSudoGranted");
+        JcloudsSshMachineLocation m = obtainMachine(MutableMap.of(
+                "grantUserSudo", false,
+                "waitForSshable", 30*1000));
+
+        int exitCode = execWithExitCode(m, ImmutableList.of(BashCommands.sudo("echo yes")));
+        Assert.assertFalse(exitCode == 0, "exit code for sudo command should not have been 0");
+    }
+
+    private String execWithOutput(SshMachineLocation m, List<String> commands) {
+        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+        ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+        exec(m, commands, stdout, stderr);
+        return new String(stdout.toByteArray());
+    }
+
+    private int execWithExitCode(SshMachineLocation m, List<String> commands) {
+        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+        ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+        return exec(m, commands, stdout, stderr);
+    }
+
+    private int exec(SshMachineLocation m, List<String> commands, ByteArrayOutputStream stdout, ByteArrayOutputStream stderr) {
+        Map<String, Object> flags = Maps.newLinkedHashMap();
+        flags.put("out", stdout);
+        flags.put("err", stderr);
+        int exitCode = m.execCommands(flags, "test", commands);
+        log.info("stdout from "+commands+":\n"+new String(stdout.toByteArray()));
+        log.info("stderr from "+commands+":\n"+new String(stderr.toByteArray()));
+        log.info("exit code: " + exitCode);
+        return exitCode;
+    }
+
+    private JcloudsLocation resolve(String spec) {
+        return (JcloudsLocation) managementContext.getLocationRegistry().resolve("jclouds:"+spec);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SingleMachineProvisioningLocationJcloudsLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SingleMachineProvisioningLocationJcloudsLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SingleMachineProvisioningLocationJcloudsLiveTest.java
new file mode 100644
index 0000000..e9b9dd8
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SingleMachineProvisioningLocationJcloudsLiveTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.jclouds;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ConfigKeys;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.basic.SingleMachineProvisioningLocation;
+
+public class SingleMachineProvisioningLocationJcloudsLiveTest extends AbstractJcloudsLiveTest {
+private static final Logger log = LoggerFactory.getLogger(SingleMachineProvisioningLocation.class);
+    
+    private SingleMachineProvisioningLocation<JcloudsSshMachineLocation> location;
+
+    private static final String JCLOUDS_LOCATION_SPEC = "jclouds:" + AWS_EC2_PROVIDER + ":" + AWS_EC2_USEAST_REGION_NAME;
+    
+    @Test(groups="Live")
+    public void testJcloudsSingle() throws Exception {
+        location = resolve("single:(target='"+JCLOUDS_LOCATION_SPEC+"')");
+        
+        MachineLocation m1 = obtainMachine();
+        assertNotNull(m1);
+
+        log.info("GOT "+m1);
+    }
+    
+    @Test(groups="Live")
+    public void testJcloudsSingleRelease() throws Exception {
+        location = resolve("single:(target='"+JCLOUDS_LOCATION_SPEC+"')");
+        
+        JcloudsSshMachineLocation m1 = obtainMachine();
+        log.info("GOT " + m1);
+        JcloudsSshMachineLocation m2 = obtainMachine();
+        log.info("GOT " + m2);
+        assertSame(m1, m2);
+        
+        location.release(m1);
+        assertTrue(m2.isSshable());
+
+        location.release(m2);
+        assertFalse(m2.isSshable());
+    }
+    
+    @Test(groups="Live")
+    public void testJcloudsSingleObtainReleaseObtain() throws Exception {
+        location = resolve("single:(target='"+JCLOUDS_LOCATION_SPEC+"')");
+        
+        JcloudsSshMachineLocation m1 = obtainMachine();
+        log.info("GOT " + m1);
+        
+        location.release(m1);
+        assertFalse(m1.isSshable());
+        
+        JcloudsSshMachineLocation m2 = obtainMachine();
+        assertTrue(m2.isSshable());
+        assertNotEquals(m1, m2);
+        
+        location.release(m2);
+        assertFalse(m2.isSshable());
+    }
+    
+    @Test(groups="Live")
+    public void testJCloudsNamedSingle() throws Exception {
+        brooklynProperties.put(ConfigKeys.newStringConfigKey("brooklyn.location.named.FooServers"), JCLOUDS_LOCATION_SPEC);
+        location = resolve("single:(target='named:FooServers')");
+        
+        JcloudsSshMachineLocation m1 = obtainMachine();
+        assertTrue(m1.isSshable());
+        
+        location.release(m1);
+        assertFalse(m1.isSshable());
+    }
+    
+    @Override
+    protected JcloudsSshMachineLocation obtainMachine(Map<?, ?> conf) throws Exception {
+        JcloudsSshMachineLocation result = location.obtain(conf);
+        machines.add(result);
+        return result;
+    }
+    
+    @Override
+    protected void releaseMachine(JcloudsSshMachineLocation machine) {
+        if (location.getChildren().contains(machine)) {
+            machines.remove(machine);
+            location.release(machine);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private SingleMachineProvisioningLocation<JcloudsSshMachineLocation> resolve(String spec) {
+        SingleMachineProvisioningLocation<JcloudsSshMachineLocation> result = (SingleMachineProvisioningLocation<JcloudsSshMachineLocation>) 
+                managementContext.getLocationRegistry().resolve(spec);
+        // FIXME Do we really need to setManagementContext?!
+        //result.setManagementContext(managementContext);
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/StandaloneJcloudsLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/StandaloneJcloudsLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/StandaloneJcloudsLiveTest.java
new file mode 100644
index 0000000..b08a087
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/StandaloneJcloudsLiveTest.java
@@ -0,0 +1,254 @@
+/*
+ * 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.jclouds;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+
+import org.jclouds.Constants;
+import org.jclouds.ContextBuilder;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.ExecResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.options.RunScriptOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.Statements;
+import org.jclouds.scriptbuilder.statements.login.AdminAccess;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class StandaloneJcloudsLiveTest {
+
+    // FIXME Why do this?
+    // Were we seeing bugs in jclouds for which this was easier to debug and report
+    // Is it because testProvisioningVmWithCustomUsername is disabled and not working?
+    
+    public static final Logger LOG = LoggerFactory.getLogger(StandaloneJcloudsLiveTest.class);
+    
+    private static final String PROVIDER = AbstractJcloudsLiveTest.AWS_EC2_PROVIDER;
+    private static final String REGION = AbstractJcloudsLiveTest.AWS_EC2_USEAST_REGION_NAME;
+    private static final String PRIVATE_IMAGE_ID = "us-east-1/ami-f95cf390";
+    
+    static BrooklynProperties globals = BrooklynProperties.Factory.newDefault();
+
+    String identity = globals.getFirst("brooklyn.location.jclouds.aws-ec2.identity");
+    String credential = globals.getFirst("brooklyn.location.jclouds.aws-ec2.credential");
+    
+    @Test(enabled=false, groups={"WIP","Live"})
+    public void createVm() {
+        String groupId = "mygroup-"+System.getProperty("user.name")+"-"+UUID.randomUUID().toString();
+ 
+        Properties properties = new Properties();
+        properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, Boolean.toString(true));
+        properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, Boolean.toString(true));
+        // handy to list all images... but very slow!
+//        properties.setProperty(AWSEC2Constants.PROPERTY_EC2_AMI_QUERY, "state=available;image-type=machine");
+
+        ComputeServiceContext computeServiceContext = ContextBuilder.newBuilder(PROVIDER).
+                modules(Arrays.asList(new SshjSshClientModule(), new SLF4JLoggingModule())).
+                credentials(identity, credential).
+                overrides(properties).
+                build(ComputeServiceContext.class);
+        
+        final ComputeService computeService = computeServiceContext.getComputeService();
+        
+        NodeMetadata node = null;
+        try {
+            LOG.info("Creating VM for "+identity);
+
+            TemplateBuilder templateBuilder = computeService.templateBuilder();
+            templateBuilder.locationId(REGION);
+            
+            Template template = templateBuilder.build();
+            Set<? extends NodeMetadata> nodes = computeService.createNodesInGroup(groupId, 1, template);
+            node = Iterables.getOnlyElement(nodes, null);
+            if (node == null) throw new IllegalStateException("No nodes returned");
+
+            assertNotNull(node.getOperatingSystem());
+
+            Credentials nodeCredentials = node.getCredentials();
+            final LoginCredentials expectedCredentials = LoginCredentials.fromCredentials(nodeCredentials);
+            
+            LOG.info("Started VM, waiting for it to be sshable");
+            boolean reachable = false;
+            for (int i=0; i<120; i++) {
+                try {
+                    Statement statement = Statements.newStatementList(Statements.exec("date"));
+                    ExecResponse response = computeService.runScriptOnNode(node.getId(), statement,
+                            RunScriptOptions.Builder.overrideLoginCredentials(expectedCredentials));
+                    if (response.getExitStatus() == 0) {
+                        LOG.info("ssh 'date' succeeded");
+                        reachable = true;
+                        break;
+                    }
+                    LOG.info("ssh 'date' failed, exit "+response.getExitStatus()+", but still in retry loop");
+                } catch (Exception e) {
+                    if (i<120)
+                        LOG.info("ssh 'date' failed, but still in retry loop: "+e);
+                    else {
+                        LOG.error("ssh 'date' failed after timeout: "+e, e);
+                        Throwables.propagate(e);
+                    }
+                }
+                Thread.sleep(1000);
+            }
+        
+            if (!reachable) {
+                throw new IllegalStateException("SSH failed, never reachable");
+            }
+
+        } catch (RunNodesException e) {
+            if (e.getNodeErrors().size() > 0) {
+                node = Iterables.get(e.getNodeErrors().keySet(), 0);
+            }
+            LOG.error("Failed to start VM: "+e, e);
+            throw Throwables.propagate(e);
+        } catch (Exception e) {
+            LOG.error("Failed to start VM: "+e, e);
+            throw Throwables.propagate(e);
+        } finally {
+            LOG.info("Now destroying VM: "+node);
+            computeService.destroyNode( node.getId() );
+
+            computeService.getContext().close();
+        }
+        
+    }
+    
+    @Test(enabled=false, groups={"WIP","Live"})
+    public void createVmWithAdminUser() {
+        String groupId = "mygroup-"+System.getProperty("user.name")+"-"+UUID.randomUUID().toString();
+ 
+        Properties properties = new Properties();
+        properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, Boolean.toString(true));
+        properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, Boolean.toString(true));
+
+        ComputeServiceContext computeServiceContext = ContextBuilder.newBuilder(PROVIDER).
+                modules(Arrays.asList(new SshjSshClientModule(), new SLF4JLoggingModule())).
+                credentials(identity, credential).
+                overrides(properties).
+                build(ComputeServiceContext.class);
+        
+        final ComputeService computeService = computeServiceContext.getComputeService();
+        
+        NodeMetadata node = null;
+        try {
+            LOG.info("Creating VM for "+identity);
+            String myPubKey = Files.toString(new File(System.getProperty("user.home")+"/.ssh/aws-id_rsa.pub"), Charset.defaultCharset());
+            String myPrivKey = Files.toString(new File(System.getProperty("user.home")+"/.ssh/aws-id_rsa"), Charset.defaultCharset());
+
+            TemplateBuilder templateBuilder = computeService.templateBuilder();
+            templateBuilder.locationId(REGION);
+            TemplateOptions opts = new TemplateOptions();
+            
+//            templateBuilder.imageId("us-east-1/ami-2342a94a");  //rightscale
+            // either use above, or below
+            templateBuilder.imageId(PRIVATE_IMAGE_ID);  //private one (to test when user isn't autodetected)
+            opts.overrideLoginUser("ec2-user");
+            
+            AdminAccess.Builder adminBuilder = AdminAccess.builder().
+                    adminUsername("bob").
+                    grantSudoToAdminUser(true).
+                    authorizeAdminPublicKey(true).adminPublicKey(myPubKey).
+                    // items below aren't wanted but values for some are required otherwise AdminAccess uses all defaults
+                    lockSsh(true).adminPassword(Identifiers.makeRandomId(12)).
+                    resetLoginPassword(false).loginPassword(Identifiers.makeRandomId(12)).
+                    installAdminPrivateKey(false).adminPrivateKey("ignored");
+            opts.runScript(adminBuilder.build());
+            
+            templateBuilder.options(opts);
+            
+            Template template = templateBuilder.build();
+            Set<? extends NodeMetadata> nodes = computeService.createNodesInGroup(groupId, 1, template);
+            node = Iterables.getOnlyElement(nodes, null);
+            if (node == null) throw new IllegalStateException("No nodes returned");
+
+            LOG.info("Started VM, waiting for it to be sshable on "+node.getPublicAddresses());
+            final LoginCredentials crds =
+//                    node.getCredentials();
+                    LoginCredentials.builder().user("bob").privateKey(myPrivKey).build();
+            boolean reachable = false;
+            for (int i=0; i<120; i++) {
+                try {
+                    Statement statement = Statements.newStatementList(Statements.exec("date"));
+                    ExecResponse response = computeService.runScriptOnNode(node.getId(), statement,
+                            RunScriptOptions.Builder.overrideLoginCredentials(crds));
+                    if (response.getExitStatus() == 0) {
+                        LOG.info("ssh 'date' succeeded");
+                        reachable = true;
+                        break;
+                    }
+                    LOG.info("ssh 'date' failed, exit "+response.getExitStatus()+", but still in retry loop");
+                } catch (Exception e) {
+                    if (i<120)
+                        LOG.info("ssh 'date' failed, but still in retry loop: "+e);
+                    else {
+                        LOG.error("ssh 'date' failed after timeout: "+e, e); 
+                        Throwables.propagate(e);
+                    }
+                }
+                Thread.sleep(1000);
+            }
+        
+            if (!reachable) {
+                throw new IllegalStateException("SSH failed, never reachable");
+            }
+            
+        } catch (RunNodesException e) {
+            if (e.getNodeErrors().size() > 0) {
+                node = Iterables.get(e.getNodeErrors().keySet(), 0);
+            }
+            LOG.error("Failed to start VM: "+e, e);
+            throw Throwables.propagate(e);
+        } catch (Exception e) {
+            LOG.error("Failed to start VM: "+e, e);
+            throw Throwables.propagate(e);
+        } finally {
+            LOG.info("Now destroying VM: "+node);
+            computeService.destroyNode( node.getId() );
+
+            computeService.getContext().close();
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizerTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizerTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizerTest.java
new file mode 100644
index 0000000..c6025ba
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizerTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.jclouds.networking;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Collections;
+
+import org.jclouds.aws.AWSResponseException;
+import org.jclouds.aws.domain.AWSError;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.domain.Location;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.mockito.Answers;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Cidr;
+
+public class JcloudsLocationSecurityGroupCustomizerTest {
+
+    JcloudsLocationSecurityGroupCustomizer customizer;
+    ComputeService computeService;
+    Location location;
+    SecurityGroupExtension securityApi;
+
+    /** Used to skip external checks in unit tests. */
+    private static class TestCidrSupplier implements Supplier<Cidr> {
+        @Override public Cidr get() {
+            return new Cidr("192.168.10.10/32");
+        }
+    }
+
+    @BeforeMethod
+    public void setUp() {
+        customizer = new JcloudsLocationSecurityGroupCustomizer("testapp", new TestCidrSupplier());
+        location = mock(Location.class);
+        securityApi = mock(SecurityGroupExtension.class);
+        computeService = mock(ComputeService.class, Answers.RETURNS_DEEP_STUBS.get());
+        when(computeService.getSecurityGroupExtension()).thenReturn(Optional.of(securityApi));
+    }
+
+    @Test
+    public void testSameInstanceReturnedForSameApplication() {
+        assertEquals(JcloudsLocationSecurityGroupCustomizer.getInstance("a"),
+                JcloudsLocationSecurityGroupCustomizer.getInstance("a"));
+        assertNotEquals(JcloudsLocationSecurityGroupCustomizer.getInstance("a"),
+                JcloudsLocationSecurityGroupCustomizer.getInstance("b"));
+    }
+
+    @Test
+    public void testSecurityGroupAddedWhenJcloudsLocationCustomised() {
+        Template template = mock(Template.class);
+        TemplateOptions templateOptions = mock(TemplateOptions.class);
+        when(template.getLocation()).thenReturn(location);
+        when(template.getOptions()).thenReturn(templateOptions);
+        SecurityGroup group = newGroup("id");
+        when(securityApi.createSecurityGroup(anyString(), eq(location))).thenReturn(group);
+
+        // Two Brooklyn.JcloudsLocations added to same Jclouds.Location
+        JcloudsLocation jcloudsLocationA = new JcloudsLocation(MutableMap.of("deferConstruction", true));
+        JcloudsLocation jcloudsLocationB = new JcloudsLocation(MutableMap.of("deferConstruction", true));
+        customizer.customize(jcloudsLocationA, computeService, template);
+        customizer.customize(jcloudsLocationB, computeService, template);
+
+        // One group with three permissions shared by both locations.
+        // Expect TCP, UDP and ICMP between members of group and SSH to Brooklyn
+        verify(securityApi).createSecurityGroup(anyString(), eq(location));
+        verify(securityApi, times(4)).addIpPermission(any(IpPermission.class), eq(group));
+        // New groups set on options
+        verify(templateOptions, times(2)).securityGroups(anyString());
+    }
+
+    @Test
+    public void testSharedGroupLoadedWhenItExistsButIsNotCached() {
+        Template template = mock(Template.class);
+        TemplateOptions templateOptions = mock(TemplateOptions.class);
+        when(template.getLocation()).thenReturn(location);
+        when(template.getOptions()).thenReturn(templateOptions);
+        JcloudsLocation jcloudsLocation = new JcloudsLocation(MutableMap.of("deferConstruction", true));
+        SecurityGroup shared = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup irrelevant = newGroup("irrelevant");
+        when(securityApi.listSecurityGroupsInLocation(location)).thenReturn(ImmutableSet.of(irrelevant, shared));
+
+        customizer.customize(jcloudsLocation, computeService, template);
+
+        verify(securityApi).listSecurityGroupsInLocation(location);
+        verify(securityApi, never()).createSecurityGroup(anyString(), any(Location.class));
+    }
+
+    @Test
+    public void testAddPermissionsToNode() {
+        IpPermission ssh = newPermission(22);
+        IpPermission jmx = newPermission(31001);
+        String nodeId = "node";
+        SecurityGroup sharedGroup = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup group = newGroup("id");
+        when(securityApi.listSecurityGroupsForNode(nodeId)).thenReturn(ImmutableSet.of(sharedGroup, group));
+        when(computeService.getContext().unwrap().getId()).thenReturn("aws-ec2");
+
+        customizer.addPermissionsToLocation(ImmutableList.of(ssh, jmx), nodeId, computeService);
+
+        verify(securityApi, never()).createSecurityGroup(anyString(), any(Location.class));
+        verify(securityApi, times(1)).addIpPermission(ssh, group);
+        verify(securityApi, times(1)).addIpPermission(jmx, group);
+    }
+
+    @Test
+    public void testAddPermissionsToNodeUsesUncachedSecurityGroup() {
+        JcloudsLocation jcloudsLocation = new JcloudsLocation(MutableMap.of("deferConstruction", true));
+        IpPermission ssh = newPermission(22);
+        String nodeId = "nodeId";
+        SecurityGroup sharedGroup = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup uniqueGroup = newGroup("unique");
+
+        Template template = mock(Template.class);
+        TemplateOptions templateOptions = mock(TemplateOptions.class);
+        when(template.getLocation()).thenReturn(location);
+        when(template.getOptions()).thenReturn(templateOptions);
+        when(securityApi.createSecurityGroup(anyString(), eq(location))).thenReturn(sharedGroup);
+        when(computeService.getContext().unwrap().getId()).thenReturn("aws-ec2");
+
+        // Call customize to cache the shared group
+        customizer.customize(jcloudsLocation, computeService, template);
+        reset(securityApi);
+        when(securityApi.listSecurityGroupsForNode(nodeId)).thenReturn(ImmutableSet.of(uniqueGroup, sharedGroup));
+        customizer.addPermissionsToLocation(ImmutableSet.of(ssh), nodeId, computeService);
+
+        // Expect the per-machine group to have been altered, not the shared group
+        verify(securityApi).addIpPermission(ssh, uniqueGroup);
+        verify(securityApi, never()).addIpPermission(any(IpPermission.class), eq(sharedGroup));
+    }
+
+    @Test
+    public void testSecurityGroupsLoadedWhenAddingPermissionsToUncachedNode() {
+        IpPermission ssh = newPermission(22);
+        String nodeId = "nodeId";
+        SecurityGroup sharedGroup = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup uniqueGroup = newGroup("unique");
+
+        when(securityApi.listSecurityGroupsForNode(nodeId)).thenReturn(ImmutableSet.of(sharedGroup, uniqueGroup));
+        when(computeService.getContext().unwrap().getId()).thenReturn("aws-ec2");
+
+        // Expect first call to list security groups on nodeId, second to use cached version
+        customizer.addPermissionsToLocation(ImmutableSet.of(ssh), nodeId, computeService);
+        customizer.addPermissionsToLocation(ImmutableSet.of(ssh), nodeId, computeService);
+
+        verify(securityApi, times(1)).listSecurityGroupsForNode(nodeId);
+        verify(securityApi, times(2)).addIpPermission(ssh, uniqueGroup);
+        verify(securityApi, never()).addIpPermission(any(IpPermission.class), eq(sharedGroup));
+    }
+
+    @Test
+    public void testAddRuleNotRetriedByDefault() {
+        IpPermission ssh = newPermission(22);
+        String nodeId = "node";
+        SecurityGroup sharedGroup = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup uniqueGroup = newGroup("unique");
+        when(securityApi.listSecurityGroupsForNode(nodeId)).thenReturn(ImmutableSet.of(sharedGroup, uniqueGroup));
+        when(securityApi.addIpPermission(eq(ssh), eq(uniqueGroup)))
+                .thenThrow(new RuntimeException("exception creating " + ssh));
+        when(computeService.getContext().unwrap().getId()).thenReturn("aws-ec2");
+
+        try {
+            customizer.addPermissionsToLocation(ImmutableList.of(ssh), nodeId, computeService);
+        } catch (Exception e) {
+            assertTrue(e.getMessage().contains("repeated errors from provider"), "message=" + e.getMessage());
+        }
+        verify(securityApi, never()).createSecurityGroup(anyString(), any(Location.class));
+        verify(securityApi, times(1)).addIpPermission(ssh, uniqueGroup);
+    }
+
+    @Test
+    public void testCustomExceptionRetryablePredicate() {
+        final String message = "testCustomExceptionRetryablePredicate";
+        Predicate<Exception> messageChecker = new Predicate<Exception>() {
+            @Override
+            public boolean apply(Exception input) {
+                Throwable t = input;
+                while (t != null) {
+                    if (t.getMessage().contains(message)) {
+                        return true;
+                    } else {
+                        t = t.getCause();
+                    }
+                }
+                return false;
+            }
+        };
+        customizer.setRetryExceptionPredicate(messageChecker);
+        when(computeService.getContext().unwrap().getId()).thenReturn("aws-ec2");
+
+        IpPermission ssh = newPermission(22);
+        String nodeId = "node";
+        SecurityGroup sharedGroup = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup uniqueGroup = newGroup("unique");
+        when(securityApi.listSecurityGroupsForNode(nodeId)).thenReturn(ImmutableSet.of(sharedGroup, uniqueGroup));
+        when(securityApi.addIpPermission(eq(ssh), eq(uniqueGroup)))
+                .thenThrow(new RuntimeException(new Exception(message)))
+                .thenThrow(new RuntimeException(new Exception(message)))
+                .thenReturn(sharedGroup);
+
+        customizer.addPermissionsToLocation(ImmutableList.of(ssh), nodeId, computeService);
+
+        verify(securityApi, never()).createSecurityGroup(anyString(), any(Location.class));
+        verify(securityApi, times(3)).addIpPermission(ssh, uniqueGroup);
+    }
+
+    @Test
+    public void testAddRuleRetriedOnAwsFailure() {
+        IpPermission ssh = newPermission(22);
+        String nodeId = "nodeId";
+        SecurityGroup sharedGroup = newGroup(customizer.getNameForSharedSecurityGroup());
+        SecurityGroup uniqueGroup = newGroup("unique");
+        customizer.setRetryExceptionPredicate(JcloudsLocationSecurityGroupCustomizer.newAwsExceptionRetryPredicate());
+        when(securityApi.listSecurityGroupsForNode(nodeId)).thenReturn(ImmutableSet.of(sharedGroup, uniqueGroup));
+        when(securityApi.addIpPermission(any(IpPermission.class), eq(uniqueGroup)))
+                .thenThrow(newAwsResponseExceptionWithCode("InvalidGroup.InUse"))
+                .thenThrow(newAwsResponseExceptionWithCode("DependencyViolation"))
+                .thenThrow(newAwsResponseExceptionWithCode("RequestLimitExceeded"))
+                .thenThrow(newAwsResponseExceptionWithCode("Blocked"))
+                .thenReturn(sharedGroup);
+        when(computeService.getContext().unwrap().getId()).thenReturn("aws-ec2");
+
+        try {
+            customizer.addPermissionsToLocation(ImmutableList.of(ssh), nodeId, computeService);
+        } catch (Exception e) {
+            String expected = "repeated errors from provider";
+            assertTrue(e.getMessage().contains(expected), "expected exception message to contain " + expected + ", was: " + e.getMessage());
+        }
+
+        verify(securityApi, never()).createSecurityGroup(anyString(), any(Location.class));
+        verify(securityApi, times(4)).addIpPermission(ssh, uniqueGroup);
+    }
+
+    private SecurityGroup newGroup(String id) {
+        URI uri = null;
+        String ownerId = null;
+        return new SecurityGroup(
+                "providerId",
+                id,
+                id,
+                location,
+                uri,
+                Collections.<String, String>emptyMap(),
+                ImmutableSet.<String>of(),
+                ImmutableSet.<IpPermission>of(),
+                ownerId);
+    }
+
+    private IpPermission newPermission(int port) {
+        return IpPermission.builder()
+                .ipProtocol(IpProtocol.TCP)
+                .fromPort(port)
+                .toPort(port)
+                .cidrBlock("0.0.0.0/0")
+                .build();
+    }
+
+    private AWSError newAwsErrorWithCode(String code) {
+        AWSError e = new AWSError();
+        e.setCode(code);
+        return e;
+    }
+
+    private Exception newAwsResponseExceptionWithCode(String code) {
+        AWSResponseException e = new AWSResponseException("irrelevant message", null, null, newAwsErrorWithCode(code));
+        return new RuntimeException(e);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwardingStubbedLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwardingStubbedLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwardingStubbedLiveTest.java
new file mode 100644
index 0000000..860a8e3
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwardingStubbedLiveTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.jclouds.networking;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.util.List;
+
+import org.apache.brooklyn.location.jclouds.AbstractJcloudsStubbedLiveTest;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.domain.LoginCredentials;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.config.ConfigKey;
+import org.apache.brooklyn.location.access.PortForwardManager;
+import org.apache.brooklyn.location.access.PortForwardManagerImpl;
+import org.apache.brooklyn.location.jclouds.JcloudsSshMachineLocation;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.net.Protocol;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.net.HostAndPort;
+
+/**
+ * The VM creation is stubbed out, but it still requires live access (i.e. real account credentials)
+ * to generate the template etc.
+ * 
+ * We supply a ComputeServiceRegistry that delegates to the real instance for everything except
+ * VM creation and deletion. For those operations, it delegates to a NodeCreator that 
+ * returns a dummy NodeMetadata, recording all calls made to it.
+ */
+public class JcloudsPortForwardingStubbedLiveTest extends AbstractJcloudsStubbedLiveTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(JcloudsPortForwardingStubbedLiveTest.class);
+
+    static class RecordingJcloudsPortForwarderExtension implements JcloudsPortForwarderExtension {
+        final PortForwardManager pfm;
+        final List<List<Object>> opens = Lists.newCopyOnWriteArrayList();
+        final List<List<Object>> closes = Lists.newCopyOnWriteArrayList();
+        int nextPublicPort = 12345;
+        
+        RecordingJcloudsPortForwarderExtension(PortForwardManager pfm) {
+            this.pfm = pfm;
+        }
+        @Override public HostAndPort openPortForwarding(NodeMetadata node, int targetPort, Optional<Integer> optionalPublicPort, Protocol protocol, Cidr accessingCidr) {
+            opens.add(ImmutableList.of(node, targetPort, optionalPublicPort, protocol, accessingCidr));
+            HostAndPort result = HostAndPort.fromParts("1.2.3.4", nextPublicPort++);
+            pfm.associate(node.getId(), result, targetPort);
+            return result;
+        }
+        @Override public void closePortForwarding(NodeMetadata node, int targetPort, HostAndPort publicHostAndPort, Protocol protocol) {
+            closes.add(ImmutableList.of(node, targetPort, publicHostAndPort, protocol));
+            pfm.forgetPortMapping(node.getId(), publicHostAndPort.getPort());
+        }
+    }
+
+    @Override
+    protected NodeCreator newNodeCreator() {
+        return new NodeCreator() {
+            int nextIpSuffix = 2;
+            @Override
+            protected NodeMetadata newNode(String group, Template template) {
+                int ipSuffix = nextIpSuffix++;
+                NodeMetadata result = new NodeMetadataBuilder()
+                        .id("myid-"+ipSuffix)
+                        .credentials(LoginCredentials.builder().identity("myuser").credential("mypassword").build())
+                        .loginPort(22)
+                        .status(Status.RUNNING)
+                        .publicAddresses(ImmutableList.of("173.194.32."+ipSuffix))
+                        .privateAddresses(ImmutableList.of("172.168.10."+ipSuffix))
+                        .build();
+                return result;
+            }
+        };
+    }
+
+    @Test(groups = {"Live", "Live-sanity"})
+    protected void testPortForwardingCallsForwarder() throws Exception {
+        PortForwardManager pfm = new PortForwardManagerImpl();
+        RecordingJcloudsPortForwarderExtension portForwarder = new RecordingJcloudsPortForwarderExtension(pfm);
+        
+        JcloudsSshMachineLocation machine = obtainMachine(ImmutableMap.<ConfigKey<?>,Object>of(
+                JcloudsLocation.USE_PORT_FORWARDING, true,
+                JcloudsLocation.PORT_FORWARDER, portForwarder));
+        
+        NodeMetadata created = nodeCreator.created.get(0);
+        assertEquals(nodeCreator.created.size(), 1, "created="+nodeCreator.created+"; machine="+machine);
+        assertEquals(machine.getNode(), created);
+        assertEquals(portForwarder.opens.size(), 1, "opens="+portForwarder.opens+"; machine="+machine);
+        assertEquals(portForwarder.opens.get(0).get(0), created);
+        assertEquals(portForwarder.opens.get(0).get(1), 22);
+        assertEquals(portForwarder.opens.get(0).get(3), Protocol.TCP);
+        assertEquals(portForwarder.opens.get(0).get(4), Cidr.UNIVERSAL);
+        assertEquals(machine.getSshHostAndPort(), HostAndPort.fromParts("1.2.3.4", 12345));
+        
+        releaseMachine(machine);
+        String destroyed = nodeCreator.destroyed.get(0);
+        assertEquals(nodeCreator.destroyed.size(), 1, "destroyed="+nodeCreator.destroyed+"; machine="+machine);
+        assertEquals(destroyed, created.getId());
+        assertEquals(portForwarder.closes.size(), 1, "closes="+portForwarder.closes+"; machine="+machine);
+        assertEquals(portForwarder.closes.get(0).get(0), created);
+        assertEquals(portForwarder.closes.get(0).get(1), 22);
+        assertEquals(portForwarder.closes.get(0).get(2), HostAndPort.fromParts("1.2.3.4", 12345));
+        assertEquals(portForwarder.closes.get(0).get(3), Protocol.TCP);
+    }
+    
+    @Test(groups = {"Live", "Live-sanity"})
+    protected void testDeregistersWithPortForwardManagerOnRelease() throws Exception {
+        PortForwardManager pfm = new PortForwardManagerImpl();
+        RecordingJcloudsPortForwarderExtension portForwarder = new RecordingJcloudsPortForwarderExtension(pfm);
+        
+        JcloudsSshMachineLocation machine = obtainMachine(ImmutableMap.<ConfigKey<?>,Object>of(
+                JcloudsLocation.PORT_FORWARDER, portForwarder,
+                JcloudsLocation.PORT_FORWARDING_MANAGER, pfm));
+        
+        // Add an association for this machine - expect that to be deleted when the machine is released.
+        HostAndPort publicHostAndPort = HostAndPort.fromParts("1.2.3.4", 1234);
+        pfm.associate("mypublicip", publicHostAndPort, machine, 80);
+        assertEquals(pfm.lookup(machine, 80), publicHostAndPort);
+        assertEquals(pfm.lookup("mypublicip", 80), publicHostAndPort);
+
+        // Release
+        releaseMachine(machine);
+        
+        // Expect to have been cleared from PortForwardManager's records
+        assertNull(pfm.lookup(machine, 80));
+        assertNull(pfm.lookup("mypublicip", 80));
+        
+        // And for port-forwarding to have been closed
+        assertEquals(portForwarder.closes.size(), 1, "closes="+portForwarder.closes+"; machine="+machine);
+        assertEquals(portForwarder.closes.get(0).get(1), 80);
+        assertEquals(portForwarder.closes.get(0).get(2), HostAndPort.fromParts("1.2.3.4", 1234));
+        assertEquals(portForwarder.closes.get(0).get(3), Protocol.TCP);
+    }
+    
+    @Test(groups = {"Live", "Live-sanity"})
+    protected void testReleaseVmDoesNotImpactOtherVms() throws Exception {
+        PortForwardManager pfm = new PortForwardManagerImpl();
+        RecordingJcloudsPortForwarderExtension portForwarder = new RecordingJcloudsPortForwarderExtension(pfm);
+        
+        JcloudsSshMachineLocation machine1 = obtainMachine(ImmutableMap.<ConfigKey<?>,Object>of(
+                JcloudsLocation.USE_PORT_FORWARDING, true,
+                JcloudsLocation.PORT_FORWARDER, portForwarder,
+                JcloudsLocation.PORT_FORWARDING_MANAGER, pfm));
+        
+        JcloudsSshMachineLocation machine2 = obtainMachine(ImmutableMap.<ConfigKey<?>,Object>of(
+                JcloudsLocation.USE_PORT_FORWARDING, true,
+                JcloudsLocation.PORT_FORWARDER, portForwarder,
+                JcloudsLocation.PORT_FORWARDING_MANAGER, pfm));
+        
+        NodeMetadata node1 = nodeCreator.created.get(0);
+
+        // Add an association for machine2 - expect that not to be touched when machine1 is released.
+        HostAndPort publicHostAndPort = HostAndPort.fromParts("1.2.3.4", 1234);
+        pfm.associate("mypublicip", publicHostAndPort, machine2, 80);
+
+        // Release machine1
+        releaseMachine(machine1);
+        
+        // Expect machine2 to still be registered
+        assertEquals(pfm.lookup(machine2, 80), publicHostAndPort);
+        assertEquals(pfm.lookup("mypublicip", 80), publicHostAndPort);
+        
+        // And no calls to "close" for machine2; just for machine1's port 22
+        assertEquals(portForwarder.closes.size(), 1, "closes="+portForwarder.closes+"; machine1="+machine1);
+        assertEquals(portForwarder.closes.get(0).get(0), node1);
+        assertEquals(portForwarder.closes.get(0).get(1), 22);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupLiveTest.java
new file mode 100644
index 0000000..0eed616
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupLiveTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.jclouds.networking;
+
+import org.testng.annotations.Test;
+
+@Test(groups = {"Live", "WIP"})
+public class SecurityGroupLiveTest {
+
+    public void testCreateEc2WithSecurityGroup() {
+        SecurityGroupDefinition sgDef = new SecurityGroupDefinition()
+            .allowingInternalPorts(8097, 8098).allowingInternalPortRange(6000, 7999)
+            .allowingPublicPort(8099);
+        // TODO create machine and test
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/pool/JcloudsMachinePoolLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/pool/JcloudsMachinePoolLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/pool/JcloudsMachinePoolLiveTest.java
new file mode 100644
index 0000000..4d30c80
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/pool/JcloudsMachinePoolLiveTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.jclouds.pool;
+
+import java.util.Arrays;
+
+import org.apache.brooklyn.location.jclouds.AbstractJcloudsLiveTest;
+import org.jclouds.ContextBuilder;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
+import org.jclouds.sshj.config.SshjSshClientModule;
+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 org.apache.brooklyn.location.jclouds.JcloudsLocation;
+
+public class JcloudsMachinePoolLiveTest extends AbstractJcloudsLiveTest {
+
+    public static final Logger log = LoggerFactory.getLogger(JcloudsMachinePoolLiveTest.class);
+    
+    private static final String PROVIDER = AWS_EC2_PROVIDER;
+    private static final String LOCATION_SPEC = PROVIDER + ":" + AWS_EC2_EUWEST_REGION_NAME;
+    
+    public static class SamplePool extends MachinePool {
+        public SamplePool(ComputeService svc) {
+            super(svc);
+        }
+
+        public final static ReusableMachineTemplate 
+            USUAL_VM = 
+                new ReusableMachineTemplate("usual").templateOwnedByMe().
+                tagOptional("tagForUsualVm").
+                metadataOptional("metadataForUsualVm", "12345").
+                minRam(1024).minCores(2);
+
+        public final static ReusableMachineTemplate 
+            ANYONE_NOT_TINY_VM = 
+                new ReusableMachineTemplate("anyone").
+                minRam(512).minCores(1).strict(false);
+
+        public static final ReusableMachineTemplate 
+            VM_LARGE1 = 
+                new ReusableMachineTemplate("vm.large1").templateOwnedByMe().
+                minRam(16384).minCores(4),
+            VM_SMALL1 = 
+                new ReusableMachineTemplate("vm.small1").templateOwnedByMe().smallest();
+        
+        { registerTemplates(USUAL_VM, ANYONE_NOT_TINY_VM, VM_LARGE1, VM_SMALL1); }
+    }
+    
+    private ComputeServiceContext context;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(LOCATION_SPEC);
+        
+        context = ContextBuilder.newBuilder(PROVIDER)
+                .modules(Arrays.asList(new SshjSshClientModule(), new SLF4JLoggingModule()))
+                .credentials(jcloudsLocation.getIdentity(), jcloudsLocation.getCredential())
+                .build(ComputeServiceContext.class);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            super.tearDown();
+        } finally {
+            if (context != null) context.close();
+        }
+    }
+    
+    @Test(groups={"Live","WIP"})
+    public void buildClaimAndDestroy() {
+        ComputeService svc = context.getComputeService();
+        SamplePool p = new SamplePool(svc);
+        log.info("buildClaimAndDestroy: created pool");
+        p.refresh();
+        log.info("buildClaimAndDestroy: refreshed pool");
+        p.ensureExists(2, SamplePool.USUAL_VM);
+        log.info("buildClaimAndDestroy: ensure have 2");
+        MachineSet l = p.claim(1, SamplePool.USUAL_VM);
+        Assert.assertEquals(l.size(), 1);
+        log.info("buildClaimAndDestroy: claimed 1");
+        MachineSet unclaimedUsual = p.unclaimed(MachinePoolPredicates.matching(SamplePool.USUAL_VM));
+        log.info("buildClaimAndDestroy: unclaimed now "+unclaimedUsual);
+        Assert.assertTrue(!unclaimedUsual.isEmpty());
+        p.destroy(unclaimedUsual);
+        unclaimedUsual = p.unclaimed(MachinePoolPredicates.matching(SamplePool.USUAL_VM));
+        log.info("buildClaimAndDestroy: destroyed, unclaimed now "+unclaimedUsual);
+        log.info("end");
+    }
+    
+
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AbstractJcloudsLocationTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AbstractJcloudsLocationTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AbstractJcloudsLocationTest.java
new file mode 100644
index 0000000..fd93a7a
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AbstractJcloudsLocationTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.jclouds.provider;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+public abstract class AbstractJcloudsLocationTest {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractJcloudsLocationTest.class);
+
+    private final String provider;
+
+    protected JcloudsLocation loc;
+    protected List<SshMachineLocation> machines = MutableList.of();
+    protected ManagementContext ctx;
+
+    protected AbstractJcloudsLocationTest(String provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * The location and image id tuplets to test.
+     */
+    @DataProvider(name = "fromImageId")
+    public abstract Object[][] cloudAndImageIds();
+
+    /**
+     * A single location and image id tuplet to test.
+     */
+    @DataProvider(name = "fromFirstImageId")
+    public Object[][] cloudAndImageFirstId() {
+        Object[][] all = cloudAndImageIds();
+        return (all != null) ? new Object[][] { all[0] } : new Object[][] { };
+    }
+
+    /**
+     * The location and image name pattern tuplets to test.
+     */
+    @DataProvider(name = "fromImageNamePattern")
+    public abstract Object[][] cloudAndImageNamePatterns();
+
+    /**
+     * The location, image pattern and image owner tuplets to test.
+     */
+    @DataProvider(name = "fromImageDescriptionPattern")
+    public abstract Object[][] cloudAndImageDescriptionPatterns();
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        BrooklynProperties props = BrooklynProperties.Factory.newDefault().addFromMap(ImmutableMap.of("provider", provider));
+        ctx = Entities.newManagementContext(props.asMapWithStringKeys());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        List<Exception> exceptions = Lists.newArrayList();
+        for (SshMachineLocation machine : machines) {
+            try {
+                loc.release(machine);
+            } catch (Exception e) {
+                LOG.warn("Error releasing {}: {}; continuing...", machine, e.getMessage());
+                exceptions.add(e);
+            }
+        }
+        if (!exceptions.isEmpty()) {
+            LOG.info("Exception during tearDown: {}", Exceptions.collapseText(exceptions.get(0)));
+        }
+        machines.clear();
+        
+        if (ctx != null) Entities.destroyAllCatching(ctx);
+    }
+
+    @Test(dataProvider="fromImageId")
+    public void testTagMapping(String regionName, String imageId, String imageOwner) {
+        Map<String, Object> dummy = ImmutableMap.<String, Object>of("identity", "DUMMY", "credential", "DUMMY");
+        loc = (JcloudsLocation) ctx.getLocationRegistry().resolve(provider + (regionName == null ? "" : ":" + regionName), dummy);
+        ImmutableMap.Builder<String, Object> builder = ImmutableMap.<String, Object>builder().put("imageId", imageId);
+        if (imageOwner != null) builder.put("imageOwner", imageOwner);
+        Map<String, Object> tagMapping = builder.build();
+        loc.setTagMapping(ImmutableMap.<String, Map<String, ? extends Object>>of("MyEntityType", tagMapping));
+
+        Map<String, Object> flags = loc.getProvisioningFlags(ImmutableList.of("MyEntityType"));
+        assertTrue(Maps.<String, Object>difference(flags, tagMapping).entriesOnlyOnRight().isEmpty(), "flags="+flags);
+    }
+
+    @Test(groups = "Live", dataProvider="fromImageId")
+    public void testProvisionVmUsingImageId(String regionName, String imageId, String imageOwner) {
+        loc = (JcloudsLocation) ctx.getLocationRegistry().resolve(provider + (regionName == null ? "" : ":" + regionName));
+        SshMachineLocation machine = obtainMachine(MutableMap.of("imageId", imageId, "imageOwner", imageOwner, JcloudsLocation.MACHINE_CREATE_ATTEMPTS, 2));
+
+        LOG.info("Provisioned {} vm {}; checking if ssh'able", provider, machine);
+        assertTrue(machine.isSshable());
+    }
+    
+    @Test(groups = "Live", dataProvider="fromImageNamePattern")
+    public void testProvisionVmUsingImageNamePattern(String regionName, String imageNamePattern, String imageOwner) {
+        loc = (JcloudsLocation) ctx.getLocationRegistry().resolve(provider + (regionName == null ? "" : ":" + regionName));
+        SshMachineLocation machine = obtainMachine(MutableMap.of("imageNameRegex", imageNamePattern, "imageOwner", imageOwner, JcloudsLocation.MACHINE_CREATE_ATTEMPTS, 2));
+        
+        LOG.info("Provisioned {} vm {}; checking if ssh'able", provider, machine);
+        assertTrue(machine.isSshable());
+    }
+    
+    @Test(groups = "Live", dataProvider="fromImageDescriptionPattern")
+    public void testProvisionVmUsingImageDescriptionPattern(String regionName, String imageDescriptionPattern, String imageOwner) {
+        loc = (JcloudsLocation) ctx.getLocationRegistry().resolve(provider + (regionName == null ? "" : ":" + regionName));
+        SshMachineLocation machine = obtainMachine(MutableMap.of("imageDescriptionRegex", imageDescriptionPattern, "imageOwner", imageOwner, JcloudsLocation.MACHINE_CREATE_ATTEMPTS, 2));
+        
+        LOG.info("Provisioned {} vm {}; checking if ssh'able", provider, machine);
+        assertTrue(machine.isSshable());
+    }
+
+    // Use this utility method to ensure machines are released on tearDown
+    protected SshMachineLocation obtainMachine(Map flags) {
+        try {
+            SshMachineLocation result = (SshMachineLocation)loc.obtain(flags);
+            machines.add(result);
+            return result;
+        } catch (NoMachinesAvailableException nmae) {
+            LOG.warn("No machines available", nmae);
+            throw Exceptions.propagate(nmae);
+        }
+    }
+    
+    protected SshMachineLocation release(SshMachineLocation machine) {
+        machines.remove(machine);
+        loc.release(machine);
+        return machine;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AwsEc2LocationLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AwsEc2LocationLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AwsEc2LocationLiveTest.java
new file mode 100644
index 0000000..6644645
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/AwsEc2LocationLiveTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.jclouds.provider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AwsEc2LocationLiveTest extends AbstractJcloudsLocationTest {
+
+    private static final String PROVIDER = "aws-ec2";
+    private static final String EUWEST_REGION_NAME = "eu-west-1";
+    private static final String USEAST_REGION_NAME = "us-east-1";
+    private static final String EUWEST_IMAGE_ID = EUWEST_REGION_NAME+"/"+"ami-89def4fd"; // RightImage_CentOS_5.4_i386_v5.5.9_EBS
+    private static final String USEAST_IMAGE_ID = USEAST_REGION_NAME+"/"+"ami-2342a94a"; // RightImage_CentOS_5.4_i386_v5.5.9_EBS
+    private static final String IMAGE_OWNER = "411009282317";
+    private static final String IMAGE_PATTERN = "RightImage_CentOS_5.4_i386_v5.5.9_EBS";
+
+    public AwsEc2LocationLiveTest() {
+        super(PROVIDER);
+    }
+
+    @Override
+    @DataProvider(name = "fromImageId")
+    public Object[][] cloudAndImageIds() {
+        return new Object[][] {
+                new Object[] { EUWEST_REGION_NAME, EUWEST_IMAGE_ID, IMAGE_OWNER },
+                new Object[] { USEAST_REGION_NAME, USEAST_IMAGE_ID, IMAGE_OWNER }
+            };
+    }
+
+    @Override
+    @DataProvider(name = "fromImageDescriptionPattern")
+    public Object[][] cloudAndImageDescriptionPatterns() {
+        return new Object[][] {
+                new Object[] { EUWEST_REGION_NAME, IMAGE_PATTERN, IMAGE_OWNER },
+                new Object[] { USEAST_REGION_NAME, IMAGE_PATTERN, IMAGE_OWNER }
+            };
+    }
+
+    @Override
+    @DataProvider(name = "fromImageNamePattern")
+    public Object[][] cloudAndImageNamePatterns() {
+        return new Object[][] {
+                new Object[] { USEAST_REGION_NAME, IMAGE_PATTERN, IMAGE_OWNER }
+            };
+    }
+
+    @Test(enabled = false)
+    public void noop() { } /* just exists to let testNG IDE run the test */
+}