From ab46268ca2c97f9f90d5db1608b0d401f538471f Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 7 Mar 2011 00:58:50 -0500 Subject: [PATCH 1/4] started code for ec2 spot instances --- ...otNullBindAvailabilityZoneToFormParam.java | 16 +- .../jclouds/aws/ec2/AWSEC2AsyncClient.java | 7 + .../org/jclouds/aws/ec2/AWSEC2Client.java | 7 + ...InstanceRequestIdsToIndexedFormParams.java | 40 ++ ...ityZoneToLaunchSpecificationFormParam.java | 21 ++ .../ec2/config/AWSEC2RestClientModule.java | 3 + .../aws/ec2/domain/SpotInstanceRequest.java | 348 ++++++++++++++++++ .../DescribeSpotPriceHistoryOptions.java | 119 ++++++ .../options/RequestSpotInstancesOptions.java | 163 ++++++++ .../ec2/services/SpotInstanceAsyncClient.java | 98 +++++ .../aws/ec2/services/SpotInstanceClient.java | 139 +++++++ ...beSpotInstanceRequestsResponseHandler.java | 79 ++++ .../ec2/xml/SpotInstanceRequestHandler.java | 97 +++++ .../DescribeSpotPriceHistoryOptionsTest.java | 122 ++++++ .../RequestSpotInstancesOptionsTest.java | 179 +++++++++ .../services/SpotInstanceAsyncClientTest.java | 194 ++++++++++ .../services/SpotInstanceClientLiveTest.java | 181 +++++++++ 17 files changed, 1811 insertions(+), 2 deletions(-) create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindSpotInstanceRequestIdsToIndexedFormParams.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/SpotInstanceRequest.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptions.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptions.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClient.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceClient.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotInstanceRequestsResponseHandler.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/DescribeSpotPriceHistoryOptionsTest.java create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptionsTest.java create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClientTest.java create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceClientLiveTest.java 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/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/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/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java new file mode 100644 index 0000000000..6a34e6185a --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java @@ -0,0 +1,21 @@ +package org.jclouds.aws.ec2.binders; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.ec2.binders.IfNotNullBindAvailabilityZoneToFormParam; + +/** + * Binds the AvailabilityZone to a form parameter if set. + * + * @author Adrian Cole + */ +@Singleton +public class IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam extends IfNotNullBindAvailabilityZoneToFormParam { + + @Inject + protected IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam() { + super("LaunchSpecification.Placement.AvailabilityZone"); + } + +} 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.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 fault; + private String instanceId; + private String launchGroup; + private String launchSpecification; + private String productDescription; + private String id; + private float spotPrice; + private String state; + private Type type; + private Date validFrom; + private Date validUntil; + + 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 fault(String fault) { + this.fault = fault; + 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(String 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(String 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, fault, 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; + } + } + } + + private final String region; + private final String availabilityZoneGroup; + private final Date createTime; + private final String fault; + private final String instanceId; + private final String launchGroup; + private final String launchSpecification; + private final String productDescription; + private final String id; + private final float spotPrice; + private final String state; + private final Type type; + private final Date validFrom; + private final Date validUntil; + + public SpotInstanceRequest(String region, String availabilityZoneGroup, Date createTime, String fault, + String instanceId, String launchGroup, String launchSpecification, String productDescription, String id, + float spotPrice, String state, Type type, Date validFrom, Date validUntil) { + this.region = checkNotNull(region, "region"); + this.availabilityZoneGroup = availabilityZoneGroup; + this.createTime = createTime; + this.fault = fault; + 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 getFault() { + return fault; + } + + public String getInstanceId() { + return instanceId; + } + + public String getLaunchGroup() { + return launchGroup; + } + + public String getLaunchSpecification() { + return launchSpecification; + } + + public String getProductDescription() { + return productDescription; + } + + public String getId() { + return id; + } + + public float getSpotPrice() { + return spotPrice; + } + + public String 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 + ((fault == null) ? 0 : fault.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 (fault == null) { + if (other.fault != null) + return false; + } else if (!fault.equals(other.fault)) + 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 + ", fault=" + fault + + ", 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/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..c87294be66 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptions.java @@ -0,0 +1,163 @@ +/** + * + * 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 java.util.Map.Entry; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.ec2.options.RunInstancesOptions; +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; + } + + /** + * 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 launchSpecification(RunInstancesOptions launchSpecification) { + for (Entry entry : checkNotNull(launchSpecification, "launchSpecification").buildFormParameters() + .entries()) { + formParameters.put("LaunchSpecification." + entry.getKey(), entry.getValue()); + } + 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); + } + + /** + * @see RequestSpotInstancesOptions#launchSpecification + */ + public static RequestSpotInstancesOptions launchSpecification(RunInstancesOptions launchSpecification) { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + return options.launchSpecification(launchSpecification); + } + } +} 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..cff2d2b962 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClient.java @@ -0,0 +1,98 @@ +/** + * + * 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 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.BindSpotInstanceRequestIdsToIndexedFormParams; +import org.jclouds.aws.ec2.binders.IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam; +import org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +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.FormParams; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.VirtualHost; + +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") + ListenableFuture describeSpotInstanceRequestsInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @BinderParam(BindSpotInstanceRequestIdsToIndexedFormParams.class) String... requestIds); + + /** + * @see SpotInstanceClient#requestSpotInstancesInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "RequestSpotInstances") + ListenableFuture requestSpotInstancesInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @Nullable @BinderParam(IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.class) String nullableAvailabilityZone, + @FormParam("LaunchSpecification.ImageId") String imageId,@FormParam("InstanceCount") int instanceCount, @FormParam("SpotPrice") float spotPrice, + RequestSpotInstancesOptions... options); + + /** + * @see SpotInstanceClient#describeSpotPriceHistoryInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DescribeSpotPriceHistory") + ListenableFuture describeSpotPriceHistoryInRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + DescribeSpotPriceHistoryOptions... options); + + /** + * @see SpotInstanceClient#cancelSpotInstanceRequestsInRegion + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "CancelSpotInstanceRequests") + 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..44bd916e2e --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/SpotInstanceClient.java @@ -0,0 +1,139 @@ +/** + * + * 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.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +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 + */ + String describeSpotInstanceRequestsInRegion(@Nullable String region, String... requestIds); + + /** + * 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 nullableAvailabilityZone + * The availability zone to launch the instances in, or null to let the system choose + * @param imageId + * The AMI ID. + * @param instanceCount + * The maximum number of Spot Instances to launch. + * @param spotPrice + * Specifies the maximum hourly price for any Spot Instance launched to fulfill the + * request. + * @param options + * control the duration of the request, grouping, and the size and parameters of the + * server to run + * + * @see #describeSpotInstanceRequestsInRegion + * @see #cancelSpotInstanceRequestsInRegion + * @see #describeSpotPriceHistoryInRegion + * @see + * @return TODO + */ + String requestSpotInstancesInRegion(@Nullable String region, @Nullable String nullableAvailabilityZone, + String imageId, int instanceCount, float spotPrice, 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 + */ + String 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/DescribeSpotInstanceRequestsResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotInstanceRequestsResponseHandler.java new file mode 100644 index 0000000000..5958342c30 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotInstanceRequestsResponseHandler.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.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.Sets; + +/** + * @author Adrian Cole + */ +//TODO finish +public class DescribeSpotInstanceRequestsResponseHandler extends + ParseSax.HandlerWithResult> { + + private Set spotRequests = Sets.newLinkedHashSet(); + private final SpotInstanceRequestHandler spotRequestHandler; + + @Inject + public DescribeSpotInstanceRequestsResponseHandler(SpotInstanceRequestHandler spotRequestHandler) { + this.spotRequestHandler = spotRequestHandler; + } + + public Set getResult() { + return spotRequests; + } + + @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")) + spotRequestHandler.startElement(uri, localName, qName, attributes); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("item")) { + spotRequests.add(spotRequestHandler.getResult()); + } + spotRequestHandler.endElement(uri, localName, qName); + } + + public void characters(char ch[], int start, int length) { + spotRequestHandler.characters(ch, start, length); + } + +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java new file mode 100644 index 0000000000..55bbf9a07b --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java @@ -0,0 +1,97 @@ +/** + * + * 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.Date; + +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest.Type; +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 + */ +// TODO finish +public class SpotInstanceRequestHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + private StringBuilder currentText = new StringBuilder(); + + @Inject + protected DateService dateService; + @Inject + @Region + private String defaultRegion; + private String availabilityZoneGroup; + private Date createTime; + private String fault; + private String instanceId; + private String launchGroup; + private String launchSpecification; + private String productDescription; + private String id; + private float spotPrice; + private String state; + private Type type; + private Date validFrom; + private Date validUntil; + + public SpotInstanceRequest getResult() { + String region = AWSUtils.findRegionInArgsOrNull(getRequest()); + if (region == null) + region = defaultRegion; + SpotInstanceRequest returnVal = new SpotInstanceRequest(region, availabilityZoneGroup, createTime, fault, + instanceId, launchGroup, launchSpecification, productDescription, id, spotPrice, state, type, validFrom, + validUntil); + this.availabilityZoneGroup = null; + this.createTime = null; + this.fault = null; + this.instanceId = null; + this.launchGroup = null; + this.launchSpecification = null; + this.productDescription = null; + this.id = null; + this.spotPrice = -1; + this.state = null; + this.type = null; + this.validFrom = null; + this.validUntil = null; + return returnVal; + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("availabilityZoneGroup")) { + this.availabilityZoneGroup = currentText.toString().trim(); + } else if (qName.equals("createTime")) { + createTime = this.dateService.iso8601DateParse(currentText.toString().trim()); + } else if (qName.equals("type")) { + type = SpotInstanceRequest.Type.fromValue(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/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..ae59d6ea2d --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptionsTest.java @@ -0,0 +1,179 @@ +/** + * + * 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.launchSpecification; +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.ec2.domain.BlockDeviceMapping; +import org.jclouds.ec2.options.RunInstancesOptions; +import org.jclouds.http.options.HttpRequestOptions; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * 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); + } + + RunInstancesOptions launchSpec = new RunInstancesOptions().withBlockDeviceMappings(ImmutableSet + . of(new BlockDeviceMapping.MapNewVolumeToDevice("/dev/sda1", 120, true))); + + @Test + public void testLaunchSpecification() { + RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); + options.launchSpecification(launchSpec); + verifyLaunchSpec(options); + } + + protected void verifyLaunchSpec(RequestSpotInstancesOptions options) { + assertEquals(options.buildFormParameters().get("LaunchSpecification.BlockDeviceMapping.1.DeviceName"), + Collections.singletonList("/dev/sda1")); + assertEquals(options.buildFormParameters().get("LaunchSpecification.BlockDeviceMapping.1.Ebs.VolumeSize"), + Collections.singletonList("120")); + assertEquals(options.buildFormParameters() + .get("LaunchSpecification.BlockDeviceMapping.1.Ebs.DeleteOnTermination"), Collections.singletonList("true")); + } + + @Test + public void testLaunchSpecificationStatic() { + RequestSpotInstancesOptions options = launchSpecification(launchSpec); + verifyLaunchSpec(options); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testLaunchSpecificationNPE() { + launchSpecification(null); + } +} 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..2baee34135 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClientTest.java @@ -0,0 +1,194 @@ +/** + * + * 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.options.DescribeSpotPriceHistoryOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +import org.jclouds.ec2.options.RunInstancesOptions; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.ReturnStringIf2xx; +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 testRequestSpotInstances() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("requestSpotInstancesInRegion", String.class, + String.class, String.class, int.class, float.class, RequestSpotInstancesOptions[].class); + HttpRequest request = processor.createRequest(method, null, null, "ami-voo", 1, 0.01); + + 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=ami-voo&InstanceCount=1&SpotPrice=0.01", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + checkFilters(request); + } + + public void testRequestSpotInstancesOptions() throws SecurityException, NoSuchMethodException, IOException { + Method method = SpotInstanceAsyncClient.class.getMethod("requestSpotInstancesInRegion", String.class, + String.class, String.class, int.class, float.class, RequestSpotInstancesOptions[].class); + HttpRequest request = processor.createRequest( + method, + "eu-west-1", + "eu-west-1a", + "ami-voo", + 1, + 0.01, + new RequestSpotInstancesOptions() + .validFrom(from) + .validUntil(to) + .availabilityZoneGroup("availabilityZoneGroup") + .launchGroup("launchGroup") + .launchSpecification( + RunInstancesOptions.Builder.withKernelId("kernelId").withSecurityGroups("group1", "group2"))); + + 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&LaunchSpecification.ImageId=ami-voo&InstanceCount=1&SpotPrice=0.01&ValidFrom=1970-05-23T21%3A21%3A18.910Z&ValidUntil=2009-02-13T23%3A31%3A31.011Z&AvailabilityZoneGroup=availabilityZoneGroup&LaunchGroup=launchGroup&LaunchSpecification.KernelId=kernelId&LaunchSpecification.SecurityGroup.1=group1&LaunchSpecification.SecurityGroup.2=group2&LaunchSpecification.Placement.AvailabilityZone=eu-west-1a", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + 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, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + 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, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + 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, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + 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, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + 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, ReturnStringIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + 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..da4d98a8d9 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceClientLiveTest.java @@ -0,0 +1,181 @@ +/** + * + * 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 org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; +import java.util.SortedSet; + +import org.jclouds.Constants; +import org.jclouds.aws.domain.Region; +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.domain.SpotInstanceRequest; +import org.jclouds.aws.ec2.options.AWSRunInstancesOptions; +import org.jclouds.aws.ec2.options.RequestSpotInstancesOptions; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ComputeServiceContextFactory; +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.common.collect.Iterables; +import com.google.inject.Module; + +/** + * Tests behavior of {@code SpotInstanceClient} + * + * @author Adrian Cole + */ +@Test(groups = "live", sequential = true) +public class SpotInstanceClientLiveTest { + + private AWSEC2Client client; + private ComputeServiceContext context; + private RetryablePredicate availableTester; + private RetryablePredicate deletedTester; + private SpotInstanceRequest request; + protected String provider = "aws-ec2"; + protected String identity; + protected String credential; + protected String endpoint; + protected String apiversion; + + @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()); + // TODO + // availableTester = new RetryablePredicate(new + // SpotInstanceAvailable(client), 60, 1, + // TimeUnit.SECONDS); + // + // deletedTester = new RetryablePredicate(new + // SpotInstanceDeleted(client), 60, 1, TimeUnit.SECONDS); + } + + @Test + void testDescribe() { + for (String region : Region.DEFAULT_REGIONS) { + String string = client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(region); + assertNotNull(string);// TODO + SortedSet allResults = ImmutableSortedSet.of(SpotInstanceRequest.builder().id("foo") + .build()); + assertNotNull(allResults); + if (allResults.size() >= 1) { + SpotInstanceRequest request = allResults.last(); + string = client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(region, request.getId()); + assertNotNull(string);// TODO + SortedSet result = ImmutableSortedSet.of(SpotInstanceRequest.builder().id("foo") + .build()); + assertNotNull(result); + SpotInstanceRequest compare = result.last(); + assertEquals(compare, request); + } + } + + for (String region : client.getAvailabilityZoneAndRegionServices().describeRegions().keySet()) { + if (!region.equals(Region.US_EAST_1)) + try { + client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(region); + assert false : "should be unsupported"; + } catch (UnsupportedOperationException e) { + } + } + } + + @Test + void testCreateSpotInstance() { + String launchGroup = PREFIX + "1"; + for (SpotInstanceRequest request : ImmutableSet. of()) + // client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(null); + if (request.getLaunchGroup().equals(launchGroup)) + client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion(null, request.getId()); + String string = client.getSpotInstanceServices().requestSpotInstancesInRegion( + null, + null, + "TODO", + 1, + 0.01f, + RequestSpotInstancesOptions.Builder.launchSpecification(AWSRunInstancesOptions.Builder.asType("m1.small")) + .launchGroup(PREFIX)); + assertNotNull(string); + + verifySpotInstance(request); + } + + private void verifySpotInstance(SpotInstanceRequest request) { + assert availableTester.apply(request) : request; + String string = client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(null, request.getId()); + assertNotNull(string);// TODO + SpotInstanceRequest oneResult = Iterables.getOnlyElement(ImmutableSet.of(SpotInstanceRequest.builder().id("foo") + .build())); + assertNotNull(oneResult); + assertEquals(oneResult, request); + // TODO: more + } + + public static final String PREFIX = System.getProperty("user.name") + "ec2"; + + @AfterTest + public void shutdown() { + if (request != null) { + client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion(request.getRegion(), request.getId()); + assert deletedTester.apply(request) : request; + } + context.close(); + } +} From 46a214b54bd29c615fe304bb12f4b5512ce535c0 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 8 Mar 2011 00:03:40 -0800 Subject: [PATCH 2/4] Issue 308 finished provider api and testing of ec2 spot instances --- .../compute/options/EC2TemplateOptions.java | 67 +-- .../ec2/domain/BlockDeviceMapping.java | 76 +++- .../ec2/options/RunInstancesOptions.java | 38 +- .../internal/BaseEC2RequestOptions.java | 10 +- .../internal/TemplateBuilderImplTest.java | 2 +- .../BindLaunchSpecificationToFormParams.java | 65 +++ ...ityZoneToLaunchSpecificationFormParam.java | 21 - .../ec2/compute/AWSEC2TemplateOptions.java | 2 +- .../aws/ec2/domain/LaunchSpecification.java | 350 +++++++++++++++ .../java/org/jclouds/aws/ec2/domain/Spot.java | 173 ++++++++ .../aws/ec2/domain/SpotInstanceRequest.java | 110 +++-- .../options/RequestSpotInstancesOptions.java | 21 - .../predicates/SpotInstanceRequestActive.java | 83 ++++ .../ec2/services/SpotInstanceAsyncClient.java | 46 +- .../aws/ec2/services/SpotInstanceClient.java | 28 +- ...scribeSpotPriceHistoryResponseHandler.java | 79 ++++ .../ec2/xml/LaunchSpecificationHandler.java | 115 +++++ .../aws/ec2/xml/PlacementGroupHandler.java | 3 - .../org/jclouds/aws/ec2/xml/SpotHandler.java | 75 ++++ .../ec2/xml/SpotInstanceRequestHandler.java | 122 ++++-- ... SpotInstanceRequestsResponseHandler.java} | 31 +- ...ndLaunchSpecificationToFormParamsTest.java | 60 +++ .../RequestSpotInstancesOptionsTest.java | 34 -- .../aws/ec2/services/AMIClientLiveTest.java | 22 +- .../services/SpotInstanceAsyncClientTest.java | 75 ++-- .../services/SpotInstanceClientLiveTest.java | 133 +++--- ...> AWSRunInstancesResponseHandlerTest.java} | 2 +- ...beSpotPriceHistoryResponseHandlerTest.java | 69 +++ .../xml/SpotInstanceRequestHandlerTest.java | 102 +++++ ...tInstancesRequestsResponseHandlerTest.java | 95 ++++ .../describe_spot_instance_requests.xml | 413 ++++++++++++++++++ .../resources/describe_spot_price_history.xml | 24 + .../resources/request_spot_instances-ebs.xml | 47 ++ .../test/resources/request_spot_instances.xml | 93 ++++ 34 files changed, 2290 insertions(+), 396 deletions(-) create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParams.java delete mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/LaunchSpecification.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Spot.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/predicates/SpotInstanceRequestActive.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandler.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/LaunchSpecificationHandler.java create mode 100644 providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotHandler.java rename providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/{DescribeSpotInstanceRequestsResponseHandler.java => SpotInstanceRequestsResponseHandler.java} (68%) create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindLaunchSpecificationToFormParamsTest.java rename providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/{RunInstancesResponseHandlerTest.java => AWSRunInstancesResponseHandlerTest.java} (98%) create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/DescribeSpotPriceHistoryResponseHandlerTest.java create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandlerTest.java create mode 100644 providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstancesRequestsResponseHandlerTest.java create mode 100644 providers/aws-ec2/src/test/resources/describe_spot_instance_requests.xml create mode 100644 providers/aws-ec2/src/test/resources/describe_spot_price_history.xml create mode 100644 providers/aws-ec2/src/test/resources/request_spot_instances-ebs.xml create mode 100644 providers/aws-ec2/src/test/resources/request_spot_instances.xml 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/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/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/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/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/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java deleted file mode 100644 index 6a34e6185a..0000000000 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.jclouds.aws.ec2.binders; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.jclouds.ec2.binders.IfNotNullBindAvailabilityZoneToFormParam; - -/** - * Binds the AvailabilityZone to a form parameter if set. - * - * @author Adrian Cole - */ -@Singleton -public class IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam extends IfNotNullBindAvailabilityZoneToFormParam { - - @Inject - protected IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam() { - super("LaunchSpecification.Placement.AvailabilityZone"); - } - -} 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..2dbb0e83a2 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 @@ -294,7 +294,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)); } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/LaunchSpecification.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/LaunchSpecification.java new file mode 100644 index 0000000000..dd7500b292 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/LaunchSpecification.java @@ -0,0 +1,350 @@ +/** + * + * 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.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 index d0d837b07f..adb52ad73c 100644 --- 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 @@ -38,18 +38,38 @@ public class SpotInstanceRequest implements Comparable { private String region; private String availabilityZoneGroup; private Date createTime; - private String fault; + private String faultCode; + private String faultMessage; private String instanceId; private String launchGroup; - private String launchSpecification; + private LaunchSpecification launchSpecification; private String productDescription; private String id; private float spotPrice; - private String state; + 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; @@ -65,8 +85,13 @@ public class SpotInstanceRequest implements Comparable { return this; } - public Builder fault(String fault) { - this.fault = fault; + public Builder faultCode(String faultCode) { + this.faultCode = faultCode; + return this; + } + + public Builder faultMessage(String faultMessage) { + this.faultMessage = faultMessage; return this; } @@ -80,7 +105,7 @@ public class SpotInstanceRequest implements Comparable { return this; } - public Builder launchSpecification(String launchSpecification) { + public Builder launchSpecification(LaunchSpecification launchSpecification) { this.launchSpecification = launchSpecification; return this; } @@ -100,7 +125,7 @@ public class SpotInstanceRequest implements Comparable { return this; } - public Builder state(String state) { + public Builder state(State state) { this.state = state; return this; } @@ -121,8 +146,8 @@ public class SpotInstanceRequest implements Comparable { } public SpotInstanceRequest build() { - return new SpotInstanceRequest(region, availabilityZoneGroup, createTime, fault, instanceId, launchGroup, - launchSpecification, productDescription, id, spotPrice, state, type, validFrom, validUntil); + return new SpotInstanceRequest(region, availabilityZoneGroup, createTime, faultCode, faultMessage, instanceId, + launchGroup, launchSpecification, productDescription, id, spotPrice, state, type, validFrom, validUntil); } } @@ -147,28 +172,51 @@ public class SpotInstanceRequest implements Comparable { } } + public enum State { + OPEN, ACTIVE, CANCELLED, 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 fault; + private final String faultCode; + private final String faultMessage; private final String instanceId; private final String launchGroup; - private final String launchSpecification; + private final LaunchSpecification launchSpecification; private final String productDescription; private final String id; private final float spotPrice; - private final String state; + 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 fault, - String instanceId, String launchGroup, String launchSpecification, String productDescription, String id, - float spotPrice, String state, Type type, Date validFrom, 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.fault = fault; + this.faultCode = faultCode; + this.faultMessage = faultMessage; this.instanceId = instanceId; this.launchGroup = launchGroup; this.launchSpecification = launchSpecification; @@ -196,8 +244,12 @@ public class SpotInstanceRequest implements Comparable { return createTime; } - public String getFault() { - return fault; + public String getFaultCode() { + return faultCode; + } + + public String getFaultMessage() { + return faultMessage; } public String getInstanceId() { @@ -208,7 +260,7 @@ public class SpotInstanceRequest implements Comparable { return launchGroup; } - public String getLaunchSpecification() { + public LaunchSpecification getLaunchSpecification() { return launchSpecification; } @@ -224,7 +276,7 @@ public class SpotInstanceRequest implements Comparable { return spotPrice; } - public String getState() { + public State getState() { return state; } @@ -246,7 +298,8 @@ public class SpotInstanceRequest implements Comparable { int result = 1; result = prime * result + ((availabilityZoneGroup == null) ? 0 : availabilityZoneGroup.hashCode()); result = prime * result + ((createTime == null) ? 0 : createTime.hashCode()); - result = prime * result + ((fault == null) ? 0 : fault.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()); @@ -279,10 +332,15 @@ public class SpotInstanceRequest implements Comparable { return false; } else if (!createTime.equals(other.createTime)) return false; - if (fault == null) { - if (other.fault != null) + if (faultCode == null) { + if (other.faultCode != null) return false; - } else if (!fault.equals(other.fault)) + } 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) @@ -334,8 +392,8 @@ public class SpotInstanceRequest implements Comparable { @Override public String toString() { return "[region=" + region + ", id=" + id + ", spotPrice=" + spotPrice + ", state=" + state - + ", availabilityZoneGroup=" + availabilityZoneGroup + ", createTime=" + createTime + ", fault=" + fault - + ", type=" + type + ", instanceId=" + instanceId + ", launchGroup=" + launchGroup + + ", availabilityZoneGroup=" + availabilityZoneGroup + ", createTime=" + createTime + ", faultCode=" + + faultCode + ", type=" + type + ", instanceId=" + instanceId + ", launchGroup=" + launchGroup + ", launchSpecification=" + launchSpecification + ", productDescription=" + productDescription + ", validFrom=" + validFrom + ", validUntil=" + validUntil + "]"; } 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 index c87294be66..e8e30eb265 100644 --- 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 @@ -22,12 +22,10 @@ package org.jclouds.aws.ec2.options; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Date; -import java.util.Map.Entry; import org.jclouds.aws.ec2.domain.SpotInstanceRequest; import org.jclouds.date.DateService; import org.jclouds.date.internal.SimpleDateFormatDateService; -import org.jclouds.ec2.options.RunInstancesOptions; import org.jclouds.ec2.options.internal.BaseEC2RequestOptions; /** @@ -99,18 +97,6 @@ public class RequestSpotInstancesOptions extends BaseEC2RequestOptions { 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 launchSpecification(RunInstancesOptions launchSpecification) { - for (Entry entry : checkNotNull(launchSpecification, "launchSpecification").buildFormParameters() - .entries()) { - formParameters.put("LaunchSpecification." + entry.getKey(), entry.getValue()); - } - return this; - } - public static class Builder { /** * @see RequestSpotInstancesOptions#validFrom @@ -152,12 +138,5 @@ public class RequestSpotInstancesOptions extends BaseEC2RequestOptions { return options.availabilityZoneGroup(availabilityZoneGroup); } - /** - * @see RequestSpotInstancesOptions#launchSpecification - */ - public static RequestSpotInstancesOptions launchSpecification(RunInstancesOptions launchSpecification) { - RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); - return options.launchSpecification(launchSpecification); - } } } 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..d1b2b762c1 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/predicates/SpotInstanceRequestActive.java @@ -0,0 +1,83 @@ +/** + * + * 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 java.util.concurrent.ExecutionException; + +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.base.Throwables; +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) + Throwables.propagate(new ExecutionException(String.format("spot request %s cancelled", spot.getId())) { + private static final long serialVersionUID = 1L; + }); + if (spot.getFaultCode() != null) + Throwables.propagate(new ExecutionException(String.format("spot request %s fault code(%s) message(%s)", + spot.getId(), spot.getFaultCode(), spot.getFaultMessage())) { + private static final long serialVersionUID = 1L; + }); + 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 index cff2d2b962..b4a58e0037 100644 --- 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 @@ -22,23 +22,34 @@ 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.binders.IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam; +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.SpotInstanceRequestsResponseHandler; +import org.jclouds.aws.ec2.xml.DescribeSpotPriceHistoryResponseHandler; 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; @@ -59,20 +70,36 @@ public interface SpotInstanceAsyncClient { @POST @Path("/") @FormParams(keys = ACTION, values = "DescribeSpotInstanceRequests") - ListenableFuture describeSpotInstanceRequestsInRegion( + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + @XMLResponseParser(SpotInstanceRequestsResponseHandler.class) + ListenableFuture> describeSpotInstanceRequestsInRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, @BinderParam(BindSpotInstanceRequestIdsToIndexedFormParams.class) String... requestIds); /** - * @see SpotInstanceClient#requestSpotInstancesInRegion + * @see SpotInstanceClient#requestSpotInstancesInRegion(String,float,String,String) */ @POST @Path("/") @FormParams(keys = ACTION, values = "RequestSpotInstances") - ListenableFuture requestSpotInstancesInRegion( + @XMLResponseParser(SpotInstanceRequestsResponseHandler.class) + ListenableFuture> requestSpotInstancesInRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, - @Nullable @BinderParam(IfNotNullBindAvailabilityZoneToLaunchSpecificationFormParam.class) String nullableAvailabilityZone, - @FormParam("LaunchSpecification.ImageId") String imageId,@FormParam("InstanceCount") int instanceCount, @FormParam("SpotPrice") float spotPrice, + @FormParam("SpotPrice") float spotPrice, @FormParam("LaunchSpecification.ImageId") String imageId, + @FormParam("LaunchSpecification.InstanceType") String instanceType); + + /** + * @see SpotInstanceClient#requestSpotInstancesInRegion(String,float,int, + * LaunchSpecification,RequestSpotInstancesOptions[]) + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "RequestSpotInstances") + @XMLResponseParser(SpotInstanceRequestsResponseHandler.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); /** @@ -81,7 +108,9 @@ public interface SpotInstanceAsyncClient { @POST @Path("/") @FormParams(keys = ACTION, values = "DescribeSpotPriceHistory") - ListenableFuture describeSpotPriceHistoryInRegion( + @XMLResponseParser(DescribeSpotPriceHistoryResponseHandler.class) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> describeSpotPriceHistoryInRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, DescribeSpotPriceHistoryOptions... options); @@ -91,7 +120,8 @@ public interface SpotInstanceAsyncClient { @POST @Path("/") @FormParams(keys = ACTION, values = "CancelSpotInstanceRequests") - ListenableFuture cancelSpotInstanceRequestsInRegion( + @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 index 44bd916e2e..c5f6010048 100644 --- 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 @@ -19,10 +19,14 @@ 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; @@ -56,7 +60,7 @@ public interface SpotInstanceClient { * /> * @return TODO */ - String describeSpotInstanceRequestsInRegion(@Nullable String region, String... requestIds); + Set describeSpotInstanceRequestsInRegion(@Nullable String region, String... requestIds); /** * Creates a Spot Instance request. Spot Instances are instances that Amazon EC2 starts on your @@ -67,18 +71,13 @@ public interface SpotInstanceClient { * * @param region * Region where the spot instance service is running - * @param nullableAvailabilityZone - * The availability zone to launch the instances in, or null to let the system choose - * @param imageId - * The AMI ID. - * @param instanceCount - * The maximum number of Spot Instances to launch. * @param spotPrice * Specifies the maximum hourly price for any Spot Instance launched to fulfill the * request. - * @param options - * control the duration of the request, grouping, and the size and parameters of the - * server to run + * @param imageId + * The AMI ID. + * @param instanceType + * The instance type (ex. m1.small) * * @see #describeSpotInstanceRequestsInRegion * @see #cancelSpotInstanceRequestsInRegion @@ -88,8 +87,10 @@ public interface SpotInstanceClient { * /> * @return TODO */ - String requestSpotInstancesInRegion(@Nullable String region, @Nullable String nullableAvailabilityZone, - String imageId, int instanceCount, float spotPrice, RequestSpotInstancesOptions... options); + Set requestSpotInstancesInRegion(@Nullable String region, float spotPrice, String imageId, String instanceType); + + Set requestSpotInstancesInRegion(@Nullable String region, float spotPrice, int instanceCount, + LaunchSpecification launchSpec, RequestSpotInstancesOptions... options); /** * @@ -112,7 +113,8 @@ public interface SpotInstanceClient { * /> * @return TODO */ - String describeSpotPriceHistoryInRegion(@Nullable String region, DescribeSpotPriceHistoryOptions... options); + @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 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/SpotInstanceRequestHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java index 55bbf9a07b..41743fdfea 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java @@ -19,79 +19,107 @@ package org.jclouds.aws.ec2.xml; -import java.util.Date; - import javax.inject.Inject; import org.jclouds.aws.ec2.domain.SpotInstanceRequest; -import org.jclouds.aws.ec2.domain.SpotInstanceRequest.Type; +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 */ -// TODO finish public class SpotInstanceRequestHandler 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 - protected DateService dateService; - @Inject - @Region - private String defaultRegion; - private String availabilityZoneGroup; - private Date createTime; - private String fault; - private String instanceId; - private String launchGroup; - private String launchSpecification; - private String productDescription; - private String id; - private float spotPrice; - private String state; - private Type type; - private Date validFrom; - private Date validUntil; + public SpotInstanceRequestHandler(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() { - String region = AWSUtils.findRegionInArgsOrNull(getRequest()); - if (region == null) - region = defaultRegion; - SpotInstanceRequest returnVal = new SpotInstanceRequest(region, availabilityZoneGroup, createTime, fault, - instanceId, launchGroup, launchSpecification, productDescription, id, spotPrice, state, type, validFrom, - validUntil); - this.availabilityZoneGroup = null; - this.createTime = null; - this.fault = null; - this.instanceId = null; - this.launchGroup = null; - this.launchSpecification = null; - this.productDescription = null; - this.id = null; - this.spotPrice = -1; - this.state = null; - this.type = null; - this.validFrom = null; - this.validUntil = null; - return returnVal; + 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("availabilityZoneGroup")) { - this.availabilityZoneGroup = currentText.toString().trim(); - } else if (qName.equals("createTime")) { - createTime = this.dateService.iso8601DateParse(currentText.toString().trim()); + 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")) { - type = SpotInstanceRequest.Type.fromValue(currentText.toString().trim()); + 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) { - currentText.append(ch, start, 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/DescribeSpotInstanceRequestsResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestsResponseHandler.java similarity index 68% rename from providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotInstanceRequestsResponseHandler.java rename to providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestsResponseHandler.java index 5958342c30..251f71e62b 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeSpotInstanceRequestsResponseHandler.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestsResponseHandler.java @@ -30,25 +30,25 @@ import org.jclouds.http.functions.ParseSax.HandlerWithResult; import org.xml.sax.Attributes; import org.xml.sax.SAXException; -import com.google.common.collect.Sets; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; /** * @author Adrian Cole */ -//TODO finish -public class DescribeSpotInstanceRequestsResponseHandler extends - ParseSax.HandlerWithResult> { +public class SpotInstanceRequestsResponseHandler extends ParseSax.HandlerWithResult> { - private Set spotRequests = Sets.newLinkedHashSet(); + private final Builder spotRequests = ImmutableSet. builder(); private final SpotInstanceRequestHandler spotRequestHandler; + private int itemDepth; @Inject - public DescribeSpotInstanceRequestsResponseHandler(SpotInstanceRequestHandler spotRequestHandler) { + public SpotInstanceRequestsResponseHandler(SpotInstanceRequestHandler spotRequestHandler) { this.spotRequestHandler = spotRequestHandler; } public Set getResult() { - return spotRequests; + return spotRequests.build(); } @Override @@ -58,22 +58,27 @@ public class DescribeSpotInstanceRequestsResponseHandler extends } @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - if (!qName.equals("item")) + 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")) { + if (qName.equals("item") && itemDepth == 1) { spotRequests.add(spotRequestHandler.getResult()); } - spotRequestHandler.endElement(uri, localName, qName); + if (qName.equals("item")) + itemDepth--; + if (itemDepth >= 1) + spotRequestHandler.endElement(uri, localName, qName); } public void characters(char ch[], int start, int length) { - spotRequestHandler.characters(ch, start, 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/options/RequestSpotInstancesOptionsTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/options/RequestSpotInstancesOptionsTest.java index ae59d6ea2d..77fb3851df 100644 --- 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 @@ -21,7 +21,6 @@ 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.launchSpecification; 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; @@ -31,13 +30,9 @@ import java.util.Collections; import java.util.Date; import org.jclouds.aws.ec2.domain.SpotInstanceRequest; -import org.jclouds.ec2.domain.BlockDeviceMapping; -import org.jclouds.ec2.options.RunInstancesOptions; import org.jclouds.http.options.HttpRequestOptions; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableSet; - /** * Tests possible uses of RequestSpotInstancesOptions and RequestSpotInstancesOptions.Builder.* * @@ -147,33 +142,4 @@ public class RequestSpotInstancesOptionsTest { validUntil(null); } - RunInstancesOptions launchSpec = new RunInstancesOptions().withBlockDeviceMappings(ImmutableSet - . of(new BlockDeviceMapping.MapNewVolumeToDevice("/dev/sda1", 120, true))); - - @Test - public void testLaunchSpecification() { - RequestSpotInstancesOptions options = new RequestSpotInstancesOptions(); - options.launchSpecification(launchSpec); - verifyLaunchSpec(options); - } - - protected void verifyLaunchSpec(RequestSpotInstancesOptions options) { - assertEquals(options.buildFormParameters().get("LaunchSpecification.BlockDeviceMapping.1.DeviceName"), - Collections.singletonList("/dev/sda1")); - assertEquals(options.buildFormParameters().get("LaunchSpecification.BlockDeviceMapping.1.Ebs.VolumeSize"), - Collections.singletonList("120")); - assertEquals(options.buildFormParameters() - .get("LaunchSpecification.BlockDeviceMapping.1.Ebs.DeleteOnTermination"), Collections.singletonList("true")); - } - - @Test - public void testLaunchSpecificationStatic() { - RequestSpotInstancesOptions options = launchSpecification(launchSpec); - verifyLaunchSpec(options); - } - - @Test(expectedExceptions = NullPointerException.class) - public void testLaunchSpecificationNPE() { - launchSpecification(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/SpotInstanceAsyncClientTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/SpotInstanceAsyncClientTest.java index 2baee34135..70433dea6a 100644 --- 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 @@ -23,11 +23,16 @@ 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.ec2.options.RunInstancesOptions; +import org.jclouds.aws.ec2.xml.DescribeSpotPriceHistoryResponseHandler; +import org.jclouds.aws.ec2.xml.SpotInstanceRequestsResponseHandler; import org.jclouds.http.HttpRequest; -import org.jclouds.http.functions.ReturnStringIf2xx; +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; @@ -43,18 +48,18 @@ import com.google.inject.TypeLiteral; public class SpotInstanceAsyncClientTest extends BaseAWSEC2AsyncClientTest { public void testRequestSpotInstances() throws SecurityException, NoSuchMethodException, IOException { Method method = SpotInstanceAsyncClient.class.getMethod("requestSpotInstancesInRegion", String.class, - String.class, String.class, int.class, float.class, RequestSpotInstancesOptions[].class); - HttpRequest request = processor.createRequest(method, null, null, "ami-voo", 1, 0.01); + 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=ami-voo&InstanceCount=1&SpotPrice=0.01", + "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, ReturnStringIf2xx.class); - assertSaxResponseParserClassEquals(method, null); + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, SpotInstanceRequestsResponseHandler.class); assertExceptionParserClassEquals(method, null); checkFilters(request); @@ -62,31 +67,21 @@ public class SpotInstanceAsyncClientTest extends BaseAWSEC2AsyncClientTest availableTester; - private RetryablePredicate deletedTester; - private SpotInstanceRequest request; + 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() { @@ -98,83 +109,97 @@ public class SpotInstanceClientLiveTest { ImmutableSet. of(new Log4JLoggingModule(), new JschSshClientModule()), overrides); client = AWSEC2Client.class.cast(context.getProviderSpecificContext().getApi()); - // TODO - // availableTester = new RetryablePredicate(new - // SpotInstanceAvailable(client), 60, 1, - // TimeUnit.SECONDS); - // - // deletedTester = new RetryablePredicate(new - // SpotInstanceDeleted(client), 60, 1, TimeUnit.SECONDS); + activeTester = new RetryablePredicate(new SpotInstanceRequestActive(client), + SPOT_DELAY_SECONDS, 1, 1, TimeUnit.SECONDS); } @Test - void testDescribe() { + void testDescribeSpotRequestsInRegion() { for (String region : Region.DEFAULT_REGIONS) { - String string = client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(region); - assertNotNull(string);// TODO - SortedSet allResults = ImmutableSortedSet.of(SpotInstanceRequest.builder().id("foo") - .build()); + SortedSet allResults = ImmutableSortedSet.copyOf(client.getSpotInstanceServices() + .describeSpotInstanceRequestsInRegion(region)); assertNotNull(allResults); if (allResults.size() >= 1) { SpotInstanceRequest request = allResults.last(); - string = client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(region, request.getId()); - assertNotNull(string);// TODO - SortedSet result = ImmutableSortedSet.of(SpotInstanceRequest.builder().id("foo") - .build()); + SortedSet result = ImmutableSortedSet.copyOf(client.getSpotInstanceServices() + .describeSpotInstanceRequestsInRegion(region, request.getId())); assertNotNull(result); SpotInstanceRequest compare = result.last(); assertEquals(compare, request); } } - for (String region : client.getAvailabilityZoneAndRegionServices().describeRegions().keySet()) { - if (!region.equals(Region.US_EAST_1)) - try { - client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(region); - assert false : "should be unsupported"; - } catch (UnsupportedOperationException e) { - } - } } @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 : ImmutableSet. of()) - // client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(null); - if (request.getLaunchGroup().equals(launchGroup)) - client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion(null, request.getId()); - String string = client.getSpotInstanceServices().requestSpotInstancesInRegion( - null, - null, - "TODO", + 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, - 0.01f, - RequestSpotInstancesOptions.Builder.launchSpecification(AWSRunInstancesOptions.Builder.asType("m1.small")) - .launchGroup(PREFIX)); - assertNotNull(string); + LaunchSpecification.builder().instanceType(InstanceType.T1_MICRO).imageId("ami-595a0a1c").build(), + launchGroup(launchGroup).availabilityZoneGroup(launchGroup).validFrom(new Date()) + .validUntil(new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(SPOT_DELAY_SECONDS)))); + assertNotNull(requests); - verifySpotInstance(request); + for (SpotInstanceRequest request : requests) + verifySpotInstance(request); } private void verifySpotInstance(SpotInstanceRequest request) { - assert availableTester.apply(request) : request; - String string = client.getSpotInstanceServices().describeSpotInstanceRequestsInRegion(null, request.getId()); - assertNotNull(string);// TODO - SpotInstanceRequest oneResult = Iterables.getOnlyElement(ImmutableSet.of(SpotInstanceRequest.builder().id("foo") - .build())); - assertNotNull(oneResult); - assertEquals(oneResult, request); - // TODO: more + 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 (request != null) { - client.getSpotInstanceServices().cancelSpotInstanceRequestsInRegion(request.getRegion(), request.getId()); - assert deletedTester.apply(request) : request; + 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/SpotInstanceRequestHandlerTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandlerTest.java new file mode 100644 index 0000000000..85ec0c4914 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandlerTest.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 SpotInstanceRequestHandler} + * + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "SpotInstanceRequestHandlerTest") +public class SpotInstanceRequestHandlerTest 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(); + SpotInstanceRequestHandler handler = injector.getInstance(SpotInstanceRequestHandler.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/SpotInstancesRequestsResponseHandlerTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstancesRequestsResponseHandlerTest.java new file mode 100644 index 0000000000..600c35d5c0 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/SpotInstancesRequestsResponseHandlerTest.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 SpotInstancesRequestsResponseHandler} + * + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "SpotInstancesRequestsResponseHandlerTest") +public class SpotInstancesRequestsResponseHandlerTest 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"); + SpotInstanceRequestsResponseHandler handler = injector + .getInstance(SpotInstanceRequestsResponseHandler.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"); + SpotInstanceRequestsResponseHandler handler = injector + .getInstance(SpotInstanceRequestsResponseHandler.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 From 25db134edc8852e25cd32dd0be4345b69ebd8442 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 8 Mar 2011 00:31:56 -0800 Subject: [PATCH 3/4] fix for duplicate key exception --- .../java/org/jclouds/ec2/compute/EC2ComputeService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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; From 77d91f6e1ac79ad18eb2c7b0725c81382e4dfb45 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 8 Mar 2011 00:32:44 -0800 Subject: [PATCH 4/4] changed single instance spot syntax to only return a single value --- .../ec2/services/SpotInstanceAsyncClient.java | 16 +++++----- .../aws/ec2/services/SpotInstanceClient.java | 32 +++++++++++++++---- ...tHandler.java => SpotInstanceHandler.java} | 4 +-- ...Handler.java => SpotInstancesHandler.java} | 6 ++-- .../services/SpotInstanceAsyncClientTest.java | 15 +++++---- .../services/SpotInstanceClientLiveTest.java | 3 +- ...Test.java => SpotInstanceHandlerTest.java} | 8 ++--- ...est.java => SpotInstancesHandlerTest.java} | 14 ++++---- 8 files changed, 59 insertions(+), 39 deletions(-) rename providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/{SpotInstanceRequestHandler.java => SpotInstanceHandler.java} (95%) rename providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/{SpotInstanceRequestsResponseHandler.java => SpotInstancesHandler.java} (90%) rename providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/{SpotInstanceRequestHandlerTest.java => SpotInstanceHandlerTest.java} (92%) rename providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/xml/{SpotInstancesRequestsResponseHandlerTest.java => SpotInstancesHandlerTest.java} (86%) 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 index b4a58e0037..3d918adc12 100644 --- 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 @@ -37,8 +37,9 @@ 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.SpotInstanceRequestsResponseHandler; 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; @@ -71,31 +72,30 @@ public interface SpotInstanceAsyncClient { @Path("/") @FormParams(keys = ACTION, values = "DescribeSpotInstanceRequests") @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - @XMLResponseParser(SpotInstanceRequestsResponseHandler.class) + @XMLResponseParser(SpotInstancesHandler.class) ListenableFuture> describeSpotInstanceRequestsInRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, @BinderParam(BindSpotInstanceRequestIdsToIndexedFormParams.class) String... requestIds); /** - * @see SpotInstanceClient#requestSpotInstancesInRegion(String,float,String,String) + * @see SpotInstanceClient#requestSpotInstanceInRegion */ @POST @Path("/") @FormParams(keys = ACTION, values = "RequestSpotInstances") - @XMLResponseParser(SpotInstanceRequestsResponseHandler.class) - ListenableFuture> requestSpotInstancesInRegion( + @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(String,float,int, - * LaunchSpecification,RequestSpotInstancesOptions[]) + * @see SpotInstanceClient#requestSpotInstancesInRegion */ @POST @Path("/") @FormParams(keys = ACTION, values = "RequestSpotInstances") - @XMLResponseParser(SpotInstanceRequestsResponseHandler.class) + @XMLResponseParser(SpotInstancesHandler.class) ListenableFuture> requestSpotInstancesInRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, @FormParam("SpotPrice") float spotPrice, @FormParam("InstanceCount") int instanceCount, 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 index c5f6010048..2771d4153b 100644 --- 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 @@ -62,6 +62,24 @@ public interface SpotInstanceClient { */ 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 @@ -74,10 +92,12 @@ public interface SpotInstanceClient { * @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) + * @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 @@ -85,10 +105,8 @@ public interface SpotInstanceClient { * @see - * @return TODO + * @return set of spot instance requests */ - Set requestSpotInstancesInRegion(@Nullable String region, float spotPrice, String imageId, String instanceType); - Set requestSpotInstancesInRegion(@Nullable String region, float spotPrice, int instanceCount, LaunchSpecification launchSpec, RequestSpotInstancesOptions... options); diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceHandler.java similarity index 95% rename from providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java rename to providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceHandler.java index 41743fdfea..c4bde57afe 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestHandler.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceHandler.java @@ -33,7 +33,7 @@ import org.xml.sax.Attributes; * * @author Adrian Cole */ -public class SpotInstanceRequestHandler extends ParseSax.HandlerForGeneratedRequestWithResult { +public class SpotInstanceHandler extends ParseSax.HandlerForGeneratedRequestWithResult { private StringBuilder currentText = new StringBuilder(); protected final DateService dateService; @@ -43,7 +43,7 @@ public class SpotInstanceRequestHandler extends ParseSax.HandlerForGeneratedRequ protected final LaunchSpecificationHandler launchSpecificationHandler; @Inject - public SpotInstanceRequestHandler(DateService dateService, @Region String defaultRegion, + public SpotInstanceHandler(DateService dateService, @Region String defaultRegion, LaunchSpecificationHandler launchSpecificationHandler, SpotInstanceRequest.Builder builder) { this.dateService = dateService; this.defaultRegion = defaultRegion; diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestsResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstancesHandler.java similarity index 90% rename from providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestsResponseHandler.java rename to providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstancesHandler.java index 251f71e62b..d5022fe8b4 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstanceRequestsResponseHandler.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/SpotInstancesHandler.java @@ -36,14 +36,14 @@ import com.google.common.collect.ImmutableSet.Builder; /** * @author Adrian Cole */ -public class SpotInstanceRequestsResponseHandler extends ParseSax.HandlerWithResult> { +public class SpotInstancesHandler extends ParseSax.HandlerWithResult> { private final Builder spotRequests = ImmutableSet. builder(); - private final SpotInstanceRequestHandler spotRequestHandler; + private final SpotInstanceHandler spotRequestHandler; private int itemDepth; @Inject - public SpotInstanceRequestsResponseHandler(SpotInstanceRequestHandler spotRequestHandler) { + public SpotInstancesHandler(SpotInstanceHandler spotRequestHandler) { this.spotRequestHandler = spotRequestHandler; } 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 index 70433dea6a..6e1dde535d 100644 --- 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 @@ -27,7 +27,8 @@ 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.SpotInstanceRequestsResponseHandler; +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; @@ -46,8 +47,8 @@ import com.google.inject.TypeLiteral; // 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 testRequestSpotInstances() throws SecurityException, NoSuchMethodException, IOException { - Method method = SpotInstanceAsyncClient.class.getMethod("requestSpotInstancesInRegion", String.class, + 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"); @@ -59,7 +60,7 @@ public class SpotInstanceAsyncClientTest extends BaseAWSEC2AsyncClientTest result = factory.create(handler).parse(is); assertEquals(result.size(), 18); @@ -79,8 +79,8 @@ public class SpotInstancesRequestsResponseHandlerTest extends BaseEC2HandlerTest public void testRequest() { InputStream is = getClass().getResourceAsStream("/request_spot_instances.xml"); - SpotInstanceRequestsResponseHandler handler = injector - .getInstance(SpotInstanceRequestsResponseHandler.class); + SpotInstancesHandler handler = injector + .getInstance(SpotInstancesHandler.class); addDefaultRegionToHandler(handler); Set result = factory.create(handler).parse(is); assertEquals(result.size(), 3);