You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by an...@apache.org on 2017/07/17 09:41:42 UTC

[2/2] jclouds git commit: [JCLOUDS-1318] fix based on nodeTerminatePredicate

[JCLOUDS-1318] fix based on nodeTerminatePredicate

- wait for serer deletion, before deleting the security group
- rename cleanupServer to cleanupResources
- remove keyPairCache
- better usage of tags to remove securityGroups created by jclouds
- remove keyPair after the creation of a group
- remove test for create unique keypair
- openstack nova re-adding support for existing keypair
- fix securityGroupApi check
- fix other unit tests
- remove ServerPredicates as it is now duplicated
- remove TemplateOptions.securityGroupNames as deprecated
- address commits for ApplyNovaTemplateOptionsCreatesNodesWithGroupEncodedIntoNameAndAddToSet
- fix testCreateNodeWhileUserSpecifiesKeyPairAndUserSpecifiedGroups


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/aa11765b
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/aa11765b
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/aa11765b

Branch: refs/heads/master
Commit: aa11765bee8e5e7f2d062ba8123c0dced822f071
Parents: 0bc935d
Author: Andrea Turli <an...@gmail.com>
Authored: Thu Jul 6 18:18:44 2017 +0200
Committer: Andrea Turli <an...@gmail.com>
Committed: Mon Jul 17 11:32:24 2017 +0200

----------------------------------------------------------------------
 .../nova/v2_0/compute/NovaComputeService.java   |  10 +-
 .../v2_0/compute/NovaComputeServiceAdapter.java |  82 ++++++-----
 .../config/NovaComputeServiceContextModule.java | 131 +++++++++++------
 .../AllocateAndAddFloatingIpToNode.java         |   8 +-
 .../compute/functions/CleanupResources.java     | 106 ++++++++++++++
 .../v2_0/compute/functions/CleanupServer.java   | 123 ----------------
 .../compute/loaders/CreateUniqueKeyPair.java    |  76 ----------
 .../compute/options/NovaTemplateOptions.java    |  60 +-------
 ...desWithGroupEncodedIntoNameThenAddToSet.java | 140 ++++++++++++-------
 .../nova/v2_0/options/CreateServerOptions.java  |   3 -
 .../nova/v2_0/predicates/ServerPredicates.java  | 118 ----------------
 .../NovaComputeServiceAdapterExpectTest.java    |  26 ++--
 .../compute/NovaComputeServiceExpectTest.java   |  71 ++++++----
 .../loaders/CreateUniqueKeyPairTest.java        | 108 --------------
 .../options/NovaTemplateOptionsTest.java        |  50 -------
 .../nova/v2_0/features/ServerApiLiveTest.java   |  23 ++-
 .../nova/v2_0/parse/ParseServerTest.java        |  24 ++--
 .../predicates/ServerPredicatesMockTest.java    | 105 --------------
 .../src/test/resources/logback-test.xml         |  42 ++++++
 .../src/test/resources/server_details.json      |   2 +-
 .../resources/server_details_suspended.json     |  87 ++++++++++++
 21 files changed, 549 insertions(+), 846 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeService.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeService.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeService.java
index df3f1ca..584c95e 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeService.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeService.java
@@ -54,7 +54,7 @@ import org.jclouds.compute.strategy.ResumeNodeStrategy;
 import org.jclouds.compute.strategy.SuspendNodeStrategy;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.Location;
-import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupServer;
+import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupResources;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import org.jclouds.scriptbuilder.functions.InitAdminAccess;
 
@@ -65,7 +65,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
 
 @Singleton
 public class NovaComputeService extends BaseComputeService {
-   protected final CleanupServer cleanupServer;
+   protected final CleanupResources cleanupResources;
 
    @Inject
    protected NovaComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
@@ -83,7 +83,7 @@ public class NovaComputeService extends BaseComputeService {
             RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
             PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
             @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
-            CleanupServer cleanupServer,
+            CleanupResources cleanupResources,
             Optional<ImageExtension> imageExtension,
             Optional<SecurityGroupExtension> securityGroupExtension) {
       super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
@@ -91,14 +91,14 @@ public class NovaComputeService extends BaseComputeService {
                startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
                nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
                persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension);
-      this.cleanupServer = checkNotNull(cleanupServer, "cleanupServer");
+      this.cleanupResources = checkNotNull(cleanupResources, "cleanupResources");
 
    }
 
    @Override
    protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
       for (NodeMetadata deadNode : deadNodes) {
-         cleanupServer.apply(deadNode.getId());
+         cleanupResources.apply(deadNode);
       }
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
index 9864d17..1149e94 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
@@ -22,9 +22,11 @@ import static com.google.common.collect.Iterables.contains;
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.transform;
 import static java.lang.String.format;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
 import static org.jclouds.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
-import static org.jclouds.util.Predicates2.retry;
+
+import java.util.Map;
 import java.util.Set;
 
 import javax.annotation.Resource;
@@ -39,32 +41,30 @@ import org.jclouds.domain.LoginCredentials;
 import org.jclouds.location.Region;
 import org.jclouds.logging.Logger;
 import org.jclouds.openstack.nova.v2_0.NovaApi;
-import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupServer;
+import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupResources;
 import org.jclouds.openstack.nova.v2_0.compute.functions.RemoveFloatingIpFromNodeAndDeallocate;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet;
 import org.jclouds.openstack.nova.v2_0.domain.Flavor;
 import org.jclouds.openstack.nova.v2_0.domain.Image;
-import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
 import org.jclouds.openstack.nova.v2_0.domain.RebootType;
 import org.jclouds.openstack.nova.v2_0.domain.Server;
 import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.FlavorInRegion;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.ImageInRegion;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.ServerInRegion;
 import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
 import org.jclouds.openstack.nova.v2_0.predicates.ImagePredicates;
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
-import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.collect.Sets;
 
 /**
  * The adapter used by the NovaComputeServiceContextModule to interface the nova-specific domain
@@ -80,20 +80,23 @@ public class NovaComputeServiceAdapter implements
    protected final NovaApi novaApi;
    protected final Supplier<Set<String>> regionIds;
    protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate;
-   protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
-   protected final CleanupServer cleanupServer;
-
+   private final Predicate<RegionAndId> serverRunningPredicate;
+   private final Predicate<RegionAndId> serverTerminatedPredicate;
+   private final CleanupResources cleanupResources;
 
    @Inject
    public NovaComputeServiceAdapter(NovaApi novaApi, @Region Supplier<Set<String>> regionIds,
-            RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
-            LoadingCache<RegionAndName, KeyPair> keyPairCache, CleanupServer cleanupServer) {
+                                    RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
+                                    @Named(TIMEOUT_NODE_RUNNING) Predicate<RegionAndId> serverRunningPredicate,
+                                    @Named(TIMEOUT_NODE_TERMINATED) Predicate<RegionAndId> serverTerminatedPredicate,
+                                    CleanupResources cleanupResources) {
       this.novaApi = checkNotNull(novaApi, "novaApi");
       this.regionIds = checkNotNull(regionIds, "regionIds");
       this.removeFloatingIpFromNodeAndDeallocate = checkNotNull(removeFloatingIpFromNodeAndDeallocate,
                "removeFloatingIpFromNodeAndDeallocate");
-      this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
-      this.cleanupServer = checkNotNull(cleanupServer, "cleanupServer");
+      this.serverRunningPredicate = serverRunningPredicate;
+      this.serverTerminatedPredicate = serverTerminatedPredicate;
+      this.cleanupResources = cleanupResources;
    }
 
    /**
@@ -104,18 +107,20 @@ public class NovaComputeServiceAdapter implements
    @Override
    public NodeAndInitialCredentials<ServerInRegion> createNodeWithGroupEncodedIntoName(String group, String name,
             Template template) {
-
-      LoginCredentials.Builder credentialsBuilder = LoginCredentials.builder();
+      final String regionId = template.getLocation().getId();
+      String imageId = template.getImage().getProviderId();
+      String flavorId = template.getHardware().getProviderId();
       NovaTemplateOptions templateOptions = template.getOptions().as(NovaTemplateOptions.class);
 
       CreateServerOptions options = new CreateServerOptions();
-      options.metadata(metadataAndTagsAsCommaDelimitedValue(template.getOptions()));
-      if (!templateOptions.getGroups().isEmpty())
-         options.securityGroupNames(templateOptions.getGroups());
+      Map<String, String> metadataAndTagsAsCommaDelimitedValue = metadataAndTagsAsCommaDelimitedValue(template.getOptions());
+      options.metadata(metadataAndTagsAsCommaDelimitedValue);
+      if (!templateOptions.getGroups().isEmpty()) options.securityGroupNames(templateOptions.getGroups());
       options.userData(templateOptions.getUserData());
       options.diskConfig(templateOptions.getDiskConfig());
       options.configDrive(templateOptions.getConfigDrive());
       options.availabilityZone(templateOptions.getAvailabilityZone());
+      
       if (templateOptions.getNovaNetworks() != null) {
          options.novaNetworks(templateOptions.getNovaNetworks());
       }
@@ -123,41 +128,32 @@ public class NovaComputeServiceAdapter implements
          options.networks(templateOptions.getNetworks());
       }
 
-      Optional<String> privateKey = Optional.absent();
       if (templateOptions.getKeyPairName() != null) {
          options.keyPairName(templateOptions.getKeyPairName());
-         KeyPair keyPair = keyPairCache.getIfPresent(RegionAndName.fromRegionAndName(template.getLocation().getId(), templateOptions.getKeyPairName()));
-         if (keyPair != null && keyPair.getPrivateKey() != null) {
-            privateKey = Optional.of(keyPair.getPrivateKey());
-            credentialsBuilder.privateKey(privateKey.get());
-         }
       }
-
-
-      final String regionId = template.getLocation().getId();
-      String imageId = template.getImage().getProviderId();
-      String flavorId = template.getHardware().getProviderId();
-
+      
       logger.debug(">> creating new server region(%s) name(%s) image(%s) flavor(%s) options(%s)", regionId, name, imageId, flavorId, options);
       final ServerCreated lightweightServer = novaApi.getServerApi(regionId).create(name, imageId, flavorId, options);
-      if (!retry(new Predicate<String>() {
-         @Override
-         public boolean apply(String serverId) {
-            Server server = novaApi.getServerApi(regionId).get(serverId);
-            return server != null && server.getAddresses() != null && !server.getAddresses().isEmpty();
-         }
-      }, 30 * 60, 1, SECONDS).apply(lightweightServer.getId())) {
-         final String message = format("Server %s was not created within %sms so it will be destroyed.", name, "30 * 60");
+      if (!serverRunningPredicate.apply(RegionAndId.fromRegionAndId(regionId, lightweightServer.getId()))) {
+         final String message = format("Server %s was not created within %sms. The resources created for it will be destroyed", name, "30 * 60");
          logger.warn(message);
-         destroyNode(RegionAndId.fromRegionAndId(regionId, lightweightServer.getId()).slashEncode());
+         String tagString = metadataAndTagsAsCommaDelimitedValue.get("jclouds_tags");
+         Set<String> tags = Sets.newHashSet(Splitter.on(',').split(tagString));
+         cleanupResources.removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionId, tags);
          throw new IllegalStateException(message);
       }
       logger.trace("<< server(%s)", lightweightServer.getId());
 
       Server server = novaApi.getServerApi(regionId).get(lightweightServer.getId());
       ServerInRegion serverInRegion = new ServerInRegion(server, regionId);
-      if (!privateKey.isPresent() && lightweightServer.getAdminPass().isPresent())
+
+      LoginCredentials.Builder credentialsBuilder = LoginCredentials.builder();
+      if (templateOptions.getLoginPrivateKey() != null) {
+         credentialsBuilder.privateKey(templateOptions.getLoginPrivateKey());
+      } 
+      if (lightweightServer.getAdminPass().isPresent()) {
          credentialsBuilder.password(lightweightServer.getAdminPass().get());
+      }
       return new NodeAndInitialCredentials<ServerInRegion>(serverInRegion, serverInRegion.slashEncode(), credentialsBuilder
                .build());
    }
@@ -266,7 +262,9 @@ public class NovaComputeServiceAdapter implements
 
    @Override
    public void destroyNode(String id) {
-      checkState(cleanupServer.apply(id), "server(%s) still there after deleting!?", id);
+      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+      novaApi.getServerApi(regionAndId.getRegion()).delete(regionAndId.getId());
+      checkState(serverTerminatedPredicate.apply(regionAndId), "server was not destroyed in the configured timeout");
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
index cead92b..700a39b 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
@@ -16,7 +16,10 @@
  */
 package org.jclouds.openstack.nova.v2_0.compute.config;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
 import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.AUTO_ALLOCATE_FLOATING_IPS;
 import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.AUTO_GENERATE_KEYPAIRS;
 import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.TIMEOUT_SECURITYGROUP_PRESENT;
@@ -42,15 +45,17 @@ import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.extensions.ImageExtension;
 import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LoginCredentials;
 import org.jclouds.functions.IdentityFunction;
+import org.jclouds.openstack.nova.v2_0.NovaApi;
 import org.jclouds.openstack.nova.v2_0.compute.NovaComputeService;
 import org.jclouds.openstack.nova.v2_0.compute.NovaComputeServiceAdapter;
 import org.jclouds.openstack.nova.v2_0.compute.extensions.NovaImageExtension;
 import org.jclouds.openstack.nova.v2_0.compute.extensions.NovaSecurityGroupExtension;
-import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupServer;
+import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupResources;
 import org.jclouds.openstack.nova.v2_0.compute.functions.CreateSecurityGroupIfNeeded;
 import org.jclouds.openstack.nova.v2_0.compute.functions.FlavorInRegionToHardware;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ImageInRegionToImage;
@@ -58,14 +63,13 @@ import org.jclouds.openstack.nova.v2_0.compute.functions.ImageToOperatingSystem;
 import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupInRegionToSecurityGroup;
 import org.jclouds.openstack.nova.v2_0.compute.functions.OrphanedGroupsByRegionId;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ServerInRegionToNodeMetadata;
-import org.jclouds.openstack.nova.v2_0.compute.loaders.CreateUniqueKeyPair;
 import org.jclouds.openstack.nova.v2_0.compute.loaders.FindSecurityGroupOrCreate;
 import org.jclouds.openstack.nova.v2_0.compute.loaders.LoadFloatingIpsForInstance;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet;
 import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
-import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
 import org.jclouds.openstack.nova.v2_0.domain.Server;
+import org.jclouds.openstack.nova.v2_0.domain.Server.Status;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.FlavorInRegion;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.ImageInRegion;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
@@ -142,17 +146,14 @@ public class NovaComputeServiceContextModule extends
       bind(CreateNodesWithGroupEncodedIntoNameThenAddToSet.class).to(
                ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.class);
 
-      bind(new TypeLiteral<CacheLoader<RegionAndName, KeyPair>>() {
-      }).to(CreateUniqueKeyPair.class);
-
       bind(new TypeLiteral<ImageExtension>() {
       }).to(NovaImageExtension.class);
 
       bind(new TypeLiteral<SecurityGroupExtension>() {
       }).to(NovaSecurityGroupExtension.class);
 
-      bind(new TypeLiteral<Function<String, Boolean>>() {
-      }).to(CleanupServer.class);
+      bind(new TypeLiteral<Function<NodeMetadata, Boolean>>() {
+      }).to(CleanupResources.class);
    }
 
    @Override
@@ -165,6 +166,23 @@ public class NovaComputeServiceContextModule extends
    }
 
    @Provides
+   @com.google.inject.name.Named(TIMEOUT_NODE_RUNNING)
+   protected Predicate<RegionAndId> provideServerRunningPredicate(final NovaApi api,
+                                                             ComputeServiceConstants.Timeouts timeouts,
+                                                             ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new ServerInStatusPredicate(api, Status.ACTIVE), timeouts.nodeRunning,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @com.google.inject.name.Named(TIMEOUT_NODE_TERMINATED)
+   protected Predicate<RegionAndId> provideServerTerminatedPredicate(final NovaApi api, ComputeServiceConstants.Timeouts timeouts,
+                                                                ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new ServerTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
+              pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
    @Singleton
    @Named("FLOATINGIP")
    protected final LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> instanceToFloatingIps(
@@ -196,13 +214,6 @@ public class NovaComputeServiceContextModule extends
 
    @Provides
    @Singleton
-   protected final LoadingCache<RegionAndName, KeyPair> keyPairMap(
-         CacheLoader<RegionAndName, KeyPair> in) {
-      return CacheBuilder.newBuilder().build(in);
-   }
-
-   @Provides
-   @Singleton
    protected final Supplier<Map<String, Location>> createLocationIndexedById(
             @Memoized Supplier<Set<? extends Location>> locations) {
       return Suppliers.compose(new Function<Set<? extends Location>, Map<String, Location>>() {
@@ -226,35 +237,35 @@ public class NovaComputeServiceContextModule extends
    }
 
    @VisibleForTesting
-   public static final Map<Server.Status, NodeMetadata.Status> toPortableNodeStatus = ImmutableMap
-            .<Server.Status, NodeMetadata.Status> builder()
-            .put(Server.Status.ACTIVE, NodeMetadata.Status.RUNNING)
-            .put(Server.Status.BUILD, NodeMetadata.Status.PENDING)
-            .put(Server.Status.DELETED, NodeMetadata.Status.TERMINATED)
-            .put(Server.Status.ERROR, NodeMetadata.Status.ERROR)
-            .put(Server.Status.HARD_REBOOT, NodeMetadata.Status.PENDING)
-            .put(Server.Status.MIGRATING, NodeMetadata.Status.PENDING)
-            .put(Server.Status.PASSWORD, NodeMetadata.Status.PENDING)
-            .put(Server.Status.PAUSED, NodeMetadata.Status.SUSPENDED)
-            .put(Server.Status.REBOOT, NodeMetadata.Status.PENDING)
-            .put(Server.Status.REBUILD, NodeMetadata.Status.PENDING)
-            .put(Server.Status.RESCUE, NodeMetadata.Status.PENDING)
-            .put(Server.Status.RESIZE, NodeMetadata.Status.PENDING)
-            .put(Server.Status.REVERT_RESIZE, NodeMetadata.Status.PENDING)
-            .put(Server.Status.SHELVED, NodeMetadata.Status.SUSPENDED)
-            .put(Server.Status.SHELVED_OFFLOADED, NodeMetadata.Status.SUSPENDED)
-            .put(Server.Status.SHUTOFF, NodeMetadata.Status.SUSPENDED)
-            .put(Server.Status.SOFT_DELETED, NodeMetadata.Status.TERMINATED)
-            .put(Server.Status.STOPPED, NodeMetadata.Status.SUSPENDED)
-            .put(Server.Status.SUSPENDED, NodeMetadata.Status.SUSPENDED)
-            .put(Server.Status.UNKNOWN, NodeMetadata.Status.UNRECOGNIZED)
-            .put(Server.Status.UNRECOGNIZED, NodeMetadata.Status.UNRECOGNIZED)
-            .put(Server.Status.VERIFY_RESIZE, NodeMetadata.Status.PENDING)
+   public static final Map<Status, NodeMetadata.Status> toPortableNodeStatus = ImmutableMap
+            .<Status, NodeMetadata.Status> builder()
+            .put(Status.ACTIVE, NodeMetadata.Status.RUNNING)
+            .put(Status.BUILD, NodeMetadata.Status.PENDING)
+            .put(Status.DELETED, NodeMetadata.Status.TERMINATED)
+            .put(Status.ERROR, NodeMetadata.Status.ERROR)
+            .put(Status.HARD_REBOOT, NodeMetadata.Status.PENDING)
+            .put(Status.MIGRATING, NodeMetadata.Status.PENDING)
+            .put(Status.PASSWORD, NodeMetadata.Status.PENDING)
+            .put(Status.PAUSED, NodeMetadata.Status.SUSPENDED)
+            .put(Status.REBOOT, NodeMetadata.Status.PENDING)
+            .put(Status.REBUILD, NodeMetadata.Status.PENDING)
+            .put(Status.RESCUE, NodeMetadata.Status.PENDING)
+            .put(Status.RESIZE, NodeMetadata.Status.PENDING)
+            .put(Status.REVERT_RESIZE, NodeMetadata.Status.PENDING)
+            .put(Status.SHELVED, NodeMetadata.Status.SUSPENDED)
+            .put(Status.SHELVED_OFFLOADED, NodeMetadata.Status.SUSPENDED)
+            .put(Status.SHUTOFF, NodeMetadata.Status.SUSPENDED)
+            .put(Status.SOFT_DELETED, NodeMetadata.Status.TERMINATED)
+            .put(Status.STOPPED, NodeMetadata.Status.SUSPENDED)
+            .put(Status.SUSPENDED, NodeMetadata.Status.SUSPENDED)
+            .put(Status.UNKNOWN, NodeMetadata.Status.UNRECOGNIZED)
+            .put(Status.UNRECOGNIZED, NodeMetadata.Status.UNRECOGNIZED)
+            .put(Status.VERIFY_RESIZE, NodeMetadata.Status.PENDING)
             .build();
 
    @Singleton
    @Provides
-   protected final Map<Server.Status, NodeMetadata.Status> toPortableNodeStatus() {
+   protected final Map<Status, NodeMetadata.Status> toPortableNodeStatus() {
       return toPortableNodeStatus;
    }
 
@@ -268,6 +279,46 @@ public class NovaComputeServiceContextModule extends
             .put(org.jclouds.openstack.nova.v2_0.domain.Image.Status.UNKNOWN, Image.Status.UNRECOGNIZED)
             .put(org.jclouds.openstack.nova.v2_0.domain.Image.Status.UNRECOGNIZED, Image.Status.UNRECOGNIZED).build();
 
+
+   @VisibleForTesting
+   static class ServerInStatusPredicate implements Predicate<RegionAndId> {
+
+      private final NovaApi api;
+      private final Status status;
+
+      public ServerInStatusPredicate(NovaApi api, Status status) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.status = checkNotNull(status, "status must not be null");
+      }
+
+      @Override
+      public boolean apply(RegionAndId regionAndId) {
+         checkNotNull(regionAndId, "regionAndId");
+         Server server = api.getServerApi(regionAndId.getRegion()).get(regionAndId.getId());
+         if (server == null) {
+            throw new IllegalStateException(String.format("Server %s not found.", regionAndId.getId()));
+         }
+         return status.equals(server.getStatus());      
+      }
+   }
+
+   @VisibleForTesting
+   static class ServerTerminatedPredicate implements Predicate<RegionAndId> {
+
+      private final NovaApi api;
+
+      public ServerTerminatedPredicate(NovaApi api) {
+         this.api = checkNotNull(api, "api must not be null");
+      }
+
+      @Override
+      public boolean apply(RegionAndId regionAndId) {
+         checkNotNull(regionAndId, "regionAndId");
+         Server server = api.getServerApi(regionAndId.getRegion()).get(regionAndId.getId());
+         return server == null;
+      }
+   }
+
    @Singleton
    @Provides
    protected final Map<org.jclouds.openstack.nova.v2_0.domain.Image.Status, Image.Status> toPortableImageStatus() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java
index 7689b53..ec7aaf8 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java
@@ -64,15 +64,15 @@ public class AllocateAndAddFloatingIpToNode implements
    private final Predicate<AtomicReference<NodeMetadata>> nodeRunning;
    private final NovaApi novaApi;
    private final LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache;
-   private final CleanupServer cleanupServer;
+   private final CleanupResources cleanupResources;
 
    @Inject
    public AllocateAndAddFloatingIpToNode(@Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
-            NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache, CleanupServer cleanupServer) {
+            NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache, CleanupResources cleanupResources) {
       this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
       this.novaApi = checkNotNull(novaApi, "novaApi");
       this.floatingIpCache = checkNotNull(floatingIpCache, "floatingIpCache");
-      this.cleanupServer = checkNotNull(cleanupServer, "cleanupServer");
+      this.cleanupResources = checkNotNull(cleanupResources, "cleanupResources");
    }
 
    @Override
@@ -86,7 +86,7 @@ public class AllocateAndAddFloatingIpToNode implements
 
       Optional<FloatingIP> ip = allocateFloatingIPForNode(floatingIpApi, poolNames, node.getId());
       if (!ip.isPresent()) {
-         cleanupServer.apply(node.getId());
+         cleanupResources.apply(node);
          throw new InsufficientResourcesException("Failed to allocate a FloatingIP for node(" + node.getId() + ")");
       }
       logger.debug(">> adding floatingIp(%s) to node(%s)", ip.get().getIp(), node.getId());

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
new file mode 100644
index 0000000..9030395
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
@@ -0,0 +1,106 @@
+/*
+ * 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.jclouds.openstack.nova.v2_0.compute.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.JCLOUDS_SG_PREFIX;
+
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+import org.jclouds.openstack.nova.v2_0.NovaApi;
+import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.FluentIterable;
+
+@Singleton
+public class CleanupResources implements Function<NodeMetadata, Boolean> {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   protected final NovaApi novaApi;
+   protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate;
+   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap;
+
+   @Inject
+   public CleanupResources(NovaApi novaApi, RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
+                           LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap) {
+
+      this.novaApi = novaApi;
+      this.removeFloatingIpFromNodeAndDeallocate = removeFloatingIpFromNodeAndDeallocate;
+      this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
+   }
+
+   @Override
+   public Boolean apply(NodeMetadata node) {
+      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(node.getId());
+      removeFloatingIpFromNodeifAny(regionAndId);
+      return removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionAndId.getRegion(), node.getTags());
+   }
+
+   public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(String regionId, Set<String> tags) {
+      String securityGroupIdCreatedByJclouds = getSecurityGroupIdCreatedByJclouds(tags);
+      if (securityGroupIdCreatedByJclouds != null) {
+         SecurityGroup securityGroup = novaApi.getSecurityGroupApi(regionId).get().get(securityGroupIdCreatedByJclouds);
+         RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionId, securityGroup.getName());
+         logger.debug(">> deleting securityGroup(%s)", regionAndName);
+         novaApi.getSecurityGroupApi(regionId).get().delete(securityGroupIdCreatedByJclouds);
+         securityGroupMap.invalidate(regionAndName);
+         logger.debug("<< deleted securityGroup(%s)", regionAndName);
+         return true;
+      }
+      return false;
+   }
+
+   private void removeFloatingIpFromNodeifAny(RegionAndId regionAndId) {
+      try {
+         removeFloatingIpFromNodeAndDeallocate.apply(regionAndId);
+      } catch (RuntimeException e) {
+         logger.warn(e, "<< error removing and deallocating ip from node(%s): %s", regionAndId, e.getMessage());
+      }
+   }
+   
+   private String getSecurityGroupIdCreatedByJclouds(Set<String> tags) {
+      return FluentIterable.from(tags).filter(new Predicate<String>() {
+         @Override
+         public boolean apply(String input) {
+            return input.startsWith(JCLOUDS_SG_PREFIX);
+         }
+      }).transform(new Function<String, String>() {
+         @Override
+         public String apply(String input) {
+            return input.substring(JCLOUDS_SG_PREFIX.length() + 1);
+         }
+      }).first().orNull();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupServer.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupServer.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupServer.java
deleted file mode 100644
index 5a922e8..0000000
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupServer.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.jclouds.openstack.nova.v2_0.compute.functions;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.JCLOUDS_KP;
-import static org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.JCLOUDS_SG;
-
-import javax.annotation.Resource;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.jclouds.compute.reference.ComputeServiceConstants;
-import org.jclouds.logging.Logger;
-import org.jclouds.openstack.nova.v2_0.NovaApi;
-import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
-import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
-import org.jclouds.openstack.nova.v2_0.domain.Server;
-import org.jclouds.openstack.nova.v2_0.domain.ServerWithSecurityGroups;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
-
-import com.google.common.base.Function;
-import com.google.common.cache.LoadingCache;
-
-@Singleton
-public class CleanupServer implements Function<String, Boolean> {
-
-   @Resource
-   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
-   protected Logger logger = Logger.NULL;
-
-   protected final NovaApi novaApi;
-   protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate;
-   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap;
-   protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
-
-   @Inject
-   public CleanupServer(NovaApi novaApi, RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
-                        LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap,
-                        LoadingCache<RegionAndName, KeyPair> keyPairCache) {
-      this.novaApi = novaApi;
-      this.removeFloatingIpFromNodeAndDeallocate = removeFloatingIpFromNodeAndDeallocate;
-      this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
-      this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
-
-   }
-
-   @Override
-   public Boolean apply(String id) {
-      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      boolean secGroupsPresent = novaApi.getServerWithSecurityGroupsApi(regionAndId.getRegion()).isPresent();
-      Server server;
-
-      if (secGroupsPresent) {
-         server = novaApi.getServerWithSecurityGroupsApi(regionAndId.getRegion()).get()
-               .get(regionAndId.getId());
-      } else {
-         server = novaApi.getServerApi(regionAndId.getRegion()).get(regionAndId.getId());
-      }
-
-      if (novaApi.getFloatingIPApi(regionAndId.getRegion()).isPresent()) {
-         try {
-            removeFloatingIpFromNodeAndDeallocate.apply(regionAndId);
-         } catch (RuntimeException e) {
-            logger.warn(e, "<< error removing and deallocating ip from node(%s): %s", id, e.getMessage());
-         }
-      }
-
-      if (containsMetadata(server, JCLOUDS_KP)) {
-        if (novaApi.getKeyPairApi(regionAndId.getRegion()).isPresent()) {
-           RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionAndId.getRegion(), server.getKeyName());
-           logger.debug(">> deleting keypair(%s)", regionAndName);
-           novaApi.getKeyPairApi(regionAndId.getRegion()).get().delete(server.getKeyName());
-           // TODO: test this clear happens
-           keyPairCache.invalidate(regionAndName);
-           logger.debug("<< deleted keypair(%s)", regionAndName);
-        }
-      }
-
-      boolean serverDeleted = novaApi.getServerApi(regionAndId.getRegion()).delete(regionAndId.getId());
-
-      if (containsMetadata(server, JCLOUDS_SG) && secGroupsPresent) {
-         for (final String securityGroupName : ((ServerWithSecurityGroups)server).getSecurityGroupNames()) {
-            for (SecurityGroup securityGroup : novaApi.getSecurityGroupApi(regionAndId.getRegion()).get().list().toList()) {
-               if (securityGroup.getName().equalsIgnoreCase(securityGroupName)) {
-                  if (novaApi.getSecurityGroupApi(regionAndId.getRegion()).isPresent()) {
-                     RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionAndId.getRegion(), securityGroup.getName());
-                     logger.debug(">> deleting securityGroup(%s)", regionAndName);
-                     novaApi.getSecurityGroupApi(regionAndId.getRegion()).get().delete(securityGroup.getId());
-                     // TODO: test this clear happens
-                     securityGroupMap.invalidate(regionAndName);
-                     logger.debug("<< deleted securityGroup(%s)", regionAndName);
-                  }
-               }
-            }
-         }
-      }
-      return serverDeleted;
-   }
-
-   private boolean containsMetadata(Server server, String key) {
-      if (server == null || server.getMetadata() == null || server.getMetadata().get("jclouds_tags") == null)
-         return false;
-      return server.getMetadata().get("jclouds_tags").contains(key);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/CreateUniqueKeyPair.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/CreateUniqueKeyPair.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/CreateUniqueKeyPair.java
deleted file mode 100644
index 9d521c4..0000000
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/CreateUniqueKeyPair.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.jclouds.openstack.nova.v2_0.compute.loaders;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import javax.annotation.Resource;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.jclouds.compute.functions.GroupNamingConvention;
-import org.jclouds.compute.reference.ComputeServiceConstants;
-import org.jclouds.logging.Logger;
-import org.jclouds.openstack.nova.v2_0.NovaApi;
-import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
-import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi;
-
-import com.google.common.base.Optional;
-import com.google.common.cache.CacheLoader;
-import com.google.inject.Inject;
-
-@Singleton
-public class CreateUniqueKeyPair extends CacheLoader<RegionAndName, KeyPair> {
-   @Resource
-   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
-   protected Logger logger = Logger.NULL;
-   protected final NovaApi novaApi;
-   protected final GroupNamingConvention.Factory namingConvention;
-
-   @Inject
-   public CreateUniqueKeyPair(NovaApi novaApi, GroupNamingConvention.Factory namingConvention) {
-      this.novaApi = checkNotNull(novaApi, "novaApi");
-      this.namingConvention = checkNotNull(namingConvention, "namingConvention");
-   }
-
-   @Override
-   public KeyPair load(RegionAndName regionAndName) {
-      String regionId = checkNotNull(regionAndName, "regionAndName").getRegion();
-      String prefix = regionAndName.getName();
-
-      Optional<? extends KeyPairApi> api = novaApi.getKeyPairApi(regionId);
-      checkArgument(api.isPresent(), "Key pairs are required, but the extension is not available in region %s!",
-            regionId);
-
-      logger.debug(">> creating keyPair region(%s) prefix(%s)", regionId, prefix);
-
-      KeyPair keyPair = null;
-      while (keyPair == null) {
-         try {
-            keyPair = api.get().create(namingConvention.createWithoutPrefix().uniqueNameForGroup(prefix));
-         } catch (IllegalStateException e) {
-
-         }
-      }
-
-      logger.debug("<< created keyPair(%s)", keyPair.getName());
-      return keyPair;
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
index d8939ad..2811e05 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
@@ -27,7 +27,9 @@ import java.util.Set;
 
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.domain.LoginCredentials;
+import org.jclouds.openstack.nova.v2_0.NovaApi;
 import org.jclouds.openstack.nova.v2_0.domain.Network;
+import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
 import org.jclouds.scriptbuilder.domain.Statement;
 
 import com.google.common.base.Objects;
@@ -65,8 +67,6 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
          eTo.autoAssignFloatingIp(shouldAutoAssignFloatingIp());
          if (getFloatingIpPoolNames().isPresent())
             eTo.floatingIpPoolNames(getFloatingIpPoolNames().get());
-         if (getSecurityGroupNames().isPresent())
-            eTo.securityGroupNames(getSecurityGroupNames().get());
          eTo.generateKeyPair(shouldGenerateKeyPair());
          eTo.keyPairName(getKeyPairName());
          if (getUserData() != null) {
@@ -134,8 +134,6 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
       return toString;
    }
 
-   public static final NovaTemplateOptions NONE = new NovaTemplateOptions();
-
    /**
     * @see #getFloatingIpPoolNames()
     */
@@ -178,31 +176,9 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
    }
 
    /**
-    *
-    * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames
-    * @deprecated Use @link {@link TemplateOptions#securityGroups(String...)} instead. To be removed in jclouds 2.0.
-    */
-   @Deprecated
-   public NovaTemplateOptions securityGroupNames(String... securityGroupNames) {
-      return securityGroupNames(ImmutableSet.copyOf(checkNotNull(securityGroupNames, "securityGroupNames")));
-   }
-
-   /**
-    * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames
-    * @deprecated Use {@link TemplateOptions#securityGroups(Iterable)} instead. To be removed in jclouds 2.0.
-    */
-   @Deprecated
-   public NovaTemplateOptions securityGroupNames(Iterable<String> securityGroupNames) {
-      for (String groupName : checkNotNull(securityGroupNames, "securityGroupNames"))
-         checkNotNull(emptyToNull(groupName), "all security groups must be non-empty");
-      securityGroups(securityGroupNames);
-      return this;
-   }
-
-   /**
     * <h3>Note</h3>
     *
-    * This requires that {@link NovaApi#getExtensionForRegion(String)} to return
+    * This requires that {@link NovaApi#getExtensionApi(String)} to return
     * {@link Optional#isPresent present}
     *
     * @return true if auto assignment of a floating ip to each vm is enabled
@@ -242,7 +218,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
    /**
     * <h3>Note</h3>
     *
-    * This requires that {@link NovaApi#getKeyPairExtensionApi(String)} to return
+    * This requires that {@link NovaApi#getKeyPairApi(String)} to return
     * {@link Optional#isPresent present}
     *
     * @return true if auto generation of keypairs is enabled
@@ -251,18 +227,6 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
       return generateKeyPair;
    }
 
-   /**
-    * if unset, generate a default group prefixed with {@link jclouds#} according
-    * to {@link #getInboundPorts()}
-    *
-    * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames
-    * @deprecated Use {@link TemplateOptions#getGroups()} instead. To be removed in jclouds 2.0.
-    */
-   @Deprecated
-   public Optional<Set<String>> getSecurityGroupNames() {
-      return getGroups().isEmpty() ? Optional.<Set<String>>absent() : Optional.of(getGroups());
-   }
-
    public byte[] getUserData() {
       return userData;
    }
@@ -334,22 +298,6 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
          return new NovaTemplateOptions().keyPairName(keyPairName);
       }
 
-      /**
-       * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames
-       */
-      public static NovaTemplateOptions securityGroupNames(String... groupNames) {
-         NovaTemplateOptions options = new NovaTemplateOptions();
-         return NovaTemplateOptions.class.cast(options.securityGroupNames(groupNames));
-      }
-
-      /**
-       * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames
-       */
-      public static NovaTemplateOptions securityGroupNames(Iterable<String> groupNames) {
-         NovaTemplateOptions options = new NovaTemplateOptions();
-         return NovaTemplateOptions.class.cast(options.securityGroupNames(groupNames));
-      }
-
       // methods that only facilitate returning the correct object type
 
       /**

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
index ca02357..673781c 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
@@ -18,7 +18,6 @@ package org.jclouds.openstack.nova.v2_0.compute.strategy;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
 
 import java.util.List;
 import java.util.Map;
@@ -35,7 +34,6 @@ import org.jclouds.compute.config.CustomizationResponse;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.functions.GroupNamingConvention;
-import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
 import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
 import org.jclouds.compute.strategy.ListNodesStrategy;
@@ -50,12 +48,14 @@ import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNa
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
 
 import com.google.common.base.Function;
+import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Multimap;
 import com.google.common.primitives.Ints;
 import com.google.common.util.concurrent.Atomics;
+import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
@@ -64,12 +64,10 @@ import com.google.common.util.concurrent.ListeningExecutorService;
 public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet extends
          CreateNodesWithGroupEncodedIntoNameThenAddToSet {
 
-   public static final String JCLOUDS_SG = "jclouds_securityGroup";
-   public static final String JCLOUDS_KP = "jclouds_keyPair";
+   public static final String JCLOUDS_SG_PREFIX = "jclouds_sg";
 
    private final AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode;
    protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache;
-   protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
    protected final NovaApi novaApi;
 
    @Inject
@@ -80,12 +78,10 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
             CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
             @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
             AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode,
-            LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache,
-            LoadingCache<RegionAndName, KeyPair> keyPairCache, NovaApi novaApi) {
+            LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache, NovaApi novaApi) {
       super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, userExecutor,
                customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
       this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
-      this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
       this.createAndAddFloatingIpToNode = checkNotNull(createAndAddFloatingIpToNode,
                "createAndAddFloatingIpToNode");
       this.novaApi = checkNotNull(novaApi, "novaApi");
@@ -95,63 +91,78 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
    public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes,
             Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
 
-      Template mutableTemplate = template.clone();
-
-      NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(mutableTemplate.getOptions());
-
-      assert template.getOptions().equals(templateOptions) : "options didn't clone properly";
-
-      String region = mutableTemplate.getLocation().getId();
-      ImmutableList.Builder<String> tagsBuilder = ImmutableList.builder();
+      NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions());
+      String region = template.getLocation().getId();
 
       if (templateOptions.shouldAutoAssignFloatingIp()) {
          checkArgument(novaApi.getFloatingIPApi(region).isPresent(),
                  "Floating IPs are required by options, but the extension is not available! options: %s",
                  templateOptions);
       }
+      if (templateOptions.shouldGenerateKeyPair() || templateOptions.getKeyPairName() != null) {
+         checkArgument(novaApi.getKeyPairApi(region).isPresent(),
+                 "Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
+      }
 
-      boolean keyPairExtensionPresent = novaApi.getKeyPairApi(region).isPresent();
+      List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
+      if (!templateOptions.getGroups().isEmpty() || !inboundPorts.isEmpty()) {
+         checkArgument(novaApi.getSecurityGroupApi(region).isPresent(),
+                 "Security groups are required by options, but the extension is not available! options: %s",
+                 templateOptions);
+      }
+      
+      KeyPair keyPair = null;
       if (templateOptions.shouldGenerateKeyPair()) {
-         checkArgument(keyPairExtensionPresent,
-                  "Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
-         KeyPair keyPair = keyPairCache.getUnchecked(RegionAndName.fromRegionAndName(region, namingConvention.create()
-                  .sharedNameForGroup(group)));
-         keyPairCache.asMap().put(RegionAndName.fromRegionAndName(region, keyPair.getName()), keyPair);
-         templateOptions.keyPairName(keyPair.getName());
-         tagsBuilder.add(JCLOUDS_KP);
-      } else if (templateOptions.getKeyPairName() != null) {
-         checkArgument(keyPairExtensionPresent,
-                  "Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
-         if (templateOptions.getLoginPrivateKey() != null) {
-            String pem = templateOptions.getLoginPrivateKey();
-            KeyPair keyPair = KeyPair.builder().name(templateOptions.getKeyPairName())
-                     .fingerprint(fingerprintPrivateKey(pem)).privateKey(pem).build();
-            keyPairCache.asMap().put(RegionAndName.fromRegionAndName(region, keyPair.getName()), keyPair);
+         keyPair = generateKeyPair(region, namingConvention.create().sharedNameForGroup(group));
+         // If a private key has not been explicitly set, configure the auto-generated one
+         if (Strings.isNullOrEmpty(templateOptions.getLoginPrivateKey())) {
+            templateOptions.overrideLoginPrivateKey(keyPair.getPrivateKey());
          }
+      } else if (templateOptions.getKeyPairName() != null) {
+         keyPair = checkNotNull(novaApi.getKeyPairApi(region).get().get(templateOptions.getKeyPairName()), 
+                     "keypair %s doesn't exist", templateOptions.getKeyPairName());
+      }
+      if (keyPair != null) {
+         templateOptions.keyPairName(keyPair.getName());
       }
 
-      boolean securityGroupExtensionPresent = novaApi.getSecurityGroupApi(region).isPresent();
-      List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
+      ImmutableList.Builder<String> tagsBuilder = ImmutableList.builder();
+
       if (!templateOptions.getGroups().isEmpty()) {
-         checkArgument(securityGroupExtensionPresent,
-                  "Security groups are required by options, but the extension is not available! options: %s",
-                  templateOptions);
-      } else if (securityGroupExtensionPresent) {
-         if (templateOptions.getGroups().isEmpty() && !inboundPorts.isEmpty()) {
-            String securityGroupName = namingConvention.create().sharedNameForGroup(group);
-            try {
-               securityGroupCache.get(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts));
-            } catch (ExecutionException e) {
-               throw Throwables.propagate(e.getCause());
-            }
-            templateOptions.securityGroups(securityGroupName);
-            tagsBuilder.add(JCLOUDS_SG);
+         for (String securityGroupName : templateOptions.getGroups()) {
+            checkNotNull(novaApi.getSecurityGroupApi(region).get().get(securityGroupName), "security group %s doesn't exist", securityGroupName);   
          }
       }
-      templateOptions.userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, group);
+      else if (!inboundPorts.isEmpty()) {
+         SecurityGroupInRegion securityGroupInRegion;
+         String securityGroupName = namingConvention.create().sharedNameForGroup(group);
+         try {
+            securityGroupInRegion = securityGroupCache.get(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts));
+         } catch (ExecutionException e) {
+            throw Throwables.propagate(e.getCause());
+         }
+         templateOptions.securityGroups(securityGroupName);
+         tagsBuilder.add(String.format("%s-%s", JCLOUDS_SG_PREFIX, securityGroupInRegion.getSecurityGroup().getId()));
+      }
       templateOptions.tags(tagsBuilder.build());
 
-      return super.execute(group, count, mutableTemplate, goodNodes, badNodes, customizationResponses);
+      Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
+              customizationResponses);
+
+      // Key pairs in Openstack are only required to create the Server. They aren't used anymore so it is better
+      // to delete the auto-generated key pairs at this point where we know exactly which ones have been
+      // auto-generated by jclouds.
+      if (templateOptions.shouldGenerateKeyPair() && keyPair != null) {
+         registerAutoGeneratedKeyPairCleanupCallbacks(responses, region, keyPair.getName());
+      }
+      return responses;
+   }
+
+   private KeyPair generateKeyPair(String region, String prefix) {
+      logger.debug(">> creating default keypair for node...");
+      KeyPair keyPair = novaApi.getKeyPairApi(region).get().create(namingConvention.createWithoutPrefix().uniqueNameForGroup(prefix));
+      logger.debug(">> keypair created! %s", keyPair.getName());
+      return keyPair;
    }
 
    @Override
@@ -177,4 +188,35 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
       }
    }
 
+   private void registerAutoGeneratedKeyPairCleanupCallbacks(final Map<?, ListenableFuture<Void>> responses,
+                                                             final String region, final String generatedKeyPairName) {
+      // The Futures.allAsList fails immediately if some of the futures fail. The Futures.successfulAsList, however,
+      // returns a list containing the results or 'null' for those futures that failed. We want to wait for all them
+      // (even if they fail), so better use the latter form.
+      ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+
+      // Key pairs must be cleaned up after all futures completed (even if some failed).
+      Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
+         @Override
+         public void onSuccess(List<Void> result) {
+            cleanupAutoGeneratedKeyPair(generatedKeyPairName);
+         }
+
+         @Override
+         public void onFailure(Throwable t) {
+            cleanupAutoGeneratedKeyPair(generatedKeyPairName);
+         }
+
+         private void cleanupAutoGeneratedKeyPair(String keyPairName) {
+            logger.debug(">> cleaning up auto-generated key pairs...");
+               try {
+                  novaApi.getKeyPairApi(region).get().delete(keyPairName);
+               } catch (Exception ex) {
+                  logger.warn(">> could not delete key pair %s: %s", keyPairName, ex.getMessage());
+               }
+         }
+
+      }, userExecutor);
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java
index f9f5362..7dd3472 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java
@@ -367,9 +367,6 @@ public class CreateServerOptions implements MapBinder {
       return securityGroupNames(ImmutableSet.copyOf(checkNotNull(securityGroupNames, "securityGroupNames")));
    }
 
-   /**
-    * @see #getSecurityGroupNames()
-    */
    public CreateServerOptions securityGroupNames(Iterable<String> securityGroupNames) {
       for (String groupName : checkNotNull(securityGroupNames, "securityGroupNames"))
          checkNotNull(emptyToNull(groupName), "all security groups must be non-empty");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java
deleted file mode 100644
index 00da77f..0000000
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.jclouds.openstack.nova.v2_0.predicates;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
-import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
-import static org.jclouds.util.Predicates2.retry;
-
-import org.jclouds.openstack.nova.v2_0.domain.Server;
-import org.jclouds.openstack.nova.v2_0.domain.Server.Status;
-import org.jclouds.openstack.nova.v2_0.features.ServerApi;
-
-import com.google.common.base.Predicate;
-
-/**
- * This class tests to see if a Server or ServerCreated has reached a desired status. This class is most useful when
- * paired with a RetryablePredicate as in the code below. Together these classes can be used to block execution until
- * the Server or ServerCreated has reached that desired status. This is useful when your Server needs to be 100% ready
- * before you can continue with execution.
- * <p/>
- * For example, you can use the factory methods like so.
- * <p/>
- * <pre>
- * {@code
- * ServerCreated serverCreated = serverApi.create("my-server", image.getId(), flavor.getId());
- *
- * if (!ServerPredicates.awaitActive(serverApi).apply(serverCreated.getId())) {
- *     throw new TimeoutException("Timeout on server: " + serverCreated);
- * }
- * </pre>
- *
- * <pre>
- * {@code
- * if (!ServerPredicates.awaitStatus(serverApi, ACTIVE, 300, 2).apply(server.getId())) {
- *   throw new TimeoutException("Timeout on server: " + serverCreated);
- * }
- * </pre>
- */
-public class ServerPredicates {
-   private static final int THIRTY_MINUTES = 600 * 3;
-   private static final int FIVE_SECONDS = 5;
-
-   /**
-    * Waits until a Server is ACTIVE.
-    *
-    * @param serverApi The ServerApi in the region where your Server resides.
-    * @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
-    */
-   public static Predicate<String> awaitActive(ServerApi serverApi) {
-      return awaitStatus(serverApi, ACTIVE, THIRTY_MINUTES, FIVE_SECONDS);
-   }
-
-   /**
-    * Waits until a Server is SHUTOFF.
-    *
-    * @param serverApi The ServerApi in the region where your Server resides.
-    * @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
-    */
-   public static Predicate<String> awaitShutoff(ServerApi serverApi) {
-      return awaitStatus(serverApi, SHUTOFF, THIRTY_MINUTES, FIVE_SECONDS);
-   }
-
-   /**
-    * Waits until a Server reaches Status.
-    *
-    * @param serverApi The ServerApi in the region where your Server resides.
-    * @return Predicate that will check the status every periodInSec seconds for a maximum of maxWaitInSec minutes.
-    */
-   public static Predicate<String> awaitStatus(
-           ServerApi serverApi, Status status, long maxWaitInSec, long periodInSec) {
-      ServerStatusPredicate statusPredicate = new ServerStatusPredicate(serverApi, status);
-
-      return retry(statusPredicate, maxWaitInSec, periodInSec, periodInSec, SECONDS);
-   }
-
-   public static class ServerStatusPredicate implements Predicate<String> {
-      private final ServerApi serverApi;
-      private final Status status;
-
-      public ServerStatusPredicate(ServerApi serverApi, Status status) {
-         this.serverApi = checkNotNull(serverApi, "serverApi must be defined");
-         this.status = checkNotNull(status, "status must be defined");
-      }
-
-      /**
-       * @return boolean Return true when the Server reaches the Status, false otherwise
-       * @throws IllegalStateException if the Server associated with serverId does not exist
-       */
-      @Override
-      public boolean apply(String serverId) {
-         checkNotNull(serverId, "server must be defined");
-
-         Server server = serverApi.get(serverId);
-
-         if (server == null) {
-            throw new IllegalStateException(String.format("Server %s not found.", serverId));
-         }
-
-         return status.equals(server.getStatus());
-      }
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
index 5cb02a7..5528349 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
@@ -32,20 +32,15 @@ import org.jclouds.domain.LoginCredentials;
 import org.jclouds.http.HttpRequest;
 import org.jclouds.http.HttpResponse;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
-import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
 import org.jclouds.openstack.nova.v2_0.domain.Network;
 import org.jclouds.openstack.nova.v2_0.domain.Server;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.ServerInRegion;
 import org.jclouds.openstack.nova.v2_0.internal.BaseNovaComputeServiceContextExpectTest;
 import org.testng.annotations.Test;
 
-import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
 
 /**
  * Tests the compute service abstraction of the nova api.
@@ -239,7 +234,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
       Injector forSecurityGroups = requestsSendResponses(requestResponseMap);
 
       Template template = forSecurityGroups.getInstance(TemplateBuilder.class).build();
-      template.getOptions().as(NovaTemplateOptions.class).securityGroupNames("group1", "group2");
+      template.getOptions().as(NovaTemplateOptions.class).securityGroups("group1", "group2");
 
       NovaComputeServiceAdapter adapter = forSecurityGroups.getInstance(NovaComputeServiceAdapter.class);
 
@@ -281,17 +276,10 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
       Injector forSecurityGroups = requestsSendResponses(requestResponseMap);
 
       Template template = forSecurityGroups.getInstance(TemplateBuilder.class).build();
-      template.getOptions().as(NovaTemplateOptions.class).keyPairName("foo");
+      template.getOptions().as(NovaTemplateOptions.class).keyPairName("foo").overrideLoginPrivateKey("privateKey");
 
       NovaComputeServiceAdapter adapter = forSecurityGroups.getInstance(NovaComputeServiceAdapter.class);
 
-      // we expect to have already an entry in the cache for the key
-      LoadingCache<RegionAndName, KeyPair> keyPairCache = forSecurityGroups.getInstance(Key
-               .get(new TypeLiteral<LoadingCache<RegionAndName, KeyPair>>() {
-               }));
-      keyPairCache.put(RegionAndName.fromRegionAndName("az-1.region-a.geo-1", "foo"), KeyPair.builder().name("foo")
-               .privateKey("privateKey").build());
-
       NodeAndInitialCredentials<ServerInRegion> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
                template);
       assertNotNull(server);
@@ -359,13 +347,16 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
             .statusCode(202)
             .build();
 
+      HttpResponse serverDetailSuspendedResponse = HttpResponse.builder().statusCode(200)
+              .payload(payloadFromResource("/server_details_suspended.json")).build();
+      
       Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
                .put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
                .put(extensionsOfNovaRequest, extensionsOfNovaResponse)
                .put(listDetail, listDetailResponse)
                .put(listFlavorsDetail, listFlavorsDetailResponse)
                .put(suspendServer, suspendServerResponse)
-               .put(serverDetail, serverDetailResponse).build();
+               .put(serverDetail, serverDetailSuspendedResponse).build();
 
       Injector forAdminExtension = requestsSendResponses(requestResponseMap);
 
@@ -394,13 +385,16 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
             .statusCode(202)
             .build();
 
+      HttpResponse serverDetailSuspendedResponse = HttpResponse.builder().statusCode(200)
+              .payload(payloadFromResource("/server_details_suspended.json")).build();
+
       Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
             .put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
             .put(extensionsOfNovaRequest, unmatchedExtensionsOfNovaResponse)
             .put(listDetail, listDetailResponse)
             .put(listFlavorsDetail, listFlavorsDetailResponse)
             .put(suspendServer, suspendServerResponse)
-            .put(serverDetail, serverDetailResponse).build();
+            .put(serverDetail, serverDetailSuspendedResponse).build();
 
       Injector forAdminExtension = requestsSendResponses(requestResponseMap);
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/aa11765b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java
index a163f02..49cc969 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java
@@ -20,7 +20,6 @@ import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
 import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions.Builder.blockUntilRunning;
 import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions.Builder.keyPairName;
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
@@ -174,7 +173,7 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
    HttpResponse securityGroupWithPort22 = HttpResponse.builder().statusCode(200)
          .payload(payloadFromResource("/securitygroup_details_port22.json")).build();
 
-   HttpRequest create = HttpRequest
+   HttpRequest createKeyPair = HttpRequest
          .builder()
          .method("POST")
          .endpoint("https://nova-api.openstack.org:9774/v2/3456/os-keypairs")
@@ -188,6 +187,16 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
    HttpResponse keyPairWithPrivateKey = HttpResponse.builder().statusCode(200)
          .payload(payloadFromResource("/keypair_created_computeservice.json")).build();
 
+   HttpRequest getKeyPair = HttpRequest
+           .builder()
+           .method("GET")
+           .endpoint("https://nova-api.openstack.org:9774/v2/3456/os-keypairs/fooPair")
+           .addHeader("Accept", "application/json")
+           .addHeader("X-Auth-Token", authToken).build();
+
+   HttpResponse keyPairDetails = HttpResponse.builder().statusCode(200)
+           .payload(payloadFromResource("/keypair_details.json")).build();
+   
    HttpRequest serverDetail = HttpRequest
          .builder()
          .method("GET")
@@ -202,18 +211,12 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
    public void testCreateNodeWithGeneratedKeyPair() throws Exception {
       Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
             .putAll(defaultTemplateOpenStack);
+      requestResponseMap.put(createKeyPair, keyPairWithPrivateKey);
       requestResponseMap.put(list, notFound);
-
       requestResponseMap.put(createWithPrefixOnGroup, securityGroupCreated);
-
       requestResponseMap.put(createRuleForDefaultPort22, securityGroupRuleCreated);
-
       requestResponseMap.put(getSecurityGroup, securityGroupWithPort22);
-
-      requestResponseMap.put(create, keyPairWithPrivateKey);
-
-      requestResponseMap.put(serverDetail, serverDetailResponse);
-
+      
       HttpRequest createServerWithGeneratedKeyPair = HttpRequest
             .builder()
             .method("POST")
@@ -222,13 +225,14 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
             .addHeader("X-Auth-Token", authToken)
             .payload(
                   payloadFromStringWithContentType(
-                        "{\"server\":{\"name\":\"test-1\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"metadata\":{\"jclouds-group\":\"test\",\"jclouds_tags\":\"jclouds_keyPair,jclouds_securityGroup\"},\"key_name\":\"jclouds-test-0\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}",
+                        "{\"server\":{\"name\":\"test-1\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"metadata\":{\"jclouds_tags\":\"jclouds_sg-2769\"},\"key_name\":\"jclouds-test-0\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}",
                         "application/json")).build();
 
       HttpResponse createdServer = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
             .payload(payloadFromResourceWithContentType("/new_server.json", "application/json; charset=UTF-8")).build();
 
       requestResponseMap.put(createServerWithGeneratedKeyPair, createdServer);
+      requestResponseMap.put(serverDetail, serverDetailResponse);
 
       ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build(), new AbstractModule() {
 
@@ -260,14 +264,14 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
             .putAll(defaultTemplateOpenStack);
       requestResponseMap.put(list, notFound);
 
+      requestResponseMap.put(getKeyPair, keyPairDetails);
+      
       requestResponseMap.put(createWithPrefixOnGroup, securityGroupCreated);
 
       requestResponseMap.put(createRuleForDefaultPort22, securityGroupRuleCreated);
 
       requestResponseMap.put(getSecurityGroup, securityGroupWithPort22);
 
-      requestResponseMap.put(serverDetail, serverDetailResponse);
-
       HttpRequest createServerWithSuppliedKeyPair = HttpRequest
             .builder()
             .method("POST")
@@ -276,19 +280,20 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
             .addHeader("X-Auth-Token", authToken)
             .payload(
                   payloadFromStringWithContentType(
-                        "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"metadata\":{\"jclouds-group\":\"test\",\"jclouds_tags\":\"jclouds_securityGroup\"},\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}",
+                        "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"metadata\":{\"jclouds_tags\":\"jclouds_sg-2769\"},\"key_name\":\"testkeypair\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}",
                         "application/json")).build();
 
       HttpResponse createdServer = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
             .payload(payloadFromResourceWithContentType("/new_server.json", "application/json; charset=UTF-8")).build();
 
       requestResponseMap.put(createServerWithSuppliedKeyPair, createdServer);
+      requestResponseMap.put(serverDetail, serverDetailResponse);
 
       ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build(), new AbstractModule() {
 
          @Override
          protected void configure() {
-            // predicatable node names
+            // predictable node names
             final AtomicInteger suffix = new AtomicInteger();
             bind(new TypeLiteral<Supplier<String>>() {
             }).toInstance(new Supplier<String>() {
@@ -304,20 +309,37 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
       });
 
       NodeMetadata node = Iterables.getOnlyElement(apiThatCreatesNode.createNodesInGroup("test", 1,
-            keyPairName("fooPair").blockUntilRunning(false)));
+            keyPairName("fooPair").overrideLoginPrivateKey("privateKey").blockUntilRunning(false)));
       // we don't have access to this private key
-      assertFalse(node.getCredentials().getOptionalPrivateKey().isPresent());
+      assertTrue(node.getCredentials().getOptionalPrivateKey().isPresent());
    }
 
-
    @Test
    public void testCreateNodeWhileUserSpecifiesKeyPairAndUserSpecifiedGroups() throws Exception {
       Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
             .putAll(defaultTemplateOpenStack);
       requestResponseMap.put(list, notFound);
 
-      requestResponseMap.put(serverDetail, serverDetailResponse);
+      requestResponseMap.put(getKeyPair, keyPairDetails);
+
+      requestResponseMap.put(createWithPrefixOnGroup, securityGroupCreated);
+
+      requestResponseMap.put(createRuleForDefaultPort22, securityGroupRuleCreated);
 
+      requestResponseMap.put(getSecurityGroup, securityGroupWithPort22);
+
+      HttpRequest getSecurityGroup = HttpRequest
+              .builder()
+              .method("GET")
+              .endpoint("https://nova-api.openstack.org:9774/v2/3456/os-security-groups/mygroup")
+              .addHeader("Accept", "application/json")
+              .addHeader("X-Auth-Token", authToken).build();
+
+      HttpResponse securityGroupWDetails = HttpResponse.builder().statusCode(200)
+              .payload(payloadFromResource("/securitygroup_details_port22.json")).build();
+
+      requestResponseMap.put(getSecurityGroup, securityGroupWDetails);
+      
       HttpRequest createServerWithSuppliedKeyPairAndGroup = HttpRequest
             .builder()
             .method("POST")
@@ -326,20 +348,21 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
             .addHeader("X-Auth-Token", authToken)
             .payload(
                   payloadFromStringWithContentType(
-                        "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\"," +
-                        "\"metadata\":{\"jclouds-group\":\"test\"},\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"mygroup\"}]}}",
+                        "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"key_name\":\"testkeypair\",\"security_groups\":[{\"name\":\"mygroup\"}]}}",
                         "application/json")).build();
 
       HttpResponse createdServer = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
             .payload(payloadFromResourceWithContentType("/new_server.json", "application/json; charset=UTF-8")).build();
 
       requestResponseMap.put(createServerWithSuppliedKeyPairAndGroup, createdServer);
+      
+      requestResponseMap.put(serverDetail, serverDetailResponse);
 
       ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build(), new AbstractModule() {
 
          @Override
          protected void configure() {
-            // predicatable node names
+            // predictable node names
             final AtomicInteger suffix = new AtomicInteger();
             bind(new TypeLiteral<Supplier<String>>() {
             }).toInstance(new Supplier<String>() {
@@ -355,9 +378,9 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe
       });
 
       NodeMetadata node = Iterables.getOnlyElement(apiThatCreatesNode.createNodesInGroup("test", 1,
-            keyPairName("fooPair").securityGroupNames("mygroup").blockUntilRunning(false)));
+            keyPairName("fooPair").securityGroups("mygroup").blockUntilRunning(false)));
       // we don't have access to this private key
-      assertFalse(node.getCredentials().getOptionalPrivateKey().isPresent());
+      assertTrue(!node.getCredentials().getOptionalPrivateKey().isPresent());
    }
 
 }