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