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:31 UTC
[08/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/JcloudsLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 0000000,7578b8c..20897d9
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@@ -1,0 -1,3122 +1,3147 @@@
+ /*
+ * 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.checkArgument;
+ import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.TimeUnit.SECONDS;
+ import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis;
+ import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+ import static org.apache.brooklyn.util.ssh.BashCommands.sbinPath;
+
+ 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.concurrent.atomic.AtomicReference;
+ import java.util.regex.Pattern;
+
+ import javax.annotation.Nullable;
+
+ import org.apache.brooklyn.api.entity.Entity;
+ 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.MachineManagementMixins;
+ import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+ import org.apache.brooklyn.api.location.PortRange;
+ import org.apache.brooklyn.api.mgmt.AccessController;
+ import org.apache.brooklyn.api.mgmt.Task;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+ import org.apache.brooklyn.core.config.ConfigUtils;
+ import org.apache.brooklyn.core.config.Sanitizer;
+ import org.apache.brooklyn.core.location.AbstractLocation;
+ import org.apache.brooklyn.core.location.BasicMachineMetadata;
+ import org.apache.brooklyn.core.location.LocationConfigKeys;
+ import org.apache.brooklyn.core.location.LocationConfigUtils;
+ import org.apache.brooklyn.core.location.LocationConfigUtils.OsCredential;
+ import org.apache.brooklyn.core.location.PortRanges;
+ import org.apache.brooklyn.core.location.access.PortForwardManager;
+ import org.apache.brooklyn.core.location.access.PortMapping;
+ import org.apache.brooklyn.core.location.cloud.AbstractCloudMachineProvisioningLocation;
+ import org.apache.brooklyn.core.location.cloud.AvailabilityZoneExtension;
+ import org.apache.brooklyn.core.location.cloud.names.AbstractCloudMachineNamer;
+ import org.apache.brooklyn.core.location.cloud.names.CloudMachineNamer;
+ import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager;
+ import org.apache.brooklyn.core.mgmt.persist.LocationWithObjectStore;
+ import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
+ import org.apache.brooklyn.core.mgmt.persist.jclouds.JcloudsBlobStoreBasedObjectStore;
+ import org.apache.brooklyn.location.jclouds.JcloudsPredicates.NodeInLocation;
+ import org.apache.brooklyn.location.jclouds.networking.JcloudsPortForwarderExtension;
+ import org.apache.brooklyn.location.jclouds.templates.PortableTemplateBuilder;
+ import org.apache.brooklyn.location.jclouds.zone.AwsAvailabilityZoneExtension;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+ import org.apache.brooklyn.util.collections.MutableList;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.core.ResourceUtils;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ import org.apache.brooklyn.util.core.crypto.SecureKeys;
+ import org.apache.brooklyn.util.core.flags.MethodCoercions;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.core.flags.TypeCoercions;
+ import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
+ import org.apache.brooklyn.util.core.internal.ssh.SshTool;
+ import org.apache.brooklyn.util.core.internal.winrm.WinRmTool;
+ import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse;
+ import org.apache.brooklyn.util.core.task.DynamicTasks;
+ import org.apache.brooklyn.util.core.task.TaskBuilder;
+ import org.apache.brooklyn.util.core.task.Tasks;
+ import org.apache.brooklyn.util.core.text.TemplateProcessor;
+ import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+ import org.apache.brooklyn.util.guava.Maybe;
+ import org.apache.brooklyn.util.javalang.Enums;
+ import org.apache.brooklyn.util.javalang.Reflections;
+ import org.apache.brooklyn.util.net.Cidr;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.net.Protocol;
+ import org.apache.brooklyn.util.os.Os;
+ import org.apache.brooklyn.util.repeat.Repeater;
+ import org.apache.brooklyn.util.ssh.BashCommands;
+ import org.apache.brooklyn.util.ssh.IptablesCommands;
+ import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
+ import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+ import org.apache.brooklyn.util.stream.Streams;
+ import org.apache.brooklyn.util.text.ByteSizeStrings;
+ import org.apache.brooklyn.util.text.Identifiers;
+ import org.apache.brooklyn.util.text.KeyValueParser;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.apache.brooklyn.util.time.Time;
+ import org.apache.commons.lang3.ArrayUtils;
+ 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.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.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 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.SuspendResumeLocation {
+
+ // 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 getComputeService(conf);
+ }
+
+ public ComputeService getComputeService(ConfigBag config) {
+ return getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(config, 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;
+ Duration semaphoreTimestamp = null;
+ Duration templateTimestamp = null;
+ Duration provisionTimestamp = null;
+ Duration usableTimestamp = null;
+ Duration customizedTimestamp = null;
+ Stopwatch provisioningStopwatch = Stopwatch.createStarted();
+
+ 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);
+ }
+ semaphoreTimestamp = Duration.of(provisioningStopwatch);
+
+ 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())
+ if (LOG.isDebugEnabled())
+ LOG.debug("NOTE: unused flags passed to obtain VM in "+setup.getDescription()+": "
+ + Sanitizer.sanitize(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();
+ }
+
+ LoginCredentials initialCredentials = node.getCredentials();
+ if (skipJcloudsSshing) {
+ boolean waitForConnectable = (windows) ? waitForWinRmable : waitForSshable;
+ if (waitForConnectable) {
+ if (windows) {
+ // TODO Does jclouds support any windows user setup?
+ initialCredentials = waitForWinRmAvailable(computeService, node, sshHostAndPortOverride, setup);
+ } else {
+ initialCredentials = waitForSshable(computeService, node, sshHostAndPortOverride, setup);
+ }
+ userCredentials = createUser(computeService, node, sshHostAndPortOverride, initialCredentials, 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.getOptionalPassword().isPresent() && !userCredentials.getOptionalPrivateKey().isPresent())) {
+ // We either don't have any userCredentials, or it is missing both a password/key.
+ // TODO See waitForSshable, which now handles if the node.getLoginCredentials has both a password+key
+ userCredentials = extractVmCredentials(setup, node, initialCredentials);
+ }
+ if (userCredentials == null) {
+ // TODO See waitForSshable, which now handles if the node.getLoginCredentials has both a password+key
+ userCredentials = extractVmCredentials(setup, node, initialCredentials);
+ }
+ 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, ImmutableList.of(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;
- }
++ machineLocation = registerJcloudsSshMachineLocation(computeService, node, Optional.fromNullable(template), userCredentials, sshHostAndPortOverride, setup);
+ }
+
+ 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)) {
+ LOG.debug("Customizing machine {}, using customizer {}", machineLocation, customizer);
+ customizer.customize(this, computeService, machineLocation);
+ }
+ for (MachineLocationCustomizer customizer : getMachineCustomizers(setup)) {
+ LOG.debug("Customizing machine {}, using customizer {}", machineLocation, customizer);
+ 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()
+ + " ("
+ + "semaphore obtained in "+Duration.of(semaphoreTimestamp).toStringRounded()+";"
+ + template+" template built in "+Duration.of(templateTimestamp).subtract(semaphoreTimestamp).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 "+setup.getDescription() + (destroyNode ? " (destroying)" : "")
+ + (node != null ? "; node "+node : "")
+ + " after "+Duration.of(provisioningStopwatch).toStringRounded()
+ + (semaphoreTimestamp != null ? " ("
+ + "semaphore obtained in "+Duration.of(semaphoreTimestamp).toStringRounded()+";"
+ + (templateTimestamp != null && semaphoreTimestamp != null ? " template built in "+Duration.of(templateTimestamp).subtract(semaphoreTimestamp).toStringRounded()+";" : "")
+ + (provisionTimestamp != null && templateTimestamp != null ? " node provisioned in "+Duration.of(provisionTimestamp).subtract(templateTimestamp).toStringRounded()+";" : "")
+ + (usableTimestamp != null && provisioningStopwatch != null ? " connection usable in "+Duration.of(usableTimestamp).subtract(provisionTimestamp).toStringRounded()+";" : "")
+ + (customizedTimestamp != null && usableTimestamp != null ? " and OS customized in "+Duration.of(customizedTimestamp).subtract(usableTimestamp).toStringRounded() : "")
+ + ")"
+ : "")
+ + ": "+e.getMessage());
+ LOG.debug(Throwables.getStackTraceAsString(e));
+
+ if (destroyNode) {
+ Stopwatch destroyingStopwatch = Stopwatch.createStarted();
+ if (machineLocation != null) {
+ releaseSafely(machineLocation);
+ } else {
+ releaseNodeSafely(node);
+ }
+ LOG.info("Destroyed " + (machineLocation != null ? "machine " + machineLocation : "node " + node)
+ + " in " + Duration.of(destroyingStopwatch).toStringRounded());
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Brings an existing machine with the given details under management.
+ * <p/>
+ * Note that this method does <b>not</b> call the lifecycle methods of any
+ * {@link #getCustomizers(ConfigBag) customizers} attached to this location.
+ *
+ * @param flags See {@link #registerMachine(ConfigBag)} for a description of required fields.
+ * @see #registerMachine(ConfigBag)
+ */
+ @Override
+ public MachineLocation resumeMachine(Map<?, ?> flags) {
+ ConfigBag setup = ConfigBag.newInstanceExtending(config().getBag(), flags);
+ LOG.info("{} using resuming node matching properties: {}", this, Sanitizer.sanitize(setup));
+ ComputeService computeService = getComputeService(setup);
+ NodeMetadata node = findNodeOrThrow(setup);
+ LOG.debug("{} resuming {}", this, node);
+ computeService.resumeNode(node.getId());
+ // Load the node a second time once it is resumed to get an object with
+ // hostname and addresses populated.
+ node = findNodeOrThrow(setup);
+ LOG.debug("{} resumed {}", this, node);
+ MachineLocation registered = registerMachineLocation(setup, node);
+ LOG.info("{} resumed and registered {}", this, registered);
+ return registered;
+ }
+
+ // ------------- 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 = toIntPortArray(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) {
+ if (t instanceof GoogleComputeEngineTemplateOptions) {
+ // see email to jclouds list, 29 Aug 2015;
+ // GCE takes this to be the only login public key,
+ // and setting this only works if you also overrideLoginPrivateKey
+ LOG.warn("Ignoring "+EXTRA_PUBLIC_KEY_DATA_TO_AUTH+"; not supported in jclouds-gce implementation.");
+ }
+ 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 {
+
<TRUNCATED>