[JCLOUDS-1318] fix based on nodeTerminatePredicate

- wait for serer deletion, before deleting the security group
- rename cleanupServer to cleanupResources
- remove keyPairCache
- better usage of tags to remove securityGroups created by jclouds
- remove keyPair after the creation of a group
- remove test for create unique keypair
- openstack nova re-adding support for existing keypair
- fix securityGroupApi check
- fix other unit tests
- remove ServerPredicates as it is now duplicated
- remove TemplateOptions.securityGroupNames as deprecated
- address commits for ApplyNovaTemplateOptionsCreatesNodesWithGroupEncodedIntoNameAndAddToSet
- fix testCreateNodeWhileUserSpecifiesKeyPairAndUserSpecifiedGroups
This commit is contained in:
Andrea Turli 2017-07-06 18:18:44 +02:00
parent 0bc935dd57
commit aa11765bee
21 changed files with 551 additions and 848 deletions

View File

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

View File

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

View File

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

View File

@ -64,15 +64,15 @@ public class AllocateAndAddFloatingIpToNode implements
private final Predicate<AtomicReference<NodeMetadata>> nodeRunning;
private final NovaApi novaApi;
private final LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache;
private final CleanupServer cleanupServer;
private final CleanupResources cleanupResources;
@Inject
public AllocateAndAddFloatingIpToNode(@Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache, CleanupServer cleanupServer) {
NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache, CleanupResources cleanupResources) {
this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
this.novaApi = checkNotNull(novaApi, "novaApi");
this.floatingIpCache = checkNotNull(floatingIpCache, "floatingIpCache");
this.cleanupServer = checkNotNull(cleanupServer, "cleanupServer");
this.cleanupResources = checkNotNull(cleanupResources, "cleanupResources");
}
@Override
@ -86,7 +86,7 @@ public class AllocateAndAddFloatingIpToNode implements
Optional<FloatingIP> ip = allocateFloatingIPForNode(floatingIpApi, poolNames, node.getId());
if (!ip.isPresent()) {
cleanupServer.apply(node.getId());
cleanupResources.apply(node);
throw new InsufficientResourcesException("Failed to allocate a FloatingIP for node(" + node.getId() + ")");
}
logger.debug(">> adding floatingIp(%s) to node(%s)", ip.get().getIp(), node.getId());

View File

@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.JCLOUDS_SG_PREFIX;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
@Singleton
public class CleanupResources implements Function<NodeMetadata, Boolean> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected final NovaApi novaApi;
protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate;
protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap;
@Inject
public CleanupResources(NovaApi novaApi, RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap) {
this.novaApi = novaApi;
this.removeFloatingIpFromNodeAndDeallocate = removeFloatingIpFromNodeAndDeallocate;
this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
}
@Override
public Boolean apply(NodeMetadata node) {
final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(node.getId());
removeFloatingIpFromNodeifAny(regionAndId);
return removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionAndId.getRegion(), node.getTags());
}
public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(String regionId, Set<String> tags) {
String securityGroupIdCreatedByJclouds = getSecurityGroupIdCreatedByJclouds(tags);
if (securityGroupIdCreatedByJclouds != null) {
SecurityGroup securityGroup = novaApi.getSecurityGroupApi(regionId).get().get(securityGroupIdCreatedByJclouds);
RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionId, securityGroup.getName());
logger.debug(">> deleting securityGroup(%s)", regionAndName);
novaApi.getSecurityGroupApi(regionId).get().delete(securityGroupIdCreatedByJclouds);
securityGroupMap.invalidate(regionAndName);
logger.debug("<< deleted securityGroup(%s)", regionAndName);
return true;
}
return false;
}
private void removeFloatingIpFromNodeifAny(RegionAndId regionAndId) {
try {
removeFloatingIpFromNodeAndDeallocate.apply(regionAndId);
} catch (RuntimeException e) {
logger.warn(e, "<< error removing and deallocating ip from node(%s): %s", regionAndId, e.getMessage());
}
}
private String getSecurityGroupIdCreatedByJclouds(Set<String> tags) {
return FluentIterable.from(tags).filter(new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.startsWith(JCLOUDS_SG_PREFIX);
}
}).transform(new Function<String, String>() {
@Override
public String apply(String input) {
return input.substring(JCLOUDS_SG_PREFIX.length() + 1);
}
}).first().orNull();
}
}

View File

@ -1,123 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.JCLOUDS_KP;
import static org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.JCLOUDS_SG;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.jclouds.openstack.nova.v2_0.domain.ServerWithSecurityGroups;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
import com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
@Singleton
public class CleanupServer implements Function<String, Boolean> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected final NovaApi novaApi;
protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate;
protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap;
protected final LoadingCache<RegionAndName, KeyPair> keyPairCache;
@Inject
public CleanupServer(NovaApi novaApi, RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap,
LoadingCache<RegionAndName, KeyPair> keyPairCache) {
this.novaApi = novaApi;
this.removeFloatingIpFromNodeAndDeallocate = removeFloatingIpFromNodeAndDeallocate;
this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
}
@Override
public Boolean apply(String id) {
final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
boolean secGroupsPresent = novaApi.getServerWithSecurityGroupsApi(regionAndId.getRegion()).isPresent();
Server server;
if (secGroupsPresent) {
server = novaApi.getServerWithSecurityGroupsApi(regionAndId.getRegion()).get()
.get(regionAndId.getId());
} else {
server = novaApi.getServerApi(regionAndId.getRegion()).get(regionAndId.getId());
}
if (novaApi.getFloatingIPApi(regionAndId.getRegion()).isPresent()) {
try {
removeFloatingIpFromNodeAndDeallocate.apply(regionAndId);
} catch (RuntimeException e) {
logger.warn(e, "<< error removing and deallocating ip from node(%s): %s", id, e.getMessage());
}
}
if (containsMetadata(server, JCLOUDS_KP)) {
if (novaApi.getKeyPairApi(regionAndId.getRegion()).isPresent()) {
RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionAndId.getRegion(), server.getKeyName());
logger.debug(">> deleting keypair(%s)", regionAndName);
novaApi.getKeyPairApi(regionAndId.getRegion()).get().delete(server.getKeyName());
// TODO: test this clear happens
keyPairCache.invalidate(regionAndName);
logger.debug("<< deleted keypair(%s)", regionAndName);
}
}
boolean serverDeleted = novaApi.getServerApi(regionAndId.getRegion()).delete(regionAndId.getId());
if (containsMetadata(server, JCLOUDS_SG) && secGroupsPresent) {
for (final String securityGroupName : ((ServerWithSecurityGroups)server).getSecurityGroupNames()) {
for (SecurityGroup securityGroup : novaApi.getSecurityGroupApi(regionAndId.getRegion()).get().list().toList()) {
if (securityGroup.getName().equalsIgnoreCase(securityGroupName)) {
if (novaApi.getSecurityGroupApi(regionAndId.getRegion()).isPresent()) {
RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionAndId.getRegion(), securityGroup.getName());
logger.debug(">> deleting securityGroup(%s)", regionAndName);
novaApi.getSecurityGroupApi(regionAndId.getRegion()).get().delete(securityGroup.getId());
// TODO: test this clear happens
securityGroupMap.invalidate(regionAndName);
logger.debug("<< deleted securityGroup(%s)", regionAndName);
}
}
}
}
}
return serverDeleted;
}
private boolean containsMetadata(Server server, String key) {
if (server == null || server.getMetadata() == null || server.getMetadata().get("jclouds_tags") == null)
return false;
return server.getMetadata().get("jclouds_tags").contains(key);
}
}

View File

@ -1,76 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.compute.loaders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi;
import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader;
import com.google.inject.Inject;
@Singleton
public class CreateUniqueKeyPair extends CacheLoader<RegionAndName, KeyPair> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected final NovaApi novaApi;
protected final GroupNamingConvention.Factory namingConvention;
@Inject
public CreateUniqueKeyPair(NovaApi novaApi, GroupNamingConvention.Factory namingConvention) {
this.novaApi = checkNotNull(novaApi, "novaApi");
this.namingConvention = checkNotNull(namingConvention, "namingConvention");
}
@Override
public KeyPair load(RegionAndName regionAndName) {
String regionId = checkNotNull(regionAndName, "regionAndName").getRegion();
String prefix = regionAndName.getName();
Optional<? extends KeyPairApi> api = novaApi.getKeyPairApi(regionId);
checkArgument(api.isPresent(), "Key pairs are required, but the extension is not available in region %s!",
regionId);
logger.debug(">> creating keyPair region(%s) prefix(%s)", regionId, prefix);
KeyPair keyPair = null;
while (keyPair == null) {
try {
keyPair = api.get().create(namingConvention.createWithoutPrefix().uniqueNameForGroup(prefix));
} catch (IllegalStateException e) {
}
}
logger.debug("<< created keyPair(%s)", keyPair.getName());
return keyPair;
}
}

View File

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

View File

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

View File

@ -367,9 +367,6 @@ public class CreateServerOptions implements MapBinder {
return securityGroupNames(ImmutableSet.copyOf(checkNotNull(securityGroupNames, "securityGroupNames")));
}
/**
* @see #getSecurityGroupNames()
*/
public CreateServerOptions securityGroupNames(Iterable<String> securityGroupNames) {
for (String groupName : checkNotNull(securityGroupNames, "securityGroupNames"))
checkNotNull(emptyToNull(groupName), "all security groups must be non-empty");

View File

@ -1,118 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.predicates;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
import static org.jclouds.util.Predicates2.retry;
import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.jclouds.openstack.nova.v2_0.domain.Server.Status;
import org.jclouds.openstack.nova.v2_0.features.ServerApi;
import com.google.common.base.Predicate;
/**
* This class tests to see if a Server or ServerCreated has reached a desired status. This class is most useful when
* paired with a RetryablePredicate as in the code below. Together these classes can be used to block execution until
* the Server or ServerCreated has reached that desired status. This is useful when your Server needs to be 100% ready
* before you can continue with execution.
* <p/>
* For example, you can use the factory methods like so.
* <p/>
* <pre>
* {@code
* ServerCreated serverCreated = serverApi.create("my-server", image.getId(), flavor.getId());
*
* if (!ServerPredicates.awaitActive(serverApi).apply(serverCreated.getId())) {
* throw new TimeoutException("Timeout on server: " + serverCreated);
* }
* </pre>
*
* <pre>
* {@code
* if (!ServerPredicates.awaitStatus(serverApi, ACTIVE, 300, 2).apply(server.getId())) {
* throw new TimeoutException("Timeout on server: " + serverCreated);
* }
* </pre>
*/
public class ServerPredicates {
private static final int THIRTY_MINUTES = 600 * 3;
private static final int FIVE_SECONDS = 5;
/**
* Waits until a Server is ACTIVE.
*
* @param serverApi The ServerApi in the region where your Server resides.
* @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
*/
public static Predicate<String> awaitActive(ServerApi serverApi) {
return awaitStatus(serverApi, ACTIVE, THIRTY_MINUTES, FIVE_SECONDS);
}
/**
* Waits until a Server is SHUTOFF.
*
* @param serverApi The ServerApi in the region where your Server resides.
* @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
*/
public static Predicate<String> awaitShutoff(ServerApi serverApi) {
return awaitStatus(serverApi, SHUTOFF, THIRTY_MINUTES, FIVE_SECONDS);
}
/**
* Waits until a Server reaches Status.
*
* @param serverApi The ServerApi in the region where your Server resides.
* @return Predicate that will check the status every periodInSec seconds for a maximum of maxWaitInSec minutes.
*/
public static Predicate<String> awaitStatus(
ServerApi serverApi, Status status, long maxWaitInSec, long periodInSec) {
ServerStatusPredicate statusPredicate = new ServerStatusPredicate(serverApi, status);
return retry(statusPredicate, maxWaitInSec, periodInSec, periodInSec, SECONDS);
}
public static class ServerStatusPredicate implements Predicate<String> {
private final ServerApi serverApi;
private final Status status;
public ServerStatusPredicate(ServerApi serverApi, Status status) {
this.serverApi = checkNotNull(serverApi, "serverApi must be defined");
this.status = checkNotNull(status, "status must be defined");
}
/**
* @return boolean Return true when the Server reaches the Status, false otherwise
* @throws IllegalStateException if the Server associated with serverId does not exist
*/
@Override
public boolean apply(String serverId) {
checkNotNull(serverId, "server must be defined");
Server server = serverApi.get(serverId);
if (server == null) {
throw new IllegalStateException(String.format("Server %s not found.", serverId));
}
return status.equals(server.getStatus());
}
}
}

View File

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

View File

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

View File

@ -1,108 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.compute.loaders;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import java.net.UnknownHostException;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.TypeLiteral;
@Test(groups = "unit", testName = "CreateUniqueKeyPairTest")
public class CreateUniqueKeyPairTest {
@Test
public void testApply() throws UnknownHostException {
final NovaApi api = createMock(NovaApi.class);
KeyPairApi keyApi = createMock(KeyPairApi.class);
KeyPair pair = createMock(KeyPair.class);
Optional optKeyApi = Optional.of(keyApi);
expect(api.getKeyPairApi("region")).andReturn(optKeyApi).atLeastOnce();
expect(keyApi.create("group-1")).andReturn(pair);
replay(api, keyApi);
CreateUniqueKeyPair parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(NovaApi.class).toInstance(api);
}
}).getInstance(CreateUniqueKeyPair.class);
assertEquals(parser.load(RegionAndName.fromRegionAndName("region", "group")), pair);
verify(api, keyApi);
}
@Test
public void testApplyWithIllegalStateException() throws UnknownHostException {
final NovaApi api = createMock(NovaApi.class);
KeyPairApi keyApi = createMock(KeyPairApi.class);
@SuppressWarnings("unchecked")
final Supplier<String> uniqueIdSupplier = createMock(Supplier.class);
KeyPair pair = createMock(KeyPair.class);
expect(api.getKeyPairApi("region")).andReturn((Optional) Optional.of(keyApi)).atLeastOnce();
expect(uniqueIdSupplier.get()).andReturn("1");
expect(keyApi.create("group-1")).andThrow(new IllegalStateException());
expect(uniqueIdSupplier.get()).andReturn("2");
expect(keyApi.create("group-2")).andReturn(pair);
replay(api, keyApi, uniqueIdSupplier);
CreateUniqueKeyPair parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(uniqueIdSupplier);
bind(NovaApi.class).toInstance(api);
}
}).getInstance(CreateUniqueKeyPair.class);
assertEquals(parser.load(RegionAndName.fromRegionAndName("region", "group")), pair);
verify(api, keyApi, uniqueIdSupplier);
}
}

View File

@ -22,7 +22,6 @@ import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOption
import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions.Builder.generateKeyPair;
import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions.Builder.inboundPorts;
import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions.Builder.installPrivateKey;
import static org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions.Builder.securityGroupNames;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
@ -31,9 +30,6 @@ import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
/**
* Tests possible uses of NovaTemplateOptions and NovaTemplateOptions.Builder.*
*/
@ -45,52 +41,6 @@ public class NovaTemplateOptionsTest {
assertEquals(options.as(NovaTemplateOptions.class), options);
}
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "all security groups must be non-empty")
public void testsecurityGroupNamesIterableBadFormat() {
NovaTemplateOptions options = new NovaTemplateOptions();
options.securityGroupNames(ImmutableSet.of("group1", ""));
}
@Test
public void testsecurityGroupNamesIterable() {
NovaTemplateOptions options = new NovaTemplateOptions();
options.securityGroupNames(ImmutableSet.of("group1", "group2"));
assertEquals(options.getSecurityGroupNames(), Optional.of(ImmutableSet.of("group1", "group2")));
}
@Test
public void testsecurityGroupNamesIterableStatic() {
NovaTemplateOptions options = securityGroupNames(ImmutableSet.of("group1", "group2"));
assertEquals(options.getSecurityGroupNames(), Optional.of(ImmutableSet.of("group1", "group2")));
}
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "all security groups must be non-empty")
public void testsecurityGroupNamesVarArgsBadFormat() {
NovaTemplateOptions options = new NovaTemplateOptions();
options.securityGroupNames("mygroup", "");
}
@Test
public void testsecurityGroupNamesVarArgs() {
NovaTemplateOptions options = new NovaTemplateOptions();
options.securityGroupNames("group1", "group2");
assertEquals(options.getSecurityGroupNames(), Optional.of(ImmutableSet.of("group1", "group2")));
}
@Test
public void testDefaultGroupsVarArgsEmpty() {
NovaTemplateOptions options = new NovaTemplateOptions();
assertEquals(options.getSecurityGroupNames(), Optional.absent());
}
@Test
public void testsecurityGroupNamesVarArgsStatic() {
NovaTemplateOptions options = securityGroupNames("group1", "group2");
assertEquals(options.getSecurityGroupNames(), Optional.of(ImmutableSet.of("group1", "group2")));
}
@Test
public void testautoAssignFloatingIpDefault() {
NovaTemplateOptions options = new NovaTemplateOptions();

View File

@ -16,9 +16,11 @@
*/
package org.jclouds.openstack.nova.v2_0.features;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import org.jclouds.http.HttpResponseException;
import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping;
import org.jclouds.openstack.nova.v2_0.domain.Network;
@ -34,11 +36,9 @@ import org.jclouds.openstack.v2_0.features.ExtensionApi;
import org.jclouds.openstack.v2_0.predicates.LinkPredicates;
import org.testng.annotations.Test;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitActive;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
/**
* Tests behavior of {@link ServerApi}
@ -120,8 +120,6 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
ServerCreated server = serverApi.create(hostName, imageId(regionId), "1", options);
serverId = server.getId();
awaitActive(serverApi).apply(server.getId());
Server serverCheck = serverApi.get(serverId);
assertEquals(serverCheck.getStatus(), ACTIVE);
} finally {
@ -162,9 +160,6 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
ServerCreated server = serverApi.create(hostName, "", flavorRef, options);
serverId = server.getId();
awaitActive(serverApi).apply(server.getId());
Server serverCheck = serverApi.get(serverId);
assertEquals(serverCheck.getStatus(), ACTIVE);
} finally {
@ -241,8 +236,6 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
ServerCreated server = serverApi.create(hostName, imageId(regionId), flavorId(regionId), options);
awaitActive(serverApi).apply(server.getId());
return serverApi.get(server.getId());
}

View File

@ -16,10 +16,14 @@
*/
package org.jclouds.openstack.nova.v2_0.parse;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Guice;
import com.google.inject.Injector;
import static org.jclouds.openstack.nova.v2_0.domain.Address.createV4;
import static org.jclouds.openstack.nova.v2_0.domain.Address.createV6;
import java.net.URI;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.json.BaseItemParserTest;
import org.jclouds.json.config.GsonModule;
@ -33,12 +37,10 @@ import org.jclouds.openstack.v2_0.domain.Resource;
import org.jclouds.rest.annotations.SelectJson;
import org.testng.annotations.Test;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import java.net.URI;
import static org.jclouds.openstack.nova.v2_0.domain.Address.createV4;
import static org.jclouds.openstack.nova.v2_0.domain.Address.createV6;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Guice;
import com.google.inject.Injector;
@Test(groups = "unit", testName = "ParseServerTest")
public class ParseServerTest extends BaseItemParserTest<Server> {
@ -63,7 +65,7 @@ public class ParseServerTest extends BaseItemParserTest<Server> {
.hostId("e4d909c290d0fb1ca068ffaddf22cbd0")
.accessIPv4("67.23.10.132")
.accessIPv6("::babe:67.23.10.132")
.status(Status.BUILD)
.status(Status.ACTIVE)
.diskConfig(Server.DISK_CONFIG_AUTO)
.availabilityZone("nova")
.image(

View File

@ -1,105 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.predicates;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.features.ServerApi;
import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
import org.testng.annotations.Test;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitActive;
import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitShutoff;
import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitStatus;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
@Test(groups = "unit", testName = "ServerPredicatesMockTest")
public class ServerPredicatesMockTest extends BaseOpenStackMockTest<NovaApi> {
public void testAwaitActive() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
String serverDetailsActive = stringFromResource("/server_details.json").replace("BUILD(scheduling)", ACTIVE.value());
server.enqueue(new MockResponse().setBody(serverDetailsActive));
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ServerApi serverApi = novaApi.getServerApi("RegionOne");
boolean result = awaitActive(serverApi).apply("71752");
assertTrue(result);
assertEquals(server.getRequestCount(), 5);
assertAuthentication(server);
} finally {
server.shutdown();
}
}
public void testAwaitShutoff() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
String serverDetailsShutoff = stringFromResource("/server_details.json").replace("BUILD(scheduling)", SHUTOFF.value());
server.enqueue(new MockResponse().setBody(serverDetailsShutoff));
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ServerApi serverApi = novaApi.getServerApi("RegionOne");
boolean result = awaitShutoff(serverApi).apply("71752");
assertTrue(result);
assertEquals(server.getRequestCount(), 7);
assertAuthentication(server);
} finally {
server.shutdown();
}
}
public void testAwaitTimeout() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
for (int i = 0; i < 20; i++) {
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
}
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ServerApi serverApi = novaApi.getServerApi("RegionOne");
boolean result = awaitStatus(serverApi, ACTIVE, 3, 1).apply("71752");
assertFalse(result);
assertAuthentication(server);
} finally {
server.shutdown();
}
}
}

View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<configuration scan="false">
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds-wire.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<appender name="COMPUTEFILE" class="ch.qos.logback.core.FileAppender">
<file>target/jclouds-compute.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<logger name="org.jclouds">
<level value="DEBUG" />
<appender-ref ref="FILE" />
</logger>
<logger name="jclouds.compute">
<level value="DEBUG" />
<appender-ref ref="COMPUTEFILE" />
</logger>
<logger name="jclouds.wire">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
<logger name="jclouds.headers">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
<root>
<level value="INFO" />
</root>
</configuration>

View File

@ -9,7 +9,7 @@
"hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
"accessIPv4": "67.23.10.132",
"accessIPv6": "::babe:67.23.10.132",
"status": "BUILD(scheduling)",
"status": "ACTIVE",
"progress": 60,
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",

View File

@ -0,0 +1,87 @@
{
"server": {
"id": "71752",
"tenant_id": "1234",
"user_id": "5678",
"name": "sample-f352",
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
"accessIPv4": "67.23.10.132",
"accessIPv6": "::babe:67.23.10.132",
"status": "SUSPENDED",
"progress": 60,
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
"image": {
"id": "52415800-8b69-11e0-9b19-734f6f006e54",
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/images/52415800-8b69-11e0-9b19-734f6f006e54"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f6f006e54"
}
]
},
"flavor": {
"id": "52415800-8b69-11e0-9b19-734f216543fd",
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/flavors/52415800-8b69-11e0-9b19-734f216543fd"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/flavors/52415800-8b69-11e0-9b19-734f216543fd"
}
]
},
"addresses": {
"public": [
{
"version": 4,
"addr": "67.23.10.132"
},
{
"version": 6,
"addr": "::babe:67.23.10.132"
},
{
"version": 4,
"addr": "67.23.10.131"
},
{
"version": 6,
"addr": "::babe:4317:0A83"
}
],
"private": [
{
"version": 4,
"addr": "10.176.42.16"
},
{
"version": 6,
"addr": "::babe:10.176.42.16"
}
]
},
"metadata": {
"Server Label": "Web Head 1",
"Image Version": "2.1"
},
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/servers/71752"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/servers/71752"
}
]
}
}