You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:30 UTC

[07/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
index 0000000,173b695..a0fa874
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
@@@ -1,0 -1,44 +1,61 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.location.jclouds;
+ 
+ import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.core.location.HasSubnetHostname;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.Template;
+ 
++import com.google.common.base.Optional;
++
+ public interface JcloudsMachineLocation extends MachineLocation, HasSubnetHostname {
+     
+     @Override
+     public JcloudsLocation getParent();
+     
++    public Optional<NodeMetadata> getOptionalNode();
++
++    /**
++     * @deprecated since 0.9.0; instead use {@link #getOptionalNode()}. After rebind, the node will 
++     *             not be available if the VM is no longer running.
++     * 
++     * @throws IllegalStateException If the node is not available (i.e. not cached, and cannot be  
++     *         found from cloud provider).
++     */
++    @Deprecated
+     public NodeMetadata getNode();
+     
++    /**
++     * @deprecated since 0.9.0; instead use {@link #getOptionalNode()}. After rebind, the node will not
++     * be available if the VM is no longer running.
++     */
++    @Deprecated
+     public Template getTemplate();
+ 
+     public String getJcloudsId();
+ 
+     /** In most clouds, the public hostname is the only way to ensure VMs in different zones can access each other. */
+     @Override
+     public String getSubnetHostname();
+ 
+     String getUser();
+ 
+     int getPort();
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
index 0000000,2db43d1..cddc98b
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
@@@ -1,0 -1,336 +1,596 @@@
+ /*
+  * 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.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+ 
 -import java.net.InetAddress;
 -import java.net.UnknownHostException;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.TimeoutException;
+ 
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.api.location.HardwareDetails;
+ import org.apache.brooklyn.api.location.MachineDetails;
+ import org.apache.brooklyn.api.location.OsDetails;
+ import org.apache.brooklyn.core.location.BasicHardwareDetails;
+ import org.apache.brooklyn.core.location.BasicMachineDetails;
+ import org.apache.brooklyn.core.location.BasicOsDetails;
++import org.apache.brooklyn.core.location.LocationConfigUtils;
++import org.apache.brooklyn.core.location.LocationConfigUtils.OsCredential;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.jclouds.compute.ComputeServiceContext;
+ import org.jclouds.compute.callables.RunScriptOnNode;
+ 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.OperatingSystem;
+ import org.jclouds.compute.domain.OsFamily;
+ import org.jclouds.compute.domain.Processor;
+ import org.jclouds.compute.domain.Template;
+ import org.jclouds.compute.options.RunScriptOptions;
+ import org.jclouds.domain.LoginCredentials;
+ import org.jclouds.scriptbuilder.domain.InterpretableStatement;
+ import org.jclouds.scriptbuilder.domain.Statement;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
++import com.google.common.annotations.VisibleForTesting;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Optional;
+ import com.google.common.base.Throwables;
+ import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.ImmutableSet;
+ import com.google.common.net.HostAndPort;
+ import com.google.common.util.concurrent.ListenableFuture;
+ 
+ public class JcloudsSshMachineLocation extends SshMachineLocation implements JcloudsMachineLocation {
+     
+     private static final Logger LOG = LoggerFactory.getLogger(JcloudsSshMachineLocation.class);
+ 
+     @SetFromFlag
+     JcloudsLocation jcloudsParent;
+     
++    /**
++     * @deprecated since 0.9.0; the node should not be persisted.
++     */
+     @SetFromFlag
++    @Deprecated
+     NodeMetadata node;
+     
++    /**
++     * @deprecated since 0.9.0; the template should not be persisted.
++     */
+     @SetFromFlag
++    @Deprecated
+     Template template;
+     
++    @SetFromFlag
++    String nodeId;
++
++    @SetFromFlag
++    String imageId;
++
++    @SetFromFlag
++    Set<String> privateAddresses;
++    
++    @SetFromFlag
++    Set<String> publicAddresses;
++
++    @SetFromFlag
++    String hostname;
++
++    // Populated lazily, on first call to getSubnetHostname()
++    @SetFromFlag
++    String privateHostname;
++
++    /**
++     * Historically, "node" and "template" were persisted. However that is a very bad idea!
++     * It means we pull in lots of jclouds classes into the persisted state. We are at an  
++     * intermediate stage, where we want to handle rebinding to old state that has "node"
++     * and new state that should not. We therefore leave in the {@code @SetFromFlag} on node
++     * so that we read it back, but we ensure the value is null when we write it out!
++     * 
++     * TODO We will rename these to get rid of the ugly underscore when the old node/template 
++     * fields are deleted.
++     */
++    private transient Optional<NodeMetadata> _node;
++
++    private transient Optional<Template> _template;
++
++    private transient Optional<Image> _image;
++
+     private RunScriptOnNode.Factory runScriptFactory;
+     
+     public JcloudsSshMachineLocation() {
+     }
+     
+     /**
+      * @deprecated since 0.6; use LocationSpec (which calls no-arg constructor)
+      */
+     @Deprecated
+     public JcloudsSshMachineLocation(Map<?,?> flags, JcloudsLocation jcloudsParent, NodeMetadata node) {
+         super(flags);
+         this.jcloudsParent = jcloudsParent;
 -        this.node = node;
+         
+         init();
+     }
+ 
+     @Override
+     public void init() {
+         if (jcloudsParent != null) {
+             super.init();
+             ComputeServiceContext context = jcloudsParent.getComputeService().getContext();
+             runScriptFactory = context.utils().injector().getInstance(RunScriptOnNode.Factory.class);
++            if (node != null) {
++                setNode(node);
++            }
++            if (template != null) {
++                setTemplate(template);
++            }
+         } else {
+             // TODO Need to fix the rebind-detection, and not call init() on rebind.
+             // This will all change when locations become entities.
+             if (LOG.isDebugEnabled()) LOG.debug("Not doing init() of {} because parent not set; presuming rebinding", this);
+         }
+     }
+     
+     @Override
+     public void rebind() {
+         super.rebind();
+         ComputeServiceContext context = jcloudsParent.getComputeService().getContext();
+         runScriptFactory = context.utils().injector().getInstance(RunScriptOnNode.Factory.class);
++        
++        if (node != null) {
++            setNode(node);
++            node = null;
++        }
++
++        if (template != null) {
++            setTemplate(template);
++            template = null;
++        }
+     }
+     
+     @Override
+     public String toVerboseString() {
+         return Objects.toStringHelper(this).omitNullValues()
+                 .add("id", getId()).add("name", getDisplayName())
+                 .add("user", getUser()).add("address", getAddress()).add("port", getConfig(SSH_PORT))
 -                .add("node", getNode())
 -                .add("jcloudsId", getJcloudsId())
 -                .add("privateAddresses", node.getPrivateAddresses())
 -                .add("publicAddresses", node.getPublicAddresses())
++                .add("node", _node)
++                .add("nodeId", getJcloudsId())
++                .add("imageId", getImageId())
++                .add("privateAddresses", getPrivateAddresses())
++                .add("publicAddresses", getPublicAddresses())
+                 .add("parentLocation", getParent())
 -                .add("osDetails", getOsDetails())
++                .add("osDetails", getOptionalOsDetails())
+                 .toString();
+     }
+ 
++    protected void setNode(NodeMetadata node) {
++        this.node = null;
++        nodeId = node.getId();
++        imageId = node.getImageId();
++        privateAddresses = node.getPrivateAddresses();
++        publicAddresses = node.getPublicAddresses();
++        hostname = node.getHostname();
++        _node = Optional.of(node);
++    }
++
++    protected void setTemplate(Template template) {
++        this.template = null;
++        _template = Optional.of(template);
++        _image = Optional.fromNullable(template.getImage());
++    }
++
++    @Override
++    public Optional<NodeMetadata> getOptionalNode() {
++      if (_node == null) {
++          _node = Optional.fromNullable(getParent().getComputeService().getNodeMetadata(nodeId));
++      }
++      return _node;
++    }
++
++    protected Optional<Image> getOptionalImage() {
++      if (_image == null) {
++          _image = Optional.fromNullable(getParent().getComputeService().getImage(imageId));
++      }
++      return _image;
++    }
++
++    /**
++     * @since 0.9.0
++     * @deprecated since 0.9.0 (only added as aid until the deprecated {@link #getTemplate()} is deleted)
++     */
++    @Deprecated
++    protected Optional<Template> getOptionalTemplate() {
++        if (_template == null) {
++            _template = Optional.absent();
++        }
++        return _template;
++    }
++
++    /**
++     * @deprecated since 0.9.0
++     */
+     @Override
++    @Deprecated
+     public NodeMetadata getNode() {
 -        return node;
++        Optional<NodeMetadata> result = getOptionalNode();
++        if (result.isPresent()) {
++            return result.get();
++        } else {
++            throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++        }
++    }
++    
++    @VisibleForTesting
++    Optional<NodeMetadata> peekNode() {
++        return _node;
+     }
+     
++    /**
++     * @deprecated since 0.9.0
++     */
+     @Override
++    @Deprecated
+     public Template getTemplate() {
 -        return template;
++        Optional<Template> result = getOptionalTemplate();
++        if (result.isPresent()) {
++            String msg = "Deprecated use of getTemplate() for "+this;
++            LOG.warn(msg + " - see debug log for stacktrace");
++            LOG.debug(msg, new Exception("for stacktrace"));
++            return result.get();
++        } else {
++            throw new IllegalStateException("Template for "+nodeId+" (in "+getParent()+") not cached (deprecated use of getTemplate())");
++        }
+     }
+     
+     @Override
+     public JcloudsLocation getParent() {
+         return jcloudsParent;
+     }
+     
+     @Override
+     public String getHostname() {
 -        return node.getHostname();
++        // Changed behaviour in Brooklyn 0.9.0. Previously it just did node.getHostname(), which
++        // was wrong on some clouds (e.g. vcloud-director, where VMs are often given a random 
++        // hostname that does not resolve on the VM and is not in any DNS).
++        // Now delegates to jcloudsParent.getPublicHostname(node).
++        if (privateHostname == null) {
++            Optional<NodeMetadata> node = getOptionalNode();
++            if (node.isPresent()) {
++                HostAndPort sshHostAndPort = getSshHostAndPort();
++                LoginCredentials creds = getLoginCredentials();
++                hostname = jcloudsParent.getPublicHostname(node.get(), Optional.of(sshHostAndPort), creds, config().getBag());
++                requestPersist();
++
++            } else {
++                // Fallback: impl taken (mostly) from jcloudsParent.getPublicHostnameGeneric(NodeMetadata, ConfigBag).
++                // But we won't have a node object (e.g. after rebind, and VM has been terminated).
++                // We also resort to address.getHostAddress as final fallback.
++                if (groovyTruth(getPublicAddresses())) {
++                    hostname = getPublicAddresses().iterator().next();
++                } else if (groovyTruth(getPrivateAddresses())) {
++                    hostname = getPrivateAddresses().iterator().next();
++                } else {
++                    hostname = getAddress().getHostAddress();
++                }
++            }
++            LOG.debug("Resolved hostname {} for {}", hostname, this);
++            requestPersist();
++        }
++        return hostname;
+     }
+     
 -    /** In most clouds, the public hostname is the only way to ensure VMs in different zones can access each other. */
++    /** In clouds like AWS, the public hostname is the only way to ensure VMs in different zones can access each other. */
+     @Override
+     public Set<String> getPublicAddresses() {
 -        return node.getPublicAddresses();
++        return (publicAddresses == null) ? ImmutableSet.<String>of() : publicAddresses;
+     }
+     
+     @Override
+     public Set<String> getPrivateAddresses() {
 -        return node.getPrivateAddresses();
++        return (privateAddresses == null) ? ImmutableSet.<String>of() : privateAddresses;
+     }
+ 
+     @Override
+     public String getSubnetHostname() {
 -        String privateHostname = jcloudsParent.getPrivateHostname(node, Optional.<HostAndPort>absent(), config().getBag());
++        if (privateHostname == null) {
++            Optional<NodeMetadata> node = getOptionalNode();
++            if (node.isPresent()) {
++                // Prefer jcloudsLocation.getPrivateHostname(): it handles AWS hostname in a special way, 
++                // by querying AWS for the hostname that resolves both inside and outside of the region.
++                // If we can't get the node (i.e. the cloud provider doesn't know that id, because it has
++                // been terminated), then we don't care as much about getting the right id!
++                HostAndPort sshHostAndPort = getSshHostAndPort();
++                LoginCredentials creds = getLoginCredentials();
++                privateHostname = jcloudsParent.getPrivateHostname(node.get(), Optional.of(sshHostAndPort), creds, config().getBag());
++
++            } else {
++                // Fallback: impl taken from jcloudsParent.getPrivateHostnameGeneric(NodeMetadata, ConfigBag).
++                // But we won't have a node object (e.g. after rebind, and VM has been terminated).
++                //prefer the private address to the hostname because hostname is sometimes wrong/abbreviated
++                //(see that javadoc; also e.g. on rackspace/cloudstack, the hostname is not registered with any DNS).
++                //Don't return local-only address (e.g. never 127.0.0.1)
++                for (String p : getPrivateAddresses()) {
++                    if (Networking.isLocalOnly(p)) continue;
++                    privateHostname = p;
++                    break;
++                }
++                if (Strings.isBlank(privateHostname) && groovyTruth(getPublicAddresses())) {
++                    privateHostname = getPublicAddresses().iterator().next();
++                } else if (Strings.isBlank(privateHostname)) {
++                    privateHostname = getHostname();
++                }
++            }
++            requestPersist();
++            LOG.debug("Resolved subnet hostname {} for {}", privateHostname, this);
++        }
++        
+         return privateHostname;
+     }
+ 
+     @Override
+     public String getSubnetIp() {
++        // Previous to Brooklyn 0.9.0, this could return the hostname or would try to do
++        // jcloudsParent.getPublicHostname, and return the resolved IP. That was clearly 
++        // not a "subnet ip"!
+         Optional<String> privateAddress = getPrivateAddress();
+         if (privateAddress.isPresent()) {
+             return privateAddress.get();
+         }
 -
 -        String hostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag());
 -        if (hostname != null && !Networking.isValidIp4(hostname)) {
 -            try {
 -                return InetAddress.getByName(hostname).getHostAddress();
 -            } catch (UnknownHostException e) {
 -                LOG.debug("Cannot resolve IP for hostname {} of machine {} (so returning hostname): {}", new Object[] {hostname, this, e});
 -            }
++        if (groovyTruth(node.getPublicAddresses())) {
++            return node.getPublicAddresses().iterator().next();
+         }
 -        return hostname;
++        return null;
+     }
+ 
+     protected Optional<String> getPrivateAddress() {
 -        if (groovyTruth(node.getPrivateAddresses())) {
 -            for (String p : node.getPrivateAddresses()) {
 -                // disallow local only addresses
 -                if (Networking.isLocalOnly(p)) continue;
 -                // other things may be public or private, but either way, return it
 -                return Optional.of(p);
 -            }
++        for (String p : getPrivateAddresses()) {
++            // disallow local only addresses
++            if (Networking.isLocalOnly(p)) continue;
++            // other things may be public or private, but either way, return it
++            return Optional.of(p);
+         }
+         return Optional.absent();
+     }
+     
+     @Override
+     public String getJcloudsId() {
 -        return node.getId();
++        return nodeId;
+     }
+     
++    protected String getImageId() {
++        return imageId;
++    }
++
+     /** executes the given statements on the server using jclouds ScriptBuilder,
+      * wrapping in a script which is polled periodically.
+      * the output is returned once the script completes (disadvantage compared to other methods)
+      * but the process is nohupped and the SSH session is not kept, 
+      * so very useful for long-running processes
++     * 
++     * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
+      */
++    @Deprecated
+     public ListenableFuture<ExecResponse> submitRunScript(String ...statements) {
+         return submitRunScript(new InterpretableStatement(statements));
+     }
++
++    /**
++     * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
++     */
++    @Deprecated
+     public ListenableFuture<ExecResponse> submitRunScript(Statement script) {
+         return submitRunScript(script, new RunScriptOptions());            
+     }
++    
++    /**
++     * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
++     */
++    @Deprecated
+     public ListenableFuture<ExecResponse> submitRunScript(Statement script, RunScriptOptions options) {
 -        return runScriptFactory.submit(node, script, options);
++        Optional<NodeMetadata> node = getOptionalNode();
++        if (node.isPresent()) {
++            return runScriptFactory.submit(node.get(), script, options);
++        } else {
++            throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++        }
+     }
 -    /** uses submitRunScript to execute the commands, and throws error if it fails or returns non-zero */
++    
++    /**
++     * Uses submitRunScript to execute the commands, and throws error if it fails or returns non-zero
++     * 
++     * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
++     */
++    @Deprecated
+     public void execRemoteScript(String ...commands) {
+         try {
+             ExecResponse result = submitRunScript(commands).get();
+             if (result.getExitStatus()!=0)
+                 throw new IllegalStateException("Error running remote commands (code "+result.getExitStatus()+"): "+commands);
+         } catch (InterruptedException e) {
+             Thread.currentThread().interrupt();
+             throw Throwables.propagate(e);
+         } catch (ExecutionException e) {
+             throw Throwables.propagate(e);
+         }
+     }
+ 
+     /**
+      * Retrieves the password for this VM, if one exists. The behaviour/implementation is different for different clouds.
+      * e.g. on Rackspace, the password for a windows VM is available immediately; on AWS-EC2, for a Windows VM you need 
+      * to poll repeatedly until the password is available which can take up to 15 minutes.
++     * 
++     * @deprecated since 0.9.0; use the machine to execute commands, so no need to extract the password
+      */
++    @Deprecated
+     public String waitForPassword() {
 -        // TODO Hacky; don't want aws specific stuff here but what to do?!
 -        if (jcloudsParent.getProvider().equals("aws-ec2")) {
 -            try {
 -                return JcloudsUtil.waitForPasswordOnAws(jcloudsParent.getComputeService(), node, 15, TimeUnit.MINUTES);
 -            } catch (TimeoutException e) {
 -                throw Throwables.propagate(e);
++        Optional<NodeMetadata> node = getOptionalNode();
++        if (node.isPresent()) {
++            // TODO Hacky; don't want aws specific stuff here but what to do?!
++            if (jcloudsParent.getProvider().equals("aws-ec2")) {
++                try {
++                    return JcloudsUtil.waitForPasswordOnAws(jcloudsParent.getComputeService(), node.get(), 15, TimeUnit.MINUTES);
++                } catch (TimeoutException e) {
++                    throw Throwables.propagate(e);
++                }
++            } else {
++                LoginCredentials credentials = node.get().getCredentials();
++                return (credentials != null) ? credentials.getOptionalPassword().orNull() : null;
+             }
+         } else {
 -            LoginCredentials credentials = node.getCredentials();
 -            return (credentials != null) ? credentials.getPassword() : null;
++            throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++        }
++    }
++
++    private LoginCredentials getLoginCredentials() {
++        OsCredential creds = LocationConfigUtils.getOsCredential(config().getBag());
++        
++        return LoginCredentials.builder()
++                .user(getUser())
++                .privateKey(creds.hasKey() ? creds.getPrivateKeyData() : null)
++                .password(creds.hasPassword() ? creds.getPassword() : null)
++                .build();
++    }
++
++    /**
++     * Returns the known OsDetails, without any attempt to retrieve them if not known.
++     */
++    protected Optional<OsDetails> getOptionalOsDetails() {
++        Optional<MachineDetails> machineDetails = getOptionalMachineDetails();
++        OsDetails result = machineDetails.isPresent() ? machineDetails.get().getOsDetails() : null;
++        return Optional.fromNullable(result);
++    }
++
++    protected Optional<OperatingSystem> getOptionalOperatingSystem() {
++        Optional<NodeMetadata> node = getOptionalNode();
++        
++        OperatingSystem os = node.isPresent() ? node.get().getOperatingSystem() : null;
++        if (os == null) {
++            // some nodes (eg cloudstack, gce) might not get OS available on the node,
++            // so also try taking it from the image if available
++            Optional<Image> image = getOptionalImage();
++            if (image.isPresent()) {
++                os = image.get().getOperatingSystem();
++            }
+         }
++        return Optional.fromNullable(os);
+     }
+ 
++    protected Optional<Hardware> getOptionalHardware() {
++        Optional<NodeMetadata> node = getOptionalNode();
++        return Optional.fromNullable(node.isPresent() ? node.get().getHardware() : null);
++    }
++    
+     @Override
+     protected MachineDetails inferMachineDetails() {
+         Optional<String> name = Optional.absent();
+         Optional<String> version = Optional.absent();
+         Optional<String> architecture = Optional.absent();
 -
 -        OperatingSystem os = node.getOperatingSystem();
 -        if (os == null && getTemplate() != null && getTemplate().getImage() != null)
 -            // some nodes (eg cloudstack, gce) might not get OS available on the node,
 -            // so also try taking it from the template if available
 -            os = getTemplate().getImage().getOperatingSystem();
 -
 -        if (os != null) {
++        Optional<OperatingSystem> os = getOptionalOperatingSystem();
++        Optional<Hardware> hardware = getOptionalHardware();
++        
++        if (os.isPresent()) {
+             // Note using family rather than name. Name is often unset.
 -            name = Optional.fromNullable(os.getFamily() != null && !OsFamily.UNRECOGNIZED.equals(os.getFamily()) ? os.getFamily().toString() : null);
 -            version = Optional.fromNullable(!Strings.isBlank(os.getVersion()) ? os.getVersion() : null);
+             // Using is64Bit rather then getArch because getArch often returns "paravirtual"
 -            architecture = Optional.fromNullable(os.is64Bit() ? BasicOsDetails.OsArchs.X_86_64 : BasicOsDetails.OsArchs.I386);
++            OsFamily family = os.get().getFamily();
++            String versionRaw = os.get().getVersion();
++            boolean is64Bit = os.get().is64Bit();
++            name = Optional.fromNullable(family != null && !OsFamily.UNRECOGNIZED.equals(family) ? family.toString() : null);
++            version = Optional.fromNullable(Strings.isNonBlank(versionRaw) ? versionRaw : null);
++            architecture = Optional.fromNullable(is64Bit ? BasicOsDetails.OsArchs.X_86_64 : BasicOsDetails.OsArchs.I386);
+         }
+ 
 -        Hardware hardware = node.getHardware();
 -        Optional<Integer> ram = hardware==null ? Optional.<Integer>absent() : Optional.fromNullable(hardware.getRam());
 -        Optional<Integer> cpus = hardware==null ? Optional.<Integer>absent() : Optional.fromNullable(hardware.getProcessors() != null ? hardware.getProcessors().size() : null);
++        Optional<Integer> ram = hardware.isPresent() ? Optional.fromNullable(hardware.get().getRam()) : Optional.<Integer>absent();
++        Optional<Integer> cpus = hardware.isPresent() ? Optional.fromNullable(hardware.get().getProcessors() != null ? hardware.get().getProcessors().size() : null) : Optional.<Integer>absent();
+ 
+         // Skip superclass' SSH to machine if all data is present, otherwise defer to super
+         if (name.isPresent() && version.isPresent() && architecture.isPresent() && ram.isPresent() && cpus.isPresent()) {
+             if (LOG.isTraceEnabled()) {
+                 LOG.trace("Gathered machine details from Jclouds, skipping SSH test on {}", this);
+             }
+             OsDetails osD = new BasicOsDetails(name.get(), architecture.get(), version.get());
+             HardwareDetails hwD = new BasicHardwareDetails(cpus.get(), ram.get());
+             return new BasicMachineDetails(hwD, osD);
+         } else if ("false".equalsIgnoreCase(getConfig(JcloudsLocation.WAIT_FOR_SSHABLE))) {
+             if (LOG.isTraceEnabled()) {
+                 LOG.trace("Machine details for {} missing from Jclouds, but skipping SSH test because waitForSshable=false. name={}, version={}, " +
+                         "arch={}, ram={}, #cpus={}",
+                         new Object[]{this, name, version, architecture, ram, cpus});
+             }
+             OsDetails osD = new BasicOsDetails(name.orNull(), architecture.orNull(), version.orNull());
+             HardwareDetails hwD = new BasicHardwareDetails(cpus.orNull(), ram.orNull());
+             return new BasicMachineDetails(hwD, osD);
+         } else {
+             if (LOG.isTraceEnabled()) {
+                 LOG.trace("Machine details for {} missing from Jclouds, using SSH test instead. name={}, version={}, " +
+                                 "arch={}, ram={}, #cpus={}",
+                         new Object[]{this, name, version, architecture, ram, cpus});
+             }
+             return super.inferMachineDetails();
+         }
+     }
+ 
+     @Override
+     public Map<String, String> toMetadataRecord() {
 -        Hardware hardware = node.getHardware();
 -        List<? extends Processor> processors = (hardware != null) ? hardware.getProcessors() : null;
++        Optional<NodeMetadata> node = getOptionalNode();
++        
++        Optional<Hardware> hardware = getOptionalHardware();
++        List<? extends Processor> processors = hardware.isPresent() ? hardware.get().getProcessors() : null;
+         
+         ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+         builder.putAll(super.toMetadataRecord());
+         putIfNotNull(builder, "provider", getParent().getProvider());
+         putIfNotNull(builder, "account", getParent().getIdentity());
 -        putIfNotNull(builder, "serverId", node.getProviderId());
 -        putIfNotNull(builder, "imageId", node.getImageId());
 -        putIfNotNull(builder, "instanceTypeName", (hardware != null ? hardware.getName() : null));
 -        putIfNotNull(builder, "instanceTypeId", (hardware != null ? hardware.getProviderId() : null));
 -        putIfNotNull(builder, "ram", "" + (hardware != null ? hardware.getRam() : null));
++        putIfNotNull(builder, "region", getParent().getRegion());
++        putIfNotNull(builder, "serverId", getJcloudsId());
++        putIfNotNull(builder, "imageId", getImageId());
++        putIfNotNull(builder, "instanceTypeName", (hardware.isPresent() ? hardware.get().getName() : null));
++        putIfNotNull(builder, "instanceTypeId", (hardware.isPresent() ? hardware.get().getProviderId() : null));
++        putIfNotNull(builder, "ram", "" + (hardware.isPresent() ? hardware.get().getRam() : null));
+         putIfNotNull(builder, "cpus", "" + (processors != null ? processors.size() : null));
+         
+         try {
+             OsDetails osDetails = getOsDetails();
+             putIfNotNull(builder, "osName", osDetails.getName());
+             putIfNotNull(builder, "osArch", osDetails.getArch());
+             putIfNotNull(builder, "is64bit", osDetails.is64bit() ? "true" : "false");
+         } catch (Exception e) {
+             Exceptions.propagateIfFatal(e);
+             LOG.warn("Unable to get OS Details for "+node+"; continuing", e);
+         }
+         
+         return builder.build();
+     }
+     
+     private void putIfNotNull(ImmutableMap.Builder<String, String> builder, String key, @Nullable String value) {
+         if (value != null) builder.put(key, value);
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
index 0000000,7b84432..b58e783
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
@@@ -1,0 -1,153 +1,308 @@@
+ /*
+  * 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.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+ 
+ import java.net.InetAddress;
 -import java.net.UnknownHostException;
 -import java.util.Iterator;
+ import java.util.Set;
+ 
+ import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.net.Networking;
++import org.jclouds.compute.ComputeServiceContext;
++import org.jclouds.compute.callables.RunScriptOnNode;
++import org.jclouds.compute.domain.Image;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.Template;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
++import com.google.common.annotations.VisibleForTesting;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Optional;
 -import com.google.common.net.HostAndPort;
++import com.google.common.collect.ImmutableSet;
+ 
+ public class JcloudsWinRmMachineLocation extends WinRmMachineLocation implements JcloudsMachineLocation {
+ 
+     private static final Logger LOG = LoggerFactory.getLogger(JcloudsWinRmMachineLocation.class);
+ 
+     @SetFromFlag
+     JcloudsLocation jcloudsParent;
+     
++    /**
++     * @deprecated since 0.9.0; the node should not be persisted.
++     */
+     @SetFromFlag
++    @Deprecated
+     NodeMetadata node;
+     
++    /**
++     * @deprecated since 0.9.0; the template should not be persisted.
++     */
+     @SetFromFlag
++    @Deprecated
+     Template template;
++    
++    @SetFromFlag
++    String nodeId;
++
++    @SetFromFlag
++    String imageId;
++
++    @SetFromFlag
++    Set<String> privateAddresses;
++    
++    @SetFromFlag
++    Set<String> publicAddresses;
++
++    @SetFromFlag
++    String hostname;
++
++    /**
++     * Historically, "node" and "template" were persisted. However that is a very bad idea!
++     * It means we pull in lots of jclouds classes into the persisted state. We are at an  
++     * intermediate stage, where we want to handle rebinding to old state that has "node"
++     * and new state that should not. We therefore leave in the {@code @SetFromFlag} on node
++     * so that we read it back, but we ensure the value is null when we write it out!
++     * 
++     * TODO We will rename these to get rid of the ugly underscore when the old node/template 
++     * fields are deleted.
++     */
++    private transient Optional<NodeMetadata> _node;
++
++    private transient Optional<Template> _template;
+ 
++    private transient Optional<Image> _image;
++
++    private transient String _privateHostname;
++    
+     public JcloudsWinRmMachineLocation() {
+     }
+ 
+     @Override
++    public void init() {
++        if (jcloudsParent != null) {
++            super.init();
++            if (node != null) {
++                setNode(node);
++            }
++            if (template != null) {
++                setTemplate(template);
++            }
++        } else {
++            // TODO Need to fix the rebind-detection, and not call init() on rebind.
++            // This will all change when locations become entities.
++            if (LOG.isDebugEnabled()) LOG.debug("Not doing init() of {} because parent not set; presuming rebinding", this);
++        }
++    }
++    
++    @Override
++    public void rebind() {
++        super.rebind();
++        
++        if (node != null) {
++            setNode(node);
++            node = null;
++        }
++
++        if (template != null) {
++            setTemplate(template);
++            template = null;
++        }
++    }
++    
++    @Override
+     public String toVerboseString() {
+         return Objects.toStringHelper(this).omitNullValues()
+                 .add("id", getId()).add("name", getDisplayName())
+                 .add("user", getUser())
+                 .add("address", getAddress())
+                 .add("port", getPort())
 -                .add("node", getNode())
 -                .add("jcloudsId", getJcloudsId())
 -                .add("privateAddresses", node.getPrivateAddresses())
 -                .add("publicAddresses", node.getPublicAddresses())
++                .add("node", _node)
++                .add("nodeId", getJcloudsId())
++                .add("imageId", getImageId())
++                .add("privateAddresses", getPrivateAddresses())
++                .add("publicAddresses", getPublicAddresses())
+                 .add("parentLocation", getParent())
+                 .add("osDetails", getOsDetails())
+                 .toString();
+     }
+ 
++    protected void setNode(NodeMetadata node) {
++        this.node = null;
++        nodeId = node.getId();
++        imageId = node.getImageId();
++        privateAddresses = node.getPrivateAddresses();
++        publicAddresses = node.getPublicAddresses();
++        hostname = node.getHostname();
++        _node = Optional.of(node);
++    }
++
++    protected void setTemplate(Template template) {
++        this.template = null;
++        _template = Optional.of(template);
++        _image = Optional.fromNullable(template.getImage());
++    }
++
+     @Override
+     public int getPort() {
+         return getConfig(WINRM_PORT);
+     }
+     
+     @Override
 -    public NodeMetadata getNode() {
 -        return node;
++    public JcloudsLocation getParent() {
++        return jcloudsParent;
+     }
+     
+     @Override
 -    public Template getTemplate() {
 -        return template;
++    public Optional<NodeMetadata> getOptionalNode() {
++      if (_node == null) {
++          _node = Optional.fromNullable(getParent().getComputeService().getNodeMetadata(nodeId));
++      }
++      return _node;
+     }
 -    
++
++    @VisibleForTesting
++    Optional<NodeMetadata> peekNode() {
++        return _node;
++    }
++
++    protected Optional<Image> getOptionalImage() {
++        if (_image == null) {
++            _image = Optional.fromNullable(getParent().getComputeService().getImage(imageId));
++        }
++        return _image;
++    }
++
++    /**
++     * @since 0.9.0
++     * @deprecated since 0.9.0 (only added as aid until the deprecated {@link #getTemplate()} is deleted)
++     */
++    @Deprecated
++    protected Optional<Template> getOptionalTemplate() {
++        if (_template == null) {
++            _template = Optional.absent();
++        }
++        return _template;
++    }
++
++    /**
++     * @deprecated since 0.9.0
++     */
+     @Override
 -    public JcloudsLocation getParent() {
 -        return jcloudsParent;
++    @Deprecated
++    public NodeMetadata getNode() {
++        Optional<NodeMetadata> result = getOptionalNode();
++        if (result.isPresent()) {
++            return result.get();
++        } else {
++            throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++        }
+     }
 -    
++
++    /**
++     * @deprecated since 0.9.0
++     */
++    @Override
++    @Deprecated
++    public Template getTemplate() {
++        Optional<Template> result = getOptionalTemplate();
++        if (result.isPresent()) {
++            String msg = "Deprecated use of getTemplate() for "+this;
++            LOG.warn(msg + " - see debug log for stacktrace");
++            LOG.debug(msg, new Exception("for stacktrace"));
++            return result.get();
++        } else {
++            throw new IllegalStateException("Template for "+nodeId+" (in "+getParent()+") not cached (deprecated use of getTemplate())");
++        }
++    }
++
+     @Override
+     public String getHostname() {
++        if (hostname != null) {
++            return hostname;
++        }
+         InetAddress address = getAddress();
+         return (address != null) ? address.getHostAddress() : null;
+     }
 -    
++
++
++    /** In clouds like AWS, the public hostname is the only way to ensure VMs in different zones can access each other. */
+     @Override
+     public Set<String> getPublicAddresses() {
 -        return node.getPublicAddresses();
++        return (publicAddresses == null) ? ImmutableSet.<String>of() : publicAddresses;
+     }
+     
+     @Override
+     public Set<String> getPrivateAddresses() {
 -        return node.getPrivateAddresses();
++        return (privateAddresses == null) ? ImmutableSet.<String>of() : privateAddresses;
+     }
+ 
++
+     @Override
+     public String getSubnetHostname() {
 -        // TODO: TEMP FIX: WAS:
 -        // String publicHostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag());
 -        // but this causes a call to JcloudsUtil.getFirstReachableAddress, which searches for accessible SSH service.
 -        // This workaround is good for public nodes but not private-subnet ones.
 -        return getHostname();
++        // Same impl as JcloudsSshMachineLocation
++        if (_privateHostname == null) {
++            for (String p : getPrivateAddresses()) {
++                if (Networking.isLocalOnly(p)) continue;
++                _privateHostname = p;
++            }
++            if (groovyTruth(getPublicAddresses())) {
++                _privateHostname = getPublicAddresses().iterator().next();
++            } else if (groovyTruth(getHostname())) {
++                _privateHostname = getHostname();
++            } else {
++                return null;
++            }
++        }
++        return _privateHostname;
+     }
+ 
+     @Override
+     public String getSubnetIp() {
++        // Same impl as JcloudsSshMachineLocation
+         Optional<String> privateAddress = getPrivateAddress();
+         if (privateAddress.isPresent()) {
+             return privateAddress.get();
+         }
 -
 -        String hostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag());
 -        if (hostname != null && !Networking.isValidIp4(hostname)) {
 -            try {
 -                return InetAddress.getByName(hostname).getHostAddress();
 -            } catch (UnknownHostException e) {
 -                LOG.debug("Cannot resolve IP for hostname {} of machine {} (so returning hostname): {}", new Object[] {hostname, this, e});
 -            }
++        if (groovyTruth(node.getPublicAddresses())) {
++            return node.getPublicAddresses().iterator().next();
+         }
 -        return hostname;
++        return null;
+     }
+ 
+     protected Optional<String> getPrivateAddress() {
 -        if (groovyTruth(node.getPrivateAddresses())) {
 -            Iterator<String> pi = node.getPrivateAddresses().iterator();
 -            while (pi.hasNext()) {
 -                String p = pi.next();
 -                // disallow local only addresses
 -                if (Networking.isLocalOnly(p)) continue;
 -                // other things may be public or private, but either way, return it
 -                return Optional.of(p);
 -            }
++        // Same impl as JcloudsSshMachineLocation
++        for (String p : getPrivateAddresses()) {
++            if (Networking.isLocalOnly(p)) continue;
++            return Optional.of(p);
+         }
+         return Optional.absent();
+     }
+     
+     @Override
+     public String getJcloudsId() {
 -        return node.getId();
++        return nodeId;
++    }
++    
++    protected String getImageId() {
++        return imageId;
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
index 0000000,851505d..2a8900a
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
+++ b/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
@@@ -1,0 -1,604 +1,610 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.location.jclouds;
+ 
+ import java.util.Collection;
+ import java.util.Map;
+ import java.util.concurrent.CountDownLatch;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicInteger;
+ 
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.api.location.LocationSpec;
+ import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.api.location.MachineLocationCustomizer;
+ import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
+ import org.apache.brooklyn.core.location.LocationConfigKeys;
+ import org.apache.brooklyn.core.location.cloud.names.CustomMachineNamer;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+ import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+ import org.apache.brooklyn.location.jclouds.JcloudsLocation.UserCreation;
+ import org.apache.brooklyn.test.Asserts;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.jclouds.scriptbuilder.domain.OsFamily;
+ import org.jclouds.scriptbuilder.domain.StatementList;
+ import org.mockito.Mockito;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Predicate;
+ import com.google.common.collect.ContiguousSet;
+ import com.google.common.collect.DiscreteDomain;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Range;
+ import com.google.common.primitives.Ints;
+ import com.google.common.reflect.TypeToken;
+ 
+ /**
+  * @author Shane Witbeck
+  */
+ public class JcloudsLocationTest implements JcloudsLocationConfig {
+ 
+     private static final Logger log = LoggerFactory.getLogger(JcloudsLocationTest.class);
+     
+     public static Predicate<ConfigBag> checkerFor(final String user, final Integer minRam, final Integer minCores) {
+         return new Predicate<ConfigBag>() {
+             @Override
+             public boolean apply(@Nullable ConfigBag input) {
+                 Assert.assertEquals(input.get(USER), user);
+                 Assert.assertEquals(input.get(MIN_RAM), minRam);
+                 Assert.assertEquals(input.get(MIN_CORES), minCores);
+                 return true;
+             }
+         };
+     }
+     
+     public static Predicate<ConfigBag> templateCheckerFor(final String ports) {
+         return new Predicate<ConfigBag>() {
+             @Override
+             public boolean apply(@Nullable ConfigBag input) {
+                 Assert.assertEquals(input.get(INBOUND_PORTS), ports);
+                 return false;
+             }
+         };
+     }
+     
+     private LocalManagementContext managementContext;
+     
+     @BeforeMethod(alwaysRun=true)
+     public void setUp() throws Exception {
 -        managementContext = LocalManagementContextForTests.newInstance(BrooklynProperties.Factory.builderEmpty().build());
++        managementContext = LocalManagementContextForTests.newInstance(BrooklynProperties.Factory.newDefault());
+     }
+     
+     @AfterMethod(alwaysRun=true)
+     public void tearUp() throws Exception {
+         if (managementContext != null) Entities.destroyAll(managementContext);
+     }
+ 
+     @Test
+     public void testCreateWithFlagsDirectly() throws Exception {
+         BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+         jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 2), checkerFor("fred", 16, 2));
+     }
+ 
+     @Test
+     public void testCreateWithFlagsDirectlyAndOverride() throws Exception {
+         BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+         jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 2, MIN_RAM, 8), checkerFor("fred", 8, 2));
+     }
+ 
+     @Test
+     public void testCreateWithFlagsSubLocation() throws Exception {
+         BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+         jcl = (BailOutJcloudsLocation) jcl.newSubLocation(MutableMap.of(USER, "jon", MIN_CORES, 2));
+         jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 3), checkerFor("jon", 16, 3));
+     }
+ 
+     @Test
+     public void testSingleInttoIntPortArray() {
+         int port = 1;
+         int[] intArray = new int[] {1};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(port), intArray);
+     }
+ 
+     @Test
+     public void testSingleStringtoIntPortArray() {
+         String portString = "1";
+         int[] intArray = new int[] {1};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(portString), intArray);
+     }
+ 
+     @Test
+     public void testStringListWithBracketstoIntPortArray() {
+         String listString = "[1, 2, 3, 4]";
+         int[] intArray = new int[] {1, 2, 3, 4};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(listString), intArray);
+     }
+ 
+     @Test
+     public void testStringListWithoutBracketstoIntPortArray() {
+         String listString = "1, 2, 3, 4";
+         int[] intArray = new int[] {1, 2, 3, 4};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(listString), intArray);
+     }
+ 
+     @Test
+     public void testEmptyStringListtoIntPortArray() {
+         String listString = "[]";
+         int[] intArray = new int[] {};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(listString), intArray);
+     }
+ 
+     @Test
+     public void testIntArraytoIntPortArray() {
+         int[] intArray = new int[] {1, 2, 3, 4};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(intArray), intArray);
+     }
+ 
+     @Test
+     public void testObjectArrayOfIntegerstoIntPortArray() {
+         Object[] integerObjectArray = new Object[] {1, 2, 3, 4};
+         int[] intArray = new int[] {1, 2, 3, 4};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(integerObjectArray), intArray);
+     }
+ 
+     @Test
+     public void testObjectArrayOfStringstoIntPortArray() {
+         Object[] stringObjectArray = new Object[] {"1", "2", "3", "4"};
+         int[] intArray = new int[] {1, 2, 3, 4};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(stringObjectArray), intArray);
+     }
+ 
+     @Test
+     public void testStringArraytoIntPortArray() {
+         String[] stringArray = new String[] {"1", "2", "3"};
+         int[] intArray = new int[] {1, 2, 3};
+         Assert.assertEquals(JcloudsLocation.toIntPortArray(stringArray), intArray);
+     }
+ 
+     @Test
+     public void testStringPortRangetoIntPortArray() {
+         String portRange = "1-100";
+         int[] intArray = Ints.toArray(ContiguousSet.create(Range.closed(1, 100), DiscreteDomain.integers()));
+         Assert.assertEquals(intArray, JcloudsLocation.toIntPortArray(portRange));
+     }
+ 
+     @Test
+     public void testStringPortPlustoIntPortArray() {
+         String portPlus = "100+";
+         int[] intArray = Ints.toArray(ContiguousSet.create(Range.closed(100, 65535), DiscreteDomain.integers()));
+         Assert.assertEquals(intArray, JcloudsLocation.toIntPortArray(portPlus));
+     }
+ 
+     @Test
+     public void testCombinationOfInputstoIntPortArray() {
+         Collection<Object> portInputs = Lists.newLinkedList();
+         portInputs.add(1);
+         portInputs.add("2");
+         portInputs.add("3-100");
+         portInputs.add("101,102,103");
+         portInputs.add("[104,105,106]");
+         portInputs.add(new int[] {107, 108, 109});
+         portInputs.add(new String[] {"110", "111", "112"});
+         portInputs.add(new Object[] {113, 114, 115});
+ 
+         int[] intArray = Ints.toArray(ContiguousSet.create(Range.closed(1, 115), DiscreteDomain.integers()));
+         Assert.assertEquals(intArray, JcloudsLocation.toIntPortArray(portInputs));
+     }
+ 
+     @Test(expectedExceptions = IllegalArgumentException.class)
+     public void testMalformedStringNumbertoIntPortArray() {
+         String numberStr = "1i";
+         JcloudsLocation.toIntPortArray(numberStr);
+     }
+ 
+     @Test(expectedExceptions = IllegalArgumentException.class)
+     public void testMalformedStringRangetoIntPortArray() {
+         String rangeString = "1-";
+         JcloudsLocation.toIntPortArray(rangeString);
+     }
+ 
+     @Test(expectedExceptions = IllegalArgumentException.class)
+     public void testMalformedStringListWithBracketstoIntPortArray() {
+         String listString = "[1,2,e]";
+         JcloudsLocation.toIntPortArray(listString);
+     }
+ 
+     @Test(expectedExceptions = IllegalArgumentException.class)
+     public void testMalformedStringListWithoutBracketstoIntPortArray() {
+         String listString = "1,2,e";
+         JcloudsLocation.toIntPortArray(listString);
+     }
+ 
+     @Test(expectedExceptions = IllegalArgumentException.class)
+     public void testMalformedStringArraytoIntPortArray() {
+         String[] stringArray = new String[] {"1", "2", "e"};
+         JcloudsLocation.toIntPortArray(stringArray);
+     }
+ 
+     @Test(expectedExceptions = IllegalArgumentException.class)
+     public void testIllegalObjectArrayOfDoublestoIntPortArray() {
+         Object[] doubleObjectArray = new Object[] {1.0, 2.0, 3.0};
+         JcloudsLocation.toIntPortArray(doubleObjectArray);
+     }
+ 
+     @Test
+     public void testVMCreationIsRetriedOnFailure() {
+         final AtomicInteger count = new AtomicInteger();
+         Function<ConfigBag, Void> countingInterceptor = new Function<ConfigBag, Void>() {
+             @Override public Void apply(ConfigBag input) {
+                 count.incrementAndGet();
+                 return null;
+             }
+         };
+         BailOutJcloudsLocation loc = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+                 MACHINE_CREATE_ATTEMPTS, 3,
+                 BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, countingInterceptor));
+         loc.tryObtain();
+         Assert.assertEquals(count.get(), 3);
+     }
+ 
+     @Test(groups={"Live", "Live-sanity"})
+     public void testCreateWithInboundPorts() {
+         BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocationForLiveTest(managementContext);
+         jcloudsLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+         jcloudsLocation.tryObtainAndCheck(MutableMap.of(), templateCheckerFor("[22, 80, 9999]"));
+         int[] ports = new int[] {22, 80, 9999};
+         Assert.assertEquals(jcloudsLocation.getTemplate().getOptions().getInboundPorts(), ports);
+     }
+     
+     @Test(groups={"Live", "Live-sanity"})
+     public void testCreateWithInboundPortsOverride() {
+         BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocationForLiveTest(managementContext);
+         jcloudsLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+         jcloudsLocation.tryObtainAndCheck(MutableMap.of(INBOUND_PORTS, "[23, 81, 9998]"), templateCheckerFor("[23, 81, 9998]"));
+         int[] ports = new int[] {23, 81, 9998};
+         Assert.assertEquals(jcloudsLocation.getTemplate().getOptions().getInboundPorts(), ports);
+     }
+ 
+     @Test
+     public void testCreateWithMaxConcurrentCallsUnboundedByDefault() throws Exception {
+         final int numCalls = 20;
+         ConcurrencyTracker interceptor = new ConcurrencyTracker();
+         ExecutorService executor = Executors.newCachedThreadPool();
+ 
+         try {
+             final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                     managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+                             BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor));
+             for (int i = 0; i < numCalls; i++) {
+                 executor.execute(new Runnable() {
+                     @Override
+                     public void run() {
+                         jcloudsLocation.tryObtain();
+                     }
+                 });
+             }
+             interceptor.assertCallCountEventually(numCalls);
+             interceptor.unblock();
+             executor.shutdown();
+             executor.awaitTermination(10, TimeUnit.SECONDS);
+         } finally {
+             executor.shutdownNow();
+         }
+     }
+ 
+     @Test(groups="Integration") // because takes 1 sec
+     public void testCreateWithMaxConcurrentCallsRespectsConfig() throws Exception {
+         final int numCalls = 4;
+         final int maxConcurrentCreations = 2;
+         ConcurrencyTracker interceptor = new ConcurrencyTracker();
+         ExecutorService executor = Executors.newCachedThreadPool();
+         
+         try {
+             final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                     managementContext, ImmutableMap.of(
+                             BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor,
+                             MAX_CONCURRENT_MACHINE_CREATIONS, maxConcurrentCreations));
+ 
+             for (int i = 0; i < numCalls; i++) {
+                 executor.execute(new Runnable() {
+                     @Override
+                     public void run() {
+                         jcloudsLocation.tryObtain();
+                     }
+                 });
+             }
+ 
+             interceptor.assertCallCountEventually(maxConcurrentCreations);
+             interceptor.assertCallCountContinually(maxConcurrentCreations);
+ 
+             interceptor.unblock();
+             interceptor.assertCallCountEventually(numCalls);
+             executor.shutdown();
+             executor.awaitTermination(10, TimeUnit.SECONDS);
+ 
+         } finally {
+             executor.shutdownNow();
+         }
+     }
+ 
+     @Test(groups="Integration") // because takes 1 sec
+     public void testCreateWithMaxConcurrentCallsAppliesToSubLocations() throws Exception {
+         final int numCalls = 4;
+         final int maxConcurrentCreations = 2;
+         ConcurrencyTracker interceptor = new ConcurrencyTracker();
+         ExecutorService executor = Executors.newCachedThreadPool();
+ 
+         try {
+             final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                     managementContext, ImmutableMap.of(
+                             BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor,
+                             MAX_CONCURRENT_MACHINE_CREATIONS, maxConcurrentCreations));
+ 
+             for (int i = 0; i < numCalls; i++) {
+                 final BailOutJcloudsLocation subLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+                 executor.execute(new Runnable() {
+                     @Override
+                     public void run() {
+                         subLocation.tryObtain();
+                     }
+                 });
+             }
+ 
+             interceptor.assertCallCountEventually(maxConcurrentCreations);
+             interceptor.assertCallCountContinually(maxConcurrentCreations);
+ 
+             interceptor.unblock();
+             interceptor.assertCallCountEventually(numCalls);
+             executor.shutdown();
+             executor.awaitTermination(10, TimeUnit.SECONDS);
+ 
+         } finally {
+             executor.shutdownNow();
+         }
+     }
+     
+     @Test
+     public void testCreateWithCustomMachineNamer() {
+         final String machineNamerClass = CustomMachineNamer.class.getName();
+         BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                 managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+                         LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass));
+         jcloudsLocation.tryObtainAndCheck(ImmutableMap.of(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored"), new Predicate<ConfigBag>() {
+             public boolean apply(ConfigBag input) {
+                 Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
+                 return true;
+             }
+         });
+     }
+     
+     @Test
+     public void testCreateWithCustomMachineNamerOnObtain() {
+         final String machineNamerClass = CustomMachineNamer.class.getName();
+         BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+         ImmutableMap<ConfigKey<String>, String> flags = ImmutableMap.of(
+                 CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored",
+                 LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass);
+         jcloudsLocation.tryObtainAndCheck(flags, new Predicate<ConfigBag>() {
+             public boolean apply(ConfigBag input) {
+                 Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
+                 return true;
+             }
+         });
+     }
+ 
+     public static class ConcurrencyTracker implements Function<ConfigBag,Void> {
+         final AtomicInteger concurrentCallsCounter = new AtomicInteger();
+         final CountDownLatch continuationLatch = new CountDownLatch(1);
+         
+         @Override public Void apply(ConfigBag input) {
+             concurrentCallsCounter.incrementAndGet();
+             try {
+                 continuationLatch.await();
+             } catch (InterruptedException e) {
+                 throw Exceptions.propagate(e);
+             }
+             return null;
+         }
+         
+         public void unblock() {
+             continuationLatch.countDown();
+         }
+ 
+         public void assertCallCountEventually(final int expected) {
+             Asserts.succeedsEventually(new Runnable() {
+                 @Override public void run() {
+                     Assert.assertEquals(concurrentCallsCounter.get(), expected);
+                 }
+             });
+         }
+         
+         public void assertCallCountContinually(final int expected) {
+             Asserts.succeedsContinually(new Runnable() {
+                 @Override public void run() {
+                     Assert.assertEquals(concurrentCallsCounter.get(), expected);
+                 }
+             });
+         }
+     }
+ 
+     
+     @SuppressWarnings("serial")
+     public static class FakeLocalhostWithParentJcloudsLocation extends JcloudsLocation {
+         public static final ConfigKey<Function<ConfigBag,Void>> BUILD_TEMPLATE_INTERCEPTOR = ConfigKeys.newConfigKey(new TypeToken<Function<ConfigBag,Void>>() {}, "buildtemplateinterceptor");
+         
+         ConfigBag lastConfigBag;
+ 
+         public FakeLocalhostWithParentJcloudsLocation() {
+             super();
+         }
+ 
+         public FakeLocalhostWithParentJcloudsLocation(Map<?, ?> conf) {
+             super(conf);
+         }
+ 
+         @Override
+         public JcloudsSshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+             JcloudsSshMachineLocation result = getManagementContext().getLocationManager().createLocation(LocationSpec.create(JcloudsSshMachineLocation.class)
+                 .configure("address", "127.0.0.1") 
+                 .configure("port", 22) 
+                 .configure("user", "bob")
 -                .configure("jcloudsParent", this));
++                .configure("jcloudsParent", this)
++                .configure("nodeId", "myNodeId")
++                .configure("imageId", "myImageId")
++                .configure("privateAddresses", ImmutableSet.of("10.0.0.1"))
++                .configure("publicAddresses", ImmutableSet.of("56.0.0.1"))
++                );
+             registerJcloudsMachineLocation("bogus", result);
+             
+             // explicitly invoke this customizer, to comply with tests
+             for (JcloudsLocationCustomizer customizer : getCustomizers(config().getBag())) {
+                 customizer.customize(this, null, (JcloudsMachineLocation)result);
+             }
+             for (MachineLocationCustomizer customizer : getMachineCustomizers(config().getBag())) {
+                 customizer.customize((JcloudsMachineLocation)result);
+             }
+ 
+             return result;
+         }
+         
+         @Override
+         protected void releaseNode(String instanceId) {
+             // no-op
+         }
+     }
+ 
+     @Test
+     public void testInheritsGeo() throws Exception {
+         ConfigBag allConfig = ConfigBag.newInstance()
+             .configure(IMAGE_ID, "bogus")
+             .configure(CLOUD_PROVIDER, "aws-ec2")
+             .configure(CLOUD_REGION_ID, "bogus")
+             .configure(ACCESS_IDENTITY, "bogus")
+             .configure(ACCESS_CREDENTIAL, "bogus")
+             .configure(LocationConfigKeys.LATITUDE, 42d)
+             .configure(LocationConfigKeys.LONGITUDE, -20d)
+             .configure(MACHINE_CREATE_ATTEMPTS, 1);
+         FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
+         MachineLocation l = ll.obtain();
+         log.info("loc:" +l);
+         HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+         log.info("geo: "+geo);
+         Assert.assertEquals(geo.latitude, 42d, 0.00001);
+         Assert.assertEquals(geo.longitude, -20d, 0.00001);
+     }
+ 
+     @SuppressWarnings("unchecked")
+     @Test
+     public void testInheritsGeoFromLocationMetadataProperties() throws Exception {
+         // in location-metadata.properties:
+ //        brooklyn.location.jclouds.softlayer@wdc01.latitude=38.909202
+ //        brooklyn.location.jclouds.softlayer@wdc01.longitude=-77.47314
+         ConfigBag allConfig = ConfigBag.newInstance()
+             .configure(IMAGE_ID, "bogus")
+             .configure(CLOUD_PROVIDER, "softlayer")
+             .configure(CLOUD_REGION_ID, "wdc01")
+             .configure(ACCESS_IDENTITY, "bogus")
+             .configure(ACCESS_CREDENTIAL, "bogus")
+             .configure(MACHINE_CREATE_ATTEMPTS, 1);
+         FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class)
+             .configure(new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties("softlayer", "wdc01", null, managementContext.getBrooklynProperties()))
+             .configure(allConfig.getAllConfig()));
+         MachineLocation l = ll.obtain();
+         log.info("loc:" +l);
+         HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+         log.info("geo: "+geo);
+         Assert.assertEquals(geo.latitude, 38.909202d, 0.00001);
+         Assert.assertEquals(geo.longitude, -77.47314d, 0.00001);
+     }
+ 
+     @Test
+     public void testInvokesCustomizerCallbacks() throws Exception {
+         JcloudsLocationCustomizer customizer = Mockito.mock(JcloudsLocationCustomizer.class);
+         MachineLocationCustomizer machineCustomizer = Mockito.mock(MachineLocationCustomizer.class);
+ //        Mockito.when(customizer.customize(Mockito.any(JcloudsLocation.class), Mockito.any(ComputeService.class), Mockito.any(JcloudsSshMachineLocation.class)));
+         ConfigBag allConfig = ConfigBag.newInstance()
+             .configure(CLOUD_PROVIDER, "aws-ec2")
+             .configure(ACCESS_IDENTITY, "bogus")
+             .configure(ACCESS_CREDENTIAL, "bogus")
+             .configure(JcloudsLocationConfig.JCLOUDS_LOCATION_CUSTOMIZERS, ImmutableList.of(customizer))
+             .configure(JcloudsLocation.MACHINE_LOCATION_CUSTOMIZERS, ImmutableList.of(machineCustomizer))
+             .configure(MACHINE_CREATE_ATTEMPTS, 1);
+         FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
+         JcloudsMachineLocation l = (JcloudsMachineLocation)ll.obtain();
+         Mockito.verify(customizer, Mockito.times(1)).customize(ll, null, l);
+         Mockito.verify(customizer, Mockito.never()).preRelease(l);
+         Mockito.verify(customizer, Mockito.never()).postRelease(l);
+         Mockito.verify(machineCustomizer, Mockito.times(1)).customize(l);
+         Mockito.verify(machineCustomizer, Mockito.never()).preRelease(l);
+         
+         ll.release(l);
+         Mockito.verify(customizer, Mockito.times(1)).preRelease(l);
+         Mockito.verify(customizer, Mockito.times(1)).postRelease(l);
+         Mockito.verify(machineCustomizer, Mockito.times(1)).preRelease(l);
+     }
+ 
+     // now test creating users
+     
+     protected String getCreateUserStatementsFor(Map<ConfigKey<?>,?> config) {
+         BailOutJcloudsLocation jl = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+                 managementContext, MutableMap.<ConfigKey<?>, Object>builder()
+                         .put(JcloudsLocationConfig.LOGIN_USER, "root").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "m0ck")
+                         .put(JcloudsLocationConfig.USER, "bob").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "b0b")
+                         .putAll(config).build());
+ 
+         UserCreation creation = jl.createUserStatements(null, jl.config().getBag());
+         return new StatementList(creation.statements).render(OsFamily.UNIX);
+     }
+     
+     @Test
+     public void testDisablesRoot() {
+         String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of());
+         Assert.assertTrue(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+         Assert.assertTrue(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+     }
+ 
+     @Test
+     public void testDisableRootFalse() {
+         String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of(
+                 JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH, false));
+         Assert.assertFalse(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+         Assert.assertTrue(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+     }
+     
+     @Test
+     public void testDisableRootAndSudoFalse() {
+         String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of(
+             JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH, false,
+             JcloudsLocationConfig.GRANT_USER_SUDO, false));
+         Assert.assertFalse(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+         Assert.assertFalse(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+     }
+ 
+     // TODO more tests, where flags come in from resolver, named locations, etc
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
index 0000000,b81d930..de3fb1c
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
+++ b/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
@@@ -1,0 -1,188 +1,231 @@@
+ /*
+  * 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 com.google.common.base.Preconditions.checkNotNull;
+ import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertFalse;
++import static org.testng.Assert.assertNull;
++import static org.testng.Assert.fail;
+ 
+ import java.util.List;
+ import java.util.Map;
+ 
+ import org.apache.brooklyn.api.location.LocationSpec;
++import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+ import org.apache.brooklyn.core.location.Locations;
+ import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
++import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
++import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse;
+ import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
+ import org.apache.brooklyn.util.stream.Streams;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.Template;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Lists;
+ 
+ /**
+  * Tests rebind (i.e. restarting Brooklyn server) when there are live JcloudsSshMachineLocation object(s).
+  */
+ public class JcloudsRebindLiveTest extends RebindTestFixtureWithApp {
+ 
+     // TODO Duplication of AbstractJcloudsLiveTest, because we're subcalling RebindTestFixture instead.
+ 
+     // TODO The mgmts tracking was added when I tried to combine JcloudsRebindLiveTest and JcloudsByonRebindLiveTest, 
+     // but turns out that is not worth the effort!
+     
+     private static final Logger LOG = LoggerFactory.getLogger(JcloudsRebindLiveTest.class);
+ 
+     public static final String AWS_EC2_REGION_NAME = AbstractJcloudsLiveTest.AWS_EC2_USEAST_REGION_NAME;
+     public static final String AWS_EC2_LOCATION_SPEC = "jclouds:" + AbstractJcloudsLiveTest.AWS_EC2_PROVIDER + (AWS_EC2_REGION_NAME == null ? "" : ":" + AWS_EC2_REGION_NAME);
+     
+     // Image: {id=us-east-1/ami-7d7bfc14, providerId=ami-7d7bfc14, name=RightImage_CentOS_6.3_x64_v5.8.8.5, location={scope=REGION, id=us-east-1, description=us-east-1, parent=aws-ec2, iso3166Codes=[US-VA]}, os={family=centos, arch=paravirtual, version=6.0, description=rightscale-us-east/RightImage_CentOS_6.3_x64_v5.8.8.5.manifest.xml, is64Bit=true}, description=rightscale-us-east/RightImage_CentOS_6.3_x64_v5.8.8.5.manifest.xml, version=5.8.8.5, status=AVAILABLE[available], loginUser=root, userMetadata={owner=411009282317, rootDeviceType=instance-store, virtualizationType=paravirtual, hypervisor=xen}}
+     public static final String AWS_EC2_CENTOS_IMAGE_ID = "us-east-1/ami-7d7bfc14";
+ 
+     public static final String SOFTLAYER_LOCATION_SPEC = "jclouds:" + AbstractJcloudsLiveTest.SOFTLAYER_PROVIDER;
+     
 -    protected List<JcloudsSshMachineLocation> machines;
++    protected List<JcloudsMachineLocation> machines;
+     
+     @BeforeMethod(alwaysRun=true)
+     public void setUp() throws Exception {
+         super.setUp();
+         machines = Lists.newCopyOnWriteArrayList();
+         
+         // Don't let any defaults from brooklyn.properties (except credentials) interfere with test
+         AbstractJcloudsLiveTest.stripBrooklynProperties(origManagementContext.getBrooklynProperties());
+     }
+ 
+     @AfterMethod(alwaysRun=true)
+     public void tearDown() throws Exception {
+         List<Exception> exceptions = Lists.newArrayList();
+         try {
+             exceptions.addAll(releaseMachineSafely(machines));
+             machines.clear();
+         } finally {
+             super.tearDown();
+         }
+         
+         if (exceptions.size() > 0) {
+             throw new CompoundRuntimeException("Error in tearDown of "+getClass(), exceptions);
+         }
+     }
+ 
+     @Override
+     protected boolean useLiveManagementContext() {
+         return true;
+     }
+ 
+     @Test(groups = {"Live"})
+     public void testEc2Rebind() throws Exception {
+         ImmutableMap<String, Object> obtainFlags = ImmutableMap.<String,Object>builder()
+                 .put("imageId", AWS_EC2_CENTOS_IMAGE_ID)
 -                .put("hardwareId", AbstractJcloudsLiveTest.AWS_EC2_SMALL_HARDWARE_ID)
++                .put("hardwareId", AbstractJcloudsLiveTest.AWS_EC2_MEDIUM_HARDWARE_ID)
+                 .put("inboundPorts", ImmutableList.of(22))
+                 .build();
+         runTest(AWS_EC2_LOCATION_SPEC, obtainFlags);
+     }
+     
+     @Test(groups = {"Live"})
++    public void testEc2WinrmRebind() throws Exception {
++        ImmutableMap<String, Object> obtainFlags = ImmutableMap.<String,Object>builder()
++                .put("imageNameRegex", "Windows_Server-2012-R2_RTM-English-64Bit-Base-.*")
++                .put("imageOwner", "801119661308")
++                .put("hardwareId", AbstractJcloudsLiveTest.AWS_EC2_MEDIUM_HARDWARE_ID)
++                .put("useJcloudsSshInit", false)
++                .put("inboundPorts", ImmutableList.of(5985, 3389))
++                .build();
++        runTest(AWS_EC2_LOCATION_SPEC, obtainFlags);
++    }
++
++    @Test(groups = {"Live"})
+     public void testSoftlayerRebind() throws Exception {
+         runTest(SOFTLAYER_LOCATION_SPEC, ImmutableMap.of("inboundPorts", ImmutableList.of(22)));
+     }
+     
+     protected void runTest(String locSpec, Map<String, ?> obtainFlags) throws Exception {
+         JcloudsLocation location = (JcloudsLocation) mgmt().getLocationRegistry().resolve(locSpec);
+         
 -        JcloudsSshMachineLocation origMachine = obtainMachine(location, obtainFlags);
++        JcloudsMachineLocation origMachine = obtainMachine(location, obtainFlags);
+         String origHostname = origMachine.getHostname();
+         NodeMetadata origNode = origMachine.getNode();
 -        Template origTemplate = origMachine.getTemplate();
 -        assertSshable(origMachine);
++        if (origMachine instanceof JcloudsSshMachineLocation) {
++            Template origTemplate = origMachine.getTemplate(); // WinRM machines don't bother with template!
++        }
++        assertConnectable(origMachine);
+ 
+         rebind();
+         
 -        // Check the machine is as before
 -        JcloudsSshMachineLocation newMachine = (JcloudsSshMachineLocation) newManagementContext.getLocationManager().getLocation(origMachine.getId());
++        // Check the machine is as before; but won't have persisted node+template.
++        // We'll be able to re-create the node object by querying the cloud-provider again though.
++        JcloudsMachineLocation newMachine = (JcloudsMachineLocation) newManagementContext.getLocationManager().getLocation(origMachine.getId());
+         JcloudsLocation newLocation = newMachine.getParent();
+         String newHostname = newMachine.getHostname();
 -        NodeMetadata newNode = newMachine.getNode();
 -        Template newTemplate = newMachine.getTemplate();
 -        assertSshable(newMachine);
++        if (newMachine instanceof JcloudsSshMachineLocation) {
++            assertFalse(((JcloudsSshMachineLocation)newMachine).getOptionalTemplate().isPresent());
++            assertNull(((JcloudsSshMachineLocation)newMachine).peekNode());
++        } else if (newMachine instanceof JcloudsWinRmMachineLocation) {
++            assertNull(((JcloudsWinRmMachineLocation)newMachine).peekNode());
++        } else {
++            fail("Unexpected new machine type: machine="+newMachine+"; type="+(newMachine == null ? null : newMachine.getClass()));
++        }
++        NodeMetadata newNode = newMachine.getOptionalNode().get();
++        assertConnectable(newMachine);
+         
+         assertEquals(newHostname, origHostname);
+         assertEquals(origNode.getId(), newNode.getId());
+     }
+     
+     protected void assertSshable(Map<?,?> machineConfig) {
+         SshMachineLocation machineWithThatConfig = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                 .configure(machineConfig));
+         try {
+             assertSshable(machineWithThatConfig);
+         } finally {
+             Streams.closeQuietly(machineWithThatConfig);
+         }
+     }
+ 
+     protected void assertNotSshable(Map<?,?> machineConfig) {
+         try {
+             assertSshable(machineConfig);
+             Assert.fail("ssh should not have succeeded "+machineConfig);
+         } catch (Exception e) {
+             // expected
+             LOG.debug("Exception as expected when testing sshable "+machineConfig);
+         }
+     }
+ 
++    protected void assertConnectable(MachineLocation machine) {
++        if (machine instanceof SshMachineLocation) {
++            assertSshable((SshMachineLocation)machine);
++        } else if (machine instanceof WinRmMachineLocation) {
++            assertWinrmable((WinRmMachineLocation)machine);
++        } else {
++            throw new UnsupportedOperationException("Unsupported machine type: machine="+machine+"; type="+(machine == null ? null : machine.getClass()));
++        }
++    }
++    
+     protected void assertSshable(SshMachineLocation machine) {
+         int result = machine.execScript("simplecommand", ImmutableList.of("true"));
+         assertEquals(result, 0);
+     }
+ 
++    protected void assertWinrmable(WinRmMachineLocation machine) {
++        WinRmToolResponse result = machine.executePsScript("echo mycmd");
++        assertEquals(result.getStatusCode(), 0, "status="+result.getStatusCode()+"; stdout="+result.getStdOut()+"; stderr="+result.getStdErr());
++    }
++
+     // Use this utility method to ensure machines are released on tearDown
 -    protected JcloudsSshMachineLocation obtainMachine(MachineProvisioningLocation<?> location, Map<?, ?> conf) throws Exception {
 -        JcloudsSshMachineLocation result = (JcloudsSshMachineLocation)location.obtain(conf);
++    protected JcloudsMachineLocation obtainMachine(MachineProvisioningLocation<?> location, Map<?, ?> conf) throws Exception {
++        JcloudsMachineLocation result = (JcloudsMachineLocation)location.obtain(conf);
+         machines.add(checkNotNull(result, "result"));
+         return result;
+     }
+ 
 -    protected void releaseMachine(JcloudsSshMachineLocation machine) {
++    protected void releaseMachine(JcloudsMachineLocation machine) {
+         if (!Locations.isManaged(machine)) return;
+         machines.remove(machine);
+         machine.getParent().release(machine);
+     }
+     
 -    protected List<Exception> releaseMachineSafely(Iterable<? extends JcloudsSshMachineLocation> machines) {
++    protected List<Exception> releaseMachineSafely(Iterable<? extends JcloudsMachineLocation> machines) {
+         List<Exception> exceptions = Lists.newArrayList();
 -        List<JcloudsSshMachineLocation> machinesCopy = ImmutableList.copyOf(machines);
++        List<JcloudsMachineLocation> machinesCopy = ImmutableList.copyOf(machines);
+         
 -        for (JcloudsSshMachineLocation machine : machinesCopy) {
++        for (JcloudsMachineLocation machine : machinesCopy) {
+             try {
+                 releaseMachine(machine);
+             } catch (Exception e) {
+                 LOG.warn("Error releasing machine "+machine+"; continuing...", e);
+                 exceptions.add(e);
+             }
+         }
+         return exceptions;
+     }
+ }