http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java new file mode 100644 index 0000000..9fce742 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; + +import org.apache.brooklyn.location.jclouds.networking.JcloudsPortForwarderExtension; +import org.jclouds.Constants; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.domain.LoginCredentials; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.event.basic.BasicConfigKey; +import org.apache.brooklyn.location.access.BrooklynAccessUtils; +import org.apache.brooklyn.location.access.PortForwardManager; +import org.apache.brooklyn.location.basic.LocationConfigKeys; +import org.apache.brooklyn.location.cloud.CloudLocationConfig; +import brooklyn.util.internal.ssh.SshTool; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.reflect.TypeToken; + +public interface JcloudsLocationConfig extends CloudLocationConfig { + + public static final ConfigKey<String> CLOUD_PROVIDER = LocationConfigKeys.CLOUD_PROVIDER; + + public static final ConfigKey<Boolean> RUN_AS_ROOT = ConfigKeys.newBooleanConfigKey("runAsRoot", + "Whether to run initial setup as root (default true)", null); + public static final ConfigKey<String> LOGIN_USER = ConfigKeys.newStringConfigKey("loginUser", + "Override the user who logs in initially to perform setup " + + "(otherwise it is detected from the cloud or known defaults in cloud or VM OS)", null); + public static final ConfigKey<String> LOGIN_USER_PASSWORD = ConfigKeys.newStringConfigKey("loginUser.password", + "Custom password for the user who logs in initially", null); + public static final ConfigKey<String> LOGIN_USER_PRIVATE_KEY_DATA = ConfigKeys.newStringConfigKey("loginUser.privateKeyData", + "Custom private key for the user who logs in initially", null); + public static final ConfigKey<String> KEY_PAIR = ConfigKeys.newStringConfigKey("keyPair", + "Custom keypair name to be re-used", null); + public static final ConfigKey<Boolean> AUTO_GENERATE_KEYPAIRS = ConfigKeys.newBooleanConfigKey("jclouds.openstack-nova.auto-generate-keypairs", + "Whether to generate keypairs for Nova"); + /** + * @deprecated since 0.7.0 Use {@link #AUTO_ASSIGN_FLOATING_IP} instead + */ + public static final ConfigKey<Boolean> AUTO_CREATE_FLOATING_IPS = ConfigKeys.newBooleanConfigKey("jclouds.openstack-nova.auto-create-floating-ips", + "Whether to generate floating ips for Nova"); + public static final ConfigKey<Boolean> AUTO_ASSIGN_FLOATING_IP = ConfigKeys.newBooleanConfigKey("autoAssignFloatingIp", + "Whether to generate floating ips (in Nova paralance), or elastic IPs (in CloudStack parlance)"); + // not supported in jclouds +// public static final ConfigKey<String> LOGIN_USER_PRIVATE_KEY_PASSPHRASE = ConfigKeys.newStringKey("loginUser.privateKeyPassphrase", +// "Passphrase for the custom private key for the user who logs in initially", null); + public static final ConfigKey<String> LOGIN_USER_PRIVATE_KEY_FILE = ConfigKeys.newStringConfigKey("loginUser.privateKeyFile", + "Custom private key for the user who logs in initially", null); + public static final ConfigKey<String> EXTRA_PUBLIC_KEY_DATA_TO_AUTH = ConfigKeys.newStringConfigKey("extraSshPublicKeyData", + "Additional public key data to add to authorized_keys", null); + @SuppressWarnings("serial") + public static final ConfigKey<List<String>> EXTRA_PUBLIC_KEY_URLS_TO_AUTH = ConfigKeys.newConfigKey(new TypeToken<List<String>>() {}, + "extraSshPublicKeyUrls", "Additional public keys (files or URLs, in SSH2/RFC4716/id_rsa.pub format) to add to authorized_keys", null); + + public static final ConfigKey<Boolean> DONT_CREATE_USER = ConfigKeys.newBooleanConfigKey("dontCreateUser", + "Whether to skip creation of 'user' when provisioning machines (default false)", false); + public static final ConfigKey<Boolean> GRANT_USER_SUDO = ConfigKeys.newBooleanConfigKey("grantUserSudo", + "Whether to grant the created user sudo privileges. Irrelevant if dontCreateUser is true. Default: true.", true); + public static final ConfigKey<Boolean> DISABLE_ROOT_AND_PASSWORD_SSH = ConfigKeys.newBooleanConfigKey("disableRootAndPasswordSsh", + "Whether to disable direct SSH access for root and disable password-based SSH, " + + "if creating a user with a key-based login; " + + "defaults to true (set false to leave root users alone)", true); + public static final ConfigKey<String> CUSTOM_TEMPLATE_OPTIONS_SCRIPT_CONTENTS = ConfigKeys.newStringConfigKey("customTemplateOptionsScriptContents", + "A custom script to pass to jclouds as part of template options, run after AdminAccess, " + + "for use primarily where a command which must run as root on first login before switching to the admin user, " + + "e.g. to customize sudoers; may start in an odd location (e.g. /tmp/bootstrap); " + + "NB: most commands should be run by entities, or if VM-specific but sudo is okay, then via setup.script, not via this"); + + public static final ConfigKey<LoginCredentials> CUSTOM_CREDENTIALS = new BasicConfigKey<LoginCredentials>(LoginCredentials.class, + "customCredentials", "Custom jclouds LoginCredentials object to be used to connect to the VM", null); + + public static final ConfigKey<String> GROUP_ID = ConfigKeys.newStringConfigKey("groupId", + "The Jclouds group provisioned machines should be members of. " + + "Users of this config key are also responsible for configuring security groups."); + + // jclouds compatibility + public static final ConfigKey<String> JCLOUDS_KEY_USERNAME = ConfigKeys.newStringConfigKey( + "userName", "Equivalent to 'user'; provided for jclouds compatibility", null); + public static final ConfigKey<String> JCLOUDS_KEY_ENDPOINT = ConfigKeys.newStringConfigKey( + Constants.PROPERTY_ENDPOINT, "Equivalent to 'endpoint'; provided for jclouds compatibility", null); + + // note causing problems on centos due to use of `sudo -n`; but required for default RHEL VM + /** + * @deprecated since 0.8.0; instead configure this on the entity. See SoftwareProcess.OPEN_IPTABLES. + */ + @Deprecated + public static final ConfigKey<Boolean> OPEN_IPTABLES = ConfigKeys.newBooleanConfigKey("openIptables", + "[DEPRECATED - use openIptables on SoftwareProcess entity] Whether to open the INBOUND_PORTS via iptables rules; " + + "if true then ssh in to run iptables commands, as part of machine provisioning", false); + + /** + * @deprecated since 0.8.0; instead configure this on the entity. See SoftwareProcess.STOP_IPTABLES. + */ + @Deprecated + public static final ConfigKey<Boolean> STOP_IPTABLES = ConfigKeys.newBooleanConfigKey("stopIptables", + "[DEPRECATED - use stopIptables on SoftwareProcess entity] Whether to stop iptables entirely; " + + "if true then ssh in to stop the iptables service, as part of machine provisioning", false); + + public static final ConfigKey<String> HARDWARE_ID = ConfigKeys.newStringConfigKey("hardwareId", + "A system-specific identifier for the hardware profile or machine type to be used when creating a VM", null); + + public static final ConfigKey<String> IMAGE_ID = ConfigKeys.newStringConfigKey("imageId", + "A system-specific identifier for the VM image to be used when creating a VM", null); + public static final ConfigKey<String> IMAGE_NAME_REGEX = ConfigKeys.newStringConfigKey("imageNameRegex", + "A regular expression to be compared against the 'name' when selecting the VM image to be used when creating a VM", null); + public static final ConfigKey<String> IMAGE_DESCRIPTION_REGEX = ConfigKeys.newStringConfigKey("imageDescriptionRegex", + "A regular expression to be compared against the 'description' when selecting the VM image to be used when creating a VM", null); + + public static final ConfigKey<String> TEMPLATE_SPEC = ConfigKeys.newStringConfigKey("templateSpec", + "A jclouds 'spec' string consisting of properties and values to be used when creating a VM " + + "(in most cases the properties can, and should, be specified individually using other Brooklyn location config keys)", null); + + public static final ConfigKey<String> DEFAULT_IMAGE_ID = ConfigKeys.newStringConfigKey("defaultImageId", + "A system-specific identifier for the VM image to be used by default when creating a VM " + + "(if no other VM image selection criteria are supplied)", null); + + public static final ConfigKey<TemplateBuilder> TEMPLATE_BUILDER = ConfigKeys.newConfigKey(TemplateBuilder.class, + "templateBuilder", "A TemplateBuilder instance provided programmatically, to be used when creating a VM"); + + public static final ConfigKey<Object> SECURITY_GROUPS = new BasicConfigKey<Object>(Object.class, "securityGroups", + "Security groups to be applied when creating a VM, on supported clouds " + + "(either a single group identifier as a String, or an Iterable<String> or String[])", null); + + public static final ConfigKey<String> USER_METADATA_STRING = ConfigKeys.newStringConfigKey("userMetadataString", + "Arbitrary user data, as a single string, on supported clouds (AWS)", null); + + @Deprecated /** @deprecated since 0.7.0 even AWS (the only one where this was supported) does not seem to want this uuencoded; + use #USER_METADATA_STRING */ + public static final ConfigKey<String> USER_DATA_UUENCODED = ConfigKeys.newStringConfigKey("userData", + "Arbitrary user data, as a single string in uuencoded format, on supported clouds (AWS)", null); + + public static final ConfigKey<Object> STRING_TAGS = new BasicConfigKey<Object>(Object.class, "tags", + "Tags to be applied when creating a VM, on supported clouds " + + "(either a single tag as a String, or an Iterable<String> or String[];" + + "note this is not key-value pairs (e.g. what AWS calls 'tags'), for that see userMetadata)", null); + + @Deprecated /** @deprecated since 0.7.0 use #STRING_TAGS */ + public static final ConfigKey<Object> TAGS = STRING_TAGS; + + public static final ConfigKey<Object> USER_METADATA_MAP = new BasicConfigKey<Object>(Object.class, "userMetadata", + "Arbitrary user metadata, as a map (or String of comma-separated key=value pairs), on supported clouds; " + + "note often values cannot be null", null); + @Deprecated /** @deprecated since 0.7.0 use #USER_METADATA_MAP */ + public static final ConfigKey<Object> USER_METADATA = USER_METADATA_MAP; + + public static final ConfigKey<Boolean> INCLUDE_BROOKLYN_USER_METADATA = ConfigKeys.newBooleanConfigKey("includeBrooklynUserMetadata", + "Whether to set metadata about the context of a machine, e.g. brooklyn-entity-id, brooklyn-app-name (default true)", true); + + public static final ConfigKey<Boolean> MAP_DEV_RANDOM_TO_DEV_URANDOM = ConfigKeys.newBooleanConfigKey( + "installDevUrandom", "Map /dev/random to /dev/urandom to prevent halting on insufficient entropy", true); + + /** @deprecated since 0.7.0; use {@link #JCLOUDS_LOCATION_CUSTOMIZERS} instead */ + @Deprecated + public static final ConfigKey<JcloudsLocationCustomizer> JCLOUDS_LOCATION_CUSTOMIZER = ConfigKeys.newConfigKey(JcloudsLocationCustomizer.class, + "customizer", "Optional location customizer"); + + @SuppressWarnings("serial") + public static final ConfigKey<Collection<JcloudsLocationCustomizer>> JCLOUDS_LOCATION_CUSTOMIZERS = ConfigKeys.newConfigKey( + new TypeToken<Collection<JcloudsLocationCustomizer>>() {}, + "customizers", "Optional location customizers"); + + /** @deprecated since 0.7.0; use {@link #JCLOUDS_LOCATION_CUSTOMIZERS} instead */ + @Deprecated + public static final ConfigKey<String> JCLOUDS_LOCATION_CUSTOMIZER_TYPE = ConfigKeys.newStringConfigKey( + "customizerType", "Optional location customizer type (to be class-loaded and constructed with no-arg constructor)"); + + /** @deprecated since 0.7.0; use {@link #JCLOUDS_LOCATION_CUSTOMIZERS} instead */ + @Deprecated + public static final ConfigKey<String> JCLOUDS_LOCATION_CUSTOMIZERS_SUPPLIER_TYPE = ConfigKeys.newStringConfigKey( + "customizersSupplierType", "Optional type of a Supplier<Collection<JcloudsLocationCustomizer>> " + + "(to be class-loaded and constructed with ConfigBag or no-arg constructor)"); + + public static final ConfigKey<String> LOCAL_TEMP_DIR = SshTool.PROP_LOCAL_TEMP_DIR; + + public static final ConfigKey<Integer> OVERRIDE_RAM = ConfigKeys.newIntegerConfigKey("overrideRam", "Custom ram value"); + + public static final ConfigKey<String> NETWORK_NAME = ConfigKeys.newStringConfigKey( + "networkName", "Network name or ID where the instance should be created (e.g. the subnet ID in AWS"); + + /** + * CUSTOM_MACHINE_SETUP_SCRIPT_URL accepts a URL location that points to a shell script. + * Please have a look at locations/jclouds/src/main/resources/sample/script/setup-server.sh as an example + */ + public static final ConfigKey<String> CUSTOM_MACHINE_SETUP_SCRIPT_URL = ConfigKeys.newStringConfigKey( + "setup.script", "Custom script to customize a node"); + + @SuppressWarnings("serial") + public static final ConfigKey<List<String>> CUSTOM_MACHINE_SETUP_SCRIPT_URL_LIST = ConfigKeys.newConfigKey(new TypeToken<List<String>>() {}, + "setup.scripts", "A list of scripts to customize a node"); + + public static final ConfigKey<String> CUSTOM_MACHINE_SETUP_SCRIPT_VARS = ConfigKeys.newStringConfigKey( + "setup.script.vars", "vars to customize a setup.script i.e.: key1:value1,key2:value2"); + + public static final ConfigKey<Boolean> GENERATE_HOSTNAME = ConfigKeys.newBooleanConfigKey( + "generate.hostname", "Use the nodename generated by jclouds", false); + + public static final ConfigKey<Boolean> USE_PORT_FORWARDING = ConfigKeys.newBooleanConfigKey( + "portforwarding.enabled", + "Whether to setup port-forwarding to subsequently access the VM (over the ssh port)", + false); + + @Beta + public static final ConfigKey<Boolean> USE_JCLOUDS_SSH_INIT = ConfigKeys.newBooleanConfigKey( + "useJcloudsSshInit", + "Whether to use jclouds for initial ssh-based setup (i.e. as part of the 'TemplateOptions'); " + + "if false will use core brooklyn ssh utilities. " + + "This config is beta; its default could be changed and/or the option removed in an upcoming release.", + true); + + public static final ConfigKey<JcloudsPortForwarderExtension> PORT_FORWARDER = ConfigKeys.newConfigKey( + JcloudsPortForwarderExtension.class, "portforwarding.forwarder", "The port-forwarder to use"); + + public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = BrooklynAccessUtils + .PORT_FORWARDING_MANAGER; + + public static final ConfigKey<Integer> MACHINE_CREATE_ATTEMPTS = ConfigKeys.newIntegerConfigKey( + "machineCreateAttempts", "Number of times to retry if jclouds fails to create a VM", 1); + + public static final ConfigKey<Integer> MAX_CONCURRENT_MACHINE_CREATIONS = ConfigKeys.newIntegerConfigKey( + "maxConcurrentMachineCreations", "Maximum number of concurrent machine-creations", Integer.MAX_VALUE); + + public static final ConfigKey<Semaphore> MACHINE_CREATION_SEMAPHORE = ConfigKeys.newConfigKey( + Semaphore.class, "machineCreationSemaphore", "Semaphore for controlling concurrent machine creation", null); + + @SuppressWarnings("serial") + public static final ConfigKey<Function<Iterable<? extends Image>,Image>> IMAGE_CHOOSER = ConfigKeys.newConfigKey( + new TypeToken<Function<Iterable<? extends Image>,Image>>() {}, + "imageChooser", "An image chooser function to control which images are preferred", + new BrooklynImageChooser().chooser()); + + public static final ConfigKey<OsFamily> OS_FAMILY = ConfigKeys.newConfigKey(OsFamily.class, "osFamily", + "OS family, e.g. CentOS, Debian, RHEL, Ubuntu"); + public static final ConfigKey<String> OS_VERSION_REGEX = ConfigKeys.newStringConfigKey("osVersionRegex", + "Regular expression for the OS version to load"); + + public static final ConfigKey<OsFamily> OS_FAMILY_OVERRIDE = ConfigKeys.newConfigKey(OsFamily.class, "osFamilyOverride", + "OS family of VMs (ignores VM metadata from jclouds, and assumes this value)"); + + public static final ConfigKey<ComputeServiceRegistry> COMPUTE_SERVICE_REGISTRY = ConfigKeys.newConfigKey( + ComputeServiceRegistry.class, + "jclouds.computeServiceRegistry", + "Registry/Factory for creating jclouds ComputeService; default is almost always fine, except where tests want to customize behaviour", + ComputeServiceRegistryImpl.INSTANCE); + + @SuppressWarnings("serial") + public static final ConfigKey<Map<String,Object>> TEMPLATE_OPTIONS = ConfigKeys.newConfigKey( + new TypeToken<Map<String, Object>>() {}, "templateOptions", "Additional jclouds template options"); + + // TODO + +// "noDefaultSshKeys" - hints that local ssh keys should not be read as defaults + // this would be useful when we need to indicate a password + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationCustomizer.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationCustomizer.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationCustomizer.java new file mode 100644 index 0000000..b6cb363 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationCustomizer.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.compute.options.TemplateOptions; + +import com.google.common.annotations.Beta; + +import brooklyn.util.config.ConfigBag; + +/** + * Customization hooks to allow apps to perform specific customisation at each stage of jclouds machine provisioning. + * For example, an app could attach an EBS volume to an EC2 node, or configure a desired availability zone. + * <p/> + * Instances will be invoked with the {@link ConfigBag} being used to obtain a machine by the + * {@link JcloudsLocation }if such a constructor exists. If not, the default no argument constructor + * will be invoked. + */ +@Beta +public interface JcloudsLocationCustomizer { + + /** + * Override to configure {@link org.jclouds.compute.domain.TemplateBuilder templateBuilder} + * before it is built and immutable. + */ + void customize(JcloudsLocation location, ComputeService computeService, TemplateBuilder templateBuilder); + + /** + * Override to configure a subclass of this with the built template, or to configure the built + * template's {@link org.jclouds.compute.options.TemplateOptions}. + * <p/> + * This method will be called before {@link #customize(JcloudsLocation, ComputeService, TemplateOptions)}. + */ + void customize(JcloudsLocation location, ComputeService computeService, Template template); + + /** + * Override to configure the {@link org.jclouds.compute.options.TemplateOptions} that will + * be used by {@link JcloudsLocation} to obtain machines. + */ + void customize(JcloudsLocation location, ComputeService computeService, TemplateOptions templateOptions); + + /** + * Override to configure the given machine once it has been created and started by Jclouds. + * <p/> + * If {@link JcloudsLocationConfig#WAIT_FOR_SSHABLE} is true the + * machine is guaranteed to be SSHable when this method is called. + * + * @since 0.7.0; use {@link #customize(JcloudsLocation, ComputeService, JcloudsMachineLocation)} + */ + @Deprecated + void customize(JcloudsLocation location, ComputeService computeService, JcloudsSshMachineLocation machine); + + /** + * Override to handle machine-related cleanup before Jclouds is called to release (destroy) the machine. + * + * @since 0.7.0; use {@link #preRelease(JcloudsMachineLocation)} + */ + @Deprecated + void preRelease(JcloudsSshMachineLocation machine); + + /** + * Override to handle machine-related cleanup after Jclouds is called to release (destroy) the machine. + * + * @since 0.7.0; use {@link #postRelesae(JcloudsMachineLocation)} + */ + @Deprecated + void postRelease(JcloudsSshMachineLocation machine); + + /** + * Override to configure the given machine once it has been created and started by Jclouds. + * <p/> + * If {@link JcloudsLocationConfig#WAIT_FOR_SSHABLE} is true the + * machine is guaranteed to be SSHable when this method is called. + */ + void customize(JcloudsLocation location, ComputeService computeService, JcloudsMachineLocation machine); + + /** + * Override to handle machine-related cleanup before Jclouds is called to release (destroy) the machine. + */ + void preRelease(JcloudsMachineLocation machine); + + /** + * Override to handle machine-related cleanup after Jclouds is called to release (destroy) the machine. + */ + void postRelease(JcloudsMachineLocation machine); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolver.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolver.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolver.java new file mode 100644 index 0000000..1ca33c2 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolver.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationResolver; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.basic.LocationConfigKeys; +import org.apache.brooklyn.location.basic.LocationConfigUtils; +import org.apache.brooklyn.location.basic.LocationInternal; +import org.jclouds.apis.ApiMetadata; +import org.jclouds.apis.Apis; +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.providers.Providers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.brooklyn.location.basic.BasicLocationRegistry; +import brooklyn.util.text.Strings; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +@SuppressWarnings("rawtypes") +public class JcloudsLocationResolver implements LocationResolver { + + public static final Logger log = LoggerFactory.getLogger(JcloudsLocationResolver.class); + + private static final String JCLOUDS = "jclouds"; + + public static final Map<String,ProviderMetadata> PROVIDERS = getProvidersMap(); + public static final Map<String,ApiMetadata> APIS = getApisMap(); + + private static Map<String,ProviderMetadata> getProvidersMap() { + Map<String,ProviderMetadata> result = Maps.newLinkedHashMap(); + for (ProviderMetadata p: Providers.all()) { + result.put(p.getId(), p); + } + return ImmutableMap.copyOf(result); + } + + private static Map<String,ApiMetadata> getApisMap() { + Map<String,ApiMetadata> result = Maps.newLinkedHashMap(); + for (ApiMetadata api: Apis.all()) { + result.put(api.getId(), api); + } + return ImmutableMap.copyOf(result); + } + + public static final Collection<String> AWS_REGIONS = Arrays.asList( + // from http://docs.amazonwebservices.com/general/latest/gr/rande.html as of Apr 2012. + // it is suggested not to maintain this list here, instead to require aws-ec2 explicitly named. + "eu-west-1","us-east-1","us-west-1","us-west-2","ap-southeast-1","ap-northeast-1","sa-east-1"); + + private ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + protected class JcloudsSpecParser { + String providerOrApi; + String parameter; + + public JcloudsSpecParser parse(String spec, boolean dryrun) { + JcloudsSpecParser result = new JcloudsSpecParser(); + int split = spec.indexOf(':'); + if (split<0) { + if (spec.equalsIgnoreCase(getPrefix())) { + if (dryrun) return null; + throw new IllegalArgumentException("Cannot use '"+spec+"' as a location ID; it is insufficient. "+ + "Try jclouds:aws-ec2 (for example)."); + } + result.providerOrApi = spec; + result.parameter = null; + } else { + result.providerOrApi = spec.substring(0, split); + result.parameter = spec.substring(split+1); + int numJcloudsPrefixes = 0; + while (result.providerOrApi.equalsIgnoreCase(getPrefix())) { + //strip any number of jclouds: prefixes, for use by static "resolve" method + numJcloudsPrefixes++; + result.providerOrApi = result.parameter; + result.parameter = null; + split = result.providerOrApi.indexOf(':'); + if (split>=0) { + result.parameter = result.providerOrApi.substring(split+1); + result.providerOrApi = result.providerOrApi.substring(0, split); + } + } + if (!dryrun && numJcloudsPrefixes > 1) { + log.warn("Use of deprecated location spec '"+spec+"'; in future use a single \"jclouds\" prefix"); + } + } + + if (result.parameter==null && AWS_REGIONS.contains(result.providerOrApi)) { + // treat amazon as a default + result.parameter = result.providerOrApi; + result.providerOrApi = "aws-ec2"; + if (!dryrun) + log.warn("Use of deprecated location '"+result.parameter+"'; in future refer to with explicit " + + "provider '"+result.providerOrApi+":"+result.parameter+"'"); + } + + return result; + } + + public boolean isProvider() { + return PROVIDERS.containsKey(providerOrApi); + } + + public boolean isApi() { + return APIS.containsKey(providerOrApi); + } + + public String getProviderOrApi() { + return providerOrApi; + } + + public String getParameter() { + return parameter; + } + } + + @Override + @SuppressWarnings("unchecked") + public JcloudsLocation newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + Map globalProperties = registry.getProperties(); + + JcloudsSpecParser details = new JcloudsSpecParser().parse(spec, false); + String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName()); + + boolean isProvider = details.isProvider(); + String providerOrApi = details.providerOrApi; + // gce claims to be an api ... perhaps just a bug? email sent to jclouds dev list, 28 mar 2014 + isProvider = isProvider || "google-compute-engine".equals(providerOrApi); + + if (Strings.isEmpty(providerOrApi)) { + throw new IllegalArgumentException("Cloud provider/API type not specified in spec \""+spec+"\""); + } + if (!isProvider && !details.isApi()) { + throw new NoSuchElementException("Cloud provider/API type "+providerOrApi+" is not supported by jclouds"); + } + + // For everything in brooklyn.properties, only use things with correct prefix (and remove that prefix). + // But for everything passed in via locationFlags, pass those as-is. + // TODO Should revisit the locationFlags: where are these actually used? Reason accepting properties without + // full prefix is that the map's context is explicitly this location, rather than being generic properties. + Map allProperties = getAllProperties(registry, globalProperties); + String regionOrEndpoint = details.parameter; + if (regionOrEndpoint==null && isProvider) regionOrEndpoint = (String)locationFlags.get(LocationConfigKeys.CLOUD_REGION_ID.getName()); + Map jcloudsProperties = new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi, regionOrEndpoint, namedLocation, allProperties); + jcloudsProperties.putAll(locationFlags); + + if (regionOrEndpoint!=null) { + // apply the regionOrEndpoint (e.g. from the parameter) as appropriate -- but only if it has not been overridden + if (isProvider) { + // providers from ServiceLoader take a location (endpoint already configured), and optionally a region name + // NB blank might be supplied if spec string is "mycloud:" -- that should be respected, + // whereas no parameter/regionName ie null value -- "mycloud" -- means don't set + if (Strings.isBlank(Strings.toString(jcloudsProperties.get(JcloudsLocationConfig.CLOUD_REGION_ID.getName())))) + jcloudsProperties.put(JcloudsLocationConfig.CLOUD_REGION_ID.getName(), regionOrEndpoint); + } else { + // other "providers" are APIs so take an _endpoint_ (but not a location); + // see note above re null here + if (Strings.isBlank(Strings.toString(jcloudsProperties.get(JcloudsLocationConfig.CLOUD_ENDPOINT.getName())))) + jcloudsProperties.put(JcloudsLocationConfig.CLOUD_ENDPOINT.getName(), regionOrEndpoint); + } + } + + return managementContext.getLocationManager().createLocation(LocationSpec.create(getLocationClass()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, jcloudsProperties, globalProperties, namedLocation)) + .configure(jcloudsProperties) ); + } + + @SuppressWarnings("unchecked") + private Map getAllProperties(LocationRegistry registry, Map<?,?> properties) { + Map<Object,Object> allProperties = Maps.newHashMap(); + if (registry!=null) allProperties.putAll(registry.getProperties()); + allProperties.putAll(properties); + return allProperties; + } + + @Override + public String getPrefix() { + return JCLOUDS; + } + + protected Class<? extends JcloudsLocation> getLocationClass() { + return JcloudsLocation.class; + } + + @Override + public boolean accepts(String spec, LocationRegistry registry) { + if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true)) return true; + JcloudsSpecParser details = new JcloudsSpecParser().parse(spec, true); + if (details==null) return false; + if (details.isProvider() || details.isApi()) return true; + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java new file mode 100644 index 0000000..2619206 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import org.apache.brooklyn.location.basic.HasSubnetHostname; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; + +import org.apache.brooklyn.location.MachineLocation; + +public interface JcloudsMachineLocation extends MachineLocation, HasSubnetHostname { + + @Override + public JcloudsLocation getParent(); + + public NodeMetadata getNode(); + + public Template getTemplate(); + + public String getJcloudsId(); + + /** In most clouds, the public hostname is the only way to ensure VMs in different zones can access each other. */ + @Override + public String getSubnetHostname(); + + String getUser(); + + int getPort(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamer.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamer.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamer.java new file mode 100644 index 0000000..4f4587e --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineNamer.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import org.apache.brooklyn.location.cloud.names.BasicCloudMachineNamer; +import brooklyn.util.config.ConfigBag; + +public class JcloudsMachineNamer extends BasicCloudMachineNamer { + + @Override + /** returns the max length of a VM name for the cloud specified in setup; + * this value is typically decremented by 9 to make room for jclouds labels */ + public Integer getCustomMaxNameLength(ConfigBag setup) { + // otherwise, for some known clouds which only allow a short name, use that length + if ("vcloud".equals( setup.peek(JcloudsLocationConfig.CLOUD_PROVIDER) )) + return 24; + if ("abiquo".equals( setup.peek(JcloudsLocationConfig.CLOUD_PROVIDER) )) + return 39; + if ("google-compute-engine".equals( setup.peek(JcloudsLocationConfig.CLOUD_PROVIDER) )) + return 39; + if ("softlayer".equals( setup.peek(JcloudsLocationConfig.CLOUD_PROVIDER) )) + return 55; + // TODO other cloud max length rules + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPredicates.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPredicates.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPredicates.java new file mode 100644 index 0000000..565425d --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPredicates.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import org.jclouds.compute.domain.ComputeMetadata; +import org.jclouds.domain.Location; + +import com.google.common.base.Predicate; + +public class JcloudsPredicates { + + public static class NodeInLocation implements Predicate<ComputeMetadata> { + private String regionId; + private boolean matchNullLocations; + public NodeInLocation(String regionId, boolean matchNullLocations) { + this.regionId = regionId; + this.matchNullLocations = matchNullLocations; + } + @Override + public boolean apply(ComputeMetadata input) { + boolean exclude; + Location nodeLocation = input.getLocation(); + if (nodeLocation==null) return matchNullLocations; + + exclude = true; + while (nodeLocation!=null && exclude) { + if (nodeLocation.getId().equals(regionId)) { + // matching location info found + exclude = false; + } + nodeLocation = nodeLocation.getParent(); + } + return !exclude; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynProperties.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynProperties.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynProperties.java new file mode 100644 index 0000000..f307041 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsPropertiesFromBrooklynProperties.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import java.util.Map; + +import org.apache.brooklyn.location.basic.DeprecatedKeysMappingBuilder; +import org.apache.brooklyn.location.basic.LocationPropertiesFromBrooklynProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigUtils; +import org.apache.brooklyn.location.basic.LocationConfigKeys; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.javalang.JavaClassNames; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + +/** + * <p> + * The properties to use for a jclouds location, loaded from brooklyn.properties file + * </p> + * + * Preferred format is: + * + * <ul> + * <li> + * brooklyn.location.named.NAME.key + * </li> + * <li> + * brooklyn.location.jclouds.PROVIDER.key + * </li> + * </ul> + * + * <p> + * A number of properties are also supported, listed in the {@code JcloudsLocationConfig} + * </p> + * + * @author andrea + **/ +public class JcloudsPropertiesFromBrooklynProperties extends LocationPropertiesFromBrooklynProperties { + + private static final Logger LOG = LoggerFactory.getLogger(JcloudsPropertiesFromBrooklynProperties.class); + + @SuppressWarnings("deprecation") + private static final Map<String, String> DEPRECATED_JCLOUDS_KEYS_MAPPING = new DeprecatedKeysMappingBuilder(LOG) + .putAll(LocationPropertiesFromBrooklynProperties.DEPRECATED_KEYS_MAPPING) + .camelToHyphen(JcloudsLocation.IMAGE_ID) + .camelToHyphen(JcloudsLocation.IMAGE_NAME_REGEX) + .camelToHyphen(JcloudsLocation.IMAGE_DESCRIPTION_REGEX) + .camelToHyphen(JcloudsLocation.HARDWARE_ID) + .build(); + + @Override + public Map<String, Object> getLocationProperties(String provider, String namedLocation, Map<String, ?> properties) { + throw new UnsupportedOperationException("Instead use getJcloudsProperties(String,String,String,Map)"); + } + + /** + * @see LocationPropertiesFromBrooklynProperties#getLocationProperties(String, String, Map) + */ + public Map<String, Object> getJcloudsProperties(String providerOrApi, String regionOrEndpoint, String namedLocation, Map<String, ?> properties) { + if(Strings.isNullOrEmpty(namedLocation) && Strings.isNullOrEmpty(providerOrApi)) { + throw new IllegalArgumentException("Neither cloud provider/API nor location name have been specified correctly"); + } + + ConfigBag jcloudsProperties = ConfigBag.newInstance(); + String provider = getProviderName(providerOrApi, namedLocation, properties); + + // named properties are preferred over providerOrApi properties + jcloudsProperties.put(LocationConfigKeys.CLOUD_PROVIDER, provider); + jcloudsProperties.putAll(transformDeprecated(getGenericLocationSingleWordProperties(properties))); + jcloudsProperties.putAll(transformDeprecated(getGenericJcloudsSingleWordProperties(providerOrApi, properties))); + jcloudsProperties.putAll(transformDeprecated(getProviderOrApiJcloudsProperties(providerOrApi, properties))); + jcloudsProperties.putAll(transformDeprecated(getRegionJcloudsProperties(providerOrApi, regionOrEndpoint, properties))); + if (!Strings.isNullOrEmpty(namedLocation)) jcloudsProperties.putAll(transformDeprecated(getNamedJcloudsProperties(namedLocation, properties))); + setLocalTempDir(properties, jcloudsProperties); + + return jcloudsProperties.getAllConfigRaw(); + } + + protected String getProviderName(String providerOrApi, String namedLocationName, Map<String, ?> properties) { + String provider = providerOrApi; + if (!Strings.isNullOrEmpty(namedLocationName)) { + String providerDefinition = (String) properties.get(String.format("brooklyn.location.named.%s", namedLocationName)); + if (providerDefinition!=null) { + String provider2 = getProviderFromDefinition(providerDefinition); + if (provider==null) { + // 0.7.0 25 Feb -- is this even needed? + LOG.warn(JavaClassNames.niceClassAndMethod()+" NOT set with provider, inferring from locationName "+namedLocationName+" as "+provider2); + provider = provider2; + } else if (!provider.equals(provider2)) { + // 0.7.0 25 Feb -- previously we switched to provider2 in this case, but that was wrong when + // working with chains of names; not sure why this case would ever occur (apart from tests which have been changed) + // 28 Mar seen this warning many times but only in cases when NOT changing is the right behaviour + LOG.debug(JavaClassNames.niceClassAndMethod()+" NOT changing provider from "+provider+" to candidate "+provider2); + } + } + } + return provider; + } + + protected String getProviderFromDefinition(String definition) { + return Iterables.get(Splitter.on(":").split(definition), 1); + } + + protected Map<String, Object> getGenericJcloudsSingleWordProperties(String providerOrApi, Map<String, ?> properties) { + if (Strings.isNullOrEmpty(providerOrApi)) return Maps.newHashMap(); + String deprecatedPrefix = "brooklyn.jclouds."; + String preferredPrefix = "brooklyn.location.jclouds."; + return getMatchingSingleWordProperties(preferredPrefix, deprecatedPrefix, properties); + } + + protected Map<String, Object> getProviderOrApiJcloudsProperties(String providerOrApi, Map<String, ?> properties) { + if (Strings.isNullOrEmpty(providerOrApi)) return Maps.newHashMap(); + String preferredPrefix = String.format("brooklyn.location.jclouds.%s.", providerOrApi); + String deprecatedPrefix = String.format("brooklyn.jclouds.%s.", providerOrApi); + + return getMatchingProperties(preferredPrefix, deprecatedPrefix, properties); + } + + protected Map<String, Object> getRegionJcloudsProperties(String providerOrApi, String regionName, Map<String, ?> properties) { + if (Strings.isNullOrEmpty(providerOrApi) || Strings.isNullOrEmpty(regionName)) return Maps.newHashMap(); + String preferredPrefix = String.format("brooklyn.location.jclouds.%s@%s.", providerOrApi, regionName); + String deprecatedPrefix = String.format("brooklyn.jclouds.%s@%s.", providerOrApi, regionName); + + return getMatchingProperties(preferredPrefix, deprecatedPrefix, properties); + } + + protected Map<String, Object> getNamedJcloudsProperties(String locationName, Map<String, ?> properties) { + if(locationName == null) return Maps.newHashMap(); + String prefix = String.format("brooklyn.location.named.%s.", locationName); + return ConfigUtils.filterForPrefixAndStrip(properties, prefix).asMapWithStringKeys(); + } + + @Override + protected Map<String, String> getDeprecatedKeysMapping() { + return DEPRECATED_JCLOUDS_KEYS_MAPPING; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java new file mode 100644 index 0000000..08fd3b0 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java @@ -0,0 +1,338 @@ +/* + * 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.groovyTruth; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.location.HardwareDetails; +import org.apache.brooklyn.location.MachineDetails; +import org.apache.brooklyn.location.OsDetails; +import org.apache.brooklyn.location.basic.BasicHardwareDetails; +import org.apache.brooklyn.location.basic.BasicMachineDetails; +import org.apache.brooklyn.location.basic.BasicOsDetails; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.callables.RunScriptOnNode; +import org.jclouds.compute.domain.ExecResponse; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.domain.Processor; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.options.RunScriptOptions; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.scriptbuilder.domain.InterpretableStatement; +import org.jclouds.scriptbuilder.domain.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.net.Networking; +import brooklyn.util.text.Strings; + +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HostAndPort; +import com.google.common.util.concurrent.ListenableFuture; + +public class JcloudsSshMachineLocation extends SshMachineLocation implements JcloudsMachineLocation { + + private static final Logger LOG = LoggerFactory.getLogger(JcloudsSshMachineLocation.class); + private static final long serialVersionUID = -443866395634771659L; + + @SetFromFlag + JcloudsLocation jcloudsParent; + + @SetFromFlag + NodeMetadata node; + + @SetFromFlag + Template template; + + private RunScriptOnNode.Factory runScriptFactory; + + public JcloudsSshMachineLocation() { + } + + /** + * @deprecated since 0.6; use LocationSpec (which calls no-arg constructor) + */ + @Deprecated + public JcloudsSshMachineLocation(Map<?,?> flags, JcloudsLocation jcloudsParent, NodeMetadata node) { + super(flags); + this.jcloudsParent = jcloudsParent; + this.node = node; + + init(); + } + + @Override + public void init() { + if (jcloudsParent != null) { + super.init(); + ComputeServiceContext context = jcloudsParent.getComputeService().getContext(); + runScriptFactory = context.utils().injector().getInstance(RunScriptOnNode.Factory.class); + } else { + // TODO Need to fix the rebind-detection, and not call init() on rebind. + // This will all change when locations become entities. + if (LOG.isDebugEnabled()) LOG.debug("Not doing init() of {} because parent not set; presuming rebinding", this); + } + } + + @Override + public void rebind() { + super.rebind(); + ComputeServiceContext context = jcloudsParent.getComputeService().getContext(); + runScriptFactory = context.utils().injector().getInstance(RunScriptOnNode.Factory.class); + } + + @Override + public String toVerboseString() { + return Objects.toStringHelper(this).omitNullValues() + .add("id", getId()).add("name", getDisplayName()) + .add("user", getUser()).add("address", getAddress()).add("port", getConfig(SSH_PORT)) + .add("node", getNode()) + .add("jcloudsId", getJcloudsId()) + .add("privateAddresses", node.getPrivateAddresses()) + .add("publicAddresses", node.getPublicAddresses()) + .add("parentLocation", getParent()) + .add("osDetails", getOsDetails()) + .toString(); + } + + @Override + public NodeMetadata getNode() { + return node; + } + + @Override + public Template getTemplate() { + return template; + } + + @Override + public JcloudsLocation getParent() { + return jcloudsParent; + } + + @Override + public String getHostname() { + return node.getHostname(); + } + + /** In most clouds, the public hostname is the only way to ensure VMs in different zones can access each other. */ + @Override + public Set<String> getPublicAddresses() { + return node.getPublicAddresses(); + } + + @Override + public Set<String> getPrivateAddresses() { + return node.getPrivateAddresses(); + } + + @Override + public String getSubnetHostname() { + String privateHostname = jcloudsParent.getPrivateHostname(node, Optional.<HostAndPort>absent(), config().getBag()); + return privateHostname; + } + + @Override + public String getSubnetIp() { + Optional<String> privateAddress = getPrivateAddress(); + if (privateAddress.isPresent()) { + return privateAddress.get(); + } + + String hostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag()); + if (hostname != null && !Networking.isValidIp4(hostname)) { + try { + return InetAddress.getByName(hostname).getHostAddress(); + } catch (UnknownHostException e) { + LOG.debug("Cannot resolve IP for hostname {} of machine {} (so returning hostname): {}", new Object[] {hostname, this, e}); + } + } + return hostname; + } + + protected Optional<String> getPrivateAddress() { + if (groovyTruth(node.getPrivateAddresses())) { + for (String p : node.getPrivateAddresses()) { + // disallow local only addresses + if (Networking.isLocalOnly(p)) continue; + // other things may be public or private, but either way, return it + return Optional.of(p); + } + } + return Optional.absent(); + } + + @Override + public String getJcloudsId() { + return node.getId(); + } + + /** executes the given statements on the server using jclouds ScriptBuilder, + * wrapping in a script which is polled periodically. + * the output is returned once the script completes (disadvantage compared to other methods) + * but the process is nohupped and the SSH session is not kept, + * so very useful for long-running processes + */ + public ListenableFuture<ExecResponse> submitRunScript(String ...statements) { + return submitRunScript(new InterpretableStatement(statements)); + } + public ListenableFuture<ExecResponse> submitRunScript(Statement script) { + return submitRunScript(script, new RunScriptOptions()); + } + public ListenableFuture<ExecResponse> submitRunScript(Statement script, RunScriptOptions options) { + return runScriptFactory.submit(node, script, options); + } + /** uses submitRunScript to execute the commands, and throws error if it fails or returns non-zero */ + public void execRemoteScript(String ...commands) { + try { + ExecResponse result = submitRunScript(commands).get(); + if (result.getExitStatus()!=0) + throw new IllegalStateException("Error running remote commands (code "+result.getExitStatus()+"): "+commands); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw Throwables.propagate(e); + } catch (ExecutionException e) { + throw Throwables.propagate(e); + } + } + + /** + * Retrieves the password for this VM, if one exists. The behaviour/implementation is different for different clouds. + * e.g. on Rackspace, the password for a windows VM is available immediately; on AWS-EC2, for a Windows VM you need + * to poll repeatedly until the password is available which can take up to 15 minutes. + */ + public String waitForPassword() { + // TODO Hacky; don't want aws specific stuff here but what to do?! + if (jcloudsParent.getProvider().equals("aws-ec2")) { + try { + return JcloudsUtil.waitForPasswordOnAws(jcloudsParent.getComputeService(), node, 15, TimeUnit.MINUTES); + } catch (TimeoutException e) { + throw Throwables.propagate(e); + } + } else { + LoginCredentials credentials = node.getCredentials(); + return (credentials != null) ? credentials.getPassword() : null; + } + } + + @Override + protected MachineDetails inferMachineDetails() { + Optional<String> name = Optional.absent(); + Optional<String> version = Optional.absent(); + Optional<String> architecture = Optional.absent(); + + OperatingSystem os = node.getOperatingSystem(); + if (os == null && getTemplate() != null && getTemplate().getImage() != null) + // some nodes (eg cloudstack, gce) might not get OS available on the node, + // so also try taking it from the template if available + os = getTemplate().getImage().getOperatingSystem(); + + if (os != null) { + // Note using family rather than name. Name is often unset. + name = Optional.fromNullable(os.getFamily() != null && !OsFamily.UNRECOGNIZED.equals(os.getFamily()) ? os.getFamily().toString() : null); + version = Optional.fromNullable(!Strings.isBlank(os.getVersion()) ? os.getVersion() : null); + // Using is64Bit rather then getArch because getArch often returns "paravirtual" + architecture = Optional.fromNullable(os.is64Bit() ? BasicOsDetails.OsArchs.X_86_64 : BasicOsDetails.OsArchs.I386); + } + + Hardware hardware = node.getHardware(); + Optional<Integer> ram = hardware==null ? Optional.<Integer>absent() : Optional.fromNullable(hardware.getRam()); + Optional<Integer> cpus = hardware==null ? Optional.<Integer>absent() : Optional.fromNullable(hardware.getProcessors() != null ? hardware.getProcessors().size() : null); + + // Skip superclass' SSH to machine if all data is present, otherwise defer to super + if (name.isPresent() && version.isPresent() && architecture.isPresent() && ram.isPresent() && cpus.isPresent()) { + if (LOG.isTraceEnabled()) { + LOG.trace("Gathered machine details from Jclouds, skipping SSH test on {}", this); + } + OsDetails osD = new BasicOsDetails(name.get(), architecture.get(), version.get()); + HardwareDetails hwD = new BasicHardwareDetails(cpus.get(), ram.get()); + return new BasicMachineDetails(hwD, osD); + } else if ("false".equalsIgnoreCase(getConfig(JcloudsLocation.WAIT_FOR_SSHABLE))) { + if (LOG.isTraceEnabled()) { + LOG.trace("Machine details for {} missing from Jclouds, but skipping SSH test because waitForSshable=false. name={}, version={}, " + + "arch={}, ram={}, #cpus={}", + new Object[]{this, name, version, architecture, ram, cpus}); + } + OsDetails osD = new BasicOsDetails(name.orNull(), architecture.orNull(), version.orNull()); + HardwareDetails hwD = new BasicHardwareDetails(cpus.orNull(), ram.orNull()); + return new BasicMachineDetails(hwD, osD); + } else { + if (LOG.isTraceEnabled()) { + LOG.trace("Machine details for {} missing from Jclouds, using SSH test instead. name={}, version={}, " + + "arch={}, ram={}, #cpus={}", + new Object[]{this, name, version, architecture, ram, cpus}); + } + return super.inferMachineDetails(); + } + } + + @Override + public Map<String, String> toMetadataRecord() { + Hardware hardware = node.getHardware(); + List<? extends Processor> processors = (hardware != null) ? hardware.getProcessors() : null; + + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + builder.putAll(super.toMetadataRecord()); + putIfNotNull(builder, "provider", getParent().getProvider()); + putIfNotNull(builder, "account", getParent().getIdentity()); + putIfNotNull(builder, "serverId", node.getProviderId()); + putIfNotNull(builder, "imageId", node.getImageId()); + putIfNotNull(builder, "instanceTypeName", (hardware != null ? hardware.getName() : null)); + putIfNotNull(builder, "instanceTypeId", (hardware != null ? hardware.getProviderId() : null)); + putIfNotNull(builder, "ram", "" + (hardware != null ? hardware.getRam() : null)); + putIfNotNull(builder, "cpus", "" + (processors != null ? processors.size() : null)); + + try { + OsDetails osDetails = getOsDetails(); + putIfNotNull(builder, "osName", osDetails.getName()); + putIfNotNull(builder, "osArch", osDetails.getArch()); + putIfNotNull(builder, "is64bit", osDetails.is64bit() ? "true" : "false"); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + LOG.warn("Unable to get OS Details for "+node+"; continuing", e); + } + + return builder.build(); + } + + private void putIfNotNull(ImmutableMap.Builder<String, String> builder, String key, @Nullable String value) { + if (value != null) builder.put(key, value); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java new file mode 100644 index 0000000..e3026df --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import static org.jclouds.compute.options.RunScriptOptions.Builder.overrideLoginCredentials; +import static org.jclouds.compute.util.ComputeServiceUtils.execHttpResponse; +import static org.jclouds.scriptbuilder.domain.Statements.appendFile; +import static org.jclouds.scriptbuilder.domain.Statements.exec; +import static org.jclouds.scriptbuilder.domain.Statements.interpret; +import static org.jclouds.scriptbuilder.domain.Statements.newStatementList; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nullable; + +import org.jclouds.Constants; +import org.jclouds.ContextBuilder; +import org.jclouds.aws.ec2.AWSEC2Api; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.RunScriptOnNodesException; +import org.jclouds.compute.domain.ExecResponse; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.options.RunScriptOptions; +import org.jclouds.compute.predicates.OperatingSystemPredicates; +import org.jclouds.docker.DockerApi; +import org.jclouds.docker.domain.Container; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.ec2.compute.domain.PasswordDataAndPrivateKey; +import org.jclouds.ec2.compute.functions.WindowsLoginCredentialsFromEncryptedData; +import org.jclouds.ec2.domain.PasswordData; +import org.jclouds.ec2.features.WindowsApi; +import org.jclouds.encryption.bouncycastle.config.BouncyCastleCryptoModule; +import org.jclouds.logging.slf4j.config.SLF4JLoggingModule; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.Statements; +import org.jclouds.ssh.SshClient; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.jclouds.util.Predicates2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.io.Files; +import com.google.inject.Module; + +import brooklyn.entity.basic.Sanitizer; +import brooklyn.util.collections.MutableList; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.net.Protocol; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.ssh.IptablesCommands; +import brooklyn.util.ssh.IptablesCommands.Chain; +import brooklyn.util.ssh.IptablesCommands.Policy; + +public class JcloudsUtil implements JcloudsLocationConfig { + + // TODO Review what utility methods are needed, and what is now supported in jclouds 1.1 + + private static final Logger LOG = LoggerFactory.getLogger(JcloudsUtil.class); + + /** + * @deprecated since 0.7; see {@link BashCommands} + */ + @Deprecated + public static String APT_INSTALL = "apt-get install -f -y -qq --force-yes"; + + /** + * @deprecated since 0.7; see {@link BashCommands} + */ + @Deprecated + public static String installAfterUpdatingIfNotPresent(String cmd) { + String aptInstallCmd = APT_INSTALL + " " + cmd; + return String.format("which %s || (%s || (apt-get update && %s))", cmd, aptInstallCmd, aptInstallCmd); + } + + /** + * @deprecated since 0.7 + */ + @Deprecated + public static Predicate<NodeMetadata> predicateMatchingById(final NodeMetadata node) { + return predicateMatchingById(node.getId()); + } + + /** + * @deprecated since 0.7 + */ + @Deprecated + public static Predicate<NodeMetadata> predicateMatchingById(final String id) { + Predicate<NodeMetadata> nodePredicate = new Predicate<NodeMetadata>() { + @Override public boolean apply(NodeMetadata arg0) { + return id.equals(arg0.getId()); + } + @Override public String toString() { + return "node.id=="+id; + } + }; + return nodePredicate; + } + + /** + * @deprecated since 0.7; see {@link IptablesCommands} + */ + @Deprecated + public static Statement authorizePortInIpTables(int port) { + // TODO gogrid rules only allow ports 22, 3389, 80 and 443. + // the first rule will be ignored, so we have to apply this + // directly + return Statements.newStatementList(// just in case iptables are being used, try to open 8080 + exec("iptables -I INPUT 1 -p tcp --dport " + port + " -j ACCEPT"),// + exec("iptables -I RH-Firewall-1-INPUT 1 -p tcp --dport " + port + " -j ACCEPT"),// + exec("iptables-save")); + } + + /** + * @throws RunScriptOnNodesException + * @throws IllegalStateException If do not find exactly one matching node + * + * @deprecated since 0.7 + */ + @Deprecated + public static ExecResponse runScriptOnNode(ComputeService computeService, NodeMetadata node, Statement statement, String scriptName) throws RunScriptOnNodesException { + // TODO Includes workaround for NodeMetadata's equals/hashcode method being wrong. + + Map<? extends NodeMetadata, ExecResponse> scriptResults = computeService.runScriptOnNodesMatching( + JcloudsUtil.predicateMatchingById(node), + statement, + new RunScriptOptions().nameTask(scriptName)); + if (scriptResults.isEmpty()) { + throw new IllegalStateException("No matching node found when executing script "+scriptName+": expected="+node); + } else if (scriptResults.size() > 1) { + throw new IllegalStateException("Multiple nodes matched predicate: id="+node.getId()+"; expected="+node+"; actual="+scriptResults.keySet()); + } else { + return Iterables.getOnlyElement(scriptResults.values()); + } + } + + /** + * @deprecated since 0.7; {@see #installJavaAndCurl(OperatingSystem)} + */ + @Deprecated + public static final Statement APT_RUN_SCRIPT = newStatementList(// + exec(installAfterUpdatingIfNotPresent("curl")),// + exec("(which java && java -fullversion 2>&1|egrep -q 1.6 ) ||"),// + execHttpResponse(URI.create("http://whirr.s3.amazonaws.com/0.2.0-incubating-SNAPSHOT/sun/java/install")),// + exec(new StringBuilder()// + .append("echo nameserver 208.67.222.222 >> /etc/resolv.conf\n")// + // jeos hasn't enough room! + .append("rm -rf /var/cache/apt /usr/lib/vmware-tools\n")// + .append("echo \"export PATH=\\\"$JAVA_HOME/bin/:$PATH\\\"\" >> /root/.bashrc")// + .toString())); + + /** + * @deprecated since 0.7; {@see #installJavaAndCurl(OperatingSystem)} + */ + @Deprecated + public static final Statement YUM_RUN_SCRIPT = newStatementList( + exec("which curl ||yum --nogpgcheck -y install curl"),// + exec("(which java && java -fullversion 2>&1|egrep -q 1.6 ) ||"),// + execHttpResponse(URI.create("http://whirr.s3.amazonaws.com/0.2.0-incubating-SNAPSHOT/sun/java/install")),// + exec(new StringBuilder()// + .append("echo nameserver 208.67.222.222 >> /etc/resolv.conf\n") // + .append("echo \"export PATH=\\\"$JAVA_HOME/bin/:$PATH\\\"\" >> /root/.bashrc")// + .toString())); + + /** + * @deprecated since 0.7; {@see #installJavaAndCurl(OperatingSystem)} + */ + @Deprecated + public static final Statement ZYPPER_RUN_SCRIPT = exec(new StringBuilder()// + .append("echo nameserver 208.67.222.222 >> /etc/resolv.conf\n")// + .append("which curl || zypper install curl\n")// + .append("(which java && java -fullversion 2>&1|egrep -q 1.6 ) || zypper install java-1.6.0-openjdk\n")// + .toString()); + + // Code taken from RunScriptData + /** + * @deprecated since 0.7; see {@link BashCommands#installJava7()} and {@link BashCommands#INSTALL_CURL} + */ + @Deprecated + public static Statement installJavaAndCurl(OperatingSystem os) { + if (os == null || OperatingSystemPredicates.supportsApt().apply(os)) + return APT_RUN_SCRIPT; + else if (OperatingSystemPredicates.supportsYum().apply(os)) + return YUM_RUN_SCRIPT; + else if (OperatingSystemPredicates.supportsZypper().apply(os)) + return ZYPPER_RUN_SCRIPT; + else + throw new IllegalArgumentException("don't know how to handle" + os.toString()); + } + + /** + * @deprecated since 0.7; see {@link ComputeServiceRegistry#findComputeService(ConfigBag, boolean)} + */ + @Deprecated + public static ComputeService findComputeService(ConfigBag conf) { + return ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, true); + } + + /** + * @deprecated since 0.7; see {@link ComputeServiceRegistry#findComputeService(ConfigBag, boolean)} + */ + @Deprecated + public static ComputeService findComputeService(ConfigBag conf, boolean allowReuse) { + return ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, allowReuse); + } + + /** + * Returns the jclouds modules we typically install + * + * @deprecated since 0.7; see {@link ComputeServiceRegistry} + */ + @Deprecated + public static ImmutableSet<Module> getCommonModules() { + return ImmutableSet.<Module> of( + new SshjSshClientModule(), + new SLF4JLoggingModule(), + new BouncyCastleCryptoModule()); + } + + /** + * Temporary constructor to address https://issues.apache.org/jira/browse/JCLOUDS-615. + * <p> + * See https://issues.apache.org/jira/browse/BROOKLYN-6 . + * When https://issues.apache.org/jira/browse/JCLOUDS-615 is fixed in the jclouds we use, + * we can remove the useSoftlayerFix argument. + * <p> + * (Marked Beta as that argument will likely be removed.) + * + * @since 0.7.0 */ + @Beta + public static BlobStoreContext newBlobstoreContext(String provider, @Nullable String endpoint, String identity, String credential) { + Properties overrides = new Properties(); + // * Java 7,8 bug workaround - sockets closed by GC break the internal bookkeeping + // of HttpUrlConnection, leading to invalid handling of the "HTTP/1.1 100 Continue" + // response. Coupled with a bug when using SSL sockets reads will block + // indefinitely even though a read timeout is explicitly set. + // * Java 6 ignores the header anyways as it is included in its restricted headers black list. + // * Also there's a bug in SL object store which still expects Content-Length bytes + // even when it responds with a 408 timeout response, leading to incorrectly + // interpreting the next request (triggered by above problem). + overrides.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true"); + + ContextBuilder contextBuilder = ContextBuilder.newBuilder(provider).credentials(identity, credential); + contextBuilder.modules(MutableList.copyOf(JcloudsUtil.getCommonModules())); + if (!brooklyn.util.text.Strings.isBlank(endpoint)) { + contextBuilder.endpoint(endpoint); + } + contextBuilder.overrides(overrides); + BlobStoreContext context = contextBuilder.buildView(BlobStoreContext.class); + return context; + } + + /** + * @deprecated since 0.7 + */ + @Deprecated + protected static String getDeprecatedProperty(ConfigBag conf, String key) { + if (conf.containsKey(key)) { + LOG.warn("Jclouds using deprecated brooklyn-jclouds property "+key+": "+Sanitizer.sanitize(conf.getAllConfig())); + return (String) conf.getStringKey(key); + } else { + return null; + } + } + + /** + * @deprecated since 0.7 + */ + @Deprecated + // Do this so that if there's a problem with our USERNAME's ssh key, we can still get in to check + // TODO Once we're really confident there are not going to be regular problems, then delete this + public static Statement addAuthorizedKeysToRoot(File publicKeyFile) throws IOException { + String publicKey = Files.toString(publicKeyFile, Charsets.UTF_8); + return addAuthorizedKeysToRoot(publicKey); + } + + /** + * @deprecated since 0.7 + */ + @Deprecated + public static Statement addAuthorizedKeysToRoot(String publicKey) { + return newStatementList( + appendFile("/root/.ssh/authorized_keys", Splitter.on('\n').split(publicKey)), + interpret("chmod 600 /root/.ssh/authorized_keys")); + } + + public static String getFirstReachableAddress(ComputeServiceContext context, NodeMetadata node) { + // To pick the address, it relies on jclouds `sshForNode().apply(Node)` to check all IPs of node (private+public), + // to find one that is reachable. It does `openSocketFinder.findOpenSocketOnNode(node, node.getLoginPort(), ...)`. + // This keeps trying for time org.jclouds.compute.reference.ComputeServiceConstants.Timeouts.portOpen. + // TODO Want to configure this timeout here. + // + // TODO We could perhaps instead just set `templateOptions.blockOnPort(loginPort, 120)`, but need + // to be careful to only set that if config WAIT_FOR_SSHABLE is true. For some advanced networking examples + // (e.g. using DNAT on CloudStack), the brooklyn machine won't be able to reach the VM until some additional + // setup steps have been done. See links from Andrea: + // https://github.com/jclouds/jclouds/pull/895 + // https://issues.apache.org/jira/browse/WHIRR-420 + // jclouds.ssh.max-retries + // jclouds.ssh.retry-auth + + SshClient client; + try { + client = context.utils().sshForNode().apply(node); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + /* i've seen: java.lang.IllegalStateException: Optional.get() cannot be called on an absent value + * from org.jclouds.crypto.ASN1Codec.createASN1Sequence(ASN1Codec.java:86), if the ssh key has a passphrase, against AWS. + * + * others have reported: java.lang.IllegalArgumentException: DER length more than 4 bytes + * when using a key with a passphrase (perhaps from other clouds?); not sure if that's this callpath or a different one. + */ + throw new IllegalStateException("Unable to connect SshClient to "+node+"; check that the node is accessible and that the SSH key exists and is correctly configured, including any passphrase defined", e); + } + return client.getHostAddress(); + } + + // Suggest at least 15 minutes for timeout + public static String waitForPasswordOnAws(ComputeService computeService, final NodeMetadata node, long timeout, TimeUnit timeUnit) throws TimeoutException { + ComputeServiceContext computeServiceContext = computeService.getContext(); + AWSEC2Api ec2Client = computeServiceContext.unwrapApi(AWSEC2Api.class); + final WindowsApi client = ec2Client.getWindowsApi().get(); + final String region = node.getLocation().getParent().getId(); + + // The Administrator password will take some time before it is ready - Amazon says sometimes 15 minutes. + // So we create a predicate that tests if the password is ready, and wrap it in a retryable predicate. + Predicate<String> passwordReady = new Predicate<String>() { + @Override public boolean apply(String s) { + if (Strings.isNullOrEmpty(s)) return false; + PasswordData data = client.getPasswordDataInRegion(region, s); + if (data == null) return false; + return !Strings.isNullOrEmpty(data.getPasswordData()); + } + }; + + LOG.info("Waiting for password, for "+node.getProviderId()+":"+node.getId()); + Predicate<String> passwordReadyRetryable = Predicates2.retry(passwordReady, timeUnit.toMillis(timeout), 10*1000, TimeUnit.MILLISECONDS); + boolean ready = passwordReadyRetryable.apply(node.getProviderId()); + if (!ready) throw new TimeoutException("Password not available for "+node+" in region "+region+" after "+timeout+" "+timeUnit.name()); + + // Now pull together Amazon's encrypted password blob, and the private key that jclouds generated + PasswordDataAndPrivateKey dataAndKey = new PasswordDataAndPrivateKey( + client.getPasswordDataInRegion(region, node.getProviderId()), + node.getCredentials().getPrivateKey()); + + // And apply it to the decryption function + WindowsLoginCredentialsFromEncryptedData f = computeServiceContext.utils().injector().getInstance(WindowsLoginCredentialsFromEncryptedData.class); + LoginCredentials credentials = f.apply(dataAndKey); + + return credentials.getPassword(); + } + + public static Map<Integer, Integer> dockerPortMappingsFor(JcloudsLocation docker, String containerId) { + ComputeServiceContext context = null; + try { + Properties properties = new Properties(); + properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, Boolean.toString(true)); + properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, Boolean.toString(true)); + context = ContextBuilder.newBuilder("docker") + .endpoint(docker.getEndpoint()) + .credentials(docker.getIdentity(), docker.getCredential()) + .overrides(properties) + .modules(ImmutableSet.<Module>of(new SLF4JLoggingModule(), new SshjSshClientModule())) + .build(ComputeServiceContext.class); + DockerApi api = context.unwrapApi(DockerApi.class); + Container container = api.getContainerApi().inspectContainer(containerId); + Map<Integer, Integer> portMappings = Maps.newLinkedHashMap(); + Map<String, List<Map<String, String>>> ports = container.networkSettings().ports(); + if (ports == null) ports = ImmutableMap.<String, List<Map<String,String>>>of(); + + LOG.debug("Docker will forward these ports {}", ports); + for (Map.Entry<String, List<Map<String, String>>> entrySet : ports.entrySet()) { + String containerPort = Iterables.get(Splitter.on("/").split(entrySet.getKey()), 0); + String hostPort = Iterables.getOnlyElement(Iterables.transform(entrySet.getValue(), + new Function<Map<String, String>, String>() { + @Override + public String apply(Map<String, String> hostIpAndPort) { + return hostIpAndPort.get("HostPort"); + } + })); + portMappings.put(Integer.parseInt(containerPort), Integer.parseInt(hostPort)); + } + return portMappings; + } finally { + if (context != null) { + context.close(); + } + } + } + + /** + * @deprecated since 0.7 + */ + @Deprecated + public static void mapSecurityGroupRuleToIpTables(ComputeService computeService, NodeMetadata node, + LoginCredentials credentials, String networkInterface, Iterable<Integer> ports) { + for (Integer port : ports) { + String insertIptableRule = IptablesCommands.insertIptablesRule(Chain.INPUT, networkInterface, + Protocol.TCP, port, Policy.ACCEPT); + Statement statement = Statements.newStatementList(exec(insertIptableRule)); + ExecResponse response = computeService.runScriptOnNode(node.getId(), statement, + overrideLoginCredentials(credentials).runAsRoot(false)); + if (response.getExitStatus() != 0) { + String msg = String.format("Cannot insert the iptables rule for port %d. Error: %s", port, + response.getError()); + LOG.error(msg); + throw new RuntimeException(msg); + } + } + } + +}
