diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java index 6f5d80d0e0..baf433b5e1 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackAsyncClient.java @@ -34,6 +34,7 @@ import org.jclouds.cloudstack.features.NetworkAsyncClient; import org.jclouds.cloudstack.features.OfferingAsyncClient; import org.jclouds.cloudstack.features.SSHKeyPairAsyncClient; import org.jclouds.cloudstack.features.SecurityGroupAsyncClient; +import org.jclouds.cloudstack.features.SnapshotAsyncClient; import org.jclouds.cloudstack.features.TemplateAsyncClient; import org.jclouds.cloudstack.features.VMGroupAsyncClient; import org.jclouds.cloudstack.features.VirtualMachineAsyncClient; @@ -177,4 +178,10 @@ public interface CloudStackAsyncClient { */ @Delegate VolumeAsyncClient getVolumeClient(); + + /** + * Provides asynchronous access to Snapshots + */ + @Delegate + SnapshotAsyncClient getSnapshotClient(); } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java index 760f36cff8..82741a32aa 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackClient.java @@ -36,6 +36,7 @@ import org.jclouds.cloudstack.features.NetworkClient; import org.jclouds.cloudstack.features.OfferingClient; import org.jclouds.cloudstack.features.SSHKeyPairClient; import org.jclouds.cloudstack.features.SecurityGroupClient; +import org.jclouds.cloudstack.features.SnapshotClient; import org.jclouds.cloudstack.features.TemplateClient; import org.jclouds.cloudstack.features.VMGroupClient; import org.jclouds.cloudstack.features.VirtualMachineClient; @@ -175,9 +176,15 @@ public interface CloudStackClient { @Delegate ISOClient getISOClient(); - /** + /** * Provides synchronous access to Volumes */ @Delegate VolumeClient getVolumeClient(); + + /** + * Provides synchronous access to Snapshots + */ + @Delegate + SnapshotClient getSnapshotClient(); } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java index 8e45950aca..0211c1757a 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackRestClientModule.java @@ -55,6 +55,8 @@ import org.jclouds.cloudstack.features.SSHKeyPairAsyncClient; import org.jclouds.cloudstack.features.SSHKeyPairClient; import org.jclouds.cloudstack.features.SecurityGroupAsyncClient; import org.jclouds.cloudstack.features.SecurityGroupClient; +import org.jclouds.cloudstack.features.SnapshotAsyncClient; +import org.jclouds.cloudstack.features.SnapshotClient; import org.jclouds.cloudstack.features.TemplateAsyncClient; import org.jclouds.cloudstack.features.TemplateClient; import org.jclouds.cloudstack.features.VMGroupAsyncClient; @@ -107,6 +109,7 @@ public class CloudStackRestClientModule extends RestClientModule { private String state; private String storage; private String storageType; - //TODO Change to enum - private String type; + private VolumeType type; private long virtualMachineId; private String vmDisplayName; private String vmName; @@ -182,7 +187,7 @@ public class Volume implements Comparable { return this; } - public Builder type(String type) { + public Builder type(VolumeType type) { this.type = type; return this; } @@ -268,8 +273,7 @@ public class Volume implements Comparable { private String storage; @SerializedName("storagetype") private String storageType; - //TODO Change to enum - private String type; + private VolumeType type; @SerializedName("virtualmachineid") private long virtualMachineId; @SerializedName("vmdisplayname") @@ -288,7 +292,7 @@ public class Volume implements Comparable { String domain, long domainId, String hypervisor, boolean extractable, long jobId, String jobStatus, String name, String serviceOfferingDisplayText, long serviceOfferingId, String serviceOfferingName, long size, long snapshotId, String state, String storage, - String storageType, String type, long virtualMachineId, String vmDisplayName, String vmName, + String storageType, VolumeType type, long virtualMachineId, String vmDisplayName, String vmName, VirtualMachine.State vmState, long zoneId, String zoneName) { this.id = id; this.attached = attached; @@ -415,7 +419,7 @@ public class Volume implements Comparable { return storageType; } - public String getType() { + public VolumeType getType() { return type; } @@ -530,4 +534,38 @@ public class Volume implements Comparable { result = 31 * result + (zoneName != null ? zoneName.hashCode() : 0); return result; } + + public enum VolumeType { + ROOT(0), + DATADISK(1), + UNRECOGNIZED(Integer.MAX_VALUE); + + private int code; + + private static final Map INDEX = Maps.uniqueIndex(ImmutableSet.copyOf(VolumeType.values()), + new Function() { + + @Override + public Integer apply(VolumeType input) { + return input.code; + } + + }); + + VolumeType(int code) { + this.code = code; + } + + @Override + public String toString() { + return name(); + } + + public static VolumeType fromValue(String resourceType) { + Integer code = new Integer(checkNotNull(resourceType, "resourcetype")); + return INDEX.containsKey(code) ? INDEX.get(code) : UNRECOGNIZED; + } + + } + } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeAsyncClient.java index 7b2e1dd7cd..28271b08f6 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeAsyncClient.java @@ -33,8 +33,8 @@ import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.Unwrap; import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; -import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; /** @@ -64,23 +64,41 @@ public interface VolumeAsyncClient { */ @GET @QueryParams(keys = "command", values = "createVolume") - @SelectJson("volume") + @Unwrap @Consumes(MediaType.APPLICATION_JSON) - @ExceptionParser(ReturnNullOnNotFoundOr404.class) ListenableFuture createVolumeFromDiskOfferingInZone(@QueryParam("name") String name, @QueryParam("diskofferingid") long diskOfferingId, @QueryParam("zoneid") long zoneId); /** - * @see VolumeClient#createVolumeWithSnapshot(String, long) + * @see VolumeClient#createVolumeFromSnapshotInZone(String, long, long) */ @GET @QueryParams(keys = "command", values = "createVolume") - @SelectJson("volume") + @Unwrap @Consumes(MediaType.APPLICATION_JSON) - @ExceptionParser(ReturnNullOnNotFoundOr404.class) - ListenableFuture createVolumeWithSnapshot(@QueryParam("name") String name, - @QueryParam("snapshotid") long diskOfferingId); + ListenableFuture createVolumeFromSnapshotInZone(@QueryParam("name") String name, + @QueryParam("snapshotid") long snapshotId, + @QueryParam("zoneid") long zoneId); + + /** + * @see VolumeClient#attachVolume(long, long) + */ + @GET + @QueryParams(keys = "command", values = "attachVolume") + @Unwrap + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture attachVolume(@QueryParam("id") long volumeId, + @QueryParam("virtualmachineid") long virtualMachineId); + + /** + * @see VolumeClient#detachVolume(long) + */ + @GET + @QueryParams(keys = "command", values = "detachVolume") + @Unwrap + @Consumes(MediaType.APPLICATION_JSON) + ListenableFuture detachVolume(@QueryParam("id") long volumeId); /** * @see VolumeClient#deleteVolume(long) diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeClient.java index 960751afd4..2a71a3b605 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/VolumeClient.java @@ -41,8 +41,8 @@ public interface VolumeClient { * * @param name name of the volume * @param diskOfferingId the ID of the disk offering. - * @param zoneId the ID of the availability zone - * @return Volume + * @param zoneId the ID of the availability zone + * @return AsyncCreateResponse job response used to track creation */ AsyncCreateResponse createVolumeFromDiskOfferingInZone(String name, long diskOfferingId, long zoneId); @@ -51,9 +51,10 @@ public interface VolumeClient { * * @param name name of the volume * @param snapshotId Snapshot id to be used while creating the volume - * @return Volume + * @param zoneId the ID of the availability zone + * @return AsyncCreateResponse job response used to track creation */ - AsyncCreateResponse createVolumeWithSnapshot(String name, long snapshotId); + AsyncCreateResponse createVolumeFromSnapshotInZone(String name, long snapshotId, long zoneId); /** * List volumes @@ -69,4 +70,21 @@ public interface VolumeClient { */ void deleteVolume(long id); + /** + * Attaches a disk volume to a virtual machine. + * + * @param volumeId the ID of the disk volume + * @param virtualMachineId the ID of the virtual machine + * @return AsyncCreateResponse job response used to track creation + */ + AsyncCreateResponse attachVolume(long volumeId, long virtualMachineId); + + /** + * Detaches a disk volume to a virtual machine. + * + * @param volumeId the ID of the disk volume + * @return AsyncCreateResponse job response used to track creation + */ + AsyncCreateResponse detachVolume(long volumeId); + } diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/CloudStackAsyncClientTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/CloudStackAsyncClientTest.java index 7042300de1..f622c029a9 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/CloudStackAsyncClientTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/CloudStackAsyncClientTest.java @@ -63,6 +63,7 @@ public class CloudStackAsyncClientTest extends BaseCloudStackAsyncClientTest> createTypeLiteral() { return new TypeLiteral>() { diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VolumeClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VolumeClientLiveTest.java index bed106c2a0..30ff3113db 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VolumeClientLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/VolumeClientLiveTest.java @@ -20,6 +20,7 @@ package org.jclouds.cloudstack.features; import static com.google.common.collect.Iterables.find; import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNotSame; import java.util.Set; @@ -40,6 +41,7 @@ public class VolumeClientLiveTest extends BaseCloudStackClientLiveTest { protected String prefix = System.getProperty("user.name"); private long zoneId; private long diskOfferingId; + private long snapshotId; private Volume volume; public void testListVolumes() { @@ -63,23 +65,101 @@ public class VolumeClientLiveTest extends BaseCloudStackClientLiveTest { assert jobComplete.apply(job.getJobId()); volume = findVolumeWithId(job.getId()); } catch (IllegalStateException e) { - //TODO Retry ? - e.printStackTrace(); + //TODO volume creation failed - retry? } } checkVolume(volume); + //Delete the volume + client.getVolumeClient().deleteVolume(volume.getId()); + } + + public void testCreateVolumeFromDiskofferingInZoneAndAttachVolumeToVirtualMachineAndDetachAndDelete() { + + zoneId = Iterables.getFirst(client.getZoneClient().listZones(), null).getId(); + //Pick some disk offering + diskOfferingId = Iterables.getFirst(client.getOfferingClient().listDiskOfferings(), null).getId(); + + + while (volume == null) { + try { + AsyncCreateResponse job = client.getVolumeClient().createVolumeFromDiskOfferingInZone(prefix + "-jclouds-volume", + diskOfferingId, zoneId); + assert jobComplete.apply(job.getJobId()); + volume = findVolumeWithId(job.getId()); + } catch (IllegalStateException e) { + //TODO volume creation failed - retry? + } + } + + checkVolume(volume); + long virtualMachineId = Iterables.getFirst(client.getVirtualMachineClient().listVirtualMachines(), null).getId(); + //Attach Volume + Volume attachedVolume = null; + while (attachedVolume == null) { + try { + AsyncCreateResponse job = client.getVolumeClient().attachVolume(volume.getId(), virtualMachineId); + assert jobComplete.apply(job.getJobId()); + attachedVolume = findVolumeWithId(volume.getId()); + assert attachedVolume.getVirtualMachineId() == virtualMachineId; + assert attachedVolume.getAttached() != null; + } catch (IllegalStateException e) { + //TODO volume creation failed - retry? + } + } + + //Detach Volume + Volume detachedVolume = null; + while (detachedVolume == null) { + try { + AsyncCreateResponse job = client.getVolumeClient().detachVolume(volume.getId()); + assert jobComplete.apply(job.getJobId()); + detachedVolume = findVolumeWithId(volume.getId()); + checkVolume(detachedVolume); + } catch (IllegalStateException e) { + //TODO volume creation failed - retry? + } + } + + //Cleanup + client.getVolumeClient().deleteVolume(volume.getId()); } - private void checkVolume(Volume volume) { + /* + TODO Uncomment this test after SnapshotClient has test coverage. + public void testCreateVolumeFromSnapshotInZoneAndDeleteVolume() { + + zoneId = Iterables.getFirst(client.getZoneClient().listZones(), null).getId(); + final Set snapshots = client.getSnapshotClient().listSnapshots(); + assertNotNull(snapshots); + assertNotSame(0, snapshots.size() ); + snapshotId = Iterables.getFirst(snapshots, null).getId(); + while (volume == null) { + try { + AsyncCreateResponse job = client.getVolumeClient().createVolumeFromSnapshotInZone(prefix + "-jclouds-volume", + snapshotId, zoneId); + assert jobComplete.apply(job.getJobId()); + volume = findVolumeWithId(job.getId()); + } catch (IllegalStateException e) { + //TODO volume creation failed - retry? + } + } + + checkVolume(volume); + //Delete the volume + client.getVolumeClient().deleteVolume(volume.getId()); + } + */ + + private void checkVolume(final Volume volume) { assertNotNull(volume.getId()); assertNotNull(volume.getName()); + assertNotSame(Volume.VolumeType.UNRECOGNIZED, volume.getType()); } private Volume findVolumeWithId(final long id) { - System.out.println(id); return find(client.getVolumeClient().listVolumes(), new Predicate() { @Override