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
This commit is contained in:
Andrea Turli 2015-09-18 17:04:48 +02:00
parent 100d1dac6c
commit a4a255fa4a
8 changed files with 166 additions and 97 deletions

View File

@ -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());
}
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

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

View File

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