Repository: brooklyn-server
Updated Branches:
  refs/heads/master fdf29403a -> 115a3712d


BROOKLYN-259: Fix JcloudsByonLocationResolver

- Previously, it created locations every time the resolver was called
  (even if that was just to query about what it would produce), which
  caused us to leak locations.
- Now it defers creating the locations (though doing this with
  LocationSpec is not practical because of the relationship between
  the JcloudsLocation and the machines)!
- Adds FixedListMachineProvisioningLocation.initialMachinesFactory,
  for deferring the machine-lookup until the byon location is really
  being created.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/9da32f6b
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/9da32f6b
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/9da32f6b

Branch: refs/heads/master
Commit: 9da32f6b8614886c71b4da89d567ffcc0f116a2f
Parents: dee1074
Author: Aled Sage <aled.s...@gmail.com>
Authored: Tue May 3 16:58:32 2016 +0100
Committer: Aled Sage <aled.s...@gmail.com>
Committed: Thu May 19 16:42:24 2016 +0100

----------------------------------------------------------------------
 .../core/location/AbstractLocationResolver.java |   4 +-
 .../location/byon/ByonLocationResolver.java     |   4 +-
 .../FixedListMachineProvisioningLocation.java   |  44 +++-
 .../jclouds/JcloudsByonLocationResolver.java    | 204 ++++++++--------
 .../location/jclouds/JcloudsLocation.java       |   4 +-
 .../jclouds/AbstractJcloudsStubbedLiveTest.java |  34 ++-
 ...dsByonLocationResolverStubbedRebindTest.java | 235 +++++++++++++++++++
 .../JcloudsByonLocationResolverStubbedTest.java | 157 +++++++++++++
 8 files changed, 568 insertions(+), 118 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java
 
b/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java
index 809e228..0a836e6 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java
@@ -92,8 +92,8 @@ public abstract class AbstractLocationResolver implements 
LocationResolver {
         }
 
         return LocationSpec.create(getLocationType())
-            .configure(config.getAllConfig())
-            .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, 
locationFlags, globalProperties, namedLocation));        
+                .configure(config.getAllConfig())
+                .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, 
locationFlags, globalProperties, namedLocation));
     }
 
     protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, 
LocationRegistry registry) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java
 
b/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java
index 90c9a53..58b4989 100644
--- 
a/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java
+++ 
b/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java
@@ -108,7 +108,7 @@ public class ByonLocationResolver extends 
AbstractLocationResolver {
         defaultProps.addIfNotNull("user", user);
         defaultProps.addIfNotNull("port", port);
 
-        List<String> hostAddresses;
+        List<?> hostAddresses;
         
         if (hosts instanceof String) {
             if (((String) hosts).isEmpty()) {
@@ -118,7 +118,7 @@ public class ByonLocationResolver extends 
AbstractLocationResolver {
                         true /* numeric */, /* no quote support though */ 
PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR);
             }
         } else if (hosts instanceof Iterable) {
-            hostAddresses = ImmutableList.copyOf((Iterable<String>)hosts);
+            hostAddresses = ImmutableList.copyOf((Iterable<?>)hosts);
         } else {
             throw new IllegalArgumentException("Invalid location '"+spec+"'; 
at least one host must be defined");
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
 
b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
index 6e88f15..15db018 100644
--- 
a/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
+++ 
b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
@@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -80,6 +81,7 @@ implements MachineProvisioningLocation<T>, Closeable {
     
     private static final Logger log = 
LoggerFactory.getLogger(FixedListMachineProvisioningLocation.class);
     
+    @SuppressWarnings("serial")
     public static final ConfigKey<Function<Iterable<? extends 
MachineLocation>, MachineLocation>> MACHINE_CHOOSER =
             ConfigKeys.newConfigKey(
                     new TypeToken<Function<Iterable<? extends 
MachineLocation>, MachineLocation>>() {}, 
@@ -103,10 +105,29 @@ implements MachineProvisioningLocation<T>, Closeable {
     @Beta
     @SuppressWarnings("serial")
     public static final ConfigKey<List<LocationSpec<? extends 
MachineLocation>>> MACHINE_SPECS = ConfigKeys.newConfigKey(
-                    new TypeToken<List<LocationSpec<? extends 
MachineLocation>>>() {}, 
-                    "byon.machineSpecs",
-                    "Specs of machines that should be immediatly instantiated 
on init",
-                    ImmutableList.<LocationSpec<? extends 
MachineLocation>>of());
+            new TypeToken<List<LocationSpec<? extends MachineLocation>>>() {}, 
+            "byon.machineSpecs",
+            "Specs of machines that should be immediatly instantiated on init",
+            ImmutableList.<LocationSpec<? extends MachineLocation>>of());
+
+    /**
+     * The initialMachinesFactory allows {@code JcloudsByonLocationResolver} 
to work, to defer 
+     * instantiating the {@code JcloudsLocation} and the {@code 
JcloudsMachineLocation} instances.
+     * (Important because the caller might not use the spec and thus might not 
unmanage the machine 
+     * instances).
+     * 
+     * We clear the initialMachinesFactory in init, so they will never be 
persisted. This will help 
+     * with backwards compatibility if we change how this is done.
+     * 
+     * By the end of init(), the {@link #machines} will contain the full list 
of locations.
+     */
+    @Beta
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Supplier<? extends List<? extends 
MachineLocation>>> INITIAL_MACHINES_FACTORY = ConfigKeys.newConfigKey(
+            new TypeToken<Supplier<? extends List<? extends 
MachineLocation>>>() {}, 
+            "byon.initialMachinesFactory",
+            "Factory for creating the machines that should be immediatly 
instantiated on init",
+            null);
 
     private final Object lock = new Object();
     
@@ -147,8 +168,21 @@ implements MachineProvisioningLocation<T>, Closeable {
         }
         config().set(MACHINE_SPECS, (List<LocationSpec<? extends 
MachineLocation>>) null);
         
+        Supplier<? extends List<? extends MachineLocation>> 
initialMachinesFactory = getConfig(INITIAL_MACHINES_FACTORY);
+        if (initialMachinesFactory != null) {
+            List<? extends MachineLocation> initialMachines = 
initialMachinesFactory.get();
+            if (initialMachines != null) {
+                for (MachineLocation machine : initialMachines) {
+                    @SuppressWarnings("unchecked")
+                    T castMachine = (T) machine;
+                    machines.add(castMachine);
+                }
+            }
+        }
+        config().set(INITIAL_MACHINES_FACTORY, (Supplier<List<? extends 
MachineLocation>>) null);
+        
         Set<T> machinesCopy = MutableSet.of();
-        for (T location: machines) {
+        for (T location : machines) {
             if (location==null) {
                 log.warn(""+this+" initialized with null location, removing 
(may be due to rebind with reference to an unmanaged location)");
             } else {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java
index c4e09fb..d832a04 100644
--- 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java
@@ -18,37 +18,32 @@
  */
 package org.apache.brooklyn.location.jclouds;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.location.LocationRegistry;
 import org.apache.brooklyn.api.location.LocationResolver;
 import org.apache.brooklyn.api.location.LocationSpec;
 import org.apache.brooklyn.api.location.NoMachinesAvailableException;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.location.BasicLocationRegistry;
-import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.config.Sanitizer;
+import org.apache.brooklyn.core.location.AbstractLocationResolver;
 import org.apache.brooklyn.core.location.LocationConfigUtils;
-import 
org.apache.brooklyn.core.location.LocationPropertiesFromBrooklynProperties;
 import org.apache.brooklyn.core.location.internal.LocationInternal;
 import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.text.KeyValueParser;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.WildcardGlobs;
 import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 
 /**
  * Examples of valid specs:
@@ -61,129 +56,140 @@ import com.google.common.collect.Maps;
  * @author aled
  */
 @SuppressWarnings({"unchecked","rawtypes"})
-public class JcloudsByonLocationResolver implements LocationResolver {
+public class JcloudsByonLocationResolver extends AbstractLocationResolver 
implements LocationResolver {
 
     public static final Logger log = 
LoggerFactory.getLogger(JcloudsByonLocationResolver.class);
     
     public static final String BYON = "jcloudsByon";
 
-    private static final Pattern PATTERN = 
Pattern.compile("("+BYON+"|"+BYON.toUpperCase()+")" + ":" + "\\((.*)\\)$");
-
-    private ManagementContext managementContext;
-
     @Override
-    public void init(ManagementContext managementContext) {
-        this.managementContext = checkNotNull(managementContext, 
"managementContext");
+    public boolean isEnabled() {
+        return LocationConfigUtils.isResolverPrefixEnabled(managementContext, 
getPrefix());
     }
     
     @Override
-    public boolean isEnabled() {
-        return LocationConfigUtils.isResolverPrefixEnabled(managementContext, 
getPrefix());
+    public String getPrefix() {
+        return BYON;
     }
     
-    // TODO Remove some duplication from JcloudsResolver; needs more careful 
review
     @Override
-    public LocationSpec<? extends Location> newLocationSpecFromString(String 
spec, Map<?, ?> locationFlags, LocationRegistry registry) {
-        Map globalProperties = registry.getProperties();
+    protected Class<? extends Location> getLocationType() {
+        return FixedListMachineProvisioningLocation.class;
+    }
 
-        Matcher matcher = PATTERN.matcher(spec);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException("Invalid location '"+spec+"'; 
must specify something like 
jcloudsByon(provider=\"aws-ec2\",region=\"us-east-1\",hosts=\"i-f2014593,i-d1234567\")");
-        }
-        
-        String argsPart = matcher.group(2);
-        Map<String, String> argsMap = KeyValueParser.parseMap(argsPart);
-        
-        // prefer args map over location flags
-        
-        String namedLocation = (String) 
locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+    @Override
+    protected SpecParser getSpecParser() {
+        return new 
AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"jcloudsByon(provider='aws-ec2',region='us-east-1',hosts='i-12345678,i-90123456')\"");
+    }
+    
+    @Override
+    protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, 
final LocationRegistry registry) {
+        ConfigBag config = super.extractConfig(locationFlags, spec, registry);
 
-        String providerOrApi = argsMap.containsKey("provider") ? 
argsMap.get("provider") : (String)locationFlags.get("provider");
+        String providerOrApi = (String) config.getStringKey("provider");
+        String regionName = (String) config.getStringKey("region");
+        String endpoint = (String) config.getStringKey("endpoint");
+        String namedLocation = (String) 
config.get(LocationInternal.NAMED_SPEC_NAME);
+        config.remove(LocationInternal.NAMED_SPEC_NAME.getName());
 
-        String regionName = argsMap.containsKey("region") ? 
argsMap.get("region") : (String)locationFlags.get("region");
-        
-        String endpoint = argsMap.containsKey("endpoint") ? 
argsMap.get("endpoint") : (String)locationFlags.get("endpoint");
-        
-        String name = argsMap.containsKey("name") ? argsMap.get("name") : 
(String)locationFlags.get("name");
+        Object hosts = config.getStringKey("hosts");
+        config.remove("hosts");
 
-        String user = argsMap.containsKey("user") ? argsMap.get("user") : 
(String)locationFlags.get("user");
+        Map jcloudsProperties = new 
JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi, 
regionName, namedLocation, config.getAllConfig());
+        config.putIfAbsent(jcloudsProperties);
 
-        String privateKeyFile = argsMap.containsKey("privateKeyFile") ? 
argsMap.get("privateKeyFile") : (String)locationFlags.get("privateKeyFile");
-        
-        String hosts = argsMap.get("hosts");
-        
         if (Strings.isEmpty(providerOrApi)) {
             throw new IllegalArgumentException("Invalid location '"+spec+"'; 
provider must be defined");
         }
-        if (hosts == null || hosts.isEmpty()) {
+        if (hosts == null || (hosts instanceof String && 
Strings.isBlank((String)hosts))) {
             throw new IllegalArgumentException("Invalid location '"+spec+"'; 
at least one host must be defined");
         }
-        if (argsMap.containsKey("name") && (Strings.isEmpty(name))) {
-            throw new IllegalArgumentException("Invalid location '"+spec+"'; 
if name supplied then value must be non-empty");
+
+        final String jcloudsSpec = "jclouds:"+providerOrApi + (regionName != 
null ? ":"+regionName : "") + (endpoint != null ? ":"+endpoint : "");
+        final Maybe<LocationSpec<? extends Location>> jcloudsLocationSpec = 
registry.getLocationSpec(jcloudsSpec, config.getAllConfig());
+        if (jcloudsLocationSpec.isAbsent()) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; 
referrenced jclouds spec '"+jcloudsSpec+"' cannot be resolved");
+        } else if 
(!JcloudsLocation.class.isAssignableFrom(jcloudsLocationSpec.get().getType())) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; 
referrenced spec '"+jcloudsSpec+"' of type 
"+jcloudsLocationSpec.get().getType()+" rather than JcloudsLocation");
         }
 
-        // For everything in brooklyn.properties, only use things with correct 
prefix (and remove that prefix).
-        // But for everything passed in via locationFlags, pass those as-is.
-        // TODO Should revisit the locationFlags: where are these actually 
used? Reason accepting properties without
-        //      full prefix is that the map's context is explicitly this 
location, rather than being generic properties.
-        Map allProperties = getAllProperties(registry, globalProperties);
-        Map jcloudsProperties = new 
JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi, 
regionName, namedLocation, allProperties);
-        jcloudsProperties.putAll(locationFlags);
-        jcloudsProperties.putAll(argsMap);
+        List<?> hostIds;
         
-        String jcloudsSpec = "jclouds:"+providerOrApi + (regionName != null ? 
":"+regionName : "") + (endpoint != null ? ":"+endpoint : "");
-        JcloudsLocation jcloudsLocation = (JcloudsLocation) 
registry.resolve(jcloudsSpec, jcloudsProperties);
+        if (hosts instanceof String) {
+            if (((String) hosts).isEmpty()) {
+                hostIds = ImmutableList.of();
+            } else {
+                hostIds = 
WildcardGlobs.getGlobsAfterBraceExpansion("{"+hosts+"}",
+                        true /* numeric */, /* no quote support though */ 
PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR);
+            }
+        } else if (hosts instanceof Iterable) {
+            hostIds = ImmutableList.copyOf((Iterable<?>)hosts);
+        } else {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; 
at least one host must be defined");
+        }
+        if (hostIds.isEmpty()) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; 
at least one host must be defined");
+        }
 
-        List<String> hostIdentifiers = 
WildcardGlobs.getGlobsAfterBraceExpansion("{"+hosts+"}",
-                true /* numeric */, /* no quote support though */ 
PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR);
-        List<JcloudsSshMachineLocation> machines = Lists.newArrayList();
-        
-        for (String hostIdentifier : hostIdentifiers) {
-            Map<?, ?> machineFlags = MutableMap.builder()
-                    .put("id", hostIdentifier)
-                    .putIfNotNull("user", user)
-                    .putIfNotNull("privateKeyFile", privateKeyFile)
-                    .build();
-            try {
-                // TODO management of these machines may be odd, as it is 
passed in as a key as config to a spec
-                JcloudsSshMachineLocation machine = 
jcloudsLocation.rebindMachine(jcloudsLocation.config().getBag().putAll(machineFlags));
-                machines.add(machine);
-            } catch (NoMachinesAvailableException e) {
-                log.warn("Error rebinding to jclouds machine 
"+hostIdentifier+" in "+jcloudsLocation, e);
-                Exceptions.propagate(e);
+        final List<Map<?,?>> machinesFlags = Lists.newArrayList();
+        for (Object hostId : hostIds) {
+            Map<?, ?> machineFlags;
+            if (hostId instanceof String) {
+                machineFlags = parseMachineFlags((String)hostId, config);
+            } else if (hostId instanceof Map) {
+                machineFlags = parseMachineFlags((Map<String,?>)hostId, 
config);
+            } else {
+                throw new IllegalArgumentException("Invalid host type 
'"+(hostId == null ? null : hostId.getClass().getName())+", referrenced in spec 
'"+spec);
             }
+            machinesFlags.add(machineFlags);
         }
         
+        Supplier<List<JcloudsMachineLocation>> machinesFactory = new 
Supplier<List<JcloudsMachineLocation>>() {
+            @Override
+            public List<JcloudsMachineLocation> get() {
+                // FIXME Can't change the parent of the 
JcloudsMachineLocation; but the FixedListMachineLocation will expect it to be 
the parent?
+                // We'll get errors on rebind. Need to reproduce/investigate.
+                List<JcloudsMachineLocation> result = Lists.newArrayList();
+                JcloudsLocation jcloudsLocation = (JcloudsLocation) 
managementContext.getLocationManager().createLocation(jcloudsLocationSpec.get());
+                for (Map<?,?> machineFlags : machinesFlags) {
+                    try {
+                        JcloudsMachineLocation machine = 
jcloudsLocation.registerMachine(jcloudsLocation.config().getBag().putAll(machineFlags));
+                        result.add(machine);
+                    } catch (NoMachinesAvailableException e) {
+                        Map<?,?> sanitizedMachineFlags = 
Sanitizer.sanitize(machineFlags);
+                        log.warn("Error rebinding to jclouds machine 
"+sanitizedMachineFlags+" in "+jcloudsLocation, e);
+                        Exceptions.propagate(e);
+                    }
+                }
+                
+                log.debug("Created machines for jclouds BYON location: 
"+result);
+                return result;
+            }
+        };
+        
         ConfigBag flags = ConfigBag.newInstance(jcloudsProperties);
 
-        flags.putStringKey("machines", machines);
-        flags.putIfNotNull(LocationConfigKeys.USER, user);
-        flags.putStringKeyIfNotNull("name", name);
-        
-        if (registry != null) 
-            
LocationPropertiesFromBrooklynProperties.setLocalTempDir(registry.getProperties(),
 flags);
+        
config.put(FixedListMachineProvisioningLocation.INITIAL_MACHINES_FACTORY, 
machinesFactory);
 
-        log.debug("Created Jclouds BYON location "+name+": "+machines);
-        
-        return LocationSpec.create(FixedListMachineProvisioningLocation.class)
-                .configure(flags.getAllConfig())
-                .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, 
locationFlags, globalProperties, namedLocation));
+        return config;
     }
     
-    private Map getAllProperties(LocationRegistry registry, Map<?,?> 
properties) {
-        Map<Object,Object> allProperties = Maps.newHashMap();
-        if (registry!=null) allProperties.putAll(registry.getProperties());
-        allProperties.putAll(properties);
-        return allProperties;
-    }
     
-    @Override
-    public String getPrefix() {
-        return BYON;
+    protected Map<?, ?> parseMachineFlags(Map<String, ?> vals, ConfigBag 
config) {
+        if (!(vals.get("id") instanceof String) || 
Strings.isBlank((String)vals.get("id"))) {
+            Map<String, Object> valSanitized = Sanitizer.sanitize(vals);
+            throw new IllegalArgumentException("In jcloudsByon, machine 
"+valSanitized+" is missing String 'id'");
+        }
+        return MutableMap.builder()
+                .putAll(config.getAllConfig())
+                .putAll(vals)
+                .build();
     }
-    
-    @Override
-    public boolean accepts(String spec, LocationRegistry registry) {
-        return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true);
+
+    protected Map<?, ?> parseMachineFlags(String hostIdentifier, ConfigBag 
config) {
+        return MutableMap.builder()
+                .putAll(config.getAllConfig())
+                .put("id", hostIdentifier)
+                .build();
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index ebce579..d55589a 100644
--- 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -2116,12 +2116,12 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
      *   <li>userName: the username for sshing into the machine (for use if it 
is not a Windows system)
      * <ul>
      */
-    public MachineLocation registerMachine(ConfigBag setup) throws 
NoMachinesAvailableException {
+    public JcloudsMachineLocation registerMachine(ConfigBag setup) throws 
NoMachinesAvailableException {
         NodeMetadata node = findNodeOrThrow(setup);
         return registerMachineLocation(setup, node);
     }
 
-    protected MachineLocation registerMachineLocation(ConfigBag setup, 
NodeMetadata node) {
+    protected JcloudsMachineLocation registerMachineLocation(ConfigBag setup, 
NodeMetadata node) {
         ComputeService computeService = getComputeService(setup);
         if (isWindows(node, setup)) {
             return registerWinRmMachineLocation(computeService, node, null, 
Optional.<HostAndPort>absent(), setup);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
index c885197..6aa62fe 100644
--- 
a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
+++ 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.ComputeMetadata;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.options.TemplateOptions;
@@ -34,6 +35,7 @@ import org.testng.annotations.BeforeMethod;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
@@ -65,6 +67,9 @@ public abstract class AbstractJcloudsStubbedLiveTest extends 
AbstractJcloudsLive
             }
             return result;
         }
+        public Set<? extends NodeMetadata> 
listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
+            return ImmutableSet.of();
+        }
         public void destroyNode(String id) {
             destroyed.add(id);
         }
@@ -83,6 +88,10 @@ public abstract class AbstractJcloudsStubbedLiveTest extends 
AbstractJcloudsLive
             return nodeCreator.createNodesInGroup(group, count, template);
         }
         @Override
+        public Set<? extends NodeMetadata> 
listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
+            return nodeCreator.listNodesDetailsMatching(filter);
+        }
+        @Override
         public void destroyNode(String id) {
             nodeCreator.destroyNode(id);
         }
@@ -99,21 +108,30 @@ public abstract class AbstractJcloudsStubbedLiveTest 
extends AbstractJcloudsLive
             throw new UnsupportedOperationException();
         }
     }
-    
+
+    public static class StubbedComputeServiceRegistry implements 
ComputeServiceRegistry {
+        private final NodeCreator nodeCreator;
+        
+        public StubbedComputeServiceRegistry(NodeCreator nodeCreator) {
+            this.nodeCreator = nodeCreator;
+        }
+        @Override
+        public ComputeService findComputeService(ConfigBag conf, boolean 
allowReuse) {
+            ComputeService delegate = 
ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, allowReuse);
+            return new StubbedComputeService(delegate, nodeCreator);
+        }
+    }
+
     protected NodeCreator nodeCreator;
+    protected ComputeServiceRegistry computeServiceRegistry;
     
     @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);
-            }
-        };
+        computeServiceRegistry = new 
StubbedComputeServiceRegistry(nodeCreator);
+
         jcloudsLocation = (JcloudsLocation) 
managementContext.getLocationRegistry().getLocationManaged(
                 getLocationSpec(), 
                 jcloudsLocationConfig(ImmutableMap.<Object, Object>of(

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java
 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java
new file mode 100644
index 0000000..0e5c4af
--- /dev/null
+++ 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.assertNotNull;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore;
+import org.apache.brooklyn.core.mgmt.rebind.RebindOptions;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.time.Duration;
+import org.jclouds.compute.domain.ComputeMetadata;
+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.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class JcloudsByonLocationResolverStubbedRebindTest extends 
AbstractJcloudsStubbedLiveTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(JcloudsByonLocationResolverStubbedRebindTest.class);
+    
+    // Using static so that the static NodeCreator class can access it.
+    // We are using the static NodeCreator to ensure it is serializable, 
without it trying to serialize JcloudsByonLocationResolverStubbedRebindLiveTest.
+    private static final String nodeId = "mynodeid";
+    private static final String nodePublicAddress = "173.194.32.123";
+    private static final String nodePrivateAddress = "172.168.10.11";
+
+    protected static final Duration TIMEOUT_MS = Duration.TEN_SECONDS;
+
+    protected ClassLoader classLoader = getClass().getClassLoader();
+    protected LocalManagementContext origManagementContext;
+    protected File mementoDir;
+    protected File mementoDirBackup;
+    
+    protected Application origApp;
+    protected Application newApp;
+    protected ManagementContext newManagementContext;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        mementoDir = Os.newTempDir(getClass());
+        File mementoDirParent = mementoDir.getParentFile();
+        mementoDirBackup = new File(mementoDirParent, 
mementoDir.getName()+"."+Identifiers.makeRandomId(4)+".bak");
+
+        origManagementContext = createOrigManagementContext();
+        origApp = 
origManagementContext.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+        LOG.info("Test "+getClass()+" persisting to "+mementoDir);
+        
+        super.setUp();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (origApp != null) 
Entities.destroyAll(origApp.getManagementContext());
+        if (newApp != null) Entities.destroyAll(newApp.getManagementContext());
+        if (newManagementContext != null) 
Entities.destroyAll(newManagementContext);
+        origApp = null;
+        newApp = null;
+        newManagementContext = null;
+
+        if (origManagementContext != null) 
Entities.destroyAll(origManagementContext);
+        if (mementoDir != null) 
FileBasedObjectStore.deleteCompletely(mementoDir);
+        if (mementoDirBackup != null) 
FileBasedObjectStore.deleteCompletely(mementoDir);
+        origManagementContext = null;
+    }
+
+    @Override
+    protected NodeCreator newNodeCreator() {
+        return new NodeCreatorForRebinding();
+    }
+    public static class NodeCreatorForRebinding extends NodeCreator {
+        @Override
+        public Set<? extends NodeMetadata> 
listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
+            NodeMetadata result = new NodeMetadataBuilder()
+                    .id(nodeId)
+                    
.credentials(LoginCredentials.builder().identity("dummy").credential("dummy").build())
+                    .loginPort(22)
+                    .status(Status.RUNNING)
+                    .publicAddresses(ImmutableList.of(nodePublicAddress))
+                    .privateAddresses(ImmutableList.of(nodePrivateAddress))
+                    .build();
+            return 
ImmutableSet.copyOf(Iterables.filter(ImmutableList.of(result), filter));
+        }
+        @Override
+        protected NodeMetadata newNode(String group, Template template) {
+            throw new UnsupportedOperationException();
+        }
+    };
+
+    @Test
+    public void testRebind() throws Exception {
+        String spec = 
"jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\",user=\"myuser\",password=\"mypassword\",hosts=\""+nodeId+"\")";
+        Map<?,?> specFlags = 
ImmutableMap.of(JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, 
computeServiceRegistry);
+
+        FixedListMachineProvisioningLocation<MachineLocation> location = 
getLocationManaged(spec, specFlags);
+        JcloudsSshMachineLocation machine = (JcloudsSshMachineLocation) 
Iterables.getOnlyElement(location.getAllMachines());
+        
+        rebind();
+
+        FixedListMachineProvisioningLocation<?> newLocation = 
(FixedListMachineProvisioningLocation<?>) 
newManagementContext.getLocationManager().getLocation(location.getId());
+        JcloudsSshMachineLocation newMachine = (JcloudsSshMachineLocation) 
newManagementContext.getLocationManager().getLocation(machine.getId());
+        assertNotNull(newLocation);
+        assertEquals(newMachine.getJcloudsId(), nodeId);
+    }
+
+    @SuppressWarnings("unchecked")
+    private FixedListMachineProvisioningLocation<MachineLocation> 
getLocationManaged(String val, Map<?,?> specFlags) {
+        return (FixedListMachineProvisioningLocation<MachineLocation>) 
managementContext.getLocationRegistry().getLocationManaged(val, specFlags);
+    }
+
+
+    
/////////////////////////////////////////////////////////////////////////////////////////////
+    // Everything below this point is copied from RebindTestFixture (or is a 
tweak required to // 
+    // make it work like that). We need this because we are extending          
                //
+    // AbstractJcloudsStubbedLiveTest. In comparison, most rebind tests extend 
                //
+    // RebindTestFixtureWithApp.                                               
                //
+    
/////////////////////////////////////////////////////////////////////////////////////////////
+
+    // called by super.setUp
+    @Override
+    protected LocalManagementContext newManagementContext() {
+        return origManagementContext;
+    }
+
+    protected Application rebind() throws Exception {
+        return rebind(RebindOptions.create());
+    }
+    
+    protected Application rebind(RebindOptions options) throws Exception {
+        if (newApp != null || newManagementContext != null) {
+            throw new IllegalStateException("already rebound - use 
switchOriginalToNewManagementContext() if you are trying to rebind multiple 
times");
+        }
+        
+        options = RebindOptions.create(options);
+        if (options.classLoader == null) options.classLoader(classLoader);
+        if (options.mementoDir == null) options.mementoDir(mementoDir);
+        if (options.origManagementContext == null) 
options.origManagementContext(origManagementContext);
+        if (options.newManagementContext == null) 
options.newManagementContext(createNewManagementContext(options.mementoDir));
+        
+        RebindTestUtils.waitForPersisted(origApp);
+        
+        newManagementContext = options.newManagementContext;
+        newApp = RebindTestUtils.rebind(options);
+        return newApp;
+    }
+    
+    /** @return A started management context */
+    protected LocalManagementContext createOrigManagementContext() {
+        return RebindTestUtils.managementContextBuilder(mementoDir, 
classLoader)
+                .persistPeriodMillis(getPersistPeriodMillis())
+                .forLive(useLiveManagementContext())
+                .emptyCatalog(useEmptyCatalog())
+                .properties(createBrooklynProperties())
+                .buildStarted();
+    }
+
+    /** As {@link #createNewManagementContext(File)} using the default memento 
dir */
+    protected LocalManagementContext createNewManagementContext() {
+        return createNewManagementContext(mementoDir);
+    }
+    
+    /** @return An unstarted management context using the specified mementoDir 
(or default if null) */
+    protected LocalManagementContext createNewManagementContext(File 
mementoDir) {
+        if (mementoDir==null) mementoDir = this.mementoDir;
+        return RebindTestUtils.managementContextBuilder(mementoDir, 
classLoader)
+                .forLive(useLiveManagementContext())
+                .emptyCatalog(useEmptyCatalog())
+                .properties(createBrooklynProperties())
+                .buildUnstarted();
+    }
+    
+    private BrooklynProperties createBrooklynProperties() {
+        // This really is stubbed; no live access to the cloud
+        BrooklynProperties brooklynProperties = 
BrooklynProperties.Factory.newEmpty();
+        
brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".identity",
 "myidentity");
+        
brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".credential",
 "mycredential");
+        return brooklynProperties;
+    }
+
+    protected boolean useLiveManagementContext() {
+        return false;
+    }
+
+    protected boolean useEmptyCatalog() {
+        return true;
+    }
+
+    protected int getPersistPeriodMillis() {
+        return 1;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java
 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java
new file mode 100644
index 0000000..86a66f7
--- /dev/null
+++ 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
+import org.jclouds.compute.domain.ComputeMetadata;
+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 com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class JcloudsByonLocationResolverStubbedTest extends 
AbstractJcloudsStubbedLiveTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = 
LoggerFactory.getLogger(JcloudsByonLocationResolverStubbedTest.class);
+    
+    private final String nodeId = "mynodeid";
+    private final String nodePublicAddress = "173.194.32.123";
+    private final String nodePrivateAddress = "172.168.10.11";
+    
+    protected LocalManagementContext newManagementContext() {
+        // This really is stubbed; no live access to the cloud
+        LocalManagementContext result = 
LocalManagementContextForTests.builder(true).build();
+        BrooklynProperties brooklynProperties = result.getBrooklynProperties();
+        
brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".identity",
 "myidentity");
+        
brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".credential",
 "mycredential");
+        return result;
+
+    }
+
+    @Override
+    protected NodeCreator newNodeCreator() {
+        return new NodeCreator() {
+            @Override
+            public Set<? extends NodeMetadata> 
listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
+                NodeMetadata result = new NodeMetadataBuilder()
+                        .id(nodeId)
+                        
.credentials(LoginCredentials.builder().identity("dummy").credential("dummy").build())
+                        .loginPort(22)
+                        .status(Status.RUNNING)
+                        .publicAddresses(ImmutableList.of(nodePublicAddress))
+                        .privateAddresses(ImmutableList.of(nodePrivateAddress))
+                        .build();
+                return 
ImmutableSet.copyOf(Iterables.filter(ImmutableList.of(result), filter));
+            }
+            @Override
+            protected NodeMetadata newNode(String group, Template template) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Test
+    public void testResolvesHostInSpec() throws Exception {
+        String spec = 
"jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\",user=\"myuser\",password=\"mypassword\",hosts=\""+nodeId+"\")";
+        Map<?,?> specFlags = 
ImmutableMap.of(JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, 
computeServiceRegistry);
+
+        FixedListMachineProvisioningLocation<MachineLocation> location = 
getLocationManaged(spec, specFlags);
+        
+        JcloudsSshMachineLocation machine = (JcloudsSshMachineLocation) 
Iterables.getOnlyElement(location.getAllMachines());
+        assertEquals(machine.getJcloudsId(), nodeId);
+        assertEquals(machine.getPublicAddresses(), 
ImmutableSet.of(nodePublicAddress));
+        assertEquals(machine.getPrivateAddresses(), 
ImmutableSet.of(nodePrivateAddress));
+        
+        // TODO what user/password should we expect? Fails below because has 
"dummy":
+//        assertEquals(machine.getUser(), "myuser");
+//        assertEquals(machine.config().get(JcloudsLocationConfig.PASSWORD), 
"mypassword");
+    }
+
+    @Test
+    public void testResolvesHostStringInFlags() throws Exception {
+        runResolvesHostsInFlags(nodeId);
+    }
+
+    @Test
+    public void testResolvesHostsListInFlags() throws Exception {
+        runResolvesHostsInFlags(ImmutableList.of(nodeId));
+    }
+    
+    @Test
+    public void testResolvesHostsListOfMapsInFlags() throws Exception {
+        runResolvesHostsInFlags(ImmutableList.of(ImmutableMap.of("id", 
nodeId)));
+    }
+    
+    protected void runResolvesHostsInFlags(Object hostsValInFlags) throws 
Exception {
+        String spec = 
"jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\")";
+        Map<?,?> specFlags = ImmutableMap.of(
+                JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, 
computeServiceRegistry,
+                "hosts", hostsValInFlags);
+
+        FixedListMachineProvisioningLocation<MachineLocation> location = 
getLocationManaged(spec, specFlags);
+        
+        JcloudsSshMachineLocation machine = (JcloudsSshMachineLocation) 
Iterables.getOnlyElement(location.getAllMachines());
+        assertEquals(machine.getJcloudsId(), nodeId);
+    }
+
+    @Test
+    public void testLocationSpecDoesNotCreateMachines() throws Exception {
+        Collection<Location> before = 
managementContext.getLocationManager().getLocations();
+        String spec = 
"jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\",user=\"myname\",hosts=\""+nodeId+"\")";
+        Map<?,?> specFlags = 
ImmutableMap.of(JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, 
computeServiceRegistry);
+
+        @SuppressWarnings("unused")
+        LocationSpec<FixedListMachineProvisioningLocation<MachineLocation>> 
locationSpec = getLocationSpec(spec, specFlags);
+        
+        Collection<Location> after = 
managementContext.getLocationManager().getLocations();
+        assertEquals(after, before, "after="+after+"; before="+before);
+    }
+
+    @SuppressWarnings("unchecked")
+    private 
LocationSpec<FixedListMachineProvisioningLocation<MachineLocation>> 
getLocationSpec(String val, Map<?,?> specFlags) {
+        return 
(LocationSpec<FixedListMachineProvisioningLocation<MachineLocation>>) 
managementContext.getLocationRegistry().getLocationSpec(val, specFlags).get();
+    }
+    
+    @SuppressWarnings("unchecked")
+    private FixedListMachineProvisioningLocation<MachineLocation> 
getLocationManaged(String val, Map<?,?> specFlags) {
+        return (FixedListMachineProvisioningLocation<MachineLocation>) 
managementContext.getLocationRegistry().getLocationManaged(val, specFlags);
+    }
+}

Reply via email to