diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApi.java index 1940faf3e0..f92dd3c889 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApi.java @@ -25,19 +25,20 @@ import org.jclouds.concurrent.Timeout; import org.jclouds.javax.annotation.Nullable; import org.jclouds.location.Zone; import org.jclouds.location.functions.ZoneToEndpoint; -import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminApi; import org.jclouds.openstack.nova.v2_0.extensions.FlavorExtraSpecsApi; import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi; import org.jclouds.openstack.nova.v2_0.extensions.HostAdministrationApi; import org.jclouds.openstack.nova.v2_0.extensions.HostAggregateApi; import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi; -import org.jclouds.openstack.nova.v2_0.extensions.QuotaClassApi; import org.jclouds.openstack.nova.v2_0.extensions.QuotaApi; +import org.jclouds.openstack.nova.v2_0.extensions.QuotaClassApi; import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi; +import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminApi; import org.jclouds.openstack.nova.v2_0.extensions.ServerWithSecurityGroupsApi; import org.jclouds.openstack.nova.v2_0.extensions.SimpleTenantUsageApi; import org.jclouds.openstack.nova.v2_0.extensions.VirtualInterfaceApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeApi; +import org.jclouds.openstack.nova.v2_0.extensions.VolumeAttachmentApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeTypeApi; import org.jclouds.openstack.nova.v2_0.features.FlavorApi; import org.jclouds.openstack.nova.v2_0.features.ImageApi; @@ -131,13 +132,6 @@ public interface NovaApi { 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); - /** * Provides synchronous access to Virtual Interface features. */ @@ -187,6 +181,20 @@ public interface NovaApi { Optional getQuotaClassExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** + * Provides synchronous access to Volume features. + */ + @Delegate + Optional getVolumeExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to Volume Attachment features. + */ + @Delegate + Optional getVolumeAttachmentExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** * Provides synchronous access to Volume Type features. */ diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaAsyncApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaAsyncApi.java index 09631d323f..7c956131ff 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaAsyncApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaAsyncApi.java @@ -23,7 +23,6 @@ import java.util.Set; import org.jclouds.javax.annotation.Nullable; import org.jclouds.location.Zone; import org.jclouds.location.functions.ZoneToEndpoint; -import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.FlavorExtraSpecsAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.HostAdministrationAsyncApi; @@ -32,10 +31,12 @@ import org.jclouds.openstack.nova.v2_0.extensions.KeyPairAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.QuotaAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.QuotaClassAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupAsyncApi; +import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.ServerWithSecurityGroupsAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.SimpleTenantUsageAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.VirtualInterfaceAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeAsyncApi; +import org.jclouds.openstack.nova.v2_0.extensions.VolumeAttachmentAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeTypeAsyncApi; import org.jclouds.openstack.nova.v2_0.features.FlavorAsyncApi; import org.jclouds.openstack.nova.v2_0.features.ImageAsyncApi; @@ -129,13 +130,6 @@ public interface NovaAsyncApi { 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); - /** * Provides asynchronous access to Virtual Interface features. */ @@ -186,6 +180,20 @@ public interface NovaAsyncApi { Optional getQuotaClassExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** + * Provides asynchronous access to Volume features. + */ + @Delegate + Optional getVolumeExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides asynchronous access to Volume features. + */ + @Delegate + Optional getVolumeAttachmentExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** * Provides asynchronous access to Volume Type features. */ diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaRestClientModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaRestClientModule.java index e6b242e20c..7dfc19c055 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaRestClientModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaRestClientModule.java @@ -32,8 +32,6 @@ import org.jclouds.http.annotation.Redirection; import org.jclouds.http.annotation.ServerError; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.NovaAsyncApi; -import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminApi; -import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.ExtensionNamespaces; import org.jclouds.openstack.nova.v2_0.extensions.FlavorExtraSpecsApi; import org.jclouds.openstack.nova.v2_0.extensions.FlavorExtraSpecsAsyncApi; @@ -51,6 +49,8 @@ import org.jclouds.openstack.nova.v2_0.extensions.QuotaClassApi; import org.jclouds.openstack.nova.v2_0.extensions.QuotaClassAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi; import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupAsyncApi; +import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminApi; +import org.jclouds.openstack.nova.v2_0.extensions.ServerAdminAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.ServerWithSecurityGroupsApi; import org.jclouds.openstack.nova.v2_0.extensions.ServerWithSecurityGroupsAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.SimpleTenantUsageApi; @@ -59,6 +59,8 @@ import org.jclouds.openstack.nova.v2_0.extensions.VirtualInterfaceApi; import org.jclouds.openstack.nova.v2_0.extensions.VirtualInterfaceAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeAsyncApi; +import org.jclouds.openstack.nova.v2_0.extensions.VolumeAttachmentApi; +import org.jclouds.openstack.nova.v2_0.extensions.VolumeAttachmentAsyncApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeTypeApi; import org.jclouds.openstack.nova.v2_0.extensions.VolumeTypeAsyncApi; import org.jclouds.openstack.nova.v2_0.features.FlavorApi; @@ -103,7 +105,6 @@ public class NovaRestClientModule ext .put(KeyPairApi.class, KeyPairAsyncApi.class) .put(HostAdministrationApi.class, HostAdministrationAsyncApi.class) .put(SimpleTenantUsageApi.class, SimpleTenantUsageAsyncApi.class) - .put(VolumeApi.class, VolumeAsyncApi.class) .put(VirtualInterfaceApi.class, VirtualInterfaceAsyncApi.class) .put(ServerWithSecurityGroupsApi.class, ServerWithSecurityGroupsAsyncApi.class) .put(ServerAdminApi.class, ServerAdminAsyncApi.class) @@ -111,6 +112,8 @@ public class NovaRestClientModule ext .put(FlavorExtraSpecsApi.class, FlavorExtraSpecsAsyncApi.class) .put(QuotaApi.class, QuotaAsyncApi.class) .put(QuotaClassApi.class, QuotaClassAsyncApi.class) + .put(VolumeApi.class, VolumeAsyncApi.class) + .put(VolumeAttachmentApi.class, VolumeAttachmentAsyncApi.class) .put(VolumeTypeApi.class, VolumeTypeAsyncApi.class) .build(); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeApi.java index 30f509fd90..126c1ff7ec 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeApi.java @@ -83,29 +83,37 @@ public interface VolumeApi { * List volume attachments for a given instance. * * @return all Floating IPs + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#listAttachmentsOnServer(String) */ - FluentIterable listAttachmentsOnServer(String serverId); + @Deprecated FluentIterable listAttachmentsOnServer(String serverId); /** * Get a specific attached volume. * * @return data about the given volume attachment. + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#getAttachmentForVolumeOnServer(String, String) */ - VolumeAttachment getAttachmentForVolumeOnServer(String volumeId, String serverId); + @Deprecated VolumeAttachment getAttachmentForVolumeOnServer(String volumeId, String serverId); /** * Attach a volume to an instance * * @return data about the new volume attachment + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#attachVolumeToServerAsDevice(String, String, String) */ - VolumeAttachment attachVolumeToServerAsDevice(String volumeId, String serverId, String device); + @Deprecated VolumeAttachment attachVolumeToServerAsDevice(String volumeId, String serverId, String device); /** * Detach a Volume from an instance. * * @return true if successful + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#detachVolumeFromServer(String, String) */ - Boolean detachVolumeFromServer(String server_id, String volumeId); + @Deprecated Boolean detachVolumeFromServer(String server_id, String volumeId); /** * Returns a summary list of snapshots. diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAsyncApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAsyncApi.java index d85bf687cc..83aaf22e6e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAsyncApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAsyncApi.java @@ -126,31 +126,37 @@ public interface VolumeAsyncApi { * List volume attachments for a given instance. * * @return all Floating IPs + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#listAttachmentsOnServer(String) */ @GET @Path("/servers/{server_id}/os-volume_attachments") @SelectJson("volumeAttachments") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) - ListenableFuture> listAttachmentsOnServer(@PathParam("server_id") String serverId); + @Deprecated ListenableFuture> listAttachmentsOnServer(@PathParam("server_id") String serverId); /** * Get a specific attached volume. * * @return data about the given volume attachment. + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#getAttachmentForVolumeOnServer(String, String) */ @GET @Path("/servers/{server_id}/os-volume_attachments/{id}") @SelectJson("volumeAttachment") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture getAttachmentForVolumeOnServer(@PathParam("id") String volumeId, + @Deprecated ListenableFuture getAttachmentForVolumeOnServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId); /** * Attach a volume to an instance * * @return the new Attachment + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#attachVolumeToServerAsDevice(String, String, String) */ @POST @Path("/servers/{server_id}/os-volume_attachments") @@ -158,19 +164,21 @@ public interface VolumeAsyncApi { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @WrapWith("volumeAttachment") - ListenableFuture attachVolumeToServerAsDevice(@PayloadParam("volumeId") String volumeId, + @Deprecated ListenableFuture attachVolumeToServerAsDevice(@PayloadParam("volumeId") String volumeId, @PathParam("server_id") String serverId, @PayloadParam("device") String device); /** * Detach a Volume from an instance. * * @return true if successful + * @deprecated To be removed in jclouds 1.7 + * @see VolumeAttachmentApi#detachVolumeFromServer(String, String) */ @DELETE @Path("/servers/{server_id}/os-volume_attachments/{id}") @Consumes(MediaType.APPLICATION_JSON) @ExceptionParser(ReturnFalseOnNotFoundOr404.class) - ListenableFuture detachVolumeFromServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId); + @Deprecated 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/v2_0/extensions/VolumeAttachmentApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApi.java new file mode 100644 index 0000000000..cecaa8da64 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApi.java @@ -0,0 +1,91 @@ +/** + * 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.v2_0.extensions; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment; +import org.jclouds.openstack.v2_0.ServiceType; +import org.jclouds.openstack.v2_0.services.Extension; + +import com.google.common.annotations.Beta; +import com.google.common.collect.FluentIterable; + +/** + * Provides synchronous access to Volume Attachments. + * + * This API strictly handles attaching Volumes to Servers. To create and manage Volumes you need to use one of the + * following APIs: + * + * 1. The Cinder API + * If your OpenStack deployment is Folsom or later and it supports the Cinder block storage service, use this API. + * @see org.jclouds.openstack.cinder.v1.features.VolumeApi + * + * 2. The nova-volume API + * If your OpenStack deployment is Essex or earlier and it supports the nova-volume extension, use this API. + * @see org.jclouds.openstack.nova.v2_0.extensions.VolumeApi + * + * @see VolumeAttachmentAsyncApi + * @author Everett Toews + */ +@Beta +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VOLUMES) +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface VolumeAttachmentApi { + /** + * List Volume Attachments for a given Server. + * + * @param serverId The ID of the Server + * @return All VolumeAttachments for the Server + */ + FluentIterable listAttachmentsOnServer(String serverId); + + /** + * Get a specific Volume Attachment for a Volume and Server. + * + * @param volumeId The ID of the Volume + * @param serverId The ID of the Server + * @return The Volume Attachment. + */ + VolumeAttachment getAttachmentForVolumeOnServer(String volumeId, String serverId); + + /** + * Attach a Volume to a Server. + * + * Note: If you are using KVM as your hypervisor then the actual device name in the Server will be different than + * the one specified. When the Server sees a new device, it picks the next available name (which in most cases is + * /dev/vdc) and the disk shows up there on the Server. + * + * @param serverId The ID of the Server + * @param volumeId The ID of the Volume + * @param device The name of the device this Volume will be identified as in the Server (e.g. /dev/vdc) + * @return The Volume Attachment. + */ + VolumeAttachment attachVolumeToServerAsDevice(String volumeId, String serverId, String device); + + /** + * Detach a Volume from a server. + * + * @param serverId The ID of the Server + * @param volumeId The ID of the Volume + * @return true if successful + */ + boolean detachVolumeFromServer(String serverId, String volumeId); +} \ No newline at end of file diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentAsyncApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentAsyncApi.java new file mode 100644 index 0000000000..495ee8bba7 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentAsyncApi.java @@ -0,0 +1,106 @@ +/** + * 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.v2_0.extensions; + +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.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment; +import org.jclouds.openstack.v2_0.ServiceType; +import org.jclouds.openstack.v2_0.services.Extension; +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.annotations.WrapWith; +import org.jclouds.rest.functions.ReturnEmptyFluentIterableOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.annotations.Beta; +import com.google.common.collect.FluentIterable; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides asynchronous access to Volume Attachments . + * + * @see VolumeAttachmentApi + * @author Everett Toews + */ +@Beta +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VOLUMES) +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface VolumeAttachmentAsyncApi { + /** + * @see VolumeAttachmentApi#listAttachmentsOnServer(String) + */ + @GET + @Path("/servers/{server_id}/os-volume_attachments") + @SelectJson("volumeAttachments") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> listAttachmentsOnServer( + @PathParam("server_id") String serverId); + + /** + * @see VolumeAttachmentApi#getAttachmentForVolumeOnServer(String, String) + */ + @GET + @Path("/servers/{server_id}/os-volume_attachments/{id}") + @SelectJson("volumeAttachment") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getAttachmentForVolumeOnServer( + @PathParam("id") String volumeId, + @PathParam("server_id") String serverId); + + /** + * @see VolumeAttachmentApi#attachVolumeToServerAsDevice(String, String, String) + */ + @POST + @Path("/servers/{server_id}/os-volume_attachments") + @SelectJson("volumeAttachment") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @WrapWith("volumeAttachment") + ListenableFuture attachVolumeToServerAsDevice( + @PayloadParam("volumeId") String volumeId, + @PathParam("server_id") String serverId, + @PayloadParam("device") String device); + + /** + * @see VolumeAttachmentApi#detachVolumeFromServer(String, String) + */ + @DELETE + @Path("/servers/{server_id}/os-volume_attachments/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture detachVolumeFromServer( + @PathParam("id") String volumeId, + @PathParam("server_id") String serverId); +} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiExpectTest.java new file mode 100644 index 0000000000..11bbfa4b03 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiExpectTest.java @@ -0,0 +1,170 @@ +/** + * 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.v2_0.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +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.HttpResponse; +import org.jclouds.openstack.nova.v2_0.domain.Volume; +import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v2_0.internal.BaseNovaApiExpectTest; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Tests VolumeAttachmentApi Guice wiring and parsing + * + * @author Adam Lowe + */ +@Test(groups = "unit", testName = "VolumeAttachmentApiExpectTest") +public class VolumeAttachmentApiExpectTest extends BaseNovaApiExpectTest { + private DateService dateService = new SimpleDateFormatDateService(); + + public void testListAttachments() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_list.json")).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + Set attachments = api.listAttachmentsOnServer("instance-1").toImmutableSet(); + assertEquals(attachments, ImmutableSet.of(testAttachment())); + // 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://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-2/os-volume_attachments"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(401).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + api.listAttachmentsOnServer("instance-2"); + } + + public void testGetAttachment() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeAttachment attachment = api.getAttachmentForVolumeOnServer("1", "instance-1"); + assertEquals(attachment, testAttachment()); + } + + public void testGetAttachmentFail() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + assertNull(api.getAttachmentForVolumeOnServer("1", "instance-1")); + } + + public void testAttachVolume() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"volumeAttachment\":{\"volumeId\":\"1\",\"device\":\"/dev/vdc\"}}", MediaType.APPLICATION_JSON)).endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + VolumeAttachment result = api.attachVolumeToServerAsDevice("1", "instance-1", "/dev/vdc"); + assertEquals(result, testAttachment()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testAttachVolumeFail() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"volumeAttachment\":{\"volumeId\":\"1\",\"device\":\"/dev/vdc\"}}", MediaType.APPLICATION_JSON)).endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + api.attachVolumeToServerAsDevice("1", "instance-1","/dev/vdc"); + } + + public void testDetachVolume() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/attachment_details.json")).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(api.detachVolumeFromServer("1", "instance-1")); + } + + public void testDetachVolumeFail() { + URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/instance-1/os-volume_attachments/1"); + VolumeAttachmentApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeAttachmentExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(api.detachVolumeFromServer("1", "instance-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 VolumeAttachment testAttachment() { + return VolumeAttachment.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/v2_0/extensions/VolumeAttachmentApiLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java new file mode 100644 index 0000000000..40368bc4af --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java @@ -0,0 +1,162 @@ +/** + * 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.v2_0.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Set; + +import org.jclouds.openstack.nova.v2_0.domain.Volume; +import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v2_0.internal.BaseNovaApiLiveTest; +import org.jclouds.openstack.nova.v2_0.options.CreateVolumeOptions; +import org.jclouds.predicates.RetryablePredicate; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +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 Volume Attachment API + * + * @author Everett Toews + */ +@Test(groups = "live", testName = "VolumeAttachmentApiLiveTest", singleThreaded = true) +public class VolumeAttachmentApiLiveTest extends BaseNovaApiLiveTest { + + private Optional volumeApi; + private Optional volumeAttachmentApi; + + private String zone; + private Volume testVolume; + + @BeforeClass(groups = {"integration", "live"}) + @Override + public void setupContext() { + super.setupContext(); + zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova"); + volumeApi = novaContext.getApi().getVolumeExtensionForZone(zone); + volumeAttachmentApi = novaContext.getApi().getVolumeAttachmentExtensionForZone(zone); + } + + @AfterClass(groups = { "integration", "live" }) + @Override + protected void tearDownContext() { + if (volumeApi.isPresent()) { + if (testVolume != null) { + final String volumeId = testVolume.getId(); + assertTrue(volumeApi.get().delete(volumeId)); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeApi volumeApi) { + return volumeApi.get(volumeId) == null; + } + }, 180 * 1000L).apply(volumeApi.get())); + } + } + + super.tearDownContext(); + } + + public void testCreateVolume() { + if (volumeApi.isPresent()) { + CreateVolumeOptions options = CreateVolumeOptions.Builder + .name("jclouds-test-volume") + .description("description of test volume") + .availabilityZone(zone); + + testVolume = volumeApi.get().create(1, options); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeApi volumeApi) { + return volumeApi.get(testVolume.getId()).getStatus() == Volume.Status.AVAILABLE; + } + }, 180 * 1000L).apply(volumeApi.get())); + } + } + + @Test(dependsOnMethods = "testCreateVolume") + public void testAttachments() { + if (volumeApi.isPresent()) { + String server_id = null; + try { + final String serverId = server_id = createServerInZone(zone).getId(); + + Set attachments = + volumeAttachmentApi.get().listAttachmentsOnServer(serverId).toImmutableSet(); + assertNotNull(attachments); + final int before = attachments.size(); + + VolumeAttachment testAttachment = volumeAttachmentApi.get().attachVolumeToServerAsDevice( + testVolume.getId(), serverId, "/dev/vdf"); + assertNotNull(testAttachment.getId()); + assertEquals(testAttachment.getVolumeId(), testVolume.getId()); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeAttachmentApi volumeAttachmentApi) { + return volumeAttachmentApi.listAttachmentsOnServer(serverId).size() > before; + } + }, 60 * 1000L).apply(volumeAttachmentApi.get())); + + attachments = volumeAttachmentApi.get().listAttachmentsOnServer(serverId).toImmutableSet(); + assertNotNull(attachments); + assertEquals(attachments.size(), before + 1); + + assertEquals(volumeApi.get().get(testVolume.getId()).getStatus(), Volume.Status.IN_USE); + + boolean foundIt = false; + for (VolumeAttachment att : attachments) { + VolumeAttachment details = volumeAttachmentApi.get() + .getAttachmentForVolumeOnServer(att.getVolumeId(), serverId); + 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"); + + volumeAttachmentApi.get().detachVolumeFromServer(testVolume.getId(), serverId); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeAttachmentApi volumeAttachmentApi) { + return volumeAttachmentApi.listAttachmentsOnServer(serverId).size() == before; + } + }, 60 * 1000L).apply(volumeAttachmentApi.get())); + + } finally { + if (server_id != null) + novaContext.getApi().getServerApiForZone(zone).delete(server_id); + } + + } + } +} \ No newline at end of file