Repository: jclouds
Updated Branches:
  refs/heads/master 100d1dac6 -> a4a255fa4


improve roll-back strategy when floating-ip are not available

- improve destroyNodes to clean up securityGroups and keyPair created 
explicitly for that node
- refactor clean up server in one place


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

Branch: refs/heads/master
Commit: a4a255fa4ae6c7a1a46aa2a8420b19cd2b7df463
Parents: 100d1da
Author: Andrea Turli <[email protected]>
Authored: Fri Sep 18 17:04:48 2015 +0200
Committer: Andrea Turli <[email protected]>
Committed: Fri Jan 22 11:02:44 2016 +0100

----------------------------------------------------------------------
 .../nova/v2_0/compute/NovaComputeService.java   |  84 ++------------
 .../v2_0/compute/NovaComputeServiceAdapter.java |  20 ++--
 .../config/NovaComputeServiceContextModule.java |   4 +
 .../AllocateAndAddFloatingIpToNode.java         |  13 ++-
 .../v2_0/compute/functions/CleanupServer.java   | 113 +++++++++++++++++++
 ...desWithGroupEncodedIntoNameThenAddToSet.java |  13 ++-
 .../nova/v2_0/handlers/NovaErrorHandler.java    |  10 +-
 .../compute/NovaComputeServiceExpectTest.java   |   6 +-
 8 files changed, 166 insertions(+), 97 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/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 c2161b9..df3f1ca 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
@@ -20,9 +20,6 @@ import static 
com.google.common.base.Preconditions.checkNotNull;
 import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
 import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
 import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
-import static 
org.jclouds.openstack.nova.v2_0.predicates.KeyPairPredicates.nameMatches;
-
-import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
@@ -42,7 +39,6 @@ import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
 import org.jclouds.compute.extensions.SecurityGroupExtension;
-import org.jclouds.compute.functions.GroupNamingConvention;
 import org.jclouds.compute.internal.BaseComputeService;
 import org.jclouds.compute.internal.PersistNodeCredentials;
 import org.jclouds.compute.options.TemplateOptions;
@@ -58,34 +54,18 @@ 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.NovaApi;
+import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupServer;
 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.SecurityGroup;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
-import 
org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
-import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi;
-import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi;
-import org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates;
 import org.jclouds.scriptbuilder.functions.InitAdminAccess;
 
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
-import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
 @Singleton
 public class NovaComputeService extends BaseComputeService {
-   protected final NovaApi novaApi;
-   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> 
securityGroupMap;
-   protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
-   protected final Function<Set<? extends NodeMetadata>, Multimap<String, 
String>> orphanedGroupsByRegionId;
-   protected final GroupNamingConvention.Factory namingConvention;
+   protected final CleanupServer cleanupServer;
 
    @Inject
    protected NovaComputeService(ComputeServiceContext context, Map<String, 
Credentials> credentialStore,
@@ -102,69 +82,23 @@ public class NovaComputeService extends BaseComputeService 
{
             InitializeRunScriptOnNodeOrPlaceInBadMap.Factory 
initScriptRunnerFactory,
             RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess 
initAdminAccess,
             PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
-            @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService 
userExecutor, NovaApi novaApi,
-            LoadingCache<RegionAndName, SecurityGroupInRegion> 
securityGroupMap,
-            LoadingCache<RegionAndName, KeyPair> keyPairCache,
-            Function<Set<? extends NodeMetadata>, Multimap<String, String>> 
orphanedGroupsByRegionId,
-            GroupNamingConvention.Factory namingConvention, 
Optional<ImageExtension> imageExtension,
+            @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService 
userExecutor,
+            CleanupServer cleanupServer,
+            Optional<ImageExtension> imageExtension,
             Optional<SecurityGroupExtension> securityGroupExtension) {
       super(context, credentialStore, images, sizes, locations, 
listNodesStrategy, getImageStrategy,
                getNodeMetadataStrategy, runNodesAndAddToSetStrategy, 
rebootNodeStrategy, destroyNodeStrategy,
                startNodeStrategy, stopNodeStrategy, templateBuilderProvider, 
templateOptionsProvider, nodeRunning,
                nodeTerminated, nodeSuspended, initScriptRunnerFactory, 
initAdminAccess, runScriptOnNodeFactory,
                persistNodeCredentials, timeouts, userExecutor, imageExtension, 
securityGroupExtension);
-      this.novaApi = checkNotNull(novaApi, "novaApi");
-      this.securityGroupMap = checkNotNull(securityGroupMap, 
"securityGroupMap");
-      this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
-      this.orphanedGroupsByRegionId = checkNotNull(orphanedGroupsByRegionId, 
"orphanedGroupsByRegionId");
-      this.namingConvention = checkNotNull(namingConvention, 
"namingConvention");
+      this.cleanupServer = checkNotNull(cleanupServer, "cleanupServer");
+
    }
 
    @Override
    protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends 
NodeMetadata> deadNodes) {
-      Multimap<String, String> regionToRegionAndGroupNames = 
orphanedGroupsByRegionId.apply(deadNodes);
-      for (Map.Entry<String, Collection<String>> entry : 
regionToRegionAndGroupNames.asMap().entrySet()) {
-         cleanOrphanedGroupsInRegion(ImmutableSet.copyOf(entry.getValue()), 
entry.getKey());
-      }
-   }
-
-   protected void cleanOrphanedGroupsInRegion(Set<String> groups, String 
regionId) {
-      cleanupOrphanedSecurityGroupsInRegion(groups, regionId);
-      cleanupOrphanedKeyPairsInRegion(groups, regionId);
-   }
-
-   private void cleanupOrphanedSecurityGroupsInRegion(Set<String> groups, 
String regionId) {
-      Optional<? extends SecurityGroupApi> securityGroupApi = 
novaApi.getSecurityGroupApi(regionId);
-      if (securityGroupApi.isPresent()) {
-         for (String group : groups) {
-            for (SecurityGroup securityGroup : 
Iterables.filter(securityGroupApi.get().list(),
-                     
SecurityGroupPredicates.nameMatches(namingConvention.create().containsGroup(group))))
 {
-               RegionAndName regionAndName = 
RegionAndName.fromRegionAndName(regionId, securityGroup.getName());
-               logger.debug(">> deleting securityGroup(%s)", regionAndName);
-               securityGroupApi.get().delete(securityGroup.getId());
-               // TODO: test this clear happens
-               securityGroupMap.invalidate(regionAndName);
-               logger.debug("<< deleted securityGroup(%s)", regionAndName);
-            }
-         }
-      }
-   }
-
-   private void cleanupOrphanedKeyPairsInRegion(Set<String> groups, String 
regionId) {
-      Optional<? extends KeyPairApi> keyPairApi = 
novaApi.getKeyPairApi(regionId);
-      if (keyPairApi.isPresent()) {
-         for (String group : groups) {
-            for (KeyPair pair : 
keyPairApi.get().list().filter(nameMatches(namingConvention.create().containsGroup(group))))
 {
-               RegionAndName regionAndName = 
RegionAndName.fromRegionAndName(regionId, pair.getName());
-               logger.debug(">> deleting keypair(%s)", regionAndName);
-               keyPairApi.get().delete(pair.getName());
-               // TODO: test this clear happens
-               keyPairCache.invalidate(regionAndName);
-               logger.debug("<< deleted keypair(%s)", regionAndName);
-            }
-            keyPairCache.invalidate(RegionAndName.fromRegionAndName(regionId,
-                     namingConvention.create().sharedNameForGroup(group)));
-         }
+      for (NodeMetadata deadNode : deadNodes) {
+         cleanupServer.apply(deadNode.getId());
       }
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/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 4714d53..08a169f 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
@@ -25,7 +25,6 @@ import static java.lang.String.format;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static 
org.jclouds.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
 import static org.jclouds.util.Predicates2.retry;
-
 import java.util.Set;
 
 import javax.annotation.Resource;
@@ -40,6 +39,7 @@ 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.RemoveFloatingIpFromNodeAndDeallocate;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import 
org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet;
@@ -81,16 +81,19 @@ public class NovaComputeServiceAdapter implements
    protected final Supplier<Set<String>> regionIds;
    protected final RemoveFloatingIpFromNodeAndDeallocate 
removeFloatingIpFromNodeAndDeallocate;
    protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
+   protected final CleanupServer cleanupServer;
+
 
    @Inject
    public NovaComputeServiceAdapter(NovaApi novaApi, @Region 
Supplier<Set<String>> regionIds,
             RemoveFloatingIpFromNodeAndDeallocate 
removeFloatingIpFromNodeAndDeallocate,
-            LoadingCache<RegionAndName, KeyPair> keyPairCache) {
+            LoadingCache<RegionAndName, KeyPair> keyPairCache, CleanupServer 
cleanupServer) {
       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");
    }
 
    /**
@@ -130,6 +133,7 @@ public class NovaComputeServiceAdapter implements
          }
       }
 
+
       final String regionId = template.getLocation().getId();
       String imageId = template.getImage().getProviderId();
       String flavorId = template.getHardware().getProviderId();
@@ -158,7 +162,7 @@ public class NovaComputeServiceAdapter implements
                .build());
    }
 
-   @Override
+  @Override
    public Iterable<FlavorInRegion> listHardwareProfiles() {
       Builder<FlavorInRegion> builder = ImmutableSet.builder();
       for (final String regionId : regionIds.get()) {
@@ -262,15 +266,7 @@ public class NovaComputeServiceAdapter implements
 
    @Override
    public void destroyNode(String id) {
-      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      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());
-         }
-      }
-      
novaApi.getServerApi(regionAndId.getRegion()).delete(regionAndId.getId());
+      checkState(cleanupServer.apply(id), "server(%s) still there after 
deleting!?", id);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/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 338dee7..b38aa46 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
@@ -51,6 +51,7 @@ 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.CreateSecurityGroupIfNeeded;
 import 
org.jclouds.openstack.nova.v2_0.compute.functions.FlavorInRegionToHardware;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ImageInRegionToImage;
@@ -159,6 +160,9 @@ public class NovaComputeServiceContextModule extends
 
       bind(new TypeLiteral<SecurityGroupExtension>() {
       }).to(NovaSecurityGroupExtension.class);
+
+      bind(new TypeLiteral<Function<String, Boolean>>() {
+      }).to(CleanupServer.class);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/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 c1b07dd..36e1050 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
@@ -44,6 +44,7 @@ import com.google.common.base.Objects;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -61,13 +62,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;
 
    @Inject
    public AllocateAndAddFloatingIpToNode(@Named(TIMEOUT_NODE_RUNNING) 
Predicate<AtomicReference<NodeMetadata>> nodeRunning,
-            NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, 
Iterable<? extends FloatingIP>> floatingIpCache) {
+            NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, 
Iterable<? extends FloatingIP>> floatingIpCache, CleanupServer cleanupServer) {
       this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
       this.novaApi = checkNotNull(novaApi, "novaApi");
       this.floatingIpCache = checkNotNull(floatingIpCache, "floatingIpCache");
+      this.cleanupServer = checkNotNull(cleanupServer, "cleanupServer");
    }
 
    @Override
@@ -81,6 +84,7 @@ public class AllocateAndAddFloatingIpToNode implements
 
       Optional<FloatingIP> ip = allocateFloatingIPForNode(floatingIpApi, 
poolNames, node.getId());
       if (!ip.isPresent()) {
+         cleanupServer.apply(node.getId());
          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());
@@ -88,7 +92,7 @@ public class AllocateAndAddFloatingIpToNode implements
       floatingIpApi.addToServer(ip.get().getIp(), node.getProviderId());
 
       
input.get().getNodeMetadata().set(NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip.get().getIp())).build());
-      floatingIpCache.invalidate(RegionAndId.fromSlashEncoded(node.getId()));
+      
floatingIpCache.asMap().putIfAbsent(RegionAndId.fromSlashEncoded(node.getId()), 
ImmutableList.of(ip.get()));
       return input.get().getNodeMetadata();
    }
 
@@ -112,7 +116,7 @@ public class AllocateAndAddFloatingIpToNode implements
                ip = floatingIpApi.allocateFromPool(poolName);
                if (ip != null)
                   return Optional.of(ip);
-            } catch (InsufficientResourcesException ire){
+            } catch (InsufficientResourcesException ire) {
                logger.trace("<< [%s] failed to allocate floating IP from pool 
%s for node(%s)", ire.getMessage(), poolName, nodeID);
             }
          }
@@ -140,6 +144,9 @@ public class AllocateAndAddFloatingIpToNode implements
 
       }));
       // try to prevent multiple parallel launches from choosing the same ip.
+      if (unassignedIps.isEmpty()) {
+         return Optional.absent();
+      }
       Collections.shuffle(unassignedIps);
       ip = Iterables.getLast(unassignedIps);
       return Optional.fromNullable(ip);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/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
new file mode 100644
index 0000000..a12c17f
--- /dev/null
+++ 
b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupServer.java
@@ -0,0 +1,113 @@
+/*
+ * 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.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);
+      ServerWithSecurityGroups server = 
novaApi.getServerWithSecurityGroupsApi(regionAndId.getRegion()).get().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)) {
+         for (final String securityGroupName : 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(ServerWithSecurityGroups 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/a4a255fa/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 9a4a482..ca02357 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
@@ -52,6 +52,7 @@ import 
org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion
 import com.google.common.base.Function;
 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;
@@ -63,6 +64,9 @@ 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";
+
    private final AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode;
    protected final LoadingCache<RegionAndName, SecurityGroupInRegion> 
securityGroupCache;
    protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
@@ -98,11 +102,12 @@ public class 
ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
       assert template.getOptions().equals(templateOptions) : "options didn't 
clone properly";
 
       String region = mutableTemplate.getLocation().getId();
+      ImmutableList.Builder<String> tagsBuilder = ImmutableList.builder();
 
       if (templateOptions.shouldAutoAssignFloatingIp()) {
          checkArgument(novaApi.getFloatingIPApi(region).isPresent(),
-                  "Floating IPs are required by options, but the extension is 
not available! options: %s",
-                  templateOptions);
+                 "Floating IPs are required by options, but the extension is 
not available! options: %s",
+                 templateOptions);
       }
 
       boolean keyPairExtensionPresent = 
novaApi.getKeyPairApi(region).isPresent();
@@ -113,6 +118,7 @@ public class 
ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
                   .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);
@@ -139,9 +145,11 @@ public class 
ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
                throw Throwables.propagate(e.getCause());
             }
             templateOptions.securityGroups(securityGroupName);
+            tagsBuilder.add(JCLOUDS_SG);
          }
       }
       templateOptions.userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, 
group);
+      templateOptions.tags(tagsBuilder.build());
 
       return super.execute(group, count, mutableTemplate, goodNodes, badNodes, 
customizationResponses);
    }
@@ -168,4 +176,5 @@ public class 
ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
          return future;
       }
    }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
 
b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
index 487ce0b..01fd2e8 100644
--- 
a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
+++ 
b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
@@ -97,6 +97,14 @@ public class NovaErrorHandler implements HttpErrorHandler {
                exception = new ResourceNotFoundException(message, exception);
             }
             break;
+         case 500:
+            // this is needed as FloatingIPApi.allocateFromPool returns 500 
when floating ips are over quota
+            if (command.getCurrentRequest().getMethod().equals("POST") &&
+                    message.indexOf("The server has either erred or is 
incapable of performing the requested operation.") != -1 &&
+                    
command.getCurrentRequest().getEndpoint().getPath().indexOf("os-floating-ips") 
!= -1) {
+             exception = new InsufficientResourcesException(message, 
exception);
+           }
+           break;
          case 413:
             if (content == null) {
                exception = new InsufficientResourcesException(message, 
exception);
@@ -111,7 +119,7 @@ public class NovaErrorHandler implements HttpErrorHandler {
     * Build an exception from the response. If it contains the JSON payload 
then
     * that is parsed to create a {@link RetryAfterException}, otherwise a
     * {@link InsufficientResourcesException} is returned
-    * 
+    *
     */
    private Exception parseAndBuildRetryException(String json, String message, 
Exception exception) {
       Set<String> retryFields = ImmutableSet.of("retryAfter", "retryAt");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a4a255fa/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 3ec6102..3fbe817 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
@@ -238,8 +238,7 @@ public class NovaComputeServiceExpectTest extends 
BaseNovaComputeServiceExpectTe
             .addHeader("X-Auth-Token", authToken)
             .payload(
                   payloadFromStringWithContentType(
-                        
"{\"server\":{\"name\":\"test-1\",\"imageRef\":\"14\",\"flavorRef\":\"1\"," +
-                        
"\"metadata\":{\"jclouds-group\":\"test\"},\"key_name\":\"jclouds-test-0\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}",
+                        
"{\"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\"}]}}",
                         "application/json")).build();
 
       HttpResponse createdServer = 
HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
@@ -293,8 +292,7 @@ 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\":\"jclouds-test\"}]}}",
+                        
"{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"metadata\":{\"jclouds-group\":\"test\",\"jclouds_tags\":\"jclouds_securityGroup\"},\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}",
                         "application/json")).build();
 
       HttpResponse createdServer = 
HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")

Reply via email to