Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 06b14d60d -> 88fbe107c


JcloudsLocation: make more testable

- allow a custom jclouds ComputeService to be injected, so can
  stub out things like VM creation and deletion


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/0d5e1ba9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/0d5e1ba9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/0d5e1ba9

Branch: refs/heads/master
Commit: 0d5e1ba9050d1a3b934ca68a4bc23a68a7450443
Parents: 8489436
Author: Aled Sage <[email protected]>
Authored: Fri Jan 23 11:42:54 2015 +0000
Committer: Aled Sage <[email protected]>
Committed: Mon Jan 26 09:17:39 2015 +0000

----------------------------------------------------------------------
 .../jclouds/ComputeServiceRegistry.java         |  28 +++
 .../jclouds/ComputeServiceRegistryImpl.java     | 175 ++++++++++++++
 .../location/jclouds/JcloudsLocation.java       |  30 ++-
 .../location/jclouds/JcloudsLocationConfig.java |   6 +
 .../brooklyn/location/jclouds/JcloudsUtil.java  | 186 ++++++---------
 .../jclouds/AbstractJcloudsStubbedLiveTest.java | 133 +++++++++++
 .../jclouds/DelegatingComputeService.java       | 229 +++++++++++++++++++
 .../JcloudsHardwareProfilesStubbedLiveTest.java |  78 +++++++
 .../JcloudsLocationRebindMachineLiveTest.java   |   8 +-
 .../location/jclouds/JcloudsMinRamLiveTest.java |  59 -----
 10 files changed, 740 insertions(+), 192 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistry.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistry.java
 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistry.java
new file mode 100644
index 0000000..519fa01
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistry.java
@@ -0,0 +1,28 @@
+/*
+ * 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 brooklyn.location.jclouds;
+
+import org.jclouds.compute.ComputeService;
+
+import brooklyn.util.config.ConfigBag;
+
+public interface ComputeServiceRegistry {
+    
+    public ComputeService findComputeService(ConfigBag conf, boolean 
allowReuse);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistryImpl.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistryImpl.java
 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistryImpl.java
new file mode 100644
index 0000000..a284ea3
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/ComputeServiceRegistryImpl.java
@@ -0,0 +1,175 @@
+/*
+ * 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 brooklyn.location.jclouds;
+
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static 
org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_AMI_QUERY;
+import static 
org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_CC_AMI_QUERY;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jclouds.Constants;
+import org.jclouds.ContextBuilder;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.encryption.bouncycastle.config.BouncyCastleCryptoModule;
+import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.inject.Module;
+
+public class ComputeServiceRegistryImpl implements ComputeServiceRegistry, 
JcloudsLocationConfig {
+    
+    private static final Logger LOG = 
LoggerFactory.getLogger(ComputeServiceRegistryImpl.class);
+
+    public static final ComputeServiceRegistryImpl INSTANCE = new 
ComputeServiceRegistryImpl();
+        
+    protected ComputeServiceRegistryImpl() {
+    }
+    
+    protected final Map<Map<?,?>,ComputeService> cachedComputeServices = new 
ConcurrentHashMap<Map<?,?>,ComputeService>();
+
+    protected final Object createComputeServicesMutex = new Object();
+
+    @Override
+    public ComputeService findComputeService(ConfigBag conf, boolean 
allowReuse) {
+        String provider = checkNotNull(conf.get(CLOUD_PROVIDER), "provider 
must not be null");
+        String identity = checkNotNull(conf.get(ACCESS_IDENTITY), "identity 
must not be null");
+        String credential = checkNotNull(conf.get(ACCESS_CREDENTIAL), 
"credential must not be null");
+        
+        Properties properties = new Properties();
+        properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, 
Boolean.toString(true));
+        properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, 
Boolean.toString(true));
+        properties.setProperty("jclouds.ssh.max-retries", 
conf.getStringKey("jclouds.ssh.max-retries") != null ? 
+                conf.getStringKey("jclouds.ssh.max-retries").toString() : 
"50");
+        // Enable aws-ec2 lazy image fetching, if given a specific imageId; 
otherwise customize for specific owners; or all as a last resort
+        // See https://issues.apache.org/jira/browse/WHIRR-416
+        if ("aws-ec2".equals(provider)) {
+            // TODO convert AWS-only flags to config keys
+            if (groovyTruth(conf.get(IMAGE_ID))) {
+                properties.setProperty(PROPERTY_EC2_AMI_QUERY, "");
+                properties.setProperty(PROPERTY_EC2_CC_AMI_QUERY, "");
+            } else if (groovyTruth(conf.getStringKey("imageOwner"))) {
+                properties.setProperty(PROPERTY_EC2_AMI_QUERY, 
"owner-id="+conf.getStringKey("imageOwner")+";state=available;image-type=machine");
+            } else if (groovyTruth(conf.getStringKey("anyOwner"))) {
+                // set `anyOwner: true` to override the default query (which 
is restricted to certain owners as per below), 
+                // allowing the AMI query to bind to any machine
+                // (note however, we sometimes pick defaults in 
JcloudsLocationFactory);
+                // (and be careful, this can give a LOT of data back, taking 
several minutes,
+                // and requiring extra memory allocated on the command-line)
+                properties.setProperty(PROPERTY_EC2_AMI_QUERY, 
"state=available;image-type=machine");
+                /*
+                 * by default the following filters are applied:
+                 * Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&
+                 * Filter.1.Value.2=063491364108&
+                 * Filter.1.Value.3=099720109477&
+                 * Filter.1.Value.4=411009282317&
+                 * Filter.2.Name=state&Filter.2.Value.1=available&
+                 * Filter.3.Name=image-type&Filter.3.Value.1=machine&
+                 */
+            }
+        }
+
+        // FIXME Deprecated mechanism, should have a ConfigKey for overrides
+        Map<String, Object> extra = Maps.filterKeys(conf.getAllConfig(), 
Predicates.containsPattern("^jclouds\\."));
+        if (extra.size() > 0) {
+            LOG.warn("Jclouds using deprecated property overrides: 
"+Entities.sanitize(extra));
+        }
+        properties.putAll(extra);
+
+        String endpoint = conf.get(CLOUD_ENDPOINT);
+        if (!groovyTruth(endpoint)) endpoint = getDeprecatedProperty(conf, 
Constants.PROPERTY_ENDPOINT);
+        if (groovyTruth(endpoint)) 
properties.setProperty(Constants.PROPERTY_ENDPOINT, endpoint);
+
+        Map<?,?> cacheKey = MutableMap.builder()
+                .putAll(properties)
+                .put("provider", provider)
+                .put("identity", identity)
+                .put("credential", credential)
+                .putIfNotNull("endpoint", endpoint)
+                .build()
+                .asUnmodifiable();
+
+        if (allowReuse) {
+            ComputeService result = cachedComputeServices.get(cacheKey);
+            if (result!=null) {
+                LOG.trace("jclouds ComputeService cache hit for compute 
service, for "+Entities.sanitize(properties));
+                return result;
+            }
+            LOG.debug("jclouds ComputeService cache miss for compute service, 
creating, for "+Entities.sanitize(properties));
+        }
+
+        Iterable<Module> modules = getCommonModules();
+
+        // Synchronizing to avoid deadlock from 
sun.reflect.annotation.AnnotationType.
+        // See https://github.com/brooklyncentral/brooklyn/issues/974
+        ComputeServiceContext computeServiceContext;
+        synchronized (createComputeServicesMutex) {
+            computeServiceContext = ContextBuilder.newBuilder(provider)
+                    .modules(modules)
+                    .credentials(identity, credential)
+                    .overrides(properties)
+                    .build(ComputeServiceContext.class);
+        }
+        final ComputeService computeService = 
computeServiceContext.getComputeService();
+        if (allowReuse) {
+            synchronized (cachedComputeServices) {
+                ComputeService result = cachedComputeServices.get(cacheKey);
+                if (result != null) {
+                    LOG.debug("jclouds ComputeService cache recovery for 
compute service, for "+Entities.sanitize(cacheKey));
+                    //keep the old one, discard the new one
+                    computeService.getContext().close();
+                    return result;
+                }
+                LOG.debug("jclouds ComputeService created "+computeService+", 
adding to cache, for "+Entities.sanitize(properties));
+                cachedComputeServices.put(cacheKey, computeService);
+            }
+        }
+        return computeService;
+     }
+
+    /** returns the jclouds modules we typically install */ 
+    protected ImmutableSet<Module> getCommonModules() {
+        return ImmutableSet.<Module> of(
+                new SshjSshClientModule(), 
+                new SLF4JLoggingModule(),
+                new BouncyCastleCryptoModule());
+    }
+     
+    protected String getDeprecatedProperty(ConfigBag conf, String key) {
+        if (conf.containsKey(key)) {
+            LOG.warn("Jclouds using deprecated brooklyn-jclouds property 
"+key+": "+Entities.sanitize(conf.getAllConfig()));
+            return (String) conf.getStringKey(key);
+        } else {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
index ec9215e..3640117 100644
--- 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
+++ 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
@@ -428,8 +428,10 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         return getComputeService(MutableMap.of());
     }
     public ComputeService getComputeService(Map<?,?> flags) {
-        return JcloudsUtil.findComputeService((flags==null || flags.isEmpty()) 
? getAllConfigBag() :
-            ConfigBag.newInstanceExtending(getAllConfigBag(), flags));
+        ConfigBag conf = (flags==null || flags.isEmpty()) 
+                ? getAllConfigBag()
+                : ConfigBag.newInstanceExtending(getAllConfigBag(), flags);
+        return getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(conf, 
true);
     }
     
     /** @deprecated since 0.7.0 use {@link #listMachines()} */ @Deprecated
@@ -552,7 +554,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         JcloudsPortForwarderExtension portForwarder = 
setup.get(PORT_FORWARDER);
         if (usePortForwarding) checkNotNull(portForwarder, "portForwarder, 
when use-port-forwarding enabled");
 
-        final ComputeService computeService = 
JcloudsUtil.findComputeService(setup);
+        final ComputeService computeService = 
getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(setup, true);
         CloudMachineNamer cloudMachineNamer = getCloudMachineNamer(setup);
         String groupId = elvis(setup.get(GROUP_ID), 
cloudMachineNamer.generateNewGroupId());
         NodeMetadata node = null;
@@ -799,6 +801,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
             throw Exceptions.propagate(e);
         }
     }
+
     
     // ------------- constructing the template, etc ------------------------
     
@@ -1128,7 +1131,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
             // TODO use key
             m1.putStringKey("anyOwner", true);
         }
-        ComputeService computeServiceLessRestrictive = 
JcloudsUtil.findComputeService(m1);
+        ComputeService computeServiceLessRestrictive = 
getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(m1, true);
         Set<? extends Image> imgs = computeServiceLessRestrictive.listImages();
         LOG.info(""+imgs.size()+" available images at "+this);
         for (Image img: imgs) {
@@ -1457,7 +1460,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
                         (rawHostname != null ? rawHostname : "<unspecified>"), 
                         getProvider()});
             
-            ComputeService computeService = 
JcloudsUtil.findComputeService(setup, true);
+            ComputeService computeService = 
getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(setup, true);
             
             Set<? extends NodeMetadata> candidateNodes = 
computeService.listNodesDetailsMatching(new Predicate<ComputeMetadata>() {
                 @Override
@@ -1589,7 +1592,10 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
                     .configureIfNotNull(CLOUD_AVAILABILITY_ZONE_ID, 
nodeAvailabilityZone)
                     .configureIfNotNull(CLOUD_REGION_ID, nodeRegion)
                     .configure(CALLER_CONTEXT, setup.get(CALLER_CONTEXT))
-                    .configure(SshMachineLocation.DETECT_MACHINE_DETAILS, 
setup.get(SshMachineLocation.DETECT_MACHINE_DETAILS)));
+                    .configure(SshMachineLocation.DETECT_MACHINE_DETAILS, 
setup.get(SshMachineLocation.DETECT_MACHINE_DETAILS))
+                    .configureIfNotNull(USE_PORT_FORWARDING, 
setup.get(USE_PORT_FORWARDING))
+                    .configureIfNotNull(PORT_FORWARDER, 
setup.get(PORT_FORWARDER))
+                    .configureIfNotNull(PORT_FORWARDING_MANAGER, 
setup.get(PORT_FORWARDING_MANAGER)));
         } else {
             LOG.warn("Using deprecated JcloudsSshMachineLocation constructor 
because "+this+" is not managed");
             return new JcloudsSshMachineLocation(MutableMap.builder()
@@ -1604,6 +1610,9 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
                     .put("callerContext", setup.get(CALLER_CONTEXT))
                     .putIfNotNull(CLOUD_AVAILABILITY_ZONE_ID.getName(), 
nodeAvailabilityZone)
                     .putIfNotNull(CLOUD_REGION_ID.getName(), nodeRegion)
+                    .put(USE_PORT_FORWARDING, setup.get(USE_PORT_FORWARDING))
+                    .put(PORT_FORWARDER, setup.get(PORT_FORWARDER))
+                    .put(PORT_FORWARDING_MANAGER, 
setup.get(PORT_FORWARDING_MANAGER))
                     .build(),
                     this,
                     node);
@@ -1647,19 +1656,21 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
     @Override
     public void release(SshMachineLocation machine) {
         String instanceId = vmInstanceIds.remove(machine);
-        if (!groovyTruth(instanceId)) {
+        if (instanceId == null) {
+            LOG.info("Attempted release of unknown machine "+machine+" in 
"+toString());
             throw new IllegalArgumentException("Unknown machine "+machine);
         }
         
         LOG.info("Releasing machine {} in {}, instance id {}", new Object[] 
{machine, this, instanceId});
         
         removeChild(machine);
+        
         try {
             releaseNode(instanceId);
         } catch (Exception e) {
             LOG.error("Problem releasing machine "+machine+" in "+this+", 
instance id "+instanceId+
                     "; discarding instance and continuing...", e);
-            Exceptions.propagate(e);
+            throw Exceptions.propagate(e);
         }
     }
 
@@ -1675,7 +1686,6 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         String instanceId = node.getId();
         LOG.info("Releasing node {} in {}, instance id {}", new Object[] 
{node, this, instanceId});
         
-        ComputeService computeService = null;
         try {
             releaseNode(instanceId);
         } catch (Exception e) {
@@ -1687,7 +1697,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
     protected void releaseNode(String instanceId) {
         ComputeService computeService = null;
         try {
-            computeService = JcloudsUtil.findComputeService(getAllConfigBag());
+            computeService = 
getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(getAllConfigBag(), true);
             computeService.destroyNode(instanceId);
         } finally {
         /*

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
index 0346940..4bcebad 100644
--- 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
+++ 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
@@ -230,6 +230,12 @@ public interface JcloudsLocationConfig extends 
CloudLocationConfig {
     public static final ConfigKey<String> OS_VERSION_REGEX = 
ConfigKeys.newStringConfigKey("osVersionRegex", 
         "Regular expression for the OS version to load");
 
+    public static final ConfigKey<ComputeServiceRegistry> 
COMPUTE_SERVICE_REGISTRY = ConfigKeys.newConfigKey(
+            ComputeServiceRegistry.class,
+            "jclouds.computeServiceRegistry",
+            "Registry/Factory for creating jclouds ComputeService; default is 
almost always fine, except where tests want to customize behaviour",
+            ComputeServiceRegistryImpl.INSTANCE);
+    
     // TODO
     
 //  "noDefaultSshKeys" - hints that local ssh keys should not be read as 
defaults

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
index e707495..3016058 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
@@ -18,10 +18,6 @@
  */
 package brooklyn.location.jclouds;
 
-import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static 
org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_AMI_QUERY;
-import static 
org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_CC_AMI_QUERY;
 import static 
org.jclouds.compute.options.RunScriptOptions.Builder.overrideLoginCredentials;
 import static org.jclouds.compute.util.ComputeServiceUtils.execHttpResponse;
 import static org.jclouds.scriptbuilder.domain.Statements.appendFile;
@@ -34,14 +30,11 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
 
-import org.jclouds.Constants;
 import org.jclouds.ContextBuilder;
 import org.jclouds.aws.ec2.AWSEC2Api;
 import org.jclouds.blobstore.BlobStoreContext;
@@ -73,7 +66,6 @@ import org.slf4j.LoggerFactory;
 import brooklyn.entity.basic.Entities;
 import brooklyn.location.jclouds.config.AlwaysRetryOnRenew;
 import brooklyn.util.collections.MutableList;
-import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.net.Protocol;
@@ -86,7 +78,6 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Charsets;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
@@ -102,17 +93,33 @@ public class JcloudsUtil implements JcloudsLocationConfig {
     
     private static final Logger LOG = 
LoggerFactory.getLogger(JcloudsUtil.class);
     
+    /**
+     * @deprecated since 0.7; see {@link BashCommands}
+     */
+    @Deprecated
     public static String APT_INSTALL = "apt-get install -f -y -qq --force-yes";
 
+    /**
+     * @deprecated since 0.7; see {@link BashCommands}
+     */
+    @Deprecated
     public static String installAfterUpdatingIfNotPresent(String cmd) {
        String aptInstallCmd = APT_INSTALL + " " + cmd;
        return String.format("which %s || (%s || (apt-get update && %s))", cmd, 
aptInstallCmd, aptInstallCmd);
     }
 
+    /**
+     * @deprecated since 0.7
+     */
+    @Deprecated
     public static Predicate<NodeMetadata> predicateMatchingById(final 
NodeMetadata node) {
         return predicateMatchingById(node.getId());
     }
 
+    /**
+     * @deprecated since 0.7
+     */
+    @Deprecated
     public static Predicate<NodeMetadata> predicateMatchingById(final String 
id) {
         Predicate<NodeMetadata> nodePredicate = new Predicate<NodeMetadata>() {
             @Override public boolean apply(NodeMetadata arg0) {
@@ -125,6 +132,10 @@ public class JcloudsUtil implements JcloudsLocationConfig {
         return nodePredicate;
     }
 
+    /**
+     * @deprecated since 0.7; see {@link IptablesCommands}
+     */
+    @Deprecated
     public static Statement authorizePortInIpTables(int port) {
         // TODO gogrid rules only allow ports 22, 3389, 80 and 443.
         // the first rule will be ignored, so we have to apply this
@@ -138,7 +149,10 @@ public class JcloudsUtil implements JcloudsLocationConfig {
     /**
      * @throws RunScriptOnNodesException
      * @throws IllegalStateException     If do not find exactly one matching 
node
+     * 
+     * @deprecated since 0.7
      */
+    @Deprecated
     public static ExecResponse runScriptOnNode(ComputeService computeService, 
NodeMetadata node, Statement statement, String scriptName) throws 
RunScriptOnNodesException {
         // TODO Includes workaround for NodeMetadata's equals/hashcode method 
being wrong.
         
@@ -209,110 +223,28 @@ public class JcloudsUtil implements 
JcloudsLocationConfig {
           throw new IllegalArgumentException("don't know how to handle" + 
os.toString());
     }
 
-    static Map<Map<?,?>,ComputeService> cachedComputeServices = new 
ConcurrentHashMap<Map<?,?>,ComputeService> ();
-
-    private static final Object createComputeServicesMutex = new Object();
-
+    /**
+     * @deprecated since 0.7; see {@link 
ComputeServiceRegistry#findComputeService(ConfigBag, boolean)}
+     */
+    @Deprecated
     public static ComputeService findComputeService(ConfigBag conf) {
-        return findComputeService(conf, true);
+        return ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, 
true);
     }
+    
+    /**
+     * @deprecated since 0.7; see {@link 
ComputeServiceRegistry#findComputeService(ConfigBag, boolean)}
+     */
+    @Deprecated
     public static ComputeService findComputeService(ConfigBag conf, boolean 
allowReuse) {
-        String provider = checkNotNull(conf.get(CLOUD_PROVIDER), "provider 
must not be null");
-        String identity = checkNotNull(conf.get(ACCESS_IDENTITY), "identity 
must not be null");
-        String credential = checkNotNull(conf.get(ACCESS_CREDENTIAL), 
"credential must not be null");
-        
-        Properties properties = new Properties();
-        properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, 
Boolean.toString(true));
-        properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, 
Boolean.toString(true));
-        properties.setProperty("jclouds.ssh.max-retries", 
conf.getStringKey("jclouds.ssh.max-retries") != null ? 
-                conf.getStringKey("jclouds.ssh.max-retries").toString() : 
"50");
-        // Enable aws-ec2 lazy image fetching, if given a specific imageId; 
otherwise customize for specific owners; or all as a last resort
-        // See https://issues.apache.org/jira/browse/WHIRR-416
-        if ("aws-ec2".equals(provider)) {
-            // TODO convert AWS-only flags to config keys
-            if (groovyTruth(conf.get(IMAGE_ID))) {
-                properties.setProperty(PROPERTY_EC2_AMI_QUERY, "");
-                properties.setProperty(PROPERTY_EC2_CC_AMI_QUERY, "");
-            } else if (groovyTruth(conf.getStringKey("imageOwner"))) {
-                properties.setProperty(PROPERTY_EC2_AMI_QUERY, 
"owner-id="+conf.getStringKey("imageOwner")+";state=available;image-type=machine");
-            } else if (groovyTruth(conf.getStringKey("anyOwner"))) {
-                // set `anyOwner: true` to override the default query (which 
is restricted to certain owners as per below), 
-                // allowing the AMI query to bind to any machine
-                // (note however, we sometimes pick defaults in 
JcloudsLocationFactory);
-                // (and be careful, this can give a LOT of data back, taking 
several minutes,
-                // and requiring extra memory allocated on the command-line)
-                properties.setProperty(PROPERTY_EC2_AMI_QUERY, 
"state=available;image-type=machine");
-                /*
-                 * by default the following filters are applied:
-                 * Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&
-                 * Filter.1.Value.2=063491364108&
-                 * Filter.1.Value.3=099720109477&
-                 * Filter.1.Value.4=411009282317&
-                 * Filter.2.Name=state&Filter.2.Value.1=available&
-                 * Filter.3.Name=image-type&Filter.3.Value.1=machine&
-                 */
-            }
-        }
-
-        // FIXME Deprecated mechanism, should have a ConfigKey for overrides
-        Map<String, Object> extra = Maps.filterKeys(conf.getAllConfig(), 
Predicates.containsPattern("^jclouds\\."));
-        if (extra.size() > 0) {
-            LOG.warn("Jclouds using deprecated property overrides: 
"+Entities.sanitize(extra));
-        }
-        properties.putAll(extra);
-
-        String endpoint = conf.get(CLOUD_ENDPOINT);
-        if (!groovyTruth(endpoint)) endpoint = getDeprecatedProperty(conf, 
Constants.PROPERTY_ENDPOINT);
-        if (groovyTruth(endpoint)) 
properties.setProperty(Constants.PROPERTY_ENDPOINT, endpoint);
-
-        Map<?,?> cacheKey = MutableMap.builder()
-                .putAll(properties)
-                .put("provider", provider)
-                .put("identity", identity)
-                .put("credential", credential)
-                .putIfNotNull("endpoint", endpoint)
-                .build()
-                .asUnmodifiable();
-
-        if (allowReuse) {
-            ComputeService result = cachedComputeServices.get(cacheKey);
-            if (result!=null) {
-                LOG.trace("jclouds ComputeService cache hit for compute 
service, for "+Entities.sanitize(properties));
-                return result;
-            }
-            LOG.debug("jclouds ComputeService cache miss for compute service, 
creating, for "+Entities.sanitize(properties));
-        }
-
-        Iterable<Module> modules = getCommonModules();
-
-        // Synchronizing to avoid deadlock from 
sun.reflect.annotation.AnnotationType.
-        // See https://github.com/brooklyncentral/brooklyn/issues/974
-        ComputeServiceContext computeServiceContext;
-        synchronized (createComputeServicesMutex) {
-            computeServiceContext = ContextBuilder.newBuilder(provider)
-                    .modules(modules)
-                    .credentials(identity, credential)
-                    .overrides(properties)
-                    .build(ComputeServiceContext.class);
-        }
-        final ComputeService computeService = 
computeServiceContext.getComputeService();
-        if (allowReuse) {
-            synchronized (cachedComputeServices) {
-                ComputeService result = cachedComputeServices.get(cacheKey);
-                if (result != null) {
-                    LOG.debug("jclouds ComputeService cache recovery for 
compute service, for "+Entities.sanitize(cacheKey));
-                    //keep the old one, discard the new one
-                    computeService.getContext().close();
-                    return result;
-                }
-                LOG.debug("jclouds ComputeService created "+computeService+", 
adding to cache, for "+Entities.sanitize(properties));
-                cachedComputeServices.put(cacheKey, computeService);
-            }
-        }
-        return computeService;
-     }
+        return ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, 
allowReuse);
+    }
 
-    /** returns the jclouds modules we typically install */ 
+    /** 
+     * Returns the jclouds modules we typically install
+     * 
+     * @deprecated since 0.7; see {@link ComputeServiceRegistry}
+     */
+    @Deprecated
     public static ImmutableSet<Module> getCommonModules() {
         return ImmutableSet.<Module> of(
                 new SshjSshClientModule(), 
@@ -349,7 +281,11 @@ public class JcloudsUtil implements JcloudsLocationConfig {
         return context;
     }
 
-     protected static String getDeprecatedProperty(ConfigBag conf, String key) 
{
+    /**
+     * @deprecated since 0.7
+     */
+    @Deprecated
+    protected static String getDeprecatedProperty(ConfigBag conf, String key) {
         if (conf.containsKey(key)) {
             LOG.warn("Jclouds using deprecated brooklyn-jclouds property 
"+key+": "+Entities.sanitize(conf.getAllConfig()));
             return (String) conf.getStringKey(key);
@@ -358,17 +294,25 @@ public class JcloudsUtil implements JcloudsLocationConfig 
{
         }
     }
 
+    /**
+     * @deprecated since 0.7
+     */
+    @Deprecated
     // Do this so that if there's a problem with our USERNAME's ssh key, we 
can still get in to check
-     // TODO Once we're really confident there are not going to be regular 
problems, then delete this
-     public static Statement addAuthorizedKeysToRoot(File publicKeyFile) 
throws IOException {
-         String publicKey = Files.toString(publicKeyFile, Charsets.UTF_8);
-         return addAuthorizedKeysToRoot(publicKey);
-     }
+    // TODO Once we're really confident there are not going to be regular 
problems, then delete this
+    public static Statement addAuthorizedKeysToRoot(File publicKeyFile) throws 
IOException {
+        String publicKey = Files.toString(publicKeyFile, Charsets.UTF_8);
+        return addAuthorizedKeysToRoot(publicKey);
+    }
      
-     public static Statement addAuthorizedKeysToRoot(String publicKey) {
-         return newStatementList(
-                 appendFile("/root/.ssh/authorized_keys", 
Splitter.on('\n').split(publicKey)),
-                 interpret("chmod 600 /root/.ssh/authorized_keys"));
+    /**
+     * @deprecated since 0.7
+     */
+    @Deprecated
+    public static Statement addAuthorizedKeysToRoot(String publicKey) {
+        return newStatementList(
+                appendFile("/root/.ssh/authorized_keys", 
Splitter.on('\n').split(publicKey)),
+                interpret("chmod 600 /root/.ssh/authorized_keys"));
     }
 
     public static String getFirstReachableAddress(ComputeServiceContext 
context, NodeMetadata node) {
@@ -471,6 +415,10 @@ public class JcloudsUtil implements JcloudsLocationConfig {
         }
     }
 
+    /**
+     * @deprecated since 0.7
+     */
+    @Deprecated
     public static void mapSecurityGroupRuleToIpTables(ComputeService 
computeService, NodeMetadata node,
             LoginCredentials credentials, String networkInterface, 
Iterable<Integer> ports) {
         for (Integer port : ports) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/test/java/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
new file mode 100644
index 0000000..3b8a4f4
--- /dev/null
+++ 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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 brooklyn.location.jclouds;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+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.compute.options.TemplateOptions;
+import org.jclouds.domain.LoginCredentials;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+
+import brooklyn.location.jclouds.ComputeServiceRegistry;
+import brooklyn.location.jclouds.ComputeServiceRegistryImpl;
+import brooklyn.location.jclouds.JcloudsLocation;
+import brooklyn.location.jclouds.JcloudsLocationConfig;
+import brooklyn.util.config.ConfigBag;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+/**
+ * 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 abstract class AbstractJcloudsStubbedLiveTest extends 
AbstractJcloudsLiveTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = 
LoggerFactory.getLogger(AbstractJcloudsStubbedLiveTest.class);
+
+    public static final String LOCATION_SPEC = "jclouds:" + SOFTLAYER_PROVIDER 
+ ":" + SOFTLAYER_AMS01_REGION_NAME;
+    
+    public static abstract class NodeCreator {
+        public final List<NodeMetadata> created = 
Lists.newCopyOnWriteArrayList();
+        public final List<String> destroyed = Lists.newCopyOnWriteArrayList();
+        
+        public Set<? extends NodeMetadata> createNodesInGroup(String group, 
int count, Template template) throws RunNodesException {
+            Set<NodeMetadata> result = Sets.newLinkedHashSet();
+            for (int i = 0; i < count; i++) {
+                NodeMetadata node = newNode(group, template);
+                created.add(node);
+                result.add(node);
+            }
+            return result;
+        }
+        public void destroyNode(String id) {
+            destroyed.add(id);
+        }
+        protected abstract NodeMetadata newNode(String group, Template 
template);
+    }
+    
+    public static class StubbedComputeService extends DelegatingComputeService 
{
+        private final NodeCreator nodeCreator;
+        
+        public StubbedComputeService(ComputeService delegate, NodeCreator 
nodeCreator) {
+            super(delegate);
+            this.nodeCreator = nodeCreator;
+        }
+        @Override
+        public Set<? extends NodeMetadata> createNodesInGroup(String group, 
int count, Template template) throws RunNodesException {
+            return nodeCreator.createNodesInGroup(group, count, template);
+        }
+        @Override
+        public void destroyNode(String id) {
+            nodeCreator.destroyNode(id);
+        }
+        @Override
+        public Set<? extends NodeMetadata> createNodesInGroup(String group, 
int count) {
+            throw new UnsupportedOperationException();
+        }
+        @Override
+        public Set<? extends NodeMetadata> createNodesInGroup(String group, 
int count, TemplateOptions templateOptions) {
+            throw new UnsupportedOperationException();
+        }
+        @Override
+        public Set<? extends NodeMetadata> 
destroyNodesMatching(Predicate<NodeMetadata> filter) {
+            throw new UnsupportedOperationException();
+        }
+    }
+    
+    protected NodeCreator nodeCreator;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        nodeCreator = newNodeCreator();
+        ComputeServiceRegistry computeServiceRegistry = new 
ComputeServiceRegistry() {
+            @Override
+            public ComputeService findComputeService(ConfigBag conf, boolean 
allowReuse) {
+                ComputeService delegate = 
ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, allowReuse);
+                return new StubbedComputeService(delegate, nodeCreator);
+            }
+        };
+        jcloudsLocation = (JcloudsLocation) 
managementContext.getLocationRegistry().resolve(
+                LOCATION_SPEC, 
+                ImmutableMap.of(
+                        JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, 
computeServiceRegistry,
+                        JcloudsLocationConfig.WAIT_FOR_SSHABLE, "false"));
+    }
+    
+    protected abstract NodeCreator newNodeCreator();
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/test/java/brooklyn/location/jclouds/DelegatingComputeService.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/DelegatingComputeService.java
 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/DelegatingComputeService.java
new file mode 100644
index 0000000..62f621e
--- /dev/null
+++ 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/DelegatingComputeService.java
@@ -0,0 +1,229 @@
+/*
+ * 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 brooklyn.location.jclouds;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.RunScriptOnNodesException;
+import org.jclouds.compute.domain.ComputeMetadata;
+import org.jclouds.compute.domain.ExecResponse;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.options.RunScriptOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.domain.Location;
+import org.jclouds.scriptbuilder.domain.Statement;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.util.concurrent.ListenableFuture;
+
+public class DelegatingComputeService implements ComputeService {
+
+    private final ComputeService delegate;
+    
+    public DelegatingComputeService(ComputeService delegate) {
+        this.delegate = delegate;
+    }
+    
+    @Override
+    public ComputeServiceContext getContext() {
+        return delegate.getContext();
+    }
+
+    @Override
+    public TemplateBuilder templateBuilder() {
+        return delegate.templateBuilder();
+    }
+
+    @Override
+    public TemplateOptions templateOptions() {
+        return delegate.templateOptions();
+    }
+
+    @Override
+    public Set<? extends Hardware> listHardwareProfiles() {
+        return delegate.listHardwareProfiles();
+    }
+
+    @Override
+    public Set<? extends Image> listImages() {
+        return delegate.listImages();
+    }
+
+    @Override
+    public Image getImage(String id) {
+        return delegate.getImage(id);
+    }
+
+    @Override
+    public Set<? extends ComputeMetadata> listNodes() {
+        return delegate.listNodes();
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> listNodesByIds(Iterable<String> ids) {
+        return delegate.listNodesByIds(ids);
+    }
+
+    @Override
+    public Set<? extends Location> listAssignableLocations() {
+        return delegate.listAssignableLocations();
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> createNodesInGroup(String group, int 
count, Template template) throws RunNodesException {
+        return delegate.createNodesInGroup(group, count, template);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> createNodesInGroup(String group, int 
count, TemplateOptions templateOptions)
+            throws RunNodesException {
+        return delegate.createNodesInGroup(group, count, templateOptions);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> createNodesInGroup(String group, int 
count) throws RunNodesException {
+        return delegate.createNodesInGroup(group, count);
+    }
+
+    @Override
+    public void resumeNode(String id) {
+        delegate.resumeNode(id);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> 
resumeNodesMatching(Predicate<NodeMetadata> filter) {
+        return delegate.resumeNodesMatching(filter);
+    }
+
+    @Override
+    public void suspendNode(String id) {
+        delegate.suspendNode(id);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> 
suspendNodesMatching(Predicate<NodeMetadata> filter) {
+        return delegate.suspendNodesMatching(filter);
+    }
+
+    @Override
+    public void destroyNode(String id) {
+        delegate.destroyNode(id);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> 
destroyNodesMatching(Predicate<NodeMetadata> filter) {
+        return delegate.destroyNodesMatching(filter);
+    }
+
+    @Override
+    public void rebootNode(String id) {
+        delegate.rebootNode(id);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> 
rebootNodesMatching(Predicate<NodeMetadata> filter) {
+        return delegate.rebootNodesMatching(filter);
+    }
+
+    @Override
+    public NodeMetadata getNodeMetadata(String id) {
+        return delegate.getNodeMetadata(id);
+    }
+
+    @Override
+    public Set<? extends NodeMetadata> 
listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
+        return delegate.listNodesDetailsMatching(filter);
+    }
+
+    @Override
+    public Map<? extends NodeMetadata, ExecResponse> 
runScriptOnNodesMatching(Predicate<NodeMetadata> filter, String runScript)
+            throws RunScriptOnNodesException {
+        return delegate.runScriptOnNodesMatching(filter, runScript);
+    }
+
+    @Override
+    public Map<? extends NodeMetadata, ExecResponse> 
runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Statement runScript)
+            throws RunScriptOnNodesException {
+        return delegate.runScriptOnNodesMatching(filter, runScript);
+    }
+
+    @Override
+    public Map<? extends NodeMetadata, ExecResponse> 
runScriptOnNodesMatching(Predicate<NodeMetadata> filter,
+            String runScript, RunScriptOptions options) throws 
RunScriptOnNodesException {
+        return delegate.runScriptOnNodesMatching(filter, runScript, options);
+    }
+
+    @Override
+    public Map<? extends NodeMetadata, ExecResponse> 
runScriptOnNodesMatching(Predicate<NodeMetadata> filter,
+            Statement runScript, RunScriptOptions options) throws 
RunScriptOnNodesException {
+        return delegate.runScriptOnNodesMatching(filter, runScript, options);
+    }
+
+    @Override
+    public ExecResponse runScriptOnNode(String id, Statement runScript, 
RunScriptOptions options) {
+        return delegate.runScriptOnNode(id, runScript, options);
+    }
+
+    @Override
+    public ListenableFuture<ExecResponse> submitScriptOnNode(String id, String 
runScript, RunScriptOptions options) {
+        return delegate.submitScriptOnNode(id, runScript, options);
+    }
+
+    @Override
+    public ListenableFuture<ExecResponse> submitScriptOnNode(String id, 
Statement runScript, RunScriptOptions options) {
+        return delegate.submitScriptOnNode(id, runScript, options);
+    }
+
+    @Override
+    public ExecResponse runScriptOnNode(String id, Statement runScript) {
+        return delegate.runScriptOnNode(id, runScript);
+    }
+
+    @Override
+    public ExecResponse runScriptOnNode(String id, String runScript, 
RunScriptOptions options) {
+        return delegate.runScriptOnNode(id, runScript, options);
+    }
+
+    @Override
+    public ExecResponse runScriptOnNode(String id, String runScript) {
+        return delegate.runScriptOnNode(id, runScript);
+    }
+
+    @Override
+    public Optional<ImageExtension> getImageExtension() {
+        return delegate.getImageExtension();
+    }
+
+    @Override
+    public Optional<SecurityGroupExtension> getSecurityGroupExtension() {
+        return delegate.getSecurityGroupExtension();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsHardwareProfilesStubbedLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsHardwareProfilesStubbedLiveTest.java
 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsHardwareProfilesStubbedLiveTest.java
new file mode 100644
index 0000000..8d7f55e
--- /dev/null
+++ 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsHardwareProfilesStubbedLiveTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 brooklyn.location.jclouds;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+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.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+
+public class JcloudsHardwareProfilesStubbedLiveTest extends 
AbstractJcloudsStubbedLiveTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = 
LoggerFactory.getLogger(JcloudsHardwareProfilesStubbedLiveTest.class);
+    
+    private Template template;
+    
+    @Override
+    protected NodeCreator newNodeCreator() {
+        return new NodeCreator() {
+            @Override protected NodeMetadata newNode(String group, Template 
template) {
+                JcloudsHardwareProfilesStubbedLiveTest.this.template = 
template;
+                
+                NodeMetadata result = new NodeMetadataBuilder()
+                        .id("myid")
+                        
.credentials(LoginCredentials.builder().identity("myuser").credential("mypassword").build())
+                        .loginPort(22)
+                        .status(Status.RUNNING)
+                        .publicAddresses(ImmutableList.of("173.194.32.123"))
+                        .privateAddresses(ImmutableList.of("172.168.10.11"))
+                        .build();
+                return result;
+            }
+        };
+    }
+
+    @Test(groups={"Live", "Live-sanity"})
+    public void testJcloudsCreateWithHardwareProfiles() throws Exception {
+        obtainMachine(MutableMap.of(JcloudsLocationConfig.MIN_RAM, "4096"));
+        assertTrue(template.getHardware().getRam() >= 4096, 
"template="+template);
+        
+        obtainMachine(MutableMap.of(JcloudsLocationConfig.MIN_CORES, "4"));
+        assertTrue(template.getHardware().getProcessors().get(0).getCores() >= 
4, "template="+template);
+
+        obtainMachine(MutableMap.of(JcloudsLocationConfig.MIN_DISK, "51"));
+        assertTrue(template.getHardware().getVolumes().get(0).getSize() >= 51, 
"template="+template);
+        
+        String hardwareId = "cpu=1,memory=6144,disk=25,type=LOCAL";
+        obtainMachine(MutableMap.of(JcloudsLocationConfig.HARDWARE_ID, 
hardwareId));
+        assertEquals(template.getHardware().getId(), hardwareId, 
"template="+template);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationRebindMachineLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationRebindMachineLiveTest.java
 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationRebindMachineLiveTest.java
index 7ec58cf..d2f1b84 100644
--- 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationRebindMachineLiveTest.java
+++ 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationRebindMachineLiveTest.java
@@ -50,12 +50,12 @@ public class JcloudsLocationRebindMachineLiveTest extends 
AbstractJcloudsLiveTes
         jcloudsLocation = (JcloudsLocation) 
managementContext.getLocationRegistry().resolve(AWS_EC2_PROVIDER+":"+AWS_EC2_EUWEST_REGION_NAME);
     }
 
-    @Test(groups = { "Live" })
+    @Test(groups = { "Live", "Live-sanity" })
     public void testRebindWithIncorrectId() throws Exception {
         try {
             jcloudsLocation.rebindMachine(ImmutableMap.of("id", "incorrectid", 
"hostname", "myhostname", "user", "myusername"));
         } catch (IllegalArgumentException e) {
-            if (e.getMessage().contains("Invalid id")) {
+            if (e.getMessage().contains("node not found")) {
                 // success
             } else {
                 throw e;
@@ -90,7 +90,7 @@ public class JcloudsLocationRebindMachineLiveTest extends 
AbstractJcloudsLiveTes
         
         // Confirm can release the re-bound machine via the new jclouds 
location
         loc2.release(machine2);
-        assertFalse(machine2.isSshable());
+        assertFalse(machine.isSshable());
         assertEquals(ImmutableSet.copyOf(loc2.getChildren()), 
Collections.emptySet());
     }
     
@@ -118,7 +118,7 @@ public class JcloudsLocationRebindMachineLiveTest extends 
AbstractJcloudsLiveTes
         
         // Confirm can release the re-bound machine via the new jclouds 
location
         loc2.release(machine2);
-        assertFalse(machine2.isSshable());
+        assertFalse(machine.isSshable());
         assertEquals(ImmutableSet.copyOf(loc2.getChildren()), 
Collections.emptySet());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0d5e1ba9/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMinRamLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMinRamLiveTest.java
 
b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMinRamLiveTest.java
deleted file mode 100644
index ee072f2..0000000
--- 
a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMinRamLiveTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 brooklyn.location.jclouds;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.collect.ImmutableMap;
-
-public class JcloudsMinRamLiveTest extends AbstractJcloudsLiveTest {
-
-    private static final Logger log = 
LoggerFactory.getLogger(JcloudsMinRamLiveTest.class);
-    
-    private static final String LOCATION_SPEC = AWS_EC2_PROVIDER + ":" + 
AWS_EC2_USEAST_REGION_NAME;
-    
-    @Test(groups="Live")
-    public void testJcloudsCreateWithMinRam() throws Exception {
-        jcloudsLocation = (JcloudsLocation) 
managementContext.getLocationRegistry().resolve(LOCATION_SPEC);
-        jcloudsLocation.configure(MutableMap.of("minRam", "4096"));
-        
-        JcloudsSshMachineLocation m1 = obtainMachine(ImmutableMap.<String, 
Object>of());
-
-        log.info("GOT "+m1);
-        
-        jcloudsLocation.release(m1);
-    }
-
-//    @Test(groups="Live")
-//    public void testJcloudsCreateNamedJungleBig() throws Exception {
-//        @SuppressWarnings("unchecked")
-//        MachineProvisioningLocation<SshMachineLocation> l = 
(MachineProvisioningLocation<SshMachineLocation>) new 
LocationRegistry().resolve("named:jungle-big");
-//        
-//        SshMachineLocation m1 = l.obtain(MutableMap.<String,String>of());
-//
-//        log.info("GOT "+m1);
-//        
-//        l.release(m1);
-//    }
-
-}

Reply via email to