Issue 717: corrected keyPair state logic which was redundantly creating keys

This commit is contained in:
Adrian Cole 2011-10-14 01:49:18 -07:00
parent 7e5a6e68cf
commit 9104eca282
14 changed files with 161 additions and 138 deletions

View File

@ -24,8 +24,9 @@ import static com.google.common.collect.Iterables.transform;
import static org.jclouds.util.Preconditions2.checkNotEmpty; import static org.jclouds.util.Preconditions2.checkNotEmpty;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import javax.inject.Inject; import javax.inject.Inject;
@ -70,8 +71,8 @@ import com.google.common.base.Predicate;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultimap.Builder;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableMultimap.Builder;
/** /**
* @author Adrian Cole * @author Adrian Cole
@ -79,7 +80,7 @@ import com.google.common.collect.ImmutableSet;
@Singleton @Singleton
public class EC2ComputeService extends BaseComputeService { public class EC2ComputeService extends BaseComputeService {
private final EC2Client ec2Client; private final EC2Client ec2Client;
private final Cache<RegionAndName, KeyPair> credentialsMap; private final ConcurrentMap<RegionAndName, KeyPair> credentialsMap;
private final Cache<RegionAndName, String> securityGroupMap; private final Cache<RegionAndName, String> securityGroupMap;
@Inject @Inject
@ -97,7 +98,7 @@ public class EC2ComputeService extends BaseComputeService {
RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, EC2Client ec2Client, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, EC2Client ec2Client,
Cache<RegionAndName, KeyPair> credentialsMap, @Named("SECURITY") Cache<RegionAndName, String> securityGroupMap) { ConcurrentMap<RegionAndName, KeyPair> credentialsMap, @Named("SECURITY") Cache<RegionAndName, String> securityGroupMap) {
super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy, super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy,
runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, stopNodeStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, stopNodeStrategy,
templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended,
@ -143,7 +144,8 @@ public class EC2ComputeService extends BaseComputeService {
logger.debug(">> deleting keyPair(%s)", keyPair.getKeyName()); logger.debug(">> deleting keyPair(%s)", keyPair.getKeyName());
ec2Client.getKeyPairServices().deleteKeyPairInRegion(region, keyPair.getKeyName()); ec2Client.getKeyPairServices().deleteKeyPairInRegion(region, keyPair.getKeyName());
// TODO: test this clear happens // TODO: test this clear happens
credentialsMap.invalidate(new RegionAndName(region, keyPair.getKeyName())); credentialsMap.remove(new RegionAndName(region, keyPair.getKeyName()));
credentialsMap.remove(new RegionAndName(region, group));
logger.debug("<< deleted keyPair(%s)", keyPair.getKeyName()); logger.debug("<< deleted keyPair(%s)", keyPair.getKeyName());
} }
} }

View File

@ -22,6 +22,7 @@ import static org.jclouds.ec2.reference.EC2Constants.PROPERTY_EC2_TIMEOUT_SECURI
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Named; import javax.inject.Named;
@ -62,7 +63,9 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.Scopes; import com.google.inject.Scopes;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
@ -97,7 +100,7 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule {
}).to(CredentialsForInstance.class); }).to(CredentialsForInstance.class);
bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() { bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() {
}).to(CreateSecurityGroupIfNeeded.class); }).to(CreateSecurityGroupIfNeeded.class);
bind(new TypeLiteral<CacheLoader<RegionAndName, KeyPair>>() { bind(new TypeLiteral<Function<RegionAndName, KeyPair>>() {
}).to(CreateUniqueKeyPair.class); }).to(CreateUniqueKeyPair.class);
bind(new TypeLiteral<CacheLoader<RegionAndName, Image>>() { bind(new TypeLiteral<CacheLoader<RegionAndName, Image>>() {
}).to(RegionAndIdToImage.class); }).to(RegionAndIdToImage.class);
@ -131,8 +134,8 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
protected Cache<RegionAndName, KeyPair> keypairMap(CacheLoader<RegionAndName, KeyPair> in) { protected ConcurrentMap<RegionAndName, KeyPair> keypairMap(Injector i) {
return CacheBuilder.newBuilder().build(in); return Maps.newConcurrentMap();
} }
@Provides @Provides

View File

@ -32,15 +32,15 @@ import org.jclouds.ec2.domain.KeyPair;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.cache.CacheLoader;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Singleton @Singleton
public class CreateUniqueKeyPair extends CacheLoader<RegionAndName, KeyPair> { public class CreateUniqueKeyPair implements Function<RegionAndName, KeyPair> {
@Resource @Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER) @Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
@ -54,7 +54,7 @@ public class CreateUniqueKeyPair extends CacheLoader<RegionAndName, KeyPair> {
} }
@Override @Override
public KeyPair load(RegionAndName from) { public KeyPair apply(RegionAndName from) {
return createNewKeyPairInRegion(from.getRegion(), from.getName()); return createNewKeyPairInRegion(from.getRegion(), from.getName());
} }
@ -67,7 +67,7 @@ public class CreateUniqueKeyPair extends CacheLoader<RegionAndName, KeyPair> {
while (keyPair == null) { while (keyPair == null) {
try { try {
keyPair = ec2Client.getKeyPairServices().createKeyPairInRegion(region, getNextName(region, group)); keyPair = ec2Client.getKeyPairServices().createKeyPairInRegion(region, getNextName(region, group));
logger.debug("<< created keyPair(%s) fingerprint(%s)", keyPair.getKeyName(), keyPair.getKeyFingerprint()); logger.debug("<< created keyPair(%s)", keyPair);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
} }

View File

@ -20,6 +20,7 @@ package org.jclouds.ec2.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import javax.inject.Inject; import javax.inject.Inject;
@ -43,12 +44,12 @@ import com.google.common.cache.CacheLoader;
*/ */
@Singleton @Singleton
public class CredentialsForInstance extends CacheLoader<RunningInstance, Credentials> { public class CredentialsForInstance extends CacheLoader<RunningInstance, Credentials> {
private final Cache<RegionAndName, KeyPair> credentialsMap; private final ConcurrentMap<RegionAndName, KeyPair> credentialsMap;
private final PopulateDefaultLoginCredentialsForImageStrategy credentialProvider; private final PopulateDefaultLoginCredentialsForImageStrategy credentialProvider;
private final Supplier<Cache<RegionAndName, ? extends Image>> imageMap; private final Supplier<Cache<RegionAndName, ? extends Image>> imageMap;
@Inject @Inject
CredentialsForInstance(Cache<RegionAndName, KeyPair> credentialsMap, CredentialsForInstance(ConcurrentMap<RegionAndName, KeyPair> credentialsMap,
PopulateDefaultLoginCredentialsForImageStrategy credentialProvider, Supplier<Cache<RegionAndName, ? extends Image>> imageMap) { PopulateDefaultLoginCredentialsForImageStrategy credentialProvider, Supplier<Cache<RegionAndName, ? extends Image>> imageMap) {
this.credentialsMap = checkNotNull(credentialsMap, "credentialsMap"); this.credentialsMap = checkNotNull(credentialsMap, "credentialsMap");
this.credentialProvider = checkNotNull(credentialProvider, "credentialProvider"); this.credentialProvider = checkNotNull(credentialProvider, "credentialProvider");

View File

@ -18,10 +18,14 @@
*/ */
package org.jclouds.ec2.compute.strategy; 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey;
import static org.jclouds.crypto.SshKeys.sha1PrivateKey;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -30,7 +34,6 @@ import javax.inject.Singleton;
import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.options.TemplateOptions;
import static org.jclouds.crypto.SshKeys.*;
import org.jclouds.ec2.compute.domain.RegionAndName; import org.jclouds.ec2.compute.domain.RegionAndName;
import org.jclouds.ec2.compute.domain.RegionNameAndIngressRules; import org.jclouds.ec2.compute.domain.RegionNameAndIngressRules;
import org.jclouds.ec2.compute.options.EC2TemplateOptions; import org.jclouds.ec2.compute.options.EC2TemplateOptions;
@ -40,10 +43,10 @@ import org.jclouds.ec2.options.RunInstancesOptions;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.util.concurrent.UncheckedExecutionException;
/** /**
* *
@ -52,15 +55,20 @@ import com.google.common.util.concurrent.UncheckedExecutionException;
@Singleton @Singleton
public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions { public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions {
@VisibleForTesting @VisibleForTesting
public final Cache<RegionAndName, KeyPair> credentialsMap; public Function<RegionAndName, KeyPair> makeKeyPair;
@VisibleForTesting
public final ConcurrentMap<RegionAndName, KeyPair> credentialsMap;
@VisibleForTesting @VisibleForTesting
public final Cache<RegionAndName, String> securityGroupMap; public final Cache<RegionAndName, String> securityGroupMap;
protected final Provider<RunInstancesOptions> optionsProvider; @VisibleForTesting
public final Provider<RunInstancesOptions> optionsProvider;
@Inject @Inject
public CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions(Cache<RegionAndName, KeyPair> credentialsMap, public CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions(Function<RegionAndName, KeyPair> makeKeyPair,
ConcurrentMap<RegionAndName, KeyPair> credentialsMap,
@Named("SECURITY") Cache<RegionAndName, String> securityGroupMap, @Named("SECURITY") Cache<RegionAndName, String> securityGroupMap,
Provider<RunInstancesOptions> optionsProvider) { Provider<RunInstancesOptions> optionsProvider) {
this.makeKeyPair = checkNotNull(makeKeyPair, "makeKeyPair");
this.credentialsMap = checkNotNull(credentialsMap, "credentialsMap"); this.credentialsMap = checkNotNull(credentialsMap, "credentialsMap");
this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap"); this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
this.optionsProvider = checkNotNull(optionsProvider, "optionsProvider"); this.optionsProvider = checkNotNull(optionsProvider, "optionsProvider");
@ -119,31 +127,36 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions {
KeyPair keyPair = KeyPair.builder().region(region).keyName(keyPairName).fingerprint( KeyPair keyPair = KeyPair.builder().region(region).keyName(keyPairName).fingerprint(
fingerprintPrivateKey(pem)).sha1OfPrivateKey(sha1PrivateKey(pem)).keyMaterial(pem).build(); fingerprintPrivateKey(pem)).sha1OfPrivateKey(sha1PrivateKey(pem)).keyMaterial(pem).build();
RegionAndName key = new RegionAndName(region, keyPairName); RegionAndName key = new RegionAndName(region, keyPairName);
credentialsMap.asMap().put(key, keyPair); credentialsMap.put(key, keyPair);
} }
} }
if (options.getRunScript() != null) { if (options.getRunScript() != null) {
RegionAndName regionAndName = new RegionAndName(region, keyPairName); RegionAndName regionAndName = new RegionAndName(region, keyPairName);
String message = String checkArgument(
.format( credentialsMap.containsKey(regionAndName),
"no private key configured for: %s; please use options.overrideLoginCredentialWith(rsa_private_text)", "no private key configured for: %s; please use options.overrideLoginCredentialWith(rsa_private_text)",
regionAndName); regionAndName);
// test to see if this is in cache.
try {
credentialsMap.getUnchecked(regionAndName);
} catch (NullPointerException nex) {
throw new IllegalArgumentException(message, nex);
} catch (UncheckedExecutionException nex) {
throw new IllegalArgumentException(message, nex);
}
} }
return keyPairName; return keyPairName;
} }
// base EC2 driver currently does not support key import // base EC2 driver currently does not support key import
protected String createOrImportKeyPair(String region, String group, TemplateOptions options) { protected String createOrImportKeyPair(String region, String group, TemplateOptions options) {
return credentialsMap.getUnchecked(new RegionAndName(region, group)).getKeyName(); 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();
} }
@VisibleForTesting @VisibleForTesting

View File

@ -47,6 +47,7 @@ import org.jclouds.encryption.internal.Base64;
import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.scriptbuilder.domain.Statements;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -251,8 +252,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
expect(options.getOverridingCredentials()).andReturn(null); expect(options.getOverridingCredentials()).andReturn(null);
expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(options.getRunScript()).andReturn(Statements.exec("echo foo"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andThrow( expect(strategy.credentialsMap.containsKey(new RegionAndName(region, userSuppliedKeyPair))).andReturn(false);
new NullPointerException());
// replay mocks // replay mocks
replay(options); replay(options);
@ -284,7 +284,7 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
expect(options.getOverridingCredentials()).andReturn(null); expect(options.getOverridingCredentials()).andReturn(null);
expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(options.getRunScript()).andReturn(Statements.exec("echo foo"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andReturn(keyPair); expect(strategy.credentialsMap.containsKey(new RegionAndName(region, userSuppliedKeyPair))).andReturn(true);
// replay mocks // replay mocks
replay(options); replay(options);
@ -300,7 +300,6 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@SuppressWarnings("unchecked")
public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldToWithRunScriptAndCredentialsSpecified() { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldToWithRunScriptAndCredentialsSpecified() {
// setup constants // setup constants
String region = Region.AP_SOUTHEAST_1; String region = Region.AP_SOUTHEAST_1;
@ -311,22 +310,19 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions strategy = setupStrategy(); CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions strategy = setupStrategy();
EC2TemplateOptions options = createMock(EC2TemplateOptions.class); EC2TemplateOptions options = createMock(EC2TemplateOptions.class);
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
ConcurrentMap<RegionAndName, KeyPair> backing = createMock(ConcurrentMap.class);
// setup expectations // setup expectations
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.getOverridingCredentials()).andReturn(CREDENTIALS).atLeastOnce(); expect(options.getOverridingCredentials()).andReturn(CREDENTIALS).atLeastOnce();
expect(strategy.credentialsMap.asMap()).andReturn(backing);
// Notice that the fingerprint and sha1 generated // Notice that the fingerprint and sha1 generated
expect(backing.put(new RegionAndName(region, userSuppliedKeyPair), KEYPAIR)).andReturn(null); expect(strategy.credentialsMap.put(new RegionAndName(region, userSuppliedKeyPair), KEYPAIR)).andReturn(null);
expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(options.getRunScript()).andReturn(Statements.exec("echo foo"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andReturn(keyPair); expect(strategy.credentialsMap.containsKey(new RegionAndName(region, userSuppliedKeyPair))).andReturn(true);
// replay mocks // replay mocks
replay(options); replay(options);
replay(keyPair); replay(keyPair);
replay(backing);
replayStrategy(strategy); replayStrategy(strategy);
// run // run
@ -335,7 +331,6 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
// verify mocks // verify mocks
verify(options); verify(options);
verify(keyPair); verify(keyPair);
verify(backing);
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@ -357,7 +352,8 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.shouldAutomaticallyCreateKeyPair()).andReturn(shouldAutomaticallyCreateKeyPair); expect(options.shouldAutomaticallyCreateKeyPair()).andReturn(shouldAutomaticallyCreateKeyPair);
expect(keyPair.getKeyName()).andReturn(systemGeneratedKeyPairName).atLeastOnce(); expect(keyPair.getKeyName()).andReturn(systemGeneratedKeyPairName).atLeastOnce();
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, group))).andReturn(keyPair); expect(strategy.credentialsMap.containsKey(new RegionAndName(region, group))).andReturn(true);
expect(strategy.credentialsMap.get(new RegionAndName(region, group))).andReturn(keyPair);
expect(options.getRunScript()).andReturn(null); expect(options.getRunScript()).andReturn(null);
// replay mocks // replay mocks
@ -545,19 +541,22 @@ public class CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptionsTest {
} }
private void verifyStrategy(CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions strategy) { private void verifyStrategy(CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions strategy) {
verify(strategy.makeKeyPair);
verify(strategy.credentialsMap); verify(strategy.credentialsMap);
verify(strategy.securityGroupMap); verify(strategy.securityGroupMap);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions setupStrategy() { private CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions setupStrategy() {
Cache<RegionAndName, KeyPair> credentialsMap = createMock(Cache.class); Function<RegionAndName, KeyPair> makeKeyPair = createMock(Function.class);
ConcurrentMap<RegionAndName, KeyPair> credentialsMap = createMock(ConcurrentMap.class);
Cache<RegionAndName, String> securityGroupMap = createMock(Cache.class); Cache<RegionAndName, String> securityGroupMap = createMock(Cache.class);
return new CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions(credentialsMap, securityGroupMap, return new CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions(makeKeyPair, credentialsMap,
OPTIONS_PROVIDER); securityGroupMap, OPTIONS_PROVIDER);
} }
private void replayStrategy(CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions strategy) { private void replayStrategy(CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions strategy) {
replay(strategy.makeKeyPair);
replay(strategy.credentialsMap); replay(strategy.credentialsMap);
replay(strategy.securityGroupMap); replay(strategy.securityGroupMap);
} }

View File

@ -232,6 +232,16 @@ public class SshKeys {
return fingerprint(certKeySpec.getPublicExponent(), certKeySpec.getModulus()); return fingerprint(certKeySpec.getPublicExponent(), certKeySpec.getModulus());
} }
/**
* @param publicKeyOpenSSH
* RSA public key in OpenSSH format
* @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
*/
public static String fingerprintPublicKey(String publicKeyOpenSSH) {
RSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
return fingerprint(publicKeySpec.getPublicExponent(), publicKeySpec.getModulus());
}
/** /**
* @return true if the keypair has the same SHA1 fingerprint as supplied * @return true if the keypair has the same SHA1 fingerprint as supplied
*/ */

View File

@ -22,8 +22,9 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.transform;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import javax.inject.Inject; import javax.inject.Inject;
@ -99,7 +100,7 @@ public class AWSEC2ComputeService extends EC2ComputeService {
RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, AWSEC2Client ec2Client, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, AWSEC2Client ec2Client,
Cache<RegionAndName, KeyPair> credentialsMap, ConcurrentMap<RegionAndName, KeyPair> credentialsMap,
@Named("SECURITY") Cache<RegionAndName, String> securityGroupMap, @Named("SECURITY") Cache<RegionAndName, String> securityGroupMap,
@Named("PLACEMENT") Cache<RegionAndName, String> placementGroupMap, @Named("PLACEMENT") Cache<RegionAndName, String> placementGroupMap,
@Named("DELETED") Predicate<PlacementGroup> placementGroupDeleted) { @Named("DELETED") Predicate<PlacementGroup> placementGroupDeleted) {

View File

@ -84,7 +84,7 @@ public class AWSEC2ComputeServiceDependenciesModule extends EC2ComputeServiceDep
}).to(CredentialsForInstance.class); }).to(CredentialsForInstance.class);
bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() { bind(new TypeLiteral<CacheLoader<RegionAndName, String>>() {
}).to(CreateSecurityGroupIfNeeded.class); }).to(CreateSecurityGroupIfNeeded.class);
bind(new TypeLiteral<CacheLoader<RegionAndName, KeyPair>>() { bind(new TypeLiteral<Function<RegionAndName, KeyPair>>() {
}).to(CreateUniqueKeyPair.class); }).to(CreateUniqueKeyPair.class);
bind(new TypeLiteral<Function<RegionNameAndPublicKeyMaterial, KeyPair>>() { bind(new TypeLiteral<Function<RegionNameAndPublicKeyMaterial, KeyPair>>() {
}).to(ImportOrReturnExistingKeypair.class); }).to(ImportOrReturnExistingKeypair.class);

View File

@ -21,6 +21,8 @@ package org.jclouds.aws.ec2.compute.strategy;
import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.or; import static com.google.common.base.Predicates.or;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -52,7 +54,7 @@ import com.google.common.cache.Cache;
*/ */
@Singleton @Singleton
public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions extends public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions extends
CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions { CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions {
@Resource @Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER) @Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
@ -65,13 +67,13 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions
@Inject @Inject
public CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions( public CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions(
Cache<RegionAndName, KeyPair> credentialsMap, Function<RegionAndName, KeyPair> makeKeyPair, ConcurrentMap<RegionAndName, KeyPair> credentialsMap,
@Named("SECURITY") Cache<RegionAndName, String> securityGroupMap, @Named("SECURITY") Cache<RegionAndName, String> securityGroupMap,
Provider<RunInstancesOptions> optionsProvider, Provider<RunInstancesOptions> optionsProvider,
@Named("PLACEMENT") Cache<RegionAndName, String> placementGroupMap, @Named("PLACEMENT") Cache<RegionAndName, String> placementGroupMap,
CreatePlacementGroupIfNeeded createPlacementGroupIfNeeded, CreatePlacementGroupIfNeeded createPlacementGroupIfNeeded,
Function<RegionNameAndPublicKeyMaterial, KeyPair> importExistingKeyPair) { Function<RegionNameAndPublicKeyMaterial, KeyPair> importExistingKeyPair) {
super(credentialsMap, securityGroupMap, optionsProvider); super(makeKeyPair, credentialsMap, securityGroupMap, optionsProvider);
this.placementGroupMap = placementGroupMap; this.placementGroupMap = placementGroupMap;
this.createPlacementGroupIfNeeded = createPlacementGroupIfNeeded; this.createPlacementGroupIfNeeded = createPlacementGroupIfNeeded;
this.importExistingKeyPair = importExistingKeyPair; this.importExistingKeyPair = importExistingKeyPair;
@ -79,10 +81,11 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions
public AWSRunInstancesOptions execute(String region, String group, Template template) { public AWSRunInstancesOptions execute(String region, String group, Template template) {
AWSRunInstancesOptions instanceOptions = AWSRunInstancesOptions.class AWSRunInstancesOptions instanceOptions = AWSRunInstancesOptions.class
.cast(super.execute(region, group, template)); .cast(super.execute(region, group, template));
String placementGroupName = template.getHardware().getId().startsWith("cc") ? createNewPlacementGroupUnlessUserSpecifiedOtherwise( String placementGroupName = template.getHardware().getId().startsWith("cc") ? createNewPlacementGroupUnlessUserSpecifiedOtherwise(
region, group, template.getOptions()) : null; region, group, template.getOptions())
: null;
if (placementGroupName != null) if (placementGroupName != null)
instanceOptions.inPlacementGroup(placementGroupName); instanceOptions.inPlacementGroup(placementGroupName);
@ -101,7 +104,7 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions
placementGroupName = AWSEC2TemplateOptions.class.cast(options).getPlacementGroup(); placementGroupName = AWSEC2TemplateOptions.class.cast(options).getPlacementGroup();
if (placementGroupName == null) if (placementGroupName == null)
shouldAutomaticallyCreatePlacementGroup = AWSEC2TemplateOptions.class.cast(options) shouldAutomaticallyCreatePlacementGroup = AWSEC2TemplateOptions.class.cast(options)
.shouldAutomaticallyCreatePlacementGroup(); .shouldAutomaticallyCreatePlacementGroup();
} }
if (placementGroupName == null && shouldAutomaticallyCreatePlacementGroup) { if (placementGroupName == null && shouldAutomaticallyCreatePlacementGroup) {
// placementGroupName must be unique within an account per // placementGroupName must be unique within an account per
@ -118,17 +121,16 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions
public String createNewKeyPairUnlessUserSpecifiedOtherwise(String region, String group, TemplateOptions options) { public String createNewKeyPairUnlessUserSpecifiedOtherwise(String region, String group, TemplateOptions options) {
RegionAndName key = new RegionAndName(region, group); RegionAndName key = new RegionAndName(region, group);
KeyPair pair; KeyPair pair;
if (credentialsMap.asMap().containsKey(key))
return credentialsMap.getUnchecked(key).getKeyName();
if (and(hasPublicKeyMaterial, or(doesntNeedSshAfterImportingPublicKey, hasLoginCredential)).apply(options)) { if (and(hasPublicKeyMaterial, or(doesntNeedSshAfterImportingPublicKey, hasLoginCredential)).apply(options)) {
pair = importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, options.getPublicKey())); pair = importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, options.getPublicKey()));
options.dontAuthorizePublicKey(); options.dontAuthorizePublicKey();
if (hasLoginCredential.apply(options)) if (hasLoginCredential.apply(options))
pair = pair.toBuilder().keyMaterial(options.getOverridingCredentials().credential).build(); pair = pair.toBuilder().keyMaterial(options.getOverridingCredentials().credential).build();
credentialsMap.asMap().put(key, pair); credentialsMap.put(key, pair);
} else { } else {
if (hasPublicKeyMaterial.apply(options)) { if (hasPublicKeyMaterial.apply(options)) {
logger.warn("to avoid creating temporary keys in aws-ec2, use templateOption overrideLoginCredentialWith(id_rsa)"); logger
.warn("to avoid creating temporary keys in aws-ec2, use templateOption overrideLoginCredentialWith(id_rsa)");
} }
return super.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options); return super.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options);
} }
@ -165,8 +167,8 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions
@Override @Override
protected boolean userSpecifiedTheirOwnGroups(TemplateOptions options) { protected boolean userSpecifiedTheirOwnGroups(TemplateOptions options) {
return options instanceof AWSEC2TemplateOptions return options instanceof AWSEC2TemplateOptions
&& AWSEC2TemplateOptions.class.cast(options).getGroupIds().size() > 0 && AWSEC2TemplateOptions.class.cast(options).getGroupIds().size() > 0
|| super.userSpecifiedTheirOwnGroups(options); || super.userSpecifiedTheirOwnGroups(options);
} }
@Override @Override

View File

@ -19,6 +19,7 @@
package org.jclouds.aws.ec2.functions; package org.jclouds.aws.ec2.functions;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.crypto.SshKeys.fingerprintPublicKey;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Inject; import javax.inject.Inject;
@ -68,15 +69,24 @@ public class ImportOrReturnExistingKeypair implements Function<RegionNameAndPubl
while (keyPair == null) while (keyPair == null)
try { try {
keyPair = ec2Client.getKeyPairServices().importKeyPairInRegion(region, "jclouds#" + group, keyPair = ec2Client.getKeyPairServices().importKeyPairInRegion(region, "jclouds#" + group,
publicKeyMaterial); publicKeyMaterial);
keyPair = addFingerprintToKeyPair(publicKeyMaterial, keyPair);
logger.debug("<< imported keyPair(%s)", keyPair); logger.debug("<< imported keyPair(%s)", keyPair);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
keyPair = Iterables.getFirst( keyPair = Iterables.getFirst(ec2Client.getKeyPairServices().describeKeyPairsInRegion(region,
ec2Client.getKeyPairServices().describeKeyPairsInRegion(region, "jclouds#" + group), null); "jclouds#" + group), null);
if (keyPair != null) if (keyPair != null) {
keyPair = addFingerprintToKeyPair(publicKeyMaterial, keyPair);
logger.debug("<< retrieved existing keyPair(%s)", keyPair); logger.debug("<< retrieved existing keyPair(%s)", keyPair);
}
} }
return keyPair; return keyPair;
} }
public KeyPair addFingerprintToKeyPair(String publicKeyMaterial, KeyPair keyPair) {
// add in the fingerprint as it makes correlating keys in ssh logs possible
keyPair = keyPair.toBuilder().fingerprint(fingerprintPublicKey(publicKeyMaterial)).build();
return keyPair;
}
} }

View File

@ -60,7 +60,6 @@ import com.google.common.base.Function;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
/** /**
* @author Adrian Cole * @author Adrian Cole
@ -399,13 +398,11 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(Maps.<RegionAndName, KeyPair> newConcurrentMap());
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.getPublicKey()).andReturn(null).times(2); expect(options.getPublicKey()).andReturn(null).times(2);
expect(options.getOverridingCredentials()).andReturn(null); expect(options.getOverridingCredentials()).andReturn(null);
expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(options.getRunScript()).andReturn(Statements.exec("echo foo"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andThrow( expect(strategy.credentialsMap.containsKey(new RegionAndName(region, userSuppliedKeyPair))).andReturn(false);
new NullPointerException());
// replay mocks // replay mocks
replay(options); replay(options);
@ -433,12 +430,11 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(Maps.<RegionAndName, KeyPair> newConcurrentMap());
expect(options.getPublicKey()).andReturn(null).times(2); expect(options.getPublicKey()).andReturn(null).times(2);
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.getOverridingCredentials()).andReturn(null); expect(options.getOverridingCredentials()).andReturn(null);
expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(options.getRunScript()).andReturn(Statements.exec("echo foo"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andReturn(keyPair); expect(strategy.credentialsMap.containsKey(new RegionAndName(region, userSuppliedKeyPair))).andReturn(true);
// replay mocks // replay mocks
replay(options); replay(options);
@ -454,8 +450,7 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@SuppressWarnings("unchecked") public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_ImportPublicKeyWhenOptionIsSet() {
public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_reusesKeyWhenToldToWithRunScriptAndCredentialsSpecified() {
// setup constants // setup constants
String region = Region.AP_SOUTHEAST_1; String region = Region.AP_SOUTHEAST_1;
String group = "group"; String group = "group";
@ -466,22 +461,20 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
EC2TemplateOptions options = createMock(EC2TemplateOptions.class); EC2TemplateOptions options = createMock(EC2TemplateOptions.class);
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
ConcurrentMap<RegionAndName, KeyPair> backing = createMock(ConcurrentMap.class); // we specify we have a public key we want to use for authentication
expect(options.getPublicKey()).andReturn("ssh-rsa").times(2);
// setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(backing).atLeastOnce();
expect(backing.containsKey(new RegionAndName(region, group))).andReturn(false);
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.getPublicKey()).andReturn(null).times(2);
expect(options.getOverridingCredentials()).andReturn(CREDENTIALS).atLeastOnce(); expect(options.getOverridingCredentials()).andReturn(CREDENTIALS).atLeastOnce();
expect(backing.put(new RegionAndName(region, userSuppliedKeyPair), KEYPAIR)).andReturn(null);
// Here, we import the keypair and place it into the cache
expect(strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, "ssh-rsa")))
.andReturn(KEYPAIR);
expect(options.dontAuthorizePublicKey()).andReturn(options);
expect(strategy.credentialsMap.put(new RegionAndName(region, group), KEYPAIR)).andReturn(null);
expect(options.getRunScript()).andReturn(Statements.exec("echo foo")); expect(options.getRunScript()).andReturn(Statements.exec("echo foo"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, userSuppliedKeyPair))).andReturn(keyPair);
// replay mocks // replay mocks
replay(options); replay(options);
replay(keyPair); replay(keyPair);
replay(backing);
replayStrategy(strategy); replayStrategy(strategy);
// run // run
@ -490,11 +483,9 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
// verify mocks // verify mocks
verify(options); verify(options);
verify(keyPair); verify(keyPair);
verify(backing);
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_importsKeyPairAndUnsetsTemplateInstructionWhenPublicKeySuppliedAndAddsCredentialToMapWhenOverridingCredsAreSet() { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_importsKeyPairAndUnsetsTemplateInstructionWhenPublicKeySuppliedAndAddsCredentialToMapWhenOverridingCredsAreSet() {
// setup constants // setup constants
@ -506,31 +497,25 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
AWSEC2TemplateOptions options = keyPair(group).authorizePublicKey("ssh-rsa").overrideCredentialsWith( AWSEC2TemplateOptions options = keyPair(group).authorizePublicKey("ssh-rsa").overrideCredentialsWith(
new Credentials("foo", CREDENTIALS.credential)); new Credentials("foo", CREDENTIALS.credential));
KeyPair keyPair = new KeyPair(region, group, "//TODO", null, null); KeyPair keyPair = new KeyPair(region, group, "//TODO", null, null);
ConcurrentMap<RegionAndName, KeyPair> backing = createMock(ConcurrentMap.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(backing).atLeastOnce();
expect(backing.containsKey(new RegionAndName(region, group))).andReturn(false);
expect( expect(
strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group,
CREDENTIALS.credential))).andReturn(keyPair); CREDENTIALS.credential))).andReturn(keyPair);
expect( expect(
backing.put(new RegionAndName(region, group), keyPair.toBuilder().keyMaterial(CREDENTIALS.credential) strategy.credentialsMap.put(new RegionAndName(region, group), keyPair.toBuilder().keyMaterial(
.build())).andReturn(null); CREDENTIALS.credential).build())).andReturn(null);
// replay mocks // replay mocks
replay(backing);
replayStrategy(strategy); replayStrategy(strategy);
// run // run
assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), group); assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), group);
// verify mocks // verify mocks
verify(backing);
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_importsKeyPairAndUnsetsTemplateInstructionWhenPublicKeySupplied() { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_importsKeyPairAndUnsetsTemplateInstructionWhenPublicKeySupplied() {
// setup constants // setup constants
@ -543,24 +528,18 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
KeyPair keyPair = new KeyPair(region, "jclouds#" + group, "fingerprint", null, null); KeyPair keyPair = new KeyPair(region, "jclouds#" + group, "fingerprint", null, null);
ConcurrentMap<RegionAndName, KeyPair> backing = createMock(ConcurrentMap.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(backing).atLeastOnce();
expect(backing.containsKey(new RegionAndName(region, group))).andReturn(false);
expect(strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, "ssh-rsa"))) expect(strategy.importExistingKeyPair.apply(new RegionNameAndPublicKeyMaterial(region, group, "ssh-rsa")))
.andReturn(keyPair); .andReturn(keyPair);
expect(backing.put(new RegionAndName(region, group), keyPair)).andReturn(null); expect(strategy.credentialsMap.put(new RegionAndName(region, group), keyPair)).andReturn(null);
// replay mocks // replay mocks
replay(backing);
replayStrategy(strategy); replayStrategy(strategy);
// run // run
assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), "jclouds#" + group); assertEquals(strategy.createNewKeyPairUnlessUserSpecifiedOtherwise(region, group, options), "jclouds#" + group);
// verify mocks // verify mocks
verify(backing);
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@ -578,10 +557,10 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(Maps.<RegionAndName, KeyPair> newConcurrentMap());
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.shouldAutomaticallyCreateKeyPair()).andReturn(shouldAutomaticallyCreateKeyPair); expect(options.shouldAutomaticallyCreateKeyPair()).andReturn(shouldAutomaticallyCreateKeyPair);
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, group))).andReturn(keyPair); expect(strategy.credentialsMap.containsKey(new RegionAndName(region, group))).andReturn(true);
expect(strategy.credentialsMap.get(new RegionAndName(region, group))).andReturn(keyPair);
expect(options.getPublicKey()).andReturn(null).times(2); expect(options.getPublicKey()).andReturn(null).times(2);
expect(keyPair.getKeyName()).andReturn(systemGeneratedKeyPairName).atLeastOnce(); expect(keyPair.getKeyName()).andReturn(systemGeneratedKeyPairName).atLeastOnce();
expect(options.getRunScript()).andReturn(null); expect(options.getRunScript()).andReturn(null);
@ -601,7 +580,6 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@SuppressWarnings("unchecked")
public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_returnsExistingKeyIfAlreadyPresent() { public void testCreateNewKeyPairUnlessUserSpecifiedOtherwise_returnsExistingKeyIfAlreadyPresent() {
// setup constants // setup constants
String region = Region.AP_SOUTHEAST_1; String region = Region.AP_SOUTHEAST_1;
@ -612,18 +590,18 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy = setupStrategy(); CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy = setupStrategy();
AWSEC2TemplateOptions options = createMock(AWSEC2TemplateOptions.class); AWSEC2TemplateOptions options = createMock(AWSEC2TemplateOptions.class);
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
ConcurrentMap<RegionAndName, KeyPair> backing = createMock(ConcurrentMap.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(backing); expect(options.getOverridingCredentials()).andReturn(null);
expect(backing.containsKey(new RegionAndName(region, group))).andReturn(true); expect(options.getRunScript()).andReturn(Statements.exec("echo hello"));
expect(strategy.credentialsMap.getUnchecked(new RegionAndName(region, group))).andReturn(keyPair); expect(options.getPublicKey()).andReturn(null).times(2);
expect(keyPair.getKeyName()).andReturn(systemGeneratedKeyPairName).atLeastOnce(); expect(options.getKeyPair()).andReturn(systemGeneratedKeyPairName);
expect(strategy.credentialsMap.containsKey(new RegionAndName(region, systemGeneratedKeyPairName)))
.andReturn(true);
// replay mocks // replay mocks
replay(options); replay(options);
replay(keyPair); replay(keyPair);
replay(backing);
replayStrategy(strategy); replayStrategy(strategy);
// run // run
@ -633,7 +611,6 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
// verify mocks // verify mocks
verify(options); verify(options);
verify(keyPair); verify(keyPair);
verify(backing);
verifyStrategy(strategy); verifyStrategy(strategy);
} }
@ -651,7 +628,6 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
KeyPair keyPair = createMock(KeyPair.class); KeyPair keyPair = createMock(KeyPair.class);
// setup expectations // setup expectations
expect(strategy.credentialsMap.asMap()).andReturn(Maps.<RegionAndName, KeyPair> newConcurrentMap());
expect(options.getPublicKey()).andReturn(null).times(2); expect(options.getPublicKey()).andReturn(null).times(2);
expect(options.getKeyPair()).andReturn(userSuppliedKeyPair); expect(options.getKeyPair()).andReturn(userSuppliedKeyPair);
expect(options.getRunScript()).andReturn(null); expect(options.getRunScript()).andReturn(null);
@ -937,6 +913,7 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
} }
private void verifyStrategy(CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy) { private void verifyStrategy(CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy) {
verify(strategy.makeKeyPair);
verify(strategy.credentialsMap); verify(strategy.credentialsMap);
verify(strategy.securityGroupMap); verify(strategy.securityGroupMap);
verify(strategy.placementGroupMap); verify(strategy.placementGroupMap);
@ -946,17 +923,20 @@ public class CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptionsT
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions setupStrategy() { private CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions setupStrategy() {
Cache<RegionAndName, KeyPair> credentialsMap = createMock(Cache.class); Function<RegionAndName, KeyPair> makeKeyPair = createMock(Function.class);
ConcurrentMap<RegionAndName, KeyPair> credentialsMap = createMock(ConcurrentMap.class);
Cache<RegionAndName, String> securityGroupMap = createMock(Cache.class); Cache<RegionAndName, String> securityGroupMap = createMock(Cache.class);
Cache<RegionAndName, String> placementGroupMap = createMock(Cache.class); Cache<RegionAndName, String> placementGroupMap = createMock(Cache.class);
Function<RegionNameAndPublicKeyMaterial, KeyPair> importExistingKeyPair = createMock(Function.class); Function<RegionNameAndPublicKeyMaterial, KeyPair> importExistingKeyPair = createMock(Function.class);
CreatePlacementGroupIfNeeded createPlacementGroupIfNeeded = createMock(CreatePlacementGroupIfNeeded.class); CreatePlacementGroupIfNeeded createPlacementGroupIfNeeded = createMock(CreatePlacementGroupIfNeeded.class);
return new CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions(credentialsMap, securityGroupMap, return new CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions(makeKeyPair, credentialsMap,
OPTIONS_PROVIDER, placementGroupMap, createPlacementGroupIfNeeded, importExistingKeyPair); securityGroupMap, OPTIONS_PROVIDER, placementGroupMap, createPlacementGroupIfNeeded,
importExistingKeyPair);
} }
private void replayStrategy(CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy) { private void replayStrategy(CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions strategy) {
replay(strategy.makeKeyPair);
replay(strategy.credentialsMap); replay(strategy.credentialsMap);
replay(strategy.securityGroupMap); replay(strategy.securityGroupMap);
replay(strategy.placementGroupMap); replay(strategy.placementGroupMap);

View File

@ -22,10 +22,9 @@ import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock; import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify; import static org.easymock.classextension.EasyMock.verify;
import static org.jclouds.crypto.SshKeys.fingerprintPublicKey;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.net.UnknownHostException;
import org.jclouds.aws.ec2.AWSEC2Client; import org.jclouds.aws.ec2.AWSEC2Client;
import org.jclouds.aws.ec2.services.AWSKeyPairClient; import org.jclouds.aws.ec2.services.AWSKeyPairClient;
import org.jclouds.ec2.domain.KeyPair; import org.jclouds.ec2.domain.KeyPair;
@ -38,39 +37,42 @@ import com.google.common.collect.ImmutableSet;
*/ */
@Test(groups = "unit") @Test(groups = "unit")
public class ImportOrReturnExistingKeypairTest { public class ImportOrReturnExistingKeypairTest {
private static final String PUBLIC_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcm8DjTHg4r72dVhNLQ33XpUyMLr+ph78i4NR3LqF1bXDP0g4xNLcI/GUTQq6g07X8zs7vIWyjoitqBPFSQ2onaZQ6pXQF/QISRQgrN5hEZ+nH5Aw+isdstBeOMWKdYrCJtm6/qWq2+rByyuNbtulazP3H7SqoozSjRSGNQyFNGpmhjGgTbNQftYDwlFq0T9tCSO/+dYF8j79bNIOEmfsCMiqXQ13hD5vGiEgkRm7zIPDUfpOl3ubDzebpRgGTh5kfv2vd3Z665AxQoi6fItvDu80knyphMlC41giIm5YqfPOPG4lR+6aF06p+NKhvOeECNMtRsD9u1kKJD9NqxXhx";
private static final KeyPair pair = KeyPair.builder().region("region").keyName("jclouds#group").sha1OfPrivateKey(
"foo").build();
private static final KeyPair pairWithFingerprint = pair.toBuilder().fingerprint(fingerprintPublicKey(PUBLIC_KEY))
.build();
@Test @Test
public void testApply() throws UnknownHostException { public void testApply() {
AWSEC2Client client = createMock(AWSEC2Client.class); AWSEC2Client client = createMock(AWSEC2Client.class);
AWSKeyPairClient keyClient = createMock(AWSKeyPairClient.class); AWSKeyPairClient keyClient = createMock(AWSKeyPairClient.class);
KeyPair pair = createMock(KeyPair.class);
expect(client.getKeyPairServices()).andReturn(keyClient).atLeastOnce(); expect(client.getKeyPairServices()).andReturn(keyClient).atLeastOnce();
expect(keyClient.importKeyPairInRegion("region", "jclouds#group", "ssh-rsa")).andReturn(pair); expect(keyClient.importKeyPairInRegion("region", "jclouds#group", PUBLIC_KEY)).andReturn(pair);
replay(client); replay(client);
replay(keyClient); replay(keyClient);
ImportOrReturnExistingKeypair parser = new ImportOrReturnExistingKeypair(client); ImportOrReturnExistingKeypair parser = new ImportOrReturnExistingKeypair(client);
assertEquals(parser.importOrReturnExistingKeypair("region", "group", "ssh-rsa"), pair); assertEquals(parser.importOrReturnExistingKeypair("region", "group", PUBLIC_KEY), pairWithFingerprint);
verify(client); verify(client);
verify(keyClient); verify(keyClient);
} }
@Test @Test
public void testApplyWithIllegalStateExceptionReturnsExistingKey() throws UnknownHostException { public void testApplyWithIllegalStateExceptionReturnsExistingKey() {
AWSEC2Client client = createMock(AWSEC2Client.class); AWSEC2Client client = createMock(AWSEC2Client.class);
AWSKeyPairClient keyClient = createMock(AWSKeyPairClient.class); AWSKeyPairClient keyClient = createMock(AWSKeyPairClient.class);
KeyPair pair = createMock(KeyPair.class);
expect(client.getKeyPairServices()).andReturn(keyClient).atLeastOnce(); expect(client.getKeyPairServices()).andReturn(keyClient).atLeastOnce();
expect(keyClient.importKeyPairInRegion("region", "jclouds#group", "ssh-rsa")).andThrow( expect(keyClient.importKeyPairInRegion("region", "jclouds#group", PUBLIC_KEY)).andThrow(
new IllegalStateException()); new IllegalStateException());
expect(keyClient.describeKeyPairsInRegion("region", "jclouds#group")).andReturn(ImmutableSet.of(pair)); expect(keyClient.describeKeyPairsInRegion("region", "jclouds#group")).andReturn(ImmutableSet.of(pair));
replay(client); replay(client);
@ -78,7 +80,8 @@ public class ImportOrReturnExistingKeypairTest {
ImportOrReturnExistingKeypair parser = new ImportOrReturnExistingKeypair(client); ImportOrReturnExistingKeypair parser = new ImportOrReturnExistingKeypair(client);
assertEquals(parser.importOrReturnExistingKeypair("region", "group", "ssh-rsa"), pair); // enriching to include the ssh fingerprint so that ssh logs are easier to correlate
assertEquals(parser.importOrReturnExistingKeypair("region", "group", PUBLIC_KEY), pairWithFingerprint);
verify(client); verify(client);
verify(keyClient); verify(keyClient);
@ -86,19 +89,17 @@ public class ImportOrReturnExistingKeypairTest {
} }
@Test @Test
public void testApplyWithIllegalStateExceptionRetriesWhenExistingKeyNotFound() throws UnknownHostException { public void testApplyWithIllegalStateExceptionRetriesWhenExistingKeyNotFound() {
AWSEC2Client client = createMock(AWSEC2Client.class); AWSEC2Client client = createMock(AWSEC2Client.class);
AWSKeyPairClient keyClient = createMock(AWSKeyPairClient.class); AWSKeyPairClient keyClient = createMock(AWSKeyPairClient.class);
KeyPair pair = createMock(KeyPair.class);
expect(client.getKeyPairServices()).andReturn(keyClient).atLeastOnce(); expect(client.getKeyPairServices()).andReturn(keyClient).atLeastOnce();
expect(keyClient.importKeyPairInRegion("region", "jclouds#group", "ssh-rsa")).andThrow( expect(keyClient.importKeyPairInRegion("region", "jclouds#group", PUBLIC_KEY)).andThrow(
new IllegalStateException()); new IllegalStateException());
expect(keyClient.describeKeyPairsInRegion("region", "jclouds#group")).andReturn(ImmutableSet.<KeyPair> of()); expect(keyClient.describeKeyPairsInRegion("region", "jclouds#group")).andReturn(ImmutableSet.<KeyPair> of());
expect(keyClient.importKeyPairInRegion("region", "jclouds#group", "ssh-rsa")).andThrow( expect(keyClient.importKeyPairInRegion("region", "jclouds#group", PUBLIC_KEY)).andThrow(
new IllegalStateException()); new IllegalStateException());
expect(keyClient.describeKeyPairsInRegion("region", "jclouds#group")).andReturn(ImmutableSet.<KeyPair> of(pair)); expect(keyClient.describeKeyPairsInRegion("region", "jclouds#group")).andReturn(ImmutableSet.<KeyPair> of(pair));
replay(client); replay(client);
@ -106,7 +107,7 @@ public class ImportOrReturnExistingKeypairTest {
ImportOrReturnExistingKeypair parser = new ImportOrReturnExistingKeypair(client); ImportOrReturnExistingKeypair parser = new ImportOrReturnExistingKeypair(client);
assertEquals(parser.importOrReturnExistingKeypair("region", "group", "ssh-rsa"), pair); assertEquals(parser.importOrReturnExistingKeypair("region", "group", PUBLIC_KEY), pairWithFingerprint);
verify(client); verify(client);
verify(keyClient); verify(keyClient);

View File

@ -130,10 +130,11 @@ public class AWSKeyPairClientLiveTest {
Set<? extends NodeMetadata> nodes = noSshContext.getComputeService().createNodesInGroup(group, 1, options); Set<? extends NodeMetadata> nodes = noSshContext.getComputeService().createNodesInGroup(group, 1, options);
NodeMetadata first = get(nodes, 0); NodeMetadata first = get(nodes, 0);
// credentials should be present as this is the default user from the image
assert first.getCredentials() != null : first; assert first.getCredentials() != null : first;
assert first.getCredentials().identity != null : first; assert first.getCredentials().identity != null : first;
assert first.getCredentials().credential != null : first; // credentials should not be present as the import public key call doesn't have access to
// the related private key
assert first.getCredentials().credential == null : first;
AWSRunningInstance instance = getInstance(instanceClient, first.getProviderId()); AWSRunningInstance instance = getInstance(instanceClient, first.getProviderId());