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

[08/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/JcloudsLocationResolverTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
new file mode 100644
index 0000000..8cd20b9
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
@@ -0,0 +1,357 @@
+/*
+ * 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.fail;
+
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.brooklyn.location.basic.LocationInternal;
+import org.apache.brooklyn.location.cloud.CloudLocationConfig;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.event.basic.MapConfigKey;
+import brooklyn.event.basic.SetConfigKey;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
+
+public class JcloudsLocationResolverTest {
+
+    private static final Logger log = LoggerFactory.getLogger(JcloudsLocationResolverTest.class);
+    
+    private LocalManagementContext managementContext;
+    private BrooklynProperties brooklynProperties;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        brooklynProperties = managementContext.getBrooklynProperties();
+
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.identity", "aws-ec2-id");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.credential", "aws-ec2-cred");
+        brooklynProperties.put("brooklyn.location.jclouds.rackspace-cloudservers-uk.identity", "cloudservers-uk-id");
+        brooklynProperties.put("brooklyn.location.jclouds.rackspace-cloudservers-uk.credential", "cloudservers-uk-cred");
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (managementContext != null)
+            managementContext.terminate();
+    }
+
+    @Test
+    public void testJcloudsTakesDotSeparateProperty() {
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.loginUser.privateKeyFile", "myfile");
+        String file = resolve("jclouds:aws-ec2").getConfig(JcloudsLocation.LOGIN_USER_PRIVATE_KEY_FILE);
+        assertEquals(file, "myfile");
+    }
+
+    @Test
+    public void testJcloudsTakesProviderScopedProperties() {
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.privateKeyFile", "myprivatekeyfile");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.publicKeyFile", "mypublickeyfile");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.privateKeyData", "myprivateKeyData");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.publicKeyData", "myPublicKeyData");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.privateKeyPassphrase", "myprivateKeyPassphrase");
+        Map<String, Object> conf = resolve("jclouds:aws-ec2").config().getBag().getAllConfig();
+
+        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
+        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
+        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
+        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
+        assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
+    }
+
+    @Test
+    public void testJcloudsTakesGenericScopedProperties() {
+        brooklynProperties.put("brooklyn.location.jclouds.privateKeyFile", "myprivatekeyfile");
+        brooklynProperties.put("brooklyn.location.jclouds.publicKeyFile", "mypublickeyfile");
+        brooklynProperties.put("brooklyn.location.jclouds.privateKeyData", "myprivateKeyData");
+        brooklynProperties.put("brooklyn.location.jclouds.publicKeyData", "myPublicKeyData");
+        brooklynProperties.put("brooklyn.location.jclouds.privateKeyPassphrase", "myprivateKeyPassphrase");
+        Map<String, Object> conf = resolve("jclouds:aws-ec2").config().getBag().getAllConfig();
+
+        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
+        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
+        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
+        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
+        assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
+    }
+
+    @Test
+    public void testJcloudsTakesDeprecatedProperties() {
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.private-key-file", "myprivatekeyfile");
+        brooklynProperties.put("brooklyn.location.jclouds.public-key-file", "mypublickeyfile");
+        brooklynProperties.put("brooklyn.location.jclouds.private-key-data", "myprivateKeyData");
+        brooklynProperties.put("brooklyn.location.jclouds.public-key-data", "myPublicKeyData");
+        brooklynProperties.put("brooklyn.location.jclouds.private-key-passphrase", "myprivateKeyPassphrase");
+        brooklynProperties.put("brooklyn.location.jclouds.image-id", "myimageid");
+        Map<String, Object> conf = resolve("jclouds:aws-ec2").config().getBag().getAllConfig();
+
+        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
+        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
+        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
+        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
+        assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
+        assertEquals(conf.get("imageId"), "myimageid");
+    }
+
+    @Test
+    public void testJcloudsPropertiesPrecedence() {
+        brooklynProperties.put("brooklyn.location.named.myaws-ec2", "jclouds:aws-ec2");
+
+        // prefer those in "named" over everything else
+        brooklynProperties.put("brooklyn.location.named.myaws-ec2.privateKeyFile", "privateKeyFile-inNamed");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.privateKeyFile", "privateKeyFile-inProviderSpecific");
+        brooklynProperties.put("brooklyn.location.jclouds.privateKeyFile", "privateKeyFile-inJcloudsGeneric");
+
+        // prefer those in provider-specific over generic
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.publicKeyFile", "publicKeyFile-inProviderSpecific");
+        brooklynProperties.put("brooklyn.location.jclouds.publicKeyFile", "publicKeyFile-inJcloudsGeneric");
+
+        // prefer deprecated properties in "named" over those less specific
+        brooklynProperties.put("brooklyn.location.named.myaws-ec2.private-key-data", "privateKeyData-inNamed");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.privateKeyData", "privateKeyData-inProviderSpecific");
+        brooklynProperties.put("brooklyn.location.jclouds.privateKeyData", "privateKeyData-inJcloudsGeneric");
+
+        // prefer generic if nothing else
+        brooklynProperties.put("brooklyn.location.jclouds.publicKeyData", "publicKeyData-inJcloudsGeneric");
+
+        // prefer "named" over everything else: confirm deprecated don't get
+        // transformed to overwrite it accidentally
+        brooklynProperties
+                .put("brooklyn.location.named.myaws-ec2.privateKeyPassphrase", "privateKeyPassphrase-inNamed");
+        brooklynProperties.put("brooklyn.location.jclouds.aws-ec2.private-key-passphrase",
+                "privateKeyPassphrase-inProviderSpecific");
+        brooklynProperties.put("brooklyn.location.jclouds.private-key-passphrase", "privateKeyPassphrase-inJcloudsGeneric");
+
+        Map<String, Object> conf = resolve("named:myaws-ec2").config().getBag().getAllConfig();
+
+        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
+        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inProviderSpecific");
+        assertEquals(conf.get("privateKeyData"), "privateKeyData-inNamed");
+        assertEquals(conf.get("publicKeyData"), "publicKeyData-inJcloudsGeneric");
+        assertEquals(conf.get("privateKeyPassphrase"), "privateKeyPassphrase-inNamed");
+    }
+
+    @Test
+    public void testJcloudsLoads() {
+        Assert.assertTrue(resolve("jclouds:aws-ec2") instanceof JcloudsLocation);
+    }
+
+    @Test
+    public void testJcloudsImplicitLoads() {
+        Assert.assertTrue(resolve("aws-ec2") instanceof JcloudsLocation);
+    }
+
+    @Test
+    public void testJcloudsLocationLoads() {
+        Assert.assertTrue(resolve("aws-ec2:eu-west-1") instanceof JcloudsLocation);
+    }
+
+    @Test
+    public void testJcloudsRegionOnlyLoads() {
+        Assert.assertTrue(resolve("eu-west-1") instanceof JcloudsLocation);
+    }
+
+    @Test
+    public void testJcloudsEndpointLoads() {
+        JcloudsLocation loc = resolve("jclouds:openstack-nova:http://foo/api");
+        assertEquals(loc.getProvider(), "openstack-nova");
+        assertEquals(loc.getEndpoint(), "http://foo/api");
+    }
+
+    @Test
+    public void testJcloudsEndpointLoadsAsProperty() {
+        brooklynProperties.put("brooklyn.location.jclouds.openstack-nova.endpoint", "myendpoint");
+        JcloudsLocation loc = resolve("jclouds:openstack-nova");
+        // just checking
+        Assert.assertEquals(loc.config().getLocalBag().getStringKey("endpoint"), "myendpoint");
+        Assert.assertEquals(loc.getConfig(CloudLocationConfig.CLOUD_ENDPOINT), "myendpoint");
+        // this is the one we really care about!:
+        assertEquals(loc.getEndpoint(), "myendpoint");
+    }
+
+    @Test
+    public void testJcloudsLegacyRandomProperty() {
+        brooklynProperties.put("brooklyn.location.jclouds.openstack-nova.foo", "bar");
+        JcloudsLocation loc = resolve("jclouds:openstack-nova");
+        Assert.assertEquals(loc.config().getLocalBag().getStringKey("foo"), "bar");
+    }
+
+    @Test
+    public void testJcloudsRandomProperty() {
+        brooklynProperties.put("brooklyn.location.jclouds.openstack-nova.foo", "bar");
+        JcloudsLocation loc = resolve("jclouds:openstack-nova");
+        Assert.assertEquals(loc.config().getLocalBag().getStringKey("foo"), "bar");
+    }
+
+    @Test
+    public void testThrowsOnInvalid() throws Exception {
+        // Tries to treat "wrongprefix" as a cloud provider
+        assertThrows("wrongprefix:aws-ec2:us-east-1", NoSuchElementException.class);
+
+        // no provider
+        assertThrows("jclouds", IllegalArgumentException.class);
+
+        // empty provider
+        assertThrows("jclouds:", IllegalArgumentException.class);
+
+        // invalid provider
+        assertThrows("jclouds:doesnotexist", NoSuchElementException.class);
+    }
+
+    @Test
+    public void testResolvesJclouds() throws Exception {
+        // test with provider + region
+        assertJcloudsEquals(resolve("jclouds:aws-ec2:us-east-1"), "aws-ec2", "us-east-1");
+
+        // test with provider that has no region
+        assertJcloudsEquals(resolve("jclouds:rackspace-cloudservers-uk"), "rackspace-cloudservers-uk", null);
+    }
+
+    @Test
+    public void testJcloudsRegionOverridesParent() {
+        Map<String, Object> conf;
+        
+        brooklynProperties.put("brooklyn.location.named.softlayer-was", "jclouds:softlayer:was01");
+        brooklynProperties.put("brooklyn.location.named.softlayer-was2", "jclouds:softlayer:was01");
+        brooklynProperties.put("brooklyn.location.named.softlayer-was2.region", "was02");
+        conf = resolve("named:softlayer-was").config().getBag().getAllConfig();
+        assertEquals(conf.get("region"), "was01");
+        
+        conf = resolve("named:softlayer-was2").config().getBag().getAllConfig();
+        assertEquals(conf.get("region"), "was02");
+        
+        conf = ((LocationInternal) managementContext.getLocationRegistry().resolve("named:softlayer-was2", MutableMap.of("region", "was03")))
+            .config().getBag().getAllConfig();;
+        assertEquals(conf.get("region"), "was03");
+    }
+    
+    // TODO Visual inspection test that it logs warnings
+    @Test
+    public void testLogsWarnings() throws Exception {
+        assertJcloudsEquals(resolve("jclouds:jclouds:aws-ec2:us-east-1"), "aws-ec2", "us-east-1");
+        assertJcloudsEquals(resolve("us-east-1"), "aws-ec2", "us-east-1");
+
+        // TODO Should we enforce a jclouds prefix? Currently we don't
+        // assertJcloudsEquals(resolve("aws-ec2:us-east-1"), "aws-ec2",
+        // "us-east-1");
+
+    }
+
+    @Test
+    public void testResolvesJcloudsFromNamedOfNamedWithPropertiesOverriddenCorrectly() throws Exception {
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop1", "1");
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop2", "1");
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop3", "1");
+        brooklynProperties.put("brooklyn.location.named.foo", "jclouds:softlayer:138124");
+        brooklynProperties.put("brooklyn.location.named.foo.prop2", "2");
+        brooklynProperties.put("brooklyn.location.named.foo.prop3", "2");
+        brooklynProperties.put("brooklyn.location.named.bar", "named:foo");
+        brooklynProperties.put("brooklyn.location.named.bar.prop3", "3");
+        
+        JcloudsLocation l = resolve("named:bar");
+        assertJcloudsEquals(l, "softlayer", "138124");
+        Assert.assertEquals(l.config().getLocalBag().getStringKey("prop3"), "3");
+        Assert.assertEquals(l.config().getLocalBag().getStringKey("prop2"), "2");
+        Assert.assertEquals(l.config().getLocalBag().getStringKey("prop1"), "1");
+    }
+
+    @Test
+    public void testResolvesListAndMapProperties() throws Exception {
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop1", "[ a, b ]");
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop2", "{ a: 1, b: 2 }");
+        brooklynProperties.put("brooklyn.location.named.foo", "jclouds:softlayer:ams01");
+        
+        JcloudsLocation l = resolve("named:foo");
+        assertJcloudsEquals(l, "softlayer", "ams01");
+        assertEquals(l.config().get(new SetConfigKey<String>(String.class, "prop1")), MutableSet.of("a", "b"));
+        assertEquals(l.config().get(new MapConfigKey<String>(String.class, "prop2")), MutableMap.of("a", 1, "b", 2));
+    }
+    
+    @Test
+    public void testResolvesListAndMapPropertiesWithoutMergeOnInheritance() throws Exception {
+        // when we have a yaml way to specify config we may wish to have different semantics;
+        // it could depend on the collection config key whether to merge on inheritance
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop1", "[ a, b ]");
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop2", "{ a: 1, b: 2 }");
+        brooklynProperties.put("brooklyn.location.named.foo", "jclouds:softlayer:ams01");
+        
+        brooklynProperties.put("brooklyn.location.named.foo.prop1", "[ a: 1, c: 3 ]");
+        brooklynProperties.put("brooklyn.location.named.foo.prop2", "{ b: 3, c: 3 }");
+        brooklynProperties.put("brooklyn.location.named.bar", "named:foo");
+        brooklynProperties.put("brooklyn.location.named.bar.prop2", "{ c: 4, d: 4 }");
+        
+        // these do NOT affect the maps
+        brooklynProperties.put("brooklyn.location.named.foo.prop2.z", "9");
+        brooklynProperties.put("brooklyn.location.named.foo.prop3.z", "9");
+        
+        JcloudsLocation l = resolve("named:bar");
+        assertJcloudsEquals(l, "softlayer", "ams01");
+        
+        Set<? extends String> prop1 = l.config().get(new SetConfigKey<String>(String.class, "prop1"));
+        log.info("prop1: "+prop1);
+        assertEquals(prop1, MutableSet.of("a: 1", "c: 3"));
+        
+        Map<String, String> prop2 = l.config().get(new MapConfigKey<String>(String.class, "prop2"));
+        log.info("prop2: "+prop2);
+        assertEquals(prop2, MutableMap.of("c", 4, "d", 4));
+        
+        Map<String, String> prop3 = l.config().get(new MapConfigKey<String>(String.class, "prop3"));
+        log.info("prop3: "+prop3);
+        assertEquals(prop3, null);
+    }
+
+    private void assertJcloudsEquals(JcloudsLocation loc, String expectedProvider, String expectedRegion) {
+        assertEquals(loc.getProvider(), expectedProvider);
+        assertEquals(loc.getRegion(), expectedRegion);
+    }
+
+    private void assertThrows(String val, Class<?> expectedExceptionType) throws Exception {
+        try {
+            resolve(val);
+            fail();
+        } catch (Exception e) {
+            if (!expectedExceptionType.isInstance(e))
+                throw e; // otherwise success
+
+        }
+    }
+
+    @Test(expectedExceptions = { NoSuchElementException.class, IllegalArgumentException.class }, expectedExceptionsMessageRegExp = ".*insufficient.*")
+    public void testJcloudsOnlyFails() {
+        resolve("jclouds");
+    }
+
+    private JcloudsLocation resolve(String spec) {
+        return (JcloudsLocation) managementContext.getLocationRegistry().resolve(spec);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
new file mode 100644
index 0000000..f173646
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.jclouds;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.util.config.ConfigBag;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.ec2.domain.BlockDeviceMapping;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class JcloudsLocationTemplateOptionsCustomisersLiveTest extends AbstractJcloudsLiveTest {
+
+    private static final String LOCATION_SPEC = AWS_EC2_PROVIDER + ":" + AWS_EC2_USEAST_REGION_NAME;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        jcloudsLocation = resolve(LOCATION_SPEC);
+    }
+
+    // Doesn't actually do much with the cloud, but jclouds requires identity and credential before it will work
+    @Test(groups = "Live")
+    public void testGeneralPurposeTemplateOptionCustomisation() throws Exception {
+        ConfigKey<Map<String, Object>> key = JcloudsLocationConfig.TEMPLATE_OPTIONS;
+
+        ConfigBag config = ConfigBag.newInstance()
+                .configure(key, ImmutableMap.of("iamInstanceProfileName", (Object)"helloworld"));
+        AWSEC2TemplateOptions templateOptions = jcloudsLocation.getComputeService().templateOptions().as(AWSEC2TemplateOptions.class);
+
+        invokeCustomizeTemplateOptions(templateOptions, JcloudsLocationConfig.TEMPLATE_OPTIONS, config);
+
+        assertEquals(templateOptions.getIAMInstanceProfileName(), "helloworld");
+    }
+
+    // Doesn't actually do much with the cloud, but jclouds requires identity and credential before it will work
+    @Test(groups = "Live")
+    public void testGeneralPurposeTemplateOptionCustomisationWithList() throws Exception {
+        ConfigKey<Map<String, Object>> key = JcloudsLocationConfig.TEMPLATE_OPTIONS;
+
+        ConfigBag config = ConfigBag.newInstance()
+                        .configure(key, ImmutableMap.of(
+                                "iamInstanceProfileName", (Object) "helloworld",
+                                "mapNewVolumeToDeviceName", (Object) ImmutableList.of("/dev/sda1/", 123, true)));
+        AWSEC2TemplateOptions templateOptions = jcloudsLocation.getComputeService().templateOptions().as(AWSEC2TemplateOptions.class);
+
+        invokeCustomizeTemplateOptions(templateOptions, JcloudsLocationConfig.TEMPLATE_OPTIONS, config);
+
+        assertEquals(templateOptions.getIAMInstanceProfileName(), "helloworld");
+        assertEquals(templateOptions.getBlockDeviceMappings().size(), 1);
+        BlockDeviceMapping blockDeviceMapping = templateOptions.getBlockDeviceMappings().iterator().next();
+        assertEquals(blockDeviceMapping.getDeviceName(), "/dev/sda1/");
+        assertEquals(blockDeviceMapping.getEbsVolumeSize(), (Integer)123);
+        assertTrue(blockDeviceMapping.getEbsDeleteOnTermination());
+    }
+
+    /**
+     * Invoke a specific template options customizer on a TemplateOptions instance.
+     *
+     * @param templateOptions the TemplateOptions instance that you expect the customizer to modify.
+     * @param keyToTest the config key that identifies the customizer. This must be present in both @{code locationConfig} and @{link JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES}.
+     * @param locationConfig simulated configuration for the location. This must contain at least an entry for @{code keyToTest}.
+     */
+    private void invokeCustomizeTemplateOptions(TemplateOptions templateOptions, ConfigKey<?> keyToTest, ConfigBag locationConfig) {
+        checkNotNull(templateOptions, "templateOptions");
+        checkNotNull(keyToTest, "keyToTest");
+        checkNotNull(locationConfig, "locationConfig");
+        checkState(JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.containsKey(keyToTest),
+                "SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES does not contain a customiser for the key " + keyToTest.getName());
+        checkState(locationConfig.containsKey(keyToTest),
+                "location config does not contain the key " + keyToTest.getName());
+
+        JcloudsLocation.CustomizeTemplateOptions code = JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.get(keyToTest);
+        code.apply(templateOptions, locationConfig, locationConfig.get(keyToTest));
+    }
+
+    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/JcloudsLocationTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
new file mode 100644
index 0000000..4c5788f
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
@@ -0,0 +1,510 @@
+/*
+ * 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.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.location.MachineLocationCustomizer;
+import org.apache.brooklyn.location.cloud.names.CustomMachineNamer;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.jclouds.scriptbuilder.domain.OsFamily;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.mockito.Mockito;
+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 com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.location.basic.LocationConfigKeys;
+import org.apache.brooklyn.location.geo.HostGeoInfo;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation.UserCreation;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+
+/**
+ * @author Shane Witbeck
+ */
+public class JcloudsLocationTest implements JcloudsLocationConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(JcloudsLocationTest.class);
+    
+    public static Predicate<ConfigBag> checkerFor(final String user, final Integer minRam, final Integer minCores) {
+        return new Predicate<ConfigBag>() {
+            @Override
+            public boolean apply(@Nullable ConfigBag input) {
+                Assert.assertEquals(input.get(USER), user);
+                Assert.assertEquals(input.get(MIN_RAM), minRam);
+                Assert.assertEquals(input.get(MIN_CORES), minCores);
+                return true;
+            }
+        };
+    }
+    
+    public static Predicate<ConfigBag> templateCheckerFor(final String ports) {
+        return new Predicate<ConfigBag>() {
+            @Override
+            public boolean apply(@Nullable ConfigBag input) {
+                Assert.assertEquals(input.get(INBOUND_PORTS), ports);
+                return false;
+            }
+        };
+    }
+    
+    private LocalManagementContext managementContext;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance(BrooklynProperties.Factory.builderEmpty().build());
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearUp() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+    
+    @Test
+    public void testCreateWithFlagsDirectly() throws Exception {
+        BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+        jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 2), checkerFor("fred", 16, 2));
+    }
+
+    @Test
+    public void testCreateWithFlagsDirectlyAndOverride() throws Exception {
+        BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+        jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 2, MIN_RAM, 8), checkerFor("fred", 8, 2));
+    }
+
+    @Test
+    public void testCreateWithFlagsSubLocation() throws Exception {
+        BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+        jcl = (BailOutJcloudsLocation) jcl.newSubLocation(MutableMap.of(USER, "jon", MIN_CORES, 2));
+        jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 3), checkerFor("jon", 16, 3));
+    }
+
+    @Test
+    public void testStringListToIntArray() {
+        String listString = "[1, 2, 3, 4]";
+        int[] intArray = new int[] {1, 2, 3, 4};
+        Assert.assertEquals(JcloudsLocation.toIntArray(listString), intArray);
+    }
+    
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testMalformedStringListToIntArray() {
+        String listString = "1, 2, 3, 4";
+        JcloudsLocation.toIntArray(listString);
+    }
+    
+    @Test
+    public void testEmptyStringListToIntArray() {
+        String listString = "[]";
+        int[] intArray = new int[] {};
+        Assert.assertEquals(JcloudsLocation.toIntArray(listString), intArray);
+    }
+    
+    @Test
+    public void testIntArrayToIntArray() {
+        int[] intArray = new int[] {1, 2, 3, 4};
+        Assert.assertEquals(JcloudsLocation.toIntArray(intArray), intArray);
+    }
+    
+    @Test
+    public void testObjectArrayToIntArray() {
+        Object[] longArray = new Object[] {1, 2, 3, 4};
+        int[] intArray = new int[] {1, 2, 3, 4};
+        Assert.assertEquals(JcloudsLocation.toIntArray(longArray), intArray);
+    }
+    
+    @Test(expectedExceptions = ClassCastException.class)
+    public void testInvalidObjectArrayToIntArray() {
+        String[] stringArray = new String[] {"1", "2", "3"};
+        JcloudsLocation.toIntArray(stringArray);
+    }
+
+    @Test
+    public void testVMCreationIsRetriedOnFailure() {
+        final AtomicInteger count = new AtomicInteger();
+        Function<ConfigBag, Void> countingInterceptor = new Function<ConfigBag, Void>() {
+            @Override public Void apply(ConfigBag input) {
+                count.incrementAndGet();
+                return null;
+            }
+        };
+        BailOutJcloudsLocation loc = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+                MACHINE_CREATE_ATTEMPTS, 3,
+                BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, countingInterceptor));
+        loc.tryObtain();
+        Assert.assertEquals(count.get(), 3);
+    }
+
+    @Test(groups={"Live", "Live-sanity"})
+    public void testCreateWithInboundPorts() {
+        BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocationForLiveTest(managementContext);
+        jcloudsLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+        jcloudsLocation.tryObtainAndCheck(MutableMap.of(), templateCheckerFor("[22, 80, 9999]"));
+        int[] ports = new int[] {22, 80, 9999};
+        Assert.assertEquals(jcloudsLocation.getTemplate().getOptions().getInboundPorts(), ports);
+    }
+    
+    @Test(groups={"Live", "Live-sanity"})
+    public void testCreateWithInboundPortsOverride() {
+        BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocationForLiveTest(managementContext);
+        jcloudsLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+        jcloudsLocation.tryObtainAndCheck(MutableMap.of(INBOUND_PORTS, "[23, 81, 9998]"), templateCheckerFor("[23, 81, 9998]"));
+        int[] ports = new int[] {23, 81, 9998};
+        Assert.assertEquals(jcloudsLocation.getTemplate().getOptions().getInboundPorts(), ports);
+    }
+
+    @Test
+    public void testCreateWithMaxConcurrentCallsUnboundedByDefault() throws Exception {
+        final int numCalls = 20;
+        ConcurrencyTracker interceptor = new ConcurrencyTracker();
+        ExecutorService executor = Executors.newCachedThreadPool();
+
+        try {
+            final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                    managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+                            BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor));
+            for (int i = 0; i < numCalls; i++) {
+                executor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        jcloudsLocation.tryObtain();
+                    }
+                });
+            }
+            interceptor.assertCallCountEventually(numCalls);
+            interceptor.unblock();
+            executor.shutdown();
+            executor.awaitTermination(10, TimeUnit.SECONDS);
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    @Test(groups="Integration") // because takes 1 sec
+    public void testCreateWithMaxConcurrentCallsRespectsConfig() throws Exception {
+        final int numCalls = 4;
+        final int maxConcurrentCreations = 2;
+        ConcurrencyTracker interceptor = new ConcurrencyTracker();
+        ExecutorService executor = Executors.newCachedThreadPool();
+        
+        try {
+            final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                    managementContext, ImmutableMap.of(
+                            BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor,
+                            MAX_CONCURRENT_MACHINE_CREATIONS, maxConcurrentCreations));
+
+            for (int i = 0; i < numCalls; i++) {
+                executor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        jcloudsLocation.tryObtain();
+                    }
+                });
+            }
+
+            interceptor.assertCallCountEventually(maxConcurrentCreations);
+            interceptor.assertCallCountContinually(maxConcurrentCreations);
+
+            interceptor.unblock();
+            interceptor.assertCallCountEventually(numCalls);
+            executor.shutdown();
+            executor.awaitTermination(10, TimeUnit.SECONDS);
+
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    @Test(groups="Integration") // because takes 1 sec
+    public void testCreateWithMaxConcurrentCallsAppliesToSubLocations() throws Exception {
+        final int numCalls = 4;
+        final int maxConcurrentCreations = 2;
+        ConcurrencyTracker interceptor = new ConcurrencyTracker();
+        ExecutorService executor = Executors.newCachedThreadPool();
+
+        try {
+            final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                    managementContext, ImmutableMap.of(
+                            BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor,
+                            MAX_CONCURRENT_MACHINE_CREATIONS, maxConcurrentCreations));
+
+            for (int i = 0; i < numCalls; i++) {
+                final BailOutJcloudsLocation subLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+                executor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        subLocation.tryObtain();
+                    }
+                });
+            }
+
+            interceptor.assertCallCountEventually(maxConcurrentCreations);
+            interceptor.assertCallCountContinually(maxConcurrentCreations);
+
+            interceptor.unblock();
+            interceptor.assertCallCountEventually(numCalls);
+            executor.shutdown();
+            executor.awaitTermination(10, TimeUnit.SECONDS);
+
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+    
+    @Test
+    public void testCreateWithCustomMachineNamer() {
+        final String machineNamerClass = CustomMachineNamer.class.getName();
+        BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+                        LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass));
+        jcloudsLocation.tryObtainAndCheck(ImmutableMap.of(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored"), new Predicate<ConfigBag>() {
+            public boolean apply(ConfigBag input) {
+                Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
+                return true;
+            }
+        });
+    }
+    
+    @Test
+    public void testCreateWithCustomMachineNamerOnObtain() {
+        final String machineNamerClass = CustomMachineNamer.class.getName();
+        BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+        ImmutableMap<ConfigKey<String>, String> flags = ImmutableMap.of(
+                CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored",
+                LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass);
+        jcloudsLocation.tryObtainAndCheck(flags, new Predicate<ConfigBag>() {
+            public boolean apply(ConfigBag input) {
+                Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
+                return true;
+            }
+        });
+    }
+
+    public static class ConcurrencyTracker implements Function<ConfigBag,Void> {
+        final AtomicInteger concurrentCallsCounter = new AtomicInteger();
+        final CountDownLatch continuationLatch = new CountDownLatch(1);
+        
+        @Override public Void apply(ConfigBag input) {
+            concurrentCallsCounter.incrementAndGet();
+            try {
+                continuationLatch.await();
+            } catch (InterruptedException e) {
+                throw Exceptions.propagate(e);
+            }
+            return null;
+        }
+        
+        public void unblock() {
+            continuationLatch.countDown();
+        }
+
+        public void assertCallCountEventually(final int expected) {
+            Asserts.succeedsEventually(new Runnable() {
+                @Override public void run() {
+                    Assert.assertEquals(concurrentCallsCounter.get(), expected);
+                }
+            });
+        }
+        
+        public void assertCallCountContinually(final int expected) {
+            Asserts.succeedsContinually(new Runnable() {
+                @Override public void run() {
+                    Assert.assertEquals(concurrentCallsCounter.get(), expected);
+                }
+            });
+        }
+    }
+
+    
+    @SuppressWarnings("serial")
+    public static class FakeLocalhostWithParentJcloudsLocation extends JcloudsLocation {
+        public static final ConfigKey<Function<ConfigBag,Void>> BUILD_TEMPLATE_INTERCEPTOR = ConfigKeys.newConfigKey(new TypeToken<Function<ConfigBag,Void>>() {}, "buildtemplateinterceptor");
+        
+        ConfigBag lastConfigBag;
+
+        public FakeLocalhostWithParentJcloudsLocation() {
+            super();
+        }
+
+        public FakeLocalhostWithParentJcloudsLocation(Map<?, ?> conf) {
+            super(conf);
+        }
+
+        @Override
+        public JcloudsSshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+            JcloudsSshMachineLocation result = getManagementContext().getLocationManager().createLocation(LocationSpec.create(JcloudsSshMachineLocation.class)
+                .configure("address", "127.0.0.1") 
+                .configure("port", 22) 
+                .configure("user", "bob")
+                .configure("jcloudsParent", this));
+            registerJcloudsMachineLocation("bogus", result);
+            
+            // explicitly invoke this customizer, to comply with tests
+            for (JcloudsLocationCustomizer customizer : getCustomizers(config().getBag())) {
+                customizer.customize(this, null, (JcloudsMachineLocation)result);
+            }
+            for (MachineLocationCustomizer customizer : getMachineCustomizers(config().getBag())) {
+                customizer.customize((JcloudsMachineLocation)result);
+            }
+
+            return result;
+        }
+        
+        @Override
+        protected void releaseNode(String instanceId) {
+            // no-op
+        }
+    }
+
+    @Test
+    public void testInheritsGeo() throws Exception {
+        ConfigBag allConfig = ConfigBag.newInstance()
+            .configure(IMAGE_ID, "bogus")
+            .configure(CLOUD_PROVIDER, "aws-ec2")
+            .configure(CLOUD_REGION_ID, "bogus")
+            .configure(ACCESS_IDENTITY, "bogus")
+            .configure(ACCESS_CREDENTIAL, "bogus")
+            .configure(LocationConfigKeys.LATITUDE, 42d)
+            .configure(LocationConfigKeys.LONGITUDE, -20d)
+            .configure(MACHINE_CREATE_ATTEMPTS, 1);
+        FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
+        MachineLocation l = ll.obtain();
+        log.info("loc:" +l);
+        HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+        log.info("geo: "+geo);
+        Assert.assertEquals(geo.latitude, 42d, 0.00001);
+        Assert.assertEquals(geo.longitude, -20d, 0.00001);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testInheritsGeoFromLocationMetadataProperties() throws Exception {
+        // in location-metadata.properties:
+//        brooklyn.location.jclouds.softlayer@wdc01.latitude=38.909202
+//        brooklyn.location.jclouds.softlayer@wdc01.longitude=-77.47314
+        ConfigBag allConfig = ConfigBag.newInstance()
+            .configure(IMAGE_ID, "bogus")
+            .configure(CLOUD_PROVIDER, "softlayer")
+            .configure(CLOUD_REGION_ID, "wdc01")
+            .configure(ACCESS_IDENTITY, "bogus")
+            .configure(ACCESS_CREDENTIAL, "bogus")
+            .configure(MACHINE_CREATE_ATTEMPTS, 1);
+        FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class)
+            .configure(new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties("softlayer", "wdc01", null, managementContext.getBrooklynProperties()))
+            .configure(allConfig.getAllConfig()));
+        MachineLocation l = ll.obtain();
+        log.info("loc:" +l);
+        HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+        log.info("geo: "+geo);
+        Assert.assertEquals(geo.latitude, 38.909202d, 0.00001);
+        Assert.assertEquals(geo.longitude, -77.47314d, 0.00001);
+    }
+
+    @Test
+    public void testInvokesCustomizerCallbacks() throws Exception {
+        JcloudsLocationCustomizer customizer = Mockito.mock(JcloudsLocationCustomizer.class);
+        MachineLocationCustomizer machineCustomizer = Mockito.mock(MachineLocationCustomizer.class);
+//        Mockito.when(customizer.customize(Mockito.any(JcloudsLocation.class), Mockito.any(ComputeService.class), Mockito.any(JcloudsSshMachineLocation.class)));
+        ConfigBag allConfig = ConfigBag.newInstance()
+            .configure(CLOUD_PROVIDER, "aws-ec2")
+            .configure(ACCESS_IDENTITY, "bogus")
+            .configure(ACCESS_CREDENTIAL, "bogus")
+            .configure(JcloudsLocationConfig.JCLOUDS_LOCATION_CUSTOMIZERS, ImmutableList.of(customizer))
+            .configure(JcloudsLocation.MACHINE_LOCATION_CUSTOMIZERS, ImmutableList.of(machineCustomizer))
+            .configure(MACHINE_CREATE_ATTEMPTS, 1);
+        FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
+        JcloudsMachineLocation l = (JcloudsMachineLocation)ll.obtain();
+        Mockito.verify(customizer, Mockito.times(1)).customize(ll, null, l);
+        Mockito.verify(customizer, Mockito.never()).preRelease(l);
+        Mockito.verify(customizer, Mockito.never()).postRelease(l);
+        Mockito.verify(machineCustomizer, Mockito.times(1)).customize(l);
+        Mockito.verify(machineCustomizer, Mockito.never()).preRelease(l);
+        
+        ll.release(l);
+        Mockito.verify(customizer, Mockito.times(1)).preRelease(l);
+        Mockito.verify(customizer, Mockito.times(1)).postRelease(l);
+        Mockito.verify(machineCustomizer, Mockito.times(1)).preRelease(l);
+    }
+
+    // now test creating users
+    
+    protected String getCreateUserStatementsFor(Map<ConfigKey<?>,?> config) {
+        BailOutJcloudsLocation jl = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                managementContext, MutableMap.<ConfigKey<?>, Object>builder()
+                        .put(JcloudsLocationConfig.LOGIN_USER, "root").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "m0ck")
+                        .put(JcloudsLocationConfig.USER, "bob").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "b0b")
+                        .putAll(config).build());
+
+        UserCreation creation = jl.createUserStatements(null, jl.config().getBag());
+        return new StatementList(creation.statements).render(OsFamily.UNIX);
+    }
+    
+    @Test
+    public void testDisablesRoot() {
+        String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of());
+        Assert.assertTrue(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+        Assert.assertTrue(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+    }
+
+    @Test
+    public void testDisableRootFalse() {
+        String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of(
+                JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH, false));
+        Assert.assertFalse(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+        Assert.assertTrue(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+    }
+    
+    @Test
+    public void testDisableRootAndSudoFalse() {
+        String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of(
+            JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH, false,
+            JcloudsLocationConfig.GRANT_USER_SUDO, false));
+        Assert.assertFalse(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+        Assert.assertFalse(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+    }
+
+    // TODO more tests, where flags come in from resolver, named locations, etc
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLoginLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLoginLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLoginLiveTest.java
new file mode 100644
index 0000000..5a17a5a
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLoginLiveTest.java
@@ -0,0 +1,407 @@
+/*
+ * 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 java.io.File;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests different login options for ssh keys, passwords, etc.
+ */
+public class JcloudsLoginLiveTest extends AbstractJcloudsLiveTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JcloudsLoginLiveTest.class);
+
+    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 == null ? "" : ":" + AWS_EC2_REGION_NAME);
+    
+    // Image: {id=us-east-1/ami-7d7bfc14, providerId=ami-7d7bfc14, name=RightImage_CentOS_6.3_x64_v5.8.8.5, location={scope=REGION, id=us-east-1, description=us-east-1, parent=aws-ec2, iso3166Codes=[US-VA]}, os={family=centos, arch=paravirtual, version=6.0, description=rightscale-us-east/RightImage_CentOS_6.3_x64_v5.8.8.5.manifest.xml, is64Bit=true}, description=rightscale-us-east/RightImage_CentOS_6.3_x64_v5.8.8.5.manifest.xml, version=5.8.8.5, status=AVAILABLE[available], loginUser=root, userMetadata={owner=411009282317, rootDeviceType=instance-store, virtualizationType=paravirtual, hypervisor=xen}}
+    public static final String AWS_EC2_CENTOS_IMAGE_ID = "us-east-1/ami-7d7bfc14";
+
+    // Image: {id=us-east-1/ami-d0f89fb9, providerId=ami-d0f89fb9, name=ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20130411.1, location={scope=REGION, id=us-east-1, description=us-east-1, parent=aws-ec2, iso3166Codes=[US-VA]}, os={family=ubuntu, arch=paravirtual, version=12.04, description=099720109477/ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20130411.1, is64Bit=true}, description=099720109477/ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20130411.1, version=20130411.1, status=AVAILABLE[available], loginUser=ubuntu, userMetadata={owner=099720109477, rootDeviceType=ebs, virtualizationType=paravirtual, hypervisor=xen}}
+    public static final String AWS_EC2_UBUNTU_IMAGE_ID = "us-east-1/ami-d0f89fb9";
+    
+    // Image: {id=us-east-1/ami-5e008437, providerId=ami-5e008437, name=RightImage_Ubuntu_10.04_x64_v5.8.8.3, location={scope=REGION, id=us-east-1, description=us-east-1, parent=aws-ec2, iso3166Codes=[US-VA]}, os={family=ubuntu, arch=paravirtual, version=10.04, description=rightscale-us-east/RightImage_Ubuntu_10.04_x64_v5.8.8.3.manifest.xml, is64Bit=true}, description=rightscale-us-east/RightImage_Ubuntu_10.04_x64_v5.8.8.3.manifest.xml, version=5.8.8.3, status=AVAILABLE[available], loginUser=root, userMetadata={owner=411009282317, rootDeviceType=instance-store, virtualizationType=paravirtual, hypervisor=xen}}
+    // Uses "root" as loginUser
+    public static final String AWS_EC2_UBUNTU_10_IMAGE_ID = "us-east-1/ami-5e008437";
+
+    public static final String RACKSPACE_LOCATION_SPEC = "jclouds:" + RACKSPACE_PROVIDER;
+    
+    // Image: {id=LON/c52a0ca6-c1f2-4cd1-b7d6-afbcd1ebda22, providerId=c52a0ca6-c1f2-4cd1-b7d6-afbcd1ebda22, name=CentOS 6.0, location={scope=ZONE, id=LON, description=LON, parent=rackspace-cloudservers-uk, iso3166Codes=[GB-SLG]}, os={family=centos, name=CentOS 6.0, version=6.0, description=CentOS 6.0, is64Bit=true}, description=CentOS 6.0, status=AVAILABLE, loginUser=root, userMetadata={os_distro=centos, com.rackspace__1__visible_core=1, com.rackspace__1__build_rackconnect=1, com.rackspace__1__options=0, image_type=base, cache_in_nova=True, com.rackspace__1__source=kickstart, org.openstack__1__os_distro=org.centos, com.rackspace__1__release_build_date=2013-07-25_18-56-29, auto_disk_config=True, com.rackspace__1__release_version=5, os_type=linux, com.rackspace__1__visible_rackconnect=1, com.rackspace__1__release_id=210, com.rackspace__1__visible_managed=0, com.rackspace__1__build_core=1, org.openstack__1__os_version=6.0, org.openstack__1__architecture=x64, com.rackspace__1__build_ma
 naged=0}}
+    public static final String RACKSPACE_CENTOS_IMAGE_NAME_REGEX = "CentOS 6.0";
+    
+    // Image: {id=LON/29fe3e2b-f119-4715-927b-763e99ebe23e, providerId=29fe3e2b-f119-4715-927b-763e99ebe23e, name=Debian 6.06 (Squeeze), location={scope=ZONE, id=LON, description=LON, parent=rackspace-cloudservers-uk, iso3166Codes=[GB-SLG]}, os={family=debian, name=Debian 6.06 (Squeeze), version=6.0, description=Debian 6.06 (Squeeze), is64Bit=true}, description=Debian 6.06 (Squeeze), status=AVAILABLE, loginUser=root, userMetadata={os_distro=debian, com.rackspace__1__visible_core=1, com.rackspace__1__build_rackconnect=1, com.rackspace__1__options=0, image_type=base, cache_in_nova=True, com.rackspace__1__source=kickstart, org.openstack__1__os_distro=org.debian, com.rackspace__1__release_build_date=2013-08-06_13-05-36, auto_disk_config=True, com.rackspace__1__release_version=4, os_type=linux, com.rackspace__1__visible_rackconnect=1, com.rackspace__1__release_id=300, com.rackspace__1__visible_managed=0, com.rackspace__1__build_core=1, org.openstack__1__os_version=6.06, org.openstack__1_
 _architecture=x64, com.rackspace__1__build_managed=0}}
+    public static final String RACKSPACE_DEBIAN_IMAGE_NAME_REGEX = "Debian 6";
+    
+    protected JcloudsSshMachineLocation machine;
+    
+    private File privateRsaFile = new File(Os.tidyPath("~/.ssh/id_rsa"));
+    private File privateDsaFile = new File(Os.tidyPath("~/.ssh/id_dsa"));
+    private File privateRsaFileTmp = new File(privateRsaFile.getAbsoluteFile()+".tmp");
+    private File privateDsaFileTmp = new File(privateDsaFile.getAbsoluteFile()+".tmp");
+    private File publicRsaFile = new File(Os.tidyPath("~/.ssh/id_rsa.pub"));
+    private File publicDsaFile = new File(Os.tidyPath("~/.ssh/id_dsa.pub"));
+    private File publicRsaFileTmp = new File(publicRsaFile.getAbsoluteFile()+".tmp");
+    private File publicDsaFileTmp = new File(publicDsaFile.getAbsoluteFile()+".tmp");
+    private boolean privateRsaFileMoved;
+    private boolean privateDsaFileMoved;
+    private boolean publicRsaFileMoved;
+    private boolean publicDsaFileMoved;
+
+    @Test(groups = {"Live"})
+    protected void testAwsEc2SpecifyingJustPrivateSshKeyInDeprecatedForm() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.LEGACY_PRIVATE_KEY_FILE.getName(), "~/.ssh/id_rsa");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        
+        machine = createEc2Machine(ImmutableMap.<String,Object>of());
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+    }
+    
+    @Test(groups = {"Live"})
+    protected void testAwsEc2SpecifyingPrivateAndPublicSshKeyInDeprecatedForm() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.LEGACY_PRIVATE_KEY_FILE.getName(), "~/.ssh/id_rsa");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.LEGACY_PUBLIC_KEY_FILE.getName(), "~/.ssh/id_rsa.pub");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        
+        machine = createEc2Machine(ImmutableMap.<String,Object>of());
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+    }
+
+    // Uses default key files
+    @Test(groups = {"Live"})
+    protected void testAwsEc2SpecifyingNoKeyFiles() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        
+        machine = createEc2Machine(ImmutableMap.<String,Object>of());
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+    }
+    
+    @Test(groups = {"Live"})
+    public void testSpecifyingPasswordAndNoDefaultKeyFilesExist() throws Exception {
+        try {
+            moveSshKeyFiles();
+            
+            brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+            brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PASSWORD.getName(), "mypassword");
+            jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(RACKSPACE_LOCATION_SPEC);
+            
+            machine = createRackspaceMachine(ImmutableMap.of("imageNameRegex", RACKSPACE_DEBIAN_IMAGE_NAME_REGEX));
+            assertSshable(machine);
+            
+            assertSshable(ImmutableMap.builder()
+                    .put("address", machine.getAddress())
+                    .put("user", "myname")
+                    .put(SshMachineLocation.PASSWORD, "mypassword")
+                    .build());
+        } finally {
+            restoreSshKeyFiles();
+        }
+    }
+
+    // Generates and uses a random password
+    @Test(groups = {"Live"})
+    protected void testSpecifyingNothingAndNoDefaultKeyFilesExist() throws Exception {
+        try {
+            moveSshKeyFiles();
+            
+            brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+            jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(RACKSPACE_LOCATION_SPEC);
+            
+            machine = createRackspaceMachine(ImmutableMap.of("imageNameRegex", RACKSPACE_DEBIAN_IMAGE_NAME_REGEX));
+            assertSshable(machine);
+            assertEquals(machine.getUser(), "myname");
+        } finally {
+            restoreSshKeyFiles();
+        }
+    }
+
+    @Test(groups = {"Live"})
+    protected void testSpecifyingPasswordAndSshKeysPrefersKeys() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PRIVATE_KEY_FILE.getName(), "~/.ssh/id_rsa");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PUBLIC_KEY_FILE.getName(), "~/.ssh/id_rsa.pub");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PASSWORD.getName(), "mypassword");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(RACKSPACE_LOCATION_SPEC);
+        
+        machine = createRackspaceMachine(ImmutableMap.of("imageNameRegex", RACKSPACE_DEBIAN_IMAGE_NAME_REGEX));
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PASSWORD, "mypassword")
+                .build());
+    }
+
+    @Test(groups = {"Live"})
+    protected void testSpecifyingPasswordIgnoresDefaultSshKeys() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PASSWORD.getName(), "mypassword");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(RACKSPACE_LOCATION_SPEC);
+        
+        machine = createRackspaceMachine(ImmutableMap.of("imageNameRegex", RACKSPACE_DEBIAN_IMAGE_NAME_REGEX));
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PASSWORD, "mypassword")
+                .build());
+        
+        assertNotSshable(ImmutableMap.builder()
+            .put("address", machine.getAddress())
+            .put("user", "myname")
+            .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+            .build());
+    }
+
+    @Test(groups = {"Live"})
+    protected void testSpecifyingPasswordWithPublicKeyAllowsKeyAccess() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PASSWORD.getName(), "mypassword");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PUBLIC_KEY_FILE.getName(), "~/.ssh/id_rsa.pub");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(RACKSPACE_LOCATION_SPEC);
+        
+        machine = createRackspaceMachine(ImmutableMap.of("imageNameRegex", RACKSPACE_DEBIAN_IMAGE_NAME_REGEX));
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "myname")
+                .put(SshMachineLocation.PASSWORD, "mypassword")
+                .build());
+    }
+
+    // user "root" matches the loginUser=root
+    @Test(groups = {"Live"})
+    protected void testSpecifyingPasswordWhenNoDefaultKeyFilesExistWithRootUser() throws Exception {
+        try {
+            moveSshKeyFiles();
+            
+            brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "root");
+            brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PASSWORD.getName(), "mypassword");
+            jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(RACKSPACE_LOCATION_SPEC);
+            
+            machine = createRackspaceMachine(ImmutableMap.of("imageNameRegex", RACKSPACE_DEBIAN_IMAGE_NAME_REGEX));
+            assertSshable(machine);
+            
+            assertSshable(ImmutableMap.builder()
+                    .put("address", machine.getAddress())
+                    .put("user", "root")
+                    .put(SshMachineLocation.PASSWORD, "mypassword")
+                    .build());
+        } finally {
+            restoreSshKeyFiles();
+        }
+    }
+
+    @Test(groups = {"Live"})
+    protected void testAwsEc2SpecifyingRootUser() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "root");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PRIVATE_KEY_FILE.getName(), "~/.ssh/id_rsa");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PUBLIC_KEY_FILE.getName(), "~/.ssh/id_rsa.pub");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        
+        machine = createEc2Machine(ImmutableMap.<String,Object>of("imageId", AWS_EC2_UBUNTU_10_IMAGE_ID));
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "root")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+    }
+    
+    @Test(groups = {"Live"})
+    protected void testAwsEc2WhenBlankUserSoUsesRootLoginUser() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PRIVATE_KEY_FILE.getName(), "~/.ssh/id_rsa");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PUBLIC_KEY_FILE.getName(), "~/.ssh/id_rsa.pub");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        
+        machine = createEc2Machine(ImmutableMap.<String,Object>of("imageId", AWS_EC2_UBUNTU_10_IMAGE_ID));
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "root")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+    }
+    
+    // In JcloudsLocation.NON_ADDABLE_USERS, "ec2-user" was treated special and was not added!
+    // That was very bad for if someone is running brooklyn on a new AWS VM, and just installs brooklyn+runs as the default ec2-user.
+    @Test(groups = {"Live"})
+    protected void testAwsEc2SpecifyingSpecialUser() throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "ec2-user");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PRIVATE_KEY_FILE.getName(), "~/.ssh/id_rsa");
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.PUBLIC_KEY_FILE.getName(), "~/.ssh/id_rsa.pub");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(AWS_EC2_LOCATION_SPEC);
+        
+        machine = createEc2Machine(ImmutableMap.<String,Object>of("imageId", AWS_EC2_UBUNTU_10_IMAGE_ID));
+        assertSshable(machine);
+        
+        assertSshable(ImmutableMap.builder()
+                .put("address", machine.getAddress())
+                .put("user", "ec2-user")
+                .put(SshMachineLocation.PRIVATE_KEY_FILE, Os.tidyPath("~/.ssh/id_rsa"))
+                .build());
+    }
+    
+    @Override
+    protected void releaseMachine(JcloudsSshMachineLocation machine) {
+        jcloudsLocation.release(machine);
+    }
+    
+    private JcloudsSshMachineLocation createEc2Machine(Map<String,? extends Object> conf) throws Exception {
+        return obtainMachine(MutableMap.<String,Object>builder()
+                .putAll(conf)
+                .putIfAbsent("imageId", AWS_EC2_CENTOS_IMAGE_ID)
+                .putIfAbsent("hardwareId", AWS_EC2_SMALL_HARDWARE_ID)
+                .putIfAbsent("inboundPorts", ImmutableList.of(22))
+                .build());
+    }
+    
+    private JcloudsSshMachineLocation createRackspaceMachine(Map<String,? extends Object> conf) throws Exception {
+        return obtainMachine(MutableMap.<String,Object>builder()
+                .putAll(conf)
+                .putIfAbsent("inboundPorts", ImmutableList.of(22))
+                .build());
+    }
+    
+    protected void assertSshable(Map<?,?> machineConfig) {
+        SshMachineLocation machineWithThatConfig = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure(machineConfig));
+        try {
+            assertSshable(machineWithThatConfig);
+        } finally {
+            Streams.closeQuietly(machineWithThatConfig);
+        }
+    }
+    
+    protected void assertNotSshable(Map<?,?> machineConfig) {
+        try {
+            assertSshable(machineConfig);
+            Assert.fail("ssh should not have succeeded "+machineConfig);
+        } catch (Exception e) {
+            // expected
+            LOG.debug("Exception as expected when testing sshable "+machineConfig);
+        }
+    }
+    
+    private void moveSshKeyFiles() throws Exception {
+        privateRsaFileMoved = false;
+        privateDsaFileMoved = false;
+        publicRsaFileMoved = false;
+        publicDsaFileMoved = false;
+
+        if (privateRsaFile.exists()) {
+            LOG.info("Moving {} to {}", privateRsaFile, privateRsaFileTmp);
+            Runtime.getRuntime().exec("mv "+privateRsaFile.getAbsolutePath()+" "+privateRsaFileTmp.getAbsolutePath());
+            privateRsaFileMoved = true;
+        }
+        if (privateDsaFile.exists()) {
+            LOG.info("Moving {} to {}", privateDsaFile, privateDsaFileTmp);
+            Runtime.getRuntime().exec("mv "+privateDsaFile.getAbsolutePath()+" "+privateDsaFileTmp.getAbsolutePath());
+            privateDsaFileMoved = true;
+        }
+        if (publicRsaFile.exists()) {
+            LOG.info("Moving {} to {}", publicRsaFile, publicRsaFileTmp);
+            Runtime.getRuntime().exec("mv "+publicRsaFile.getAbsolutePath()+" "+publicRsaFileTmp.getAbsolutePath());
+            publicRsaFileMoved = true;
+        }
+        if (publicDsaFile.exists()) {
+            LOG.info("Moving {} to {}", publicDsaFile, publicDsaFileTmp);
+            Runtime.getRuntime().exec("mv "+publicDsaFile.getAbsolutePath()+" "+publicDsaFileTmp.getAbsolutePath());
+            publicDsaFileMoved = true;
+        }
+    }
+    
+    private void restoreSshKeyFiles() throws Exception {
+        if (privateRsaFileMoved) {
+            LOG.info("Restoring {} form {}", privateRsaFile, privateRsaFileTmp);
+            Runtime.getRuntime().exec("mv "+privateRsaFileTmp.getAbsolutePath()+" "+privateRsaFile.getAbsolutePath());
+            privateRsaFileMoved = false;
+        }
+        if (privateDsaFileMoved) {
+            LOG.info("Restoring {} form {}", privateDsaFile, privateDsaFileTmp);
+            Runtime.getRuntime().exec("mv "+privateDsaFileTmp.getAbsolutePath()+" "+privateDsaFile.getAbsolutePath());
+            privateDsaFileMoved = false;
+        }
+        if (publicRsaFileMoved) {
+            LOG.info("Restoring {} form {}", publicRsaFile, publicRsaFileTmp);
+            Runtime.getRuntime().exec("mv "+publicRsaFileTmp.getAbsolutePath()+" "+publicRsaFile.getAbsolutePath());
+            publicRsaFileMoved = false;
+        }
+        if (publicDsaFileMoved) {
+            LOG.info("Restoring {} form {}", publicDsaFile, publicDsaFileTmp);
+            Runtime.getRuntime().exec("mv "+publicDsaFileTmp.getAbsolutePath()+" "+publicDsaFile.getAbsolutePath());
+            publicDsaFileMoved = false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamerTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamerTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamerTest.java
new file mode 100644
index 0000000..5e25a8c
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamerTest.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.jclouds;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.text.Strings;
+
+public class JcloudsMachineNamerTest {
+
+    private static final Logger log = LoggerFactory.getLogger(JcloudsMachineNamerTest.class);
+    
+    @Test
+    public void testGenerateGroupIdInVcloud() {
+        ConfigBag cfg = new ConfigBag()
+            .configure(JcloudsLocationConfig.CLOUD_PROVIDER, "vcloud")
+            .configure(JcloudsLocationConfig.CALLER_CONTEXT, "!mycontext!");
+        
+        String result = new JcloudsMachineNamer().generateNewGroupId(cfg);
+        
+        log.info("test mycontext vcloud group id gives: "+result);
+        // brooklyn-user-!mycontext!-1234
+        // br-<code>-<user>-myco-1234
+        Assert.assertTrue(result.length() <= 24-4-1, "result: "+result);
+        
+        String user = Strings.maxlen(System.getProperty("user.name"), 2).toLowerCase();
+        // (length 2 will happen if user is brooklyn, to avoid brooklyn-brooklyn at start!)
+        Assert.assertTrue(result.indexOf(user) >= 0);
+        Assert.assertTrue(result.indexOf("-myc") >= 0);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynPropertiesTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynPropertiesTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynPropertiesTest.java
new file mode 100644
index 0000000..69eda84
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynPropertiesTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.jclouds;
+
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Maps;
+
+public class JcloudsPropertiesFromBrooklynPropertiesTest {
+    
+    protected static Map<String, Object> sampleProviderOrApiProps() {
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("brooklyn.location.jclouds.FooServers.identity", "bob");
+        map.put("brooklyn.location.jclouds.FooServers.credential", "s3cr3t");
+        map.put("brooklyn.location.jclouds.FooServers.jclouds.ssh.max-retries", "100");
+        return map;
+    }
+
+    protected static Map<String, Object> sampleNamedProps() {
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("brooklyn.location.named.cloudfirst", "jclouds:openstack-nova");
+        map.put("brooklyn.location.named.cloudfirst.identity", "myId");
+        map.put("brooklyn.location.named.cloudfirst.credential", "password");
+        map.put("brooklyn.location.named.cloudfirst.imageId", "RegionOne/1");
+        map.put("brooklyn.location.named.cloudfirst.securityGroups", "universal");
+        return map;
+    }
+
+    protected static Map<String, Object> unsupportedSampleProviderOrApiProps() {
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("brooklyn.location.jclouds.FooServers.image-id", "invalid-image-id");
+        return map;
+    }
+    
+    protected static Map<String, Object> unsupportedNamedProps() {
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("brooklyn.location.named.cloudfirst", "jclouds:openstack-nova");
+        map.put("brooklyn.location.named.cloudfirst.hardware-id", "invalid-hardware-id");
+        return map;
+    }
+    
+    private JcloudsPropertiesFromBrooklynProperties parser;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        parser = new JcloudsPropertiesFromBrooklynProperties();
+    }
+    
+    @Test
+    public void testProviderOrApiProperties() {
+        Map<String, Object> map = parser.getJcloudsProperties("FooServers", null, null, sampleProviderOrApiProps());
+        Assert.assertEquals(map.get("identity"), "bob");
+        Assert.assertEquals(map.get("credential"), "s3cr3t");
+        Assert.assertEquals(map.get("provider"), "FooServers");
+    }
+
+    @Test
+    public void testNamedProperties() {
+        Map<String, Object> map = parser.getJcloudsProperties("openstack-nova", null, "cloudfirst", sampleNamedProps());
+        Assert.assertEquals(map.get("provider"), "openstack-nova");
+        Assert.assertEquals(map.get("identity"), "myId");
+        Assert.assertEquals(map.get("credential"), "password");
+        Assert.assertEquals(map.get("imageId"), "RegionOne/1");
+        Assert.assertEquals(map.get("securityGroups"), "universal");
+    }
+    
+    @Test
+    public void testOrderOfPreference() {
+        Map<String, Object> allProperties = Maps.newHashMap();
+        allProperties.putAll(sampleProviderOrApiProps());
+        allProperties.putAll(sampleNamedProps());
+        Map<String, Object> map = parser.getJcloudsProperties("openstack-nova", null, "cloudfirst", allProperties);
+        Assert.assertEquals(map.get("provider"), "openstack-nova");
+        Assert.assertEquals(map.get("identity"), "myId");
+        Assert.assertEquals(map.get("credential"), "password");
+        Assert.assertEquals(map.get("imageId"), "RegionOne/1");
+        Assert.assertEquals(map.get("securityGroups"), "universal");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSshingLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSshingLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSshingLiveTest.java
new file mode 100644
index 0000000..48ce9cf
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSshingLiveTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 org.testng.annotations.Test;
+
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Tests the initial ssh command execution (e.g. user creation), using jclouds TemplateOptions
+ * and using just brooklyn.
+ */
+public class JcloudsSshingLiveTest extends AbstractJcloudsLiveTest {
+
+    public static final String SOFTLAYER_REGION_NAME = SOFTLAYER_AMS01_REGION_NAME;
+    public static final String SOTLAYER_LOCATION_SPEC = "jclouds:" + SOFTLAYER_PROVIDER + (SOFTLAYER_REGION_NAME == null ? "" : ":" + SOFTLAYER_REGION_NAME);
+    
+    protected JcloudsSshMachineLocation machine;
+    
+    @Test(groups = {"Live"})
+    public void testCreatesUserUsingJcloudsTemplateOptions() throws Exception {
+        runCreatesUser(true);
+    }
+    
+    @Test(groups = {"Live"})
+    public void testCreatesUserWithoutUsingJcloudsTemplateOptions() throws Exception {
+        runCreatesUser(false);
+    }
+    
+    protected void runCreatesUser(boolean useJcloudsSshInit) throws Exception {
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USE_JCLOUDS_SSH_INIT.getName(), Boolean.toString(useJcloudsSshInit));
+        brooklynProperties.put(BROOKLYN_PROPERTIES_PREFIX+JcloudsLocationConfig.USER.getName(), "myname");
+        jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().resolve(SOTLAYER_LOCATION_SPEC);
+        
+        JcloudsSshMachineLocation machine = obtainMachine(MutableMap.<String,Object>builder()
+                .putIfAbsent("inboundPorts", ImmutableList.of(22))
+                .build());
+        assertSshable(machine);
+        assertEquals(machine.getUser(), "myname");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/LiveTestEntity.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/LiveTestEntity.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/LiveTestEntity.java
new file mode 100644
index 0000000..0ff4474
--- /dev/null
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/LiveTestEntity.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.jclouds;
+
+import java.util.Collection;
+
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.apache.brooklyn.test.entity.TestEntityImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Lifecycle;
+import org.apache.brooklyn.location.MachineProvisioningLocation;
+import org.apache.brooklyn.location.basic.LocationInternal;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+@ImplementedBy(LiveTestEntity.LiveTestEntityImpl.class)
+public interface LiveTestEntity extends TestEntity {
+
+    MachineProvisioningLocation<?> getProvisioningLocation();
+    JcloudsSshMachineLocation getObtainedLocation();
+
+    public static class LiveTestEntityImpl extends TestEntityImpl implements LiveTestEntity {
+
+        private static final Logger LOG = LoggerFactory.getLogger(LiveTestEntityImpl.class);
+        private JcloudsLocation provisioningLocation;
+        private JcloudsSshMachineLocation obtainedLocation;
+
+        @Override
+        public void start(final Collection<? extends Location> locs) {
+            LOG.trace("Starting {}", this);
+            callHistory.add("start");
+            setAttribute(SERVICE_STATE, Lifecycle.STARTING);
+            counter.incrementAndGet();
+            addLocations(locs);
+            provisioningLocation = (JcloudsLocation) Iterables.find(locs, Predicates.instanceOf(JcloudsLocation.class));
+            try {
+                obtainedLocation = (JcloudsSshMachineLocation)provisioningLocation.obtain(((LocationInternal)provisioningLocation).config().getBag().getAllConfig());
+            } catch (NoMachinesAvailableException e) {
+                throw Throwables.propagate(e);
+            }
+            addLocations(ImmutableList.of(obtainedLocation));
+            setAttribute(SERVICE_STATE, Lifecycle.RUNNING);
+        }
+
+        @Override
+        public void stop() {
+            LOG.trace("Stopping {}", this);
+            callHistory.add("stop");
+            setAttribute(SERVICE_STATE, Lifecycle.STOPPING);
+            counter.decrementAndGet();
+            if (provisioningLocation != null && obtainedLocation != null) {
+                provisioningLocation.release(obtainedLocation);
+            }
+            setAttribute(SERVICE_STATE, Lifecycle.STOPPED);
+        }
+
+        public MachineProvisioningLocation<?> getProvisioningLocation() {
+            return provisioningLocation;
+        }
+
+        public JcloudsSshMachineLocation getObtainedLocation() {
+            return obtainedLocation;
+        }
+    }
+
+}