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")
