Moved the Volume Attachment stuff into its own API to make it totally clear to users that this is the API to use for attaching volumes.

This commit is contained in:
Everett Toews 2012-11-06 15:49:29 -06:00
parent 36e7c1664a
commit 2682759b49
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);
}
}
}
}