From 01de74236b064191cd1a05e2d2e5087ef86a4b85 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Mon, 23 Apr 2012 15:32:17 +0100 Subject: [PATCH 1/7] Adding Volumes extension - first stage includes get/list volumes and list attachments --- .../openstack/nova/v1_1/NovaAsyncClient.java | 7 + .../openstack/nova/v1_1/NovaClient.java | 9 + .../v1_1/config/NovaRestClientModule.java | 1 + .../nova/v1_1/domain/Attachment.java | 160 +++++++++ .../openstack/nova/v1_1/domain/Snapshot.java | 220 ++++++++++++ .../openstack/nova/v1_1/domain/Volume.java | 316 ++++++++++++++++++ .../v1_1/extensions/VolumeAsyncClient.java | 224 +++++++++++++ .../nova/v1_1/extensions/VolumeClient.java | 139 ++++++++ ...paceEqualsAnyNamespaceInExtensionsSet.java | 2 + .../extensions/VolumeClientExpectTest.java | 129 +++++++ .../v1_1/extensions/VolumeClientLiveTest.java | 128 +++++++ .../src/test/resources/attachment_list.json | 1 + .../src/test/resources/volume_details.json | 1 + .../src/test/resources/volume_list.json | 1 + .../test/resources/volume_list_detail.json | 1 + 15 files changed, 1339 insertions(+) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Attachment.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java create mode 100644 apis/openstack-nova/src/test/resources/attachment_list.json create mode 100644 apis/openstack-nova/src/test/resources/volume_details.json create mode 100644 apis/openstack-nova/src/test/resources/volume_list.json create mode 100644 apis/openstack-nova/src/test/resources/volume_list_detail.json diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index da9d6507b5..9f5d32b035 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -28,6 +28,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageAsyncClient; +import org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient; @@ -120,4 +121,10 @@ public interface NovaAsyncClient { Optional getSimpleTenantUsageExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** + * Provides asynchronous access to Volume features. + */ + @Delegate + Optional getVolumeExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index d33e0ab91b..37dc81e228 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -30,6 +30,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageClient; +import org.jclouds.openstack.nova.v1_1.extensions.VolumeClient; import org.jclouds.openstack.nova.v1_1.features.ExtensionClient; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient; @@ -122,4 +123,12 @@ public interface NovaClient { Optional getSimpleTenantUsageExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to Volume features. + */ + @Delegate + Optional getVolumeExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java index fdb3b039a8..8a9a6e20bc 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java @@ -73,6 +73,7 @@ public class NovaRestClientModule extends RestClientModule builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromAttachment(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private String volumeId; + private String serverId; + private String device; + + public T id(String id) { + this.id = id; + return self(); + } + + public T volumeId(String volumeId) { + this.volumeId = volumeId; + return self(); + } + + public T serverId(String serverId) { + this.serverId = serverId; + return self(); + } + + public T device(String device) { + this.device = device; + return self(); + } + + public Attachment build() { + return new Attachment(this); + } + + public T fromAttachment(Attachment in) { + return this + .id(in.getId()) + .volumeId(in.getVolumeId()) + .serverId(in.getServerId()) + .device(in.getDevice()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final String volumeId; + private final String serverId; + private final String device; + + protected Attachment(Builder builder) { + this.id = builder.id; + this.volumeId = builder.volumeId; + this.serverId = builder.serverId; + this.device = builder.device; + } + + /** + */ + @Nullable + public String getId() { + return this.id; + } + + /** + */ + @Nullable + public String getVolumeId() { + return this.volumeId; + } + + /** + */ + @Nullable + public String getServerId() { + return this.serverId; + } + + /** + */ + @Nullable + public String getDevice() { + return this.device; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, volumeId, serverId, device); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Attachment that = Attachment.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.volumeId, that.volumeId) + && Objects.equal(this.serverId, that.serverId) + && Objects.equal(this.device, that.device) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("volumeId", volumeId) + .add("serverId", serverId) + .add("device", device) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file 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/Snapshot.java new file mode 100644 index 0000000000..d2db66289c --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Snapshot.java @@ -0,0 +1,220 @@ +/** + * 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.domain; + +import java.util.Date; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.gson.annotations.SerializedName; + +/** + * An Openstack Nova Volume Snapshot + */ +public class Snapshot { + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromSnapshot(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private String volumeId; + private String status; + private int size; + private Date created; + private String name; + private String description; + + public T id(String id) { + this.id = id; + return self(); + } + + public T volumeId(String volumeId) { + this.volumeId = volumeId; + return self(); + } + + public T status(String status) { + this.status = status; + return self(); + } + + public T size(int size) { + this.size = size; + return self(); + } + + public T created(Date created) { + this.created = created; + return self(); + } + + public T name(String name) { + this.name = name; + return self(); + } + + public T description(String description) { + this.description = description; + return self(); + } + + public Snapshot build() { + return new Snapshot(this); + } + + public T fromSnapshot(Snapshot in) { + return this + .id(in.getId()) + .volumeId(in.getVolumeId()) + .status(in.getStatus()) + .size(in.getSize()) + .created(in.getCreated()) + .name(in.getName()) + .description(in.getDescription()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final String volumeId; + private final String status; + private final int size; + @SerializedName(value="createdAt") + private final Date created; + @SerializedName(value="displayName") + private final String name; + @SerializedName(value="displayDescription") + private final String description; + + protected Snapshot(Builder builder) { + this.id = builder.id; + this.volumeId = builder.volumeId; + this.status = builder.status; + this.size = builder.size; + this.created = builder.created; + this.name = builder.name; + this.description = builder.description; + } + + /** + */ + @Nullable + public String getId() { + return this.id; + } + + /** + */ + @Nullable + public String getVolumeId() { + return this.volumeId; + } + + /** + */ + @Nullable + public String getStatus() { + return this.status; + } + + /** + */ + @Nullable + public int getSize() { + return this.size; + } + + /** + */ + @Nullable + public Date getCreated() { + return this.created; + } + + /** + */ + @Nullable + public String getName() { + return this.name; + } + + /** + */ + @Nullable + public String getDescription() { + return this.description; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, volumeId, status, size, created, name, description); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Snapshot that = Snapshot.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.volumeId, that.volumeId) + && Objects.equal(this.status, that.status) + && Objects.equal(this.size, that.size) + && Objects.equal(this.created, that.created) + && Objects.equal(this.name, that.name) + && Objects.equal(this.description, that.description) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("volumeId", volumeId) + .add("status", status) + .add("size", size) + .add("created", created) + .add("name", name) + .add("description", description) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7b179a1b7a --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Volume.java @@ -0,0 +1,316 @@ +/** + * 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.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.CaseFormat; +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.Maps; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +/** + * An Openstack Nova Volume + */ +public class Volume { + + public static enum Status { + CREATING, AVAILABLE, IN_USE, DELETING, ERROR, UNRECOGNIZED; + public String value() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name()); + } + + @Override + public String toString() { + return value(); + } + + public static Status fromValue(String status) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(status, "status"))); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromVolume(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private Status status; + private int size; + private String zone; + private Date created; + private Set attachments = Sets.newLinkedHashSet(); + private String volumeType; + private String snapshotId; + private String name; + private String description; + private Map metadata = Maps.newHashMap(); + + public T id(String id) { + this.id = id; + return self(); + } + + public T status(Status status) { + this.status = status; + return self(); + } + + public T size(int size) { + this.size = size; + return self(); + } + + public T zone(String zone) { + this.zone = zone; + return self(); + } + + public T created(Date created) { + this.created = created; + return self(); + } + + public T attachments(Set attachments) { + this.attachments = attachments; + return self(); + } + + public T volumeType(String volumeType) { + this.volumeType = volumeType; + return self(); + } + + public T snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return self(); + } + + public T metadata(Map metadata) { + this.metadata = metadata; + return self(); + } + + public T name(String name) { + this.name = name; + return self(); + } + + public T description(String description) { + this.description = description; + return self(); + } + + public Volume build() { + return new Volume(this); + } + + public T fromVolume(Volume in) { + return this + .id(in.getId()) + .status(in.getStatus()) + .size(in.getSize()) + .zone(in.getZone()) + .created(in.getCreated()) + .attachments(in.getAttachments()) + .volumeType(in.getVolumeType()) + .snapshotId(in.getSnapshotId()) + .metadata(in.getMetadata()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final Status status; + private final int size; + @SerializedName(value="availabilityZone") + private final String zone; + @SerializedName(value="createdAt") + private final Date created; + private final Set attachments; + private final String volumeType; + private final String snapshotId; + @SerializedName(value="displayName") + private final String name; + @SerializedName(value="displayDescription") + private final String description; + private final Map metadata; + + protected Volume(Builder builder) { + this.id = builder.id; + this.status = builder.status; + this.size = builder.size; + this.zone = builder.zone; + this.created = builder.created; + this.attachments = ImmutableSet.copyOf(checkNotNull(builder.attachments, "attachments")); + this.volumeType = builder.volumeType; + this.snapshotId = builder.snapshotId; + this.name = builder.name; + this.description = builder.description; + this.metadata = ImmutableMap.copyOf(builder.metadata); + } + + /** + */ + public String getId() { + return this.id; + } + + /** + */ + public Status getStatus() { + return this.status; + } + + /** + */ + public int getSize() { + return this.size; + } + + /** + */ + public String getZone() { + return this.zone; + } + + /** + */ + public Date getCreated() { + return this.created; + } + + /** + */ + @Nullable + public Set getAttachments() { + return Collections.unmodifiableSet(this.attachments); + } + + /** + */ + @Nullable + public String getVolumeType() { + return this.volumeType; + } + + /** + */ + @Nullable + public String getSnapshotId() { + return this.snapshotId; + } + + /** + */ + @Nullable + public String getName() { + return this.name; + } + + /** + */ + @Nullable + public String getDescription() { + return this.description; + } + + /** + */ + @Nullable + public Map getMetadata() { + return Collections.unmodifiableMap(this.metadata); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, status, size, zone, created, attachments, volumeType, snapshotId, name, description, metadata); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Volume that = Volume.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.status, that.status) + && Objects.equal(this.size, that.size) + && Objects.equal(this.zone, that.zone) + && Objects.equal(this.created, that.created) + && Objects.equal(this.attachments, that.attachments) + && Objects.equal(this.volumeType, that.volumeType) + && Objects.equal(this.snapshotId, that.snapshotId) + && Objects.equal(this.name, that.name) + && Objects.equal(this.description, that.description) + && Objects.equal(this.metadata, that.metadata) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("status", status) + .add("size", size) + .add("zone", zone) + .add("created", created) + .add("attachments", attachments) + .add("volumeType", volumeType) + .add("snapshotId", snapshotId) + .add("name", name) + .add("description", description) + .add("metadata", metadata) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..b8b1275542 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeAsyncClient.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.extensions; + +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +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.Volume; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides synchronous access to Volumes. + *

+ * + * @see org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VOLUMES) +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface VolumeAsyncClient { + /** + * Returns a summary list of volumes. + * + * @return the list of volumes + */ + @GET + @Path("/os-volumes") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listVolumes(); + + /** + * Returns a detailed list of volumes. + * + * @return the list of volumes. + */ + @GET + @Path("/os-volumes/detail") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listVolumesInDetail(); + + /** + * Return data about the given volume. + * + * @return details of a specific volume. + */ + @GET + @Path("/os-volumes/{id}") + @SelectJson("volume") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getVolume(@PathParam("id") String volumeId); + + /** + * Creates a new volume + * + * @return the new Snapshot + */ + @POST + @Path("/os-volumes/") + @SelectJson("volume") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture createVolume(@PayloadParam("volume") Volume createVolume); + + /** + * Delete a volume. + * + * @return true if successful + */ + @DELETE + @Path("/os-volumes/{id}") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture deleteVolume(@PathParam("id") String volumeId); + + /** + * List volume attachments for a given instance. + * + * @return all Floating IPs + */ + @GET + @Path("/servers/{server_id}/os-volume_attachments") + @SelectJson("volumeAttachments") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listAttachments(@PathParam("server_id") String serverId); + + /** + * Get a specific attached volume. + * + * @return data about the given volume attachment. + */ + @GET + @Path("/servers/{server_id}/os-volume_attachments/{id}") + @SelectJson("volumeAttachment") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + + /** + * Attach a volume to an instance + * + * @return the new Attachment + */ + @POST + @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); + + /** + * Detach a Volume from an instance. + * + * @return true if successful + */ + @DELETE + @Path("/servers/{server_id}/os-volume_attachments/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture detachVolume(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + @GET + @Path("/os-snapshots") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listSnapshots(); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + @GET + @Path("/os-snapshots/detail") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listSnapshotsInDetail(); + + /** + * Return data about the given snapshot. + * + * @return details of a specific snapshot. + */ + @GET + @Path("/os-snapshots/{id}") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getSnapshot(@PathParam("id") String snapshotId); + + /** + * Creates a new Snapshot + * + * @return the new Snapshot + */ + @GET + @Path("/os-snapshots/detail") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture createSnapshot(@PayloadParam("snapshot") Snapshot createSnapshot); + + /** + * Delete a snapshot. + * + * @return true if successful + */ + @DELETE + @Path("/os-snapshots/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture deleteSnapshot(@PathParam("id") String snapshotId); + +} 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 new file mode 100644 index 0000000000..2eea3ddbfa --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClient.java @@ -0,0 +1,139 @@ +/** + * 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.extensions; + +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.Volume; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; + +/** + * Provides synchronous access to Volumes. + *

+ * + * @see org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VOLUMES) +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface VolumeClient { + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + Set listVolumes(); + + /** + * Returns a detailed list of volumes. + * + * @return the list of volumes. + */ + Set listVolumesInDetail(); + + /** + * Return data about the given volume. + * + * @return details of a specific snapshot. + */ + Volume getVolume(String volumeId); + + /** + * Creates a new Snapshot + * + * @return the new Snapshot + */ + Volume createVolume(Volume createVolume); + + /** + * Delete a snapshot. + * + * @return + */ + Boolean deleteVolume(String volumeId); + + /** + * List volume attachments for a given instance. + * + * @return all Floating IPs + */ + Set listAttachments(String serverId); + + /** + * Get a specific attached volume. + * + * @return data about the given volume attachment. + */ + Attachment getAttachment(String serverId, String volumeId); + + /** + * Attach a volume to an instance + * + * @return a new Volume + */ + Attachment attachVolume(String serverId, Attachment attachVolume); + + /** + * Detach a Volume from an instance. + * + * @return + */ + Boolean detachVolume(String server_id, String volumeId); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + Set listSnapshots(); + + /** + * Returns a summary list of snapshots. + * + * @return the list of snapshots + */ + Set listSnapshotsInDetail(); + + /** + * Return data about the given snapshot. + * + * @return details of a specific snapshot. + */ + Snapshot getSnapshot(String snapshotId); + + /** + * Creates a new Snapshot + * + * @return the new Snapshot + */ + Snapshot createSnapshot(Snapshot createSnapshot); + + /** + * Delete a snapshot. + * + * @return + */ + Boolean deleteSnapshot(String snapshotId); + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java index 5db7bb9d46..85406b5e4c 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java @@ -66,6 +66,8 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio URI.create("http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1")) .put(URI.create(ExtensionNamespaces.HOSTS), URI.create("http://docs.openstack.org/compute/ext/hosts/api/v1.1")) + .put(URI.create(ExtensionNamespaces.VOLUMES), + URI.create("http://docs.openstack.org/compute/ext/volumes/api/v1.1")) .build(); @Inject diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java new file mode 100644 index 0000000000..1f1633deab --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java @@ -0,0 +1,129 @@ +/** + * 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.extensions; + +import static org.testng.Assert.assertEquals; + +import java.net.URI; +import java.util.Set; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.nova.v1_1.domain.Attachment; +import org.jclouds.openstack.nova.v1_1.domain.Volume; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Tests VolumeClient guice wiring and parsing + * + * @author Adam Lowe + */ +@Test(groups = "unit", testName = "VolumeClientExpectTest") +public class VolumeClientExpectTest extends BaseNovaClientExpectTest { + private DateService dateService = new SimpleDateFormatDateService(); + + public void testListVolumes() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumes(); + assertEquals(volumes, ImmutableSet.of(testVolume())); + } + + + public void testListVolumesInDetail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list_detail.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumesInDetail(); + assertEquals(volumes, ImmutableSet.of(testVolume())); + } + + public void testGetVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Volume volume = client.getVolume("1"); + assertEquals(volume, testVolume()); + // double-check equals() + assertEquals(volume.getStatus(), Volume.Status.IN_USE); + assertEquals(volume.getDescription(), "This is a test volume"); + assertEquals(volume.getZone(), "nova"); + assertEquals(volume.getName(), "test"); + assertEquals(volume.getStatus(), Volume.Status.IN_USE); + assertEquals(Iterables.getOnlyElement(volume.getAttachments()), testAttachment()); + } + + public void testListAttachments() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_list.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set attachments = client.listAttachments("instance-1"); + assertEquals(attachments, ImmutableSet.of(testAttachment())); + // double-check equals() + Attachment attachment = Iterables.getOnlyElement(attachments); + assertEquals(attachment.getDevice(), "/dev/vdc"); + assertEquals(attachment.getServerId(), "b4785058-cb80-491b-baa3-e4ee6546450e"); + assertEquals(attachment.getId(), "1"); + assertEquals(attachment.getVolumeId(), "1"); + } + + protected Volume testVolume() { + return Volume.builder().status(Volume.Status.IN_USE).description("This is a test volume").zone("nova").name("test") + .attachments(ImmutableSet.of(testAttachment())).size(1).id("1").created(dateService.iso8601SecondsDateParse("2012-04-23 12:16:45")).build(); + } + + protected Attachment testAttachment() { + return Attachment.builder().device("/dev/vdc").serverId("b4785058-cb80-491b-baa3-e4ee6546450e").id("1").volumeId("1").build(); + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..a254df0f85 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientLiveTest.java @@ -0,0 +1,128 @@ +/** + * 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.extensions; + +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.Volume; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; + +/** + * Tests behavior of SimpleTenantUsageClient + * + * @author Adam Lowe + */ +@Test(groups = "live", testName = "VolumeClientLiveTest", singleThreaded = true) +public class VolumeClientLiveTest extends BaseNovaClientLiveTest { + + private VolumeClient client; + private String zone; + + @BeforeMethod(alwaysRun = true) + public void setUpClient() { + zone = Iterables.getFirst(novaContext.getApi().getConfiguredZones(), "nova"); + Optional optClient = novaContext.getApi().getVolumeExtensionForZone(zone); + if (optClient.isPresent()) { + client = optClient.get(); + } + } + + public void testListVolumes() throws Exception { + if (client != null) { + Set volumes = client.listVolumes(); + assertNotNull(volumes); + for (Volume vol : volumes) { + Volume details = client.getVolume(vol.getId()); + assertNotNull(details); + } + } + } + + public void testListVolumesInDetail() throws Exception { + if (client != null) { + Set volumes = client.listVolumesInDetail(); + assertNotNull(volumes); + 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()); + } + } + } + + public void testListSnapshots() throws Exception { + if (client != null) { + Set snapshots = client.listSnapshots(); + assertNotNull(snapshots); + for (Snapshot snap : snapshots) { + Snapshot details = client.getSnapshot(snap.getId()); + assertNotNull(details); + assertNotNull(details.getId()); + } + } + } + + public void testListSnapshotsInDetail() throws Exception { + if (client != null) { + Set snapshots = client.listSnapshotsInDetail(); + assertNotNull(snapshots); + for (Snapshot snap : snapshots) { + Snapshot details = client.getSnapshot(snap.getId()); + assertNotNull(details); + assertEquals(details, snap); + } + } + } + + public void testListAttachments() 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); + assertNotNull(attachments); + assertTrue(attachments.size() > 0); + for (Attachment att : attachments) { + Attachment details = client.getAttachment(serverId, att.getId()); + assertNotNull(details); + assertNotNull(details.getId()); + assertNotNull(details.getServerId()); + assertNotNull(details.getVolumeId()); + } + } + } + } +} diff --git a/apis/openstack-nova/src/test/resources/attachment_list.json b/apis/openstack-nova/src/test/resources/attachment_list.json new file mode 100644 index 0000000000..b40de0bd46 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/attachment_list.json @@ -0,0 +1 @@ +{"volumeAttachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}]} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/volume_details.json b/apis/openstack-nova/src/test/resources/volume_details.json new file mode 100644 index 0000000000..8dd11dc4e6 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/volume_details.json @@ -0,0 +1 @@ +{"volume": {"status": "in-use", "displayDescription": "This is a test volume", "availabilityZone": "nova", "displayName": "test", "attachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}], "volumeType": null, "snapshotId": null, "size": 1, "id": 1, "createdAt": "2012-04-23 12:16:45", "metadata": {}}} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/volume_list.json b/apis/openstack-nova/src/test/resources/volume_list.json new file mode 100644 index 0000000000..d2179e799d --- /dev/null +++ b/apis/openstack-nova/src/test/resources/volume_list.json @@ -0,0 +1 @@ +{"volumes": [{"status": "in-use", "displayDescription": "This is a test volume", "availabilityZone": "nova", "displayName": "test", "attachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}], "volumeType": null, "snapshotId": null, "size": 1, "id": 1, "createdAt": "2012-04-23 12:16:45", "metadata": {}}]} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/volume_list_detail.json b/apis/openstack-nova/src/test/resources/volume_list_detail.json new file mode 100644 index 0000000000..d2179e799d --- /dev/null +++ b/apis/openstack-nova/src/test/resources/volume_list_detail.json @@ -0,0 +1 @@ +{"volumes": [{"status": "in-use", "displayDescription": "This is a test volume", "availabilityZone": "nova", "displayName": "test", "attachments": [{"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}], "volumeType": null, "snapshotId": null, "size": 1, "id": 1, "createdAt": "2012-04-23 12:16:45", "metadata": {}}]} \ No newline at end of file From 58d4de4bbe4dc4b5fc940a473683345365ce4c53 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 14:36:16 +0100 Subject: [PATCH 2/7] Adding CreateVolume and CreateSnapshot options and improving live tests accordingly --- .../openstack/nova/v1_1/domain/Volume.java | 8 +- ...{Attachment.java => VolumeAttachment.java} | 12 +- .../{Snapshot.java => VolumeSnapshot.java} | 20 +- .../v1_1/extensions/VolumeAsyncClient.java | 46 ++-- .../nova/v1_1/extensions/VolumeClient.java | 30 +-- .../v1_1/options/CreateVolumeOptions.java | 224 ++++++++++++++++++ .../options/CreateVolumeSnapshotOptions.java | 140 +++++++++++ .../v1_1/extensions/VolumeClientLiveTest.java | 196 ++++++++++++--- 8 files changed, 597 insertions(+), 79 deletions(-) rename apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/{Attachment.java => VolumeAttachment.java} (92%) rename apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/{Snapshot.java => VolumeSnapshot.java} (92%) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeOptions.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateVolumeSnapshotOptions.java 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)); } } } From 0e9153017c45f834d7588ce59fc0d7c13dd7c4d6 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:17:07 +0100 Subject: [PATCH 3/7] Adding remaining VolumeClientExpectTest methods --- .../extensions/VolumeClientExpectTest.java | 373 ++++++++++++++++-- .../v1_1/internal/BaseNovaExpectTest.java | 12 + .../test/resources/attachment_details.json | 1 + .../src/test/resources/snapshot_details.json | 1 + .../src/test/resources/snapshot_list.json | 1 + .../test/resources/snapshot_list_detail.json | 1 + 6 files changed, 367 insertions(+), 22 deletions(-) create mode 100644 apis/openstack-nova/src/test/resources/attachment_details.json create mode 100644 apis/openstack-nova/src/test/resources/snapshot_details.json create mode 100644 apis/openstack-nova/src/test/resources/snapshot_list.json create mode 100644 apis/openstack-nova/src/test/resources/snapshot_list_detail.json diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java index 1f1633deab..4f70e81496 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java @@ -18,7 +18,7 @@ */ package org.jclouds.openstack.nova.v1_1.extensions; -import static org.testng.Assert.assertEquals; +import static org.testng.Assert.*; import java.net.URI; import java.util.Set; @@ -27,14 +27,16 @@ import javax.ws.rs.core.MediaType; import org.jclouds.date.DateService; import org.jclouds.date.internal.SimpleDateFormatDateService; -import org.jclouds.http.HttpRequest; -import org.jclouds.http.HttpResponse; -import org.jclouds.openstack.nova.v1_1.domain.Attachment; import org.jclouds.openstack.nova.v1_1.domain.Volume; +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.internal.BaseNovaClientExpectTest; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeOptions; +import org.jclouds.openstack.nova.v1_1.options.CreateVolumeSnapshotOptions; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -52,38 +54,93 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_list.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); Set volumes = client.listVolumes(); assertEquals(volumes, ImmutableSet.of(testVolume())); } + public void testListVolumesFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumes(); + assertTrue(volumes.isEmpty()); + } public void testListVolumesInDetail() { URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/detail"); VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list_detail.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_list_detail.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); Set volumes = client.listVolumesInDetail(); assertEquals(volumes, ImmutableSet.of(testVolume())); } + public void testListVolumesInDetailFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set volumes = client.listVolumesInDetail(); + assertTrue(volumes.isEmpty()); + } + + public void testCreateVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"volume\":{\"display_name\":\"jclouds-test-volume\",\"display_description\":\"description of test volume\",\"size\":\"1\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Volume volume = client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume")); + assertEquals(volume, testVolume()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testCreateVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .endpoint(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"volume\":{\"display_name\":\"jclouds-test-volume\",\"display_description\":\"description of test volume\",\"size\":\"1\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(404).payload(payloadFromResource("/volume_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.createVolume(1, CreateVolumeOptions.Builder.name("jclouds-test-volume").description("description of test volume")); + } + public void testGetVolume() { URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_details.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/volume_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); Volume volume = client.getVolume("1"); @@ -97,33 +154,305 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { assertEquals(Iterables.getOnlyElement(volume.getAttachments()), testAttachment()); } + public void testGetVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(client.getVolume("1")); + } + + public void testDeleteVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.deleteVolume("1")); + } + + public void testDeleteVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-volumes/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.deleteVolume("1")); + } + public void testListAttachments() { URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); VolumeClient client = requestsSendResponses( keystoneAuthWithUsernameAndPassword, responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, - HttpRequest.builder().method("GET").headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) - .endpoint(endpoint).build(), - HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_list.json")).build() + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_list.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - Set attachments = client.listAttachments("instance-1"); + Set attachments = client.listAttachments("instance-1"); assertEquals(attachments, ImmutableSet.of(testAttachment())); - // double-check equals() - Attachment attachment = Iterables.getOnlyElement(attachments); + // double-check individual fields + VolumeAttachment attachment = Iterables.getOnlyElement(attachments); assertEquals(attachment.getDevice(), "/dev/vdc"); assertEquals(attachment.getServerId(), "b4785058-cb80-491b-baa3-e4ee6546450e"); assertEquals(attachment.getId(), "1"); assertEquals(attachment.getVolumeId(), "1"); } + + @Test(expectedExceptions = AuthorizationException.class) + public void testListAttachmentsFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-2/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(401).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.listAttachments("instance-2"); + } + + public void testGetAttachment() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeAttachment attachment = client.getAttachment("instance-1", "1"); + assertEquals(attachment, testAttachment()); + } + + public void testGetAttachmentFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(client.getAttachment("instance-1", "1")); + } + + public void testAttachVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"volumeAttachment\":{\"volumeId\":\"1\",\"device\":\"/dev/vdc\"}}", MediaType.APPLICATION_JSON)).endpoint(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeAttachment result = client.attachVolume("instance-1", "1", "/dev/vdc"); + assertEquals(result, testAttachment()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testAttachVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"volumeAttachment\":{\"volumeId\":\"1\",\"device\":\"/dev/vdc\"}}", MediaType.APPLICATION_JSON)).endpoint(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.attachVolume("instance-1", "1", "/dev/vdc"); + } + + public void testDetachVolume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.detachVolume("instance-1", "1")); + } + + public void testDetachVolumeFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.detachVolume("instance-1", "1")); + } + + public void testListSnapshots() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_list.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshots(); + assertEquals(snapshots, ImmutableSet.of(testSnapshot())); + } + + public void testListSnapshotsFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshots(); + assertTrue(snapshots.isEmpty()); + } + + public void testGetSnapshot() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeSnapshot snapshot = client.getSnapshot("1"); + assertEquals(snapshot, testSnapshot()); + } + + public void testGetSnapshotFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(client.getSnapshot("1")); + } + + public void testListSnapshotsInDetail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_list_detail.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshotsInDetail(); + assertEquals(snapshots, ImmutableSet.of(testSnapshot())); + + // double-check individual fields + VolumeSnapshot snappy = Iterables.getOnlyElement(snapshots); + assertEquals(snappy.getId(), "7"); + assertEquals(snappy.getVolumeId(), "9"); + assertEquals(snappy.getStatus(), Volume.Status.AVAILABLE); + assertEquals(snappy.getDescription(), "jclouds live test snapshot"); + assertEquals(snappy.getName(), "jclouds-live-test"); + assertEquals(snappy.getSize(), 1); + } + + public void testListSnapshotsInDetailFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/detail"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + Set snapshots = client.listSnapshotsInDetail(); + assertTrue(snapshots.isEmpty()); + } + + public void testCreateSnapshot() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"snapshot\":{\"display_name\":\"jclouds-live-test\",\"volume_id\":\"13\",\"display_description\":\"jclouds live test snapshot\",\"force\":\"true\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeSnapshot snapshot = client.createSnapshot("13", CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + assertEquals(snapshot, testSnapshot()); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testCreateSnapshotFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint) + .method("POST") + .payload(payloadFromStringWithContentType("{\"snapshot\":{\"display_name\":\"jclouds-live-test\",\"volume_id\":\"13\",\"display_description\":\"jclouds live test snapshot\",\"force\":\"true\"}}", MediaType.APPLICATION_JSON)) + .build(), + standardResponseBuilder(401).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.createSnapshot("13", CreateVolumeSnapshotOptions.Builder.name("jclouds-live-test").description("jclouds live test snapshot").force()); + } + + public void testDeleteSnapshot() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(200).payload(payloadFromResource("/snapshot_details.json")).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.deleteSnapshot("1")); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testDeleteSnapshotFail() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-snapshots/1"); + VolumeClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("DELETE").build(), + standardResponseBuilder(401).build() + ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); + + client.deleteSnapshot("1"); + } protected Volume testVolume() { return Volume.builder().status(Volume.Status.IN_USE).description("This is a test volume").zone("nova").name("test") .attachments(ImmutableSet.of(testAttachment())).size(1).id("1").created(dateService.iso8601SecondsDateParse("2012-04-23 12:16:45")).build(); } - protected Attachment testAttachment() { - return Attachment.builder().device("/dev/vdc").serverId("b4785058-cb80-491b-baa3-e4ee6546450e").id("1").volumeId("1").build(); + protected VolumeAttachment testAttachment() { + return VolumeAttachment.builder().device("/dev/vdc").serverId("b4785058-cb80-491b-baa3-e4ee6546450e").id("1").volumeId("1").build(); } + protected VolumeSnapshot testSnapshot() { + return VolumeSnapshot.builder().id("7").volumeId("9").description("jclouds live test snapshot").status(Volume.Status.AVAILABLE) + .name("jclouds-live-test").size(1).created(dateService.iso8601SecondsDateParse("2012-04-24 13:34:42")).build(); + } } \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java index 336f507ba7..8815279eb4 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaExpectTest.java @@ -20,6 +20,8 @@ package org.jclouds.openstack.nova.v1_1.internal; import java.net.URI; +import javax.ws.rs.core.MediaType; + import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.openstack.keystone.v2_0.internal.KeystoneFixture; @@ -68,4 +70,14 @@ public class BaseNovaExpectTest extends BaseRestClientExpectTest { unmatchedExtensionsOfNovaResponse = HttpResponse.builder().statusCode(200) .payload(payloadFromResource("/extension_list.json")).build(); } + + protected HttpRequest.Builder standardRequestBuilder(URI endpoint) { + return HttpRequest.builder().method("GET") + .headers(ImmutableMultimap.of("Accept", MediaType.APPLICATION_JSON, "X-Auth-Token", authToken)) + .endpoint(endpoint); + } + + protected HttpResponse.Builder standardResponseBuilder(int status) { + return HttpResponse.builder().statusCode(status); + } } diff --git a/apis/openstack-nova/src/test/resources/attachment_details.json b/apis/openstack-nova/src/test/resources/attachment_details.json new file mode 100644 index 0000000000..c436406780 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/attachment_details.json @@ -0,0 +1 @@ +{"volumeAttachment": {"device": "/dev/vdc", "serverId": "b4785058-cb80-491b-baa3-e4ee6546450e", "id": 1, "volumeId": 1}} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/snapshot_details.json b/apis/openstack-nova/src/test/resources/snapshot_details.json new file mode 100644 index 0000000000..375127b705 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/snapshot_details.json @@ -0,0 +1 @@ +{"snapshot": {"status": "available", "displayDescription": "jclouds live test snapshot", "displayName": "jclouds-live-test", "volumeId": 9, "id": 7, "createdAt": "2012-04-24 13:34:42", "size": 1}} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/snapshot_list.json b/apis/openstack-nova/src/test/resources/snapshot_list.json new file mode 100644 index 0000000000..03980b1697 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/snapshot_list.json @@ -0,0 +1 @@ +{"snapshots": [{"status": "available", "displayDescription": "jclouds live test snapshot", "displayName": "jclouds-live-test", "volumeId": 9, "id": 7, "createdAt": "2012-04-24 13:34:42", "size": 1}]} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/snapshot_list_detail.json b/apis/openstack-nova/src/test/resources/snapshot_list_detail.json new file mode 100644 index 0000000000..03980b1697 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/snapshot_list_detail.json @@ -0,0 +1 @@ +{"snapshots": [{"status": "available", "displayDescription": "jclouds live test snapshot", "displayName": "jclouds-live-test", "volumeId": 9, "id": 7, "createdAt": "2012-04-24 13:34:42", "size": 1}]} \ No newline at end of file From 01c6a48786e4d58e6a08407f649515e508141f13 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:33:00 +0100 Subject: [PATCH 4/7] Nova VolumeClient: improving javadocs --- .../openstack/nova/v1_1/domain/Volume.java | 14 ++++++++++--- .../nova/v1_1/domain/VolumeAttachment.java | 14 ++++++++----- .../nova/v1_1/domain/VolumeSnapshot.java | 20 ++++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) 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 8a77bdbb8e..299e6d9c22 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 @@ -193,40 +193,46 @@ public class Volume { this.snapshotId = builder.snapshotId; this.name = builder.name; this.description = builder.description; - this.metadata = ImmutableMap.copyOf(builder.metadata); + this.metadata = ImmutableMap.copyOf(checkNotNull(builder.metadata, "metadata")); } /** + * @return the id of this volume */ public String getId() { return this.id; } /** + * @return the status of this volume */ public Status getStatus() { return this.status; } /** + * @return the size in GB of this volume */ public int getSize() { return this.size; } /** + * @return the availabilityZone containing this volume */ public String getZone() { return this.zone; } /** + * @return the time this volume was created */ public Date getCreated() { return this.created; } /** + * @return the set of attachments (to Servers) */ @Nullable public Set getAttachments() { @@ -234,6 +240,7 @@ public class Volume { } /** + * @return the type of this volume */ @Nullable public String getVolumeType() { @@ -241,6 +248,7 @@ public class Volume { } /** + * @return the snapshot id this volume is associated with. */ @Nullable public String getSnapshotId() { @@ -248,6 +256,7 @@ public class Volume { } /** + * @return the name of this volume - as displayed in the openstack console */ @Nullable public String getName() { @@ -255,14 +264,13 @@ public class Volume { } /** + * @return the description of this volume - as displayed in the openstack console */ @Nullable public String getDescription() { return this.description; } - /** - */ @Nullable public Map getMetadata() { return Collections.unmodifiableMap(this.metadata); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java index ef93d9d18b..b6a231d7d1 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java @@ -18,13 +18,15 @@ */ package org.jclouds.openstack.nova.v1_1.domain; +import static com.google.common.base.Preconditions.checkNotNull; + import org.jclouds.javax.annotation.Nullable; import com.google.common.base.Objects; import com.google.common.base.Objects.ToStringHelper; /** - * An Openstack Nova Volume Attachment + * An Openstack Nova Volume Attachment (describes how Volumes are attached to Servers) */ public class VolumeAttachment { @@ -92,27 +94,28 @@ public class VolumeAttachment { private final String device; protected VolumeAttachment(Builder builder) { - this.id = builder.id; - this.volumeId = builder.volumeId; + this.id = checkNotNull(builder.id, "id"); + this.volumeId = checkNotNull(builder.volumeId, "volumeId"); this.serverId = builder.serverId; this.device = builder.device; } /** + * @return the attachment id (typically the same as #getVolumeId()) */ - @Nullable public String getId() { return this.id; } /** + * @return the id of the volume attached */ - @Nullable public String getVolumeId() { return this.volumeId; } /** + * @return the id of the server the volume is attached to */ @Nullable public String getServerId() { @@ -120,6 +123,7 @@ public class VolumeAttachment { } /** + * @return the device name (e.g. "/dev/vdc") */ @Nullable public String getDevice() { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java index fa7ca86cb8..b20d47c467 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java @@ -18,6 +18,8 @@ */ package org.jclouds.openstack.nova.v1_1.domain; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Date; import org.jclouds.javax.annotation.Nullable; @@ -122,9 +124,9 @@ public class VolumeSnapshot { private final String description; protected VolumeSnapshot(Builder builder) { - this.id = builder.id; - this.volumeId = builder.volumeId; - this.status = builder.status; + this.id = checkNotNull(builder.id, "id"); + this.volumeId = checkNotNull(builder.volumeId, "volumeId"); + this.status = checkNotNull(builder.status, "status"); this.size = builder.size; this.created = builder.created; this.name = builder.name; @@ -132,34 +134,35 @@ public class VolumeSnapshot { } /** + * @return the id of this snapshot */ - @Nullable public String getId() { return this.id; } /** + * @return the id of the Volume this snapshot was taken from */ - @Nullable public String getVolumeId() { return this.volumeId; } /** + * @return the status of this snapshot */ - @Nullable public Volume.Status getStatus() { return this.status; } /** + * @return the size in GB of the volume this snapshot was taken from */ - @Nullable public int getSize() { return this.size; } /** + * @return the data the snapshot was taken */ @Nullable public Date getCreated() { @@ -167,13 +170,16 @@ public class VolumeSnapshot { } /** + * @return the name of this snapshot - as displayed in the openstack console */ @Nullable public String getName() { return this.name; } + /** + * @return the description of this snapshot - as displayed in the openstack console */ @Nullable public String getDescription() { From 1dfcc6cde5334ba490f09b8f30191e15601ff657 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:39:33 +0100 Subject: [PATCH 5/7] Nova VolumeClient: improving javadocs --- .../openstack/nova/v1_1/domain/Volume.java | 18 +++++++++++++----- .../nova/v1_1/domain/VolumeAttachment.java | 4 ++++ .../nova/v1_1/domain/VolumeSnapshot.java | 7 +++++++ 3 files changed, 24 insertions(+), 5 deletions(-) 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 299e6d9c22..ab7017ae26 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 @@ -84,56 +84,67 @@ public class Volume { private String description; private Map metadata = Maps.newHashMap(); + /** @see Volume#getId() */ public T id(String id) { this.id = id; return self(); } + /** @see Volume#getStatus() */ public T status(Status status) { this.status = status; return self(); } + /** @see Volume#getSize() */ public T size(int size) { this.size = size; return self(); } + /** @see Volume#getZone() */ public T zone(String zone) { this.zone = zone; return self(); } + /** @see Volume#getCreated() */ public T created(Date created) { this.created = created; return self(); } + /** @see Volume#getAttachments() */ public T attachments(Set attachments) { this.attachments = attachments; return self(); } - + + /** @see Volume#getVolumeType() */ public T volumeType(String volumeType) { this.volumeType = volumeType; return self(); } + /** @see Volume#getSnapshotId() */ public T snapshotId(String snapshotId) { this.snapshotId = snapshotId; return self(); } - + + /** @see Volume#getMetadata() */ public T metadata(Map metadata) { this.metadata = metadata; return self(); } + /** @see Volume#getName() */ public T name(String name) { this.name = name; return self(); } + /** @see Volume#getDescription() */ public T description(String description) { this.description = description; return self(); @@ -247,9 +258,6 @@ public class Volume { return this.volumeType; } - /** - * @return the snapshot id this volume is associated with. - */ @Nullable public String getSnapshotId() { return this.snapshotId; diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java index b6a231d7d1..bc33a0ba63 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeAttachment.java @@ -46,21 +46,25 @@ public class VolumeAttachment { private String serverId; private String device; + /** @see VolumeAttachment#getId() */ public T id(String id) { this.id = id; return self(); } + /** @see VolumeAttachment#getVolumeId() */ public T volumeId(String volumeId) { this.volumeId = volumeId; return self(); } + /** @see VolumeAttachment#getServerId() */ public T serverId(String serverId) { this.serverId = serverId; return self(); } + /** @see VolumeAttachment#getDevice() */ public T device(String device) { this.device = device; return self(); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java index b20d47c467..d79d3d3231 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VolumeSnapshot.java @@ -52,36 +52,43 @@ public class VolumeSnapshot { private String name; private String description; + /** @see VolumeSnapshot#getId() */ public T id(String id) { this.id = id; return self(); } + /** @see VolumeSnapshot#getVolumeId() */ public T volumeId(String volumeId) { this.volumeId = volumeId; return self(); } + /** @see VolumeSnapshot#getStatus() */ public T status(Volume.Status status) { this.status = status; return self(); } + /** @see VolumeSnapshot#getSize() */ public T size(int size) { this.size = size; return self(); } + /** @see VolumeSnapshot#getCreated() */ public T created(Date created) { this.created = created; return self(); } + /** @see VolumeSnapshot#getName() */ public T name(String name) { this.name = name; return self(); } + /** @see VolumeSnapshot#getDescription() */ public T description(String description) { this.description = description; return self(); From ff1428e6f7bb99a5222b61ea5e1e7b59ffde2ccf Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 16:41:54 +0100 Subject: [PATCH 6/7] Nova VolumeClient: improving javadocs --- .../openstack/nova/v1_1/extensions/VolumeClientLiveTest.java | 2 -- 1 file changed, 2 deletions(-) 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 522aaa3215..b4b61ed103 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 @@ -42,8 +42,6 @@ import com.google.common.collect.Iterables; /** * 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) From 32022723ade9a680dcea066fbfd98721841b8669 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 24 Apr 2012 19:47:54 +0100 Subject: [PATCH 7/7] Nova VolumeClient: adjusting attachment method names after review --- .../nova/v1_1/extensions/VolumeAsyncClient.java | 12 ++++++------ .../nova/v1_1/extensions/VolumeClient.java | 8 ++++---- .../v1_1/extensions/VolumeClientExpectTest.java | 16 ++++++++-------- .../v1_1/extensions/VolumeClientLiveTest.java | 14 +++++++------- 4 files changed, 25 insertions(+), 25 deletions(-) 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 fbc7465e0b..97584b1d07 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 @@ -131,7 +131,7 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachments") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) - ListenableFuture> listAttachments(@PathParam("server_id") String serverId); + ListenableFuture> listAttachmentsOnServer(@PathParam("server_id") String serverId); /** * Get a specific attached volume. @@ -143,7 +143,8 @@ public interface VolumeAsyncClient { @SelectJson("volumeAttachment") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getAttachment(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + ListenableFuture getAttachmentForVolumeOnServer(@PathParam("id") String volumeId, + @PathParam("server_id") String serverId); /** * Attach a volume to an instance @@ -156,9 +157,8 @@ public interface VolumeAsyncClient { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @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); + ListenableFuture attachVolumeToServerAsDevice(@PayloadParam("id") String volumeId, + @PathParam("server_id") String serverId, @PayloadParam("device") String device); /** * Detach a Volume from an instance. @@ -169,7 +169,7 @@ public interface VolumeAsyncClient { @Path("/servers/{server_id}/os-volume_attachments/{id}") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnFalseOnNotFoundOr404.class) - ListenableFuture detachVolume(@PathParam("server_id") String serverId, @PathParam("id") String volumeId); + ListenableFuture detachVolumeFromServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId); /** * Returns a summary list of snapshots. 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 fc26aad4cf..3f40523d79 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 @@ -80,28 +80,28 @@ public interface VolumeClient { * * @return all Floating IPs */ - Set listAttachments(String serverId); + Set listAttachmentsOnServer(String serverId); /** * Get a specific attached volume. * * @return data about the given volume attachment. */ - VolumeAttachment getAttachment(String serverId, String volumeId); + VolumeAttachment getAttachmentForVolumeOnServer(String volumeId, String serverId); /** * Attach a volume to an instance * * @return data about the new volume attachment */ - VolumeAttachment attachVolume(String serverId, String volumeId, String device); + VolumeAttachment attachVolumeToServerAsDevice(String volumeId, String serverId, String device); /** * Detach a Volume from an instance. * * @return true if successful */ - Boolean detachVolume(String server_id, String volumeId); + Boolean detachVolumeFromServer(String server_id, String volumeId); /** * Returns a summary list of snapshots. diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java index 4f70e81496..3349f0ac92 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VolumeClientExpectTest.java @@ -199,7 +199,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_list.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - Set attachments = client.listAttachments("instance-1"); + Set attachments = client.listAttachmentsOnServer("instance-1"); assertEquals(attachments, ImmutableSet.of(testAttachment())); // double-check individual fields VolumeAttachment attachment = Iterables.getOnlyElement(attachments); @@ -219,7 +219,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(401).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - client.listAttachments("instance-2"); + client.listAttachmentsOnServer("instance-2"); } public void testGetAttachment() { @@ -231,7 +231,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - VolumeAttachment attachment = client.getAttachment("instance-1", "1"); + VolumeAttachment attachment = client.getAttachmentForVolumeOnServer("1", "instance-1"); assertEquals(attachment, testAttachment()); } @@ -244,7 +244,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(404).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - assertNull(client.getAttachment("instance-1", "1")); + assertNull(client.getAttachmentForVolumeOnServer("1", "instance-1")); } public void testAttachVolume() { @@ -257,7 +257,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - VolumeAttachment result = client.attachVolume("instance-1", "1", "/dev/vdc"); + VolumeAttachment result = client.attachVolumeToServerAsDevice("1", "instance-1", "/dev/vdc"); assertEquals(result, testAttachment()); } @@ -272,7 +272,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(404).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - client.attachVolume("instance-1", "1", "/dev/vdc"); + client.attachVolumeToServerAsDevice("1", "instance-1","/dev/vdc"); } public void testDetachVolume() { @@ -284,7 +284,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(200).payload(payloadFromResource("/attachment_details.json")).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - assertTrue(client.detachVolume("instance-1", "1")); + assertTrue(client.detachVolumeFromServer("1", "instance-1")); } public void testDetachVolumeFail() { @@ -296,7 +296,7 @@ public class VolumeClientExpectTest extends BaseNovaClientExpectTest { standardResponseBuilder(404).build() ).getVolumeExtensionForZone("az-1.region-a.geo-1").get(); - assertFalse(client.detachVolume("instance-1", "1")); + assertFalse(client.detachVolumeFromServer("1", "instance-1")); } public void testListSnapshots() { 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 b4b61ed103..bca364837c 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 @@ -218,22 +218,22 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { Set servers = novaContext.getApi().getServerClientForZone(zone).listServers(); if (!servers.isEmpty()) { final String serverId = Iterables.getFirst(servers, null).getId(); - Set attachments = client.listAttachments(serverId); + Set attachments = client.listAttachmentsOnServer(serverId); assertNotNull(attachments); final int before = attachments.size(); - VolumeAttachment testAttachment = client.attachVolume(serverId, testVolume.getId(), "/dev/vdf"); + VolumeAttachment testAttachment = client.attachVolumeToServerAsDevice(testVolume.getId(), serverId, "/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; + return client.listAttachmentsOnServer(serverId).size() == before+1; } }, 60 * 1000L).apply(client)); - attachments = client.listAttachments(serverId); + attachments = client.listAttachmentsOnServer(serverId); assertNotNull(attachments); assertEquals(attachments.size(), before+1); @@ -241,7 +241,7 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { boolean foundIt = false; for (VolumeAttachment att : attachments) { - VolumeAttachment details = client.getAttachment(serverId, att.getId()); + VolumeAttachment details = client.getAttachmentForVolumeOnServer(att.getVolumeId(), serverId); assertNotNull(details); assertNotNull(details.getId()); assertNotNull(details.getServerId()); @@ -255,11 +255,11 @@ public class VolumeClientLiveTest extends BaseNovaClientLiveTest { assertTrue(foundIt, "Failed to find the attachment we created in listAttachments() response"); - client.detachVolume(serverId, testVolume.getId()); + client.detachVolumeFromServer(testVolume.getId(), serverId); assertTrue(new RetryablePredicate(new Predicate() { @Override public boolean apply(VolumeClient volumeClient) { - return client.listAttachments(serverId).size() == before; + return client.listAttachmentsOnServer(serverId).size() == before; } }, 60 * 1000L).apply(client)); }