http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/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
new file mode 100644
index 0000000..d355721
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -0,0 +1,2861 @@
+/*
+ * 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 brooklyn.util.JavaGroovyEquivalents.elvis;
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static 
org.jclouds.compute.options.RunScriptOptions.Builder.overrideLoginCredentials;
+import static org.jclouds.scriptbuilder.domain.Statements.exec;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.management.AccessController;
+import org.apache.brooklyn.location.MachineLocationCustomizer;
+import org.apache.brooklyn.location.MachineManagementMixins;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import org.apache.brooklyn.location.access.PortMapping;
+import org.apache.brooklyn.location.basic.AbstractLocation;
+import 
org.apache.brooklyn.location.cloud.AbstractCloudMachineProvisioningLocation;
+import org.apache.brooklyn.location.cloud.names.AbstractCloudMachineNamer;
+import 
org.apache.brooklyn.location.jclouds.networking.JcloudsPortForwarderExtension;
+import org.apache.brooklyn.location.jclouds.zone.AwsAvailabilityZoneExtension;
+import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
+import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.config.AdminAccessConfiguration;
+import org.jclouds.compute.domain.ComputeMetadata;
+import org.jclouds.compute.domain.ExecResponse;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.domain.TemplateBuilderSpec;
+import org.jclouds.compute.functions.Sha512Crypt;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.ec2.compute.options.EC2TemplateOptions;
+import 
org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.scriptbuilder.domain.LiteralStatement;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.jclouds.scriptbuilder.domain.Statements;
+import org.jclouds.scriptbuilder.functions.InitAdminAccess;
+import org.jclouds.scriptbuilder.statements.login.AdminAccess;
+import org.jclouds.scriptbuilder.statements.login.ReplaceShadowPasswordEntry;
+import org.jclouds.scriptbuilder.statements.ssh.AuthorizeRSAPublicKeys;
+import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import com.google.common.io.Files;
+import com.google.common.net.HostAndPort;
+import com.google.common.primitives.Ints;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+import brooklyn.config.ConfigUtils;
+import brooklyn.entity.basic.Sanitizer;
+import brooklyn.entity.rebind.persister.LocationWithObjectStore;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import 
brooklyn.entity.rebind.persister.jclouds.JcloudsBlobStoreBasedObjectStore;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.access.PortForwardManager;
+import org.apache.brooklyn.location.basic.BasicMachineMetadata;
+import org.apache.brooklyn.location.basic.LocationConfigKeys;
+import org.apache.brooklyn.location.basic.LocationConfigUtils;
+import org.apache.brooklyn.location.basic.LocationConfigUtils.OsCredential;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.WinRmMachineLocation;
+import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension;
+import org.apache.brooklyn.location.cloud.names.CloudMachineNamer;
+import org.apache.brooklyn.location.jclouds.JcloudsPredicates.NodeInLocation;
+import org.apache.brooklyn.location.jclouds.templates.PortableTemplateBuilder;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.crypto.SecureKeys;
+import brooklyn.util.exceptions.CompoundRuntimeException;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.ReferenceWithError;
+import brooklyn.util.flags.MethodCoercions;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.internal.ssh.ShellTool;
+import brooklyn.util.internal.ssh.SshTool;
+import brooklyn.util.javalang.Enums;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.net.Networking;
+import brooklyn.util.net.Protocol;
+import brooklyn.util.os.Os;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.ssh.IptablesCommands;
+import brooklyn.util.ssh.IptablesCommands.Chain;
+import brooklyn.util.ssh.IptablesCommands.Policy;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.ByteSizeStrings;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.KeyValueParser;
+import brooklyn.util.text.Strings;
+import brooklyn.util.text.TemplateProcessor;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+import io.cloudsoft.winrm4j.pywinrm.Session;
+import io.cloudsoft.winrm4j.pywinrm.WinRMFactory;
+
+/**
+ * For provisioning and managing VMs in a particular provider/region, using 
jclouds.
+ * Configuration flags are defined in {@link JcloudsLocationConfig}.
+ */
+@SuppressWarnings("serial")
+public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation 
implements
+        JcloudsLocationConfig, 
MachineManagementMixins.RichMachineProvisioningLocation<MachineLocation>,
+        LocationWithObjectStore, MachineManagementMixins.SuspendsMachines {
+
+    // TODO After converting from Groovy to Java, this is now very bad code! 
It relies entirely on putting
+    // things into and taking them out of maps; it's not type-safe, and it's 
thus very error-prone.
+    // In Groovy, that's considered ok but not in Java.
+
+    // TODO test (and fix) ability to set config keys from flags
+
+    // TODO we say config is inherited, but it isn't the case for many "deep" 
/ jclouds properties
+    // e.g. when we pass getRawLocalConfigBag() in and decorate it with 
additional flags
+    // (inheritance only works when we call getConfig in this class)
+
+    public static final Logger LOG = 
LoggerFactory.getLogger(JcloudsLocation.class);
+
+    public static final String ROOT_USERNAME = "root";
+    /** these userNames are known to be the preferred/required logins in some 
common/default images
+     *  where root@ is not allowed to log in */
+    public static final List<String> ROOT_ALIASES = ImmutableList.of("ubuntu", 
"ec2-user");
+    public static final List<String> COMMON_USER_NAMES_TO_TRY = 
ImmutableList.<String>builder().add(ROOT_USERNAME).addAll(ROOT_ALIASES).add("admin").build();
+
+    private static final Pattern LIST_PATTERN = 
Pattern.compile("^\\[(.*)\\]$");
+    private static final Pattern INTEGER_PATTERN = Pattern.compile("^\\d*$");
+
+    private static final int NOTES_MAX_LENGTH = 1000;
+
+    private final AtomicBoolean loggedSshKeysHint = new AtomicBoolean(false);
+    private final AtomicBoolean listedAvailableTemplatesOnNoSuchTemplate = new 
AtomicBoolean(false);
+
+    private final Map<String,Map<String, ? extends Object>> tagMapping = 
Maps.newLinkedHashMap();
+
+    @SetFromFlag // so it's persisted
+    private final Map<MachineLocation,String> vmInstanceIds = 
Maps.newLinkedHashMap();
+    
+    static { Networking.init(); }
+
+    public JcloudsLocation() {
+        super();
+    }
+
+    /** typically wants at least ACCESS_IDENTITY and ACCESS_CREDENTIAL */
+    public JcloudsLocation(Map<?,?> conf) {
+       super(conf);
+    }
+
+    @Override
+    @Deprecated
+    public JcloudsLocation configure(Map<?,?> properties) {
+        super.configure(properties);
+
+        if (config().getLocalBag().containsKey("providerLocationId")) {
+            LOG.warn("Using deprecated 'providerLocationId' key in "+this);
+            if (!config().getLocalBag().containsKey(CLOUD_REGION_ID))
+                
config().addToLocalBag(MutableMap.of(CLOUD_REGION_ID.getName(), 
(String)config().getLocalBag().getStringKey("providerLocationId")));
+        }
+
+        if (isDisplayNameAutoGenerated() || !groovyTruth(getDisplayName())) {
+            setDisplayName(elvis(getProvider(), "unknown") +
+                   (groovyTruth(getRegion()) ? ":"+getRegion() : "") +
+                   (groovyTruth(getEndpoint()) ? ":"+getEndpoint() : ""));
+        }
+
+        setCreationString(config().getLocalBag());
+
+        if (getConfig(MACHINE_CREATION_SEMAPHORE) == null) {
+            Integer maxConcurrent = 
getConfig(MAX_CONCURRENT_MACHINE_CREATIONS);
+            if (maxConcurrent == null || maxConcurrent < 1) {
+                throw new 
IllegalStateException(MAX_CONCURRENT_MACHINE_CREATIONS.getName() + " must be >= 
1, but was "+maxConcurrent);
+            }
+            config().set(MACHINE_CREATION_SEMAPHORE, new 
Semaphore(maxConcurrent, true));
+        }
+        return this;
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        if ("aws-ec2".equals(getProvider())) {
+            addExtension(AvailabilityZoneExtension.class, new 
AwsAvailabilityZoneExtension(getManagementContext(), this));
+        }
+    }
+
+    @Override
+    public JcloudsLocation newSubLocation(Map<?,?> newFlags) {
+        return newSubLocation(getClass(), newFlags);
+    }
+
+    @Override
+    public JcloudsLocation newSubLocation(Class<? extends 
AbstractCloudMachineProvisioningLocation> type, Map<?,?> newFlags) {
+        // TODO should be able to use ConfigBag.newInstanceExtending; would 
require moving stuff around to api etc
+        return (JcloudsLocation) 
getManagementContext().getLocationManager().createLocation(LocationSpec.create(type)
+                .parent(this)
+                .configure(config().getLocalBag().getAllConfig())  // FIXME 
Should this just be inherited?
+                .configure(MACHINE_CREATION_SEMAPHORE, 
getMachineCreationSemaphore())
+                .configure(newFlags));
+    }
+
+    @Override
+    public String toString() {
+        Object identity = getIdentity();
+        String configDescription = config().getLocalBag().getDescription();
+        if (configDescription!=null && 
configDescription.startsWith(getClass().getSimpleName()))
+            return configDescription;
+        return getClass().getSimpleName()+"["+getDisplayName()+":"+(identity 
!= null ? identity : null)+
+                (configDescription!=null ? "/"+configDescription : "") + "@" + 
getId() + "]";
+    }
+
+    @Override
+    public String toVerboseString() {
+        return Objects.toStringHelper(this).omitNullValues()
+                .add("id", getId()).add("name", 
getDisplayName()).add("identity", getIdentity())
+                .add("description", 
config().getLocalBag().getDescription()).add("provider", getProvider())
+                .add("region", getRegion()).add("endpoint", getEndpoint())
+                .toString();
+    }
+
+    public String getProvider() {
+        return getConfig(CLOUD_PROVIDER);
+    }
+
+    public String getIdentity() {
+        return getConfig(ACCESS_IDENTITY);
+    }
+
+    public String getCredential() {
+        return getConfig(ACCESS_CREDENTIAL);
+    }
+
+    /** returns the location ID used by the provider, if set, e.g. us-west-1 */
+    public String getRegion() {
+        return getConfig(CLOUD_REGION_ID);
+    }
+
+    public String getEndpoint() {
+        return (String) config().getBag().getWithDeprecation(CLOUD_ENDPOINT, 
JCLOUDS_KEY_ENDPOINT);
+    }
+
+    public String getUser(ConfigBag config) {
+        return (String) config.getWithDeprecation(USER, JCLOUDS_KEY_USERNAME);
+    }
+
+    public boolean isWindows(Template template, ConfigBag config) {
+        return isWindows(template.getImage(), config);
+    }
+    
+    /**
+     * Whether VMs provisioned from this image will be Windows. Assume windows 
if the image
+     * explicitly says so, or if image does not tell us then fall back to 
whether the config 
+     * explicitly says windows in {@link JcloudsLocationConfig#OS_FAMILY}.
+     * 
+     * Will first look at {@link JcloudsLocationConfig#OS_FAMILY_OVERRIDE}, to 
check if that 
+     * is set. If so, no further checks are done: the value is compared 
against {@link OsFamily#WINDOWS}.
+     * 
+     * We believe the config (e.g. from brooklyn.properties) because for some 
clouds there is 
+     * insufficient meta-data so the Image might not tell us. Thus a user can 
work around it
+     * by explicitly supplying configuration. 
+     */
+    public boolean isWindows(Image image, ConfigBag config) {
+        OsFamily override = config.get(OS_FAMILY_OVERRIDE);
+        if (override != null) return override == OsFamily.WINDOWS;
+        
+        OsFamily confFamily = config.get(OS_FAMILY);
+        OperatingSystem os = (image != null) ? image.getOperatingSystem() : 
null;
+        return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) 
+                ? (OsFamily.WINDOWS == os.getFamily()) 
+                : (OsFamily.WINDOWS == confFamily);
+    }
+
+    /**
+     * Whether the given VM is Windows.
+     * 
+     * @see {@link #isWindows(Image, ConfigBag)}
+     */
+    public boolean isWindows(NodeMetadata node, ConfigBag config) {
+        OsFamily override = config.get(OS_FAMILY_OVERRIDE);
+        if (override != null) return override == OsFamily.WINDOWS;
+        
+        OsFamily confFamily = config.get(OS_FAMILY);
+        OperatingSystem os = (node != null) ? node.getOperatingSystem() : null;
+        return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) 
+                ? (OsFamily.WINDOWS == os.getFamily()) 
+                : (OsFamily.WINDOWS == confFamily);
+    }
+
+    public boolean isLocationFirewalldEnabled(SshMachineLocation location) {
+        int result = location.execCommands("checking if firewalld is active", 
+                ImmutableList.of(IptablesCommands.firewalldServiceIsActive()));
+        if (result == 0) {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    protected Semaphore getMachineCreationSemaphore() {
+        return checkNotNull(getConfig(MACHINE_CREATION_SEMAPHORE), 
MACHINE_CREATION_SEMAPHORE.getName());
+    }
+
+    protected CloudMachineNamer getCloudMachineNamer(ConfigBag config) {
+        String namerClass = 
config.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS);
+        if (Strings.isNonBlank(namerClass)) {
+            Optional<CloudMachineNamer> cloudNamer = 
Reflections.invokeConstructorWithArgs(getManagementContext().getCatalogClassLoader(),
 namerClass);
+            if (cloudNamer.isPresent()) {
+                return cloudNamer.get();
+            } else {
+                throw new IllegalStateException("Failed to create 
CloudMachineNamer "+namerClass+" for location "+this);
+            }
+        } else {
+            return new JcloudsMachineNamer();
+        }
+    }
+
+    protected Collection<JcloudsLocationCustomizer> getCustomizers(ConfigBag 
setup) {
+        @SuppressWarnings("deprecation")
+        JcloudsLocationCustomizer customizer = 
setup.get(JCLOUDS_LOCATION_CUSTOMIZER);
+        Collection<JcloudsLocationCustomizer> customizers = 
setup.get(JCLOUDS_LOCATION_CUSTOMIZERS);
+        @SuppressWarnings("deprecation")
+        String customizerType = setup.get(JCLOUDS_LOCATION_CUSTOMIZER_TYPE);
+        @SuppressWarnings("deprecation")
+        String customizersSupplierType = 
setup.get(JCLOUDS_LOCATION_CUSTOMIZERS_SUPPLIER_TYPE);
+
+        ClassLoader catalogClassLoader = 
getManagementContext().getCatalogClassLoader();
+        List<JcloudsLocationCustomizer> result = new 
ArrayList<JcloudsLocationCustomizer>();
+        if (customizer != null) result.add(customizer);
+        if (customizers != null) result.addAll(customizers);
+        if (Strings.isNonBlank(customizerType)) {
+            Optional<JcloudsLocationCustomizer> customizerByType = 
Reflections.invokeConstructorWithArgs(catalogClassLoader, customizerType, 
setup);
+            if (customizerByType.isPresent()) {
+                result.add(customizerByType.get());
+            } else {
+                customizerByType = 
Reflections.invokeConstructorWithArgs(catalogClassLoader, customizerType);
+                if (customizerByType.isPresent()) {
+                    result.add(customizerByType.get());
+                } else {
+                    throw new IllegalStateException("Failed to create 
JcloudsLocationCustomizer "+customizersSupplierType+" for location "+this);
+                }
+            }
+        }
+        if (Strings.isNonBlank(customizersSupplierType)) {
+            Optional<Supplier<Collection<JcloudsLocationCustomizer>>> supplier 
= Reflections.invokeConstructorWithArgs(catalogClassLoader, 
customizersSupplierType, setup);
+            if (supplier.isPresent()) {
+                result.addAll(supplier.get().get());
+            } else {
+                supplier = 
Reflections.invokeConstructorWithArgs(catalogClassLoader, 
customizersSupplierType);
+                if (supplier.isPresent()) {
+                    result.addAll(supplier.get().get());
+                } else {
+                    throw new IllegalStateException("Failed to create 
JcloudsLocationCustomizer supplier "+customizersSupplierType+" for location 
"+this);
+                }
+            }
+        }
+        return result;
+    }
+
+    protected Collection<MachineLocationCustomizer> 
getMachineCustomizers(ConfigBag setup) {
+        Collection<MachineLocationCustomizer> customizers = 
setup.get(MACHINE_LOCATION_CUSTOMIZERS);
+        return (customizers == null ? 
ImmutableList.<MachineLocationCustomizer>of() : customizers);
+    }
+
+    public void setDefaultImageId(String val) {
+        config().set(DEFAULT_IMAGE_ID, val);
+    }
+
+    // TODO remove tagMapping, or promote it
+    // (i think i favour removing it, letting the config come in from the 
entity)
+
+    public void setTagMapping(Map<String,Map<String, ? extends Object>> val) {
+        tagMapping.clear();
+        tagMapping.putAll(val);
+    }
+
+    // TODO Decide on semantics. If I give "TomcatServer" and "Ubuntu", then 
must I get back an image that matches both?
+    // Currently, just takes first match that it finds...
+    @Override
+    public Map<String,Object> getProvisioningFlags(Collection<String> tags) {
+        Map<String,Object> result = Maps.newLinkedHashMap();
+        Collection<String> unmatchedTags = Lists.newArrayList();
+        for (String it : tags) {
+            if (groovyTruth(tagMapping.get(it)) && !groovyTruth(result)) {
+                result.putAll(tagMapping.get(it));
+            } else {
+                unmatchedTags.add(it);
+            }
+        }
+        if (unmatchedTags.size() > 0) {
+            LOG.debug("Location {}, failed to match provisioning tags {}", 
this, unmatchedTags);
+        }
+        return result;
+    }
+
+    public static final Set<ConfigKey<?>> getAllSupportedProperties() {
+        Set<String> configsOnClass = Sets.newLinkedHashSet(
+            
Iterables.transform(ConfigUtils.getStaticKeysOnClass(JcloudsLocation.class),
+                new Function<HasConfigKey<?>,String>() {
+                    @Override @Nullable
+                    public String apply(@Nullable HasConfigKey<?> input) {
+                        return input.getConfigKey().getName();
+                    }
+                }));
+        Set<ConfigKey<?>> configKeysInList = 
ImmutableSet.<ConfigKey<?>>builder()
+                .addAll(SUPPORTED_TEMPLATE_BUILDER_PROPERTIES.keySet())
+                .addAll(SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.keySet())
+                .build();
+        Set<String> configsInList = Sets.newLinkedHashSet(
+            Iterables.transform(configKeysInList,
+            new Function<ConfigKey<?>,String>() {
+                @Override @Nullable
+                public String apply(@Nullable ConfigKey<?> input) {
+                    return input.getName();
+                }
+            }));
+
+        SetView<String> extrasInList = Sets.difference(configsInList, 
configsOnClass);
+        // notInList is normal
+        if (!extrasInList.isEmpty())
+            LOG.warn("JcloudsLocation supported properties differs from config 
defined on class: " + extrasInList);
+        return Collections.unmodifiableSet(configKeysInList);
+    }
+
+    public ComputeService getComputeService() {
+        return getComputeService(MutableMap.of());
+    }
+    public ComputeService getComputeService(Map<?,?> flags) {
+        ConfigBag conf = (flags==null || flags.isEmpty())
+                ? config().getBag()
+                : ConfigBag.newInstanceExtending(config().getBag(), flags);
+        return getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(conf, 
true);
+    }
+
+    /** @deprecated since 0.7.0 use {@link #listMachines()} */ @Deprecated
+    public Set<? extends ComputeMetadata> listNodes() {
+        return listNodes(MutableMap.of());
+    }
+    /** @deprecated since 0.7.0 use {@link #listMachines()}.
+     * (no support for custom compute service flags; if that is needed, we'll 
have to introduce a new method,
+     * but it seems there are no usages) */ @Deprecated
+    public Set<? extends ComputeMetadata> listNodes(Map<?,?> flags) {
+        return getComputeService(flags).listNodes();
+    }
+
+    @Override
+    public Map<String, MachineManagementMixins.MachineMetadata> listMachines() 
{
+        Set<? extends ComputeMetadata> nodes =
+            getRegion()!=null ? 
getComputeService().listNodesDetailsMatching(new NodeInLocation(getRegion(), 
true))
+                : getComputeService().listNodes();
+        Map<String,MachineManagementMixins.MachineMetadata> result = new 
LinkedHashMap<String, MachineManagementMixins.MachineMetadata>();
+
+        for (ComputeMetadata node: nodes)
+            result.put(node.getId(), getMachineMetadata(node));
+
+        return result;
+    }
+
+    protected MachineManagementMixins.MachineMetadata 
getMachineMetadata(ComputeMetadata node) {
+        if (node==null)
+            return null;
+        return new BasicMachineMetadata(node.getId(), node.getName(),
+            ((node instanceof NodeMetadata) ? Iterators.tryFind( 
((NodeMetadata)node).getPublicAddresses().iterator(), Predicates.alwaysTrue() 
).orNull() : null),
+            ((node instanceof NodeMetadata) ? 
((NodeMetadata)node).getStatus()==Status.RUNNING : null),
+            node);
+    }
+
+    @Override
+    public MachineManagementMixins.MachineMetadata 
getMachineMetadata(MachineLocation l) {
+        if (l instanceof JcloudsSshMachineLocation) {
+            return getMachineMetadata( ((JcloudsSshMachineLocation)l).node );
+        }
+        return null;
+    }
+
+    @Override
+    public void killMachine(String cloudServiceId) {
+        getComputeService().destroyNode(cloudServiceId);
+    }
+
+    @Override
+    public void killMachine(MachineLocation l) {
+        MachineManagementMixins.MachineMetadata m = getMachineMetadata(l);
+        if (m==null) throw new NoSuchElementException("Machine "+l+" is not 
known at "+this);
+        killMachine(m.getId());
+    }
+
+    /** attaches a string describing where something is being created
+     * (provider, region/location and/or endpoint, callerContext) */
+    protected void setCreationString(ConfigBag config) {
+        config.setDescription(elvis(config.get(CLOUD_PROVIDER), "unknown")+
+                (config.containsKey(CLOUD_REGION_ID) ? 
":"+config.get(CLOUD_REGION_ID) : "")+
+                (config.containsKey(CLOUD_ENDPOINT) ? 
":"+config.get(CLOUD_ENDPOINT) : "")+
+                (config.containsKey(CALLER_CONTEXT) ? 
"@"+config.get(CALLER_CONTEXT) : ""));
+    }
+
+    // ----------------- obtaining a new machine ------------------------
+    public MachineLocation obtain() throws NoMachinesAvailableException {
+        return obtain(MutableMap.of());
+    }
+    public MachineLocation obtain(TemplateBuilder tb) throws 
NoMachinesAvailableException {
+        return obtain(MutableMap.of(), tb);
+    }
+    public MachineLocation obtain(Map<?,?> flags, TemplateBuilder tb) throws 
NoMachinesAvailableException {
+        return obtain(MutableMap.builder().putAll(flags).put(TEMPLATE_BUILDER, 
tb).build());
+    }
+
+    /** core method for obtaining a VM using jclouds;
+     * Map should contain CLOUD_PROVIDER and CLOUD_ENDPOINT or CLOUD_REGION, 
depending on the cloud,
+     * as well as ACCESS_IDENTITY and ACCESS_CREDENTIAL,
+     * plus any further properties to specify e.g. images, hardware profiles, 
accessing user
+     * (for initial login, and a user potentially to create for subsequent ie 
normal access) */
+    @Override
+    public MachineLocation obtain(Map<?,?> flags) throws 
NoMachinesAvailableException {
+        ConfigBag setup = ConfigBag.newInstanceExtending(config().getBag(), 
flags);
+        Integer attempts = setup.get(MACHINE_CREATE_ATTEMPTS);
+        List<Exception> exceptions = Lists.newArrayList();
+        if (attempts == null || attempts < 1) attempts = 1;
+        for (int i = 1; i <= attempts; i++) {
+            try {
+                return obtainOnce(setup);
+            } catch (RuntimeException e) {
+                LOG.warn("Attempt #{}/{} to obtain machine threw error: {}", 
new Object[]{i, attempts, e});
+                exceptions.add(e);
+            }
+        }
+        String msg = String.format("Failed to get VM after %d attempt%s.", 
attempts, attempts == 1 ? "" : "s");
+
+        Exception cause = (exceptions.size() == 1)
+                ? exceptions.get(0)
+                : new CompoundRuntimeException(msg + " - "
+                    + "First cause is "+exceptions.get(0)+" (listed in primary 
trace); "
+                    + "plus " + (exceptions.size()-1) + " more (e.g. the last 
is "+exceptions.get(exceptions.size()-1)+")",
+                    exceptions.get(0), exceptions);
+
+        if (exceptions.get(exceptions.size()-1) instanceof 
NoMachinesAvailableException) {
+            throw new NoMachinesAvailableException(msg, cause);
+        } else {
+            throw Exceptions.propagate(cause);
+        }
+    }
+
+    protected MachineLocation obtainOnce(ConfigBag setup) throws 
NoMachinesAvailableException {
+        AccessController.Response access = 
getManagementContext().getAccessController().canProvisionLocation(this);
+        if (!access.isAllowed()) {
+            throw new IllegalStateException("Access controller forbids 
provisioning in "+this+": "+access.getMsg());
+        }
+
+        setCreationString(setup);
+        boolean waitForSshable = 
!"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
+        boolean waitForWinRmable = 
!"false".equalsIgnoreCase(setup.get(WAIT_FOR_WINRM_AVAILABLE));
+        boolean usePortForwarding = setup.get(USE_PORT_FORWARDING);
+        boolean skipJcloudsSshing = 
Boolean.FALSE.equals(setup.get(USE_JCLOUDS_SSH_INIT)) || usePortForwarding;
+        JcloudsPortForwarderExtension portForwarder = 
setup.get(PORT_FORWARDER);
+        if (usePortForwarding) checkNotNull(portForwarder, "portForwarder, 
when use-port-forwarding enabled");
+
+        final ComputeService computeService = 
getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(setup, true);
+        CloudMachineNamer cloudMachineNamer = getCloudMachineNamer(setup);
+        String groupId = elvis(setup.get(GROUP_ID), 
cloudMachineNamer.generateNewGroupId(setup));
+        NodeMetadata node = null;
+        JcloudsMachineLocation machineLocation = null;
+        
+        try {
+            LOG.info("Creating VM "+setup.getDescription()+" in "+this);
+
+            Semaphore machineCreationSemaphore = getMachineCreationSemaphore();
+            boolean acquired = machineCreationSemaphore.tryAcquire(0, 
TimeUnit.SECONDS);
+            if (!acquired) {
+                LOG.info("Waiting in {} for machine-creation permit ({} other 
queuing requests already)", new Object[] {this, 
machineCreationSemaphore.getQueueLength()});
+                Stopwatch blockStopwatch = Stopwatch.createStarted();
+                machineCreationSemaphore.acquire();
+                LOG.info("Acquired in {} machine-creation permit, after 
waiting {}", this, Time.makeTimeStringRounded(blockStopwatch));
+            } else {
+                LOG.debug("Acquired in {} machine-creation permit 
immediately", this);
+            }
+
+            Stopwatch provisioningStopwatch = Stopwatch.createStarted();
+            Duration templateTimestamp, provisionTimestamp, usableTimestamp, 
customizedTimestamp;
+
+            LoginCredentials userCredentials = null;
+            Set<? extends NodeMetadata> nodes;
+            Template template;
+            try {
+                // Setup the template
+                template = buildTemplate(computeService, setup);
+                boolean expectWindows = isWindows(template, setup);
+                if (!skipJcloudsSshing) {
+                    if (expectWindows) {
+                        // TODO Was this too early to look at 
template.getImage? e.g. customizeTemplate could subsequently modify it.
+                        LOG.warn("Ignoring invalid configuration for Windows 
provisioning of "+template.getImage()+": "+USE_JCLOUDS_SSH_INIT.getName()+" 
should be false");
+                        skipJcloudsSshing = true;
+                    } else if (waitForSshable) {
+                        userCredentials = initTemplateForCreateUser(template, 
setup);
+                    }
+                }
+
+                templateTimestamp = Duration.of(provisioningStopwatch);
+                // "Name" metadata seems to set the display name; at least in 
AWS
+                // TODO it would be nice if this salt comes from the 
location's ID (but we don't know that yet as the ssh machine location isn't 
created yet)
+                // TODO in softlayer we want to control the suffix of the 
hostname which is 3 random hex digits
+                template.getOptions().getUserMetadata().put("Name", 
cloudMachineNamer.generateNewMachineUniqueNameFromGroupId(setup, groupId));
+                
+                if 
(setup.get(JcloudsLocationConfig.INCLUDE_BROOKLYN_USER_METADATA)) {
+                    
template.getOptions().getUserMetadata().put("brooklyn-user", 
System.getProperty("user.name"));
+                    
+                    Object context = setup.get(CALLER_CONTEXT);
+                    if (context instanceof Entity) {
+                        Entity entity = (Entity)context;
+                        
template.getOptions().getUserMetadata().put("brooklyn-app-id", 
entity.getApplicationId());
+                        
template.getOptions().getUserMetadata().put("brooklyn-app-name", 
entity.getApplication().getDisplayName());
+                        
template.getOptions().getUserMetadata().put("brooklyn-entity-id", 
entity.getId());
+                        
template.getOptions().getUserMetadata().put("brooklyn-entity-name", 
entity.getDisplayName());
+                        
template.getOptions().getUserMetadata().put("brooklyn-server-creation-date", 
Time.makeDateSimpleStampString());
+                    }
+                }
+                
+                customizeTemplate(setup, computeService, template);
+                
+                LOG.debug("jclouds using template {} / options {} to provision 
machine in {}",
+                        new Object[] {template, template.getOptions(), 
setup.getDescription()});
+
+                if (!setup.getUnusedConfig().isEmpty())
+                    LOG.debug("NOTE: unused flags passed to obtain VM in 
"+setup.getDescription()+": "+
+                            setup.getUnusedConfig());
+                
+                nodes = computeService.createNodesInGroup(groupId, 1, 
template);
+                provisionTimestamp = Duration.of(provisioningStopwatch);
+            } finally {
+                machineCreationSemaphore.release();
+            }
+
+            node = Iterables.getOnlyElement(nodes, null);
+            LOG.debug("jclouds created {} for {}", node, 
setup.getDescription());
+            if (node == null)
+                throw new IllegalStateException("No nodes returned by jclouds 
create-nodes in " + setup.getDescription());
+
+            boolean windows = isWindows(node, setup);
+            if (windows) {
+                int newLoginPort = node.getLoginPort() == 22 ? 5985 : 
node.getLoginPort();
+                String newLoginUser = 
"root".equals(node.getCredentials().getUser()) ? "Administrator" : 
node.getCredentials().getUser();
+                LOG.debug("jclouds created Windows VM {}; transforming 
connection details: loginPort from {} to {}; loginUser from {} to {}", 
+                        new Object[] {node, node.getLoginPort(), newLoginPort, 
node.getCredentials().getUser(), newLoginUser});
+                
+                node = NodeMetadataBuilder.fromNodeMetadata(node)
+                        .loginPort(newLoginPort)
+                        
.credentials(LoginCredentials.builder(node.getCredentials()).user(newLoginUser).build())
+                        .build();
+            }
+            // FIXME How do we influence the node.getLoginPort, so it is set 
correctly for Windows?
+            // Setup port-forwarding, if required
+            Optional<HostAndPort> sshHostAndPortOverride;
+            if (usePortForwarding) {
+                sshHostAndPortOverride = 
Optional.of(portForwarder.openPortForwarding(
+                        node,
+                        node.getLoginPort(),
+                        Optional.<Integer>absent(),
+                        Protocol.TCP,
+                        Cidr.UNIVERSAL));
+            } else {
+                sshHostAndPortOverride = Optional.absent();
+            }
+
+            if (skipJcloudsSshing) {
+                boolean waitForConnectable = (windows) ? waitForWinRmable : 
waitForSshable;
+                if (waitForConnectable) {
+                    if (windows) {
+                        // TODO Does jclouds support any windows user setup?
+                        waitForWinRmAvailable(computeService, node, 
sshHostAndPortOverride, node.getCredentials(), setup);
+                    } else {
+                        waitForSshable(computeService, node, 
sshHostAndPortOverride, node.getCredentials(), setup);
+                    }
+                    userCredentials = createUser(computeService, node, 
sshHostAndPortOverride, setup);
+                }
+            }
+
+            // Figure out which login-credentials to use
+            LoginCredentials customCredentials = setup.get(CUSTOM_CREDENTIALS);
+            if (customCredentials != null) {
+                userCredentials = customCredentials;
+                //set userName and other data, from these credentials
+                Object oldUsername = setup.put(USER, 
customCredentials.getUser());
+                LOG.debug("node {} username {} / {} (customCredentials)", new 
Object[] { node, customCredentials.getUser(), oldUsername });
+                if (customCredentials.getOptionalPassword().isPresent()) 
setup.put(PASSWORD, customCredentials.getOptionalPassword().get());
+                if (customCredentials.getOptionalPrivateKey().isPresent()) 
setup.put(PRIVATE_KEY_DATA, customCredentials.getOptionalPrivateKey().get());
+            }
+            if (userCredentials == null) {
+                userCredentials = extractVmCredentials(setup, node);
+            }
+            if (userCredentials != null) {
+                node = 
NodeMetadataBuilder.fromNodeMetadata(node).credentials(userCredentials).build();
+            } else {
+                // only happens if something broke above...
+                userCredentials = 
LoginCredentials.fromCredentials(node.getCredentials());
+            }
+            // store the credentials, in case they have changed
+            setup.putIfNotNull(JcloudsLocationConfig.PASSWORD, 
userCredentials.getOptionalPassword().orNull());
+            setup.putIfNotNull(JcloudsLocationConfig.PRIVATE_KEY_DATA, 
userCredentials.getOptionalPrivateKey().orNull());
+
+            // Wait for the VM to be reachable over SSH
+            if (waitForSshable && !windows) {
+                waitForSshable(computeService, node, sshHostAndPortOverride, 
userCredentials, setup);
+            } else {
+                LOG.debug("Skipping ssh check for {} ({}) due to config 
waitForSshable=false", node, setup.getDescription());
+            }
+            usableTimestamp = Duration.of(provisioningStopwatch);
+
+//            JcloudsSshMachineLocation jcloudsSshMachineLocation = null;
+//            WinRmMachineLocation winRmMachineLocation = null;
+            // Create a JcloudsSshMachineLocation, and register it
+            if (windows) {
+                machineLocation = registerWinRmMachineLocation(computeService, 
node, userCredentials, sshHostAndPortOverride, setup);
+            } else {
+                machineLocation = 
registerJcloudsSshMachineLocation(computeService, node, userCredentials, 
sshHostAndPortOverride, setup);
+                if (template!=null && machineLocation.getTemplate()==null) {
+                    ((JcloudsSshMachineLocation)machineLocation).template = 
template;
+                }
+            }
+
+            if (usePortForwarding && sshHostAndPortOverride.isPresent()) {
+                // Now that we have the sshMachineLocation, we can associate 
the port-forwarding address with it.
+                PortForwardManager portForwardManager = 
setup.get(PORT_FORWARDING_MANAGER);
+                if (portForwardManager != null) {
+                    portForwardManager.associate(node.getId(), 
sshHostAndPortOverride.get(), machineLocation, node.getLoginPort());
+                } else {
+                    LOG.warn("No port-forward manager for {} so could not 
associate {} -> {} for {}",
+                            new Object[] {this, node.getLoginPort(), 
sshHostAndPortOverride, machineLocation});
+                }
+            }
+
+            if ("docker".equals(this.getProvider())) {
+                if (windows) {
+                    throw new UnsupportedOperationException("Docker not 
supported on Windows");
+                }
+                Map<Integer, Integer> portMappings = 
JcloudsUtil.dockerPortMappingsFor(this, node.getId());
+                PortForwardManager portForwardManager = 
setup.get(PORT_FORWARDING_MANAGER);
+                if (portForwardManager != null) {
+                    for(Integer containerPort : portMappings.keySet()) {
+                        Integer hostPort = portMappings.get(containerPort);
+                        String dockerHost = 
((JcloudsSshMachineLocation)machineLocation).getSshHostAndPort().getHostText();
+                        portForwardManager.associate(node.getId(), 
HostAndPort.fromParts(dockerHost, hostPort), machineLocation, containerPort);
+                    }
+                } else {
+                    LOG.warn("No port-forward manager for {} so could not 
associate docker port-mappings for {}",
+                            this, machineLocation);
+                }
+            }
+
+            List<String> customisationForLogging = new ArrayList<String>();
+            // Apply same securityGroups rules to iptables, if iptables is 
running on the node
+            if (waitForSshable) {
+
+                String setupScript = 
setup.get(JcloudsLocationConfig.CUSTOM_MACHINE_SETUP_SCRIPT_URL);
+                List<String> setupScripts = 
setup.get(JcloudsLocationConfig.CUSTOM_MACHINE_SETUP_SCRIPT_URL_LIST);
+                Collection<String> allScripts = new 
MutableList<String>().appendIfNotNull(setupScript).appendAll(setupScripts);
+                for (String setupScriptItem : allScripts) {
+                    if (Strings.isNonBlank(setupScriptItem)) {
+                        customisationForLogging.add("custom setup script " + 
setupScriptItem);
+
+                        String setupVarsString = 
setup.get(JcloudsLocationConfig.CUSTOM_MACHINE_SETUP_SCRIPT_VARS);
+                        Map<String, String> substitutions = (setupVarsString 
!= null)
+                                ? 
Splitter.on(",").withKeyValueSeparator(":").split(setupVarsString)
+                                : ImmutableMap.<String, String>of();
+                        String scriptContent = 
ResourceUtils.create(this).getResourceAsString(setupScriptItem);
+                        String script = 
TemplateProcessor.processTemplateContents(scriptContent, 
getManagementContext(), substitutions);
+                        if (windows) {
+                            
((WinRmMachineLocation)machineLocation).executeScript(ImmutableList.copyOf((script.replace("\r",
 "").split("\n"))));
+                        } else {
+                            
((SshMachineLocation)machineLocation).execCommands("Customizing node " + this, 
ImmutableList.of(script));
+                        }
+                    }
+                }
+
+                if 
(setup.get(JcloudsLocationConfig.MAP_DEV_RANDOM_TO_DEV_URANDOM)) {
+                    if (windows) {
+                        LOG.warn("Ignoring flag MAP_DEV_RANDOM_TO_DEV_URANDOM 
on Windows location {}", machineLocation);
+                    } else {
+                        customisationForLogging.add("point /dev/random to 
urandom");
+
+                        
((SshMachineLocation)machineLocation).execCommands("using urandom instead of 
random",
+                                Arrays.asList("sudo mv /dev/random 
/dev/random-real", "sudo ln -s /dev/urandom /dev/random"));
+                    }
+                }
+
+
+                if (setup.get(GENERATE_HOSTNAME)) {
+                    if (windows) {
+                        // TODO: Generate Windows Hostname
+                        LOG.warn("Ignoring flag GENERATE_HOSTNAME on Windows 
location {}", machineLocation);
+                    } else {
+                        customisationForLogging.add("configure hostname");
+
+                        
((SshMachineLocation)machineLocation).execCommands("Generate hostname " + 
node.getName(),
+                                Arrays.asList("sudo hostname " + 
node.getName(),
+                                        "sudo sed -i 
\"s/HOSTNAME=.*/HOSTNAME=" + node.getName() + "/g\" /etc/sysconfig/network",
+                                        "sudo bash -c \"echo 127.0.0.1   
`hostname` >> /etc/hosts\"")
+                        );
+                    }
+                }
+
+                if (setup.get(OPEN_IPTABLES)) {
+                    if (windows) {
+                        LOG.warn("Ignoring DEPRECATED flag OPEN_IPTABLES on 
Windows location {}", machineLocation);
+                    } else {
+                        LOG.warn("Using DEPRECATED flag OPEN_IPTABLES (will 
not be supported in future versions) for {} at {}", machineLocation, this);
+                        
+                        @SuppressWarnings("unchecked")
+                        Iterable<Integer> inboundPorts = (Iterable<Integer>) 
setup.get(INBOUND_PORTS);
+
+                        if (inboundPorts == null || 
Iterables.isEmpty(inboundPorts)) {
+                            LOG.info("No ports to open in iptables (no inbound 
ports) for {} at {}", machineLocation, this);
+                        } else {
+                            customisationForLogging.add("open iptables");
+
+                            List<String> iptablesRules = Lists.newArrayList();
+
+                            if 
(isLocationFirewalldEnabled((SshMachineLocation)machineLocation)) {
+                                for (Integer port : inboundPorts) {
+                                    
iptablesRules.add(IptablesCommands.addFirewalldRule(Chain.INPUT, Protocol.TCP, 
port, Policy.ACCEPT));
+                                 }
+                            } else {
+                                iptablesRules = 
createIptablesRulesForNetworkInterface(inboundPorts);
+                                
iptablesRules.add(IptablesCommands.saveIptablesRules());
+                            }
+                            List<String> batch = Lists.newArrayList();
+                            // Some entities, such as Riak (erlang based) have 
a huge range of ports, which leads to a script that
+                            // is too large to run (fails with a broken pipe). 
Batch the rules into batches of 50
+                            for (String rule : iptablesRules) {
+                                batch.add(rule);
+                                if (batch.size() == 50) {
+                                    
((SshMachineLocation)machineLocation).execCommands("Inserting iptables rules, 
50 command batch", batch);
+                                    batch.clear();
+                                }
+                            }
+                            if (batch.size() > 0) {
+                                
((SshMachineLocation)machineLocation).execCommands("Inserting iptables rules", 
batch);
+                            }
+                            
((SshMachineLocation)machineLocation).execCommands("List iptables rules", 
ImmutableList.of(IptablesCommands.listIptablesRule()));
+                        }
+                    }
+                }
+
+                if (setup.get(STOP_IPTABLES)) {
+                    if (windows) {
+                        LOG.warn("Ignoring DEPRECATED flag OPEN_IPTABLES on 
Windows location {}", machineLocation);
+                    } else {
+                        LOG.warn("Using DEPRECATED flag STOP_IPTABLES (will 
not be supported in future versions) for {} at {}", machineLocation, this);
+                        
+                        customisationForLogging.add("stop iptables");
+
+                        List<String> cmds = ImmutableList.<String>of();
+                        if 
(isLocationFirewalldEnabled((SshMachineLocation)machineLocation)) {
+                            cmds = 
ImmutableList.of(IptablesCommands.firewalldServiceStop(), 
IptablesCommands.firewalldServiceStatus());
+                        } else {
+                            cmds = 
ImmutableList.of(IptablesCommands.iptablesServiceStop(), 
IptablesCommands.iptablesServiceStatus());
+                        }
+                        
((SshMachineLocation)machineLocation).execCommands("Stopping iptables", cmds);
+                    }
+                }
+
+                List<String> extraKeyUrlsToAuth = 
setup.get(EXTRA_PUBLIC_KEY_URLS_TO_AUTH);
+                if (extraKeyUrlsToAuth!=null && !extraKeyUrlsToAuth.isEmpty()) 
{
+                    if (windows) {
+                        LOG.warn("Ignoring flag EXTRA_PUBLIC_KEY_URLS_TO_AUTH 
on Windows location", machineLocation);
+                    } else {
+                        List<String> extraKeyDataToAuth = MutableList.of();
+                        for (String keyUrl : extraKeyUrlsToAuth) {
+                            
extraKeyDataToAuth.add(ResourceUtils.create().getResourceAsString(keyUrl));
+                        }
+                        
((SshMachineLocation)machineLocation).execCommands("Authorizing ssh keys",
+                                ImmutableList.of(new 
AuthorizeRSAPublicKeys(extraKeyDataToAuth).render(org.jclouds.scriptbuilder.domain.OsFamily.UNIX)));
+                    }
+                }
+
+            } else {
+                // Otherwise we have deliberately not waited to be ssh'able, 
so don't try now to
+                // ssh to exec these commands!
+            }
+
+            // Apply any optional app-specific customization.
+            for (JcloudsLocationCustomizer customizer : getCustomizers(setup)) 
{
+                customizer.customize(this, computeService, machineLocation);
+            }
+            for (MachineLocationCustomizer customizer : 
getMachineCustomizers(setup)) {
+                customizer.customize(machineLocation);
+            }
+
+            customizedTimestamp = Duration.of(provisioningStopwatch);
+
+            try {
+                String logMessage = "Finished VM "+setup.getDescription()+" 
creation:"
+                        + " 
"+machineLocation.getUser()+"@"+machineLocation.getAddress()+":"+machineLocation.getPort()
+                        + (Boolean.TRUE.equals(setup.get(LOG_CREDENTIALS))
+                                ? "password=" + 
userCredentials.getOptionalPassword().or("<absent>")
+                                + " && key=" + 
userCredentials.getOptionalPrivateKey().or("<absent>")
+                                : "")
+                        + " ready after 
"+Duration.of(provisioningStopwatch).toStringRounded()
+                        + " ("+template+" template built in 
"+Duration.of(templateTimestamp).toStringRounded()+";"
+                        + " "+node+" provisioned in 
"+Duration.of(provisionTimestamp).subtract(templateTimestamp).toStringRounded()+";"
+                        + " "+machineLocation+" connection usable in 
"+Duration.of(usableTimestamp).subtract(provisionTimestamp).toStringRounded()+";"
+                        + " and os customized in 
"+Duration.of(customizedTimestamp).subtract(usableTimestamp).toStringRounded()+"
 - "+Joiner.on(", ").join(customisationForLogging)+")";
+                LOG.info(logMessage);
+            } catch (Exception e){
+                // TODO Remove try-catch! @Nakomis: why did you add it? What 
exception happened during logging?
+                Exceptions.propagateIfFatal(e);
+                LOG.warn("Problem generating log message summarising 
completion of jclouds machine provisioning "+machineLocation+" by "+this, e);
+            }
+
+            return machineLocation;
+            
+        } catch (Exception e) {
+            if (e instanceof RunNodesException && 
((RunNodesException)e).getNodeErrors().size() > 0) {
+                node = 
Iterables.get(((RunNodesException)e).getNodeErrors().keySet(), 0);
+            }
+            // sometimes AWS nodes come up busted (eg ssh not allowed); just 
throw it back (and maybe try for another one)
+            boolean destroyNode = (node != null) && 
Boolean.TRUE.equals(setup.get(DESTROY_ON_FAILURE));
+
+            if (e.toString().contains("VPCResourceNotSpecified")) {
+                LOG.error("Detected that your EC2 account is a legacy 
'classic' account, but the recommended instance type requires VPC. "
+                    + "You can specify the 'eu-central-1' region to avoid this 
problem, or you can specify a classic-compatible instance type, "
+                    + "or you can specify a subnet to use with 'networkName' "
+                    + "(taking care that the subnet auto-assigns public IP's 
and allows ingress on all ports, "
+                    + "as Brooklyn does not currently configure security 
groups for non-default VPC's; "
+                    + "or setting up Brooklyn to be in the subnet or have a 
jump host or other subnet access configuration). "
+                    + "For more information on VPC vs classic see 
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-vpc.html.";);
+            }
+            
+            LOG.error("Failed to start VM for {}{}: {}",
+                    new Object[] {setup.getDescription(), (destroyNode ? " 
(destroying "+node+")" : ""), e.getMessage()});
+            LOG.debug(Throwables.getStackTraceAsString(e));
+            
+            if (destroyNode) {
+                if (machineLocation != null) {
+                    releaseSafely(machineLocation);
+                } else {
+                    releaseNodeSafely(node);
+                }
+            }
+
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    // ------------- suspend and resume ------------------------------------
+
+    /**
+     * Suspends the given location.
+     * <p>
+     * Note that this method does <b>not</b> call the lifecycle methods of any
+     * {@link #getCustomizers(ConfigBag) customizers} attached to this 
location.
+     */
+    @Override
+    public void suspendMachine(MachineLocation rawLocation) {
+        String instanceId = vmInstanceIds.remove(rawLocation);
+        if (instanceId == null) {
+            LOG.info("Attempt to suspend unknown machine " + rawLocation + " 
in " + this);
+            throw new IllegalArgumentException("Unknown machine " + 
rawLocation);
+        }
+        LOG.info("Suspending machine {} in {}, instance id {}", new 
Object[]{rawLocation, this, instanceId});
+        Exception toThrow = null;
+        try {
+            getComputeService().suspendNode(instanceId);
+        } catch (Exception e) {
+            toThrow = e;
+            LOG.error("Problem suspending machine " + rawLocation + " in " + 
this + ", instance id " + instanceId, e);
+        }
+        removeChild(rawLocation);
+        if (toThrow != null) {
+            throw Exceptions.propagate(toThrow);
+        }
+    }
+
+    // ------------- constructing the template, etc ------------------------
+
+    private static interface CustomizeTemplateBuilder {
+        void apply(TemplateBuilder tb, ConfigBag props, Object v);
+    }
+
+    public static interface CustomizeTemplateOptions {
+        void apply(TemplateOptions tb, ConfigBag props, Object v);
+    }
+
+    /** properties which cause customization of the TemplateBuilder */
+    public static final Map<ConfigKey<?>,CustomizeTemplateBuilder> 
SUPPORTED_TEMPLATE_BUILDER_PROPERTIES = 
ImmutableMap.<ConfigKey<?>,CustomizeTemplateBuilder>builder()
+            .put(OS_64_BIT, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        Boolean os64Bit = TypeCoercions.coerce(v, 
Boolean.class);
+                        if (os64Bit!=null)
+                            tb.os64Bit(os64Bit);
+                    }})
+            .put(MIN_RAM, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.minRam( 
(int)(ByteSizeStrings.parse(Strings.toString(v), "mb")/1000/1000) );
+                    }})
+            .put(MIN_CORES, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.minCores(TypeCoercions.coerce(v, Double.class));
+                    }})
+            .put(MIN_DISK, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.minDisk( 
(int)(ByteSizeStrings.parse(Strings.toString(v), "gb")/1000/1000/1000) );
+                    }})
+            .put(HARDWARE_ID, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.hardwareId(((CharSequence)v).toString());
+                    }})
+            .put(IMAGE_ID, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.imageId(((CharSequence)v).toString());
+                    }})
+            .put(IMAGE_DESCRIPTION_REGEX, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        
tb.imageDescriptionMatches(((CharSequence)v).toString());
+                    }})
+            .put(IMAGE_NAME_REGEX, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.imageNameMatches(((CharSequence)v).toString());
+                    }})
+            .put(OS_FAMILY, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        Maybe<OsFamily> osFamily = 
Enums.valueOfIgnoreCase(OsFamily.class, v.toString());
+                        if (osFamily.isAbsent())
+                            throw new IllegalArgumentException("Invalid 
"+OS_FAMILY+" value "+v);
+                        tb.osFamily(osFamily.get());
+                    }})
+            .put(OS_VERSION_REGEX, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        tb.osVersionMatches( ((CharSequence)v).toString() );
+                    }})
+            .put(TEMPLATE_SPEC, new CustomizeTemplateBuilder() {
+                public void apply(TemplateBuilder tb, ConfigBag props, Object 
v) {
+                        
tb.from(TemplateBuilderSpec.parse(((CharSequence)v).toString()));
+                    }})
+            .put(DEFAULT_IMAGE_ID, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        /* done in the code, but included here so that it is 
in the map */
+                    }})
+            .put(TEMPLATE_BUILDER, new CustomizeTemplateBuilder() {
+                    public void apply(TemplateBuilder tb, ConfigBag props, 
Object v) {
+                        /* done in the code, but included here so that it is 
in the map */
+                    }})
+            .build();
+
+    /** properties which cause customization of the TemplateOptions */
+    public static final Map<ConfigKey<?>,CustomizeTemplateOptions> 
SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES = 
ImmutableMap.<ConfigKey<?>,CustomizeTemplateOptions>builder()
+            .put(SECURITY_GROUPS, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof EC2TemplateOptions) {
+                            String[] securityGroups = toStringArray(v);
+                            
((EC2TemplateOptions)t).securityGroups(securityGroups);
+                        } else if (t instanceof NovaTemplateOptions) {
+                            String[] securityGroups = toStringArray(v);
+                            
((NovaTemplateOptions)t).securityGroups(securityGroups);
+                        } else if (t instanceof SoftLayerTemplateOptions) {
+                            String[] securityGroups = toStringArray(v);
+                            
((SoftLayerTemplateOptions)t).securityGroups(securityGroups);
+                        } else if (t instanceof 
GoogleComputeEngineTemplateOptions) {
+                            String[] securityGroups = toStringArray(v);
+                            
((GoogleComputeEngineTemplateOptions)t).securityGroups(securityGroups);
+                        } else {
+                            LOG.info("ignoring securityGroups({}) in VM 
creation because not supported for cloud/type ({})", v, t.getClass());
+                        }
+                    }})
+            .put(INBOUND_PORTS, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        int[] inboundPorts = toIntArray(v);
+                        if (LOG.isDebugEnabled()) LOG.debug("opening inbound 
ports {} for cloud/type {}", Arrays.toString(inboundPorts), t.getClass());
+                        t.inboundPorts(inboundPorts);
+                    }})
+            .put(USER_METADATA_STRING, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof EC2TemplateOptions) {
+                            // See AWS docs: 
http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/UsingConfig_WinAMI.html#user-data-execution
+                            if (v==null) return;
+                            String data = v.toString();
+                            if (!(data.startsWith("<script>") || 
data.startsWith("<powershell>"))) {
+                                data = "<script> " + data + " </script>";
+                            }
+                            ((EC2TemplateOptions)t).userData(data.getBytes());
+                        } else if (t instanceof SoftLayerTemplateOptions) {
+                            
((SoftLayerTemplateOptions)t).userData(Strings.toString(v));
+                        } else {
+                            // Try reflection: userData(String), or 
guestCustomizationScript(String);
+                            // the latter is used by vCloud Director.
+                            Class<? extends TemplateOptions> clazz = 
t.getClass();
+                            Method userDataMethod = null;
+                            try {
+                                userDataMethod = clazz.getMethod("userData", 
String.class);
+                            } catch (SecurityException e) {
+                                LOG.info("Problem reflectively inspecting 
methods of "+t.getClass()+" for setting userData", e);
+                            } catch (NoSuchMethodException e) {
+                                try {
+                                    // For vCloud Director
+                                    userDataMethod = 
clazz.getMethod("guestCustomizationScript", String.class);
+                                } catch (NoSuchMethodException e2) {
+                                    // expected on various other clouds
+                                }
+                            }
+                            if (userDataMethod != null) {
+                                try {
+                                    userDataMethod.invoke(t, 
Strings.toString(v));
+                                } catch (InvocationTargetException e) {
+                                    LOG.info("Problem invoking 
"+userDataMethod.getName()+" of "+t.getClass()+", for setting userData 
(rethrowing)", e);
+                                    throw Exceptions.propagate(e);
+                                } catch (IllegalAccessException e) {
+                                    LOG.debug("Unable to reflectively invoke 
"+userDataMethod.getName()+" of "+t.getClass()+", for setting userData 
(rethrowing)", e);
+                                    throw Exceptions.propagate(e);
+                                }
+                            } else {
+                                LOG.info("ignoring userDataString({}) in VM 
creation because not supported for cloud/type ({})", v, t.getClass());
+                            }
+                        }
+                    }})
+            .put(USER_DATA_UUENCODED, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof EC2TemplateOptions) {
+                            byte[] bytes = toByteArray(v);
+                            ((EC2TemplateOptions)t).userData(bytes);
+                        } else if (t instanceof SoftLayerTemplateOptions) {
+                            
((SoftLayerTemplateOptions)t).userData(Strings.toString(v));
+                        } else {
+                            LOG.info("ignoring userData({}) in VM creation 
because not supported for cloud/type ({})", v, t.getClass());
+                        }
+                    }})
+            .put(STRING_TAGS, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        List<String> tags = toListOfStrings(v);
+                        if (LOG.isDebugEnabled()) LOG.debug("setting VM tags 
{} for {}", tags, t);
+                        t.tags(tags);
+                    }})
+            .put(USER_METADATA_MAP, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (v != null) {
+                            t.userMetadata(toMapStringString(v));
+                        }
+                    }})
+            .put(EXTRA_PUBLIC_KEY_DATA_TO_AUTH, new CustomizeTemplateOptions() 
{
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        t.authorizePublicKey(((CharSequence)v).toString());
+                    }})
+            .put(RUN_AS_ROOT, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        t.runAsRoot((Boolean)v);
+                    }})
+            .put(LOGIN_USER, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (v != null) {
+                            t.overrideLoginUser(((CharSequence)v).toString());
+                        }
+                    }})
+            .put(LOGIN_USER_PASSWORD, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (v != null) {
+                            
t.overrideLoginPassword(((CharSequence)v).toString());
+                        }
+                    }})
+            .put(LOGIN_USER_PRIVATE_KEY_FILE, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (v != null) {
+                            String privateKeyFileName = 
((CharSequence)v).toString();
+                            String privateKey;
+                            try {
+                                privateKey = Files.toString(new 
File(Os.tidyPath(privateKeyFileName)), Charsets.UTF_8);
+                            } catch (IOException e) {
+                                LOG.error(privateKeyFileName + "not found", e);
+                                throw Exceptions.propagate(e);
+                            }
+                            t.overrideLoginPrivateKey(privateKey);
+                        }
+                    }})
+            .put(LOGIN_USER_PRIVATE_KEY_DATA, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (v != null) {
+                            
t.overrideLoginPrivateKey(((CharSequence)v).toString());
+                        }
+                    }})
+            .put(KEY_PAIR, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof EC2TemplateOptions) {
+                            
((EC2TemplateOptions)t).keyPair(((CharSequence)v).toString());
+                        } else if (t instanceof NovaTemplateOptions) {
+                            
((NovaTemplateOptions)t).keyPairName(((CharSequence)v).toString());
+                        } else if (t instanceof CloudStackTemplateOptions) {
+                            ((CloudStackTemplateOptions) 
t).keyPair(((CharSequence) v).toString());
+                        } else {
+                            LOG.info("ignoring keyPair({}) in VM creation 
because not supported for cloud/type ({})", v, t);
+                        }
+                    }})
+            .put(AUTO_GENERATE_KEYPAIRS, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof NovaTemplateOptions) {
+                            
((NovaTemplateOptions)t).generateKeyPair((Boolean)v);
+                        } else if (t instanceof CloudStackTemplateOptions) {
+                            ((CloudStackTemplateOptions) 
t).generateKeyPair((Boolean) v);
+                        } else {
+                            LOG.info("ignoring auto-generate-keypairs({}) in 
VM creation because not supported for cloud/type ({})", v, t);
+                        }
+                    }})
+            .put(AUTO_CREATE_FLOATING_IPS, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof NovaTemplateOptions) {
+                            
((NovaTemplateOptions)t).autoAssignFloatingIp((Boolean)v);
+                        } else {
+                            LOG.info("ignoring auto-generate-floating-ips({}) 
in VM creation because not supported for cloud/type ({})", v, t);
+                        }
+                    }})
+            .put(AUTO_ASSIGN_FLOATING_IP, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof NovaTemplateOptions) {
+                            
((NovaTemplateOptions)t).autoAssignFloatingIp((Boolean)v);
+                        } else if (t instanceof CloudStackTemplateOptions) {
+                            
((CloudStackTemplateOptions)t).setupStaticNat((Boolean)v);
+                        } else {
+                            LOG.info("ignoring auto-assign-floating-ip({}) in 
VM creation because not supported for cloud/type ({})", v, t);
+                        }
+                    }})
+            .put(NETWORK_NAME, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof AWSEC2TemplateOptions) {
+                            // subnet ID is the sensible interpretation of 
network name in EC2
+                            ((AWSEC2TemplateOptions)t).subnetId((String)v);
+                            
+                        } else {
+                            if (t instanceof 
GoogleComputeEngineTemplateOptions) {
+                                // no warning needed
+                                // we think this is the only jclouds endpoint 
which supports this option
+                                
+                            } else if (t instanceof SoftLayerTemplateOptions) {
+                                LOG.warn("networkName is not be supported in 
SoftLayer; use `templateOptions` with `primaryNetworkComponentNetworkVlanId` or 
`primaryNetworkBackendComponentNetworkVlanId`");
+                            } else if (!(t instanceof 
CloudStackTemplateOptions) && !(t instanceof NovaTemplateOptions)) {
+                                LOG.warn("networkName is experimental in many 
jclouds endpoints may not be supported in this cloud");
+                                // NB, from @andreaturli
+//                                Cloudstack uses custom securityGroupIds and 
networkIds not the generic networks
+//                                Openstack Nova uses securityGroupNames which 
is marked as @deprecated (suggests to use groups which is maybe even more 
confusing)
+//                                Azure supports the custom 
networkSecurityGroupName
+                            }
+                            
+                            t.networks((String)v);
+                        }
+                    }})
+            .put(DOMAIN_NAME, new CustomizeTemplateOptions() {
+                    public void apply(TemplateOptions t, ConfigBag props, 
Object v) {
+                        if (t instanceof SoftLayerTemplateOptions) {
+                            
((SoftLayerTemplateOptions)t).domainName(TypeCoercions.coerce(v, String.class));
+                        } else {
+                            LOG.info("ignoring domain-name({}) in VM creation 
because not supported for cloud/type ({})", v, t);                            
+                        }
+                    }})
+            .put(TEMPLATE_OPTIONS, new CustomizeTemplateOptions() {
+                @Override
+                public void apply(TemplateOptions options, ConfigBag config, 
Object v) {
+                    if (v == null) return;
+                    @SuppressWarnings("unchecked") Map<String, Object> 
optionsMap = (Map<String, Object>) v;
+                    if (optionsMap.isEmpty()) return;
+
+                    Class<? extends TemplateOptions> clazz = 
options.getClass();
+                    for(final Map.Entry<String, Object> option : 
optionsMap.entrySet()) {
+                        Maybe<?> result = 
MethodCoercions.tryFindAndInvokeBestMatchingMethod(options, option.getKey(), 
option.getValue());
+                        if(result.isAbsent()) {
+                            LOG.warn("Ignoring request to set template option 
{} because this is not supported by {}", new Object[] { option.getKey(), 
clazz.getCanonicalName() });
+                        }
+                    }
+                }})
+            .build();
+
+    /** hook whereby template customizations can be made for various clouds */
+    protected void customizeTemplate(ConfigBag setup, ComputeService 
computeService, Template template) {
+        for (JcloudsLocationCustomizer customizer : getCustomizers(setup)) {
+            customizer.customize(this, computeService, template);
+            customizer.customize(this, computeService, template.getOptions());
+        }
+
+        // these things are nice on softlayer
+        if (template.getOptions() instanceof SoftLayerTemplateOptions) {
+            SoftLayerTemplateOptions slT = 
((SoftLayerTemplateOptions)template.getOptions());
+            if (Strings.isBlank(slT.getDomainName()) || 
"jclouds.org".equals(slT.getDomainName())) {
+                // set a quasi-sensible domain name if none was provided 
(better than the default, jclouds.org)
+                // NB: things like brooklyn.local are disallowed
+                slT.domainName("local.brooklyncentral.org");
+            }
+            // convert user metadata to tags and notes because user metadata 
is otherwise ignored
+            Map<String, String> md = slT.getUserMetadata();
+            if (md!=null && !md.isEmpty()) {
+                Set<String> tags = MutableSet.copyOf(slT.getTags());
+                for (Map.Entry<String,String> entry: md.entrySet()) {
+                    
tags.add(AbstractCloudMachineNamer.sanitize(entry.getKey())+":"+AbstractCloudMachineNamer.sanitize(entry.getValue()));
+                }
+                slT.tags(tags);
+
+                if (!md.containsKey("notes")) {
+                    String notes = "User Metadata\n=============\n\n  * " + 
Joiner.on("\n  * ").withKeyValueSeparator(": ").join(md);
+                    if (notes.length() > NOTES_MAX_LENGTH) {
+                        String truncatedMsg = "...\n<truncated - notes total 
length is " + notes.length() + " characters>";
+                        notes = notes.substring(0, NOTES_MAX_LENGTH - 
truncatedMsg.length()) + truncatedMsg;
+                    }
+                    md.put("notes", notes);
+                }
+            }
+        }
+    }
+
+    /** returns the jclouds Template which describes the image to be built, 
for the given config and compute service */
+    public Template buildTemplate(ComputeService computeService, ConfigBag 
config) {
+        TemplateBuilder templateBuilder = (TemplateBuilder) 
config.get(TEMPLATE_BUILDER);
+        if (templateBuilder==null) {
+            templateBuilder = new 
PortableTemplateBuilder<PortableTemplateBuilder<?>>();
+        } else {
+            LOG.debug("jclouds using templateBuilder {} as custom base for 
provisioning in {} for {}", new Object[] {
+                    templateBuilder, this, config.getDescription()});
+        }
+        if (templateBuilder instanceof PortableTemplateBuilder<?>) {
+            if 
(((PortableTemplateBuilder<?>)templateBuilder).imageChooser()==null) {
+                Function<Iterable<? extends Image>, Image> chooser = 
config.get(JcloudsLocationConfig.IMAGE_CHOOSER);
+                chooser = BrooklynImageChooser.cloneFor(chooser, 
computeService);
+                templateBuilder.imageChooser(chooser);
+            } else {
+                // an image chooser is already set, so do nothing
+            }
+        } else {
+            // template builder supplied, and we cannot check image chooser 
status; warn, for now
+            LOG.warn("Cannot check imageChooser status for {} due to manually 
supplied black-box TemplateBuilder; "
+                + "it is recommended to use a PortableTemplateBuilder if you 
supply a TemplateBuilder", config.getDescription());
+        }
+
+        if (!Strings.isEmpty(config.get(CLOUD_REGION_ID))) {
+            templateBuilder.locationId(config.get(CLOUD_REGION_ID));
+        }
+
+        // Apply the template builder and options properties
+        for (Map.Entry<ConfigKey<?>, CustomizeTemplateBuilder> entry : 
SUPPORTED_TEMPLATE_BUILDER_PROPERTIES.entrySet()) {
+            ConfigKey<?> name = entry.getKey();
+            CustomizeTemplateBuilder code = entry.getValue();
+            if (config.containsKey(name))
+                code.apply(templateBuilder, config, config.get(name));
+        }
+
+        if (templateBuilder instanceof PortableTemplateBuilder) {
+            
((PortableTemplateBuilder<?>)templateBuilder).attachComputeService(computeService);
+            // do the default last, and only if nothing else specified 
(guaranteed to be a PTB if nothing else specified)
+            if (groovyTruth(config.get(DEFAULT_IMAGE_ID))) {
+                if (((PortableTemplateBuilder<?>)templateBuilder).isBlank()) {
+                    
templateBuilder.imageId(config.get(DEFAULT_IMAGE_ID).toString());
+                }
+            }
+        }
+
+        // Then apply any optional app-specific customization.
+        for (JcloudsLocationCustomizer customizer : getCustomizers(config)) {
+            customizer.customize(this, computeService, templateBuilder);
+        }
+
+        LOG.debug("jclouds using templateBuilder {} for provisioning in {} for 
{}", new Object[] {
+            templateBuilder, this, config.getDescription()});
+
+        // Finally try to build the template
+        Template template;
+        Image image;
+        try {
+            template = templateBuilder.build();
+            if (template==null) throw new NullPointerException("No template 
found (templateBuilder.build returned null)");
+            image = template.getImage();
+            LOG.debug("jclouds found template "+template+" (image "+image+") 
for provisioning in "+this+" for "+config.getDescription());
+            if (image==null) throw new NullPointerException("Template does not 
contain an image (templateBuilder.build returned invalid template)");
+        } catch (AuthorizationException e) {
+            LOG.warn("Error resolving template: not authorized (rethrowing: 
"+e+")");
+            throw new IllegalStateException("Not authorized to access cloud 
"+this+" to resolve "+templateBuilder, e);
+        } catch (Exception e) {
+            try {
+                IOException ioe = Exceptions.getFirstThrowableOfType(e, 
IOException.class);
+                if (ioe != null) {
+                    LOG.warn("IOException found...", ioe);
+                    throw ioe;
+                }
+                if 
(listedAvailableTemplatesOnNoSuchTemplate.compareAndSet(false, true)) {
+                    // delay subsequent log.warns (put in synch block) so the 
"Loading..." message is obvious
+                    LOG.warn("Unable to match required VM template constraints 
"+templateBuilder+" when trying to provision VM in "+this+" (rethrowing): "+e);
+                    logAvailableTemplates(config);
+                }
+            } catch (Exception e2) {
+                LOG.warn("Error loading available images to report (following 
original error matching template which will be rethrown): "+e2, e2);
+                throw new IllegalStateException("Unable to access cloud 
"+this+" to resolve "+templateBuilder+": "+e, e);
+            }
+            throw new IllegalStateException("Unable to match required VM 
template constraints "+templateBuilder+" when trying to provision VM in 
"+this+"; "
+                + "see list of images in log. Root cause: "+e, e);
+        }
+        TemplateOptions options = template.getOptions();
+
+        boolean windows = isWindows(template, config);
+        if (windows) {
+            if 
(!(config.containsKey(JcloudsLocationConfig.USER_METADATA_STRING) || 
config.containsKey(JcloudsLocationConfig.USER_METADATA_MAP))) {
+                config.put(JcloudsLocationConfig.USER_METADATA_STRING, 
WinRmMachineLocation.getDefaultUserMetadataString());
+            }
+        }
+               
+        for (Map.Entry<ConfigKey<?>, CustomizeTemplateOptions> entry : 
SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.entrySet()) {
+            ConfigKey<?> key = entry.getKey();
+            CustomizeTemplateOptions code = entry.getValue();
+            if (config.containsKey(key))
+                code.apply(options, config, config.get(key));
+        }
+
+        return template;
+    }
+
+    protected void logAvailableTemplates(ConfigBag config) {
+        LOG.info("Loading available images at "+this+" for reference...");
+        ConfigBag m1 = ConfigBag.newInstanceCopying(config);
+        if (m1.containsKey(IMAGE_ID)) {
+            // if caller specified an image ID, remove that, but don't apply 
default filters
+            m1.remove(IMAGE_ID);
+            // TODO use key
+            m1.putStringKey("anyOwner", true);
+        }
+        ComputeService computeServiceLessRestrictive = 
getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(m1, true);
+        Set<? extends Image> imgs = computeServiceLessRestrictive.listImages();
+        LOG.info(""+imgs.size()+" available images at "+this);
+        for (Image img: imgs) {
+            LOG.info(" Image: "+img);
+        }
+
+        Set<? extends Hardware> profiles = 
computeServiceLessRestrictive.listHardwareProfiles();
+        LOG.info(""+profiles.size()+" available profiles at "+this);
+        for (Hardware profile: profiles) {
+            LOG.info(" Profile: "+profile);
+        }
+
+        Set<? extends org.jclouds.domain.Location> assignableLocations = 
computeServiceLessRestrictive.listAssignableLocations();
+        LOG.info(""+assignableLocations.size()+" available locations at 
"+this);
+        for (org.jclouds.domain.Location assignableLocation: 
assignableLocations) {
+            LOG.info(" Location: "+assignableLocation);
+        }
+    }
+
+    protected SshMachineLocation createTemporarySshMachineLocation(HostAndPort 
hostAndPort, LoginCredentials creds, ConfigBag config) {
+        Optional<String> initialPassword = creds.getOptionalPassword();
+        Optional<String> initialPrivateKey = creds.getOptionalPrivateKey();
+        String initialUser = creds.getUser();
+
+        Map<String,Object> sshProps = 
Maps.newLinkedHashMap(config.getAllConfig());
+        sshProps.put("user", initialUser);
+        sshProps.put("address", hostAndPort.getHostText());
+        sshProps.put("port", hostAndPort.getPort());
+        sshProps.put(AbstractLocation.TEMPORARY_LOCATION.getName(), true);
+        if (initialPassword.isPresent()) sshProps.put("password", 
initialPassword.get());
+        if (initialPrivateKey.isPresent()) sshProps.put("privateKeyData", 
initialPrivateKey.get());
+        if (initialPrivateKey.isPresent()) sshProps.put("privateKeyData", 
initialPrivateKey.get());
+
+        if (isManaged()) {
+            return 
getManagementContext().getLocationManager().createLocation(sshProps, 
SshMachineLocation.class);
+        } else {
+            return new SshMachineLocation(sshProps);
+        }
+    }
+
+    /**
+     * Create the user immediately - executing ssh commands as required.
+     */
+    protected LoginCredentials createUser(ComputeService computeService, 
NodeMetadata node, Optional<HostAndPort> hostAndPortOverride, ConfigBag config) 
{
+        Image image = (node.getImageId() != null) ? 
computeService.getImage(node.getImageId()) : null;
+        UserCreation userCreation = createUserStatements(image, config);
+
+        if (!userCreation.statements.isEmpty()) {
+            // If unsure of OS family, default to unix for rendering 
statements.
+            org.jclouds.scriptbuilder.domain.OsFamily scriptOsFamily;
+            if (isWindows(node, config)) {
+                scriptOsFamily = 
org.jclouds.scriptbuilder.domain.OsFamily.WINDOWS;
+            } else {
+                scriptOsFamily = 
org.jclouds.scriptbuilder.domain.OsFamily.UNIX;
+            }
+
+            boolean windows = isWindows(node, config);
+
+            if (windows) {
+                LOG.warn("Unable to execute statements on WinRM in 
JcloudsLocation; skipping for "+node+": "+userCreation.statements);
+                
+            } else {
+                List<String> commands = Lists.newArrayList();
+                for (Statement statement : userCreation.statements) {
+                    InitAdminAccess initAdminAccess = new InitAdminAccess(new 
AdminAccessConfiguration.Default());
+                    initAdminAccess.visit(statement);
+                    commands.add(statement.render(scriptOsFamily));
+                }
+
+                LoginCredentials initialCredentials = node.getCredentials();
+                Optional<String> initialPassword = 
initialCredentials.getOptionalPassword();
+                Optional<String> initialPrivateKey = 
initialCredentials.getOptionalPrivateKey();
+           

<TRUNCATED>

Reply via email to