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