diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java index 7b179a1b7a..8a77bdbb8e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -77,7 +77,7 @@ public class Volume { private int size; private String zone; private Date created; - private Set attachments = Sets.newLinkedHashSet(); + private Set attachments = Sets.newLinkedHashSet(); private String volumeType; private String snapshotId; private String name; @@ -109,7 +109,7 @@ public class Volume { return self(); } - public T attachments(Set attachments) { + public T attachments(Set attachments) { this.attachments = attachments; return self(); } @@ -173,7 +173,7 @@ public class Volume { private final String zone; @SerializedName(value="createdAt") private final Date created; - private final Set attachments; + private final Set attachments; private final String volumeType; private final String snapshotId; @SerializedName(value="displayName") @@ -229,7 +229,7 @@ public class Volume { /** */ @Nullable - public Set getAttachments() { + public Set getAttachments() { return Collections.unmodifiableSet(this.attachments); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java similarity index 92% rename from apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java rename to apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java index a9388b201d..ef93d9d18b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java @@ -26,7 +26,7 @@ import com.google.common.base.Objects.ToStringHelper; /** * An Openstack Nova Volume Attachment */ -public class Attachment { +public class VolumeAttachment { public static Builder builder() { return new ConcreteBuilder(); @@ -64,11 +64,11 @@ public class Attachment { return self(); } - public Attachment build() { - return new Attachment(this); + public VolumeAttachment build() { + return new VolumeAttachment(this); } - public T fromAttachment(Attachment in) { + public T fromAttachment(VolumeAttachment in) { return this .id(in.getId()) .volumeId(in.getVolumeId()) @@ -91,7 +91,7 @@ public class Attachment { private final String serverId; private final String device; - protected Attachment(Builder builder) { + protected VolumeAttachment(Builder builder) { this.id = builder.id; this.volumeId = builder.volumeId; this.serverId = builder.serverId; @@ -135,7 +135,7 @@ public class Attachment { public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - Attachment that = Attachment.class.cast(obj); + VolumeAttachment that = VolumeAttachment.class.cast(obj); return Objects.equal(this.id, that.id) && Objects.equal(this.volumeId, that.volumeId) && Objects.equal(this.serverId, that.serverId) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java similarity index 92% rename from apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java rename to apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java index d2db66289c..fa7ca86cb8 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java @@ -29,7 +29,7 @@ import com.google.gson.annotations.SerializedName; /** * An Openstack Nova Volume Snapshot */ -public class Snapshot { +public class VolumeSnapshot { public static Builder builder() { return new ConcreteBuilder(); @@ -44,7 +44,7 @@ public class Snapshot { private String id; private String volumeId; - private String status; + private Volume.Status status; private int size; private Date created; private String name; @@ -60,7 +60,7 @@ public class Snapshot { return self(); } - public T status(String status) { + public T status(Volume.Status status) { this.status = status; return self(); } @@ -85,11 +85,11 @@ public class Snapshot { return self(); } - public Snapshot build() { - return new Snapshot(this); + public VolumeSnapshot build() { + return new VolumeSnapshot(this); } - public T fromSnapshot(Snapshot in) { + public T fromSnapshot(VolumeSnapshot in) { return this .id(in.getId()) .volumeId(in.getVolumeId()) @@ -112,7 +112,7 @@ public class Snapshot { private final String id; private final String volumeId; - private final String status; + private final Volume.Status status; private final int size; @SerializedName(value="createdAt") private final Date created; @@ -121,7 +121,7 @@ public class Snapshot { @SerializedName(value="displayDescription") private final String description; - protected Snapshot(Builder builder) { + protected VolumeSnapshot(Builder builder) { this.id = builder.id; this.volumeId = builder.volumeId; this.status = builder.status; @@ -148,7 +148,7 @@ public class Snapshot { /** */ @Nullable - public String getStatus() { + public Volume.Status getStatus() { return this.status; } @@ -189,7 +189,7 @@ public class Snapshot { public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - Snapshot that = Snapshot.class.cast(obj); + VolumeSnapshot that = VolumeSnapshot.class.cast(obj); return Objects.equal(this.id, that.id) && Objects.equal(this.volumeId, that.volumeId) && Objects.equal(this.status, that.status) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java index b8b1275542..fbc7465e0b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java @@ -30,12 +30,16 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.jclouds.openstack.filters.AuthenticateRequest; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; -import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; import org.jclouds.openstack.services.Extension; import org.jclouds.openstack.services.ServiceType; import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.Payload; import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.SelectJson; @@ -99,10 +103,12 @@ public interface VolumeAsyncClient { * @return the new Snapshot */ @POST - @Path("/os-volumes/") + @Path("/os-volumes") @SelectJson("volume") @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture createVolume(@PayloadParam("volume") Volume createVolume); + @Produces(MediaType.APPLICATION_JSON) + @MapBinder(CreateVolumeOptions.class) + ListenableFuture createVolume(@PayloadParam("size") int sizeGB, CreateVolumeOptions... options); /** * Delete a volume. @@ -111,7 +117,6 @@ public interface VolumeAsyncClient { */ @DELETE @Path("/os-volumes/{id}") - @SelectJson("volumes") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnFalseOnNotFoundOr404.class) ListenableFuture deleteVolume(@PathParam("id") String volumeId); @@ -126,7 +131,7 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachments") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listAttachments(@PathParam("server_id") String serverId); + ListenableFuture> listAttachments(@PathParam("server_id") String serverId); /** * Get a specific attached volume. @@ -138,7 +143,7 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachment") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); /** * Attach a volume to an instance @@ -146,11 +151,14 @@ public interface VolumeAsyncClient { * @return the new Attachment */ @POST - @Path("/servers/{server_id}/os-volume_attachments/") + @Path("/servers/{server_id}/os-volume_attachments") @SelectJson("volumeAttachment") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture attachVolume(@PathParam("server_id") String serverId, @PayloadParam("attachment") Attachment attachVolume); + @Payload("%7B\"volumeAttachment\":%7B\"volumeId\":\"{id}\",\"device\":\"{device}\"%7D%7D") + ListenableFuture attachVolume(@PathParam("server_id") String serverId, + @PayloadParam("id") String volumeId, + @PayloadParam("device") String device); /** * Detach a Volume from an instance. @@ -170,10 +178,10 @@ public interface VolumeAsyncClient { */ @GET @Path("/os-snapshots") - @SelectJson("volumes") + @SelectJson("snapshots") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listSnapshots(); + ListenableFuture> listSnapshots(); /** * Returns a summary list of snapshots. @@ -185,7 +193,7 @@ public interface VolumeAsyncClient { @SelectJson("snapshots") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listSnapshotsInDetail(); + ListenableFuture> listSnapshotsInDetail(); /** * Return data about the given snapshot. @@ -194,21 +202,23 @@ public interface VolumeAsyncClient { */ @GET @Path("/os-snapshots/{id}") - @SelectJson("snapshots") + @SelectJson("snapshot") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getSnapshot(@PathParam("id") String snapshotId); + ListenableFuture getSnapshot(@PathParam("id") String snapshotId); /** * Creates a new Snapshot * * @return the new Snapshot */ - @GET - @Path("/os-snapshots/detail") - @SelectJson("snapshots") + @POST + @Path("/os-snapshots") + @SelectJson("snapshot") + @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - ListenableFuture createSnapshot(@PayloadParam("snapshot") Snapshot createSnapshot); + @MapBinder(CreateVolumeSnapshotOptions.class) + ListenableFuture createSnapshot(@PayloadParam("volume_id") String volumeId, CreateVolumeSnapshotOptions... options); /** * Delete a snapshot. diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java index 2eea3ddbfa..fc26aad4cf 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java @@ -22,9 +22,11 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.jclouds.concurrent.Timeout; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; -import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; import org.jclouds.openstack.services.Extension; import org.jclouds.openstack.services.ServiceType; @@ -64,12 +66,12 @@ public interface VolumeClient { * * @return the new Snapshot */ - Volume createVolume(Volume createVolume); + Volume createVolume(int sizeGB, CreateVolumeOptions... options); /** * Delete a snapshot. * - * @return + * @return true if successful */ Boolean deleteVolume(String volumeId); @@ -78,26 +80,26 @@ public interface VolumeClient { * * @return all Floating IPs */ - Set listAttachments(String serverId); + Set listAttachments(String serverId); /** * Get a specific attached volume. * * @return data about the given volume attachment. */ - Attachment getAttachment(String serverId, String volumeId); + VolumeAttachment getAttachment(String serverId, String volumeId); /** * Attach a volume to an instance * - * @return a new Volume + * @return data about the new volume attachment */ - Attachment attachVolume(String serverId, Attachment attachVolume); + VolumeAttachment attachVolume(String serverId, String volumeId, String device); /** * Detach a Volume from an instance. * - * @return + * @return true if successful */ Boolean detachVolume(String server_id, String volumeId); @@ -106,33 +108,33 @@ public interface VolumeClient { * * @return the list of snapshots */ - Set listSnapshots(); + Set listSnapshots(); /** * Returns a summary list of snapshots. * * @return the list of snapshots */ - Set listSnapshotsInDetail(); + Set listSnapshotsInDetail(); /** * Return data about the given snapshot. * * @return details of a specific snapshot. */ - Snapshot getSnapshot(String snapshotId); + VolumeSnapshot getSnapshot(String snapshotId); /** * Creates a new Snapshot * * @return the new Snapshot */ - Snapshot createSnapshot(Snapshot createSnapshot); + VolumeSnapshot createSnapshot(String volumeId, CreateVolumeSnapshotOptions... options); /** * Delete a snapshot. * - * @return + * @return true if successful */ Boolean deleteSnapshot(String snapshotId); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java new file mode 100644 index 0000000000..e92f3566db --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java @@ -0,0 +1,224 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.openstack.nova.v1_1.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; +import static com.google.common.base.Preconditions.*; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.encryption.internal.Base64; +import org.jclouds.http.HttpRequest; +import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; +import org.jclouds.util.Preconditions2; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +/** + * @author Adam Lowe + */ +public class CreateVolumeOptions implements MapBinder { + public static final CreateVolumeOptions NONE = new CreateVolumeOptions(); + + @Inject + private BindToJsonPayload jsonBinder; + + private String name; + private String description; + private String volumeType; + private String availabilityZone; + private String snapshotId; + private Map metadata = ImmutableMap.of(); + + @Override + public R bindToRequest(R request, Map postParams) { + Map image = Maps.newHashMap(); + image.putAll(postParams); + if (name != null) + image.put("display_name", name); + if (description != null) + image.put("display_description", description); + if (!metadata.isEmpty()) + image.put("metadata", metadata); + return jsonBinder.bindToRequest(request, ImmutableMap.of("volume", image)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("CreateVolume is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateVolumeOptions)) return false; + final CreateVolumeOptions other = CreateVolumeOptions.class.cast(object); + return equal(volumeType, other.volumeType) && equal(availabilityZone, other.availabilityZone) && equal(snapshotId, other.snapshotId) + && equal(name, other.name) && equal(description, other.description) && equal(metadata, other.metadata); + } + + @Override + public int hashCode() { + return Objects.hashCode(volumeType, availabilityZone, snapshotId, name, description, metadata); + } + + protected ToStringHelper string() { + return toStringHelper("").add("volumeType", volumeType).add("availabilityZone", availabilityZone) + .add("snapshotId", snapshotId).add("name", name).add("description", description).add("metadata", metadata); + } + + @Override + public String toString() { + return string().toString(); + } + + /** + * Custom cloud server metadata can also be supplied at launch time. This + * metadata is stored in the API system where it is retrievable by querying + * the API for server status. The maximum size of the metadata key and value + * is each 255 bytes and the maximum number of key-value pairs that can be + * supplied per volume is 5. + */ + public CreateVolumeOptions metadata(Map metadata) { + checkNotNull(metadata, "metadata"); + checkArgument(metadata.size() <= 5, + "you cannot have more then 5 metadata values. You specified: " + metadata.size()); + for (Entry entry : metadata.entrySet()) { + checkArgument( + entry.getKey().getBytes().length < 255, + String.format("maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", + entry.getKey(), entry.getKey().getBytes().length)); + checkArgument(entry.getKey().getBytes().length < 255, String.format( + "maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", + entry.getKey(), entry.getValue(), entry.getValue().getBytes().length)); + } + this.metadata = ImmutableMap.copyOf(metadata); + return this; + } + + public CreateVolumeOptions name(String name) { + this.name = name; + return this; + } + + public CreateVolumeOptions description(String description) { + this.description = description; + return this; + } + + public CreateVolumeOptions volumeType(String volumeType) { + this.volumeType = volumeType; + return this; + } + + public CreateVolumeOptions availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; + } + + public CreateVolumeOptions snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return this; + } + + public String getVolumeType() { + return volumeType; + } + + public String getAvailabilityZone() { + return availabilityZone; + } + + public String getSnapshotId() { + return snapshotId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Map getMetadata() { + return metadata; + } + + public static class Builder { + /** + * @see CreateVolumeOptions#getName() + */ + public static CreateVolumeOptions name(String name) { + return new CreateVolumeOptions().name(name); + } + /** + * @see CreateVolumeOptions#getDescription() + */ + public static CreateVolumeOptions description(String description) { + return new CreateVolumeOptions().description(description); + } + + /** + * @see CreateVolumeOptions#getVolumeType() + */ + public static CreateVolumeOptions volumeType(String volumeType) { + return new CreateVolumeOptions().volumeType(volumeType); + } + + /** + * @see CreateVolumeOptions#getAvailabilityZone() + */ + public static CreateVolumeOptions availabilityZone(String availabilityZone) { + return new CreateVolumeOptions().availabilityZone(availabilityZone); + } + + /** + * @see CreateVolumeOptions#getSnapshotId() + */ + public static CreateVolumeOptions snapshotId(String snapshotId) { + return new CreateVolumeOptions().snapshotId(snapshotId); + } + + /** + * @see CreateVolumeOptions#getMetadata() + */ + public static CreateVolumeOptions metadata(Map metadata) { + return new CreateVolumeOptions().metadata(metadata); + } + } + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java new file mode 100644 index 0000000000..df9b628970 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java @@ -0,0 +1,140 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.openstack.nova.v1_1.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; + +import java.util.Map; + +import javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * @author Adam Lowe + */ +public class CreateVolumeSnapshotOptions implements MapBinder { + public static final CreateVolumeSnapshotOptions NONE = new CreateVolumeSnapshotOptions(); + + @Inject + private BindToJsonPayload jsonBinder; + + private String name; + private String description; + private boolean force = false; + + @Override + public R bindToRequest(R request, Map postParams) { + Map data = Maps.newHashMap(postParams); + if (name != null) + data.put("display_name", name); + if (description != null) + data.put("display_description", description); + if (force) + data.put("force", "true"); + return jsonBinder.bindToRequest(request, ImmutableMap.of("snapshot", data)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("CreateSnapshot is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateVolumeSnapshotOptions)) return false; + final CreateVolumeSnapshotOptions other = CreateVolumeSnapshotOptions.class.cast(object); + return equal(name, other.name) && equal(description, other.description); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, description); + } + + protected ToStringHelper string() { + return toStringHelper("").add("name", name).add("description", description); + } + + @Override + public String toString() { + return string().toString(); + } + + public CreateVolumeSnapshotOptions name(String name) { + this.name = name; + return this; + } + + public CreateVolumeSnapshotOptions description(String description) { + this.description = description; + return this; + } + + public CreateVolumeSnapshotOptions force() { + this.force = true; + return this; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public boolean isForce() { + return force; + } + + public static class Builder { + /** + * @see CreateVolumeSnapshotOptions#getName() + */ + public static CreateVolumeSnapshotOptions name(String name) { + return new CreateVolumeSnapshotOptions().name(name); + } + /** + * @see CreateVolumeSnapshotOptions#getDescription() + */ + public static CreateVolumeSnapshotOptions description(String description) { + return new CreateVolumeSnapshotOptions().description(description); + } + + /** + * @see CreateVolumeSnapshotOptions#isForce() + */ + public static CreateVolumeSnapshotOptions force() { + return new CreateVolumeSnapshotOptions().force(); + } + } + +} diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java index a254df0f85..522aaa3215 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -23,22 +23,27 @@ import static org.testng.Assert.*; import java.util.Set; import org.jclouds.openstack.domain.Resource; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; -import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.SimpleTenantUsage; -import org.jclouds.openstack.nova.v1_1.domain.Snapshot; +import org.jclouds.openstack.nova.v1_1.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v1_1.domain.VolumeSnapshot; import org.jclouds.openstack.nova.v1_1.domain.Volume; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; +import org.jclouds.predicates.RetryablePredicate; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; +import com.google.common.base.Objects; import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; /** - * Tests behavior of SimpleTenantUsageClient + * Tests behavior of VolumeClient * + * Cannot create snapshots whilst a volume is attached, hence single threaded + * * @author Adam Lowe */ @Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true) @@ -47,8 +52,13 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { private VolumeClient client; private String zone; - @BeforeMethod(alwaysRun = true) - public void setUpClient() { + private Volume testVolume; + private VolumeSnapshot testSnapshot; + + @BeforeGroups(groups = { "integration", "live" }) + @Override + public void setupContext() { + super.setupContext(); zone = Iterables.getFirst(novaContext.getApi().getConfiguredZones(), "nova"); Optional optClient = novaContext.getApi().getVolumeExtensionForZone(zone); if (optClient.isPresent()) { @@ -56,72 +66,204 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { } } + @AfterGroups(groups = "live", alwaysRun = true) + @Override + protected void tearDown() { + if (client != null) { + if (testSnapshot != null) { + final String snapshotId = testSnapshot.getId(); + assertTrue(client.deleteSnapshot(snapshotId)); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.getSnapshot(snapshotId) == null; + } + }, 30 * 1000L).apply(client)); + } + if (testVolume != null) { + final String volumeId = testVolume.getId(); + assertTrue(client.deleteVolume(volumeId)); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.getVolume(volumeId) == null; + } + }, 180 * 1000L).apply(client)); + } + } + super.tearDown(); + } + + public void testCreateVolume() throws Exception { + testVolume = client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume").availabilityZone(zone)); + for (int i=0; i<100 && testVolume.getStatus() == Volume.Status.CREATING; i++) { + Thread.sleep(100); + testVolume = client.getVolume(testVolume.getId()); + } + assertEquals(testVolume.getStatus(), Volume.Status.AVAILABLE); + } + + @Test(dependsOnMethods = "testCreateVolume") public void testListVolumes() throws Exception { if (client != null) { Set volumes = client.listVolumes(); assertNotNull(volumes); + boolean foundIt = false; for (Volume vol : volumes) { Volume details = client.getVolume(vol.getId()); assertNotNull(details); + if (Objects.equal(details.getId(), testVolume.getId())) { + foundIt = true; + } } + assertTrue(foundIt, "Failed to find the volume we created in listVolumes() response"); } } + @Test(dependsOnMethods = "testCreateVolume") public void testListVolumesInDetail() throws Exception { if (client != null) { Set volumes = client.listVolumesInDetail(); assertNotNull(volumes); + assertTrue(volumes.contains(testVolume)); + boolean foundIt = false; for (Volume vol : volumes) { Volume details = client.getVolume(vol.getId()); assertNotNull(details); - assertEquals(details, vol); assertNotNull(details.getId()); - assertNotNull(details.getStatus()); - assertTrue(details.getSize() > -1); assertNotNull(details.getCreated()); + assertTrue(details.getSize() > -1); + + assertEquals(details.getId(), vol.getId()); + assertEquals(details.getSize(), vol.getSize()); + assertEquals(details.getName(), vol.getName()); + assertEquals(details.getDescription(), vol.getDescription()); + assertEquals(details.getCreated(), vol.getCreated()); + if (Objects.equal(details.getId(), testVolume.getId())) { + foundIt = true; + } } + assertTrue(foundIt, "Failed to find the volume we previously created in listVolumesInDetail() response"); } } + @Test(dependsOnMethods = "testCreateVolume") + public void testCreateSnapshot() throws Exception { + if (client != null) { + testSnapshot = client.createSnapshot(testVolume.getId(), CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + assertNotNull(testSnapshot); + assertNotNull(testSnapshot.getId()); + final String snapshotId = testSnapshot.getId(); + assertNotNull(testSnapshot.getStatus()); + assertTrue(testSnapshot.getSize() > -1); + assertNotNull(testSnapshot.getCreated()); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.getSnapshot(snapshotId).getStatus() == Volume.Status.AVAILABLE; + } + }, 30 * 1000L).apply(client)); + } + } + + @Test(dependsOnMethods = "testCreateSnapshot") public void testListSnapshots() throws Exception { if (client != null) { - Set snapshots = client.listSnapshots(); + Set snapshots = client.listSnapshots(); assertNotNull(snapshots); - for (Snapshot snap : snapshots) { - Snapshot details = client.getSnapshot(snap.getId()); + boolean foundIt = false; + for (VolumeSnapshot snap : snapshots) { + VolumeSnapshot details = client.getSnapshot(snap.getId()); + if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { + foundIt = true; + } assertNotNull(details); - assertNotNull(details.getId()); + assertEquals(details.getId(), snap.getId()); + assertEquals(details.getVolumeId(), snap.getVolumeId()); } + assertTrue(foundIt, "Failed to find the snapshot we previously created in listSnapshots() response"); } } + @Test(dependsOnMethods = "testCreateSnapshot") public void testListSnapshotsInDetail() throws Exception { if (client != null) { - Set snapshots = client.listSnapshotsInDetail(); + Set snapshots = client.listSnapshotsInDetail(); assertNotNull(snapshots); - for (Snapshot snap : snapshots) { - Snapshot details = client.getSnapshot(snap.getId()); - assertNotNull(details); - assertEquals(details, snap); + boolean foundIt = false; + for (VolumeSnapshot snap : snapshots) { + VolumeSnapshot details = client.getSnapshot(snap.getId()); + if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { + foundIt = true; + assertSame(details, testSnapshot); + } + assertSame(details, snap); } + + assertTrue(foundIt, "Failed to find the snapshot we created in listSnapshotsInDetail() response"); } } + + private void assertSame(VolumeSnapshot a, VolumeSnapshot b) { + assertNotNull(a); + assertNotNull(b); + assertEquals(a.getId(), b.getId()); + assertEquals(a.getDescription(), b.getDescription()); + assertEquals(a.getName(), b.getName()); + assertEquals(a.getVolumeId(), b.getVolumeId()); + } - public void testListAttachments() throws Exception { + @Test(enabled=false, dependsOnMethods = "testCreateVolume") // disabled as it alters an existing server + public void testAttachments() throws Exception { if (client != null) { Set servers = novaContext.getApi().getServerClientForZone(zone).listServers(); if (!servers.isEmpty()) { - String serverId = Iterables.getFirst(servers, null).getId(); - Set attachments = client.listAttachments(serverId); + final String serverId = Iterables.getFirst(servers, null).getId(); + Set attachments = client.listAttachments(serverId); assertNotNull(attachments); - assertTrue(attachments.size() > 0); - for (Attachment att : attachments) { - Attachment details = client.getAttachment(serverId, att.getId()); + final int before = attachments.size(); + + VolumeAttachment testAttachment = client.attachVolume(serverId, testVolume.getId(), "/dev/vdf"); + assertNotNull(testAttachment.getId()); + assertEquals(testAttachment.getVolumeId(), testVolume.getId()); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.listAttachments(serverId).size() == before+1; + } + }, 60 * 1000L).apply(client)); + + attachments = client.listAttachments(serverId); + assertNotNull(attachments); + assertEquals(attachments.size(), before+1); + + assertEquals(client.getVolume(testVolume.getId()).getStatus(), Volume.Status.IN_USE); + + boolean foundIt = false; + for (VolumeAttachment att : attachments) { + VolumeAttachment details = client.getAttachment(serverId, att.getId()); assertNotNull(details); assertNotNull(details.getId()); assertNotNull(details.getServerId()); assertNotNull(details.getVolumeId()); + if (Objects.equal(details.getVolumeId(), testVolume.getId())) { + foundIt = true; + assertEquals(details.getDevice(), "/dev/vdf"); + assertEquals(details.getServerId(), serverId); + } } + + assertTrue(foundIt, "Failed to find the attachment we created in listAttachments() response"); + + client.detachVolume(serverId, testVolume.getId()); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeClient volumeClient) { + return client.listAttachments(serverId).size() == before; + } + }, 60 * 1000L).apply(client)); } } }