blob: 80920cd79cd5b903b0b87c7591b2efcb85d7d901 [file] [log] [blame]
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.jclouds.ec2.compute.strategy;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.compute.config.ComputeServiceProperties.RESOURCENAME_DELIMITER;
import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey;
import static org.jclouds.crypto.SshKeys.sha1PrivateKey;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.ec2.compute.domain.RegionAndName;
import org.jclouds.ec2.compute.domain.RegionNameAndIngressRules;
import org.jclouds.ec2.compute.options.EC2TemplateOptions;
import org.jclouds.ec2.domain.BlockDeviceMapping;
import org.jclouds.ec2.domain.KeyPair;
import org.jclouds.ec2.options.RunInstancesOptions;
import org.jclouds.javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.inject.Inject;
/**
*
* @author Adrian Cole
*/
@Singleton
public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions {
@VisibleForTesting
public Function<RegionAndName, KeyPair> makeKeyPair;
@VisibleForTesting
public final ConcurrentMap<RegionAndName, KeyPair> credentialsMap;
@VisibleForTesting
public final LoadingCache<RegionAndName, String> securityGroupMap;
@VisibleForTesting
public final Provider<RunInstancesOptions> optionsProvider;
@Inject
public CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions(Function<RegionAndName, KeyPair> makeKeyPair,
ConcurrentMap<RegionAndName, KeyPair> credentialsMap,
@Named("SECURITY") LoadingCache<RegionAndName, String> securityGroupMap,
Provider<RunInstancesOptions> optionsProvider) {
this.makeKeyPair = checkNotNull(makeKeyPair, "makeKeyPair");
this.credentialsMap = checkNotNull(credentialsMap, "credentialsMap");
this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
this.optionsProvider = checkNotNull(optionsProvider, "optionsProvider");
}
public RunInstancesOptions execute(String region, String group, Template template) {
RunInstancesOptions instanceOptions = getOptionsProvider().get().asType(template.getHardware().getId());
String keyPairName = createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, template.getOptions());
addSecurityGroups(region, group, template, instanceOptions);
if (template.getOptions() instanceof EC2TemplateOptions) {
if (keyPairName != null)
instanceOptions.withKeyName(keyPairName);
byte[] userData = EC2TemplateOptions.class.cast(template.getOptions()).getUserData();
if (userData != null)
instanceOptions.withUserData(userData);
Set<BlockDeviceMapping> blockDeviceMappings = EC2TemplateOptions.class.cast(template.getOptions())
.getBlockDeviceMappings();
if (blockDeviceMappings.size() > 0) {
checkState("ebs".equals(template.getImage().getUserMetadata().get("rootDeviceType")),
"BlockDeviceMapping only available on ebs boot");
instanceOptions.withBlockDeviceMappings(blockDeviceMappings);
}
}
return instanceOptions;
}
protected void addSecurityGroups(String region, String group, Template template, RunInstancesOptions instanceOptions) {
Set<String> groups = getSecurityGroupsForTagAndOptions(region, group, template.getOptions());
instanceOptions.withSecurityGroups(groups);
}
@VisibleForTesting
public String createNewKeyPairUnlessUserSpecifiedOtherwise(String region, String group, TemplateOptions options) {
String keyPairName = null;
boolean shouldAutomaticallyCreateKeyPair = true;
if (options instanceof EC2TemplateOptions) {
keyPairName = EC2TemplateOptions.class.cast(options).getKeyPair();
if (keyPairName == null)
shouldAutomaticallyCreateKeyPair = EC2TemplateOptions.class.cast(options)
.shouldAutomaticallyCreateKeyPair();
}
if (keyPairName == null && shouldAutomaticallyCreateKeyPair) {
keyPairName = createOrImportKeyPair(region, group, options);
} else if (keyPairName != null) {
if (options.getLoginPrivateKey() != null) {
String pem = options.getLoginPrivateKey();
KeyPair keyPair = KeyPair.builder().region(region).keyName(keyPairName).fingerprint(
fingerprintPrivateKey(pem)).sha1OfPrivateKey(sha1PrivateKey(pem)).keyMaterial(pem).build();
RegionAndName key = new RegionAndName(region, keyPairName);
credentialsMap.put(key, keyPair);
}
}
if (options.getRunScript() != null) {
RegionAndName regionAndName = new RegionAndName(region, keyPairName);
checkArgument(
credentialsMap.containsKey(regionAndName),
"no private key configured for: %s; please use options.overrideLoginCredentialWith(rsa_private_text)",
regionAndName);
}
return keyPairName;
}
// base EC2 driver currently does not support key import
protected String createOrImportKeyPair(String region, String group, TemplateOptions options) {
RegionAndName regionAndGroup = new RegionAndName(region, group);
KeyPair keyPair;
// make sure that we don't request multiple keys simultaneously
synchronized (credentialsMap) {
// if there is already a keypair for the group specified, use it
if (credentialsMap.containsKey(regionAndGroup))
return credentialsMap.get(regionAndGroup).getKeyName();
// otherwise create a new keypair and key it under the group and also the regular keyname
keyPair = makeKeyPair.apply(new RegionAndName(region, group));
credentialsMap.put(regionAndGroup, keyPair);
}
credentialsMap.put(new RegionAndName(region, keyPair.getKeyName()), keyPair);
return keyPair.getKeyName();
}
@Inject(optional = true)
@Named(RESOURCENAME_DELIMITER)
char delimiter = '#';
@VisibleForTesting
public Set<String> getSecurityGroupsForTagAndOptions(String region, @Nullable String group, TemplateOptions options) {
Builder<String> groups = ImmutableSet.<String> builder();
if (group != null) {
String markerGroup = String.format("jclouds#%s#%s", group, region).replace('#', delimiter);
groups.add(markerGroup);
RegionNameAndIngressRules regionNameAndIngessRulesForMarkerGroup;
if (userSpecifiedTheirOwnGroups(options)) {
regionNameAndIngessRulesForMarkerGroup = new RegionNameAndIngressRules(region, markerGroup, new int[] {},
false);
groups.addAll(EC2TemplateOptions.class.cast(options).getGroups());
} else {
regionNameAndIngessRulesForMarkerGroup = new RegionNameAndIngressRules(region, markerGroup, options
.getInboundPorts(), true);
}
// this will create if not yet exists.
securityGroupMap.getUnchecked(regionNameAndIngessRulesForMarkerGroup);
}
return groups.build();
}
protected boolean userSpecifiedTheirOwnGroups(TemplateOptions options) {
return options instanceof EC2TemplateOptions && EC2TemplateOptions.class.cast(options).getGroups().size() > 0;
}
// allows us to mock this method
@VisibleForTesting
public javax.inject.Provider<RunInstancesOptions> getOptionsProvider() {
return optionsProvider;
}
}