From 6da3f392c8d9a35e757e3491ca2d6ffc74271f00 Mon Sep 17 00:00:00 2001 From: Everett Toews Date: Sun, 11 Nov 2012 22:24:28 -0600 Subject: [PATCH] Added Predicates for handling Volume and Snapshot Status. Improved some comments. --- .../v2_0/extensions/VolumeAttachmentApi.java | 8 +- .../openstack/cinder/v1/domain/Volume.java | 6 +- .../cinder/v1/features/SnapshotApi.java | 2 +- .../cinder/v1/features/VolumeApi.java | 2 +- .../v1/predicates/SnapshotPredicates.java | 111 ++++++++++++++++ .../v1/predicates/VolumePredicates.java | 123 ++++++++++++++++++ .../VolumeAndSnapshotApiLiveTest.java | 17 +-- 7 files changed, 250 insertions(+), 19 deletions(-) create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/SnapshotPredicates.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/VolumePredicates.java 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 index cecaa8da64..b310b51b3c 100644 --- 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 @@ -83,9 +83,11 @@ public interface VolumeAttachmentApi { /** * Detach a Volume from a server. * - * @param serverId The ID of the Server + * Note: Make sure you've unmounted the volume first. Failure to do so could result in failure or data loss. + * * @param volumeId The ID of the Volume + * @param serverId The ID of the Server * @return true if successful */ - boolean detachVolumeFromServer(String serverId, String volumeId); -} \ No newline at end of file + boolean detachVolumeFromServer(String volumeId, String serverId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java index b280240fd3..9075c2fcfb 100644 --- a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java @@ -205,6 +205,10 @@ public class Volume { return this; } } + + public static Volume forId(String volumeId) { + return builder().id(volumeId).build(); + } private final String id; private final Volume.Status status; @@ -315,7 +319,7 @@ public class Volume { public Map getMetadata() { return this.metadata; } - + @Override public int hashCode() { return Objects.hashCode(id, status, size, zone, created, attachments, volumeType, snapshotId, name, description, metadata); diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java index f18dec140a..9b2cc2edbb 100644 --- a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java @@ -40,7 +40,7 @@ public interface SnapshotApi { Snapshot get(String snapshotId); /** - * Creates a new Snapshot + * Creates a new Snapshot. The Volume status must be Available. * * @param volumeId The Volume Id from which to create the Snapshot * @param options See CreateSnapshotOptions diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java index de2b84b059..53cf852abb 100644 --- a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java @@ -52,7 +52,7 @@ public interface VolumeApi { Volume create(int sizeGB, CreateVolumeOptions... options); /** - * Delete a Volume. + * Delete a Volume. The Volume status must be Available or Error. * * @param volumeId Id of the Volume * @return true if successful, false otherwise diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/SnapshotPredicates.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/SnapshotPredicates.java new file mode 100644 index 0000000000..81ba385cd4 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/SnapshotPredicates.java @@ -0,0 +1,111 @@ +/** + * 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.cinder.v1.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.openstack.cinder.v1.domain.Snapshot; +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.domain.Volume.Status; +import org.jclouds.openstack.cinder.v1.features.SnapshotApi; +import org.jclouds.predicates.RetryablePredicate; + +import com.google.common.base.Predicate; + +/** + * Tests to see if snapshot has reached status. This class is most useful when paired with a RetryablePredicate as + * in the code below. This class can be used to block execution until the Snapshot status has reached a desired state. + * This is useful when your Snapshot needs to be 100% ready before you can continue with execution. + * + *
+ * {@code
+ * Snapshot snapshot = snapshotApi.create(volumeId);
+ * RetryablePredicate awaitAvailable = new RetryablePredicate(
+ *    SnapshotPredicates.available(snapshotApi), 600, 10, 10, TimeUnit.SECONDS);
+ * 
+ * if (!awaitAvailable.apply(snapshot.getId())) {
+ *    throw new TimeoutException("Timeout on snapshot: " + snapshot); 
+ * }    
+ * }
+ * 
+ * + * You can also use the static convenience methods as so. + * + *
+ * {@code
+ * Snapshot snapshot = snapshotApi.create(volumeId);
+ * 
+ * if (!SnapshotPredicates.awaitAvailable(snapshotApi).apply(snapshot.getId())) {
+ *    throw new TimeoutException("Timeout on snapshot: " + snapshot);     
+ * }
+ * }
+ * 
+ * + * @author Everett Toews + */ +public class SnapshotPredicates { + /** + * Wait until a Snapshot is Available. + * + * @param snapshotApi The SnapshotApi in the zone where your Snapshot resides. + * @return RetryablePredicate That will check the status every 5 seconds for a maxiumum of 20 minutes. + */ + public static RetryablePredicate awaitAvailable(SnapshotApi snapshotApi) { + StatusUpdatedPredicate statusPredicate = new StatusUpdatedPredicate(snapshotApi, Volume.Status.AVAILABLE); + + return new RetryablePredicate(statusPredicate, 1200, 5, 5, TimeUnit.SECONDS); + } + + public static RetryablePredicate awaitStatus( + SnapshotApi snapshotApi, Volume.Status status, long maxWaitInSec, long periodInSec) { + StatusUpdatedPredicate statusPredicate = new StatusUpdatedPredicate(snapshotApi, status); + + return new RetryablePredicate(statusPredicate, maxWaitInSec, periodInSec, periodInSec, TimeUnit.SECONDS); + } + + private static class StatusUpdatedPredicate implements Predicate { + private SnapshotApi snapshotApi; + private Status status; + + public StatusUpdatedPredicate(SnapshotApi snapshotApi, Volume.Status status) { + this.snapshotApi = checkNotNull(snapshotApi, "snapshotApi must be defined"); + this.status = checkNotNull(status, "status must be defined"); + } + + /** + * @return boolean Return true when the snapshot reaches status, false otherwise + */ + @Override + public boolean apply(Snapshot snapshot) { + checkNotNull(snapshot, "snapshotId must be defined"); + + if (status.equals(snapshot.getStatus())) { + return true; + } + else { + Snapshot snapshotUpdated = snapshotApi.get(snapshot.getId()); + checkNotNull(snapshotUpdated, "Snapshot %s not found.", snapshot.getId()); + + return status.equals(snapshotUpdated.getStatus()); + } + } + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/VolumePredicates.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/VolumePredicates.java new file mode 100644 index 0000000000..e6eaf82cc8 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/predicates/VolumePredicates.java @@ -0,0 +1,123 @@ +/** + * 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.cinder.v1.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.domain.Volume.Status; +import org.jclouds.openstack.cinder.v1.features.VolumeApi; +import org.jclouds.predicates.RetryablePredicate; + +import com.google.common.base.Predicate; + +/** + * Tests to see if volume has reached status. This class is most useful when paired with a RetryablePredicate as + * in the code below. This class can be used to block execution until the Volume status has reached a desired state. + * This is useful when your Volume needs to be 100% ready before you can continue with execution. + * + *
+ * {@code
+ * Volume volume = volumeApi.create(100);
+ * 
+ * RetryablePredicate awaitAvailable = new RetryablePredicate(
+ *    VolumePredicates.available(volumeApi), 600, 10, 10, TimeUnit.SECONDS);
+ * 
+ * if (!awaitAvailable.apply(volume.getId())) {
+ *    throw new TimeoutException("Timeout on volume: " + volume); 
+ * }    
+ * }
+ * 
+ * + * You can also use the static convenience methods as so. + * + *
+ * {@code
+ * Volume volume = volumeApi.create(100);
+ * 
+ * if (!VolumePredicates.awaitAvailable(volumeApi).apply(volume.getId())) {
+ *    throw new TimeoutException("Timeout on volume: " + volume);     
+ * }
+ * }
+ * 
+ * + * @author Everett Toews + */ +public class VolumePredicates { + /** + * Wait until a Volume is Available. + * + * @param volumeApi The VolumeApi in the zone where your Volume resides. + * @return RetryablePredicate That will check the status every 5 seconds for a maxiumum of 10 minutes. + */ + public static RetryablePredicate awaitAvailable(VolumeApi volumeApi) { + StatusUpdatedPredicate statusPredicate = new StatusUpdatedPredicate(volumeApi, Volume.Status.AVAILABLE); + + return new RetryablePredicate(statusPredicate, 600, 5, 5, TimeUnit.SECONDS); + } + + /** + * Wait until a Volume is In Use. + * + * @param volumeApi The VolumeApi in the zone where your Volume resides. + * @return RetryablePredicate That will check the status every 5 seconds for a maxiumum of 10 minutes. + */ + public static RetryablePredicate awaitInUse(VolumeApi volumeApi) { + StatusUpdatedPredicate statusPredicate = new StatusUpdatedPredicate(volumeApi, Volume.Status.IN_USE); + + return new RetryablePredicate(statusPredicate, 600, 5, 5, TimeUnit.SECONDS); + } + + public static RetryablePredicate awaitStatus( + VolumeApi volumeApi, Volume.Status status, long maxWaitInSec, long periodInSec) { + StatusUpdatedPredicate statusPredicate = new StatusUpdatedPredicate(volumeApi, status); + + return new RetryablePredicate(statusPredicate, maxWaitInSec, periodInSec, periodInSec, TimeUnit.SECONDS); + } + + private static class StatusUpdatedPredicate implements Predicate { + private VolumeApi volumeApi; + private Status status; + + public StatusUpdatedPredicate(VolumeApi volumeApi, Volume.Status status) { + this.volumeApi = checkNotNull(volumeApi, "volumeApi must be defined"); + this.status = checkNotNull(status, "status must be defined"); + } + + /** + * @return boolean Return true when the volume reaches status, false otherwise + */ + @Override + public boolean apply(Volume volume) { + checkNotNull(volume, "volume must be defined"); + + if (status.equals(volume.getStatus())) { + return true; + } + else { + Volume volumeUpdated = volumeApi.get(volume.getId()); + checkNotNull(volumeUpdated, "Volume %s not found.", volume.getId()); + + return status.equals(volumeUpdated.getStatus()); + } + } + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java index 9db5c04bce..84979cdead 100644 --- a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java @@ -29,6 +29,8 @@ import org.jclouds.openstack.cinder.v1.domain.Volume; import org.jclouds.openstack.cinder.v1.internal.BaseCinderApiLiveTest; import org.jclouds.openstack.cinder.v1.options.CreateSnapshotOptions; import org.jclouds.openstack.cinder.v1.options.CreateVolumeOptions; +import org.jclouds.openstack.cinder.v1.predicates.SnapshotPredicates; +import org.jclouds.openstack.cinder.v1.predicates.VolumePredicates; import org.jclouds.predicates.RetryablePredicate; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -97,12 +99,7 @@ public class VolumeAndSnapshotApiLiveTest extends BaseCinderApiLiveTest { .availabilityZone(zone); testVolume = volumeApi.create(100, options); - assertTrue(new RetryablePredicate(new Predicate() { - @Override - public boolean apply(VolumeApi volumeApi) { - return volumeApi.get(testVolume.getId()).getStatus() == Volume.Status.AVAILABLE; - } - }, 600 * 1000L).apply(volumeApi)); + assertTrue(VolumePredicates.awaitAvailable(volumeApi).apply(testVolume)); } @Test(dependsOnMethods = "testCreateVolume") @@ -152,17 +149,11 @@ public class VolumeAndSnapshotApiLiveTest extends BaseCinderApiLiveTest { "jclouds live test snapshot").force()); assertNotNull(testSnapshot); assertNotNull(testSnapshot.getId()); - final String snapshotId = testSnapshot.getId(); assertNotNull(testSnapshot.getStatus()); assertTrue(testSnapshot.getSize() > -1); assertNotNull(testSnapshot.getCreated()); - assertTrue(new RetryablePredicate(new Predicate() { - @Override - public boolean apply(VolumeApi volumeApi) { - return snapshotApi.get(snapshotId).getStatus() == Volume.Status.AVAILABLE; - } - }, 1200 * 1000L).apply(volumeApi)); + assertTrue(SnapshotPredicates.awaitAvailable(snapshotApi).apply(testSnapshot)); } @Test(dependsOnMethods = "testCreateSnapshot")