Merge pull request #965 from rackspace/openstack-nova-volume-attachment-api

OpenStack Nova Volume Attachment API
This commit is contained in:
Adrian Cole 2012-11-07 06:45:49 -08:00
commit d557b581e3
9 changed files with 592 additions and 28 deletions

View File

@ -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<? extends SimpleTenantUsageApi> getSimpleTenantUsageExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Volume features.
*/
@Delegate
Optional<? extends VolumeApi> getVolumeExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Virtual Interface features.
*/
@ -187,6 +181,20 @@ public interface NovaApi {
Optional<? extends QuotaClassApi> getQuotaClassExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Volume features.
*/
@Delegate
Optional<? extends VolumeApi> getVolumeExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Volume Attachment features.
*/
@Delegate
Optional<? extends VolumeAttachmentApi> getVolumeAttachmentExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Volume Type features.
*/

View File

@ -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<? extends SimpleTenantUsageAsyncApi> getSimpleTenantUsageExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Volume features.
*/
@Delegate
Optional<? extends VolumeAsyncApi> getVolumeExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Virtual Interface features.
*/
@ -186,6 +180,20 @@ public interface NovaAsyncApi {
Optional<? extends QuotaClassAsyncApi> getQuotaClassExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Volume features.
*/
@Delegate
Optional<? extends VolumeAsyncApi> getVolumeExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Volume features.
*/
@Delegate
Optional<? extends VolumeAttachmentAsyncApi> getVolumeAttachmentExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Volume Type features.
*/

View File

@ -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<S extends NovaApi, A extends NovaAsyncApi> 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<S extends NovaApi, A extends NovaAsyncApi> 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();

View File

@ -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<? extends VolumeAttachment> listAttachmentsOnServer(String serverId);
@Deprecated FluentIterable<? extends VolumeAttachment> 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.

View File

@ -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<? extends FluentIterable<? extends VolumeAttachment>> listAttachmentsOnServer(@PathParam("server_id") String serverId);
@Deprecated ListenableFuture<? extends FluentIterable<? extends VolumeAttachment>> 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<? extends VolumeAttachment> getAttachmentForVolumeOnServer(@PathParam("id") String volumeId,
@Deprecated ListenableFuture<? extends VolumeAttachment> 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<? extends VolumeAttachment> attachVolumeToServerAsDevice(@PayloadParam("volumeId") String volumeId,
@Deprecated ListenableFuture<? extends VolumeAttachment> 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<Boolean> detachVolumeFromServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId);
@Deprecated ListenableFuture<Boolean> detachVolumeFromServer(@PathParam("id") String volumeId, @PathParam("server_id") String serverId);
/**
* Returns a summary list of snapshots.

View File

@ -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<? extends VolumeAttachment> 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);
}

View File

@ -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<? extends FluentIterable<? extends VolumeAttachment>> 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<? extends VolumeAttachment> 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<? extends VolumeAttachment> 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<Boolean> detachVolumeFromServer(
@PathParam("id") String volumeId,
@PathParam("server_id") String serverId);
}

View File

@ -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<? extends VolumeAttachment> 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();
}
}

View File

@ -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<? extends VolumeApi> volumeApi;
private Optional<? extends VolumeAttachmentApi> 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<VolumeApi>(new Predicate<VolumeApi>() {
@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<VolumeApi>(new Predicate<VolumeApi>() {
@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<? extends VolumeAttachment> 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<VolumeAttachmentApi>(new Predicate<VolumeAttachmentApi>() {
@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<VolumeAttachmentApi>(new Predicate<VolumeAttachmentApi>() {
@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);
}
}
}
}