JCLOUDS-1024: ImageExtension can take snapshots of stopped droplets

Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/200e0e12
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/200e0e12
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/200e0e12

Branch: refs/heads/master
Commit: 200e0e12ba1c8eac60d9022d26c2833aba1f57dc
Parents: 6254526
Author: Ignasi Barrera <[email protected]>
Authored: Thu Oct 22 11:14:09 2015 +0200
Committer: Ignasi Barrera <[email protected]>
Committed: Thu Oct 22 15:53:06 2015 +0200

----------------------------------------------------------------------
 .../digitalocean2/DigitalOcean2ApiMetadata.java |  8 +++
 ...igitalOcean2ComputeServiceContextModule.java | 29 ++++++++--
 .../extensions/DigitalOcean2ImageExtension.java | 12 ++--
 .../config/DropletInStatusPredicateTest.java    | 58 ++++++++++++++++++++
 4 files changed, 96 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/200e0e12/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
 
b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
index 7e9861d..25b42c7 100644
--- 
a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
+++ 
b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
@@ -20,6 +20,9 @@ import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
 import static 
org.jclouds.compute.config.ComputeServiceProperties.POLL_INITIAL_PERIOD;
 import static 
org.jclouds.compute.config.ComputeServiceProperties.POLL_MAX_PERIOD;
 import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
 import static 
org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS;
 import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
 import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
@@ -68,6 +71,11 @@ public class DigitalOcean2ApiMetadata extends 
BaseHttpApiMetadata<DigitalOcean2A
       properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true");
       properties.put(POLL_INITIAL_PERIOD, 5000);
       properties.put(POLL_MAX_PERIOD, 20000);
+      // Node operations in DigitalOcean can be quite slow. Use a 5 minutes
+      // timeout by default
+      properties.put(TIMEOUT_NODE_RUNNING, 300000);
+      properties.put(TIMEOUT_NODE_SUSPENDED, 300000);
+      properties.put(TIMEOUT_NODE_TERMINATED, 300000);
       return properties;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/200e0e12/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java
 
b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java
index c2ed858..7159634 100644
--- 
a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java
+++ 
b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java
@@ -108,16 +108,16 @@ public class DigitalOcean2ComputeServiceContextModule 
extends
    @Named(TIMEOUT_NODE_RUNNING)
    protected Predicate<Integer> provideDropletRunningPredicate(final 
DigitalOcean2Api api, Timeouts timeouts,
          PollPeriod pollPeriod) {
-      return retry(new ActionDonePredicate(api), timeouts.nodeRunning, 
pollPeriod.pollInitialPeriod,
-            pollPeriod.pollMaxPeriod);
+      return retry(new DropletInStatusPredicate(api, Droplet.Status.ACTIVE), 
timeouts.nodeRunning,
+            pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
    }
 
    @Provides
    @Named(TIMEOUT_NODE_SUSPENDED)
    protected Predicate<Integer> provideDropletSuspendedPredicate(final 
DigitalOcean2Api api, Timeouts timeouts,
          PollPeriod pollPeriod) {
-      return retry(new ActionDonePredicate(api), timeouts.nodeSuspended, 
pollPeriod.pollInitialPeriod,
-            pollPeriod.pollMaxPeriod);
+      return retry(new DropletInStatusPredicate(api, Droplet.Status.OFF), 
timeouts.nodeSuspended,
+            pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
    }
 
    @Provides
@@ -188,11 +188,30 @@ public class DigitalOcean2ComputeServiceContextModule 
extends
 
       @Override
       public boolean apply(Integer input) {
-         checkNotNull(input, "droplet");
+         checkNotNull(input, "droplet id");
          Droplet droplet = api.dropletApi().get(input);
          return droplet == null;
       }
    }
+   
+   @VisibleForTesting
+   static class DropletInStatusPredicate implements Predicate<Integer> {
+
+      private final DigitalOcean2Api api;
+      private final Droplet.Status status;
+
+      public DropletInStatusPredicate(DigitalOcean2Api api, Droplet.Status 
status) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.status = checkNotNull(status, "status must not be null");
+      }
+
+      @Override
+      public boolean apply(Integer input) {
+         checkNotNull(input, "droplet id");
+         Droplet droplet = api.dropletApi().get(input);
+         return droplet != null && status == droplet.status();
+      }
+   }
 
    @VisibleForTesting
    static class RegionAvailablePredicate implements Predicate<Region> {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/200e0e12/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java
 
b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java
index 77ccd2a..524e4d1 100644
--- 
a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java
+++ 
b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java
@@ -86,14 +86,14 @@ public class DigitalOcean2ImageExtension implements 
ImageExtension {
    public ListenableFuture<Image> createImage(ImageTemplate template) {
       checkState(template instanceof CloneImageTemplate, "DigitalOcean only 
supports creating images through cloning.");
       final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
+      int dropletId = Integer.parseInt(cloneTemplate.getSourceNodeId());
 
       // Droplet needs to be stopped
-      int dropletId = Integer.parseInt(cloneTemplate.getSourceNodeId());
-      Action powerOffEvent = api.dropletApi().powerOff(dropletId);
-      checkState(nodeStoppedPredicate.apply(powerOffEvent.id()), "node was not 
powered off in the configured timeout");
-      
       Droplet droplet = api.dropletApi().get(dropletId);
-      checkState(droplet.status() == Status.OFF, "node was not powered off in 
the configured timeout");
+      if (droplet.status() != Status.OFF) {
+         api.dropletApi().powerOff(dropletId);
+         checkState(nodeStoppedPredicate.apply(dropletId), "node was not 
powered off in the configured timeout");
+      }
 
       Action snapshotEvent = 
api.dropletApi().snapshot(Integer.parseInt(cloneTemplate.getSourceNodeId()),
             cloneTemplate.getName());
@@ -103,7 +103,7 @@ public class DigitalOcean2ImageExtension implements 
ImageExtension {
       // Until the process completes we don't have enough information to build 
an image to return
       checkState(imageAvailablePredicate.apply(snapshotEvent.id()),
             "snapshot failed to complete in the configured timeout");
-
+      
       org.jclouds.digitalocean2.domain.Image snapshot = 
api.imageApi().list().concat().firstMatch(
             new Predicate<org.jclouds.digitalocean2.domain.Image>() {
                @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/200e0e12/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletInStatusPredicateTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletInStatusPredicateTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletInStatusPredicateTest.java
new file mode 100644
index 0000000..4445907
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletInStatusPredicateTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.digitalocean2.compute.config;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Date;
+
+import org.easymock.EasyMock;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import 
org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule.DropletInStatusPredicate;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.Droplet.Status;
+import org.jclouds.digitalocean2.features.DropletApi;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+@Test(groups = "unit", testName = "DropletInStatusPredicateTest")
+public class DropletInStatusPredicateTest {
+
+   public void testDropletSuspended() {
+      DropletApi dropletApi = EasyMock.createMock(DropletApi.class);
+      DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
+
+      expect(dropletApi.get(1)).andReturn(mockDroplet(Status.ACTIVE));
+      expect(dropletApi.get(2)).andReturn(mockDroplet(Status.OFF));
+      expect(api.dropletApi()).andReturn(dropletApi).times(2);
+      replay(dropletApi, api);
+
+      DropletInStatusPredicate predicate = new DropletInStatusPredicate(api, 
Status.OFF);
+      assertFalse(predicate.apply(1));
+      assertTrue(predicate.apply(2));
+   }
+
+   private static Droplet mockDroplet(Status status) {
+      return Droplet.create(1, "foo", 1024, 1, 20, false, new Date(), status,
+            ImmutableList.<Integer> of(), ImmutableList.<Integer> of(), 
ImmutableList.<String> of(), null, null, null,
+            "", null, null);
+   }
+}

Reply via email to