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); + } +}
