diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/binders/IfNotNullBindAvailabilityZoneToFormParam.java b/apis/ec2/src/main/java/org/jclouds/ec2/binders/IfNotNullBindAvailabilityZoneToFormParam.java index 1a2084fc4f..eeb4f03a89 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/binders/IfNotNullBindAvailabilityZoneToFormParam.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/binders/IfNotNullBindAvailabilityZoneToFormParam.java @@ -21,6 +21,7 @@ package org.jclouds.ec2.binders; import static com.google.common.base.Preconditions.checkArgument; +import javax.inject.Inject; import javax.inject.Singleton; import org.jclouds.http.HttpRequest; @@ -34,11 +35,22 @@ import org.jclouds.rest.Binder; */ @Singleton public class IfNotNullBindAvailabilityZoneToFormParam implements Binder { + private final String param; + + @Inject + protected IfNotNullBindAvailabilityZoneToFormParam() { + this("Placement.AvailabilityZone"); + } + + protected IfNotNullBindAvailabilityZoneToFormParam(String param) { + this.param = param; + } + @Override public R bindToRequest(R request, Object input) { if (input != null) { - checkArgument(input instanceof String, "this binder is only valid for AvailabilityZone!"); - return ModifyRequest.addFormParam(request, "Placement.AvailabilityZone", (String) input); + checkArgument(input instanceof String, "this binder is only valid for String!"); + return ModifyRequest.addFormParam(request, param, (String) input); } return request; } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java index 431aab0663..c31c674e48 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java @@ -66,8 +66,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableMultimap.Builder; import com.google.common.collect.ImmutableSet; /** @@ -177,12 +177,12 @@ public class EC2ComputeService extends BaseComputeService { @Override public Set destroyNodesMatching(Predicate filter) { Set deadOnes = super.destroyNodesMatching(filter); - Builder regionGroups = ImmutableMap. builder(); + Builder regionGroups = ImmutableMultimap. builder(); for (NodeMetadata nodeMetadata : deadOnes) { if (nodeMetadata.getGroup() != null) regionGroups.put(AWSUtils.parseHandle(nodeMetadata.getId())[0], nodeMetadata.getGroup()); } - for (Entry regionGroup : regionGroups.build().entrySet()) { + for (Entry regionGroup : regionGroups.build().entries()) { cleanUpIncidentalResources(regionGroup); } return deadOnes; diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java index afa6a5653f..118e5645c3 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java @@ -25,7 +25,6 @@ import static org.jclouds.ec2.reference.EC2Constants.PROPERTY_EC2_AMI_OWNERS; import java.security.SecureRandom; import java.util.Map; -import java.util.concurrent.TimeUnit; import javax.inject.Named; import javax.inject.Singleton; @@ -54,13 +53,10 @@ import org.jclouds.ec2.compute.options.EC2TemplateOptions; import org.jclouds.ec2.domain.InstanceState; import org.jclouds.ec2.domain.KeyPair; import org.jclouds.ec2.domain.RunningInstance; -import org.jclouds.ec2.predicates.InstancePresent; -import org.jclouds.predicates.RetryablePredicate; import org.jclouds.rest.RestContext; import org.jclouds.rest.internal.RestContextImpl; import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; @@ -77,11 +73,11 @@ import com.google.inject.TypeLiteral; public class EC2ComputeServiceDependenciesModule extends AbstractModule { public static final Map instanceToNodeState = ImmutableMap - . builder().put(InstanceState.PENDING, NodeState.PENDING) - .put(InstanceState.RUNNING, NodeState.RUNNING).put(InstanceState.SHUTTING_DOWN, NodeState.PENDING) - .put(InstanceState.TERMINATED, NodeState.TERMINATED).put(InstanceState.STOPPING, NodeState.PENDING) - .put(InstanceState.STOPPED, NodeState.SUSPENDED).put(InstanceState.UNRECOGNIZED, NodeState.UNRECOGNIZED) - .build(); + . builder().put(InstanceState.PENDING, NodeState.PENDING).put( + InstanceState.RUNNING, NodeState.RUNNING).put(InstanceState.SHUTTING_DOWN, NodeState.PENDING).put( + InstanceState.TERMINATED, NodeState.TERMINATED).put(InstanceState.STOPPING, NodeState.PENDING) + .put(InstanceState.STOPPED, NodeState.SUSPENDED).put(InstanceState.UNRECOGNIZED, NodeState.UNRECOGNIZED) + .build(); @Singleton @Provides @@ -89,13 +85,6 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule { return instanceToNodeState; } - @Provides - @Singleton - @Named("PRESENT") - protected Predicate instancePresent(InstancePresent present) { - return new RetryablePredicate(present, 5000, 200, TimeUnit.MILLISECONDS); - } - @Override protected void configure() { bind(TemplateBuilder.class).to(EC2TemplateBuilderImpl.class); diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/RunningInstanceToNodeMetadata.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/RunningInstanceToNodeMetadata.java index 5944293b8f..b2d1200212 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/RunningInstanceToNodeMetadata.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/RunningInstanceToNodeMetadata.java @@ -73,7 +73,7 @@ public class RunningInstanceToNodeMetadata implements Function instanceToNodeState; @Inject - RunningInstanceToNodeMetadata(Map instanceToNodeState, + protected RunningInstanceToNodeMetadata(Map instanceToNodeState, Map credentialStore, Map instanceToImage, @Memoized Supplier> locations, @Memoized Supplier> hardware) { this.locations = checkNotNull(locations, "locations"); @@ -85,13 +85,14 @@ public class RunningInstanceToNodeMetadata implements Function() { diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java index d469ad3605..1f1db3f8b8 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java @@ -89,7 +89,7 @@ public class EC2TemplateOptions extends TemplateOptions implements Cloneable { private String keyPair = null; private boolean noKeyPair; private byte[] userData; - private Set blockDeviceMappings = ImmutableSet.of(); + private ImmutableSet.Builder blockDeviceMappings = ImmutableSet. builder(); public static final EC2TemplateOptions NONE = new EC2TemplateOptions(); @@ -142,80 +142,29 @@ public class EC2TemplateOptions extends TemplateOptions implements Cloneable { return this; } - /** - * Specifies the block device mappings to be used to run the instance - */ public EC2TemplateOptions mapEBSSnapshotToDeviceName(String deviceName, String snapshotId, @Nullable Integer sizeInGib, boolean deleteOnTermination) { - checkNotNull(deviceName, "deviceName cannot be null"); - Preconditions2.checkNotEmpty(deviceName, "deviceName must be non-empty"); - checkNotNull(snapshotId, "snapshotId cannot be null"); - Preconditions2.checkNotEmpty(snapshotId, "snapshotId must be non-empty"); - com.google.common.collect.ImmutableSet.Builder mappings = ImmutableSet - . builder(); - mappings.addAll(blockDeviceMappings); - MapEBSSnapshotToDevice mapping = new MapEBSSnapshotToDevice(deviceName, snapshotId, sizeInGib, - deleteOnTermination); - mappings.add(mapping); - blockDeviceMappings = mappings.build(); + blockDeviceMappings.add(new MapEBSSnapshotToDevice(deviceName, snapshotId, sizeInGib, deleteOnTermination)); return this; } - /** - * Specifies the block device mappings to be used to run the instance - */ public EC2TemplateOptions mapNewVolumeToDeviceName(String deviceName, int sizeInGib, boolean deleteOnTermination) { - checkNotNull(deviceName, "deviceName cannot be null"); - Preconditions2.checkNotEmpty(deviceName, "deviceName must be non-empty"); - - com.google.common.collect.ImmutableSet.Builder mappings = ImmutableSet - . builder(); - mappings.addAll(blockDeviceMappings); - MapNewVolumeToDevice mapping = new MapNewVolumeToDevice(deviceName, sizeInGib, deleteOnTermination); - mappings.add(mapping); - blockDeviceMappings = mappings.build(); + blockDeviceMappings.add(new MapNewVolumeToDevice(deviceName, sizeInGib, deleteOnTermination)); return this; } - /** - * Specifies the block device mappings to be used to run the instance - */ public EC2TemplateOptions mapEphemeralDeviceToDeviceName(String deviceName, String virtualName) { - checkNotNull(deviceName, "deviceName cannot be null"); - Preconditions2.checkNotEmpty(deviceName, "deviceName must be non-empty"); - checkNotNull(virtualName, "virtualName cannot be null"); - Preconditions2.checkNotEmpty(virtualName, "virtualName must be non-empty"); - - com.google.common.collect.ImmutableSet.Builder mappings = ImmutableSet - . builder(); - mappings.addAll(blockDeviceMappings); - MapEphemeralDeviceToDevice mapping = new MapEphemeralDeviceToDevice(deviceName, virtualName); - mappings.add(mapping); - blockDeviceMappings = mappings.build(); + blockDeviceMappings.add(new MapEphemeralDeviceToDevice(deviceName, virtualName)); return this; } - /** - * Specifies the block device mappings to be used to run the instance - */ public EC2TemplateOptions unmapDeviceNamed(String deviceName) { - checkNotNull(deviceName, "deviceName cannot be null"); - Preconditions2.checkNotEmpty(deviceName, "deviceName must be non-empty"); - - com.google.common.collect.ImmutableSet.Builder mappings = ImmutableSet - . builder(); - mappings.addAll(blockDeviceMappings); - UnmapDeviceNamed mapping = new UnmapDeviceNamed(deviceName); - mappings.add(mapping); - blockDeviceMappings = mappings.build(); + blockDeviceMappings.add(new UnmapDeviceNamed(deviceName)); return this; } - /** - * Specifies the block device mappings to be used to run the instance - */ - public EC2TemplateOptions blockDeviceMappings(Set blockDeviceMappings) { - this.blockDeviceMappings = ImmutableSet.copyOf(checkNotNull(blockDeviceMappings, "blockDeviceMappings")); + public EC2TemplateOptions blockDeviceMappings(Iterable blockDeviceMappings) { + this.blockDeviceMappings.addAll(checkNotNull(blockDeviceMappings, "blockDeviceMappings")); return this; } @@ -511,7 +460,7 @@ public class EC2TemplateOptions extends TemplateOptions implements Cloneable { * @return BlockDeviceMapping to use when running the instance or null. */ public Set getBlockDeviceMappings() { - return blockDeviceMappings; + return blockDeviceMappings.build(); } @Override diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/predicates/InstancePresent.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/predicates/InstancePresent.java similarity index 69% rename from apis/ec2/src/main/java/org/jclouds/ec2/predicates/InstancePresent.java rename to apis/ec2/src/main/java/org/jclouds/ec2/compute/predicates/InstancePresent.java index 03d1fc8cff..7ae0b0ed12 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/predicates/InstancePresent.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/predicates/InstancePresent.java @@ -17,7 +17,9 @@ * ==================================================================== */ -package org.jclouds.ec2.predicates; +package org.jclouds.ec2.compute.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; import java.util.NoSuchElementException; @@ -25,7 +27,7 @@ import javax.annotation.Resource; import javax.inject.Singleton; import org.jclouds.ec2.EC2Client; -import org.jclouds.ec2.domain.RunningInstance; +import org.jclouds.ec2.compute.domain.RegionAndName; import org.jclouds.logging.Logger; import org.jclouds.rest.ResourceNotFoundException; @@ -34,13 +36,11 @@ import com.google.common.collect.Iterables; import com.google.inject.Inject; /** - * - * Tests to see if a task succeeds. * * @author Adrian Cole */ @Singleton -public class InstancePresent implements Predicate { +public class InstancePresent implements Predicate { private final EC2Client client; @@ -49,13 +49,13 @@ public class InstancePresent implements Predicate { @Inject public InstancePresent(EC2Client client) { - this.client = client; + this.client = checkNotNull(client, "client"); } - public boolean apply(RunningInstance instance) { - logger.trace("looking for instance %s", instance); + public boolean apply(RegionAndName instance) { + logger.trace("looking for instance %s/%s", instance.getRegion(), instance.getName()); try { - instance = refresh(instance); + refresh(instance); return true; } catch (ResourceNotFoundException e) { return false; @@ -64,8 +64,8 @@ public class InstancePresent implements Predicate { } } - private RunningInstance refresh(RunningInstance instance) { - return Iterables.getOnlyElement(Iterables.getOnlyElement(client.getInstanceServices().describeInstancesInRegion( - instance.getRegion(), instance.getId()))); + protected void refresh(RegionAndName instance) { + Iterables.getOnlyElement(Iterables.getOnlyElement(client.getInstanceServices().describeInstancesInRegion( + instance.getRegion(), instance.getName()))); } } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java index c4eaaddf2d..adf98e6b9b 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java @@ -19,10 +19,10 @@ package org.jclouds.ec2.compute.strategy; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.all; import static com.google.common.collect.Iterables.transform; import static org.jclouds.ec2.compute.util.EC2ComputeUtils.getZoneFromLocationOrNull; -import static org.jclouds.ec2.compute.util.EC2ComputeUtils.instanceToId; import java.util.Map; import java.util.Set; @@ -44,6 +44,8 @@ import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; import org.jclouds.compute.util.ComputeUtils; import org.jclouds.domain.Credentials; import org.jclouds.ec2.EC2Client; +import org.jclouds.ec2.compute.domain.RegionAndName; +import org.jclouds.ec2.compute.predicates.InstancePresent; import org.jclouds.ec2.domain.RunningInstance; import org.jclouds.ec2.options.RunInstancesOptions; import org.jclouds.logging.Logger; @@ -51,7 +53,6 @@ import org.jclouds.logging.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; @@ -76,49 +77,58 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen final Function runningInstanceToNodeMetadata; @VisibleForTesting final ComputeUtils utils; - final Predicate instancePresent; + final InstancePresent instancePresent; final Function instanceToCredentials; final Map credentialStore; final Provider templateBuilderProvider; @Inject - EC2CreateNodesInGroupThenAddToSet( - EC2Client client, - Provider templateBuilderProvider, - CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, - @Named("PRESENT") Predicate instancePresent, - Function runningInstanceToNodeMetadata, - Function instanceToCredentials, Map credentialStore, - ComputeUtils utils) { - this.client = client; - this.templateBuilderProvider = templateBuilderProvider; - this.instancePresent = instancePresent; - this.createKeyPairAndSecurityGroupsAsNeededAndReturncustomize = createKeyPairAndSecurityGroupsAsNeededAndReturncustomize; - this.runningInstanceToNodeMetadata = runningInstanceToNodeMetadata; - this.instanceToCredentials = instanceToCredentials; - this.credentialStore = credentialStore; - this.utils = utils; + protected EC2CreateNodesInGroupThenAddToSet( + EC2Client client, + Provider templateBuilderProvider, + CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, + InstancePresent instancePresent, Function runningInstanceToNodeMetadata, + Function instanceToCredentials, Map credentialStore, + ComputeUtils utils) { + this.client = checkNotNull(client, "client"); + this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider"); + this.instancePresent = checkNotNull(instancePresent, "instancePresent"); + this.createKeyPairAndSecurityGroupsAsNeededAndReturncustomize = checkNotNull( + createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, + "createKeyPairAndSecurityGroupsAsNeededAndReturncustomize"); + this.runningInstanceToNodeMetadata = checkNotNull(runningInstanceToNodeMetadata, "runningInstanceToNodeMetadata"); + this.instanceToCredentials = checkNotNull(instanceToCredentials, "instanceToCredentials"); + this.credentialStore = checkNotNull(credentialStore, "credentialStore"); + this.utils = checkNotNull(utils, "utils"); } + public static Function instanceToRegionAndName = new Function() { + @Override + public RegionAndName apply(RunningInstance from) { + return new RegionAndName(from.getRegion(), from.getId()); + } + }; + @Override public Map> execute(String group, int count, Template template, Set goodNodes, - Map badNodes, Multimap customizationResponses) { + Map badNodes, Multimap customizationResponses) { // ensure we don't mutate the input template template = templateBuilderProvider.get().fromTemplate(template).build(); Iterable started = createKeyPairAndSecurityGroupsAsNeededThenRunInstances(group, - count, template); - Iterable ids = transform(started, instanceToId); + count, template); + + Iterable ids = transform(started, instanceToRegionAndName); String idsString = Joiner.on(',').join(ids); if (Iterables.size(ids) > 0) { logger.debug("<< started instances(%s)", idsString); - all(started, instancePresent); + all(ids, instancePresent); logger.debug("<< present instances(%s)", idsString); populateCredentials(started); } - return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(template.getOptions(), - transform(started, runningInstanceToNodeMetadata), goodNodes, badNodes, customizationResponses); + return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(template.getOptions(), transform(started, + runningInstanceToNodeMetadata), goodNodes, badNodes, customizationResponses); } protected void populateCredentials(Iterable started) { @@ -131,19 +141,20 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen if (credentials != null) for (RunningInstance instance : started) credentialStore.put("node#" + instance.getRegion() + "/" + instance.getId(), credentials); - } // TODO write test for this - @VisibleForTesting - Iterable createKeyPairAndSecurityGroupsAsNeededThenRunInstances(String group, int count, - Template template) { + protected Iterable createKeyPairAndSecurityGroupsAsNeededThenRunInstances(String group, + int count, Template template) { String region = AWSUtils.getRegionFromLocationOrNull(template.getLocation()); String zone = getZoneFromLocationOrNull(template.getLocation()); - RunInstancesOptions instanceOptions = createKeyPairAndSecurityGroupsAsNeededAndReturncustomize.execute(region, - group, template); + group, template); + return createNodesInRegionAndZone(region, zone, count, template, instanceOptions); + } + protected Iterable createNodesInRegionAndZone(String region, String zone, int count, + Template template, RunInstancesOptions instanceOptions) { int countStarted = 0; int tries = 0; Iterable started = ImmutableSet. of(); @@ -151,12 +162,10 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen while (countStarted < count && tries++ < count) { if (logger.isDebugEnabled()) logger.debug(">> running %d instance region(%s) zone(%s) ami(%s) params(%s)", count - countStarted, region, - zone, template.getImage().getProviderId(), instanceOptions.buildFormParameters()); + zone, template.getImage().getProviderId(), instanceOptions.buildFormParameters()); - started = Iterables.concat( - started, - client.getInstanceServices().runInstancesInRegion(region, zone, template.getImage().getProviderId(), 1, - count - countStarted, instanceOptions)); + started = Iterables.concat(started, client.getInstanceServices().runInstancesInRegion(region, zone, + template.getImage().getProviderId(), 1, count - countStarted, instanceOptions)); countStarted = Iterables.size(started); if (countStarted < count) diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2DestroyNodeStrategy.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2DestroyNodeStrategy.java index 59063c7127..c2e807c21f 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2DestroyNodeStrategy.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2DestroyNodeStrategy.java @@ -19,6 +19,8 @@ package org.jclouds.ec2.compute.strategy; +import static com.google.common.base.Preconditions.checkNotNull; + import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; @@ -41,14 +43,13 @@ public class EC2DestroyNodeStrategy implements DestroyNodeStrategy { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; - protected final EC2Client ec2Client; + protected final EC2Client client; protected final GetNodeMetadataStrategy getNode; @Inject - protected EC2DestroyNodeStrategy(EC2Client ec2Client, - GetNodeMetadataStrategy getNodeMetadataStrategy) { - this.ec2Client = ec2Client; - this.getNode = getNodeMetadataStrategy; + protected EC2DestroyNodeStrategy(EC2Client client, GetNodeMetadataStrategy getNode) { + this.client = checkNotNull(client, "client"); + this.getNode = checkNotNull(getNode, "getNode"); } @Override @@ -56,8 +57,11 @@ public class EC2DestroyNodeStrategy implements DestroyNodeStrategy { String[] parts = AWSUtils.parseHandle(id); String region = parts[0]; String instanceId = parts[1]; - ec2Client.getInstanceServices().terminateInstancesInRegion(region, - instanceId); + destroyInstanceInRegion(region, instanceId); return getNode.getNode(id); } + + protected void destroyInstanceInRegion(String region, String instanceId) { + client.getInstanceServices().terminateInstancesInRegion(region, instanceId); + } } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2GetNodeMetadataStrategy.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2GetNodeMetadataStrategy.java index 53a5a75b03..2cb5daf264 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2GetNodeMetadataStrategy.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2GetNodeMetadataStrategy.java @@ -19,6 +19,7 @@ package org.jclouds.ec2.compute.strategy; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getOnlyElement; import java.util.NoSuchElementException; @@ -31,7 +32,6 @@ import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.strategy.GetNodeMetadataStrategy; import org.jclouds.ec2.EC2Client; import org.jclouds.ec2.domain.RunningInstance; -import org.jclouds.ec2.services.InstanceClient; import com.google.common.base.Function; import com.google.common.collect.Iterables; @@ -48,28 +48,27 @@ public class EC2GetNodeMetadataStrategy implements GetNodeMetadataStrategy { @Inject protected EC2GetNodeMetadataStrategy(EC2Client client, - Function runningInstanceToNodeMetadata) { - this.client = client; - this.runningInstanceToNodeMetadata = runningInstanceToNodeMetadata; + Function runningInstanceToNodeMetadata) { + this.client = checkNotNull(client, "client"); + this.runningInstanceToNodeMetadata = checkNotNull(runningInstanceToNodeMetadata, "runningInstanceToNodeMetadata"); } @Override public NodeMetadata getNode(String id) { + checkNotNull(id, "id"); String[] parts = AWSUtils.parseHandle(id); String region = parts[0]; String instanceId = parts[1]; try { - RunningInstance runningInstance = getOnlyElement(getAllRunningInstancesInRegion(client.getInstanceServices(), - region, instanceId)); + RunningInstance runningInstance = getRunningInstanceInRegion(region, instanceId); return runningInstanceToNodeMetadata.apply(runningInstance); } catch (NoSuchElementException e) { return null; } } - public static Iterable getAllRunningInstancesInRegion(InstanceClient client, String region, - String id) { - return Iterables.concat(client.describeInstancesInRegion(region, id)); + public RunningInstance getRunningInstanceInRegion(String region, String id) { + return getOnlyElement(Iterables.concat(client.getInstanceServices().describeInstancesInRegion(region, id))); } } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java index 73d6585cee..79402928e0 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java @@ -19,10 +19,12 @@ package org.jclouds.ec2.compute.strategy; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.and; +import static com.google.common.base.Predicates.notNull; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Sets.newLinkedHashSet; import static org.jclouds.concurrent.FutureIterables.transformParallel; import java.util.Set; @@ -35,19 +37,20 @@ import javax.inject.Named; import javax.inject.Singleton; import org.jclouds.Constants; -import org.jclouds.ec2.EC2AsyncClient; -import org.jclouds.ec2.domain.Reservation; -import org.jclouds.ec2.domain.RunningInstance; import org.jclouds.compute.domain.ComputeMetadata; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.predicates.NodePredicates; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.strategy.ListNodesStrategy; +import org.jclouds.ec2.EC2AsyncClient; +import org.jclouds.ec2.domain.Reservation; +import org.jclouds.ec2.domain.RunningInstance; import org.jclouds.location.Region; import org.jclouds.logging.Logger; import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; /** * @@ -59,19 +62,19 @@ public class EC2ListNodesStrategy implements ListNodesStrategy { @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; - private final EC2AsyncClient client; - private final Set regions; - private final Function runningInstanceToNodeMetadata; - private final ExecutorService executor; + protected final EC2AsyncClient client; + protected final Set regions; + protected final Function runningInstanceToNodeMetadata; + protected final ExecutorService executor; @Inject protected EC2ListNodesStrategy(EC2AsyncClient client, @Region Set regions, - Function runningInstanceToNodeMetadata, - @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) { - this.client = client; - this.regions = regions; - this.runningInstanceToNodeMetadata = runningInstanceToNodeMetadata; - this.executor = executor; + Function runningInstanceToNodeMetadata, + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) { + this.client = checkNotNull(client, "client"); + this.regions = checkNotNull(regions, "regions"); + this.runningInstanceToNodeMetadata = checkNotNull(runningInstanceToNodeMetadata, "runningInstanceToNodeMetadata"); + this.executor = checkNotNull(executor, "executor"); } @Override @@ -81,19 +84,25 @@ public class EC2ListNodesStrategy implements ListNodesStrategy { @Override public Set listDetailsOnNodesMatching(Predicate filter) { + Iterable instances = pollRunningInstances(); + Iterable nodes = filter(transform(filter(instances, notNull()), + runningInstanceToNodeMetadata), and(notNull(), filter)); + return ImmutableSet.copyOf(nodes); + } + + protected Iterable pollRunningInstances() { Iterable>> reservations = transformParallel( - regions, new Function>>>() { + regions, new Function>>>() { - @SuppressWarnings("unchecked") - @Override - public Future>> apply(String from) { - return (Future>>) client.getInstanceServices().describeInstancesInRegion(from); - } + @SuppressWarnings("unchecked") + @Override + public Future>> apply(String from) { + return (Future>>) client + .getInstanceServices().describeInstancesInRegion(from); + } - }, executor, null, logger, "reservations"); + }, executor, null, logger, "reservations"); - Iterable instances = concat(concat(reservations)); - Iterable nodes = filter(transform(instances, runningInstanceToNodeMetadata), filter); - return newLinkedHashSet(nodes); + return concat(concat(reservations)); } } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/util/EC2ComputeUtils.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/util/EC2ComputeUtils.java index c3f0a8a049..8018d482ce 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/util/EC2ComputeUtils.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/util/EC2ComputeUtils.java @@ -21,12 +21,9 @@ package org.jclouds.ec2.compute.util; import javax.inject.Singleton; -import org.jclouds.ec2.domain.RunningInstance; import org.jclouds.domain.Location; import org.jclouds.domain.LocationScope; -import com.google.common.base.Function; - /** * * @author Adrian Cole @@ -34,13 +31,6 @@ import com.google.common.base.Function; @Singleton public class EC2ComputeUtils { - public static Function instanceToId = new Function() { - @Override - public String apply(RunningInstance from) { - return from.getId(); - } - }; - public static String getZoneFromLocationOrNull(Location location) { return location.getScope() == LocationScope.ZONE ? location.getId() : null; } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/BlockDeviceMapping.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/BlockDeviceMapping.java index 0e6c3563f8..05d9e445b8 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/domain/BlockDeviceMapping.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/BlockDeviceMapping.java @@ -30,7 +30,64 @@ import org.jclouds.util.Preconditions2; * * @author Lili Nadar */ -public class BlockDeviceMapping { +public class BlockDeviceMapping implements Comparable{ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String deviceName; + private String virtualName; + private String snapshotId; + private Integer sizeInGib; + private Boolean noDevice; + private Boolean deleteOnTermination; + + public Builder deviceName(String deviceName) { + this.deviceName = deviceName; + return this; + } + + public Builder virtualName(String virtualName) { + this.virtualName = virtualName; + return this; + } + + public Builder snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return this; + } + + public Builder sizeInGib(Integer sizeInGib) { + this.sizeInGib = sizeInGib; + return this; + } + + public Builder noDevice(Boolean noDevice) { + this.noDevice = noDevice; + return this; + } + + public Builder deleteOnTermination(Boolean deleteOnTermination) { + this.deleteOnTermination = deleteOnTermination; + return this; + } + + public BlockDeviceMapping build() { + return new BlockDeviceMapping(deviceName, virtualName, snapshotId, sizeInGib, noDevice, deleteOnTermination); + } + + public Builder clear() { + this.deviceName = null; + this.virtualName = null; + this.snapshotId = null; + this.sizeInGib = null; + this.noDevice = null; + this.deleteOnTermination = null; + return this; + } + } + private final String deviceName; private final String virtualName; private final String snapshotId; @@ -43,14 +100,14 @@ public class BlockDeviceMapping { private static final Integer VOLUME_SIZE_MAX_VALUE = 1000; BlockDeviceMapping(String deviceName, @Nullable String virtualName, @Nullable String snapshotId, - @Nullable Integer sizeInGib, @Nullable Boolean noDevice, @Nullable Boolean deleteOnTermination) { + @Nullable Integer sizeInGib, @Nullable Boolean noDevice, @Nullable Boolean deleteOnTermination) { checkNotNull(deviceName, "deviceName cannot be null"); Preconditions2.checkNotEmpty(deviceName, "the deviceName must be non-empty"); if (sizeInGib != null) { - checkArgument((sizeInGib >= VOLUME_SIZE_MIN_VALUE && sizeInGib <= VOLUME_SIZE_MAX_VALUE), String.format( - "Size in Gib must be between %s and %s GB", VOLUME_SIZE_MIN_VALUE, VOLUME_SIZE_MAX_VALUE)); + checkArgument((sizeInGib >= VOLUME_SIZE_MIN_VALUE && sizeInGib <= VOLUME_SIZE_MAX_VALUE), + String.format("Size in Gib must be between %s and %s GB", VOLUME_SIZE_MIN_VALUE, VOLUME_SIZE_MAX_VALUE)); } this.deviceName = deviceName; this.virtualName = virtualName; @@ -142,13 +199,13 @@ public class BlockDeviceMapping { @Override public String toString() { return "[deviceName=" + deviceName + ", virtualName=" + virtualName + ", snapshotId=" + snapshotId - + ", sizeInGib=" + sizeInGib + ", noDevice=" + noDevice + ", deleteOnTermination=" + deleteOnTermination - + "]"; + + ", sizeInGib=" + sizeInGib + ", noDevice=" + noDevice + ", deleteOnTermination=" + deleteOnTermination + + "]"; } public static class MapEBSSnapshotToDevice extends BlockDeviceMapping { public MapEBSSnapshotToDevice(String deviceName, String snapshotId, @Nullable Integer sizeInGib, - @Nullable Boolean deleteOnTermination) { + @Nullable Boolean deleteOnTermination) { super(deviceName, null, snapshotId, sizeInGib, null, deleteOnTermination); checkNotNull(snapshotId, "snapshotId cannot be null"); Preconditions2.checkNotEmpty(snapshotId, "the snapshotId must be non-empty"); @@ -175,4 +232,9 @@ public class BlockDeviceMapping { super(deviceName, null, null, null, true, null); } } + + @Override + public int compareTo(BlockDeviceMapping arg0) { + return deviceName.compareTo(arg0.deviceName); + } } \ No newline at end of file diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/RunningInstance.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/RunningInstance.java index 020c2e975f..97d2067919 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/domain/RunningInstance.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/RunningInstance.java @@ -39,6 +39,9 @@ import com.google.common.collect.Sets; * @author Adrian Cole */ public class RunningInstance implements Comparable { + public static Builder builder() { + return new Builder(); + } public static class Builder { protected String region; @@ -265,8 +268,8 @@ public class RunningInstance implements Comparable { this.ipAddress = ipAddress; this.kernelId = kernelId; this.keyName = keyName; - this.launchTime = checkNotNull(launchTime, "launchTime"); - this.availabilityZone = checkNotNull(availabilityZone, "availabilityZone"); + this.launchTime = launchTime;// nullable on spot. + this.availabilityZone = availabilityZone;// nullable on spot. this.virtualizationType = virtualizationType; this.platform = platform; this.privateDnsName = privateDnsName;// nullable on runinstances. diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/options/RunInstancesOptions.java b/apis/ec2/src/main/java/org/jclouds/ec2/options/RunInstancesOptions.java index 30c166afdc..f3d0481811 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/options/RunInstancesOptions.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/options/RunInstancesOptions.java @@ -58,10 +58,6 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { return this; } - String getKeyName() { - return getFirstFormOrNull("KeyName"); - } - /** * Attach multiple security groups */ @@ -88,10 +84,6 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { return withSecurityGroups(securityGroup); } - String getSecurityGroup() { - return getFirstFormOrNull("SecurityGroup.1"); - } - /** * Unencoded data */ @@ -103,10 +95,6 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { return this; } - String getUserData() { - return getFirstFormOrNull("UserData"); - } - /** * Specifies the instance type. default small; */ @@ -115,10 +103,6 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { return this; } - String getType() { - return getFirstFormOrNull("InstanceType"); - } - /** * The ID of the kernel with which to launch the instance. */ @@ -127,10 +111,6 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { return this; } - String getKernelId() { - return getFirstFormOrNull("KernelId"); - } - /** * The ID of the RAM disk with which to launch the instance. Some kernels require additional * drivers at l aunch. Check the kernel requirements for information on whether you need to @@ -142,15 +122,10 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { return this; } - String getRamdiskId() { - return getFirstFormOrNull("RamdiskId"); - } - /** * Specifies the Block Device Mapping for the instance * */ - public RunInstancesOptions withBlockDeviceMappings(Set mappings) { int i = 1; for (BlockDeviceMapping mapping : checkNotNull(mappings, "mappings")) { @@ -161,15 +136,14 @@ public class RunInstancesOptions extends BaseEC2RequestOptions { if (mapping.getEbsSnapshotId() != null) formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.SnapshotId", i), mapping.getEbsSnapshotId()); if (mapping.getEbsVolumeSize() != null) - formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.VolumeSize", i), String.valueOf(mapping - .getEbsVolumeSize())); + formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.VolumeSize", i), + String.valueOf(mapping.getEbsVolumeSize())); if (mapping.getEbsNoDevice() != null) - formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.NoDevice", i), String.valueOf(mapping - .getEbsNoDevice())); + formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.NoDevice", i), + String.valueOf(mapping.getEbsNoDevice())); if (mapping.getEbsDeleteOnTermination() != null) - formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.DeleteOnTermination", i), String - .valueOf(mapping.getEbsDeleteOnTermination())); - + formParameters.put(String.format("BlockDeviceMapping.%d.Ebs.DeleteOnTermination", i), + String.valueOf(mapping.getEbsDeleteOnTermination())); i++; } return this; diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/options/internal/BaseEC2RequestOptions.java b/apis/ec2/src/main/java/org/jclouds/ec2/options/internal/BaseEC2RequestOptions.java index dd79c47635..037371cbe0 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/options/internal/BaseEC2RequestOptions.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/options/internal/BaseEC2RequestOptions.java @@ -26,8 +26,9 @@ import java.util.Set; import org.jclouds.http.options.BaseHttpRequestOptions; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; /** * @@ -51,7 +52,7 @@ public class BaseEC2RequestOptions extends BaseHttpRequestOptions { } protected Set getFormValuesWithKeysPrefixedBy(final String prefix) { - Set values = Sets.newLinkedHashSet(); + Builder values = ImmutableSet. builder(); for (String key : Iterables.filter(formParameters.keySet(), new Predicate() { public boolean apply(String input) { @@ -59,10 +60,9 @@ public class BaseEC2RequestOptions extends BaseHttpRequestOptions { } })) { - values.add(formParameters.get(key).iterator().next()); - + values.add(Iterables.get(formParameters.get(key), 0)); } - return values; + return values.build(); } } diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2RunNodesAndAddToSetStrategyTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java similarity index 95% rename from apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2RunNodesAndAddToSetStrategyTest.java rename to apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java index e79ff2b0c7..ec9b73ab95 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2RunNodesAndAddToSetStrategyTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java @@ -42,8 +42,10 @@ import org.jclouds.domain.Location; import org.jclouds.domain.LocationBuilder; import org.jclouds.domain.LocationScope; import org.jclouds.ec2.EC2Client; +import org.jclouds.ec2.compute.domain.RegionAndName; import org.jclouds.ec2.compute.functions.RunningInstanceToNodeMetadata; import org.jclouds.ec2.compute.options.EC2TemplateOptions; +import org.jclouds.ec2.compute.predicates.InstancePresent; import org.jclouds.ec2.domain.Reservation; import org.jclouds.ec2.domain.RunningInstance; import org.jclouds.ec2.options.RunInstancesOptions; @@ -51,7 +53,6 @@ import org.jclouds.ec2.services.InstanceClient; import org.testng.annotations.Test; import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; @@ -61,7 +62,7 @@ import com.google.inject.util.Providers; * @author Adrian Cole */ @Test(groups = "unit") -public class EC2RunNodesAndAddToSetStrategyTest { +public class EC2CreateNodesInGroupThenAddToSetTest { @Test public void testZoneAsALocation() { @@ -126,10 +127,10 @@ public class EC2RunNodesAndAddToSetStrategyTest { // simulate a lazy credentials fetch Credentials creds = new Credentials("foo", "bar"); expect(strategy.instanceToCredentials.apply(instance)).andReturn(creds); - expect(instance.getRegion()).andReturn(region); + expect(instance.getRegion()).andReturn(region).atLeastOnce(); expect(strategy.credentialStore.put("node#" + region + "/" + instanceCreatedId, creds)).andReturn(null); - expect(strategy.instancePresent.apply(instance)).andReturn(true); + expect(strategy.instancePresent.apply(new RegionAndName(region, instanceCreatedId))).andReturn(true); expect(input.template.getOptions()).andReturn(input.options).atLeastOnce(); expect(strategy.runningInstanceToNodeMetadata.apply(instance)).andReturn(nodeMetadata); @@ -220,13 +221,13 @@ public class EC2RunNodesAndAddToSetStrategyTest { private EC2CreateNodesInGroupThenAddToSet setupStrategy(TemplateBuilder template) { EC2Client client = createMock(EC2Client.class); CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions createKeyPairAndSecurityGroupsAsNeededAndReturncustomize = createMock(CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions.class); - Predicate instanceStateRunning = createMock(Predicate.class); + InstancePresent instancePresent = createMock(InstancePresent.class); RunningInstanceToNodeMetadata runningInstanceToNodeMetadata = createMock(RunningInstanceToNodeMetadata.class); Function instanceToCredentials = createMock(Function.class); Map credentialStore = createMock(Map.class); ComputeUtils utils = createMock(ComputeUtils.class); return new EC2CreateNodesInGroupThenAddToSet(client, Providers. of(template), - createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, instanceStateRunning, + createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, instancePresent, runningInstanceToNodeMetadata, instanceToCredentials, credentialStore, utils); } diff --git a/compute/src/main/java/org/jclouds/compute/predicates/NodePresentAndInIntendedState.java b/compute/src/main/java/org/jclouds/compute/predicates/NodePresentAndInIntendedState.java index 1564f2ec7b..d9c27f09a2 100644 --- a/compute/src/main/java/org/jclouds/compute/predicates/NodePresentAndInIntendedState.java +++ b/compute/src/main/java/org/jclouds/compute/predicates/NodePresentAndInIntendedState.java @@ -65,6 +65,8 @@ public class NodePresentAndInIntendedState implements Predicate { } private NodeMetadata refresh(NodeMetadata node) { + if (node == null || node.getId() == null) + return null; return client.getNodeMetadata(node.getId()); } } diff --git a/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java b/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java index fbe3440ea7..33ffec27f6 100644 --- a/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java +++ b/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java @@ -22,6 +22,7 @@ package org.jclouds.compute.strategy; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Throwables.getRootCause; +import static java.lang.String.format; import static org.jclouds.compute.util.ComputeServiceUtils.findReachableSocketOnNode; import java.util.Map; @@ -36,6 +37,7 @@ import org.jclouds.compute.callables.RunScriptOnNode; import org.jclouds.compute.config.CustomizationResponse; import org.jclouds.compute.domain.ExecResponse; import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeState; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.predicates.RetryIfSocketNotYetOpen; import org.jclouds.compute.reference.ComputeServiceConstants; @@ -125,15 +127,24 @@ public class CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap implements Cal public Void call() { checkState(!tainted, "this object is not designed to be reused: %s", toString()); tainted = true; + String originalId = node.getId(); + NodeMetadata originalNode = node; try { if (options.shouldBlockUntilRunning()) { if (nodeRunning.apply(node)) { - node = getNode.getNode(node.getId()); + node = getNode.getNode(originalId); } else { - throw new IllegalStateException(String.format( - "node didn't achieve the state running on node %s within %d seconds, final state: %s", node - .getId(), timeouts.nodeRunning / 1000, node.getState())); + NodeMetadata nodeForState = getNode.getNode(originalId); + NodeState state = nodeForState == null ? NodeState.TERMINATED : nodeForState.getState(); + if (state == NodeState.TERMINATED) + throw new IllegalStateException(format("node(%s) terminated before we could customize", originalId)); + else + throw new IllegalStateException(format( + "node(%s) didn't achieve the state running within %d seconds, final state: %s", originalId, + timeouts.nodeRunning / 1000, state)); } + if (node == null) + throw new IllegalStateException(format("node %s terminated before applying options", originalId)); if (statement != null) { RunScriptOnNode runner = initScriptRunnerFactory.create(node, statement, options, badNodes).call(); if (runner != null) { @@ -145,11 +156,11 @@ public class CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap implements Cal findReachableSocketOnNode(socketTester.seconds(options.getSeconds()), node, options.getPort()); } } - logger.debug("<< options applied node(%s)", node.getId()); + logger.debug("<< options applied node(%s)", originalId); goodNodes.add(node); } catch (Exception e) { - logger.error(e, "<< problem applying options to node(%s): ", node.getId(), getRootCause(e).getMessage()); - badNodes.put(node, e); + logger.error(e, "<< problem applying options to node(%s): ", originalId, getRootCause(e).getMessage()); + badNodes.put(node == null ? originalNode : node, e); } return null; } diff --git a/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java b/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java index cab0f53cd0..cca248b6a6 100644 --- a/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java +++ b/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java @@ -301,7 +301,7 @@ public class TemplateBuilderImplTest { TemplateBuilder defaultTemplate = createMock(TemplateBuilder.class); expect(templateBuilderProvider.get()).andReturn(defaultTemplate); - expect(defaultTemplate.options(options)).andReturn(defaultTemplate); + expect(defaultTemplate.options(from)).andReturn(defaultTemplate); expect(defaultTemplate.build()).andReturn(null); expect(optionsProvider.get()).andReturn(from).atLeastOnce(); diff --git a/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java b/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java new file mode 100644 index 0000000000..778dbf7ecd --- /dev/null +++ b/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java @@ -0,0 +1,144 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.compute.strategy; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createMock; +import static org.easymock.classextension.EasyMock.replay; +import static org.easymock.classextension.EasyMock.verify; +import static org.testng.Assert.assertEquals; + +import java.util.Map; +import java.util.Set; + +import org.jclouds.compute.config.CustomizationResponse; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.domain.NodeState; +import org.jclouds.compute.functions.TemplateOptionsToStatement; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.predicates.RetryIfSocketNotYetOpen; +import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts; +import org.jclouds.scriptbuilder.domain.Statement; +import org.testng.annotations.Test; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest { + + @SuppressWarnings("unchecked") + public void testBreakWhenNodeStillPending() { + Predicate nodeRunning = createMock(Predicate.class); + InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory = createMock(InitializeRunScriptOnNodeOrPlaceInBadMap.Factory.class); + GetNodeMetadataStrategy getNode = createMock(GetNodeMetadataStrategy.class); + RetryIfSocketNotYetOpen socketTester = createMock(RetryIfSocketNotYetOpen.class); + Timeouts timeouts = new Timeouts(); + Function templateOptionsToStatement = new TemplateOptionsToStatement(); + @SuppressWarnings("unused") + Statement statement = null; + TemplateOptions options = new TemplateOptions(); + Set goodNodes = Sets.newLinkedHashSet(); + Map badNodes = Maps.newLinkedHashMap(); + Multimap customizationResponses = LinkedHashMultimap.create(); + + NodeMetadata node = new NodeMetadataBuilder().ids("id").state(NodeState.PENDING).build(); + + // node never reached running state + expect(nodeRunning.apply(node)).andReturn(false); + expect(getNode.getNode(node.getId())).andReturn(node); + + // replay mocks + replay(nodeRunning); + replay(initScriptRunnerFactory); + replay(getNode); + replay(socketTester); + + // run + new CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap(nodeRunning, getNode, socketTester, timeouts, + templateOptionsToStatement, initScriptRunnerFactory, options, node, goodNodes, badNodes, + customizationResponses).apply(node); + + assertEquals(goodNodes.size(), 0); + assertEquals(badNodes.keySet(), ImmutableSet.of(node)); + assertEquals(badNodes.get(node).getMessage(), + "node(id) didn't achieve the state running within 1200 seconds, final state: PENDING"); + assertEquals(customizationResponses.size(), 0); + + // verify mocks + verify(nodeRunning); + verify(initScriptRunnerFactory); + verify(getNode); + verify(socketTester); + } + + @SuppressWarnings("unchecked") + public void testBreakGraceFullyWhenNodeDied() { + Predicate nodeRunning = createMock(Predicate.class); + InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory = createMock(InitializeRunScriptOnNodeOrPlaceInBadMap.Factory.class); + GetNodeMetadataStrategy getNode = createMock(GetNodeMetadataStrategy.class); + RetryIfSocketNotYetOpen socketTester = createMock(RetryIfSocketNotYetOpen.class); + Timeouts timeouts = new Timeouts(); + Function templateOptionsToStatement = new TemplateOptionsToStatement(); + @SuppressWarnings("unused") + Statement statement = null; + TemplateOptions options = new TemplateOptions(); + Set goodNodes = Sets.newLinkedHashSet(); + Map badNodes = Maps.newLinkedHashMap(); + Multimap customizationResponses = LinkedHashMultimap.create(); + + NodeMetadata node = new NodeMetadataBuilder().ids("id").state(NodeState.PENDING).build(); + + // node never reached running state + expect(nodeRunning.apply(node)).andReturn(false); + expect(getNode.getNode(node.getId())).andReturn(null); + + // replay mocks + replay(nodeRunning); + replay(initScriptRunnerFactory); + replay(getNode); + replay(socketTester); + + // run + new CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap(nodeRunning, getNode, socketTester, timeouts, + templateOptionsToStatement, initScriptRunnerFactory, options, node, goodNodes, badNodes, + customizationResponses).apply(node); + + assertEquals(goodNodes.size(), 0); + assertEquals(badNodes.keySet(), ImmutableSet.of(node)); + assertEquals(badNodes.get(node).getMessage(), "node(id) terminated before we could customize"); + assertEquals(customizationResponses.size(), 0); + + // verify mocks + verify(nodeRunning); + verify(initScriptRunnerFactory); + verify(getNode); + verify(socketTester); + } +} diff --git a/core/src/main/java/org/jclouds/predicates/RetryablePredicate.java b/core/src/main/java/org/jclouds/predicates/RetryablePredicate.java index e011e778c0..b7ae108251 100644 --- a/core/src/main/java/org/jclouds/predicates/RetryablePredicate.java +++ b/core/src/main/java/org/jclouds/predicates/RetryablePredicate.java @@ -19,14 +19,16 @@ package org.jclouds.predicates; +import static org.jclouds.util.Throwables2.getFirstThrowableOfType; + import java.util.Date; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.annotation.Resource; import org.jclouds.logging.Logger; -import org.jclouds.util.Throwables2; import com.google.common.base.Predicate; @@ -75,10 +77,16 @@ public class RetryablePredicate implements Predicate { } catch (InterruptedException e) { logger.warn(e, "predicate %s on %s interrupted, returning false", input, predicate); } catch (RuntimeException e) { - ExecutionException exec = Throwables2.getFirstThrowableOfType(e, ExecutionException.class); - if (exec != null) - logger.warn(exec, "predicate %s on %s error, returning false", input, predicate); - else + if (getFirstThrowableOfType(e, ExecutionException.class) != null) { + logger.warn(e, "predicate %s on %s errored [%s], returning false", input, predicate, e.getMessage()); + return false; + } else if (getFirstThrowableOfType(e, IllegalStateException.class) != null) { + logger.warn(e, "predicate %s on %s illegal state [%s], returning false", input, predicate, e.getMessage()); + return false; + } else if (getFirstThrowableOfType(e, TimeoutException.class) != null) { + logger.warn(e, "predicate %s on %s timed out [%s], returning false", input, predicate, e.getMessage()); + return false; + } else throw e; } return false; diff --git a/core/src/test/java/org/jclouds/predicates/RetryablePredicateTest.java b/core/src/test/java/org/jclouds/predicates/RetryablePredicateTest.java index 7b419442fb..4eeb6d8b89 100644 --- a/core/src/test/java/org/jclouds/predicates/RetryablePredicateTest.java +++ b/core/src/test/java/org/jclouds/predicates/RetryablePredicateTest.java @@ -20,12 +20,15 @@ package org.jclouds.predicates; import java.util.Date; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.testng.annotations.Test; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.base.Supplier; /** * @@ -36,6 +39,56 @@ import com.google.common.base.Predicates; public class RetryablePredicateTest { public static int SLOW_BUILD_SERVER_GRACE = 50; + @Test + void testFalseOnIllegalStateExeception() { + ensureImmediateReturnFor(new IllegalStateException()); + } + + @SuppressWarnings("serial") + @Test + void testFalseOnExecutionException() { + ensureImmediateReturnFor(new ExecutionException() { + }); + } + + @SuppressWarnings("serial") + @Test + void testFalseOnTimeoutException() { + ensureImmediateReturnFor(new TimeoutException() { + }); + } + + @SuppressWarnings("serial") + @Test(expectedExceptions = RuntimeException.class) + void testPropagateOnException() { + ensureImmediateReturnFor(new Exception() { + }); + } + + private void ensureImmediateReturnFor(final Exception ex) { + RetryablePredicate> predicate = new RetryablePredicate>( + new Predicate>() { + + @Override + public boolean apply(Supplier input) { + return "goo".equals(input.get()); + } + + }, 3, 1, TimeUnit.SECONDS); + Date startPlusThird = new Date(System.currentTimeMillis() + 1000); + assert !predicate.apply(new Supplier() { + + @Override + public String get() { + throw new RuntimeException(ex); + } + + }); + Date now = new Date(); + assert now.compareTo(startPlusThird) < 0 : String.format("%s should be less than %s", now.getTime(), + startPlusThird.getTime()); + } + @Test void testAlwaysTrue() { RetryablePredicate predicate = new RetryablePredicate(Predicates. alwaysTrue(), 3, 1, diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java index dc856615b3..634c855e86 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java @@ -24,6 +24,7 @@ import org.jclouds.aws.ec2.services.AWSInstanceAsyncClient; import org.jclouds.aws.ec2.services.AWSKeyPairAsyncClient; import org.jclouds.aws.ec2.services.MonitoringAsyncClient; import org.jclouds.aws.ec2.services.PlacementGroupAsyncClient; +import org.jclouds.aws.ec2.services.SpotInstanceAsyncClient; import org.jclouds.ec2.EC2AsyncClient; import org.jclouds.rest.annotations.Delegate; @@ -67,4 +68,10 @@ public interface AWSEC2AsyncClient extends EC2AsyncClient { @Delegate @Override AWSKeyPairAsyncClient getKeyPairServices(); + + /** + * Provides asynchronous access to SpotInstance services. + */ + @Delegate + SpotInstanceAsyncClient getSpotInstanceServices(); } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java index 9a6f9087b6..91a5eaadac 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java @@ -26,6 +26,7 @@ import org.jclouds.aws.ec2.services.AWSInstanceClient; import org.jclouds.aws.ec2.services.AWSKeyPairClient; import org.jclouds.aws.ec2.services.MonitoringClient; import org.jclouds.aws.ec2.services.PlacementGroupClient; +import org.jclouds.aws.ec2.services.SpotInstanceClient; import org.jclouds.concurrent.Timeout; import org.jclouds.ec2.EC2Client; import org.jclouds.rest.annotations.Delegate; @@ -70,4 +71,10 @@ public interface AWSEC2Client extends EC2Client { @Delegate @Override AWSKeyPairClient getKeyPairServices(); + + /** + * Provides synchronous access to SpotInstance services. + */ + @Delegate + SpotInstanceClient getSpotInstanceServices(); } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParams.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParams.java new file mode 100644 index 0000000000..db323eb46e --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParams.java @@ -0,0 +1,65 @@ +package org.jclouds.aws.ec2.binders; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.options.AWSRunInstancesOptions; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.utils.ModifyRequest; +import org.jclouds.rest.Binder; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Multimaps; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class BindLaunchSpecificationToFormParams implements Binder, Function> { + + @Override + public R bindToRequest(R request, Object input) { + checkArgument(input instanceof LaunchSpecification, "this binder is only valid for LaunchSpecifications!"); + LaunchSpecification launchSpec = LaunchSpecification.class.cast(input); + return ModifyRequest.putFormParams(request, Multimaps.forMap(apply(launchSpec))); + } + + @Override + public Map apply(LaunchSpecification launchSpec) { + Builder builder = ImmutableMap. builder(); + builder.put("LaunchSpecification.ImageId", checkNotNull(launchSpec.getImageId(), "imageId")); + if (launchSpec.getAvailabilityZone() != null) + builder.put("LaunchSpecification.Placement.AvailabilityZone", launchSpec.getAvailabilityZone()); + + AWSRunInstancesOptions options = new AWSRunInstancesOptions(); + if (launchSpec.getBlockDeviceMappings().size() > 0) + options.withBlockDeviceMappings(launchSpec.getBlockDeviceMappings()); + if (launchSpec.getGroupIds().size() > 0) + options.withSecurityGroups(launchSpec.getGroupIds()); + options.asType(checkNotNull(launchSpec.getInstanceType(), "instanceType")); + if (launchSpec.getKernelId() != null) + options.withKernelId(launchSpec.getKernelId()); + if (launchSpec.getKeyName() != null) + options.withKeyName(launchSpec.getKeyName()); + if (launchSpec.getRamdiskId() != null) + options.withRamdisk(launchSpec.getRamdiskId()); + if (Boolean.TRUE.equals(launchSpec.isMonitoringEnabled())) + options.enableMonitoring(); + if (launchSpec.getUserData() != null) + options.withUserData(launchSpec.getUserData()); + + for (Entry entry : options.buildFormParameters().entries()) { + builder.put("LaunchSpecification." + entry.getKey(), entry.getValue()); + } + return builder.build(); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindSpotInstanceRequestIdsToIndexedFormParams.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindSpotInstanceRequestIdsToIndexedFormParams.java new file mode 100644 index 0000000000..479cccee0a --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindSpotInstanceRequestIdsToIndexedFormParams.java @@ -0,0 +1,40 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.binders; + +import javax.inject.Singleton; + +import org.jclouds.aws.util.AWSUtils; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.Binder; + +/** + * Binds the String [] to form parameters named with SpotInstanceRequestId.index + * + * @author Adrian Cole + */ +@Singleton +public class BindSpotInstanceRequestIdsToIndexedFormParams implements Binder { + @Override + public R bindToRequest(R request, Object input) { + return AWSUtils.indexStringArrayToFormValuesWithPrefix(request, "SpotInstanceRequestId", input); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java index a6f221d181..dc5b5cdd24 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java @@ -27,6 +27,7 @@ import java.util.Set; import javax.annotation.Nullable; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.domain.Credentials; import org.jclouds.ec2.compute.options.EC2TemplateOptions; @@ -73,6 +74,10 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab eTo.noPlacementGroup(); if (getPlacementGroup() != null) eTo.placementGroup(getPlacementGroup()); + if (getSpotPrice() != null) + eTo.spotPrice(getSpotPrice()); + if (getSpotOptions() != null) + eTo.spotOptions(getSpotOptions()); } } @@ -80,6 +85,8 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab private String placementGroup = null; private boolean noPlacementGroup; private String subnetId; + private Float spotPrice; + private RequestSpotInstancesOptions spotOptions = RequestSpotInstancesOptions.NONE; public static final AWSEC2TemplateOptions NONE = new AWSEC2TemplateOptions(); @@ -123,6 +130,22 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab return this; } + /** + * Specifies the maximum spot price to use + */ + public AWSEC2TemplateOptions spotPrice(Float spotPrice) { + this.spotPrice = spotPrice; + return this; + } + + /** + * Options for starting spot instances + */ + public AWSEC2TemplateOptions spotOptions(RequestSpotInstancesOptions spotOptions) { + this.spotOptions = spotOptions != null ? spotOptions : RequestSpotInstancesOptions.NONE; + return this; + } + public static class Builder { /** * @see EC2TemplateOptions#blockDeviceMappings @@ -136,7 +159,7 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab * @see EC2TemplateOptions#mapEBSSnapshotToDeviceName */ public static AWSEC2TemplateOptions mapEBSSnapshotToDeviceName(String deviceName, String snapshotId, - @Nullable Integer sizeInGib, boolean deleteOnTermination) { + @Nullable Integer sizeInGib, boolean deleteOnTermination) { AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); return options.mapEBSSnapshotToDeviceName(deviceName, snapshotId, sizeInGib, deleteOnTermination); } @@ -145,7 +168,7 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab * @see EC2TemplateOptions#mapNewVolumeToDeviceName */ public static AWSEC2TemplateOptions mapNewVolumeToDeviceName(String deviceName, int sizeInGib, - boolean deleteOnTermination) { + boolean deleteOnTermination) { AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); return options.mapNewVolumeToDeviceName(deviceName, sizeInGib, deleteOnTermination); } @@ -280,12 +303,28 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab } /** - * @see TemplateOptions#withSubnetId + * @see TemplateOptions#spotPrice */ public static AWSEC2TemplateOptions subnetId(String subnetId) { AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); return options.subnetId(subnetId); } + + /** + * @see TemplateOptions#spotPrice + */ + public static AWSEC2TemplateOptions spotPrice(Float spotPrice) { + AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); + return options.spotPrice(spotPrice); + } + + /** + * @see TemplateOptions#spotOptions + */ + public static AWSEC2TemplateOptions spotOptions(RequestSpotInstancesOptions spotOptions) { + AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); + return options.spotOptions(spotOptions); + } } // methods that only facilitate returning the correct object type @@ -294,7 +333,7 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab * {@inheritDoc} */ @Override - public AWSEC2TemplateOptions blockDeviceMappings(Set blockDeviceMappings) { + public AWSEC2TemplateOptions blockDeviceMappings(Iterable blockDeviceMappings) { return AWSEC2TemplateOptions.class.cast(super.blockDeviceMappings(blockDeviceMappings)); } @@ -312,9 +351,9 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab @Override public AWSEC2TemplateOptions mapEBSSnapshotToDeviceName(String deviceName, String snapshotId, Integer sizeInGib, - boolean deleteOnTermination) { + boolean deleteOnTermination) { return AWSEC2TemplateOptions.class.cast(super.mapEBSSnapshotToDeviceName(deviceName, snapshotId, sizeInGib, - deleteOnTermination)); + deleteOnTermination)); } /** @@ -331,7 +370,7 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab @Override public AWSEC2TemplateOptions mapNewVolumeToDeviceName(String deviceName, int sizeInGib, boolean deleteOnTermination) { return AWSEC2TemplateOptions.class.cast(super - .mapNewVolumeToDeviceName(deviceName, sizeInGib, deleteOnTermination)); + .mapNewVolumeToDeviceName(deviceName, sizeInGib, deleteOnTermination)); } /** @@ -525,6 +564,20 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab return subnetId; } + /** + * @return maximum spot price or null. + */ + public Float getSpotPrice() { + return spotPrice; + } + + /** + * @return options for controlling spot instance requests. + */ + public RequestSpotInstancesOptions getSpotOptions() { + return spotOptions; + } + @Override public int hashCode() { final int prime = 31; @@ -532,6 +585,8 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab result = prime * result + (monitoringEnabled ? 1231 : 1237); result = prime * result + (noPlacementGroup ? 1231 : 1237); result = prime * result + ((placementGroup == null) ? 0 : placementGroup.hashCode()); + result = prime * result + ((spotOptions == null) ? 0 : spotOptions.hashCode()); + result = prime * result + ((spotPrice == null) ? 0 : spotPrice.hashCode()); result = prime * result + ((subnetId == null) ? 0 : subnetId.hashCode()); return result; } @@ -554,6 +609,16 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab return false; } else if (!placementGroup.equals(other.placementGroup)) return false; + if (spotOptions == null) { + if (other.spotOptions != null) + return false; + } else if (!spotOptions.equals(other.spotOptions)) + return false; + if (spotPrice == null) { + if (other.spotPrice != null) + return false; + } else if (!spotPrice.equals(other.spotPrice)) + return false; if (subnetId == null) { if (other.subnetId != null) return false; @@ -566,9 +631,10 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab public String toString() { return "[groupIds=" + getGroupIds() + ", keyPair=" + getKeyPair() + ", noKeyPair=" - + !shouldAutomaticallyCreateKeyPair() + ", monitoringEnabled=" + monitoringEnabled + ", placementGroup=" - + placementGroup + ", noPlacementGroup=" + noPlacementGroup + ", subnetId=" + subnetId + ", userData=" - + Arrays.toString(getUserData()) + ", blockDeviceMappings=" + getBlockDeviceMappings() + "]"; + + !shouldAutomaticallyCreateKeyPair() + ", monitoringEnabled=" + monitoringEnabled + ", placementGroup=" + + placementGroup + ", noPlacementGroup=" + noPlacementGroup + ", subnetId=" + subnetId + ", userData=" + + Arrays.toString(getUserData()) + ", blockDeviceMappings=" + getBlockDeviceMappings() + ", spotPrice=" + + spotPrice + ", spotOptions=" + spotOptions + "]"; } } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java index 0ea1bd580c..7ae5e9ce33 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java @@ -22,14 +22,26 @@ package org.jclouds.aws.ec2.compute.config; import static org.jclouds.compute.domain.OsFamily.AMZN_LINUX; import org.jclouds.aws.ec2.compute.AWSEC2TemplateBuilderImpl; +import org.jclouds.aws.ec2.compute.functions.AWSRunningInstanceToNodeMetadata; +import org.jclouds.aws.ec2.compute.predicates.AWSEC2InstancePresent; +import org.jclouds.aws.ec2.compute.strategy.AWSEC2CreateNodesInGroupThenAddToSet; +import org.jclouds.aws.ec2.compute.strategy.AWSEC2DestroyNodeStrategy; +import org.jclouds.aws.ec2.compute.strategy.AWSEC2GetNodeMetadataStrategy; +import org.jclouds.aws.ec2.compute.strategy.AWSEC2ListNodesStrategy; import org.jclouds.aws.ec2.compute.strategy.AWSEC2ReviseParsedImage; import org.jclouds.aws.ec2.compute.strategy.CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions; import org.jclouds.aws.ec2.compute.suppliers.AWSEC2HardwareSupplier; import org.jclouds.aws.ec2.compute.suppliers.AWSRegionAndNameToImageSupplier; import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.ec2.compute.config.EC2ComputeServiceContextModule; +import org.jclouds.ec2.compute.functions.RunningInstanceToNodeMetadata; import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl; +import org.jclouds.ec2.compute.predicates.InstancePresent; import org.jclouds.ec2.compute.strategy.CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions; +import org.jclouds.ec2.compute.strategy.EC2CreateNodesInGroupThenAddToSet; +import org.jclouds.ec2.compute.strategy.EC2DestroyNodeStrategy; +import org.jclouds.ec2.compute.strategy.EC2GetNodeMetadataStrategy; +import org.jclouds.ec2.compute.strategy.EC2ListNodesStrategy; import org.jclouds.ec2.compute.strategy.ReviseParsedImage; import org.jclouds.ec2.compute.suppliers.EC2HardwareSupplier; import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier; @@ -56,6 +68,12 @@ public class AWSEC2ComputeServiceContextModule extends EC2ComputeServiceContextM bind(EC2HardwareSupplier.class).to(AWSEC2HardwareSupplier.class); bind(RegionAndNameToImageSupplier.class).to(AWSRegionAndNameToImageSupplier.class); bind(EC2TemplateBuilderImpl.class).to(AWSEC2TemplateBuilderImpl.class); + bind(EC2GetNodeMetadataStrategy.class).to(AWSEC2GetNodeMetadataStrategy.class); + bind(EC2ListNodesStrategy.class).to(AWSEC2ListNodesStrategy.class); + bind(EC2DestroyNodeStrategy.class).to(AWSEC2DestroyNodeStrategy.class); + bind(InstancePresent.class).to(AWSEC2InstancePresent.class); + bind(EC2CreateNodesInGroupThenAddToSet.class).to(AWSEC2CreateNodesInGroupThenAddToSet.class); + bind(RunningInstanceToNodeMetadata.class).to(AWSRunningInstanceToNodeMetadata.class); } @Override diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/functions/AWSRunningInstanceToNodeMetadata.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/functions/AWSRunningInstanceToNodeMetadata.java new file mode 100644 index 0000000000..e663f8542f --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/functions/AWSRunningInstanceToNodeMetadata.java @@ -0,0 +1,69 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.compute.functions; + +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.domain.AWSRunningInstance; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.domain.NodeState; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.Location; +import org.jclouds.ec2.compute.domain.RegionAndName; +import org.jclouds.ec2.compute.functions.RunningInstanceToNodeMetadata; +import org.jclouds.ec2.domain.InstanceState; +import org.jclouds.ec2.domain.RunningInstance; + +import com.google.common.base.Supplier; + +/** + * @author Adrian Cole + */ +@Singleton +public class AWSRunningInstanceToNodeMetadata extends RunningInstanceToNodeMetadata { + + @Inject + protected AWSRunningInstanceToNodeMetadata(Map instanceToNodeState, + Map credentialStore, Map instanceToImage, + @Memoized Supplier> locations, @Memoized Supplier> hardware) { + super(instanceToNodeState, credentialStore, instanceToImage, locations, hardware); + } + + @Override + protected void addCredentialsForInstance(NodeMetadataBuilder builder, RunningInstance instance) { + Credentials creds = credentialStore.get("node#" + instance.getRegion() + "/" + instance.getId()); + String spotRequestId = AWSRunningInstance.class.cast(instance).getSpotInstanceRequestId(); + if (creds == null && spotRequestId != null) { + creds = credentialStore.get("node#" + instance.getRegion() + "/" + spotRequestId); + if (creds != null) + credentialStore.put("node#" + instance.getRegion() + "/" + instance.getId(), creds); + } + if (creds != null) + builder.credentials(creds); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/predicates/AWSEC2InstancePresent.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/predicates/AWSEC2InstancePresent.java new file mode 100644 index 0000000000..f1865dc647 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/predicates/AWSEC2InstancePresent.java @@ -0,0 +1,56 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.compute.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.ec2.compute.domain.RegionAndName; +import org.jclouds.ec2.compute.predicates.InstancePresent; + +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class AWSEC2InstancePresent extends InstancePresent { + + private final AWSEC2Client client; + + @Inject + public AWSEC2InstancePresent(AWSEC2Client client) { + super(client); + this.client = checkNotNull(client, "client"); + } + + @Override + protected void refresh(RegionAndName instance) { + if (instance.getName().indexOf("sir-") != 0) + super.refresh(instance); + else + Iterables.getOnlyElement(client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion( + instance.getRegion(), instance.getName())); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2CreateNodesInGroupThenAddToSet.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2CreateNodesInGroupThenAddToSet.java new file mode 100644 index 0000000000..f8399e968e --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2CreateNodesInGroupThenAddToSet.java @@ -0,0 +1,109 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.compute.strategy; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions; +import org.jclouds.aws.ec2.compute.predicates.AWSEC2InstancePresent; +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.functions.SpotInstanceRequestToAWSRunningInstance; +import org.jclouds.aws.ec2.options.AWSRunInstancesOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.compute.util.ComputeUtils; +import org.jclouds.domain.Credentials; +import org.jclouds.ec2.compute.strategy.EC2CreateNodesInGroupThenAddToSet; +import org.jclouds.ec2.domain.RunningInstance; +import org.jclouds.ec2.options.RunInstancesOptions; +import org.jclouds.logging.Logger; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.Iterables; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class AWSEC2CreateNodesInGroupThenAddToSet extends EC2CreateNodesInGroupThenAddToSet { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + @VisibleForTesting + final AWSEC2Client client; + final SpotInstanceRequestToAWSRunningInstance spotConverter; + + @Inject + protected AWSEC2CreateNodesInGroupThenAddToSet( + AWSEC2Client client, + Provider templateBuilderProvider, + CreateKeyPairPlacementAndSecurityGroupsAsNeededAndReturnRunOptions createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, + AWSEC2InstancePresent instancePresent, + Function runningInstanceToNodeMetadata, + Function instanceToCredentials, Map credentialStore, + ComputeUtils utils, SpotInstanceRequestToAWSRunningInstance spotConverter) { + + super(client, templateBuilderProvider, createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, instancePresent, + runningInstanceToNodeMetadata, instanceToCredentials, credentialStore, utils); + this.client = checkNotNull(client, "client"); + this.spotConverter = checkNotNull(spotConverter, "spotConverter"); + } + + protected Iterable createNodesInRegionAndZone(String region, String zone, int count, + Template template, RunInstancesOptions instanceOptions) { + Float spotPrice = getSpotPriceOrNull(template.getOptions()); + if (spotPrice != null) { + LaunchSpecification spec = AWSRunInstancesOptions.class.cast(instanceOptions).getLaunchSpecificationBuilder() + .imageId(template.getImage().getProviderId()).availabilityZone(zone).build(); + RequestSpotInstancesOptions options = AWSEC2TemplateOptions.class.cast(template.getOptions()).getSpotOptions(); + if (logger.isDebugEnabled()) + logger.debug(">> requesting %d spot instances region(%s) price(%f) spec(%s) options(%s)", count, region, + spotPrice, spec, options); + + return Iterables.transform(client.getSpotInstanceServices().requestSpotInstancesInRegion(region, spotPrice, + count, spec, options), spotConverter); + } else { + return super.createNodesInRegionAndZone(zone, zone, count, template, instanceOptions); + } + + } + + private Float getSpotPriceOrNull(TemplateOptions options) { + return options instanceof AWSEC2TemplateOptions ? AWSEC2TemplateOptions.class.cast(options).getSpotPrice() : null; + } + +} \ No newline at end of file diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2DestroyNodeStrategy.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2DestroyNodeStrategy.java new file mode 100644 index 0000000000..f9d76ad2f9 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2DestroyNodeStrategy.java @@ -0,0 +1,55 @@ +package org.jclouds.aws.ec2.compute.strategy; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; + +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.compute.strategy.GetNodeMetadataStrategy; +import org.jclouds.domain.Credentials; +import org.jclouds.ec2.compute.strategy.EC2DestroyNodeStrategy; + +import com.google.common.collect.Iterables; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class AWSEC2DestroyNodeStrategy extends EC2DestroyNodeStrategy { + + protected final AWSEC2Client client; + protected final Map credentialStore; + + @Inject + protected AWSEC2DestroyNodeStrategy(AWSEC2Client client, GetNodeMetadataStrategy getNode, + Map credentialStore) { + super(client, getNode); + this.client = checkNotNull(client, "client"); + this.credentialStore = checkNotNull(credentialStore, "credentialStore"); + } + + @Override + protected void destroyInstanceInRegion(String region, String id) { + String spotId = id; + if (id.indexOf("sir-") != 0) { + try { + spotId = getOnlyElement( + Iterables.concat(client.getInstanceServices().describeInstancesInRegion(region, id))) + .getSpotInstanceRequestId(); + credentialStore.remove("node#" + region + "/" + spotId); + } catch (NoSuchElementException e) { + } + super.destroyInstanceInRegion(region, id); + } else { + client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion(region, spotId); + credentialStore.remove("node#" + region + "/" + id); + } + + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2GetNodeMetadataStrategy.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2GetNodeMetadataStrategy.java new file mode 100644 index 0000000000..996cc32402 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2GetNodeMetadataStrategy.java @@ -0,0 +1,68 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.compute.strategy; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.functions.SpotInstanceRequestToAWSRunningInstance; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.ec2.compute.strategy.EC2GetNodeMetadataStrategy; +import org.jclouds.ec2.domain.RunningInstance; + +import com.google.common.base.Function; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class AWSEC2GetNodeMetadataStrategy extends EC2GetNodeMetadataStrategy { + + private final AWSEC2Client client; + private final SpotInstanceRequestToAWSRunningInstance spotConverter; + + @Inject + protected AWSEC2GetNodeMetadataStrategy(AWSEC2Client client, + Function runningInstanceToNodeMetadata, + SpotInstanceRequestToAWSRunningInstance spotConverter) { + super(client, runningInstanceToNodeMetadata); + this.client = checkNotNull(client, "client"); + this.spotConverter = checkNotNull(spotConverter, "spotConverter"); + } + + @Override + public RunningInstance getRunningInstanceInRegion(String region, String id) { + if (id.indexOf("sir-") != 0) + return super.getRunningInstanceInRegion(region, id); + SpotInstanceRequest spot = getOnlyElement(client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion( + region, id)); + if (spot.getState() == SpotInstanceRequest.State.ACTIVE) + return super.getRunningInstanceInRegion(region, spot.getInstanceId()); + else + return spotConverter.apply(spot); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java new file mode 100644 index 0000000000..dd9cf3863b --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java @@ -0,0 +1,85 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.compute.strategy; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.notNull; +import static com.google.common.collect.Iterables.concat; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; +import static org.jclouds.concurrent.FutureIterables.transformParallel; + +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.aws.ec2.AWSEC2AsyncClient; +import org.jclouds.aws.ec2.domain.AWSRunningInstance; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.functions.SpotInstanceRequestToAWSRunningInstance; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.ec2.compute.strategy.EC2ListNodesStrategy; +import org.jclouds.ec2.domain.RunningInstance; +import org.jclouds.location.Region; + +import com.google.common.base.Function; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class AWSEC2ListNodesStrategy extends EC2ListNodesStrategy { + + protected final AWSEC2AsyncClient client; + protected final SpotInstanceRequestToAWSRunningInstance spotConverter; + + @Inject + protected AWSEC2ListNodesStrategy(AWSEC2AsyncClient client, @Region Set regions, + Function runningInstanceToNodeMetadata, + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, + SpotInstanceRequestToAWSRunningInstance spotConverter) { + super(client, regions, runningInstanceToNodeMetadata, executor); + this.client = checkNotNull(client, "client"); + this.spotConverter = checkNotNull(spotConverter, "spotConverter"); + } + + @Override + protected Iterable pollRunningInstances() { + Iterable spots = filter(transform(concat(transformParallel(regions, + new Function>>() { + + @SuppressWarnings("unchecked") + @Override + public Future> apply(String from) { + return (Future>) client.getSpotInstanceServices() + .describeSpotInstanceRequestsInRegion(from); + } + + }, executor, null, logger, "reservations")), spotConverter), notNull()); + + return concat(super.pollRunningInstances(), spots); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java index 719b3eb411..2fe4323885 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java @@ -37,6 +37,8 @@ import org.jclouds.aws.ec2.services.MonitoringAsyncClient; import org.jclouds.aws.ec2.services.MonitoringClient; import org.jclouds.aws.ec2.services.PlacementGroupAsyncClient; import org.jclouds.aws.ec2.services.PlacementGroupClient; +import org.jclouds.aws.ec2.services.SpotInstanceAsyncClient; +import org.jclouds.aws.ec2.services.SpotInstanceClient; import org.jclouds.ec2.EC2AsyncClient; import org.jclouds.ec2.EC2Client; import org.jclouds.ec2.config.EC2RestClientModule; @@ -82,6 +84,7 @@ public class AWSEC2RestClientModule extends EC2RestClientModule + * + * ==================================================================== + * Licensed 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.aws.ec2.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Arrays; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.jclouds.ec2.domain.BlockDeviceMapping; +import org.jclouds.ec2.domain.BlockDeviceMapping.MapEBSSnapshotToDevice; +import org.jclouds.ec2.domain.BlockDeviceMapping.MapEphemeralDeviceToDevice; +import org.jclouds.ec2.domain.BlockDeviceMapping.MapNewVolumeToDevice; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; + +/** + * + * @see + * @author Adrian Cole + */ +public class LaunchSpecification { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + protected ImmutableSet.Builder groupIds = ImmutableSet. builder(); + protected String imageId; + protected String instanceType; + protected String kernelId; + protected String keyName; + protected String availabilityZone; + protected String ramdiskId; + protected Boolean monitoringEnabled; + protected ImmutableSet.Builder blockDeviceMappings = ImmutableSet + . builder(); + protected byte[] userData; + + public void clear() { + groupIds = ImmutableSet. builder(); + imageId = null; + instanceType = null; + kernelId = null; + keyName = null; + availabilityZone = null; + ramdiskId = null; + monitoringEnabled = false; + blockDeviceMappings = ImmutableSet. builder(); + userData = null; + } + + public Builder groupIds(Iterable groupIds) { + this.groupIds.addAll(checkNotNull(groupIds, "groupIds")); + return this; + } + + public Builder groupId(String groupId) { + if (groupId != null) + this.groupIds.add(groupId); + return this; + } + + public Builder imageId(String imageId) { + this.imageId = imageId; + return this; + } + + public Builder monitoringEnabled(Boolean monitoringEnabled) { + this.monitoringEnabled = monitoringEnabled; + return this; + } + + public Builder instanceType(String instanceType) { + this.instanceType = instanceType; + return this; + } + + public Builder kernelId(String kernelId) { + this.kernelId = kernelId; + return this; + } + + public Builder keyName(String keyName) { + this.keyName = keyName; + return this; + } + + public Builder availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; + } + + public Builder ramdiskId(String ramdiskId) { + this.ramdiskId = ramdiskId; + return this; + } + + public Builder mapEBSSnapshotToDevice(String deviceName, String snapshotId, @Nullable Integer sizeInGib, + boolean deleteOnTermination) { + blockDeviceMappings.add(new MapEBSSnapshotToDevice(deviceName, snapshotId, sizeInGib, deleteOnTermination)); + return this; + } + + public Builder mapNewVolumeToDevice(String deviceName, int sizeInGib, boolean deleteOnTermination) { + blockDeviceMappings.add(new MapNewVolumeToDevice(deviceName, sizeInGib, deleteOnTermination)); + return this; + } + + public Builder mapEphemeralDeviceToDevice(String deviceName, String virtualName) { + blockDeviceMappings.add(new MapEphemeralDeviceToDevice(deviceName, virtualName)); + return this; + } + + public Builder blockDeviceMapping(BlockDeviceMapping blockDeviceMapping) { + this.blockDeviceMappings.add(checkNotNull(blockDeviceMapping, "blockDeviceMapping")); + return this; + } + + public Builder blockDeviceMappings(Iterable blockDeviceMappings) { + this.blockDeviceMappings.addAll(checkNotNull(blockDeviceMappings, "blockDeviceMappings")); + return this; + } + + public Builder userData(byte[] userData) { + this.userData = userData; + return this; + } + + public LaunchSpecification build() { + return new LaunchSpecification(instanceType, imageId, kernelId, ramdiskId, availabilityZone, keyName, + groupIds.build(), blockDeviceMappings.build(), monitoringEnabled, userData); + } + + public static Builder fromLaunchSpecification(LaunchSpecification in) { + return new Builder().instanceType(in.getInstanceType()).imageId(in.getImageId()).kernelId(in.getKernelId()) + .ramdiskId(in.getRamdiskId()).availabilityZone(in.getAvailabilityZone()).keyName(in.getKeyName()) + .groupIds(in.getGroupIds()).blockDeviceMappings(in.getBlockDeviceMappings()) + .monitoringEnabled(in.isMonitoringEnabled()).userData(in.getUserData()); + } + } + + protected final String instanceType; + protected final String imageId; + protected final String kernelId; + protected final String ramdiskId; + protected final String availabilityZone; + protected final String keyName; + protected final Set groupIds; + protected final Set blockDeviceMappings; + protected final Boolean monitoringEnabled; + protected final byte[] userData; + + public LaunchSpecification(String instanceType, String imageId, String kernelId, String ramdiskId, + String availabilityZone, String keyName, Iterable groupIds, + Iterable blockDeviceMappings, Boolean monitoringEnabled, byte[] userData) { + this.instanceType = checkNotNull(instanceType, "instanceType"); + this.imageId = checkNotNull(imageId, "imageId"); + this.kernelId = kernelId; + this.ramdiskId = ramdiskId; + this.availabilityZone = availabilityZone; + this.keyName = keyName; + this.groupIds = ImmutableSortedSet.copyOf(checkNotNull(groupIds, "groupIds")); + this.blockDeviceMappings = ImmutableSortedSet.copyOf(checkNotNull(blockDeviceMappings, "blockDeviceMappings")); + this.monitoringEnabled = monitoringEnabled; + this.userData = userData; + } + + /** + * Image ID of the AMI used to launch the instance. + */ + public String getImageId() { + return imageId; + } + + /** + * CloudWatch support + */ + public Boolean isMonitoringEnabled() { + return monitoringEnabled; + } + + /** + * The instance type. + */ + public String getInstanceType() { + return instanceType; + } + + /** + * Optional. Kernel associated with this instance. + */ + public String getKernelId() { + return kernelId; + } + + /** + * If this instance was launched with an associated key pair, this displays the key pair name. + */ + public String getKeyName() { + return keyName; + } + + /** + * The location where the instance launched. + */ + public String getAvailabilityZone() { + return availabilityZone; + } + + /** + * Optional. RAM disk associated with this instance. + */ + public String getRamdiskId() { + return ramdiskId; + } + + /** + * volumes mappings associated with the instance. + */ + public Set getBlockDeviceMappings() { + return blockDeviceMappings; + } + + /** + * Names of the security groups. + */ + public Set getGroupIds() { + return groupIds; + } + + /** + * User Data + */ + public byte[] getUserData() { + return userData; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((availabilityZone == null) ? 0 : availabilityZone.hashCode()); + result = prime * result + ((blockDeviceMappings == null) ? 0 : blockDeviceMappings.hashCode()); + result = prime * result + ((groupIds == null) ? 0 : groupIds.hashCode()); + result = prime * result + ((imageId == null) ? 0 : imageId.hashCode()); + result = prime * result + ((instanceType == null) ? 0 : instanceType.hashCode()); + result = prime * result + ((kernelId == null) ? 0 : kernelId.hashCode()); + result = prime * result + ((keyName == null) ? 0 : keyName.hashCode()); + result = prime * result + ((monitoringEnabled == null) ? 0 : monitoringEnabled.hashCode()); + result = prime * result + ((ramdiskId == null) ? 0 : ramdiskId.hashCode()); + result = prime * result + Arrays.hashCode(userData); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LaunchSpecification other = (LaunchSpecification) obj; + if (availabilityZone == null) { + if (other.availabilityZone != null) + return false; + } else if (!availabilityZone.equals(other.availabilityZone)) + return false; + if (blockDeviceMappings == null) { + if (other.blockDeviceMappings != null) + return false; + } else if (!blockDeviceMappings.equals(other.blockDeviceMappings)) + return false; + if (groupIds == null) { + if (other.groupIds != null) + return false; + } else if (!groupIds.equals(other.groupIds)) + return false; + if (imageId == null) { + if (other.imageId != null) + return false; + } else if (!imageId.equals(other.imageId)) + return false; + if (instanceType == null) { + if (other.instanceType != null) + return false; + } else if (!instanceType.equals(other.instanceType)) + return false; + if (kernelId == null) { + if (other.kernelId != null) + return false; + } else if (!kernelId.equals(other.kernelId)) + return false; + if (keyName == null) { + if (other.keyName != null) + return false; + } else if (!keyName.equals(other.keyName)) + return false; + if (monitoringEnabled == null) { + if (other.monitoringEnabled != null) + return false; + } else if (!monitoringEnabled.equals(other.monitoringEnabled)) + return false; + if (ramdiskId == null) { + if (other.ramdiskId != null) + return false; + } else if (!ramdiskId.equals(other.ramdiskId)) + return false; + if (!Arrays.equals(userData, other.userData)) + return false; + return true; + } + + public Builder toBuilder() { + return Builder.fromLaunchSpecification(this); + } + + @Override + public String toString() { + return "[instanceType=" + instanceType + ", imageId=" + imageId + ", kernelId=" + kernelId + ", ramdiskId=" + + ramdiskId + ", availabilityZone=" + availabilityZone + ", keyName=" + keyName + ", groupIds=" + groupIds + + ", blockDeviceMappings=" + blockDeviceMappings + ", monitoringEnabled=" + monitoringEnabled + + ", userData=" + (userData != null) + "]"; + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Spot.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Spot.java new file mode 100644 index 0000000000..a350c3f940 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Spot.java @@ -0,0 +1,173 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Date; + +/** + * @see + * @author Adrian Cole + */ +public class Spot implements Comparable { + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String region; + private String instanceType; + private String productDescription; + private float spotPrice; + private Date timestamp; + + public void clear() { + this.region = null; + this.instanceType = null; + this.productDescription = null; + this.spotPrice = 0.0f; + this.timestamp = null; + } + + public Builder region(String region) { + this.region = region; + return this; + } + + public Builder instanceType(String instanceType) { + this.instanceType = instanceType; + return this; + } + + public Builder productDescription(String productDescription) { + this.productDescription = productDescription; + return this; + } + + public Builder spotPrice(float spotPrice) { + this.spotPrice = spotPrice; + return this; + } + + public Builder timestamp(Date timestamp) { + this.timestamp = timestamp; + return this; + } + + public Spot build() { + return new Spot(region, instanceType, productDescription, spotPrice, timestamp); + } + } + + private final String region; + private final String instanceType; + private final String productDescription; + private final float spotPrice; + private final Date timestamp; + + public Spot(String region, String instanceType, String productDescription, float spotPrice, Date timestamp) { + this.region = checkNotNull(region, "region"); + this.instanceType = checkNotNull(instanceType, "instanceType"); + this.productDescription = checkNotNull(productDescription, "productDescription"); + this.spotPrice = spotPrice; + this.timestamp = checkNotNull(timestamp, "timestamp"); + } + + public String getRegion() { + return region; + } + + public String getInstanceType() { + return instanceType; + } + + public String getProductDescription() { + return productDescription; + } + + public float getSpotPrice() { + return spotPrice; + } + + public Date getTimestamp() { + return timestamp; + } + + @Override + public int compareTo(Spot o) { + return Float.compare(spotPrice, o.spotPrice); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((instanceType == null) ? 0 : instanceType.hashCode()); + result = prime * result + ((productDescription == null) ? 0 : productDescription.hashCode()); + result = prime * result + ((region == null) ? 0 : region.hashCode()); + result = prime * result + Float.floatToIntBits(spotPrice); + result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Spot other = (Spot) obj; + if (instanceType == null) { + if (other.instanceType != null) + return false; + } else if (!instanceType.equals(other.instanceType)) + return false; + if (productDescription == null) { + if (other.productDescription != null) + return false; + } else if (!productDescription.equals(other.productDescription)) + return false; + if (region == null) { + if (other.region != null) + return false; + } else if (!region.equals(other.region)) + return false; + if (Float.floatToIntBits(spotPrice) != Float.floatToIntBits(other.spotPrice)) + return false; + if (timestamp == null) { + if (other.timestamp != null) + return false; + } else if (!timestamp.equals(other.timestamp)) + return false; + return true; + } + + @Override + public String toString() { + return "[region=" + region + ", instanceType=" + instanceType + ", productDescription=" + productDescription + + ", spotPrice=" + spotPrice + ", timestamp=" + timestamp + "]"; + } + +} \ No newline at end of file diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/SpotInstanceRequest.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/SpotInstanceRequest.java new file mode 100644 index 0000000000..6e2cd8a149 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/SpotInstanceRequest.java @@ -0,0 +1,406 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Date; + +import com.google.common.base.CaseFormat; + +/** + * + * @author Adrian Cole + */ +public class SpotInstanceRequest implements Comparable { + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String region; + private String availabilityZoneGroup; + private Date createTime; + private String faultCode; + private String faultMessage; + private String instanceId; + private String launchGroup; + private LaunchSpecification launchSpecification; + private String productDescription; + private String id; + private float spotPrice; + private State state; + private Type type; + private Date validFrom; + private Date validUntil; + + public Builder clear() { + this.region = null; + this.availabilityZoneGroup = null; + this.createTime = null; + this.faultCode = null; + this.faultMessage = null; + this.instanceId = null; + this.launchGroup = null; + this.launchSpecification = null; + this.productDescription = null; + this.id = null; + this.spotPrice = 0; + this.state = null; + this.type = null; + this.validFrom = null; + this.validUntil = null; + return this; + } + + public Builder region(String region) { + this.region = region; + return this; + } + + public Builder availabilityZoneGroup(String availabilityZoneGroup) { + this.availabilityZoneGroup = availabilityZoneGroup; + return this; + } + + public Builder createTime(Date createTime) { + this.createTime = createTime; + return this; + } + + public Builder faultCode(String faultCode) { + this.faultCode = faultCode; + return this; + } + + public Builder faultMessage(String faultMessage) { + this.faultMessage = faultMessage; + return this; + } + + public Builder instanceId(String instanceId) { + this.instanceId = instanceId; + return this; + } + + public Builder launchGroup(String launchGroup) { + this.launchGroup = launchGroup; + return this; + } + + public Builder launchSpecification(LaunchSpecification launchSpecification) { + this.launchSpecification = launchSpecification; + return this; + } + + public Builder productDescription(String productDescription) { + this.productDescription = productDescription; + return this; + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder spotPrice(float spotPrice) { + this.spotPrice = spotPrice; + return this; + } + + public Builder state(State state) { + this.state = state; + return this; + } + + public Builder type(Type type) { + this.type = type; + return this; + } + + public Builder validFrom(Date validFrom) { + this.validFrom = validFrom; + return this; + } + + public Builder validUntil(Date validUntil) { + this.validUntil = validUntil; + return this; + } + + public SpotInstanceRequest build() { + return new SpotInstanceRequest(region, availabilityZoneGroup, createTime, faultCode, faultMessage, instanceId, + launchGroup, launchSpecification, productDescription, id, spotPrice, state, type, validFrom, validUntil); + } + } + + public enum Type { + ONE_TIME, PERSISTENT, UNRECOGNIZED; + + public String value() { + return (CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name())); + } + + @Override + public String toString() { + return value(); + } + + public static Type fromValue(String type) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(type, "type"))); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + public enum State { + OPEN, ACTIVE, CANCELLED, CLOSED, UNRECOGNIZED; + + public String value() { + return (CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name())); + } + + @Override + public String toString() { + return value(); + } + + public static State fromValue(String state) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(state, "type"))); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + private final String region; + private final String availabilityZoneGroup; + private final Date createTime; + private final String faultCode; + private final String faultMessage; + private final String instanceId; + private final String launchGroup; + private final LaunchSpecification launchSpecification; + private final String productDescription; + private final String id; + private final float spotPrice; + private final State state; + private final Type type; + private final Date validFrom; + private final Date validUntil; + + public SpotInstanceRequest(String region, String availabilityZoneGroup, Date createTime, String faultCode, + String faultMessage, String instanceId, String launchGroup, LaunchSpecification launchSpecification, + String productDescription, String id, float spotPrice, State state, Type type, Date validFrom, Date validUntil) { + this.region = checkNotNull(region, "region"); + this.availabilityZoneGroup = availabilityZoneGroup; + this.createTime = createTime; + this.faultCode = faultCode; + this.faultMessage = faultMessage; + this.instanceId = instanceId; + this.launchGroup = launchGroup; + this.launchSpecification = launchSpecification; + this.productDescription = productDescription; + this.id = checkNotNull(id, "id"); + this.spotPrice = spotPrice; + this.state = checkNotNull(state, "state"); + this.type = checkNotNull(type, "type"); + this.validFrom = validFrom; + this.validUntil = validUntil; + } + + /** + * @return spot instance requests are in a region + */ + public String getRegion() { + return region; + } + + public String getAvailabilityZoneGroup() { + return availabilityZoneGroup; + } + + public Date getCreateTime() { + return createTime; + } + + public String getFaultCode() { + return faultCode; + } + + public String getFaultMessage() { + return faultMessage; + } + + public String getInstanceId() { + return instanceId; + } + + public String getLaunchGroup() { + return launchGroup; + } + + public LaunchSpecification getLaunchSpecification() { + return launchSpecification; + } + + public String getProductDescription() { + return productDescription; + } + + public String getId() { + return id; + } + + public float getSpotPrice() { + return spotPrice; + } + + public State getState() { + return state; + } + + public Type getType() { + return type; + } + + public Date getValidFrom() { + return validFrom; + } + + public Date getValidUntil() { + return validUntil; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((availabilityZoneGroup == null) ? 0 : availabilityZoneGroup.hashCode()); + result = prime * result + ((createTime == null) ? 0 : createTime.hashCode()); + result = prime * result + ((faultCode == null) ? 0 : faultCode.hashCode()); + result = prime * result + ((faultMessage == null) ? 0 : faultMessage.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((instanceId == null) ? 0 : instanceId.hashCode()); + result = prime * result + ((launchGroup == null) ? 0 : launchGroup.hashCode()); + result = prime * result + ((launchSpecification == null) ? 0 : launchSpecification.hashCode()); + result = prime * result + ((productDescription == null) ? 0 : productDescription.hashCode()); + result = prime * result + ((region == null) ? 0 : region.hashCode()); + result = prime * result + Float.floatToIntBits(spotPrice); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((validFrom == null) ? 0 : validFrom.hashCode()); + result = prime * result + ((validUntil == null) ? 0 : validUntil.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SpotInstanceRequest other = (SpotInstanceRequest) obj; + if (availabilityZoneGroup == null) { + if (other.availabilityZoneGroup != null) + return false; + } else if (!availabilityZoneGroup.equals(other.availabilityZoneGroup)) + return false; + if (createTime == null) { + if (other.createTime != null) + return false; + } else if (!createTime.equals(other.createTime)) + return false; + if (faultCode == null) { + if (other.faultCode != null) + return false; + } else if (!faultCode.equals(other.faultCode)) + return false; + if (faultMessage == null) { + if (other.faultMessage != null) + return false; + } else if (!faultMessage.equals(other.faultMessage)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (instanceId == null) { + if (other.instanceId != null) + return false; + } else if (!instanceId.equals(other.instanceId)) + return false; + if (launchGroup == null) { + if (other.launchGroup != null) + return false; + } else if (!launchGroup.equals(other.launchGroup)) + return false; + if (launchSpecification == null) { + if (other.launchSpecification != null) + return false; + } else if (!launchSpecification.equals(other.launchSpecification)) + return false; + if (productDescription == null) { + if (other.productDescription != null) + return false; + } else if (!productDescription.equals(other.productDescription)) + return false; + if (region == null) { + if (other.region != null) + return false; + } else if (!region.equals(other.region)) + return false; + if (Float.floatToIntBits(spotPrice) != Float.floatToIntBits(other.spotPrice)) + return false; + if (type != other.type) + return false; + if (validFrom == null) { + if (other.validFrom != null) + return false; + } else if (!validFrom.equals(other.validFrom)) + return false; + if (validUntil == null) { + if (other.validUntil != null) + return false; + } else if (!validUntil.equals(other.validUntil)) + return false; + return true; + } + + @Override + public String toString() { + return "[region=" + region + ", id=" + id + ", spotPrice=" + spotPrice + ", state=" + state + + ", availabilityZoneGroup=" + availabilityZoneGroup + ", createTime=" + createTime + ", faultCode=" + + faultCode + ", type=" + type + ", instanceId=" + instanceId + ", launchGroup=" + launchGroup + + ", launchSpecification=" + launchSpecification + ", productDescription=" + productDescription + + ", validFrom=" + validFrom + ", validUntil=" + validUntil + "]"; + } + + @Override + public int compareTo(SpotInstanceRequest arg0) { + return createTime.compareTo(arg0.createTime); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/functions/SpotInstanceRequestToAWSRunningInstance.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/functions/SpotInstanceRequestToAWSRunningInstance.java new file mode 100644 index 0000000000..8c700e3eb7 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/functions/SpotInstanceRequestToAWSRunningInstance.java @@ -0,0 +1,64 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.functions; + +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.domain.AWSRunningInstance; +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.MonitoringState; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.ec2.domain.InstanceState; + +import com.google.common.base.Function; + +/** + * @author Adrian Cole + */ +@Singleton +public class SpotInstanceRequestToAWSRunningInstance implements Function { + + @Override + public AWSRunningInstance apply(SpotInstanceRequest request) { + if (request == null) + return null; + if (request.getState() != SpotInstanceRequest.State.OPEN) + return null; + AWSRunningInstance.Builder builder = AWSRunningInstance.builder(); + builder.spotInstanceRequestId(request.getId()); + builder.instanceId(request.getId()); + builder.instanceState(InstanceState.PENDING); + builder.region(request.getRegion()); + LaunchSpecification spec = request.getLaunchSpecification(); + builder.availabilityZone(spec.getAvailabilityZone()); + // TODO convert + // builder.devices(spec.getBlockDeviceMappings()); + builder.groupIds(spec.getGroupIds()); + builder.imageId(spec.getImageId()); + builder.instanceType(spec.getInstanceType()); + builder.kernelId(spec.getKernelId()); + builder.keyName(spec.getKeyName()); + builder.ramdiskId(spec.getRamdiskId()); + builder.monitoringState(Boolean.TRUE.equals(spec.isMonitoringEnabled()) ? MonitoringState.PENDING + : MonitoringState.DISABLED); + return builder.build(); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/AWSRunInstancesOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/AWSRunInstancesOptions.java index a4cf04183c..13860ce69e 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/AWSRunInstancesOptions.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/AWSRunInstancesOptions.java @@ -23,10 +23,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.Set; +import org.jclouds.aws.ec2.domain.LaunchSpecification; import org.jclouds.ec2.domain.BlockDeviceMapping; import org.jclouds.ec2.domain.InstanceType; import org.jclouds.ec2.options.RunInstancesOptions; +import com.google.common.collect.ImmutableSet; + /** * Contains options supported in the Form API for the RunInstances operation.

* Usage

The recommended way to instantiate a RunInstancesOptions object is to statically @@ -46,6 +49,7 @@ import org.jclouds.ec2.options.RunInstancesOptions; * /> */ public class AWSRunInstancesOptions extends RunInstancesOptions { + private LaunchSpecification.Builder launchSpecificationBuilder = LaunchSpecification.builder(); public static final AWSRunInstancesOptions NONE = new AWSRunInstancesOptions(); /** @@ -60,22 +64,15 @@ public class AWSRunInstancesOptions extends RunInstancesOptions { return this; } - String getPlacementGroup() { - return getFirstFormOrNull("Placement.GroupName"); - } - /** * Enables monitoring for the instance. */ public AWSRunInstancesOptions enableMonitoring() { formParameters.put("Monitoring.Enabled", "true"); + launchSpecificationBuilder.monitoringEnabled(true); return this; } - String getMonitoringEnabled() { - return getFirstFormOrNull("Monitoring.Enabled"); - } - /** * Specifies the subnet ID within which to launch the instance(s) for Amazon Virtual Private * Cloud. @@ -85,10 +82,6 @@ public class AWSRunInstancesOptions extends RunInstancesOptions { return this; } - String getSubnetId() { - return getFirstFormOrNull("SubnetId"); - } - public static class Builder extends RunInstancesOptions.Builder { /** @@ -175,46 +168,63 @@ public class AWSRunInstancesOptions extends RunInstancesOptions { @Override public AWSRunInstancesOptions withBlockDeviceMappings(Set mappings) { + launchSpecificationBuilder.blockDeviceMappings(mappings); return AWSRunInstancesOptions.class.cast(super.withBlockDeviceMappings(mappings)); } @Override public AWSRunInstancesOptions withKernelId(String kernelId) { + launchSpecificationBuilder.kernelId(kernelId); return AWSRunInstancesOptions.class.cast(super.withKernelId(kernelId)); } @Override public AWSRunInstancesOptions withKeyName(String keyName) { + launchSpecificationBuilder.keyName(keyName); return AWSRunInstancesOptions.class.cast(super.withKeyName(keyName)); } @Override public AWSRunInstancesOptions withRamdisk(String ramDiskId) { + launchSpecificationBuilder.ramdiskId(ramDiskId); return AWSRunInstancesOptions.class.cast(super.withRamdisk(ramDiskId)); } @Override public AWSRunInstancesOptions withSecurityGroup(String securityGroup) { + launchSpecificationBuilder.groupId(securityGroup); return AWSRunInstancesOptions.class.cast(super.withSecurityGroup(securityGroup)); } @Override public AWSRunInstancesOptions withSecurityGroups(Iterable securityGroups) { + launchSpecificationBuilder.groupIds(securityGroups); return AWSRunInstancesOptions.class.cast(super.withSecurityGroups(securityGroups)); } @Override public AWSRunInstancesOptions withSecurityGroups(String... securityGroups) { + launchSpecificationBuilder.groupIds(ImmutableSet.copyOf(securityGroups)); return AWSRunInstancesOptions.class.cast(super.withSecurityGroups(securityGroups)); } @Override public AWSRunInstancesOptions withUserData(byte[] unencodedData) { + launchSpecificationBuilder.userData(unencodedData); return AWSRunInstancesOptions.class.cast(super.withUserData(unencodedData)); } @Override public AWSRunInstancesOptions asType(String type) { + launchSpecificationBuilder.instanceType(type); return AWSRunInstancesOptions.class.cast(super.asType(type)); } + + public synchronized LaunchSpecification.Builder getLaunchSpecificationBuilder() { + try { + return launchSpecificationBuilder.imageId("fake").build().toBuilder().imageId(null); + } finally { + launchSpecificationBuilder.imageId(null); + } + } } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptions.java new file mode 100644 index 0000000000..5f4176dd87 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptions.java @@ -0,0 +1,119 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.options; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Date; + +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.ec2.domain.InstanceType; +import org.jclouds.ec2.options.internal.BaseEC2RequestOptions; + +/** + * Contains options supported in the Form API for the DescribeSpotPriceHistory operation.

+ * Usage

The recommended way to instantiate a DescribeSpotPriceHistoryOptions object is to + * statically import DescribeSpotPriceHistoryOptions.Builder.* and invoke a static creation method + * followed by an instance mutator (if needed): + *

+ * + * import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.* + *

+ * AWSEC2Client client = // get connection + * history = client.getSpotInstanceServices().describeSpotPriceHistoryInRegion(from(yesterday).instanceType("m1.small")); + * + * + * @author Adrian Cole + * @see + */ +public class DescribeSpotPriceHistoryOptions extends BaseEC2RequestOptions { + public static final DescribeSpotPriceHistoryOptions NONE = new DescribeSpotPriceHistoryOptions(); + private static final DateService service = new SimpleDateFormatDateService(); + + /** + * Start date and time of the Spot Instance price history data. + */ + public DescribeSpotPriceHistoryOptions from(Date start) { + formParameters.put("StartTime", service.iso8601DateFormat(checkNotNull(start, "start"))); + return this; + } + + /** + * End date and time of the Spot Instance price history data. + */ + public DescribeSpotPriceHistoryOptions to(Date end) { + formParameters.put("EndTime", service.iso8601DateFormat(checkNotNull(end, "end"))); + return this; + } + + /** + * Specifies the instance type to return. + */ + public DescribeSpotPriceHistoryOptions instanceType(String type) { + formParameters.put("InstanceType.1", checkNotNull(type, "type")); + return this; + } + + /** + * The description of the AMI. + */ + public DescribeSpotPriceHistoryOptions productDescription(String description) { + formParameters.put("ProductDescription", checkNotNull(description, "description")); + return this; + } + + public static class Builder { + /** + * @see DescribeSpotPriceHistoryOptions#from + */ + public static DescribeSpotPriceHistoryOptions from(Date start) { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + return options.from(start); + } + + /** + * @see DescribeSpotPriceHistoryOptions#to + */ + public static DescribeSpotPriceHistoryOptions to(Date end) { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + return options.to(end); + } + + /** + * @see DescribeSpotPriceHistoryOptions#instanceType(InstanceType) + */ + public static DescribeSpotPriceHistoryOptions instanceType(String instanceType) { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + return options.instanceType(instanceType); + } + + /** + * @see DescribeSpotPriceHistoryOptions#productDescription(String) + */ + public static DescribeSpotPriceHistoryOptions productDescription(String description) { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + return options.productDescription(description); + } + + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptions.java new file mode 100644 index 0000000000..e8e30eb265 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptions.java @@ -0,0 +1,142 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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 validUntil 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.aws.ec2.options; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Date; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.ec2.options.internal.BaseEC2RequestOptions; + +/** + * Contains options supported in the Form API for the RequestSpotInstances operation.

+ * Usage

The recommended way validUntil instantiate a RequestSpotInstancesOptions object is + * validUntil statically import RequestSpotInstancesOptions.Builder.* and invoke a static creation + * method followed by an instance mutator (if needed): + *

+ * + * import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.* + *

+ * AWSEC2Client client = // get connection + * history = client.getSpotInstanceServices().requestSpotInstancesInRegion("us-east-1",validFrom(yesterday).type("m1.small")); + * + * + * @author Adrian Cole + * @see + */ +public class RequestSpotInstancesOptions extends BaseEC2RequestOptions { + public static final RequestSpotInstancesOptions NONE = new RequestSpotInstancesOptions(); + private static final DateService service = new SimpleDateFormatDateService(); + + /** + * Start date of the request. If this is a one-time request, the request becomes active at this + * date and time and remains active until all instances launch, the request expires, or the + * request is canceled. If the request is persistent, the request becomes active at this date and + * time and remains active until it expires or is canceled. + */ + public RequestSpotInstancesOptions validFrom(Date start) { + formParameters.put("ValidFrom", service.iso8601DateFormat(checkNotNull(start, "start"))); + return this; + } + + /** + * End date of the request. If this is a one-time request, the request remains active until all + * instances launch, the request is canceled, or this date is reached. If the request is + * persistent, it remains active until it is canceled or this date and time is reached. + */ + public RequestSpotInstancesOptions validUntil(Date end) { + formParameters.put("ValidUntil", service.iso8601DateFormat(checkNotNull(end, "end"))); + return this; + } + + /** + * Specifies the Spot Instance type. + */ + public RequestSpotInstancesOptions type(SpotInstanceRequest.Type type) { + formParameters.put("Type", checkNotNull(type, "type").toString()); + return this; + } + + /** + * Specifies the instance launch group. Launch groups are Spot Instances that launch together and + * terminate together. + */ + public RequestSpotInstancesOptions launchGroup(String launchGroup) { + formParameters.put("LaunchGroup", checkNotNull(launchGroup, "launchGroup")); + return this; + } + + /** + * Specifies the Availability Zone group. If you specify the same Availability Zone group for all + * Spot Instance requests, all Spot Instances are launched in the same Availability Zone. + */ + public RequestSpotInstancesOptions availabilityZoneGroup(String availabilityZoneGroup) { + formParameters.put("AvailabilityZoneGroup", checkNotNull(availabilityZoneGroup, "availabilityZoneGroup")); + return this; + } + + public static class Builder { + /** + * @see RequestSpotInstancesOptions#validFrom + */ + public static RequestSpotInstancesOptions validFrom(Date start) { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + return options.validFrom(start); + } + + /** + * @see RequestSpotInstancesOptions#validUntil + */ + public static RequestSpotInstancesOptions validUntil(Date end) { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + return options.validUntil(end); + } + + /** + * @see RequestSpotInstancesOptions#type + */ + public static RequestSpotInstancesOptions type(SpotInstanceRequest.Type type) { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + return options.type(type); + } + + /** + * @see RequestSpotInstancesOptions#launchGroup(String) + */ + public static RequestSpotInstancesOptions launchGroup(String launchGroup) { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + return options.launchGroup(launchGroup); + } + + /** + * @see RequestSpotInstancesOptions#availabilityZoneGroup + */ + public static RequestSpotInstancesOptions availabilityZoneGroup(String availabilityZoneGroup) { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + return options.availabilityZoneGroup(availabilityZoneGroup); + } + + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/predicates/SpotInstanceRequestActive.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/predicates/SpotInstanceRequestActive.java new file mode 100644 index 0000000000..2937ab0c0e --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/predicates/SpotInstanceRequestActive.java @@ -0,0 +1,78 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.predicates; + +import java.util.NoSuchElementException; + +import javax.annotation.Resource; +import javax.inject.Singleton; + +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.logging.Logger; +import org.jclouds.rest.ResourceNotFoundException; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +/** + * + * + * @author Adrian Cole + */ +@Singleton +public class SpotInstanceRequestActive implements Predicate { + + private final AWSEC2Client client; + + @Resource + protected Logger logger = Logger.NULL; + + @Inject + public SpotInstanceRequestActive(AWSEC2Client client) { + this.client = client; + } + + public boolean apply(SpotInstanceRequest spot) { + logger.trace("looking for state on spot %s", spot); + try { + spot = refresh(spot); + logger.trace("%s: looking for spot state %s: currently: %s", spot.getId(), SpotInstanceRequest.State.ACTIVE, + spot.getState()); + if (spot.getState() == SpotInstanceRequest.State.CANCELLED + || spot.getState() == SpotInstanceRequest.State.CLOSED) + throw new IllegalStateException(String.format("spot request %s %s", spot.getId(), spot.getState())); + if (spot.getFaultCode() != null) + throw new IllegalStateException(String.format("spot request %s fault code(%s) message(%s)", spot.getId(), + spot.getFaultCode(), spot.getFaultMessage())); + return spot.getState() == SpotInstanceRequest.State.ACTIVE; + } catch (ResourceNotFoundException e) { + return false; + } catch (NoSuchElementException e) { + return false; + } + } + + private SpotInstanceRequest refresh(SpotInstanceRequest spot) { + return Iterables.getOnlyElement(client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion( + spot.getRegion(), spot.getId())); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClient.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClient.java new file mode 100644 index 0000000000..3d918adc12 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClient.java @@ -0,0 +1,128 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.services; + +import static org.jclouds.aws.reference.FormParameters.ACTION; +import static org.jclouds.aws.reference.FormParameters.VERSION; + +import java.util.Set; + +import javax.annotation.Nullable; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.jclouds.aws.ec2.AWSEC2AsyncClient; +import org.jclouds.aws.ec2.binders.BindLaunchSpecificationToFormParams; +import org.jclouds.aws.ec2.binders.BindSpotInstanceRequestIdsToIndexedFormParams; +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.Spot; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +import org.jclouds.aws.ec2.xml.DescribeSpotPriceHistoryResponseHandler; +import org.jclouds.aws.ec2.xml.SpotInstanceHandler; +import org.jclouds.aws.ec2.xml.SpotInstancesHandler; +import org.jclouds.aws.filters.FormSigner; +import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.EndpointParam; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.FormParams; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.VirtualHost; +import org.jclouds.rest.annotations.XMLResponseParser; +import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides access to EC2 Spot Instances via their REST API. + *

+ * + * @author Adrian Cole + */ +@RequestFilters(FormSigner.class) +@FormParams(keys = VERSION, values = AWSEC2AsyncClient.VERSION) +@VirtualHost +public interface SpotInstanceAsyncClient { + + /** + * @see SpotInstanceClient#describeSpotInstanceRequestsInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DescribeSpotInstanceRequests") + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + @XMLResponseParser(SpotInstancesHandler.class) + ListenableFuture> describeSpotInstanceRequestsInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @BinderParam(BindSpotInstanceRequestIdsToIndexedFormParams.class) String... requestIds); + + /** + * @see SpotInstanceClient#requestSpotInstanceInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "RequestSpotInstances") + @XMLResponseParser(SpotInstanceHandler.class) + ListenableFuture requestSpotInstanceInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @FormParam("SpotPrice") float spotPrice, @FormParam("LaunchSpecification.ImageId") String imageId, + @FormParam("LaunchSpecification.InstanceType") String instanceType); + + /** + * @see SpotInstanceClient#requestSpotInstancesInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "RequestSpotInstances") + @XMLResponseParser(SpotInstancesHandler.class) + ListenableFuture> requestSpotInstancesInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @FormParam("SpotPrice") float spotPrice, @FormParam("InstanceCount") int instanceCount, + @BinderParam(BindLaunchSpecificationToFormParams.class) LaunchSpecification launchSpec, + RequestSpotInstancesOptions... options); + + /** + * @see SpotInstanceClient#describeSpotPriceHistoryInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DescribeSpotPriceHistory") + @XMLResponseParser(DescribeSpotPriceHistoryResponseHandler.class) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> describeSpotPriceHistoryInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + DescribeSpotPriceHistoryOptions... options); + + /** + * @see SpotInstanceClient#cancelSpotInstanceRequestsInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "CancelSpotInstanceRequests") + @ExceptionParser(ReturnVoidOnNotFoundOr404.class) + ListenableFuture cancelSpotInstanceRequestsInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @BinderParam(BindSpotInstanceRequestIdsToIndexedFormParams.class) String... requestIds); + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceClient.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceClient.java new file mode 100644 index 0000000000..2771d4153b --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceClient.java @@ -0,0 +1,159 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.services; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.Spot; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +import org.jclouds.concurrent.Timeout; + +/** + * Provides Spot Instance services for EC2. For more information, refer to the Amazon EC2 Developer + * Guide. + *

+ * + * @author Adrian Cole + */ +@Timeout(duration = 45, timeUnit = TimeUnit.SECONDS) +public interface SpotInstanceClient { + /** + * Describes Spot Instance requests. Spot Instances are instances that Amazon EC2 starts on your + * behalf when the maximum price that you specify exceeds the current Spot Price. Amazon EC2 + * periodically sets the Spot Price based on available Spot Instance capacity and current spot + * instance requests. For conceptual information about Spot Instances, refer to the Amazon + * Elastic Compute Cloud Developer Guide or Amazon Elastic Compute Cloud User Guide. + * + * @param region + * Region where the spot instance service is running + * @param requestIds + * Specifies the ID of the Spot Instance request. + * + * @see #requestSpotInstancesInRegion + * @see #cancelSpotInstanceRequestsInRegion + * @see #describeSpotPriceHistoryInRegion + * @see + * @return TODO + */ + Set describeSpotInstanceRequestsInRegion(@Nullable String region, String... requestIds); + + /** + * request a single spot instance + * + * @param region + * Region where the spot instance service is running + * @param spotPrice + * Specifies the maximum hourly price for any Spot Instance launched to fulfill the + * request. + * @param imageId + * The AMI ID. + * @param instanceType + * The instance type (ex. m1.small) + * @return spot instance request + * @see #requestSpotInstancesInRegion + */ + SpotInstanceRequest requestSpotInstanceInRegion(@Nullable String region, float spotPrice, String imageId, + String instanceType); + + /** + * Creates a Spot Instance request. Spot Instances are instances that Amazon EC2 starts on your + * behalf when the maximum price that you specify exceeds the current Spot Price. Amazon EC2 + * periodically sets the Spot Price based on available Spot Instance capacity and current spot + * instance requests. For conceptual information about Spot Instances, refer to the Amazon + * Elastic Compute Cloud Developer Guide or Amazon Elastic Compute Cloud User Guide. + * + * @param region + * Region where the spot instance service is running + * @param spotPrice + * Specifies the maximum hourly price for any Spot Instance launched to fulfill the + * request. + * @param instanceCount + * number of instances to request + * @param launchSpec + * includes at least The AMI ID and instance type (ex. m1.small) + * @param options + * options including expiration time or grouping + * + * @see #describeSpotInstanceRequestsInRegion + * @see #cancelSpotInstanceRequestsInRegion + * @see #describeSpotPriceHistoryInRegion + * @see + * @return set of spot instance requests + */ + Set requestSpotInstancesInRegion(@Nullable String region, float spotPrice, int instanceCount, + LaunchSpecification launchSpec, RequestSpotInstancesOptions... options); + + /** + * + * Describes Spot Price history. Spot Instances are instances that Amazon EC2 starts on your + * behalf when the maximum price that you specify exceeds the current Spot Price. Amazon EC2 + * periodically sets the Spot Price based on available Spot Instance capacity and current spot + * instance requests. For conceptual information about Spot Instances, refer to the Amazon + * Elastic Compute Cloud Developer Guide or Amazon Elastic Compute Cloud User Guide. + * + * @param region + * Region where the spot instance service is running + * @param options + * options to control the list + * + * @see #describeSpotInstanceRequestsInRegion + * @see #requestSpotInstancesInRegion + * @see #cancelSpotInstanceRequestsInRegion + * @see + * @return TODO + */ + @Timeout(duration = 2, timeUnit = TimeUnit.MINUTES) + Set describeSpotPriceHistoryInRegion(@Nullable String region, DescribeSpotPriceHistoryOptions... options); + + /** + * Cancels one or more Spot Instance requests. Spot Instances are instances that Amazon EC2 + * starts on your behalf when the maximum price that you specify exceeds the current Spot Price. + * Amazon EC2 periodically sets the Spot Price based on available Spot Instance capacity and + * current spot instance requests. For conceptual information about Spot Instances, refer to the + * Amazon Elastic Compute Cloud Developer Guide or Amazon Elastic Compute Cloud User Guide. + * + * @param region + * Region where the spot instance service is running + * @param requestIds + * Specifies the ID of the Spot Instance request. + * + * @see #describeSpotInstanceRequestsInRegion + * @see #requestSpotInstancesInRegion + * @see #describeSpotPriceHistoryInRegion + * @see + * @return TODO + */ + String cancelSpotInstanceRequestsInRegion(@Nullable String region, String... requestIds); + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandler.java new file mode 100644 index 0000000000..210ca4c711 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandler.java @@ -0,0 +1,79 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.xml; + +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.Spot; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.ParseSax.HandlerWithResult; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; + +/** + * @author Adrian Cole + */ +public class DescribeSpotPriceHistoryResponseHandler extends + ParseSax.HandlerWithResult> { + + private Builder spots = ImmutableSet.builder(); + private final SpotHandler spotHandler; + + @Inject + public DescribeSpotPriceHistoryResponseHandler(SpotHandler spotHandler) { + this.spotHandler = spotHandler; + } + + public Set getResult() { + return spots.build(); + } + + @Override + public HandlerWithResult> setContext(HttpRequest request) { + spotHandler.setContext(request); + return super.setContext(request); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (!qName.equals("item")) + spotHandler.startElement(uri, localName, qName, attributes); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("item")) { + spots.add(spotHandler.getResult()); + } + spotHandler.endElement(uri, localName, qName); + } + + public void characters(char ch[], int start, int length) { + spotHandler.characters(ch, start, length); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/LaunchSpecificationHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/LaunchSpecificationHandler.java new file mode 100644 index 0000000000..a3e14e2540 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/LaunchSpecificationHandler.java @@ -0,0 +1,115 @@ +package org.jclouds.aws.ec2.xml; + +import javax.annotation.Resource; +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.LaunchSpecification.Builder; +import org.jclouds.date.DateService; +import org.jclouds.ec2.domain.BlockDeviceMapping; +import org.jclouds.http.functions.ParseSax.HandlerForGeneratedRequestWithResult; +import org.jclouds.location.Region; +import org.jclouds.logging.Logger; +import org.xml.sax.Attributes; + +/** + * + * @author Adrian Cole + */ +public class LaunchSpecificationHandler extends HandlerForGeneratedRequestWithResult { + + @Resource + protected Logger logger = Logger.NULL; + + protected final DateService dateService; + protected final String defaultRegion; + protected final Builder builder; + protected final BlockDeviceMapping.Builder blockDeviceMappingBuilder; + + @Inject + public LaunchSpecificationHandler(DateService dateService, @Region String defaultRegion, + LaunchSpecification.Builder builder, BlockDeviceMapping.Builder blockDeviceMappingBuilder) { + this.dateService = dateService; + this.defaultRegion = defaultRegion; + this.builder = builder; + this.blockDeviceMappingBuilder = blockDeviceMappingBuilder; + } + + protected String currentOrNull() { + String returnVal = currentText.toString().trim(); + return returnVal.equals("") ? null : returnVal; + } + + protected StringBuilder currentText = new StringBuilder(); + + private boolean inBlockDeviceMapping; + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if (qName.equals("blockDeviceMapping")) { + inBlockDeviceMapping = true; + } + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("blockDeviceMapping")) { + inBlockDeviceMapping = false; + } else if (qName.equals("item") && inBlockDeviceMapping) { + try { + builder.blockDeviceMapping(blockDeviceMappingBuilder.build()); + } finally { + blockDeviceMappingBuilder.clear(); + } + } else if (qName.equals("deviceName")) { + blockDeviceMappingBuilder.deviceName(currentOrNull()); + } else if (qName.equals("virtualName")) { + blockDeviceMappingBuilder.virtualName(currentOrNull()); + } else if (qName.equals("snapshotId")) { + blockDeviceMappingBuilder.snapshotId(currentOrNull()); + } else if (qName.equals("volumeSize")) { + String volumeSize = currentOrNull(); + if (volumeSize != null) + blockDeviceMappingBuilder.sizeInGib(Integer.parseInt(volumeSize)); + } else if (qName.equals("noDevice")) { + String noDevice = currentOrNull(); + if (noDevice != null) + blockDeviceMappingBuilder.noDevice(Boolean.parseBoolean(noDevice)); + } else if (qName.equals("deleteOnTermination")) { + String deleteOnTermination = currentOrNull(); + if (deleteOnTermination != null) + blockDeviceMappingBuilder.deleteOnTermination(Boolean.parseBoolean(deleteOnTermination)); + } else if (qName.equals("groupId")) { + builder.groupId(currentOrNull()); + } else if (qName.equals("imageId")) { + builder.imageId(currentOrNull()); + } else if (qName.equals("instanceType")) { + builder.instanceType(currentOrNull()); + } else if (qName.equals("kernelId")) { + builder.kernelId(currentOrNull()); + } else if (qName.equals("keyName")) { + builder.keyName(currentOrNull()); + } else if (qName.equals("availabilityZone")) { + builder.availabilityZone(currentOrNull()); + } else if (qName.equals("ramdiskId")) { + builder.ramdiskId(currentOrNull()); + } else if (qName.equals("enabled")) { + String monitoringEnabled = currentOrNull(); + if (monitoringEnabled != null) + builder.monitoringEnabled(new Boolean(monitoringEnabled)); + } + currentText = new StringBuilder(); + } + + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } + + @Override + public LaunchSpecification getResult() { + try { + return builder.build(); + } finally { + builder.clear(); + } + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/PlacementGroupHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/PlacementGroupHandler.java index e466204b6c..8eb7615cef 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/PlacementGroupHandler.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/PlacementGroupHandler.java @@ -24,7 +24,6 @@ import javax.inject.Inject; import org.jclouds.aws.ec2.domain.PlacementGroup; import org.jclouds.aws.ec2.domain.PlacementGroup.State; import org.jclouds.aws.util.AWSUtils; -import org.jclouds.date.DateService; import org.jclouds.http.functions.ParseSax; import org.jclouds.location.Region; @@ -36,8 +35,6 @@ public class PlacementGroupHandler extends ParseSax.HandlerForGeneratedRequestWithResult { private StringBuilder currentText = new StringBuilder(); - @Inject - protected DateService dateService; @Inject @Region String defaultRegion; diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotHandler.java new file mode 100644 index 0000000000..de6d3da33f --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotHandler.java @@ -0,0 +1,75 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.xml; + +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.Spot; +import org.jclouds.aws.util.AWSUtils; +import org.jclouds.date.DateService; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.location.Region; + +/** + * + * @author Adrian Cole + */ +public class SpotHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + private StringBuilder currentText = new StringBuilder(); + + protected final DateService dateService; + protected final String defaultRegion; + + @Inject + public SpotHandler(DateService dateService, @Region String defaultRegion) { + this.dateService = dateService; + this.defaultRegion = defaultRegion; + } + + private Spot.Builder builder = Spot.builder(); + + public Spot getResult() { + try { + String region = getRequest() == null ? null : AWSUtils.findRegionInArgsOrNull(getRequest()); + if (region == null) + region = defaultRegion; + return builder.region(region).build(); + } finally { + builder.clear(); + } + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("instanceType")) { + builder.instanceType(currentText.toString().trim()); + } else if (qName.equals("productDescription")) { + builder.productDescription(currentText.toString().trim()); + } else if (qName.equals("spotPrice")) { + builder.spotPrice(Float.parseFloat(currentText.toString().trim())); + } else if (qName.equals("timestamp")) { + builder.timestamp(dateService.iso8601DateParse(currentText.toString().trim())); + } + currentText = new StringBuilder(); + } + + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceHandler.java new file mode 100644 index 0000000000..c4bde57afe --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceHandler.java @@ -0,0 +1,125 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.xml; + +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest.Builder; +import org.jclouds.aws.util.AWSUtils; +import org.jclouds.date.DateService; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.location.Region; +import org.xml.sax.Attributes; + +/** + * + * @author Adrian Cole + */ +public class SpotInstanceHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + private StringBuilder currentText = new StringBuilder(); + + protected final DateService dateService; + protected final String defaultRegion; + protected final Builder builder; + protected boolean inLaunchSpecification; + protected final LaunchSpecificationHandler launchSpecificationHandler; + + @Inject + public SpotInstanceHandler(DateService dateService, @Region String defaultRegion, + LaunchSpecificationHandler launchSpecificationHandler, SpotInstanceRequest.Builder builder) { + this.dateService = dateService; + this.defaultRegion = defaultRegion; + this.launchSpecificationHandler = launchSpecificationHandler; + this.builder = builder; + } + + protected String currentOrNull() { + String returnVal = currentText.toString().trim(); + return returnVal.equals("") ? null : returnVal; + } + + public SpotInstanceRequest getResult() { + try { + String region = getRequest() != null ? AWSUtils.findRegionInArgsOrNull(getRequest()) : null; + if (region == null) + region = defaultRegion; + return builder.region(region).build(); + } finally { + builder.clear(); + } + } + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if (qName.equals("launchSpecification")) { + inLaunchSpecification = true; + } + if (inLaunchSpecification) + launchSpecificationHandler.startElement(uri, name, qName, attrs); + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("launchSpecification")) { + inLaunchSpecification = false; + builder.launchSpecification(launchSpecificationHandler.getResult()); + } + if (inLaunchSpecification) { + launchSpecificationHandler.endElement(uri, name, qName); + } else if (qName.equals("spotInstanceRequestId")) { + builder.id(currentOrNull()); + } else if (qName.equals("instanceId")) { + builder.instanceId(currentOrNull()); + } else if (qName.equals("availabilityZoneGroup")) { + builder.availabilityZoneGroup(currentOrNull()); + } else if (qName.equals("launchGroup")) { + builder.launchGroup(currentOrNull()); + } else if (qName.equals("code")) { + builder.faultCode(currentOrNull()); + } else if (qName.equals("message")) { + builder.faultMessage(currentOrNull()); + } else if (qName.equals("spotPrice")) { + String price = currentOrNull(); + if (price != null) + builder.spotPrice(Float.parseFloat(price)); + } else if (qName.equals("type")) { + String type = currentOrNull(); + if (type != null) + builder.type(SpotInstanceRequest.Type.fromValue(type)); + } else if (qName.equals("state")) { + String state = currentOrNull(); + if (state != null) + builder.state(SpotInstanceRequest.State.fromValue(state)); + } else if (qName.equals("createTime")) { + String createTime = currentOrNull(); + if (createTime != null) + builder.createTime(dateService.iso8601DateParse(createTime)); + } else if (qName.equals("productDescription")) { + builder.productDescription(currentOrNull()); + } + currentText = new StringBuilder(); + } + + public void characters(char ch[], int start, int length) { + if (inLaunchSpecification) + launchSpecificationHandler.characters(ch, start, length); + else + currentText.append(ch, start, length); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstancesHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstancesHandler.java new file mode 100644 index 0000000000..d5022fe8b4 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstancesHandler.java @@ -0,0 +1,84 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.xml; + +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.ParseSax.HandlerWithResult; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; + +/** + * @author Adrian Cole + */ +public class SpotInstancesHandler extends ParseSax.HandlerWithResult> { + + private final Builder spotRequests = ImmutableSet. builder(); + private final SpotInstanceHandler spotRequestHandler; + private int itemDepth; + + @Inject + public SpotInstancesHandler(SpotInstanceHandler spotRequestHandler) { + this.spotRequestHandler = spotRequestHandler; + } + + public Set getResult() { + return spotRequests.build(); + } + + @Override + public HandlerWithResult> setContext(HttpRequest request) { + spotRequestHandler.setContext(request); + return super.setContext(request); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("item")) + itemDepth++; + if (itemDepth >= 1) + spotRequestHandler.startElement(uri, localName, qName, attributes); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("item") && itemDepth == 1) { + spotRequests.add(spotRequestHandler.getResult()); + } + if (qName.equals("item")) + itemDepth--; + if (itemDepth >= 1) + spotRequestHandler.endElement(uri, localName, qName); + } + + public void characters(char ch[], int start, int length) { + if (itemDepth >= 1) + spotRequestHandler.characters(ch, start, length); + } + +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParamsTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParamsTest.java new file mode 100644 index 0000000000..589c23c9c5 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParamsTest.java @@ -0,0 +1,60 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.binders; + +import static org.testng.Assert.assertEquals; + +import java.net.UnknownHostException; + +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.ec2.domain.InstanceType; +import org.jclouds.encryption.internal.Base64; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class BindLaunchSpecificationToFormParamsTest { + BindLaunchSpecificationToFormParams binder = new BindLaunchSpecificationToFormParams(); + + @Test + public void testApplyWithBlockDeviceMappings() throws UnknownHostException { + LaunchSpecification spec = LaunchSpecification.builder().instanceType(InstanceType.T1_MICRO).imageId("ami-123") + .mapNewVolumeToDevice("/dev/sda1", 120, true).build(); + + assertEquals(binder.apply(spec), ImmutableMap.of("LaunchSpecification.InstanceType", "t1.micro", + "LaunchSpecification.ImageId", "ami-123", "LaunchSpecification.BlockDeviceMapping.1.DeviceName", + "/dev/sda1", "LaunchSpecification.BlockDeviceMapping.1.Ebs.VolumeSize", "120", + "LaunchSpecification.BlockDeviceMapping.1.Ebs.DeleteOnTermination", "true")); + } + + @Test + public void testApplyWithUserData() throws UnknownHostException { + LaunchSpecification spec = LaunchSpecification.builder().instanceType(InstanceType.T1_MICRO).imageId("ami-123") + .userData("hello".getBytes()).build(); + + assertEquals(binder.apply(spec), ImmutableMap.of("LaunchSpecification.InstanceType", "t1.micro", + "LaunchSpecification.ImageId", "ami-123", "LaunchSpecification.UserData", + Base64.encodeBytes("hello".getBytes()))); + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceLiveTest.java index b02b8e7dea..d9ec64ff43 100644 --- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceLiveTest.java +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceLiveTest.java @@ -59,13 +59,13 @@ public class AWSEC2ComputeServiceLiveTest extends EC2ComputeServiceLiveTest { @Test(enabled = true, dependsOnMethods = "testCompareSizes") public void testExtendedOptionsAndLogin() throws Exception { SecurityGroupClient securityGroupClient = EC2Client.class.cast(context.getProviderSpecificContext().getApi()) - .getSecurityGroupServices(); + .getSecurityGroupServices(); KeyPairClient keyPairClient = EC2Client.class.cast(context.getProviderSpecificContext().getApi()) - .getKeyPairServices(); + .getKeyPairServices(); InstanceClient instanceClient = EC2Client.class.cast(context.getProviderSpecificContext().getApi()) - .getInstanceServices(); + .getInstanceServices(); String group = this.group + "o"; @@ -76,6 +76,7 @@ public class AWSEC2ComputeServiceLiveTest extends EC2ComputeServiceLiveTest { options.as(AWSEC2TemplateOptions.class).securityGroups(group); options.as(AWSEC2TemplateOptions.class).keyPair(group); options.as(AWSEC2TemplateOptions.class).enableMonitoring(); + options.as(AWSEC2TemplateOptions.class).spotPrice(0.3f); String startedId = null; try { @@ -117,17 +118,17 @@ public class AWSEC2ComputeServiceLiveTest extends EC2ComputeServiceLiveTest { // } // make sure we made our dummy group and also let in the user's group - assertEquals(Sets.newTreeSet(instance.getGroupIds()), - ImmutableSortedSet. of("jclouds#" + group + "#" + instance.getRegion(), group)); + assertEquals(Sets.newTreeSet(instance.getGroupIds()), ImmutableSortedSet. of("jclouds#" + group + "#" + + instance.getRegion(), group)); // make sure our dummy group has no rules SecurityGroup secgroup = Iterables.getOnlyElement(securityGroupClient.describeSecurityGroupsInRegion(null, - "jclouds#" + group + "#" + instance.getRegion())); + "jclouds#" + group + "#" + instance.getRegion())); assert secgroup.getIpPermissions().size() == 0 : secgroup; // try to run a script with the original keyPair - runScriptWithCreds(group, first.getOperatingSystem(), - new Credentials(first.getCredentials().identity, result.getKeyMaterial())); + runScriptWithCreds(group, first.getOperatingSystem(), new Credentials(first.getCredentials().identity, result + .getKeyMaterial())); } finally { client.destroyNodesMatching(NodePredicates.inGroup(group)); @@ -151,13 +152,13 @@ public class AWSEC2ComputeServiceLiveTest extends EC2ComputeServiceLiveTest { } InstanceClient instanceClient = EC2Client.class.cast(context.getProviderSpecificContext().getApi()) - .getInstanceServices(); + .getInstanceServices(); String group = this.group + "g"; TemplateOptions options = client.templateOptions(); - options.as(AWSEC2TemplateOptions.class).subnetId(subnetId); + options.as(AWSEC2TemplateOptions.class).subnetId(subnetId).spotPrice(0.3f); String startedId = null; String nodeId = null; diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/functions/SpotInstanceRequestToAWSRunningInstanceTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/functions/SpotInstanceRequestToAWSRunningInstanceTest.java new file mode 100644 index 0000000000..e4703bbedb --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/functions/SpotInstanceRequestToAWSRunningInstanceTest.java @@ -0,0 +1,74 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. .info@cloudconscious.com(" + * + * ==================================================================== + * Licensed 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.aws.ec2.functions; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.aws.ec2.domain.AWSRunningInstance; +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.MonitoringState; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.functions.SpotInstanceRequestToAWSRunningInstance; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.ec2.domain.InstanceState; +import org.testng.annotations.Test; + +/** + * Tests behavior of {@code SpotInstanceRequestToAWSRunningInstance} + * + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "SpotInstanceRequestToAWSRunningInstanceTest") +public class SpotInstanceRequestToAWSRunningInstanceTest { + + public void testConvert() { + + SpotInstanceRequest input = SpotInstanceRequest.builder().region("us-east-1").id("sir-228e6406") + .spotPrice(0.001f).type(SpotInstanceRequest.Type.ONE_TIME).state(SpotInstanceRequest.State.OPEN) + .launchSpecification( + LaunchSpecification.builder().imageId("ami-595a0a1c").groupId("default").instanceType( + "m1.large").mapNewVolumeToDevice("/dev/sda1", 1, true).mapEBSSnapshotToDevice( + "/dev/sda2", "snap-1ea27576", 1, true).mapEphemeralDeviceToDevice("/dev/sda3", "vre1") + .monitoringEnabled(false).build()).createTime( + new SimpleDateFormatDateService().iso8601DateParse("2011-03-08T03:30:36.000Z")) + .productDescription("Linux/UNIX").build(); + + assertEquals(new SpotInstanceRequestToAWSRunningInstance().apply(input), AWSRunningInstance.builder().region( + "us-east-1").instanceId("sir-228e6406").spotInstanceRequestId("sir-228e6406").instanceState( + InstanceState.PENDING).imageId("ami-595a0a1c").groupId("default").instanceType("m1.large") + .monitoringState(MonitoringState.PENDING).build()); + } + + public void testConvertWhenNotOpenReturnsNull() { + + assertEquals(new SpotInstanceRequestToAWSRunningInstance().apply(SpotInstanceRequest.builder() + .region("us-east-1").id("sir-228e6406").type(SpotInstanceRequest.Type.ONE_TIME).state( + SpotInstanceRequest.State.ACTIVE).build()), null); + + assertEquals(new SpotInstanceRequestToAWSRunningInstance().apply(SpotInstanceRequest.builder() + .region("us-east-1").id("sir-228e6406").type(SpotInstanceRequest.Type.ONE_TIME).state( + SpotInstanceRequest.State.CANCELLED).build()), null); + + assertEquals(new SpotInstanceRequestToAWSRunningInstance().apply(SpotInstanceRequest.builder() + .region("us-east-1").id("sir-228e6406").type(SpotInstanceRequest.Type.ONE_TIME).state( + SpotInstanceRequest.State.UNRECOGNIZED).build()), null); + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptionsTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptionsTest.java new file mode 100644 index 0000000000..717298f8a1 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptionsTest.java @@ -0,0 +1,122 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.options; + +import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.from; +import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.instanceType; +import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.productDescription; +import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.to; +import static org.testng.Assert.assertEquals; + +import java.util.Collections; +import java.util.Date; + +import org.jclouds.http.options.HttpRequestOptions; +import org.testng.annotations.Test; + +/** + * Tests possible uses of DescribeSpotPriceHistoryOptions and + * DescribeSpotPriceHistoryOptions.Builder.* + * + * @author Adrian Cole + */ +public class DescribeSpotPriceHistoryOptionsTest { + + @Test + public void testAssignability() { + assert HttpRequestOptions.class.isAssignableFrom(DescribeSpotPriceHistoryOptions.class); + assert !String.class.isAssignableFrom(DescribeSpotPriceHistoryOptions.class); + } + + @Test + public void testDescription() { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + options.productDescription("test"); + assertEquals(options.buildFormParameters().get("ProductDescription"), Collections.singletonList("test")); + } + + @Test + public void testDescriptionStatic() { + DescribeSpotPriceHistoryOptions options = productDescription("test"); + assertEquals(options.buildFormParameters().get("ProductDescription"), Collections.singletonList("test")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testDescriptionNPE() { + productDescription(null); + } + + @Test + public void testInstanceType() { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + options.instanceType("test"); + assertEquals(options.buildFormParameters().get("InstanceType.1"), Collections.singletonList("test")); + } + + @Test + public void testInstanceTypeStatic() { + DescribeSpotPriceHistoryOptions options = instanceType("test"); + assertEquals(options.buildFormParameters().get("InstanceType.1"), Collections.singletonList("test")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testInstanceTypeNPE() { + instanceType(null); + } + + @Test + public void testFrom() { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + options.from(test); + assertEquals(options.buildFormParameters().get("StartTime"), Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + Date test = new Date(12345678910l); + + @Test + public void testFromStatic() { + DescribeSpotPriceHistoryOptions options = from(test); + assertEquals(options.buildFormParameters().get("StartTime"), Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testFromNPE() { + from(null); + } + + @Test + public void testTo() { + DescribeSpotPriceHistoryOptions options = new DescribeSpotPriceHistoryOptions(); + options.to(test); + assertEquals(options.buildFormParameters().get("EndTime"), Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + @Test + public void testToStatic() { + DescribeSpotPriceHistoryOptions options = to(test); + assertEquals(options.buildFormParameters().get("EndTime"), Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testToNPE() { + to(null); + } + +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptionsTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptionsTest.java new file mode 100644 index 0000000000..77fb3851df --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptionsTest.java @@ -0,0 +1,145 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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 validUntil 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.aws.ec2.options; + +import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.availabilityZoneGroup; +import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.launchGroup; +import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.type; +import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.validFrom; +import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.validUntil; +import static org.testng.Assert.assertEquals; + +import java.util.Collections; +import java.util.Date; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.http.options.HttpRequestOptions; +import org.testng.annotations.Test; + +/** + * Tests possible uses of RequestSpotInstancesOptions and RequestSpotInstancesOptions.Builder.* + * + * @author Adrian Cole + */ +public class RequestSpotInstancesOptionsTest { + + @Test + public void testAssignability() { + assert HttpRequestOptions.class.isAssignableFrom(RequestSpotInstancesOptions.class); + assert !String.class.isAssignableFrom(RequestSpotInstancesOptions.class); + } + + @Test + public void testAvailabilityZoneGroup() { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + options.availabilityZoneGroup("test"); + assertEquals(options.buildFormParameters().get("AvailabilityZoneGroup"), Collections.singletonList("test")); + } + + @Test + public void testAvailabilityZoneGroupStatic() { + RequestSpotInstancesOptions options = availabilityZoneGroup("test"); + assertEquals(options.buildFormParameters().get("AvailabilityZoneGroup"), Collections.singletonList("test")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testAvailabilityZoneGroupNPE() { + availabilityZoneGroup(null); + } + + @Test + public void testLaunchGroup() { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + options.launchGroup("test"); + assertEquals(options.buildFormParameters().get("LaunchGroup"), Collections.singletonList("test")); + } + + @Test + public void testLaunchGroupStatic() { + RequestSpotInstancesOptions options = launchGroup("test"); + assertEquals(options.buildFormParameters().get("LaunchGroup"), Collections.singletonList("test")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testLaunchGroupNPE() { + launchGroup(null); + } + + @Test + public void testInstanceType() { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + options.type(SpotInstanceRequest.Type.PERSISTENT); + assertEquals(options.buildFormParameters().get("Type"), Collections.singletonList("persistent")); + } + + @Test + public void testInstanceTypeStatic() { + RequestSpotInstancesOptions options = type(SpotInstanceRequest.Type.PERSISTENT); + assertEquals(options.buildFormParameters().get("Type"), Collections.singletonList("persistent")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testInstanceTypeNPE() { + type(null); + } + + @Test + public void testFrom() { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + options.validFrom(test); + assertEquals(options.buildFormParameters().get("ValidFrom"), + Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + Date test = new Date(12345678910l); + + @Test + public void testFromStatic() { + RequestSpotInstancesOptions options = validFrom(test); + assertEquals(options.buildFormParameters().get("ValidFrom"), + Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testFromNPE() { + validFrom(null); + } + + @Test + public void testTo() { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + options.validUntil(test); + assertEquals(options.buildFormParameters().get("ValidUntil"), + Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + @Test + public void testToStatic() { + RequestSpotInstancesOptions options = validUntil(test); + assertEquals(options.buildFormParameters().get("ValidUntil"), + Collections.singletonList("1970-05-23T21:21:18.910Z")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testToNPE() { + validUntil(null); + } + +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AMIClientLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AMIClientLiveTest.java index c6081b6cad..ec9b684939 100644 --- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AMIClientLiveTest.java +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AMIClientLiveTest.java @@ -100,7 +100,7 @@ public class AMIClientLiveTest { setupCredentials(); Properties overrides = setupProperties(); context = new ComputeServiceContextFactory().createContext(provider, - ImmutableSet. of(new Log4JLoggingModule()), overrides).getProviderSpecificContext(); + ImmutableSet. of(new Log4JLoggingModule()), overrides).getProviderSpecificContext(); client = context.getApi().getAMIServices(); } @@ -135,7 +135,7 @@ public class AMIClientLiveTest { String imageRegisteredId = client.registerImageFromManifestInRegion(null, "jcloudstest1", DEFAULT_MANIFEST); imagesToDeregister.add(imageRegisteredId); Image imageRegisteredFromManifest = Iterables.getOnlyElement(client.describeImagesInRegion(null, - imageIds(imageRegisteredId))); + imageIds(imageRegisteredId))); assertEquals(imageRegisteredFromManifest.getName(), "jcloudstest1"); assertEquals(imageRegisteredFromManifest.getImageLocation(), DEFAULT_MANIFEST); assertEquals(imageRegisteredFromManifest.getImageType(), ImageType.MACHINE); @@ -146,10 +146,10 @@ public class AMIClientLiveTest { @Test(enabled = false) public void testRegisterImageFromManifestOptions() { String imageRegisteredWithOptionsId = client.registerImageFromManifestInRegion(null, "jcloudstest2", - DEFAULT_MANIFEST, withDescription("adrian")); + DEFAULT_MANIFEST, withDescription("adrian")); imagesToDeregister.add(imageRegisteredWithOptionsId); Image imageRegisteredFromManifestWithOptions = Iterables.getOnlyElement(client.describeImagesInRegion(null, - imageIds(imageRegisteredWithOptionsId))); + imageIds(imageRegisteredWithOptionsId))); assertEquals(imageRegisteredFromManifestWithOptions.getName(), "jcloudstest2"); assertEquals(imageRegisteredFromManifestWithOptions.getImageLocation(), DEFAULT_MANIFEST); assertEquals(imageRegisteredFromManifestWithOptions.getImageType(), ImageType.MACHINE); @@ -164,7 +164,7 @@ public class AMIClientLiveTest { String imageRegisteredId = client.registerUnixImageBackedByEbsInRegion(null, "jcloudstest1", DEFAULT_MANIFEST); imagesToDeregister.add(imageRegisteredId); Image imageRegistered = Iterables - .getOnlyElement(client.describeImagesInRegion(null, imageIds(imageRegisteredId))); + .getOnlyElement(client.describeImagesInRegion(null, imageIds(imageRegisteredId))); assertEquals(imageRegistered.getName(), "jcloudstest1"); assertEquals(imageRegistered.getImageType(), ImageType.MACHINE); assertEquals(imageRegistered.getRootDeviceType(), RootDeviceType.EBS); @@ -175,18 +175,19 @@ public class AMIClientLiveTest { // awaiting EBS functionality to be added to jclouds public void testRegisterImageBackedByEBSOptions() { String imageRegisteredWithOptionsId = client.registerUnixImageBackedByEbsInRegion(null, "jcloudstest2", - DEFAULT_SNAPSHOT, addNewBlockDevice("/dev/sda2", "myvirtual", 1).withDescription("adrian")); + DEFAULT_SNAPSHOT, addNewBlockDevice("/dev/sda2", "myvirtual", 1).withDescription("adrian")); imagesToDeregister.add(imageRegisteredWithOptionsId); Image imageRegisteredWithOptions = Iterables.getOnlyElement(client.describeImagesInRegion(null, - imageIds(imageRegisteredWithOptionsId))); + imageIds(imageRegisteredWithOptionsId))); assertEquals(imageRegisteredWithOptions.getName(), "jcloudstest2"); assertEquals(imageRegisteredWithOptions.getImageType(), ImageType.MACHINE); assertEquals(imageRegisteredWithOptions.getRootDeviceType(), RootDeviceType.EBS); assertEquals(imageRegisteredWithOptions.getRootDeviceName(), "/dev/sda1"); assertEquals(imageRegisteredWithOptions.getDescription(), "adrian"); - assertEquals(imageRegisteredWithOptions.getEbsBlockDevices().entrySet(), ImmutableMap.of("/dev/sda1", - new Image.EbsBlockDevice("/dev/sda1", 30, true), "/dev/sda2", - new Image.EbsBlockDevice("/dev/sda2", 1, true)).entrySet()); + assertEquals( + imageRegisteredWithOptions.getEbsBlockDevices().entrySet(), + ImmutableMap.of("/dev/sda1", new Image.EbsBlockDevice("/dev/sda1", 30, true), "/dev/sda2", + new Image.EbsBlockDevice("/dev/sda2", 1, true)).entrySet()); } @Test(enabled = false) @@ -216,6 +217,7 @@ public class AMIClientLiveTest { // TODO client.resetLaunchPermissionsOnImageInRegion(null, imageId); } + @Test(enabled = false) public void testGetLaunchPermissionForImage() { System.out.println(client.getLaunchPermissionForImageInRegion(null, imageId)); } diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AWSKeyPairClientLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AWSKeyPairClientLiveTest.java index 5e95f7c655..97b9947f7d 100644 --- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AWSKeyPairClientLiveTest.java +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/AWSKeyPairClientLiveTest.java @@ -41,6 +41,7 @@ import org.jclouds.Constants; import org.jclouds.aws.domain.Region; import org.jclouds.aws.ec2.AWSEC2AsyncClient; import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions; import org.jclouds.aws.ec2.domain.AWSRunningInstance; import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.ComputeServiceContextFactory; @@ -49,10 +50,7 @@ import org.jclouds.compute.domain.ExecResponse; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.domain.Credentials; -import org.jclouds.ec2.EC2Client; import org.jclouds.ec2.domain.KeyPair; -import org.jclouds.ec2.domain.RunningInstance; -import org.jclouds.ec2.services.InstanceClient; import org.jclouds.logging.log4j.config.Log4JLoggingModule; import org.jclouds.rest.RestContext; import org.jclouds.ssh.jsch.config.JschSshClientModule; @@ -84,7 +82,7 @@ public class AWSKeyPairClientLiveTest { protected void setupCredentials() { identity = checkNotNull(System.getProperty("test." + provider + ".identity"), "test." + provider + ".identity"); credential = checkNotNull(System.getProperty("test." + provider + ".credential"), "test." + provider - + ".credential"); + + ".credential"); endpoint = System.getProperty("test." + provider + ".endpoint", null); apiversion = System.getProperty("test." + provider + ".apiversion", null); } @@ -106,8 +104,8 @@ public class AWSKeyPairClientLiveTest { public void setupClient() { setupCredentials(); Properties overrides = setupProperties(); - computeContext = new ComputeServiceContextFactory().createContext(provider, - ImmutableSet. of(new Log4JLoggingModule(), new JschSshClientModule()), overrides); + computeContext = new ComputeServiceContextFactory().createContext(provider, ImmutableSet. of( + new Log4JLoggingModule(), new JschSshClientModule()), overrides); context = computeContext.getProviderSpecificContext(); client = context.getApi().getKeyPairServices(); } @@ -116,19 +114,19 @@ public class AWSKeyPairClientLiveTest { Map keyPair = ComputeTestUtils.setupKeyPair(); - InstanceClient instanceClient = EC2Client.class.cast(context.getApi()).getInstanceServices(); + AWSInstanceClient instanceClient = AWSEC2Client.class.cast(context.getApi()).getInstanceServices(); String group = PREFIX + "unssh"; computeContext.getComputeService().destroyNodesMatching(inGroup(group)); TemplateOptions options = computeContext.getComputeService().templateOptions(); - options.authorizePublicKey(keyPair.get("public")); + options.authorizePublicKey(keyPair.get("public")).as(AWSEC2TemplateOptions.class).spotPrice(0.3f); ComputeServiceContext noSshContext = null; try { - noSshContext = new ComputeServiceContextFactory().createContext(provider, - ImmutableSet.of(new Log4JLoggingModule()), setupProperties()); + noSshContext = new ComputeServiceContextFactory().createContext(provider, ImmutableSet + .of(new Log4JLoggingModule()), setupProperties()); Set nodes = noSshContext.getComputeService().createNodesInGroup(group, 1, options); @@ -136,18 +134,19 @@ public class AWSKeyPairClientLiveTest { assert first.getCredentials() != null : first; assert first.getCredentials().identity != null : first; - AWSRunningInstance instance = AWSRunningInstance.class - .cast(getInstance(instanceClient, first.getProviderId())); + AWSRunningInstance instance = getInstance(instanceClient, first.getProviderId()); + assert instance.getSpotInstanceRequestId() != null : instance; assertEquals(instance.getKeyName(), "jclouds#" + group); assertEquals(first.getCredentials().credential, null); Map responses = computeContext.getComputeService() - .runScriptOnNodesMatching( - runningInGroup(group), - exec("echo hello"), - overrideCredentialsWith(new Credentials(first.getCredentials().identity, keyPair.get("private"))) - .wrapInInitScript(false).runAsRoot(false)); + .runScriptOnNodesMatching( + runningInGroup(group), + exec("echo hello"), + overrideCredentialsWith( + new Credentials(first.getCredentials().identity, keyPair.get("private"))) + .wrapInInitScript(false).runAsRoot(false)); ExecResponse hello = getOnlyElement(responses.values()); assertEquals(hello.getOutput().trim(), "hello"); @@ -235,9 +234,8 @@ public class AWSKeyPairClientLiveTest { assertEquals(listPair.getKeyFingerprint(), keyPair.getKeyFingerprint()); } - protected RunningInstance getInstance(InstanceClient instanceClient, String id) { - RunningInstance instance = getOnlyElement(getOnlyElement(instanceClient.describeInstancesInRegion(null, id))); - return instance; + protected AWSRunningInstance getInstance(AWSInstanceClient instanceClient, String id) { + return getOnlyElement(getOnlyElement(instanceClient.describeInstancesInRegion(null, id))); } @AfterTest diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClientTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClientTest.java new file mode 100644 index 0000000000..6e1dde535d --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClientTest.java @@ -0,0 +1,190 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.services; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Date; + +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +import org.jclouds.aws.ec2.xml.DescribeSpotPriceHistoryResponseHandler; +import org.jclouds.aws.ec2.xml.SpotInstanceHandler; +import org.jclouds.aws.ec2.xml.SpotInstancesHandler; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.ReleasePayloadAndReturn; +import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; +import org.jclouds.rest.internal.RestAnnotationProcessor; +import org.testng.annotations.Test; + +import com.google.inject.TypeLiteral; + +/** + * Tests behavior of {@code SpotInstanceAsyncClient} + * + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "SpotInstanceAsyncClientTest") +public class SpotInstanceAsyncClientTest extends BaseAWSEC2AsyncClientTest { + public void testRequestSpotInstance() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("requestSpotInstanceInRegion", String.class, + float.class, String.class, String.class); + HttpRequest request = processor.createRequest(method, null, 0.01f, "m1.small", "ami-voo"); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals( + request, + "Version=2010-11-15&Action=RequestSpotInstances&LaunchSpecification.ImageId=m1.small&SpotPrice=0.01&LaunchSpecification.InstanceType=ami-voo", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, SpotInstanceHandler.class); + assertExceptionParserClassEquals(method, null); + + checkFilters(request); + } + + public void testRequestSpotInstancesOptions() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("requestSpotInstancesInRegion", String.class, + float.class, int.class, LaunchSpecification.class, RequestSpotInstancesOptions[].class); + HttpRequest request = processor.createRequest(method, "eu-west-1", 0.01, 3, + LaunchSpecification.builder().instanceType("m1.small").imageId("ami-voo").availabilityZone("eu-west-1a") + .kernelId("kernelId").groupId("group1").build(), new RequestSpotInstancesOptions().validFrom(from) + .validUntil(to).availabilityZoneGroup("availabilityZoneGroup").launchGroup("launchGroup")); + + assertRequestLineEquals(request, "POST https://ec2.eu-west-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.eu-west-1.amazonaws.com\n"); + assertPayloadEquals( + request, + "Version=2010-11-15&Action=RequestSpotInstances&InstanceCount=3&SpotPrice=0.01&ValidFrom=1970-05-23T21%3A21%3A18.910Z&ValidUntil=2009-02-13T23%3A31%3A31.011Z&AvailabilityZoneGroup=availabilityZoneGroup&LaunchGroup=launchGroup&LaunchSpecification.ImageId=ami-voo&LaunchSpecification.Placement.AvailabilityZone=eu-west-1a&LaunchSpecification.SecurityGroup.1=group1&LaunchSpecification.InstanceType=m1.small&LaunchSpecification.KernelId=kernelId", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, SpotInstancesHandler.class); + assertExceptionParserClassEquals(method, null); + + checkFilters(request); + } + + public void testCancelSpotInstanceRequests() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("cancelSpotInstanceRequestsInRegion", String.class, + String[].class); + HttpRequest request = processor.createRequest(method, null, "id"); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2010-11-15&Action=CancelSpotInstanceRequests&SpotInstanceRequestId.1=id", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class); + + checkFilters(request); + } + + public void testDescribeSpotInstanceRequests() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("describeSpotInstanceRequestsInRegion", String.class, + String[].class); + HttpRequest request = processor.createRequest(method, (String) null); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2010-11-15&Action=DescribeSpotInstanceRequests", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, SpotInstancesHandler.class); + assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class); + + checkFilters(request); + } + + public void testDescribeSpotInstanceRequestsArgs() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("describeSpotInstanceRequestsInRegion", String.class, + String[].class); + HttpRequest request = processor.createRequest(method, null, "1", "2"); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals( + request, + "Version=2010-11-15&Action=DescribeSpotInstanceRequests&SpotInstanceRequestId.1=1&SpotInstanceRequestId.2=2", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, SpotInstancesHandler.class); + assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class); + + checkFilters(request); + } + + public void testDescribeSpotPriceHistory() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("describeSpotPriceHistoryInRegion", String.class, + DescribeSpotPriceHistoryOptions[].class); + HttpRequest request = processor.createRequest(method, (String) null); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2010-11-15&Action=DescribeSpotPriceHistory", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, DescribeSpotPriceHistoryResponseHandler.class); + assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class); + + checkFilters(request); + } + + Date from = new Date(12345678910l); + Date to = new Date(1234567891011l); + + public void testDescribeSpotPriceHistoryArgs() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("describeSpotPriceHistoryInRegion", String.class, + DescribeSpotPriceHistoryOptions[].class); + HttpRequest request = processor.createRequest(method, null, DescribeSpotPriceHistoryOptions.Builder.from(from) + .to(to).productDescription("description").instanceType("m1.small")); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals( + request, + "Version=2010-11-15&Action=DescribeSpotPriceHistory&StartTime=1970-05-23T21%3A21%3A18.910Z&EndTime=2009-02-13T23%3A31%3A31.011Z&ProductDescription=description&InstanceType.1=m1.small", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, DescribeSpotPriceHistoryResponseHandler.class); + assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class); + + checkFilters(request); + } + + @Override + protected TypeLiteral> createTypeLiteral() { + return new TypeLiteral>() { + }; + } + +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceClientLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceClientLiveTest.java new file mode 100644 index 0000000000..ab3b59d96d --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceClientLiveTest.java @@ -0,0 +1,207 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.aws.ec2.services; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.in; +import static com.google.common.collect.Iterables.getOnlyElement; +import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.from; +import static org.jclouds.aws.ec2.options.RequestSpotInstancesOptions.Builder.launchGroup; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Date; +import java.util.Properties; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.TimeUnit; + +import org.jclouds.Constants; +import org.jclouds.aws.domain.Region; +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.domain.AWSRunningInstance; +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.Spot; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.predicates.SpotInstanceRequestActive; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ComputeServiceContextFactory; +import org.jclouds.ec2.domain.InstanceType; +import org.jclouds.logging.log4j.config.Log4JLoggingModule; +import org.jclouds.predicates.RetryablePredicate; +import org.jclouds.ssh.jsch.config.JschSshClientModule; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.inject.Module; + +/** + * Tests behavior of {@code SpotInstanceClient} + * + * @author Adrian Cole + */ +@Test(groups = "live", sequential = true) +public class SpotInstanceClientLiveTest { + + private static final int SPOT_DELAY_SECONDS = 300; + private AWSEC2Client client; + private ComputeServiceContext context; + private RetryablePredicate activeTester; + private Set requests; + protected String provider = "aws-ec2"; + protected String identity; + protected String credential; + protected String endpoint; + protected String apiversion; + private AWSRunningInstance instance; + private long start; + + @BeforeClass + protected void setupCredentials() { + identity = checkNotNull(System.getProperty("test." + provider + ".identity"), "test." + provider + ".identity"); + credential = System.getProperty("test." + provider + ".credential"); + endpoint = System.getProperty("test." + provider + ".endpoint"); + apiversion = System.getProperty("test." + provider + ".apiversion"); + } + + protected Properties setupProperties() { + Properties overrides = new Properties(); + overrides.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true"); + overrides.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true"); + overrides.setProperty(provider + ".identity", identity); + if (credential != null) + overrides.setProperty(provider + ".credential", credential); + if (endpoint != null) + overrides.setProperty(provider + ".endpoint", endpoint); + if (apiversion != null) + overrides.setProperty(provider + ".apiversion", apiversion); + return overrides; + } + + @BeforeGroups(groups = { "live" }) + public void setupClient() throws FileNotFoundException, IOException { + setupCredentials(); + Properties overrides = setupProperties(); + context = new ComputeServiceContextFactory().createContext(provider, + ImmutableSet. of(new Log4JLoggingModule(), new JschSshClientModule()), overrides); + + client = AWSEC2Client.class.cast(context.getProviderSpecificContext().getApi()); + activeTester = new RetryablePredicate(new SpotInstanceRequestActive(client), + SPOT_DELAY_SECONDS, 1, 1, TimeUnit.SECONDS); + } + + @Test + void testDescribeSpotRequestsInRegion() { + for (String region : Region.DEFAULT_REGIONS) { + SortedSet allResults = ImmutableSortedSet.copyOf(client.getSpotInstanceServices() + .describeSpotInstanceRequestsInRegion(region)); + assertNotNull(allResults); + if (allResults.size() >= 1) { + SpotInstanceRequest request = allResults.last(); + SortedSet result = ImmutableSortedSet.copyOf(client.getSpotInstanceServices() + .describeSpotInstanceRequestsInRegion(region, request.getId())); + assertNotNull(result); + SpotInstanceRequest compare = result.last(); + assertEquals(compare, request); + } + } + + } + + @Test + void testDescribeSpotPriceHistoryInRegion() { + for (final String region : Region.DEFAULT_REGIONS) { + Set spots = client.getSpotInstanceServices().describeSpotPriceHistoryInRegion(region, from(new Date())); + assertNotNull(spots); + assert spots.size() > 0; + for (Spot spot : spots) { + assert spot.getSpotPrice() > 0 : spots; + assertEquals(spot.getRegion(), region); + assert in(ImmutableSet.of("Linux/UNIX", "SUSE Linux", "Windows")).apply(spot.getProductDescription()) : spot; + assert in( + ImmutableSet.of("c1.medium", "c1.xlarge", "m1.large", "m1.small", "m1.xlarge", "m2.2xlarge", + "m2.4xlarge", "m2.xlarge", "t1.micro")).apply(spot.getInstanceType()) : spot; + + } + } + + } + + @Test(enabled = true) + void testCreateSpotInstance() { + String launchGroup = PREFIX + "1"; + for (SpotInstanceRequest request : client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion( + "us-west-1")) + if (launchGroup.equals(request.getLaunchGroup())) + client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion("us-west-1", request.getId()); + start = System.currentTimeMillis(); + + requests = client.getSpotInstanceServices().requestSpotInstancesInRegion( + "us-west-1", + 0.03f, + 1, + LaunchSpecification.builder().imageId("ami-595a0a1c").instanceType(InstanceType.T1_MICRO).build(), + launchGroup(launchGroup).availabilityZoneGroup(launchGroup).validFrom(new Date()) + .validUntil(new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(SPOT_DELAY_SECONDS)))); + assertNotNull(requests); + + for (SpotInstanceRequest request : requests) + verifySpotInstance(request); + } + + private void verifySpotInstance(SpotInstanceRequest request) { + SpotInstanceRequest spot = refresh(request); + assertNotNull(spot); + assertEquals(spot, request); + assert activeTester.apply(request) : refresh(request); + System.out.println(System.currentTimeMillis() - start); + spot = refresh(request); + assert spot.getInstanceId() != null : spot; + instance = getOnlyElement(getOnlyElement(client.getInstanceServices().describeInstancesInRegion("us-west-1", + spot.getInstanceId()))); + assertEquals(instance.getSpotInstanceRequestId(), spot.getId()); + } + + public SpotInstanceRequest refresh(SpotInstanceRequest request) { + return getOnlyElement(client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion("us-west-1", + request.getId())); + } + + public static final String PREFIX = System.getProperty("user.name") + "ec2"; + + @AfterTest + public void shutdown() { + if (requests != null) { + for (SpotInstanceRequest request : requests) + client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion(request.getRegion(), request.getId()); + // assert deletedTester.apply(request) : request; + } + if (instance != null) { + client.getInstanceServices().terminateInstancesInRegion("us-west-1", instance.getId()); + } + context.close(); + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/RunInstancesResponseHandlerTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/AWSRunInstancesResponseHandlerTest.java similarity index 98% rename from providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/RunInstancesResponseHandlerTest.java rename to providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/AWSRunInstancesResponseHandlerTest.java index c2e18320de..0e1f4cef9c 100644 --- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/RunInstancesResponseHandlerTest.java +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/AWSRunInstancesResponseHandlerTest.java @@ -53,7 +53,7 @@ import com.google.inject.Guice; */ // NOTE:without testName, this will not call @Before* and fail w/NPE during surefire @Test(groups = "unit", testName = "RunInstancesResponseHandlerTest") -public class RunInstancesResponseHandlerTest extends BaseEC2HandlerTest { +public class AWSRunInstancesResponseHandlerTest extends BaseEC2HandlerTest { private DateService dateService; diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandlerTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandlerTest.java new file mode 100644 index 0000000000..23e0d87067 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandlerTest.java @@ -0,0 +1,69 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. expected = ImmutableSet.of( + Spot.builder().region("us-west-1").instanceType("t1.micro").productDescription("SUSE Linux").spotPrice(0.013f) + .timestamp(new SimpleDateFormatDateService().iso8601DateParse("2011-03-07T12:17:19.000Z")).build(), + Spot.builder().region("us-west-1").instanceType("m1.large").productDescription("Linux/UNIX").spotPrice(0.119f) + .timestamp(new SimpleDateFormatDateService().iso8601DateParse("2011-03-07T16:29:16.000Z")).build(), + Spot.builder().region("us-west-1").instanceType("t1.micro").productDescription("Windows").spotPrice(0.013f) + .timestamp(new SimpleDateFormatDateService().iso8601DateParse("2011-03-07T17:56:54.000Z")).build() + + ); + + Set result = factory.create(injector.createChildInjector(new AbstractModule(){ + + @Override + protected void configure() { + bindConstant().annotatedWith(Region.class).to("us-west-1"); + } + + }).getInstance(DescribeSpotPriceHistoryResponseHandler.class)).parse(is); + + assertEquals(result, expected); + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstanceHandlerTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstanceHandlerTest.java new file mode 100644 index 0000000000..460b77f6eb --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstanceHandlerTest.java @@ -0,0 +1,102 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. .info@cloudconscious.com(" + * + * ==================================================================== + * Licensed 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.aws.ec2.xml; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createMock; +import static org.easymock.classextension.EasyMock.replay; +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; + +import org.jclouds.aws.ec2.domain.LaunchSpecification; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.ec2.xml.BaseEC2HandlerTest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.config.SaxParserModule; +import org.jclouds.location.Region; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; + +/** + * Tests behavior of {@code SpotInstanceHandler} + * + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "SpotInstanceHandlerTest") +public class SpotInstanceHandlerTest extends BaseEC2HandlerTest { + + private DateService dateService; + + @BeforeTest + @Override + protected void setUpInjector() { + injector = Guice.createInjector(new SaxParserModule(), new AbstractModule() { + + @Override + protected void configure() { + bind(String.class).annotatedWith(Region.class).toInstance("us-east-1"); + } + + }); + factory = injector.getInstance(ParseSax.Factory.class); + dateService = injector.getInstance(DateService.class); + assert dateService != null; + } + + public void testApplyInputStream() { + + InputStream is = getClass().getResourceAsStream("/request_spot_instances-ebs.xml"); + + SpotInstanceRequest expected = SpotInstanceRequest + .builder() + .region("us-east-1") + .id("sir-228e6406") + .spotPrice(0.001f) + .type(SpotInstanceRequest.Type.ONE_TIME) + .state(SpotInstanceRequest.State.OPEN) + .launchSpecification( + LaunchSpecification.builder().imageId("ami-595a0a1c").groupId("default").instanceType("m1.large") + .mapNewVolumeToDevice("/dev/sda1", 1, true) + .mapEBSSnapshotToDevice("/dev/sda2", "snap-1ea27576", 1, true) + .mapEphemeralDeviceToDevice("/dev/sda3", "vre1").monitoringEnabled(false).build()) + .createTime(new SimpleDateFormatDateService().iso8601DateParse("2011-03-08T03:30:36.000Z")) + .productDescription("Linux/UNIX").build(); + SpotInstanceHandler handler = injector.getInstance(SpotInstanceHandler.class); + addDefaultRegionToHandler(handler); + SpotInstanceRequest result = factory.create(handler).parse(is); + assertEquals(result, expected); + } + + private void addDefaultRegionToHandler(ParseSax.HandlerWithResult handler) { + GeneratedHttpRequest request = createMock(GeneratedHttpRequest.class); + expect(request.getArgs()).andReturn(ImmutableList. of()).atLeastOnce(); + replay(request); + handler.setContext(request); + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstancesHandlerTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstancesHandlerTest.java new file mode 100644 index 0000000000..498f9787ea --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstancesHandlerTest.java @@ -0,0 +1,95 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. .info@cloudconscious.com(" + * + * ==================================================================== + * Licensed 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.aws.ec2.xml; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createMock; +import static org.easymock.classextension.EasyMock.replay; +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; +import java.util.Set; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.date.DateService; +import org.jclouds.ec2.xml.BaseEC2HandlerTest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.config.SaxParserModule; +import org.jclouds.location.Region; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; + +/** + * Tests behavior of {@code SpotInstancesHandler} + * + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "SpotInstancesHandlerTest") +public class SpotInstancesHandlerTest extends BaseEC2HandlerTest { + + private DateService dateService; + + @BeforeTest + @Override + protected void setUpInjector() { + injector = Guice.createInjector(new SaxParserModule(), new AbstractModule() { + + @Override + protected void configure() { + bind(String.class).annotatedWith(Region.class).toInstance("us-east-1"); + } + + }); + factory = injector.getInstance(ParseSax.Factory.class); + dateService = injector.getInstance(DateService.class); + assert dateService != null; + } + public void testDescribe() { + + InputStream is = getClass().getResourceAsStream("/describe_spot_instance_requests.xml"); + SpotInstancesHandler handler = injector + .getInstance(SpotInstancesHandler.class); + addDefaultRegionToHandler(handler); + Set result = factory.create(handler).parse(is); + assertEquals(result.size(), 18); + } + public void testRequest() { + + InputStream is = getClass().getResourceAsStream("/request_spot_instances.xml"); + SpotInstancesHandler handler = injector + .getInstance(SpotInstancesHandler.class); + addDefaultRegionToHandler(handler); + Set result = factory.create(handler).parse(is); + assertEquals(result.size(), 3); + } + + private void addDefaultRegionToHandler(ParseSax.HandlerWithResult handler) { + GeneratedHttpRequest request = createMock(GeneratedHttpRequest.class); + expect(request.getArgs()).andReturn(ImmutableList. of()).atLeastOnce(); + replay(request); + handler.setContext(request); + } +} diff --git a/providers/aws-ec2/src/test/resources/describe_spot_instance_requests.xml b/providers/aws-ec2/src/test/resources/describe_spot_instance_requests.xml new file mode 100644 index 0000000000..a34b0f63a2 --- /dev/null +++ b/providers/aws-ec2/src/test/resources/describe_spot_instance_requests.xml @@ -0,0 +1,413 @@ + + 7c4dd2bd-106d-4cd3-987c-35ee819180a6 + + + sir-067a4805 + 0.040000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:13:07.000Z + Linux/UNIX + + + sir-aa67d410 + 0.040000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:13:57.000Z + Linux/UNIX + + + sir-32e32810 + 0.010000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:15:52.000Z + Linux/UNIX + + + sir-46a36210 + 0.010000 + one-time + cancelled + 2011-03-07T20:20:00.000Z + foo + azfoo + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + true + + + 2011-03-07T20:18:25.000Z + Linux/UNIX + + + sir-91780010 + 0.010000 + one-time + cancelled + 2011-03-07T20:20:00.000Z + foo + azfoo + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + true + + + 2011-03-07T20:18:25.000Z + Linux/UNIX + + + sir-6f1fa605 + 0.001000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:19:27.000Z + Linux/UNIX + + + sir-a33eee10 + 0.001000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:21:16.000Z + Linux/UNIX + + + sir-aa690410 + 0.001000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:21:52.000Z + Linux/UNIX + + + sir-99ba4e06 + 0.010000 + one-time + cancelled + 2011-03-07T20:26:00.000Z + doo + dooo + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + true + + + 2011-03-07T20:24:30.000Z + Linux/UNIX + + + sir-a617c406 + 0.010000 + one-time + cancelled + 2011-03-07T20:26:00.000Z + doo + dooo + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + true + + + 2011-03-07T20:24:30.000Z + Linux/UNIX + + + sir-2147a405 + 0.001000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:25:19.000Z + Linux/UNIX + + + sir-c441c805 + 0.001000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:29:09.000Z + Linux/UNIX + + + sir-4658fe10 + 0.010000 + one-time + cancelled + 2011-03-07T21:10:00.000Z + check3 + check3 + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + false + + + 2011-03-07T20:31:34.000Z + Linux/UNIX + + + sir-49a3ce10 + 0.010000 + one-time + cancelled + 2011-03-07T21:10:00.000Z + check3 + check3 + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + false + + + 2011-03-07T20:31:34.000Z + Linux/UNIX + + + sir-91b30610 + 0.010000 + one-time + cancelled + 2011-03-07T21:10:00.000Z + check3 + check3 + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + false + + + 2011-03-07T20:31:34.000Z + Linux/UNIX + + + sir-d8561606 + 0.001000 + one-time + cancelled + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T20:34:10.000Z + Linux/UNIX + + + sir-4cdaa406 + 0.001000 + persistent + cancelled + 2011-03-07T22:25:00.000Z + + ami-595a0a1c + default + + + quick-start-1 + + + t1.micro + + + false + + + 2011-03-07T22:23:19.000Z + Linux/UNIX + + + sir-e19f2206 + 0.001000 + one-time + open + + ami-595a0a1c + + + default + + + t1.micro + + + false + + + 2011-03-07T22:32:50.000Z + Linux/UNIX + + + \ No newline at end of file diff --git a/providers/aws-ec2/src/test/resources/describe_spot_price_history.xml b/providers/aws-ec2/src/test/resources/describe_spot_price_history.xml new file mode 100644 index 0000000000..b6fd33692c --- /dev/null +++ b/providers/aws-ec2/src/test/resources/describe_spot_price_history.xml @@ -0,0 +1,24 @@ + + + 99777a75-2a2b-4296-a305-650c442d2d63 + + + t1.micro + SUSE Linux + 0.013000 + 2011-03-07T12:17:19.000Z + + + m1.large + Linux/UNIX + 0.119000 + 2011-03-07T16:29:16.000Z + + + t1.micro + Windows + 0.013000 + 2011-03-07T17:56:54.000Z + + + \ No newline at end of file diff --git a/providers/aws-ec2/src/test/resources/request_spot_instances-ebs.xml b/providers/aws-ec2/src/test/resources/request_spot_instances-ebs.xml new file mode 100644 index 0000000000..e95d8d948b --- /dev/null +++ b/providers/aws-ec2/src/test/resources/request_spot_instances-ebs.xml @@ -0,0 +1,47 @@ + + + 02401e8e-a4f5-4285-8ea8-6d742fbaadd8 + + + sir-228e6406 + 0.001000 + one-time + open + + ami-595a0a1c + + + default + + + m1.large + + + /dev/sda1 + + 1 + true + + + + /dev/sda3 + vre1 + + + /dev/sda2 + + snap-1ea27576 + 1 + true + + + + + false + + + 2011-03-08T03:30:36.000Z + Linux/UNIX + + + \ No newline at end of file diff --git a/providers/aws-ec2/src/test/resources/request_spot_instances.xml b/providers/aws-ec2/src/test/resources/request_spot_instances.xml new file mode 100644 index 0000000000..3df9689cd0 --- /dev/null +++ b/providers/aws-ec2/src/test/resources/request_spot_instances.xml @@ -0,0 +1,93 @@ + + + 2ffc645f-6835-4d23-bd18-f6f53c253067 + + + sir-7c74f805 + 0.001000 + one-time + open + + ami-595a0a1c + + + default + + + t1.micro + + + /dev/sda1 + + 120 + true + + + + + false + + + 2011-03-08T02:36:32.000Z + Linux/UNIX + + + sir-78ca7605 + 0.001000 + one-time + open + + ami-595a0a1c + + + default + + + t1.micro + + + /dev/sda1 + + 120 + true + + + + + false + + + 2011-03-08T02:36:32.000Z + Linux/UNIX + + + sir-7e0f6005 + 0.001000 + one-time + open + + ami-595a0a1c + + + default + + + t1.micro + + + /dev/sda1 + + 120 + true + + + + + false + + + 2011-03-08T02:36:32.000Z + Linux/UNIX + + + \ No newline at end of file diff --git a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/JobComplete.java b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/JobComplete.java index 5d05d32440..4a110176e1 100644 --- a/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/JobComplete.java +++ b/sandbox-apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/JobComplete.java @@ -21,8 +21,6 @@ package org.jclouds.cloudstack.predicates; import static com.google.common.base.Preconditions.checkNotNull; -import java.util.concurrent.ExecutionException; - import javax.annotation.Resource; import javax.inject.Singleton; @@ -31,7 +29,6 @@ import org.jclouds.cloudstack.domain.AsyncJob; import org.jclouds.logging.Logger; import com.google.common.base.Predicate; -import com.google.common.base.Throwables; import com.google.inject.Inject; /** @@ -60,10 +57,8 @@ public class JobComplete implements Predicate { return false; logger.trace("%s: looking for job status %s: currently: %s", job.getId(), 1, job.getStatus()); if (job.getError() != null) - Throwables.propagate(new ExecutionException(String.format("job %s failed with exception %s", job.getId(), job - .getError().toString())) { - private static final long serialVersionUID = 4371112085613620239L; - }); + throw new IllegalStateException(String.format("job %s failed with exception %s", job.getId(), job.getError() + .toString())); return job.getStatus() > 0; }