You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2016/01/21 01:03:25 UTC
[01/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean
v2 API
Repository: jclouds
Updated Branches:
refs/heads/master 52dc1a3cc -> edacad2b6
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/regions-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/regions-last.json b/providers/digitalocean2/src/test/resources/regions-last.json
new file mode 100644
index 0000000..d332ed8
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/regions-last.json
@@ -0,0 +1,128 @@
+{
+ "links" : {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/regions?page=1&per_page=5",
+ "prev" : "https://api.digitalocean.com/v2/regions?page=1&per_page=5"
+ }
+ },
+ "meta" : {
+ "total" : 10
+ },
+ "regions" : [
+ {
+ "available" : true,
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "name" : "Singapore 1",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "sgp1"
+ },
+ {
+ "available" : true,
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "name" : "London 1",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "lon1"
+ },
+ {
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "name" : "New York 3",
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "available" : true,
+ "slug" : "nyc3"
+ },
+ {
+ "available" : true,
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "name" : "Amsterdam 3",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "ams3"
+ },
+ {
+ "available" : true,
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "name" : "Frankfurt 1",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "fra1"
+ }
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/shutdown.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/shutdown.json b/providers/digitalocean2/src/test/resources/shutdown.json
new file mode 100644
index 0000000..9c03fcf
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/shutdown.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "in-progress",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "shutdown"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/sizes-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/sizes-first.json b/providers/digitalocean2/src/test/resources/sizes-first.json
new file mode 100644
index 0000000..1ac9595
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/sizes-first.json
@@ -0,0 +1,123 @@
+{
+ "links" : {
+ "pages" : {
+ "next" : "https://api.digitalocean.com/v2/sizes?page=2&per_page=5",
+ "last" : "https://api.digitalocean.com/v2/sizes?page=2&per_page=5"
+ }
+ },
+ "meta" : {
+ "total" : 9
+ },
+ "sizes" : [
+ {
+ "vcpus" : 1,
+ "disk" : 20,
+ "price_monthly" : 5,
+ "regions" : [
+ "nyc1",
+ "sgp1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "ams2",
+ "fra1"
+ ],
+ "price_hourly" : 0.00744,
+ "transfer" : 1,
+ "available" : true,
+ "slug" : "512mb",
+ "memory" : 512
+ },
+ {
+ "vcpus" : 1,
+ "disk" : 30,
+ "price_monthly" : 10,
+ "regions" : [
+ "nyc2",
+ "sgp1",
+ "ams1",
+ "sfo1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "nyc1",
+ "ams2",
+ "fra1"
+ ],
+ "price_hourly" : 0.01488,
+ "transfer" : 2,
+ "slug" : "1gb",
+ "available" : true,
+ "memory" : 1024
+ },
+ {
+ "price_hourly" : 0.02976,
+ "regions" : [
+ "nyc2",
+ "sfo1",
+ "ams1",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "nyc1",
+ "ams2",
+ "fra1"
+ ],
+ "transfer" : 3,
+ "available" : true,
+ "slug" : "2gb",
+ "memory" : 2048,
+ "vcpus" : 2,
+ "disk" : 40,
+ "price_monthly" : 20
+ },
+ {
+ "transfer" : 4,
+ "price_hourly" : 0.05952,
+ "regions" : [
+ "nyc2",
+ "ams1",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "nyc1",
+ "ams2",
+ "sfo1",
+ "fra1"
+ ],
+ "memory" : 4096,
+ "slug" : "4gb",
+ "available" : true,
+ "disk" : 60,
+ "vcpus" : 2,
+ "price_monthly" : 40
+ },
+ {
+ "available" : true,
+ "slug" : "8gb",
+ "memory" : 8192,
+ "regions" : [
+ "nyc2",
+ "sgp1",
+ "ams1",
+ "nyc1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "ams2",
+ "sfo1",
+ "fra1"
+ ],
+ "price_hourly" : 0.11905,
+ "transfer" : 5,
+ "price_monthly" : 80,
+ "vcpus" : 4,
+ "disk" : 80
+ }
+ ]
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/sizes-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/sizes-last.json b/providers/digitalocean2/src/test/resources/sizes-last.json
new file mode 100644
index 0000000..c5b47f7
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/sizes-last.json
@@ -0,0 +1,98 @@
+{
+ "meta" : {
+ "total" : 9
+ },
+ "links" : {
+ "pages" : {
+ "prev" : "https://api.digitalocean.com/v2/sizes?page=1&per_page=5",
+ "first" : "https://api.digitalocean.com/v2/sizes?page=1&per_page=5"
+ }
+ },
+ "sizes" : [
+ {
+ "regions" : [
+ "sgp1",
+ "nyc1",
+ "nyc3",
+ "lon1",
+ "nyc2",
+ "ams1",
+ "ams3",
+ "ams2",
+ "sfo1",
+ "fra1"
+ ],
+ "transfer" : 6,
+ "price_monthly" : 160,
+ "available" : true,
+ "vcpus" : 8,
+ "disk" : 160,
+ "slug" : "16gb",
+ "memory" : 16384,
+ "price_hourly" : 0.2381
+ },
+ {
+ "price_hourly" : 0.47619,
+ "price_monthly" : 320,
+ "available" : true,
+ "transfer" : 7,
+ "regions" : [
+ "nyc2",
+ "sgp1",
+ "nyc1",
+ "lon1",
+ "ams3",
+ "nyc3",
+ "ams2",
+ "sfo1",
+ "fra1"
+ ],
+ "memory" : 32768,
+ "slug" : "32gb",
+ "disk" : 320,
+ "vcpus" : 12
+ },
+ {
+ "price_monthly" : 480,
+ "available" : true,
+ "transfer" : 8,
+ "regions" : [
+ "sgp1",
+ "nyc1",
+ "lon1",
+ "nyc2",
+ "ams3",
+ "nyc3",
+ "ams2",
+ "sfo1",
+ "fra1"
+ ],
+ "memory" : 49152,
+ "slug" : "48gb",
+ "disk" : 480,
+ "vcpus" : 16,
+ "price_hourly" : 0.71429
+ },
+ {
+ "price_hourly" : 0.95238,
+ "price_monthly" : 640,
+ "available" : true,
+ "transfer" : 9,
+ "regions" : [
+ "sgp1",
+ "nyc1",
+ "nyc2",
+ "lon1",
+ "ams3",
+ "nyc3",
+ "ams2",
+ "sfo1",
+ "fra1"
+ ],
+ "memory" : 65536,
+ "slug" : "64gb",
+ "disk" : 640,
+ "vcpus" : 20
+ }
+ ]
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/snapshot.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/snapshot.json b/providers/digitalocean2/src/test/resources/snapshot.json
new file mode 100644
index 0000000..193d9da
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/snapshot.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "in-progress",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "snapshot"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/snapshots-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/snapshots-first.json b/providers/digitalocean2/src/test/resources/snapshots-first.json
new file mode 100644
index 0000000..6befa29
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/snapshots-first.json
@@ -0,0 +1,27 @@
+{
+ "snapshots": [
+ {
+ "id": 7938206,
+ "name": "nginx-fresh",
+ "distribution": "Ubuntu",
+ "slug": null,
+ "public": false,
+ "regions": [
+ "nyc3",
+ "nyc3"
+ ],
+ "created_at": "2014-11-14T16:37:34Z",
+ "type": "snapshot",
+ "min_disk_size": 20
+ }
+ ],
+ "links": {
+ "pages" : {
+ "last" : "https://api.digitalocean.com/v2/droplets/3067509/snapshots?page=2",
+ "next" : "https://api.digitalocean.com/v2/droplets/3067509/snapshots?page=2"
+ }
+ },
+ "meta": {
+ "total": 2
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/snapshots-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/snapshots-last.json b/providers/digitalocean2/src/test/resources/snapshots-last.json
new file mode 100644
index 0000000..70e4162
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/snapshots-last.json
@@ -0,0 +1,27 @@
+{
+ "snapshots": [
+ {
+ "id": 7938206,
+ "name": "nginx-fresh",
+ "distribution": "Ubuntu",
+ "slug": null,
+ "public": false,
+ "regions": [
+ "nyc3",
+ "nyc3"
+ ],
+ "created_at": "2014-11-14T16:37:34Z",
+ "type": "snapshot",
+ "min_disk_size": 20
+ }
+ ],
+ "links": {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/droplets/3067509/snapshots?page=1",
+ "prev" : "https://api.digitalocean.com/v2/droplets/3067509/snapshots?page=1"
+ }
+ },
+ "meta": {
+ "total": 2
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/ssh-dsa.pub
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/ssh-dsa.pub b/providers/digitalocean2/src/test/resources/ssh-dsa.pub
new file mode 100644
index 0000000..3dc78e4
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/ssh-dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAK5uLwicCrFEpaVKBzkWxC7RQn+smg5ZQb5keh9RQKo8AszFTol5npgUAr0JWmqKIHv7nof0HndO86x9iIqNjq3vrz9CIVcFfZM7poKBJZ27Hv3v0fmSKfAc6eGdx8eM9UkZe1gzcLXK8UP2HaeY1Y4LlaHXS5tPi/dXooFVgiA7AAAAFQCQl6LZo/VYB9VgPEZzOmsmQevnswAAAIBCNKGsVP5eZ+IJklXheUyzyuL75i04OOtEGW6MO5TymKMwTZlU9r4ukuwxty+T9Ot2LqlNRnLSPQUjb0vplasZ8Ix45JOpRbuSvPovryn7rvS7//klu9hIkFAAQ/AZfGTw+696EjFBg4F5tN6MGMA6KrTQVLXeuYcZeRXwE5t5lwAAAIEAl2xYh098bozJUANQ82DiZznjHc5FW76Xm1apEqsZtVRFuh3V9nc7QNcBekhmHp5Z0sHthXCm1XqnFbkRCdFlX02NpgtNs7OcKpaJP47N8C+C/Yrf8qK/Wt3fExrL2ZLX5XD2tiotugSkwZJMW5Bv0mtjrNt0Q7P45rZjNNTag2c=
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/ssh-ecdsa.pub
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/ssh-ecdsa.pub b/providers/digitalocean2/src/test/resources/ssh-ecdsa.pub
new file mode 100644
index 0000000..f04a852
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/ssh-ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACeO2+Ci8Aw76SDF1FJyrwAp2fUhNeIgkP7G2x0SvF/yvZarOkZbCg1MAav3JobuqYZEA4GbLnoT2UK9OBAenucTgHXhEX91mQaFWe3pKqroealb7Vc059za+h6b6IfzONNhyXGpCVYlh6tUdq8y1nhGjMzIsUrma+hA1+ERJGFfGDLgA==
[10/19] jclouds git commit: JCLOUDS-1025: Add support for metadata
and tags in the ComputeService
Posted by na...@apache.org.
JCLOUDS-1025: Add support for metadata and tags in the ComputeService
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/4596471b
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/4596471b
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/4596471b
Branch: refs/heads/master
Commit: 4596471bb258c391af06b7bde6f017af02e965db
Parents: 200e0e1
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri Oct 23 00:18:45 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri Oct 23 00:18:45 2015 +0200
----------------------------------------------------------------------
.../DigitalOcean2ComputeServiceAdapter.java | 21 ++++++++++++++++-
.../compute/functions/RegionToLocation.java | 2 ++
.../domain/options/CreateDropletOptions.java | 24 ++++++++++++++++++++
.../DigitalOcean2ComputeServiceLiveTest.java | 4 ++--
.../compute/functions/RegionToLocationTest.java | 10 +++++---
5 files changed, 55 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4596471b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
index f1f5919..43b1585 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
@@ -27,7 +27,10 @@ import static com.google.common.collect.Sets.newHashSet;
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.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
@@ -48,6 +51,7 @@ import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
import org.jclouds.domain.LoginCredentials;
+import org.jclouds.json.Json;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
@@ -67,15 +71,18 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
private final Predicate<Integer> nodeRunningPredicate;
private final Predicate<Integer> nodeStoppedPredicate;
private final Predicate<Integer> nodeTerminatedPredicate;
+ private final Json json;
@Inject DigitalOcean2ComputeServiceAdapter(DigitalOcean2Api api,
@Named(TIMEOUT_NODE_RUNNING) Predicate<Integer> nodeRunningPredicate,
@Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
- @Named(TIMEOUT_NODE_TERMINATED) Predicate<Integer> nodeTerminatedPredicate) {
+ @Named(TIMEOUT_NODE_TERMINATED) Predicate<Integer> nodeTerminatedPredicate,
+ Json json) {
this.api = api;
this.nodeRunningPredicate = nodeRunningPredicate;
this.nodeStoppedPredicate = nodeStoppedPredicate;
this.nodeTerminatedPredicate = nodeTerminatedPredicate;
+ this.json = json;
}
@Override
@@ -91,6 +98,18 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
options.addSshKeyIds(templateOptions.getSshKeyIds());
}
+ Map<String, String> metadataAndTags = metadataAndTagsAsCommaDelimitedValue(templateOptions);
+ if (!metadataAndTags.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ List<String> regionFeatures = (List<String>) template.getLocation().getMetadata().get("features");
+ if (regionFeatures.contains("metadata")) {
+ options.userData(json.toJson(metadataAndTags));
+ } else {
+ logger.debug(">> region %s does not support metadata, ignoring provided user data", template.getLocation()
+ .getId());
+ }
+ }
+
DropletCreate dropletCreated = api.dropletApi().create(name,
template.getLocation().getId(),
template.getHardware().getProviderId(),
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4596471b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
index 4adf240..adde1b7 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
@@ -29,6 +29,7 @@ import org.jclouds.domain.LocationScope;
import org.jclouds.location.suppliers.all.JustProvider;
import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
/**
@@ -52,6 +53,7 @@ public class RegionToLocation implements Function<Region, Location> {
builder.scope(LocationScope.REGION);
builder.parent(getOnlyElement(justProvider.get()));
builder.iso3166Codes(ImmutableSet.<String> of());
+ builder.metadata(ImmutableMap.<String, Object> of("available", input.available(), "features", input.features()));
return builder.build();
}
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4596471b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
index b20fc96..91fb090 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
@@ -104,6 +104,14 @@ public class CreateDropletOptions implements MapBinder {
return backupsEnabled;
}
+ public boolean isIpv6Enabled() {
+ return ipv6Enabled;
+ }
+
+ public String getUserData() {
+ return userData;
+ }
+
public static Builder builder() {
return new Builder();
}
@@ -148,6 +156,22 @@ public class CreateDropletOptions implements MapBinder {
return this;
}
+ /**
+ * Sets the user data for the droplet.
+ */
+ public Builder userData(String userData) {
+ this.userData = userData;
+ return this;
+ }
+
+ /**
+ * Enables/disables IPv6 for the droplet.
+ */
+ public Builder ipv6Enabled(boolean ipv6Enabled) {
+ this.ipv6Enabled = ipv6Enabled;
+ return this;
+ }
+
public CreateDropletOptions build() {
return new CreateDropletOptions(sshKeyIds.build(), backupsEnabled, ipv6Enabled, privateNetworking, userData);
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4596471b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
index b1dcc1b..b8fbbe7 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
@@ -47,12 +47,12 @@ public class DigitalOcean2ComputeServiceLiveTest extends BaseComputeServiceLiveT
@Override
protected void checkTagsInNodeEquals(NodeMetadata node, ImmutableSet<String> tags) {
- // DigitalOcean does not support tags
+ // We encode the tags in the user data but the DigitalOcean API does not return it
}
@Override
protected void checkUserMetadataContains(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
- // DigitalOcean does not support user metadata
+ // The DigitalOcean API does not return the user data
}
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4596471b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
index 879091b..7e6ecea 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
@@ -42,11 +42,15 @@ public class RegionToLocationTest {
JustProvider locationsSupplier = new JustProvider(metadata.getId(), Suppliers.<URI> ofInstance(URI
.create(metadata.getEndpoint())), ImmutableSet.<String> of());
- Region region = Region.create("reg1", "Region1", ImmutableList.<String> of(), true, ImmutableList.<String> of());
+ Region region = Region.create("reg1", "Region1", ImmutableList.<String> of(), true,
+ ImmutableList.<String> of("virtio", "metadata"));
Location expected = new LocationBuilder().id("reg1").description("reg1/Region 1")
.parent(getOnlyElement(locationsSupplier.get())).scope(LocationScope.REGION).build();
- RegionToLocation function = new RegionToLocation(locationsSupplier);
- assertEquals(function.apply(region), expected);
+ Location location = new RegionToLocation(locationsSupplier).apply(region);
+
+ assertEquals(location, expected);
+ assertEquals(location.getMetadata().get("available"), true);
+ assertEquals(location.getMetadata().get("features"), ImmutableList.of("virtio", "metadata"));
}
}
[14/19] jclouds git commit: JCLOUDS-1033: Don't fail if no private
key has been provided in DigitalOcean
Posted by na...@apache.org.
JCLOUDS-1033: Don't fail if no private key has been provided in DigitalOcean
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/575d39e7
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/575d39e7
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/575d39e7
Branch: refs/heads/master
Commit: 575d39e7c5c9974ecbe080a09a2787d08534ac3a
Parents: 3fbd399
Author: Ignasi Barrera <na...@apache.org>
Authored: Mon Nov 2 13:03:55 2015 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Mon Nov 16 22:58:29 2015 +0100
----------------------------------------------------------------------
.../compute/strategy/CreateKeyPairsThenCreateNodes.java | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/575d39e7/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
index 3e4aae3..bc304b7 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
@@ -16,7 +16,6 @@
*/
package org.jclouds.digitalocean2.compute.strategy;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.security.PublicKey;
@@ -101,11 +100,11 @@ public class CreateKeyPairsThenCreateNodes extends CreateNodesWithGroupEncodedIn
generateKeyPairAndAddKeyToSet(options, generatedSshKeyIds, group);
}
- // If there is a script to run in the node, make sure a private key has been configured so jclouds will be able to
- // access the node
- if (options.getRunScript() != null) {
- checkArgument(!Strings.isNullOrEmpty(options.getLoginPrivateKey()),
- "no private key configured for: %s; please use options.overrideLoginPrivateKey(rsa_private_text)", group);
+ // If there is a script to run in the node, make sure a private key has
+ // been configured so jclouds will be able to access the node
+ if (options.getRunScript() != null && Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+ logger.warn(">> A runScript has been configured but no SSH key has been provided."
+ + " Authentication will delegate to the ssh-agent");
}
// If there is a key configured, then make sure there is a key pair for it
[19/19] jclouds git commit: Amend poms after promoting DigitalOcean v2
Posted by na...@apache.org.
Amend poms after promoting DigitalOcean v2
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/edacad2b
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/edacad2b
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/edacad2b
Branch: refs/heads/master
Commit: edacad2b6e01ff94cdf4c5733f847cd9374110b9
Parents: 886aa15
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Jan 21 00:08:42 2016 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Jan 21 01:02:58 2016 +0100
----------------------------------------------------------------------
allcompute/pom.xml | 5 +
project/pom.xml | 1 +
providers/digitalocean2/pom.xml | 263 ++++++++++++++++++-----------------
providers/pom.xml | 1 +
4 files changed, 142 insertions(+), 128 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/edacad2b/allcompute/pom.xml
----------------------------------------------------------------------
diff --git a/allcompute/pom.xml b/allcompute/pom.xml
index 04edaa6..cf6b4e0 100644
--- a/allcompute/pom.xml
+++ b/allcompute/pom.xml
@@ -123,5 +123,10 @@
<artifactId>google-compute-engine</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.provider</groupId>
+ <artifactId>digitalocean2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
http://git-wip-us.apache.org/repos/asf/jclouds/blob/edacad2b/project/pom.xml
----------------------------------------------------------------------
diff --git a/project/pom.xml b/project/pom.xml
index ed38a8f..7909e93 100644
--- a/project/pom.xml
+++ b/project/pom.xml
@@ -490,6 +490,7 @@
<!-- SSH keys -->
<exclude>**/test</exclude>
<exclude>**/test.pub</exclude>
+ <exclude>**/src/test/resources/**/ssh-*.pub</exclude>
<!-- temporary files or those generated by IDE or SCM -->
<exclude>**/target/**</exclude>
http://git-wip-us.apache.org/repos/asf/jclouds/blob/edacad2b/providers/digitalocean2/pom.xml
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/pom.xml b/providers/digitalocean2/pom.xml
index 5b211d4..c1dd83d 100644
--- a/providers/digitalocean2/pom.xml
+++ b/providers/digitalocean2/pom.xml
@@ -18,135 +18,142 @@
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.apache.jclouds.labs</groupId>
- <artifactId>jclouds-labs</artifactId>
- <version>2.0.0-SNAPSHOT</version>
- </parent>
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-project</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ <relativePath>../../project/pom.xml</relativePath>
+ </parent>
- <groupId>org.apache.jclouds.labs</groupId>
- <artifactId>digitalocean2</artifactId>
- <name>jclouds DigitalOcean v2 API Provider</name>
- <description>jclouds provider for Digital Ocean v2 Compute API</description>
+ <groupId>org.apache.jclouds.provider</groupId>
+ <artifactId>digitalocean2</artifactId>
+ <name>jclouds DigitalOcean v2 API Provider</name>
+ <description>jclouds provider for Digital Ocean v2 Compute API</description>
- <properties>
- <test.digitalocean2.endpoint>https://api.digitalocean.com/v2/</test.digitalocean2.endpoint>
- <test.digitalocean2.api-version>2</test.digitalocean2.api-version>
- <test.digitalocean2.identity>FIXME</test.digitalocean2.identity>
- <test.digitalocean2.credential>FIXME</test.digitalocean2.credential>
- <test.digitalocean2.template>osFamily=UBUNTU,os64Bit=true</test.digitalocean2.template>
- </properties>
+ <properties>
+ <test.digitalocean2.endpoint>https://api.digitalocean.com/v2/</test.digitalocean2.endpoint>
+ <test.digitalocean2.api-version>2</test.digitalocean2.api-version>
+ <test.digitalocean2.identity>FIXME</test.digitalocean2.identity>
+ <test.digitalocean2.credential>FIXME</test.digitalocean2.credential>
+ <test.digitalocean2.template>osFamily=UBUNTU,os64Bit=true</test.digitalocean2.template>
+ <jclouds.osgi.export>org.jclouds.digitalocean2*;version="${project.version}"</jclouds.osgi.export>
+ <jclouds.osgi.import>
+ org.jclouds.rest.internal;version="${project.version}",
+ org.jclouds*;version="${project.version}",
+ *
+ </jclouds.osgi.import>
+ </properties>
- <dependencies>
- <dependency>
- <groupId>org.apache.jclouds</groupId>
- <artifactId>jclouds-core</artifactId>
- <version>${jclouds.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds.api</groupId>
- <artifactId>oauth</artifactId>
- <version>${jclouds.version}</version>
- <type>jar</type>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds.api</groupId>
- <artifactId>oauth</artifactId>
- <version>${jclouds.version}</version>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds</groupId>
- <artifactId>jclouds-compute</artifactId>
- <version>${jclouds.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds</groupId>
- <artifactId>jclouds-compute</artifactId>
- <version>${jclouds.version}</version>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds</groupId>
- <artifactId>jclouds-core</artifactId>
- <version>${jclouds.version}</version>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds.driver</groupId>
- <artifactId>jclouds-slf4j</artifactId>
- <version>${jclouds.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.jclouds.driver</groupId>
- <artifactId>jclouds-sshj</artifactId>
- <version>${jclouds.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.squareup.okhttp</groupId>
- <artifactId>mockwebserver</artifactId>
- <scope>test</scope>
- <exclusions>
- <!-- Already provided by jclouds-sshj -->
- <exclusion>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>com.google.auto.value</groupId>
- <artifactId>auto-value</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.google.auto.service</groupId>
- <artifactId>auto-service</artifactId>
- <scope>provided</scope>
- </dependency>
- </dependencies>
- <profiles>
- <profile>
- <id>live</id>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <executions>
- <execution>
- <id>integration</id>
- <phase>integration-test</phase>
- <goals>
- <goal>test</goal>
- </goals>
- <configuration>
- <systemPropertyVariables>
- <test.digitalocean2.endpoint>${test.digitalocean2.endpoint}</test.digitalocean2.endpoint>
- <test.digitalocean2.api-version>${test.digitalocean2.api-version}</test.digitalocean2.api-version>
- <test.digitalocean2.build-version>${test.digitalocean2.build-version}</test.digitalocean2.build-version>
- <test.digitalocean2.identity>${test.digitalocean2.identity}</test.digitalocean2.identity>
- <test.digitalocean2.credential>${test.digitalocean2.credential}</test.digitalocean2.credential>
- <test.digitalocean2.template>${test.digitalocean2.template}</test.digitalocean2.template>
- </systemPropertyVariables>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- </profiles>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.api</groupId>
+ <artifactId>oauth</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.api</groupId>
+ <artifactId>oauth</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-compute</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-compute</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-core</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-slf4j</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-sshj</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp</groupId>
+ <artifactId>mockwebserver</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <!-- Already provided by jclouds-sshj -->
+ <exclusion>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.value</groupId>
+ <artifactId>auto-value</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.service</groupId>
+ <artifactId>auto-service</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <profiles>
+ <profile>
+ <id>live</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>integration</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <systemPropertyVariables>
+ <test.digitalocean2.endpoint>${test.digitalocean2.endpoint}</test.digitalocean2.endpoint>
+ <test.digitalocean2.api-version>${test.digitalocean2.api-version}</test.digitalocean2.api-version>
+ <test.digitalocean2.build-version>${test.digitalocean2.build-version}</test.digitalocean2.build-version>
+ <test.digitalocean2.identity>${test.digitalocean2.identity}</test.digitalocean2.identity>
+ <test.digitalocean2.credential>${test.digitalocean2.credential}</test.digitalocean2.credential>
+ <test.digitalocean2.template>${test.digitalocean2.template}</test.digitalocean2.template>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
http://git-wip-us.apache.org/repos/asf/jclouds/blob/edacad2b/providers/pom.xml
----------------------------------------------------------------------
diff --git a/providers/pom.xml b/providers/pom.xml
index ec6fffe..353c2f4 100644
--- a/providers/pom.xml
+++ b/providers/pom.xml
@@ -36,6 +36,7 @@
<module>aws-ec2</module>
<module>aws-sqs</module>
<module>aws-cloudwatch</module>
+ <module>digitalocean2</module>
<module>elastichosts-lon-p</module>
<module>elastichosts-sat-p</module>
<module>elastichosts-lon-b</module>
[07/19] jclouds git commit: JCLOUDS-946: Properly scope images to the
locations where they are available
Posted by na...@apache.org.
JCLOUDS-946: Properly scope images to the locations where they are available
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/26210fe0
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/26210fe0
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/26210fe0
Branch: refs/heads/master
Commit: 26210fe0981ac3bd646fb9c4bf2d7591752266e9
Parents: 057be8d
Author: Ignasi Barrera <na...@apache.org>
Authored: Sun Jun 28 23:51:08 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Jun 30 23:08:07 2015 +0200
----------------------------------------------------------------------
providers/digitalocean2/pom.xml | 2 +-
.../digitalocean2/DigitalOcean2ApiMetadata.java | 2 +-
.../DigitalOcean2ComputeServiceAdapter.java | 45 +++++++--
...igitalOcean2ComputeServiceContextModule.java | 15 +--
.../extensions/DigitalOcean2ImageExtension.java | 8 +-
.../functions/DropletToNodeMetadata.java | 25 ++---
.../compute/functions/ImageInRegionToImage.java | 92 ++++++++++++++++++
.../compute/functions/ImageToImage.java | 65 -------------
.../compute/internal/ImageInRegion.java | 54 +++++++++++
.../services/org.jclouds.apis.ApiMetadata | 18 ----
.../DigitalOcean2TemplateBuilderLiveTest.java | 2 +-
.../functions/DropletToNodeMetadataTest.java | 4 +-
.../functions/ImageInRegionToImageTest.java | 98 ++++++++++++++++++++
.../compute/functions/ImageToImageTest.java | 57 ------------
14 files changed, 308 insertions(+), 179 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/pom.xml
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/pom.xml b/providers/digitalocean2/pom.xml
index db5139f..5b211d4 100644
--- a/providers/digitalocean2/pom.xml
+++ b/providers/digitalocean2/pom.xml
@@ -36,7 +36,7 @@
<test.digitalocean2.api-version>2</test.digitalocean2.api-version>
<test.digitalocean2.identity>FIXME</test.digitalocean2.identity>
<test.digitalocean2.credential>FIXME</test.digitalocean2.credential>
- <test.digitalocean2.template>imageId=ubuntu-14-04-x64</test.digitalocean2.template>
+ <test.digitalocean2.template>osFamily=UBUNTU,os64Bit=true</test.digitalocean2.template>
</properties>
<dependencies>
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/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 0b20b96..7e9861d 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
@@ -65,7 +65,7 @@ public class DigitalOcean2ApiMetadata extends BaseHttpApiMetadata<DigitalOcean2A
properties.put(AUDIENCE, "https://cloud.digitalocean.com/v1/oauth/token");
properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
properties.put(PROPERTY_SESSION_INTERVAL, 3600);
- properties.put(TEMPLATE, "imageId=ubuntu-14-04-x64");
+ properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true");
properties.put(POLL_INITIAL_PERIOD, 5000);
properties.put(POLL_MAX_PERIOD, 20000);
return properties;
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
index aa4f656..ec8dc11 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
@@ -17,13 +17,19 @@
package org.jclouds.digitalocean2.compute;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.contains;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Sets.newHashSet;
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 java.util.Set;
+
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
@@ -32,6 +38,7 @@ import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Droplet;
@@ -43,13 +50,14 @@ import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.logging.Logger;
+import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
/**
* Implementation of the Compute Service for the DigitalOcean API.
*/
-public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter<Droplet, Size, Image, Region> {
+public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter<Droplet, Size, ImageInRegion, Region> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
@@ -101,8 +109,30 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
}
@Override
- public Iterable<Image> listImages() {
- return api.imageApi().list().concat();
+ public Iterable<ImageInRegion> listImages() {
+ // Images can claim to be available in a region that is currently marked as "unavailable". We shouldn't return
+ // the images scoped to those regions.
+ final Set<String> availableRegionsIds = newHashSet(transform(listLocations(), new Function<Region, String>() {
+ @Override
+ public String apply(Region input) {
+ return input.slug();
+ }
+ }));
+
+ // Public images re globally available, but non-public ones can only be available in certain regions.
+ // For these kind of images, return one instance of an ImageInRegion for each region where the image is
+ // available. This way we can properly scope global and concrete images so they can be properly looked up.
+ return concat(filter(api.imageApi().list().concat().transform(new Function<Image, Iterable<ImageInRegion>>() {
+ @Override
+ public Iterable<ImageInRegion> apply(final Image image) {
+ return transform(image.regions(), new Function<String, ImageInRegion>() {
+ @Override
+ public ImageInRegion apply(String region) {
+ return availableRegionsIds.contains(region) ? ImageInRegion.create(image, region) : null;
+ }
+ });
+ }
+ }), notNull()));
}
@Override
@@ -142,11 +172,14 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
}
@Override
- public Image getImage(String id) {
+ public ImageInRegion getImage(String id) {
+ String region = ImageInRegion.extractRegion(id);
+ String imageId = ImageInRegion.extractImageId(id);
// The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is
// provided. If it can be parsed as a number, use the method to get by ID. Otherwise, get by slug.
- Integer imageId = Ints.tryParse(id);
- return imageId != null ? api.imageApi().get(imageId) : api.imageApi().get(id);
+ Integer numericId = Ints.tryParse(imageId);
+ Image image = numericId == null ? api.imageApi().get(imageId) : api.imageApi().get(numericId);
+ return ImageInRegion.create(image, region);
}
@Override
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/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 7809f9d..c2ed858 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
@@ -28,6 +28,7 @@ import javax.inject.Singleton;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadata.Status;
import org.jclouds.compute.extensions.ImageExtension;
@@ -41,15 +42,15 @@ import org.jclouds.digitalocean2.compute.DigitalOcean2ComputeServiceAdapter;
import org.jclouds.digitalocean2.compute.extensions.DigitalOcean2ImageExtension;
import org.jclouds.digitalocean2.compute.functions.DropletStatusToStatus;
import org.jclouds.digitalocean2.compute.functions.DropletToNodeMetadata;
-import org.jclouds.digitalocean2.compute.functions.ImageToImage;
+import org.jclouds.digitalocean2.compute.functions.ImageInRegionToImage;
import org.jclouds.digitalocean2.compute.functions.RegionToLocation;
import org.jclouds.digitalocean2.compute.functions.SizeToHardware;
import org.jclouds.digitalocean2.compute.functions.TemplateOptionsToStatementWithoutPublicKey;
+import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
import org.jclouds.digitalocean2.compute.strategy.CreateKeyPairsThenCreateNodes;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Droplet;
-import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.domain.Location;
@@ -67,19 +68,19 @@ import com.google.inject.name.Named;
* Configures the compute service classes for the DigitalOcean API.
*/
public class DigitalOcean2ComputeServiceContextModule extends
- ComputeServiceAdapterContextModule<Droplet, Size, Image, Region> {
+ ComputeServiceAdapterContextModule<Droplet, Size, ImageInRegion, Region> {
@Override
protected void configure() {
super.configure();
- bind(new TypeLiteral<ComputeServiceAdapter<Droplet, Size, Image, Region>>() {
+ bind(new TypeLiteral<ComputeServiceAdapter<Droplet, Size, ImageInRegion, Region>>() {
}).to(DigitalOcean2ComputeServiceAdapter.class);
bind(new TypeLiteral<Function<Droplet, NodeMetadata>>() {
}).to(DropletToNodeMetadata.class);
- bind(new TypeLiteral<Function<Image, org.jclouds.compute.domain.Image>>() {
- }).to(ImageToImage.class);
+ bind(new TypeLiteral<Function<ImageInRegion, Image>>() {
+ }).to(ImageInRegionToImage.class);
bind(new TypeLiteral<Function<Region, Location>>() {
}).to(RegionToLocation.class);
bind(new TypeLiteral<Function<Size, Hardware>>() {
@@ -87,7 +88,7 @@ public class DigitalOcean2ComputeServiceContextModule extends
bind(new TypeLiteral<Function<Droplet.Status, Status>>() {
}).to(DropletStatusToStatus.class);
- install(new LocationsFromComputeServiceAdapterModule<Droplet, Size, Image, Region>() {
+ install(new LocationsFromComputeServiceAdapterModule<Droplet, Size, ImageInRegion, Region>() {
});
bind(CreateNodesInGroupThenAddToSet.class).to(CreateKeyPairsThenCreateNodes.class);
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/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 41e3270..77ccd2a 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
@@ -35,6 +35,7 @@ import org.jclouds.compute.domain.ImageTemplateBuilder;
import org.jclouds.compute.extensions.ImageExtension;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Droplet.Status;
@@ -58,12 +59,12 @@ public class DigitalOcean2ImageExtension implements ImageExtension {
private final DigitalOcean2Api api;
private final Predicate<Integer> imageAvailablePredicate;
private final Predicate<Integer> nodeStoppedPredicate;
- private final Function<org.jclouds.digitalocean2.domain.Image, Image> imageTransformer;
+ private final Function<ImageInRegion, Image> imageTransformer;
@Inject DigitalOcean2ImageExtension(DigitalOcean2Api api,
@Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<Integer> imageAvailablePredicate,
@Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
- Function<org.jclouds.digitalocean2.domain.Image, Image> imageTransformer) {
+ Function<ImageInRegion, Image> imageTransformer) {
this.api = api;
this.imageAvailablePredicate = imageAvailablePredicate;
this.nodeStoppedPredicate = nodeStoppedPredicate;
@@ -111,7 +112,8 @@ public class DigitalOcean2ImageExtension implements ImageExtension {
}
}).get();
- return immediateFuture(imageTransformer.apply(snapshot));
+ // By default snapshots are only available in the Droplet's region
+ return immediateFuture(imageTransformer.apply(ImageInRegion.create(snapshot, droplet.region().slug())));
}
@Override
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
index eebc121..11594f8 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
@@ -18,10 +18,11 @@ package org.jclouds.digitalocean2.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.find;
-import static com.google.common.collect.Iterables.tryFind;
+import static org.jclouds.digitalocean2.compute.internal.ImageInRegion.encodeId;
import java.util.Map;
import java.util.Set;
+
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
@@ -35,6 +36,7 @@ import org.jclouds.compute.domain.NodeMetadata.Status;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Networks;
import org.jclouds.digitalocean2.domain.Region;
@@ -42,6 +44,7 @@ import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.logging.Logger;
+
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
@@ -91,7 +94,7 @@ public class DropletToNodeMetadata implements Function<Droplet, NodeMetadata> {
builder.hardware(getHardware(input.sizeSlug()));
builder.location(getLocation(input.region()));
- Optional<? extends Image> image = findImage(input.image().id());
+ Optional<? extends Image> image = findImage(input.image(), input.region().slug());
if (image.isPresent()) {
builder.imageId(image.get().getId());
builder.operatingSystem(image.get().getOperatingSystem());
@@ -138,22 +141,8 @@ public class DropletToNodeMetadata implements Function<Droplet, NodeMetadata> {
return builder.build();
}
- protected Optional<? extends Image> findImage(Integer id) {
- // Try to find the image by ID in the cache. The cache is indexed by slug (for public images) and by id (for
- // private ones).
- final String imageId = String.valueOf(id);
- Optional<? extends Image> image = Optional.fromNullable(images.get().get(imageId));
- if (!image.isPresent()) {
- // If it is a public image (indexed by slug) but the "int" form of the id was provided, try to find it in the
- // whole list of cached images
- image = tryFind(images.get().values(), new Predicate<Image>() {
- @Override
- public boolean apply(Image input) {
- return input.getProviderId().equals(imageId);
- }
- });
- }
- return image;
+ protected Optional<? extends Image> findImage(org.jclouds.digitalocean2.domain.Image image, String region) {
+ return Optional.fromNullable(images.get().get(encodeId(ImageInRegion.create(image, region))));
}
protected Hardware getHardware(final String slug) {
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImage.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImage.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImage.java
new file mode 100644
index 0000000..08c6c71
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImage.java
@@ -0,0 +1,92 @@
+/*
+ * 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.functions;
+
+import static com.google.common.collect.Iterables.find;
+import static org.jclouds.compute.domain.OperatingSystem.builder;
+import static org.jclouds.digitalocean2.compute.internal.ImageInRegion.encodeId;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.Image.Status;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
+import org.jclouds.digitalocean2.domain.OperatingSystem;
+import org.jclouds.domain.Location;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Transforms an {@link ImageInRegion} to the jclouds portable model.
+ */
+@Singleton
+public class ImageInRegionToImage implements Function<ImageInRegion, Image> {
+
+ private final Supplier<Set<? extends Location>> locations;
+
+ @Inject ImageInRegionToImage(@Memoized Supplier<Set<? extends Location>> locations) {
+ this.locations = locations;
+ }
+
+ @Override
+ public Image apply(final ImageInRegion input) {
+ String description = input.image().distribution() + " " + input.image().name();
+ ImageBuilder builder = new ImageBuilder();
+ // Private images don't have a slug
+ builder.id(encodeId(input));
+ builder.providerId(String.valueOf(input.image().id()));
+ builder.name(input.image().name());
+ builder.description(description);
+ builder.status(Status.AVAILABLE);
+ builder.location(getLocation(input.region()));
+
+ OperatingSystem os = OperatingSystem.create(input.image().name(), input.image().distribution());
+
+ builder.operatingSystem(builder()
+ .name(os.distribution().value())
+ .family(os.distribution().osFamily())
+ .description(description)
+ .arch(os.arch())
+ .version(os.version())
+ .is64Bit(os.is64bit())
+ .build());
+
+ ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
+ metadata.put("publicImage", String.valueOf(input.image().isPublic()));
+ builder.userMetadata(metadata.build());
+
+ return builder.build();
+ }
+
+ protected Location getLocation(final String region) {
+ return find(locations.get(), new Predicate<Location>() {
+ @Override
+ public boolean apply(Location location) {
+ return region.equals(location.getId());
+ }
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java
deleted file mode 100644
index 8f9ad92..0000000
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.functions;
-
-import static org.jclouds.compute.domain.OperatingSystem.builder;
-
-import javax.inject.Singleton;
-
-import org.jclouds.compute.domain.Image.Status;
-import org.jclouds.compute.domain.ImageBuilder;
-import org.jclouds.digitalocean2.domain.Image;
-import org.jclouds.digitalocean2.domain.OperatingSystem;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableMap;
-
-/**
- * Transforms an {@link Image} to the jclouds portable model.
- */
-@Singleton
-public class ImageToImage implements Function<Image, org.jclouds.compute.domain.Image> {
-
- @Override
- public org.jclouds.compute.domain.Image apply(final Image input) {
- String description = input.distribution() + " " + input.name();
- ImageBuilder builder = new ImageBuilder();
- // Private images don't have a slug
- builder.id(input.slug() != null ? input.slug() : String.valueOf(input.id()));
- builder.providerId(String.valueOf(input.id()));
- builder.name(input.name());
- builder.description(description);
- builder.status(Status.AVAILABLE);
-
- OperatingSystem os = OperatingSystem.create(input.name(), input.distribution());
-
- builder.operatingSystem(builder()
- .name(os.distribution().value())
- .family(os.distribution().osFamily())
- .description(description)
- .arch(os.arch())
- .version(os.version())
- .is64Bit(os.is64bit())
- .build());
-
- ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
- metadata.put("publicImage", String.valueOf(input.isPublic()));
- builder.userMetadata(metadata.build());
-
- return builder.build();
- }
-
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/internal/ImageInRegion.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/internal/ImageInRegion.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/internal/ImageInRegion.java
new file mode 100644
index 0000000..b5beb8d
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/internal/ImageInRegion.java
@@ -0,0 +1,54 @@
+/*
+ * 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.internal;
+
+import org.jclouds.digitalocean2.domain.Image;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Scopes an image to a particular region.
+ */
+@AutoValue
+public abstract class ImageInRegion {
+
+ public abstract Image image();
+ public abstract String region();
+
+ public static ImageInRegion create(Image image, String region) {
+ return new AutoValue_ImageInRegion(image, region);
+ }
+
+ public static String encodeId(ImageInRegion imageInRegion) {
+ // Private images don't have a slug
+ return String.format("%s/%s", imageInRegion.region(), slugOrId(imageInRegion.image()));
+ }
+
+ public static String extractRegion(String imageId) {
+ return imageId.substring(0, imageId.indexOf('/'));
+ }
+
+ public static String extractImageId(String imageId) {
+ return imageId.substring(imageId.indexOf('/') + 1);
+ }
+
+ private static String slugOrId(Image image) {
+ return image.slug() != null ? image.slug() : String.valueOf(image.id());
+ }
+
+ ImageInRegion() { }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
deleted file mode 100644
index 0be234c..0000000
--- a/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# 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.
-#
-
-org.jclouds.digitalocean2.DigitalOcean2ApiMetadata
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
index e508789..8480cc1 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
@@ -40,7 +40,7 @@ public class DigitalOcean2TemplateBuilderLiveTest extends BaseTemplateBuilderLiv
@Override
public void testDefaultTemplateBuilder() throws IOException {
Template defaultTemplate = view.getComputeService().templateBuilder().build();
- assert defaultTemplate.getImage().getOperatingSystem().getVersion().equals("14.04") : defaultTemplate
+ assert defaultTemplate.getImage().getOperatingSystem().getVersion().equals("15.04") : defaultTemplate
.getImage().getOperatingSystem().getVersion();
assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true);
assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.UBUNTU);
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
index 27dbad9..ba6d3de 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
@@ -83,7 +83,7 @@ public class DropletToNodeMetadataTest {
region = Region.create("sfo1", "San Francisco 1", ImmutableList.of("2gb"), true, ImmutableList.<String> of());
images = ImmutableSet.of(new ImageBuilder()
- .id("ubuntu-1404-x86")
+ .id("sfo1/ubuntu-1404-x86")
.providerId("1")
.name("mock image")
.status(AVAILABLE)
@@ -132,7 +132,7 @@ public class DropletToNodeMetadataTest {
ImmutableList.<Networks.Address> of()), null);
NodeMetadata expected = new NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares))
- .imageId("ubuntu-1404-x86").status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet")
+ .imageId("sfo1/ubuntu-1404-x86").status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet")
.hostname("mock-droplet").group("mock").credentials(credentials)
.publicAddresses(ImmutableSet.of("84.45.69.3")).privateAddresses(ImmutableSet.of("192.168.2.5"))
.providerId("1").backendStatus(ACTIVE.name()).operatingSystem(getOnlyElement(images).getOperatingSystem())
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImageTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImageTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImageTest.java
new file mode 100644
index 0000000..f1072d6
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageInRegionToImageTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.functions;
+
+import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Date;
+import java.util.Set;
+
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+@Test(groups = "unit", testName = "ImageToImageTest")
+public class ImageInRegionToImageTest {
+
+ private Set<Location> locations;
+
+ private ImageInRegionToImage function;
+
+ @BeforeMethod
+ public void setup() {
+ locations = ImmutableSet.of(
+ new LocationBuilder()
+ .id("sfo1")
+ .description("sfo1/San Francisco 1")
+ .scope(LocationScope.REGION)
+ .parent(
+ new LocationBuilder().id("0").description("mock parent location").scope(LocationScope.PROVIDER)
+ .build()).build(),
+ new LocationBuilder()
+ .id("lon1")
+ .description("lon1/London 1")
+ .scope(LocationScope.REGION)
+ .parent(
+ new LocationBuilder().id("0").description("mock parent location").scope(LocationScope.PROVIDER)
+ .build()).build());
+
+ function = new ImageInRegionToImage(new Supplier<Set<? extends Location>>() {
+ @Override
+ public Set<? extends Location> get() {
+ return locations;
+ }
+ });
+ }
+
+ @Test
+ public void testConvertImage() {
+ Image image = Image.create(1, "14.04 x64", "distribution", "Ubuntu", "ubuntu-1404-x86", true,
+ ImmutableList.of("sfo1", "lon1"), new Date());
+ org.jclouds.compute.domain.Image expected = new ImageBuilder()
+ .id("lon1/ubuntu-1404-x86") // Location scoped images have the location encoded in the id
+ .providerId("1")
+ .name("14.04 x64")
+ .description("Ubuntu 14.04 x64")
+ .status(AVAILABLE)
+ .operatingSystem(
+ OperatingSystem.builder().name("Ubuntu").description("Ubuntu 14.04 x64").family(OsFamily.UBUNTU)
+ .version("14.04").arch("x64").is64Bit(true).build())
+ .location(Iterables.get(locations, 1))
+ .userMetadata(ImmutableMap.of("publicImage", "true")).build();
+
+ org.jclouds.compute.domain.Image result = function.apply(ImageInRegion.create(image, "lon1"));
+ assertEquals(result, expected);
+ assertEquals(result.getDescription(), expected.getDescription());
+ assertEquals(result.getOperatingSystem(), expected.getOperatingSystem());
+ assertEquals(result.getStatus(), expected.getStatus());
+ assertEquals(result.getLocation(), Iterables.get(locations, 1));
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/26210fe0/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
deleted file mode 100644
index 6ab020c..0000000
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.functions;
-
-import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
-import static org.testng.Assert.assertEquals;
-
-import java.util.Date;
-
-import org.jclouds.compute.domain.ImageBuilder;
-import org.jclouds.compute.domain.OperatingSystem;
-import org.jclouds.compute.domain.OsFamily;
-import org.jclouds.digitalocean2.domain.Image;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-@Test(groups = "unit", testName = "ImageToImageTest")
-public class ImageToImageTest {
-
- @Test
- public void testConvertImage() {
- Image image = Image.create(1, "14.04 x64", "distribution", "Ubuntu", "ubuntu-1404-x86", true,
- ImmutableList.of("sfo1"), new Date());
- org.jclouds.compute.domain.Image expected = new ImageBuilder()
- .id("ubuntu-1404-x86")
- .providerId("1")
- .name("14.04 x64")
- .description("Ubuntu 14.04 x64")
- .status(AVAILABLE)
- .operatingSystem(
- OperatingSystem.builder().name("Ubuntu").description("Ubuntu 14.04 x64").family(OsFamily.UBUNTU)
- .version("14.04").arch("x64").is64Bit(true).build())
- .userMetadata(ImmutableMap.of("publicImage", "true")).build();
-
- org.jclouds.compute.domain.Image result = new ImageToImage().apply(image);
- assertEquals(result, expected);
- assertEquals(result.getDescription(), expected.getDescription());
- assertEquals(result.getOperatingSystem(), expected.getOperatingSystem());
- assertEquals(result.getStatus(), expected.getStatus());
- }
-}
[08/19] jclouds git commit: JCLOUDS-1023: Fix DigitalOcean getImage()
method
Posted by na...@apache.org.
JCLOUDS-1023: Fix DigitalOcean getImage() method
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/62545262
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/62545262
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/62545262
Branch: refs/heads/master
Commit: 6254526296117616cc6c3aacc26167296b6c0277
Parents: 26210fe
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Oct 22 11:13:30 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Oct 22 11:13:30 2015 +0200
----------------------------------------------------------------------
.../digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/62545262/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
index ec8dc11..f1f5919 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
@@ -179,7 +179,7 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
// provided. If it can be parsed as a number, use the method to get by ID. Otherwise, get by slug.
Integer numericId = Ints.tryParse(imageId);
Image image = numericId == null ? api.imageApi().get(imageId) : api.imageApi().get(numericId);
- return ImageInRegion.create(image, region);
+ return image == null ? null : ImageInRegion.create(image, region);
}
@Override
[18/19] jclouds git commit: Promote DigitalOcean v2
Posted by na...@apache.org.
Promote DigitalOcean v2
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/886aa156
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/886aa156
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/886aa156
Branch: refs/heads/master
Commit: 886aa156b44d34520ac2b4d2723f7de39086f850
Parents: 52dc1a3 c188570
Author: Ignasi Barrera <na...@apache.org>
Authored: Wed Jan 20 23:57:03 2016 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Wed Jan 20 23:57:03 2016 +0100
----------------------------------------------------------------------
providers/digitalocean2/pom.xml | 152 +++++++
.../jclouds/digitalocean2/DigitalOcean2Api.java | 73 ++++
.../digitalocean2/DigitalOcean2ApiMetadata.java | 111 +++++
.../DigitalOcean2ProviderMetadata.java | 78 ++++
.../DigitalOcean2ComputeServiceAdapter.java | 243 +++++++++++
...igitalOcean2ComputeServiceContextModule.java | 218 ++++++++++
.../extensions/DigitalOcean2ImageExtension.java | 149 +++++++
.../functions/DropletStatusToStatus.java | 46 +++
.../functions/DropletToNodeMetadata.java | 165 ++++++++
.../compute/functions/ImageInRegionToImage.java | 92 +++++
.../compute/functions/RegionToLocation.java | 59 +++
.../compute/functions/SizeToHardware.java | 58 +++
...plateOptionsToStatementWithoutPublicKey.java | 59 +++
.../compute/internal/ImageInRegion.java | 54 +++
.../options/DigitalOcean2TemplateOptions.java | 174 ++++++++
.../strategy/CreateKeyPairsThenCreateNodes.java | 216 ++++++++++
.../config/DigitalOcean2HttpApiModule.java | 57 +++
.../config/DigitalOcean2Properties.java | 33 ++
.../config/DigitalOcean2RateLimitModule.java | 30 ++
.../config/DigitalOceanParserModule.java | 144 +++++++
.../jclouds/digitalocean2/domain/Action.java | 71 ++++
.../jclouds/digitalocean2/domain/Backup.java | 43 ++
.../digitalocean2/domain/Distribution.java | 69 ++++
.../jclouds/digitalocean2/domain/Droplet.java | 92 +++++
.../digitalocean2/domain/DropletCreate.java | 66 +++
.../org/jclouds/digitalocean2/domain/Image.java | 48 +++
.../jclouds/digitalocean2/domain/Kernel.java | 35 ++
.../org/jclouds/digitalocean2/domain/Key.java | 39 ++
.../jclouds/digitalocean2/domain/Networks.java | 77 ++++
.../digitalocean2/domain/OperatingSystem.java | 60 +++
.../jclouds/digitalocean2/domain/Region.java | 39 ++
.../org/jclouds/digitalocean2/domain/Size.java | 46 +++
.../jclouds/digitalocean2/domain/Snapshot.java | 47 +++
.../domain/internal/PaginatedCollection.java | 111 +++++
.../domain/options/CreateDropletOptions.java | 179 +++++++++
.../domain/options/ImageListOptions.java | 74 ++++
.../domain/options/ListOptions.java | 60 +++
...DigitalOcean2RateLimitExceededException.java | 81 ++++
.../digitalocean2/features/ActionApi.java | 113 ++++++
.../digitalocean2/features/DropletApi.java | 350 ++++++++++++++++
.../digitalocean2/features/ImageApi.java | 131 ++++++
.../jclouds/digitalocean2/features/KeyApi.java | 164 ++++++++
.../digitalocean2/features/RegionApi.java | 107 +++++
.../jclouds/digitalocean2/features/SizeApi.java | 100 +++++
.../functions/BaseToPagedIterable.java | 59 +++
.../functions/LinkToImageListOptions.java | 67 ++++
.../functions/LinkToListOptions.java | 61 +++
.../handlers/DigitalOcean2ErrorHandler.java | 72 ++++
.../handlers/RateLimitRetryHandler.java | 111 +++++
.../org/jclouds/digitalocean2/ssh/DSAKeys.java | 172 ++++++++
.../jclouds/digitalocean2/ssh/ECDSAKeys.java | 343 ++++++++++++++++
.../DigitalOcean2ProviderMetadataTest.java | 29 ++
.../DigitalOcean2ComputeServiceLiveTest.java | 66 +++
.../DigitalOcean2TemplateBuilderLiveTest.java | 55 +++
.../compute/config/ActionDonePredicateTest.java | 74 ++++
.../config/DropletInStatusPredicateTest.java | 58 +++
.../config/DropletTerminatedPredicateTest.java | 57 +++
.../DigitalOcean2ImageExtensionLiveTest.java | 40 ++
.../functions/DropletStatusToStatusTest.java | 36 ++
.../functions/DropletToNodeMetadataTest.java | 237 +++++++++++
.../functions/ImageInRegionToImageTest.java | 98 +++++
.../compute/functions/RegionToLocationTest.java | 56 +++
.../compute/functions/SizeToHardwareTest.java | 49 +++
...eOptionsToStatementWithoutPublicKeyTest.java | 75 ++++
.../DigitalOcean2TemplateOptionsTest.java | 52 +++
.../domain/OperatingSystemTest.java | 104 +++++
.../exceptions/RateLimitExceptionMockTest.java | 63 +++
.../features/ActionApiLiveTest.java | 70 ++++
.../features/ActionApiMockTest.java | 110 +++++
.../features/DropletApiLiveTest.java | 186 +++++++++
.../features/DropletApiMockTest.java | 401 +++++++++++++++++++
.../features/ImageApiLiveTest.java | 97 +++++
.../features/ImageApiMockTest.java | 150 +++++++
.../digitalocean2/features/KeyApiLiveTest.java | 99 +++++
.../digitalocean2/features/KeyApiMockTest.java | 203 ++++++++++
.../features/RegionApiLiveTest.java | 62 +++
.../features/RegionApiMockTest.java | 77 ++++
.../digitalocean2/features/SizeApiLiveTest.java | 62 +++
.../digitalocean2/features/SizeApiMockTest.java | 77 ++++
.../functions/LinkToImageListOptionsTest.java | 65 +++
.../functions/LinkToListOptionsTest.java | 58 +++
.../handlers/RateLimitRetryHandlerTest.java | 153 +++++++
.../internal/BaseDigitalOcean2ApiLiveTest.java | 140 +++++++
.../internal/BaseDigitalOcean2ApiMockTest.java | 142 +++++++
.../jclouds/digitalocean2/ssh/DSAKeysTest.java | 54 +++
.../digitalocean2/ssh/ECDSAKeysTest.java | 55 +++
.../src/test/resources/action.json | 33 ++
.../src/test/resources/actions-first.json | 168 ++++++++
.../src/test/resources/actions-last.json | 106 +++++
.../src/test/resources/backups-first.json | 26 ++
.../src/test/resources/backups-last.json | 26 ++
.../src/test/resources/droplet-create-req.json | 12 +
.../src/test/resources/droplet-create-res.json | 35 ++
.../src/test/resources/droplet.json | 105 +++++
.../src/test/resources/droplets-first.json | 115 ++++++
.../src/test/resources/droplets-last.json | 115 ++++++
.../digitalocean2/src/test/resources/image.json | 24 ++
.../src/test/resources/images-first.json | 108 +++++
.../src/test/resources/images-last.json | 123 ++++++
.../src/test/resources/kernels-first.json | 38 ++
.../src/test/resources/kernels-last.json | 38 ++
.../digitalocean2/src/test/resources/key.json | 8 +
.../src/test/resources/keys-first.json | 43 ++
.../src/test/resources/keys-last.json | 25 ++
.../src/test/resources/logback-test.xml | 42 ++
.../src/test/resources/power-cycle.json | 33 ++
.../src/test/resources/power-off.json | 33 ++
.../src/test/resources/power-on.json | 33 ++
.../src/test/resources/reboot.json | 33 ++
.../src/test/resources/regions-first.json | 111 +++++
.../src/test/resources/regions-last.json | 128 ++++++
.../src/test/resources/shutdown.json | 33 ++
.../src/test/resources/sizes-first.json | 123 ++++++
.../src/test/resources/sizes-last.json | 98 +++++
.../src/test/resources/snapshot.json | 33 ++
.../src/test/resources/snapshots-first.json | 27 ++
.../src/test/resources/snapshots-last.json | 27 ++
.../src/test/resources/ssh-dsa.pub | 1 +
.../src/test/resources/ssh-ecdsa.pub | 1 +
119 files changed, 10612 insertions(+)
----------------------------------------------------------------------
[16/19] jclouds git commit: Remove unnecessary code to register the
compute extensions
Posted by na...@apache.org.
Remove unnecessary code to register the compute extensions
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/62f1c0bc
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/62f1c0bc
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/62f1c0bc
Branch: refs/heads/master
Commit: 62f1c0bc9a1a7c4d4e5a4a4301505e4e6e1cc36e
Parents: 1ac6fa8
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri Nov 27 09:40:45 2015 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Jan 19 11:17:21 2016 +0100
----------------------------------------------------------------------
.../config/DigitalOcean2ComputeServiceContextModule.java | 7 -------
1 file changed, 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/62f1c0bc/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 7159634..03caf85 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
@@ -57,9 +57,7 @@ import org.jclouds.domain.Location;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
-import com.google.common.base.Optional;
import com.google.common.base.Predicate;
-import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
@@ -99,11 +97,6 @@ public class DigitalOcean2ComputeServiceContextModule extends
}).to(DigitalOcean2ImageExtension.class);
}
- @Override
- protected Optional<ImageExtension> provideImageExtension(Injector i) {
- return Optional.of(i.getInstance(ImageExtension.class));
- }
-
@Provides
@Named(TIMEOUT_NODE_RUNNING)
protected Predicate<Integer> provideDropletRunningPredicate(final DigitalOcean2Api api, Timeouts timeouts,
[12/19] jclouds git commit: JCLOUDS-1027: When waiting to a droplet
to be created we check the proper dropletId
Posted by na...@apache.org.
JCLOUDS-1027: When waiting to a droplet to be created we check the proper dropletId
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/83ff38eb
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/83ff38eb
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/83ff38eb
Branch: refs/heads/master
Commit: 83ff38ebee5e410038aaae19b66ad5170757a6ac
Parents: 7e866ad
Author: Ruben Rubio Rey <ru...@manageacloud.com>
Authored: Tue Oct 27 11:45:30 2015 +1100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Oct 27 23:35:36 2015 +0100
----------------------------------------------------------------------
.../DigitalOcean2ComputeServiceAdapter.java | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/83ff38eb/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
index 43b1585..2d76176 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
@@ -21,7 +21,6 @@ import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.contains;
import static com.google.common.collect.Iterables.filter;
-import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.newHashSet;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
@@ -118,7 +117,7 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
// We have to actively wait until the droplet has been provisioned until
// we can build the entire Droplet object we want to return
- nodeRunningPredicate.apply(getOnlyElement(dropletCreated.links().actions()).id());
+ nodeRunningPredicate.apply(dropletCreated.droplet().id());
Droplet droplet = api.dropletApi().get(dropletCreated.droplet().id());
LoginCredentials defaultCredentials = LoginCredentials.builder().user("root")
@@ -219,25 +218,27 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
public void rebootNode(String id) {
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
- Action action = api.dropletApi().reboot(Integer.parseInt(id));
- checkState(nodeRunningPredicate.apply(action.id()), "node did not restart in the configured timeout");
+ int dropletId = Integer.parseInt(id);
+ api.dropletApi().reboot(dropletId);
+ checkState(nodeRunningPredicate.apply(dropletId), "node did not restart in the configured timeout");
}
@Override
public void resumeNode(String id) {
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
- Action action = api.dropletApi().powerOn(Integer.parseInt(id));
- checkState(nodeRunningPredicate.apply(action.id()), "node did not started in the configured timeout");
+ int dropletId = Integer.parseInt(id);
+ api.dropletApi().powerOn(dropletId);
+ checkState(nodeRunningPredicate.apply(dropletId), "node did not started in the configured timeout");
}
@Override
public void suspendNode(String id) {
- int dropletId = Integer.parseInt(id);
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
+ int dropletId = Integer.parseInt(id);
Action action = api.dropletApi().powerOff(dropletId);
- checkState(nodeStoppedPredicate.apply(action.id()), "node did not stop in the configured timeout");
+ checkState(nodeStoppedPredicate.apply(dropletId), "node did not stop in the configured timeout");
}
}
[04/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean
v2 API
Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/DropletApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/DropletApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/DropletApi.java
new file mode 100644
index 0000000..c2d9b71
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/DropletApi.java
@@ -0,0 +1,350 @@
+/*
+ * 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.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+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.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Backup;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.DropletCreate;
+import org.jclouds.digitalocean2.domain.Kernel;
+import org.jclouds.digitalocean2.domain.Snapshot;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.Payload;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.Transform;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Droplets via their REST API.
+ *
+ * @see <a href="https://developers.digitalocean.com/v2/#droplets"/>
+ * @see DropletApi
+ */
+@Path("/droplets")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface DropletApi extends Closeable {
+
+ @Named("droplet:list")
+ @GET
+ @ResponseParser(ParseDroplets.class)
+ @Transform(ParseDroplets.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Droplet> list();
+
+ @Named("droplet:list")
+ @GET
+ @ResponseParser(ParseDroplets.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Droplet> list(ListOptions options);
+
+ static final class ParseDroplets extends ParseJson<ParseDroplets.Droplets> {
+ @Inject ParseDroplets(Json json) {
+ super(json, TypeLiteral.get(Droplets.class));
+ }
+
+ private static class Droplets extends PaginatedCollection<Droplet> {
+ @ConstructorProperties({ "droplets", "meta", "links" })
+ public Droplets(List<Droplet> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Droplet, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Droplet> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.dropletApi().list(options);
+ }
+ }
+ }
+
+ @Named("droplet:listkernels")
+ @GET
+ @Path("/{id}/kernels")
+ @ResponseParser(ParseKernels.class)
+ @Transform(ParseKernels.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Kernel> listKernels(@PathParam("id") int id);
+
+ @Named("droplet:listkernels")
+ @GET
+ @Path("/{id}/kernels")
+ @ResponseParser(ParseKernels.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Kernel> listKernels(@PathParam("id") int id, ListOptions options);
+
+ static final class ParseKernels extends ParseJson<ParseKernels.Kernels> {
+ @Inject ParseKernels(Json json) {
+ super(json, TypeLiteral.get(Kernels.class));
+ }
+
+ private static class Kernels extends PaginatedCollection<Kernel> {
+ @ConstructorProperties({ "kernels", "meta", "links" })
+ public Kernels(List<Kernel> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Kernel, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Kernel> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.dropletApi().listKernels((Integer) arg0.get(), options);
+ }
+ }
+ }
+
+ @Named("droplet:listsnapshots")
+ @GET
+ @Path("/{id}/snapshots")
+ @ResponseParser(ParseSnapshots.class)
+ @Transform(ParseSnapshots.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Snapshot> listSnapshots(@PathParam("id") int id);
+
+ @Named("droplet:listsnapshots")
+ @GET
+ @Path("/{id}/snapshots")
+ @ResponseParser(ParseSnapshots.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Snapshot> listSnapshots(@PathParam("id") int id, ListOptions options);
+
+ static final class ParseSnapshots extends ParseJson<ParseSnapshots.Snapshots> {
+ @Inject ParseSnapshots(Json json) {
+ super(json, TypeLiteral.get(Snapshots.class));
+ }
+
+ private static class Snapshots extends PaginatedCollection<Snapshot> {
+ @ConstructorProperties({ "snapshots", "meta", "links" })
+ public Snapshots(List<Snapshot> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Snapshot, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Snapshot> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.dropletApi().listSnapshots((Integer) arg0.get(), options);
+ }
+ }
+ }
+
+ @Named("droplet:listbackups")
+ @GET
+ @Path("/{id}/backups")
+ @ResponseParser(ParseBackups.class)
+ @Transform(ParseBackups.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Backup> listBackups(@PathParam("id") int id);
+
+ @Named("droplet:listbackups")
+ @GET
+ @Path("/{id}/backups")
+ @ResponseParser(ParseBackups.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Backup> listBackups(@PathParam("id") int id, ListOptions options);
+
+ static final class ParseBackups extends ParseJson<ParseBackups.Backups> {
+ @Inject ParseBackups(Json json) {
+ super(json, TypeLiteral.get(Backups.class));
+ }
+
+ private static class Backups extends PaginatedCollection<Backup> {
+ @ConstructorProperties({ "backups", "meta", "links" })
+ public Backups(List<Backup> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Backup, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Backup> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.dropletApi().listBackups((Integer) arg0.get(), options);
+ }
+ }
+ }
+
+ @Named("droplet:actions")
+ @GET
+ @Path("/{id}/actions")
+ @ResponseParser(ParseDropletActions.class)
+ @Transform(ParseDropletActions.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Action> listActions(@PathParam("id") int id);
+
+ @Named("droplet:actions")
+ @GET
+ @Path("/{id}/actions")
+ @ResponseParser(ParseDropletActions.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Action> listActions(@PathParam("id") int id, ListOptions options);
+
+ static final class ParseDropletActions extends ParseJson<ParseDropletActions.DropletActions> {
+ @Inject ParseDropletActions(Json json) {
+ super(json, TypeLiteral.get(DropletActions.class));
+ }
+
+ private static class DropletActions extends PaginatedCollection<Action> {
+ @ConstructorProperties({ "actions", "meta", "links" })
+ public DropletActions(List<Action> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Action, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Action> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.dropletApi().listActions((Integer) arg0.get(), options);
+ }
+ }
+ }
+
+ @Named("droplet:create")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @MapBinder(BindToJsonPayload.class)
+ DropletCreate create(@PayloadParam("name") String name, @PayloadParam("region") String region,
+ @PayloadParam("size") String size, @PayloadParam("image") String image);
+
+ @Named("droplet:create")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @MapBinder(CreateDropletOptions.class)
+ DropletCreate create(@PayloadParam("name") String name, @PayloadParam("region") String region,
+ @PayloadParam("size") String size, @PayloadParam("image") String image, CreateDropletOptions options);
+
+ @Named("droplet:get")
+ @GET
+ @SelectJson("droplet")
+ @Path("/{id}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @Nullable
+ Droplet get(@PathParam("id") int id);
+
+ @Named("droplet:delete")
+ @DELETE
+ @Path("/{id}")
+ @Fallback(VoidOnNotFoundOr404.class)
+ void delete(@PathParam("id") int id);
+
+ @Named("droplet:reboot")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("action")
+ @Path("/{id}/actions")
+ @Payload("{\"type\":\"reboot\"}")
+ Action reboot(@PathParam("id") int id);
+
+ @Named("droplet:powercycle")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("action")
+ @Path("/{id}/actions")
+ @Payload("{\"type\":\"power_cycle\"}")
+ Action powerCycle(@PathParam("id") int id);
+
+ @Named("droplet:shutdown")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("action")
+ @Path("/{id}/actions")
+ @Payload("{\"type\":\"shutdown\"}")
+ Action shutdown(@PathParam("id") int id);
+
+ @Named("droplet:poweroff")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("action")
+ @Path("/{id}/actions")
+ @Payload("{\"type\":\"power_off\"}")
+ Action powerOff(@PathParam("id") int id);
+
+ @Named("droplet:poweron")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("action")
+ @Path("/{id}/actions")
+ @Payload("{\"type\":\"power_on\"}")
+ Action powerOn(@PathParam("id") int id);
+
+ @Named("droplet:snapshot")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("action")
+ @Path("/{id}/actions")
+ @Payload("%7B\"type\":\"snapshot\",\"name\":\"{name}\"%7D")
+ Action snapshot(@PathParam("id") int id, @PayloadParam("name") String name);
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ImageApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ImageApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ImageApi.java
new file mode 100644
index 0000000..9803ac3
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ImageApi.java
@@ -0,0 +1,131 @@
+/*
+ * 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.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.ImageListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Images via the REST API.
+ *
+ * @see <a href="https://developers.digitalocean.com/v2/#images"/>
+ * @see ImageApi
+ */
+@Path("/images")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface ImageApi extends Closeable {
+
+ @Named("image:list")
+ @GET
+ @ResponseParser(ParseImages.class)
+ @Transform(ParseImages.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Image> list();
+
+ @Named("image:list")
+ @GET
+ @ResponseParser(ParseImages.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Image> list(ImageListOptions options);
+
+ static final class ParseImages extends ParseJson<ParseImages.Images> {
+ @Inject ParseImages(Json json) {
+ super(json, TypeLiteral.get(Images.class));
+ }
+
+ private static class Images extends PaginatedCollection<Image> {
+ @ConstructorProperties({ "images", "meta", "links" })
+ public Images(List<Image> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Image, ImageListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ImageListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Image> fetchPageUsingOptions(ImageListOptions options, Optional<Object> arg0) {
+ return api.imageApi().list(options);
+ }
+ }
+ }
+
+ @Named("image:get")
+ @GET
+ @SelectJson("image")
+ @Path("/{id}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @Nullable
+ Image get(@PathParam("id") int id);
+
+ @Named("image:get")
+ @GET
+ @SelectJson("image")
+ @Path("/{slug}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @Nullable
+ Image get(@PathParam("slug") String slug);
+
+ @Named("image:delete")
+ @DELETE
+ @Path("/{id}")
+ @Fallback(VoidOnNotFoundOr404.class)
+ void delete(@PathParam("id") int id);
+
+ //TODO: Add delete and create
+
+}
+
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/KeyApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/KeyApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/KeyApi.java
new file mode 100644
index 0000000..4889c5d
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/KeyApi.java
@@ -0,0 +1,164 @@
+/*
+ * 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.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.Transform;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Keys via the REST API.
+ *
+ * @see <a href="https://developers.digitalocean.com/v2/#keys"/>
+ * @see KeyApi
+ */
+@Path("/account/keys")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface KeyApi extends Closeable {
+
+ @Named("key:list")
+ @GET
+ @ResponseParser(ParseKeys.class)
+ @Transform(ParseKeys.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Key> list();
+
+ @Named("key:list")
+ @GET
+ @ResponseParser(ParseKeys.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Key> list(ListOptions options);
+
+ static final class ParseKeys extends ParseJson<ParseKeys.Keys> {
+ @Inject ParseKeys(Json json) {
+ super(json, TypeLiteral.get(Keys.class));
+ }
+
+ private static class Keys extends PaginatedCollection<Key> {
+ @ConstructorProperties({ "ssh_keys", "meta", "links" })
+ public Keys(List<Key> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Key, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Key> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.keyApi().list(options);
+ }
+ }
+ }
+
+ @Named("key:create")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("ssh_key")
+ @MapBinder(BindToJsonPayload.class)
+ Key create(@PayloadParam("name") String name, @PayloadParam("public_key") String key);
+
+ @Named("key:get")
+ @GET
+ @SelectJson("ssh_key")
+ @Path("/{id}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @Nullable
+ Key get(@PathParam("id") int id);
+
+ @Named("key:get")
+ @GET
+ @SelectJson("ssh_key")
+ @Path("/{fingerprint}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @Nullable
+ Key get(@PathParam("fingerprint") String fingerprint);
+
+ @Named("key:update")
+ @PUT
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("ssh_key")
+ @Path("/{id}")
+ @MapBinder(BindToJsonPayload.class)
+ Key update(@PathParam("id") int id, @PayloadParam("name") String name);
+
+ @Named("key:update")
+ @PUT
+ @Produces(MediaType.APPLICATION_JSON)
+ @SelectJson("ssh_key")
+ @Path("/{fingerprint}")
+ @MapBinder(BindToJsonPayload.class)
+ Key update(@PathParam("fingerprint") String fingerprint, @PayloadParam("name") String name);
+
+ @Named("key:delete")
+ @DELETE
+ @Path("/{id}")
+ @Fallback(VoidOnNotFoundOr404.class)
+ void delete(@PathParam("id") int id);
+
+ @Named("key:delete")
+ @DELETE
+ @Path("/{fingerprint}")
+ @Fallback(VoidOnNotFoundOr404.class)
+ void delete(@PathParam("fingerprint") String fingerprint);
+
+}
+
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/RegionApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/RegionApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/RegionApi.java
new file mode 100644
index 0000000..9fb7128
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/RegionApi.java
@@ -0,0 +1,107 @@
+/*
+ * 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.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Regions via the REST API.
+ */
+@Path("/regions")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface RegionApi extends Closeable {
+
+ /**
+ * Get the list of all regions.
+ *
+ * @return The (paginated) list of all regions.
+ */
+ @Named("region:list")
+ @GET
+ @ResponseParser(ParseRegions.class)
+ @Transform(ParseRegions.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Region> list();
+
+ /**
+ * Get a single page of the region list.
+ *
+ * @param options The options to configure the page to get and the size of the page.
+ * @return The page with the requested regions.
+ */
+ @Named("region:list")
+ @GET
+ @ResponseParser(ParseRegions.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Region> list(ListOptions options);
+
+ static final class ParseRegions extends ParseJson<ParseRegions.Regions> {
+ @Inject ParseRegions(Json json) {
+ super(json, TypeLiteral.get(Regions.class));
+ }
+
+ private static class Regions extends PaginatedCollection<Region> {
+ @ConstructorProperties({ "regions", "meta", "links" })
+ public Regions(List<Region> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ static class ToPagedIterable extends BaseToPagedIterable<Region, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Region> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.regionApi().list(options);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/SizeApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/SizeApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/SizeApi.java
new file mode 100644
index 0000000..9165809
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/SizeApi.java
@@ -0,0 +1,100 @@
+/*
+ * 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.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Sizes via the REST API.
+ *
+ * @see <a href="https://developers.digitalocean.com/v2/#sizes"/>
+ * @see org.jclouds.digitalocean2.features.SizeApi
+ */
+@Path("/sizes")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface SizeApi extends Closeable {
+
+ @Named("size:list")
+ @GET
+ @ResponseParser(ParseSizes.class)
+ @Transform(ParseSizes.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Size> list();
+
+ @Named("size:list")
+ @GET
+ @ResponseParser(ParseSizes.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Size> list(ListOptions options);
+
+ static final class ParseSizes extends ParseJson<ParseSizes.Sizes> {
+ @Inject ParseSizes(Json json) {
+ super(json, TypeLiteral.get(Sizes.class));
+ }
+
+ private static class Sizes extends PaginatedCollection<Size> {
+ @ConstructorProperties({ "sizes", "meta", "links" })
+ public Sizes(List<Size> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Size, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Size> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.sizeApi().list(options);
+ }
+ }
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/BaseToPagedIterable.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/BaseToPagedIterable.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/BaseToPagedIterable.java
new file mode 100644
index 0000000..ebedef5
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/BaseToPagedIterable.java
@@ -0,0 +1,59 @@
+/*
+ * 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.functions;
+
+import java.net.URI;
+
+import javax.inject.Inject;
+
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.internal.Arg0ToPagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+
+/**
+ * Base class to implement the functions that build the
+ * <code>PagedIterable</code>. Subclasses just need to override the
+ * {@link #fetchPageUsingOptions(ListOptions, Optional)} to invoke the right API
+ * method with the given options parameter to get the next page.
+ */
+public abstract class BaseToPagedIterable<T, O extends ListOptions> extends
+ Arg0ToPagedIterable<T, BaseToPagedIterable<T, O>> {
+ private final Function<URI, O> linkToOptions;
+ protected final DigitalOcean2Api api;
+
+ @Inject protected BaseToPagedIterable(DigitalOcean2Api api, Function<URI, O> linkToOptions) {
+ this.api = api;
+ this.linkToOptions = linkToOptions;
+ }
+
+ protected abstract IterableWithMarker<T> fetchPageUsingOptions(O options, Optional<Object> arg0);
+
+ @Override protected Function<Object, IterableWithMarker<T>> markerToNextForArg0(final Optional<Object> arg0) {
+ return new Function<Object, IterableWithMarker<T>>() {
+ @Override
+ public IterableWithMarker<T> apply(Object input) {
+ O nextOptions = linkToOptions.apply(URI.class.cast(input));
+ return fetchPageUsingOptions(nextOptions, arg0);
+ }
+ };
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToImageListOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToImageListOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToImageListOptions.java
new file mode 100644
index 0000000..85701e5
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToImageListOptions.java
@@ -0,0 +1,67 @@
+/*
+ * 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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.PRIVATE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.TYPE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
+import static org.jclouds.digitalocean2.functions.LinkToListOptions.getFirstOrNull;
+import static org.jclouds.http.utils.Queries.queryParser;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.domain.options.ImageListOptions;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Multimap;
+
+/**
+ * Transforms a link returned by the API into a {@link ListOptions} that can be
+ * used to perform a request to get another page of a paginated list.
+ */
+public class LinkToImageListOptions implements Function<URI, ImageListOptions> {
+
+ @Override public ImageListOptions apply(URI input) {
+ checkNotNull(input, "input cannot be null");
+
+ Multimap<String, String> queryParams = queryParser().apply(input.getQuery());
+ String nextPage = getFirstOrNull(PAGE_PARAM, queryParams);
+ String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams);
+ String nextType = getFirstOrNull(TYPE_PARAM, queryParams);
+ String nextPrivate = getFirstOrNull(PRIVATE_PARAM, queryParams);
+
+ ImageListOptions options = new ImageListOptions();
+ if (nextPage != null) {
+ options.page(Integer.parseInt(nextPage));
+ }
+ if (nextPerPage != null) {
+ options.perPage(Integer.parseInt(nextPerPage));
+ }
+ if (nextType != null) {
+ options.type(nextType);
+ }
+ if (nextPrivate != null) {
+ options.privateImages(Boolean.parseBoolean(nextPrivate));
+ }
+
+ return options;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToListOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToListOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToListOptions.java
new file mode 100644
index 0000000..1dc22db
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/functions/LinkToListOptions.java
@@ -0,0 +1,61 @@
+/*
+ * 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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.emptyToNull;
+import static com.google.common.collect.Iterables.getFirst;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
+import static org.jclouds.http.utils.Queries.queryParser;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Multimap;
+
+/**
+ * Transforms a link returned by the API into a {@link ListOptions} that can be
+ * used to perform a request to get another page of a paginated list.
+ */
+public class LinkToListOptions implements Function<URI, ListOptions> {
+
+ @Override public ListOptions apply(URI input) {
+ checkNotNull(input, "input cannot be null");
+
+ Multimap<String, String> queryParams = queryParser().apply(input.getQuery());
+ String nextPage = getFirstOrNull(PAGE_PARAM, queryParams);
+ String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams);
+
+ ListOptions options = new ListOptions();
+ if (nextPage != null) {
+ options.page(Integer.parseInt(nextPage));
+ }
+ if (nextPerPage != null) {
+ options.perPage(Integer.parseInt(nextPerPage));
+ }
+
+ return options;
+ }
+
+ public static String getFirstOrNull(String key, Multimap<String, String> params) {
+ return params.containsKey(key) ? emptyToNull(getFirst(params.get(key), null)) : null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
new file mode 100644
index 0000000..5eda6eb
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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.handlers;
+
+import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
+
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.InsufficientResourcesException;
+import org.jclouds.rest.ResourceNotFoundException;
+
+/**
+ * This will parse and set an appropriate exception on the command object.
+ */
+@Singleton
+public class DigitalOcean2ErrorHandler implements HttpErrorHandler {
+ public void handleError(HttpCommand command, HttpResponse response) {
+ // it is important to always read fully and close streams
+ byte[] data = closeClientButKeepContentStream(response);
+ String message = data != null ? new String(data) : null;
+
+ Exception exception = message != null ? new HttpResponseException(command, response, message)
+ : new HttpResponseException(command, response);
+ message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
+ response.getStatusLine());
+ switch (response.getStatusCode()) {
+ case 400:
+ break;
+ case 401:
+ case 403:
+ if (message.contains("droplet limit")) {
+ exception = new InsufficientResourcesException(message, exception);
+ } else {
+ exception = new AuthorizationException(message, exception);
+ }
+ break;
+ case 404:
+ if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
+ exception = new ResourceNotFoundException(message, exception);
+ }
+ break;
+ case 409:
+ exception = new IllegalStateException(message, exception);
+ break;
+ }
+ command.setException(exception);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/DSAKeys.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/DSAKeys.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/DSAKeys.java
new file mode 100644
index 0000000..b3c0760
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/DSAKeys.java
@@ -0,0 +1,172 @@
+/*
+ * 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.ssh;
+
+import static com.google.common.base.Joiner.on;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Splitter.fixedLength;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.size;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.io.BaseEncoding.base64;
+import static org.jclouds.util.Strings2.toStringAndClose;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+
+/**
+ * Utility methods to work with DSA SSH keys.
+ * <p>
+ * Methods in this class should be moved to the {@link org.jclouds.ssh.SshKeys} class.
+ *
+ *
+ * @see org.jclouds.ssh.SshKeys
+ */
+public class DSAKeys {
+
+ public static String encodeAsOpenSSH(DSAPublicKey key) {
+ DSAParams params = key.getParams();
+ byte[] keyBlob = keyBlob(params.getP(), params.getQ(), params.getG(), key.getY());
+ return "ssh-dss " + base64().encode(keyBlob);
+ }
+
+ /**
+ * Executes {@link org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)} on the
+ * string which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ *
+ * @param idRsaPub formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
+ * @see org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)
+ */
+ public static DSAPublicKeySpec publicKeySpecFromOpenSSH(String idDsaPub) {
+ try {
+ return publicKeySpecFromOpenSSH(ByteSource.wrap(idDsaPub.getBytes(Charsets.UTF_8)));
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+
+ /**
+ * Returns {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ *
+ * @param supplier the input stream factory, formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
+ *
+ * @return the {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ * @throws java.io.IOException if an I/O error occurs
+ */
+ public static DSAPublicKeySpec publicKeySpecFromOpenSSH(ByteSource supplier) throws IOException {
+ InputStream stream = supplier.openStream();
+ Iterable<String> parts = Splitter.on(' ').split(toStringAndClose(stream).trim());
+ checkArgument(size(parts) >= 2 && "ssh-dss".equals(get(parts, 0)), "bad format, should be: ssh-dss AAAAB3...");
+ stream = new ByteArrayInputStream(base64().decode(get(parts, 1)));
+ String marker = new String(readLengthFirst(stream));
+ checkArgument("ssh-dss".equals(marker), "looking for marker ssh-dss but got %s", marker);
+ BigInteger p = new BigInteger(readLengthFirst(stream));
+ BigInteger q = new BigInteger(readLengthFirst(stream));
+ BigInteger g = new BigInteger(readLengthFirst(stream));
+ BigInteger y = new BigInteger(readLengthFirst(stream));
+ return new DSAPublicKeySpec(y, p, q, g);
+ }
+
+ /**
+ * @param publicKeyOpenSSH RSA public key in OpenSSH format
+ * @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
+ */
+ public static String fingerprintPublicKey(String publicKeyOpenSSH) {
+ DSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
+ return fingerprint(publicKeySpec.getP(), publicKeySpec.getQ(), publicKeySpec.getG(), publicKeySpec.getY());
+ }
+
+ /**
+ * Create a fingerprint per the following <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00"
+ * >spec</a>
+ *
+ * @return hex fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
+ */
+ public static String fingerprint(BigInteger p, BigInteger q, BigInteger g, BigInteger y) {
+ byte[] keyBlob = keyBlob(p, q, g, y);
+ return hexColonDelimited(Hashing.md5().hashBytes(keyBlob));
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ private static String hexColonDelimited(HashCode hc) {
+ return on(':').join(fixedLength(2).split(base16().lowerCase().encode(hc.asBytes())));
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ private static byte[] keyBlob(BigInteger p, BigInteger q, BigInteger g, BigInteger y) {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeLengthFirst("ssh-dss".getBytes(), out);
+ writeLengthFirst(p.toByteArray(), out);
+ writeLengthFirst(q.toByteArray(), out);
+ writeLengthFirst(g.toByteArray(), out);
+ writeLengthFirst(y.toByteArray(), out);
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ // http://www.ietf.org/rfc/rfc4253.txt
+ private static byte[] readLengthFirst(InputStream in) throws IOException {
+ int byte1 = in.read();
+ int byte2 = in.read();
+ int byte3 = in.read();
+ int byte4 = in.read();
+ int length = (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0);
+ byte[] val = new byte[length];
+ ByteStreams.readFully(in, val);
+ return val;
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ // http://www.ietf.org/rfc/rfc4253.txt
+ private static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException {
+ out.write(array.length >>> 24 & 0xFF);
+ out.write(array.length >>> 16 & 0xFF);
+ out.write(array.length >>> 8 & 0xFF);
+ out.write(array.length >>> 0 & 0xFF);
+ if (array.length == 1 && array[0] == (byte) 0x00) {
+ out.write(new byte[0]);
+ } else {
+ out.write(array);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/ECDSAKeys.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/ECDSAKeys.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/ECDSAKeys.java
new file mode 100644
index 0000000..f17098a
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/ssh/ECDSAKeys.java
@@ -0,0 +1,343 @@
+/*
+ * 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.ssh;
+
+import static com.google.common.base.Joiner.on;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Splitter.fixedLength;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.size;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.io.BaseEncoding.base64;
+import static org.jclouds.util.Strings2.toStringAndClose;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+
+/**
+ * Utility methods to work with ECDSA Elliptic Curve DSA keys.
+ * <p>
+ * Methods in this class should be moved to the {@link org.jclouds.ssh.SshKeys} class.
+ *
+ * @see org.jclouds.ssh.SshKeys
+ */
+public class ECDSAKeys {
+ public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-";
+
+ private static final String NISTP256 = "nistp256";
+ private static final String NISTP384 = "nistp384";
+ private static final String NISTP521 = "nistp521";
+
+ private static final Map<String, ECParameterSpec> CURVES = new TreeMap<String, ECParameterSpec>();
+ static {
+ CURVES.put(NISTP256, EllipticCurves.nistp256);
+ CURVES.put(NISTP384, EllipticCurves.nistp384);
+ CURVES.put(NISTP521, EllipticCurves.nistp521);
+ }
+
+ private static final Map<Integer, String> CURVE_SIZES = new TreeMap<Integer, String>();
+ static {
+ CURVE_SIZES.put(256, NISTP256);
+ CURVE_SIZES.put(384, NISTP384);
+ CURVE_SIZES.put(521, NISTP521);
+ }
+
+ public static String encodeAsOpenSSH(ECPublicKey key) {
+
+ String curveName = null;
+ try {
+ curveName = getCurveName(key.getParams());
+ } catch (IOException e) {
+ propagate(e);
+ }
+
+ String keyFormat = ECDSA_SHA2_PREFIX + curveName;
+
+ byte[] keyBlob = keyBlob(key);
+ return keyFormat + " " + base64().encode(keyBlob);
+ }
+
+ /**
+ * Executes {@link org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)} on the
+ * string which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ *
+ * @param idRsaPub formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
+ * @see org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)
+ */
+ public static ECPublicKeySpec publicKeySpecFromOpenSSH(String ecDsaPub) {
+ try {
+ return publicKeySpecFromOpenSSH(ByteSource.wrap(ecDsaPub.getBytes(Charsets.UTF_8)));
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+
+ /**
+ * Returns {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ *
+ * @param supplier the input stream factory, formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
+ *
+ * @return the {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
+ * @throws java.io.IOException if an I/O error occurs
+ */
+ public static ECPublicKeySpec publicKeySpecFromOpenSSH(ByteSource supplier) throws IOException {
+ InputStream stream = supplier.openStream();
+ Iterable<String> parts = Splitter.on(' ').split(toStringAndClose(stream).trim());
+ String signatureFormat = get(parts, 0);
+ checkArgument(size(parts) >= 2 && signatureFormat.startsWith(ECDSA_SHA2_PREFIX), "bad format, should be: ecdsa-sha2-xxx AAAAB3...");
+
+ String curveName = signatureFormat.substring(ECDSA_SHA2_PREFIX.length());
+ if (!CURVES.containsKey(curveName)) {
+ throw new IOException("Unsupported curve: " + curveName);
+ }
+ ECParameterSpec spec = CURVES.get(curveName);
+ stream = new ByteArrayInputStream(base64().decode(get(parts, 1)));
+ @SuppressWarnings("unused")
+ String keyType = new String(readLengthFirst(stream));
+ String curveMarker = new String(readLengthFirst(stream));
+ checkArgument(curveName.equals(curveMarker), "looking for marker %s but got %s", curveName, curveMarker);
+
+ ECPoint ecPoint = decodeECPoint(readLengthFirst(stream), spec.getCurve());
+
+ return new ECPublicKeySpec(ecPoint, spec);
+ }
+
+ /**
+ * @param publicKeyOpenSSH RSA public key in OpenSSH format
+ * @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
+ */
+ public static String fingerprintPublicKey(String publicKeyOpenSSH) throws IOException {
+ ECPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
+ String fingerprint = null;
+ try {
+ ECPublicKey pk = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
+ fingerprint = fingerprint(pk);
+ } catch (InvalidKeySpecException e) {
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ return fingerprint;
+ }
+
+ /**
+ * Create a fingerprint per the following <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00"
+ * >spec</a>
+ *
+ * @return hex fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
+ */
+ public static String fingerprint(ECPublicKey publicKey) {
+ byte[] keyBlob = keyBlob(publicKey);
+ return hexColonDelimited(Hashing.md5().hashBytes(keyBlob));
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ private static String hexColonDelimited(HashCode hc) {
+ return on(':').join(fixedLength(2).split(base16().lowerCase().encode(hc.asBytes())));
+ }
+
+ private static byte[] keyBlob(ECPublicKey key) {
+ try {
+ String curveName = getCurveName(key.getParams());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeLengthFirst((ECDSA_SHA2_PREFIX + curveName).getBytes(), out);
+ writeLengthFirst(curveName.getBytes(), out);
+ writeLengthFirst(encodeECPoint(key.getW(), key.getParams().getCurve()), out);
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw propagate(e);
+ }
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ // http://www.ietf.org/rfc/rfc4253.txt
+ private static byte[] readLengthFirst(InputStream in) throws IOException {
+ int byte1 = in.read();
+ int byte2 = in.read();
+ int byte3 = in.read();
+ int byte4 = in.read();
+ int length = (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0);
+ byte[] val = new byte[length];
+ ByteStreams.readFully(in, val);
+ return val;
+ }
+
+ /**
+ * @see org.jclouds.ssh.SshKeys
+ */
+ // http://www.ietf.org/rfc/rfc4253.txt
+ private static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException {
+ out.write(array.length >>> 24 & 0xFF);
+ out.write(array.length >>> 16 & 0xFF);
+ out.write(array.length >>> 8 & 0xFF);
+ out.write(array.length >>> 0 & 0xFF);
+ if (array.length == 1 && array[0] == (byte) 0x00) {
+ out.write(new byte[0]);
+ } else {
+ out.write(array);
+ }
+ }
+
+ private static String getCurveName(ECParameterSpec params) throws IOException {
+ int fieldSize = getCurveSize(params);
+ String curveName = CURVE_SIZES.get(fieldSize);
+ if (curveName == null) {
+ throw new IOException("Unsupported curve field size: " + fieldSize);
+ }
+ return curveName;
+ }
+
+ private static int getCurveSize(ECParameterSpec params) {
+ return params.getCurve().getField().getFieldSize();
+ }
+
+ /**
+ * Encode EllipticCurvePoint to an OctetString
+ */
+ public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve)
+ {
+ // M has len 2 ceil(log_2(q)/8) + 1 ?
+ int elementSize = (curve.getField().getFieldSize() + 7) / 8;
+ byte[] M = new byte[2 * elementSize + 1];
+
+ // Uncompressed format
+ M[0] = 0x04;
+
+ {
+ byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray());
+ System.arraycopy(affineX, 0, M, 1 + elementSize - affineX.length, affineX.length);
+ }
+
+ {
+ byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray());
+ System.arraycopy(affineY, 0, M, 1 + elementSize + elementSize - affineY.length,
+ affineY.length);
+ }
+
+ return M;
+ }
+
+ private static byte[] removeLeadingZeroes(byte[] input) {
+ if (input[0] != 0x00) {
+ return input;
+ }
+
+ int pos = 1;
+ while (pos < input.length - 1 && input[pos] == 0x00) {
+ pos++;
+ }
+
+ byte[] output = new byte[input.length - pos];
+ System.arraycopy(input, pos, output, 0, output.length);
+ return output;
+ }
+
+ /**
+ * Decode an OctetString to EllipticCurvePoint according to SECG 2.3.4
+ */
+ public static ECPoint decodeECPoint(byte[] M, EllipticCurve curve) {
+ if (M.length == 0) {
+ return null;
+ }
+
+ // M has len 2 ceil(log_2(q)/8) + 1 ?
+ int elementSize = (curve.getField().getFieldSize() + 7) / 8;
+ if (M.length != 2 * elementSize + 1) {
+ return null;
+ }
+
+ // step 3.2
+ if (M[0] != 0x04) {
+ return null;
+ }
+
+ // Step 3.3
+ byte[] xp = new byte[elementSize];
+ System.arraycopy(M, 1, xp, 0, elementSize);
+
+ // Step 3.4
+ byte[] yp = new byte[elementSize];
+ System.arraycopy(M, 1 + elementSize, yp, 0, elementSize);
+
+ ECPoint P = new ECPoint(new BigInteger(1, xp), new BigInteger(1, yp));
+
+ // TODO check point 3.5
+
+ // Step 3.6
+ return P;
+ }
+
+ public static class EllipticCurves {
+ public static ECParameterSpec nistp256 = new ECParameterSpec(
+ new EllipticCurve(
+ new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)),
+ new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)),
+ new ECPoint(new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16),
+ new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)),
+ new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16),
+ 1);
+
+ public static ECParameterSpec nistp384 = new ECParameterSpec(
+ new EllipticCurve(
+ new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)),
+ new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16),
+ new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)),
+ new ECPoint(new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16),
+ new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)),
+ new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16),
+ 1);
+
+ public static ECParameterSpec nistp521 = new ECParameterSpec(
+ new EllipticCurve(
+ new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)),
+ new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),
+ new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)
+ ),
+ new ECPoint(new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16),
+ new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)
+ ),
+ new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16),
+ 1);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
new file mode 100644
index 0000000..0be234c
--- /dev/null
+++ b/providers/digitalocean2/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.jclouds.digitalocean2.DigitalOcean2ApiMetadata
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadataTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadataTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadataTest.java
new file mode 100644
index 0000000..7756813
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadataTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import org.jclouds.providers.internal.BaseProviderMetadataTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "DigitalOcean2ProviderMetadataTest")
+public class DigitalOcean2ProviderMetadataTest extends BaseProviderMetadataTest {
+
+ public DigitalOcean2ProviderMetadataTest() {
+ super(new DigitalOcean2ProviderMetadata(), new DigitalOcean2ApiMetadata());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
new file mode 100644
index 0000000..b1dcc1b
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.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;
+
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Live tests for the {@link org.jclouds.compute.ComputeService} integration.
+ */
+@Test(groups = "live", singleThreaded = true, testName = "DigitalOcean2ComputeServiceLiveTest")
+public class DigitalOcean2ComputeServiceLiveTest extends BaseComputeServiceLiveTest {
+
+ public DigitalOcean2ComputeServiceLiveTest() {
+ provider = "digitalocean2";
+ }
+
+ @Override
+ protected Module getSshModule() {
+ return new SshjSshClientModule();
+ }
+
+ @Override
+ public void testOptionToNotBlock() throws Exception {
+ // DigitalOcean ComputeService implementation has to block until the node
+ // is provisioned, to be able to return it.
+ }
+
+ @Override
+ protected void checkTagsInNodeEquals(NodeMetadata node, ImmutableSet<String> tags) {
+ // DigitalOcean does not support tags
+ }
+
+ @Override
+ protected void checkUserMetadataContains(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
+ // DigitalOcean does not support user metadata
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
new file mode 100644
index 0000000..e508789
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.internal.BaseTemplateBuilderLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "live", testName = "DigitalOcean2TemplateBuilderLiveTest")
+public class DigitalOcean2TemplateBuilderLiveTest extends BaseTemplateBuilderLiveTest {
+
+ public DigitalOcean2TemplateBuilderLiveTest() {
+ provider = "digitalocean2";
+ }
+
+ @Test
+ @Override
+ public void testDefaultTemplateBuilder() throws IOException {
+ Template defaultTemplate = view.getComputeService().templateBuilder().build();
+ assert defaultTemplate.getImage().getOperatingSystem().getVersion().equals("14.04") : defaultTemplate
+ .getImage().getOperatingSystem().getVersion();
+ assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true);
+ assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.UBUNTU);
+ assertEquals(getCores(defaultTemplate.getHardware()), 1.0d);
+ }
+
+ @Override
+ protected Set<String> getIso3166Codes() {
+ return ImmutableSet.<String> of();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/ActionDonePredicateTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/ActionDonePredicateTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/ActionDonePredicateTest.java
new file mode 100644
index 0000000..0c852a1
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/ActionDonePredicateTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.anyInt;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Date;
+
+import org.easymock.EasyMock;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule.ActionDonePredicate;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.features.ActionApi;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "ActionDonePredicateTest")
+public class ActionDonePredicateTest {
+
+ public void testActionStatusOk() {
+ ActionApi actionApi = EasyMock.createMock(ActionApi.class);
+ DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
+
+ expect(actionApi.get(1)).andReturn(action(Action.Status.COMPLETED));
+ expect(actionApi.get(2)).andReturn(action(Action.Status.IN_PROGRESS));
+ expect(api.actionApi()).andReturn(actionApi).times(2);
+ replay(actionApi, api);
+
+ ActionDonePredicate predicate = new ActionDonePredicate(api);
+ assertTrue(predicate.apply(1));
+ assertFalse(predicate.apply(2));
+ }
+
+ public void testActionStatusError() {
+ ActionApi actionApi = EasyMock.createMock(ActionApi.class);
+ DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
+
+ expect(actionApi.get(anyInt())).andReturn(action(Action.Status.ERRORED));
+ expect(api.actionApi()).andReturn(actionApi);
+ replay(actionApi, api);
+
+ ActionDonePredicate predicate = new ActionDonePredicate(api);
+
+ try {
+ predicate.apply(1);
+ fail("Method should have thrown an IllegalStateException");
+ } catch (IllegalStateException ex) {
+ assertEquals(ex.getMessage(), "Resource is in invalid status: ERRORED");
+ }
+ }
+
+ private static Action action(Action.Status status) {
+ return Action.create(1, status, "foo", new Date(), new Date(), 1, "", null, "");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletTerminatedPredicateTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletTerminatedPredicateTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletTerminatedPredicateTest.java
new file mode 100644
index 0000000..6858e6d
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/config/DropletTerminatedPredicateTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.DropletTerminatedPredicate;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.features.DropletApi;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+@Test(groups = "unit", testName = "DropletTerminatedPredicateTest")
+public class DropletTerminatedPredicateTest {
+
+ public void testDropletTerminated() {
+ DropletApi dropletApi = EasyMock.createMock(DropletApi.class);
+ DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
+
+ expect(dropletApi.get(1)).andReturn(mockDroplet());
+ expect(dropletApi.get(2)).andReturn(null);
+ expect(api.dropletApi()).andReturn(dropletApi).times(2);
+ replay(dropletApi, api);
+
+ DropletTerminatedPredicate predicate = new DropletTerminatedPredicate(api);
+ assertFalse(predicate.apply(1));
+ assertTrue(predicate.apply(2));
+ }
+
+ private static Droplet mockDroplet() {
+ return Droplet.create(1, "foo", 1024, 1, 20, false, new Date(), Droplet.Status.ACTIVE,
+ ImmutableList.<Integer> of(), ImmutableList.<Integer> of(), ImmutableList.<String> of(), null, null, null,
+ "", null, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtensionLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtensionLiveTest.java
new file mode 100644
index 0000000..623d136
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtensionLiveTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.extensions;
+
+import org.jclouds.compute.extensions.internal.BaseImageExtensionLiveTest;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.Test;
+
+import com.google.inject.Module;
+
+/**
+ * Live tests for the {@link org.jclouds.compute.extensions.ImageExtension} integration.
+ */
+@Test(groups = "live", singleThreaded = true, testName = "DigitalOcean2ImageExtensionLiveTest")
+public class DigitalOcean2ImageExtensionLiveTest extends BaseImageExtensionLiveTest {
+
+ public DigitalOcean2ImageExtensionLiveTest() {
+ provider = "digitalocean2";
+ }
+
+ @Override
+ protected Module getSshModule() {
+ return new SshjSshClientModule();
+ }
+
+}
[06/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean
v2 API
Posted by na...@apache.org.
JCLOUDS-613: Implement the DigitalOcean v2 API
Thanks to [~nacx] for pagination, many tests, fixes, and improvements to help push this over the finish line!
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/057be8df
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/057be8df
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/057be8df
Branch: refs/heads/master
Commit: 057be8df999bce950b672f9181547482e4a9b849
Parents:
Author: Chris Custine <cc...@apache.org>
Authored: Sat Jun 27 19:52:05 2015 -0600
Committer: Ignasi Barrera <na...@apache.org>
Committed: Sun Jun 28 22:12:03 2015 +0200
----------------------------------------------------------------------
providers/digitalocean2/pom.xml | 152 +++++++
.../jclouds/digitalocean2/DigitalOcean2Api.java | 73 ++++
.../digitalocean2/DigitalOcean2ApiMetadata.java | 103 +++++
.../DigitalOcean2ProviderMetadata.java | 78 ++++
.../DigitalOcean2ComputeServiceAdapter.java | 191 +++++++++
...igitalOcean2ComputeServiceContextModule.java | 205 ++++++++++
.../extensions/DigitalOcean2ImageExtension.java | 132 ++++++
.../functions/DropletStatusToStatus.java | 46 +++
.../functions/DropletToNodeMetadata.java | 176 ++++++++
.../compute/functions/ImageToImage.java | 65 +++
.../compute/functions/RegionToLocation.java | 57 +++
.../compute/functions/SizeToHardware.java | 58 +++
...plateOptionsToStatementWithoutPublicKey.java | 59 +++
.../options/DigitalOcean2TemplateOptions.java | 174 ++++++++
.../strategy/CreateKeyPairsThenCreateNodes.java | 217 ++++++++++
.../config/DigitalOcean2HttpApiModule.java | 57 +++
.../config/DigitalOceanParserModule.java | 144 +++++++
.../jclouds/digitalocean2/domain/Action.java | 71 ++++
.../jclouds/digitalocean2/domain/Backup.java | 43 ++
.../digitalocean2/domain/Distribution.java | 69 ++++
.../jclouds/digitalocean2/domain/Droplet.java | 92 +++++
.../digitalocean2/domain/DropletCreate.java | 66 +++
.../org/jclouds/digitalocean2/domain/Image.java | 48 +++
.../jclouds/digitalocean2/domain/Kernel.java | 35 ++
.../org/jclouds/digitalocean2/domain/Key.java | 39 ++
.../jclouds/digitalocean2/domain/Networks.java | 77 ++++
.../digitalocean2/domain/OperatingSystem.java | 60 +++
.../jclouds/digitalocean2/domain/Region.java | 39 ++
.../org/jclouds/digitalocean2/domain/Size.java | 46 +++
.../jclouds/digitalocean2/domain/Snapshot.java | 47 +++
.../domain/internal/PaginatedCollection.java | 111 +++++
.../domain/options/CreateDropletOptions.java | 155 +++++++
.../domain/options/ImageListOptions.java | 74 ++++
.../domain/options/ListOptions.java | 60 +++
.../digitalocean2/features/ActionApi.java | 113 ++++++
.../digitalocean2/features/DropletApi.java | 350 ++++++++++++++++
.../digitalocean2/features/ImageApi.java | 131 ++++++
.../jclouds/digitalocean2/features/KeyApi.java | 164 ++++++++
.../digitalocean2/features/RegionApi.java | 107 +++++
.../jclouds/digitalocean2/features/SizeApi.java | 100 +++++
.../functions/BaseToPagedIterable.java | 59 +++
.../functions/LinkToImageListOptions.java | 67 ++++
.../functions/LinkToListOptions.java | 61 +++
.../handlers/DigitalOcean2ErrorHandler.java | 67 ++++
.../org/jclouds/digitalocean2/ssh/DSAKeys.java | 172 ++++++++
.../jclouds/digitalocean2/ssh/ECDSAKeys.java | 343 ++++++++++++++++
.../services/org.jclouds.apis.ApiMetadata | 18 +
.../DigitalOcean2ProviderMetadataTest.java | 29 ++
.../DigitalOcean2ComputeServiceLiveTest.java | 58 +++
.../DigitalOcean2TemplateBuilderLiveTest.java | 55 +++
.../compute/config/ActionDonePredicateTest.java | 74 ++++
.../config/DropletTerminatedPredicateTest.java | 57 +++
.../DigitalOcean2ImageExtensionLiveTest.java | 40 ++
.../functions/DropletStatusToStatusTest.java | 36 ++
.../functions/DropletToNodeMetadataTest.java | 237 +++++++++++
.../compute/functions/ImageToImageTest.java | 57 +++
.../compute/functions/RegionToLocationTest.java | 52 +++
.../compute/functions/SizeToHardwareTest.java | 49 +++
...eOptionsToStatementWithoutPublicKeyTest.java | 75 ++++
.../DigitalOcean2TemplateOptionsTest.java | 52 +++
.../domain/OperatingSystemTest.java | 104 +++++
.../features/ActionApiLiveTest.java | 70 ++++
.../features/ActionApiMockTest.java | 110 +++++
.../features/DropletApiLiveTest.java | 195 +++++++++
.../features/DropletApiMockTest.java | 401 +++++++++++++++++++
.../features/ImageApiLiveTest.java | 97 +++++
.../features/ImageApiMockTest.java | 150 +++++++
.../digitalocean2/features/KeyApiLiveTest.java | 101 +++++
.../digitalocean2/features/KeyApiMockTest.java | 203 ++++++++++
.../features/RegionApiLiveTest.java | 62 +++
.../features/RegionApiMockTest.java | 77 ++++
.../digitalocean2/features/SizeApiLiveTest.java | 62 +++
.../digitalocean2/features/SizeApiMockTest.java | 77 ++++
.../functions/LinkToImageListOptionsTest.java | 65 +++
.../functions/LinkToListOptionsTest.java | 58 +++
.../internal/BaseDigitalOcean2ApiLiveTest.java | 120 ++++++
.../internal/BaseDigitalOcean2ApiMockTest.java | 137 +++++++
.../jclouds/digitalocean2/ssh/DSAKeysTest.java | 54 +++
.../digitalocean2/ssh/ECDSAKeysTest.java | 55 +++
.../src/test/resources/action.json | 33 ++
.../src/test/resources/actions-first.json | 168 ++++++++
.../src/test/resources/actions-last.json | 106 +++++
.../src/test/resources/backups-first.json | 26 ++
.../src/test/resources/backups-last.json | 26 ++
.../src/test/resources/droplet-create-req.json | 12 +
.../src/test/resources/droplet-create-res.json | 35 ++
.../src/test/resources/droplet.json | 105 +++++
.../src/test/resources/droplets-first.json | 115 ++++++
.../src/test/resources/droplets-last.json | 115 ++++++
.../digitalocean2/src/test/resources/image.json | 24 ++
.../src/test/resources/images-first.json | 108 +++++
.../src/test/resources/images-last.json | 123 ++++++
.../src/test/resources/kernels-first.json | 38 ++
.../src/test/resources/kernels-last.json | 38 ++
.../digitalocean2/src/test/resources/key.json | 8 +
.../src/test/resources/keys-first.json | 43 ++
.../src/test/resources/keys-last.json | 25 ++
.../src/test/resources/logback-test.xml | 42 ++
.../src/test/resources/power-cycle.json | 33 ++
.../src/test/resources/power-off.json | 33 ++
.../src/test/resources/power-on.json | 33 ++
.../src/test/resources/reboot.json | 33 ++
.../src/test/resources/regions-first.json | 111 +++++
.../src/test/resources/regions-last.json | 128 ++++++
.../src/test/resources/shutdown.json | 33 ++
.../src/test/resources/sizes-first.json | 123 ++++++
.../src/test/resources/sizes-last.json | 98 +++++
.../src/test/resources/snapshot.json | 33 ++
.../src/test/resources/snapshots-first.json | 27 ++
.../src/test/resources/snapshots-last.json | 27 ++
.../src/test/resources/ssh-dsa.pub | 1 +
.../src/test/resources/ssh-ecdsa.pub | 1 +
112 files changed, 9844 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/pom.xml
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/pom.xml b/providers/digitalocean2/pom.xml
new file mode 100644
index 0000000..db5139f
--- /dev/null
+++ b/providers/digitalocean2/pom.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.jclouds.labs</groupId>
+ <artifactId>jclouds-labs</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.apache.jclouds.labs</groupId>
+ <artifactId>digitalocean2</artifactId>
+ <name>jclouds DigitalOcean v2 API Provider</name>
+ <description>jclouds provider for Digital Ocean v2 Compute API</description>
+
+ <properties>
+ <test.digitalocean2.endpoint>https://api.digitalocean.com/v2/</test.digitalocean2.endpoint>
+ <test.digitalocean2.api-version>2</test.digitalocean2.api-version>
+ <test.digitalocean2.identity>FIXME</test.digitalocean2.identity>
+ <test.digitalocean2.credential>FIXME</test.digitalocean2.credential>
+ <test.digitalocean2.template>imageId=ubuntu-14-04-x64</test.digitalocean2.template>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-core</artifactId>
+ <version>${jclouds.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.api</groupId>
+ <artifactId>oauth</artifactId>
+ <version>${jclouds.version}</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.api</groupId>
+ <artifactId>oauth</artifactId>
+ <version>${jclouds.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-compute</artifactId>
+ <version>${jclouds.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-compute</artifactId>
+ <version>${jclouds.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds</groupId>
+ <artifactId>jclouds-core</artifactId>
+ <version>${jclouds.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-slf4j</artifactId>
+ <version>${jclouds.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jclouds.driver</groupId>
+ <artifactId>jclouds-sshj</artifactId>
+ <version>${jclouds.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp</groupId>
+ <artifactId>mockwebserver</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <!-- Already provided by jclouds-sshj -->
+ <exclusion>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.value</groupId>
+ <artifactId>auto-value</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.service</groupId>
+ <artifactId>auto-service</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <profiles>
+ <profile>
+ <id>live</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>integration</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <systemPropertyVariables>
+ <test.digitalocean2.endpoint>${test.digitalocean2.endpoint}</test.digitalocean2.endpoint>
+ <test.digitalocean2.api-version>${test.digitalocean2.api-version}</test.digitalocean2.api-version>
+ <test.digitalocean2.build-version>${test.digitalocean2.build-version}</test.digitalocean2.build-version>
+ <test.digitalocean2.identity>${test.digitalocean2.identity}</test.digitalocean2.identity>
+ <test.digitalocean2.credential>${test.digitalocean2.credential}</test.digitalocean2.credential>
+ <test.digitalocean2.template>${test.digitalocean2.template}</test.digitalocean2.template>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java
new file mode 100644
index 0000000..773fa59
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2Api.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import java.io.Closeable;
+
+import org.jclouds.digitalocean2.features.ActionApi;
+import org.jclouds.digitalocean2.features.DropletApi;
+import org.jclouds.digitalocean2.features.ImageApi;
+import org.jclouds.digitalocean2.features.KeyApi;
+import org.jclouds.digitalocean2.features.RegionApi;
+import org.jclouds.digitalocean2.features.SizeApi;
+import org.jclouds.rest.annotations.Delegate;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides access to DigitalOcean.
+ */
+@Beta
+public interface DigitalOcean2Api extends Closeable {
+
+ /**
+ * Provides access to Droplet features
+ */
+ @Delegate
+ DropletApi dropletApi();
+
+ /**
+ * Provides access to SSH Key features
+ */
+ @Delegate
+ KeyApi keyApi();
+
+ /**
+ * Provides access to Images
+ */
+ @Delegate
+ ImageApi imageApi();
+
+ /**
+ * Provides access to Actions
+ */
+ @Delegate
+ ActionApi actionApi();
+
+ /**
+ * Provides access to Sizes
+ */
+ @Delegate
+ SizeApi sizeApi();
+
+ /**
+ * Provides access to Regions
+ */
+ @Delegate
+ RegionApi regionApi();
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/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
new file mode 100644
index 0000000..0b20b96
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ApiMetadata.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+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.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;
+import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
+import static org.jclouds.reflect.Reflection2.typeToken;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule;
+import org.jclouds.digitalocean2.config.DigitalOcean2HttpApiModule;
+import org.jclouds.digitalocean2.config.DigitalOceanParserModule;
+import org.jclouds.oauth.v2.config.OAuthModule;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Implementation of {@link ApiMetadata} for DigitalOcean v2 API
+ */
+public class DigitalOcean2ApiMetadata extends BaseHttpApiMetadata<DigitalOcean2Api> {
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder().fromApiMetadata(this);
+ }
+
+ public DigitalOcean2ApiMetadata() {
+ this(new Builder());
+ }
+
+ protected DigitalOcean2ApiMetadata(Builder builder) {
+ super(builder);
+ }
+
+ public static Properties defaultProperties() {
+ Properties properties = BaseHttpApiMetadata.defaultProperties();
+ properties.put("oauth.endpoint", "https://cloud.digitalocean.com/v1/oauth/token");
+ properties.put(JWS_ALG, "RS256");
+ properties.put(AUDIENCE, "https://cloud.digitalocean.com/v1/oauth/token");
+ properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
+ properties.put(PROPERTY_SESSION_INTERVAL, 3600);
+ properties.put(TEMPLATE, "imageId=ubuntu-14-04-x64");
+ properties.put(POLL_INITIAL_PERIOD, 5000);
+ properties.put(POLL_MAX_PERIOD, 20000);
+ return properties;
+ }
+
+ public static class Builder extends BaseHttpApiMetadata.Builder<DigitalOcean2Api, Builder> {
+
+ protected Builder() {
+ id("digitalocean2")
+ .name("Digital Ocean v2 API")
+ .identityName("Not used for OAuth")
+ .credentialName("Must be oauth2 Bearer Token")
+ .documentation(URI.create("https://developers.digitalocean.com/v2/"))
+ .defaultEndpoint("https://api.digitalocean.com/v2")
+ .defaultProperties(DigitalOcean2ApiMetadata.defaultProperties())
+ .view(typeToken(ComputeServiceContext.class))
+ .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
+ .add(DigitalOcean2HttpApiModule.class)
+ .add(OAuthModule.class)
+ .add(DigitalOceanParserModule.class)
+ .add(DigitalOcean2ComputeServiceContextModule.class)
+ .build());
+ }
+
+ @Override
+ public DigitalOcean2ApiMetadata build() {
+ return new DigitalOcean2ApiMetadata(this);
+ }
+
+ @Override
+ protected Builder self() {
+ return this;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java
new file mode 100644
index 0000000..0f64f78
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/DigitalOcean2ProviderMetadata.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.providers.internal.BaseProviderMetadata;
+
+import com.google.auto.service.AutoService;
+
+/**
+ * Implementation of {@link org.jclouds.providers.ProviderMetadata} for DigitalOcean.
+ */
+@AutoService(ProviderMetadata.class)
+public class DigitalOcean2ProviderMetadata extends BaseProviderMetadata {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return builder().fromProviderMetadata(this);
+ }
+
+ public DigitalOcean2ProviderMetadata() {
+ super(builder());
+ }
+
+ public DigitalOcean2ProviderMetadata(Builder builder) {
+ super(builder);
+ }
+
+ public static Properties defaultProperties() {
+ Properties properties = DigitalOcean2ApiMetadata.defaultProperties();
+ return properties;
+ }
+
+ public static class Builder extends BaseProviderMetadata.Builder {
+
+ protected Builder() {
+ id("digitalocean2")
+ .name("DigitalOcean Compute Services")
+ .apiMetadata(new DigitalOcean2ApiMetadata())
+ .homepage(URI.create("https://www.digitalocean.com/"))
+ .console(URI.create("https://cloud.digitalocean.com/"))
+ .endpoint("https://api.digitalocean.com/v2")
+ .defaultProperties(DigitalOcean2ProviderMetadata.defaultProperties());
+ }
+
+ @Override
+ public DigitalOcean2ProviderMetadata build() {
+ return new DigitalOcean2ProviderMetadata(this);
+ }
+
+ @Override
+ public Builder fromProviderMetadata(ProviderMetadata in) {
+ super.fromProviderMetadata(in);
+ return this;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
new file mode 100644
index 0000000..aa4f656
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
@@ -0,0 +1,191 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.contains;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.getOnlyElement;
+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 javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.DropletCreate;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Predicate;
+import com.google.common.primitives.Ints;
+
+/**
+ * Implementation of the Compute Service for the DigitalOcean API.
+ */
+public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter<Droplet, Size, Image, Region> {
+
+ @Resource
+ @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+ protected Logger logger = Logger.NULL;
+
+ private final DigitalOcean2Api api;
+ private final Predicate<Integer> nodeRunningPredicate;
+ private final Predicate<Integer> nodeStoppedPredicate;
+ private final Predicate<Integer> nodeTerminatedPredicate;
+
+ @Inject DigitalOcean2ComputeServiceAdapter(DigitalOcean2Api api,
+ @Named(TIMEOUT_NODE_RUNNING) Predicate<Integer> nodeRunningPredicate,
+ @Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
+ @Named(TIMEOUT_NODE_TERMINATED) Predicate<Integer> nodeTerminatedPredicate) {
+ this.api = api;
+ this.nodeRunningPredicate = nodeRunningPredicate;
+ this.nodeStoppedPredicate = nodeStoppedPredicate;
+ this.nodeTerminatedPredicate = nodeTerminatedPredicate;
+ }
+
+ @Override
+ public NodeAndInitialCredentials<Droplet> createNodeWithGroupEncodedIntoName(String group, final String name,
+ Template template) {
+ DigitalOcean2TemplateOptions templateOptions = template.getOptions().as(DigitalOcean2TemplateOptions.class);
+ CreateDropletOptions.Builder options = CreateDropletOptions.builder();
+
+ // DigitalOcean specific options
+ options.privateNetworking(templateOptions.getPrivateNetworking());
+ options.backupsEnabled(templateOptions.getBackupsEnabled());
+ if (!templateOptions.getSshKeyIds().isEmpty()) {
+ options.addSshKeyIds(templateOptions.getSshKeyIds());
+ }
+
+ DropletCreate dropletCreated = api.dropletApi().create(name,
+ template.getLocation().getId(),
+ template.getHardware().getProviderId(),
+ template.getImage().getProviderId(),
+ options.build());
+
+ // We have to actively wait until the droplet has been provisioned until
+ // we can build the entire Droplet object we want to return
+ nodeRunningPredicate.apply(getOnlyElement(dropletCreated.links().actions()).id());
+ Droplet droplet = api.dropletApi().get(dropletCreated.droplet().id());
+
+ LoginCredentials defaultCredentials = LoginCredentials.builder().user("root")
+ .privateKey(templateOptions.getLoginPrivateKey()).build();
+
+ return new NodeAndInitialCredentials<Droplet>(droplet, String.valueOf(droplet.id()), defaultCredentials);
+ }
+
+ @Override
+ public Iterable<Image> listImages() {
+ return api.imageApi().list().concat();
+ }
+
+ @Override
+ public Iterable<Size> listHardwareProfiles() {
+ return filter(api.sizeApi().list().concat(), new Predicate<Size>() {
+ @Override
+ public boolean apply(Size size) {
+ return size.available();
+ }
+ });
+ }
+
+ @Override
+ public Iterable<Region> listLocations() {
+ // DigitalOcean lists regions that are unavailable for droplet creation
+ return filter(api.regionApi().list().concat(), new Predicate<Region>() {
+ @Override
+ public boolean apply(Region region) {
+ return region.available();
+ }
+ });
+ }
+
+ @Override
+ public Iterable<Droplet> listNodes() {
+ return api.dropletApi().list().concat();
+ }
+
+ @Override
+ public Iterable<Droplet> listNodesByIds(final Iterable<String> ids) {
+ return filter(listNodes(), new Predicate<Droplet>() {
+ @Override
+ public boolean apply(Droplet droplet) {
+ return contains(ids, String.valueOf(droplet.id()));
+ }
+ });
+ }
+
+ @Override
+ public Image getImage(String id) {
+ // The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is
+ // provided. If it can be parsed as a number, use the method to get by ID. Otherwise, get by slug.
+ Integer imageId = Ints.tryParse(id);
+ return imageId != null ? api.imageApi().get(imageId) : api.imageApi().get(id);
+ }
+
+ @Override
+ public Droplet getNode(String id) {
+ return api.dropletApi().get(Integer.parseInt(id));
+ }
+
+ @Override
+ public void destroyNode(String id) {
+ // We have to wait here, as the api does not properly populate the state
+ // but fails if there is a pending event
+ int dropletId = Integer.parseInt(id);
+ api.dropletApi().delete(dropletId);
+ checkState(nodeTerminatedPredicate.apply(dropletId), "node was not destroyed in the configured timeout");
+ }
+
+ @Override
+ public void rebootNode(String id) {
+ // We have to wait here, as the api does not properly populate the state
+ // but fails if there is a pending event
+ Action action = api.dropletApi().reboot(Integer.parseInt(id));
+ checkState(nodeRunningPredicate.apply(action.id()), "node did not restart in the configured timeout");
+ }
+
+ @Override
+ public void resumeNode(String id) {
+ // We have to wait here, as the api does not properly populate the state
+ // but fails if there is a pending event
+ Action action = api.dropletApi().powerOn(Integer.parseInt(id));
+ checkState(nodeRunningPredicate.apply(action.id()), "node did not started in the configured timeout");
+ }
+
+ @Override
+ public void suspendNode(String id) {
+ int dropletId = Integer.parseInt(id);
+ // We have to wait here, as the api does not properly populate the state
+ // but fails if there is a pending event
+ Action action = api.dropletApi().powerOff(dropletId);
+ checkState(nodeStoppedPredicate.apply(action.id()), "node did not stop in the configured timeout");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/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
new file mode 100644
index 0000000..7809f9d
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/config/DigitalOcean2ComputeServiceContextModule.java
@@ -0,0 +1,205 @@
+/*
+ * 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 com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
+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.util.Predicates2.retry;
+
+import javax.inject.Singleton;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.functions.TemplateOptionsToStatement;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod;
+import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.DigitalOcean2ComputeServiceAdapter;
+import org.jclouds.digitalocean2.compute.extensions.DigitalOcean2ImageExtension;
+import org.jclouds.digitalocean2.compute.functions.DropletStatusToStatus;
+import org.jclouds.digitalocean2.compute.functions.DropletToNodeMetadata;
+import org.jclouds.digitalocean2.compute.functions.ImageToImage;
+import org.jclouds.digitalocean2.compute.functions.RegionToLocation;
+import org.jclouds.digitalocean2.compute.functions.SizeToHardware;
+import org.jclouds.digitalocean2.compute.functions.TemplateOptionsToStatementWithoutPublicKey;
+import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
+import org.jclouds.digitalocean2.compute.strategy.CreateKeyPairsThenCreateNodes;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.domain.Location;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+/**
+ * Configures the compute service classes for the DigitalOcean API.
+ */
+public class DigitalOcean2ComputeServiceContextModule extends
+ ComputeServiceAdapterContextModule<Droplet, Size, Image, Region> {
+
+ @Override
+ protected void configure() {
+ super.configure();
+
+ bind(new TypeLiteral<ComputeServiceAdapter<Droplet, Size, Image, Region>>() {
+ }).to(DigitalOcean2ComputeServiceAdapter.class);
+
+ bind(new TypeLiteral<Function<Droplet, NodeMetadata>>() {
+ }).to(DropletToNodeMetadata.class);
+ bind(new TypeLiteral<Function<Image, org.jclouds.compute.domain.Image>>() {
+ }).to(ImageToImage.class);
+ bind(new TypeLiteral<Function<Region, Location>>() {
+ }).to(RegionToLocation.class);
+ bind(new TypeLiteral<Function<Size, Hardware>>() {
+ }).to(SizeToHardware.class);
+ bind(new TypeLiteral<Function<Droplet.Status, Status>>() {
+ }).to(DropletStatusToStatus.class);
+
+ install(new LocationsFromComputeServiceAdapterModule<Droplet, Size, Image, Region>() {
+ });
+
+ bind(CreateNodesInGroupThenAddToSet.class).to(CreateKeyPairsThenCreateNodes.class);
+ bind(TemplateOptions.class).to(DigitalOcean2TemplateOptions.class);
+ bind(TemplateOptionsToStatement.class).to(TemplateOptionsToStatementWithoutPublicKey.class);
+
+ bind(new TypeLiteral<ImageExtension>() {
+ }).to(DigitalOcean2ImageExtension.class);
+ }
+
+ @Override
+ protected Optional<ImageExtension> provideImageExtension(Injector i) {
+ return Optional.of(i.getInstance(ImageExtension.class));
+ }
+
+ @Provides
+ @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);
+ }
+
+ @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);
+ }
+
+ @Provides
+ @Named(TIMEOUT_NODE_TERMINATED)
+ protected Predicate<Integer> provideDropletTerminatedPredicate(final DigitalOcean2Api api, Timeouts timeouts,
+ PollPeriod pollPeriod) {
+ return retry(new DropletTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
+ pollPeriod.pollMaxPeriod);
+ }
+
+ @Provides
+ @Named(TIMEOUT_IMAGE_AVAILABLE)
+ protected Predicate<Integer> provideImageAvailablePredicate(final DigitalOcean2Api api, Timeouts timeouts,
+ PollPeriod pollPeriod) {
+ return retry(new ActionDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
+ pollPeriod.pollMaxPeriod);
+ }
+
+ @Provides
+ @Singleton
+ protected Predicate<Region> provideRegionAvailablePredicate(final DigitalOcean2Api api, Timeouts timeouts,
+ PollPeriod pollPeriod) {
+ return retry(new RegionAvailablePredicate(), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
+ pollPeriod.pollMaxPeriod);
+ }
+
+ @Provides
+ protected Predicate<Integer> provideActionCompletedPredicate(final DigitalOcean2Api api, Timeouts timeouts,
+ PollPeriod pollPeriod) {
+ return retry(new ActionDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
+ pollPeriod.pollMaxPeriod);
+ }
+
+ @VisibleForTesting
+ static class ActionDonePredicate implements Predicate<Integer> {
+
+ private final DigitalOcean2Api api;
+
+ public ActionDonePredicate(DigitalOcean2Api api) {
+ this.api = checkNotNull(api, "api must not be null");
+ }
+
+ @Override
+ public boolean apply(Integer input) {
+ checkNotNull(input, "action id cannot be null");
+ Action current = api.actionApi().get(input);
+ switch (current.status()) {
+ case COMPLETED:
+ return true;
+ case IN_PROGRESS:
+ return false;
+ case ERRORED:
+ default:
+ throw new IllegalStateException("Resource is in invalid status: " + current.status().name());
+ }
+ }
+
+ }
+
+ @VisibleForTesting
+ static class DropletTerminatedPredicate implements Predicate<Integer> {
+
+ private final DigitalOcean2Api api;
+
+ public DropletTerminatedPredicate(DigitalOcean2Api api) {
+ this.api = checkNotNull(api, "api must not be null");
+ }
+
+ @Override
+ public boolean apply(Integer input) {
+ checkNotNull(input, "droplet");
+ Droplet droplet = api.dropletApi().get(input);
+ return droplet == null;
+ }
+ }
+
+ @VisibleForTesting
+ static class RegionAvailablePredicate implements Predicate<Region> {
+ @Override
+ public boolean apply(Region input) {
+ return input.available();
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/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
new file mode 100644
index 0000000..41e3270
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/extensions/DigitalOcean2ImageExtension.java
@@ -0,0 +1,132 @@
+/*
+ * 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.extensions;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+
+import java.util.NoSuchElementException;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.domain.CloneImageTemplate;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageTemplate;
+import org.jclouds.compute.domain.ImageTemplateBuilder;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.Droplet.Status;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.primitives.Ints;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * The {@link org.jclouds.compute.extensions.ImageExtension} implementation for the DigitalOcean provider.
+ */
+@Singleton
+public class DigitalOcean2ImageExtension implements ImageExtension {
+
+ @Resource
+ @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+ protected Logger logger = Logger.NULL;
+
+ private final DigitalOcean2Api api;
+ private final Predicate<Integer> imageAvailablePredicate;
+ private final Predicate<Integer> nodeStoppedPredicate;
+ private final Function<org.jclouds.digitalocean2.domain.Image, Image> imageTransformer;
+
+ @Inject DigitalOcean2ImageExtension(DigitalOcean2Api api,
+ @Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<Integer> imageAvailablePredicate,
+ @Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
+ Function<org.jclouds.digitalocean2.domain.Image, Image> imageTransformer) {
+ this.api = api;
+ this.imageAvailablePredicate = imageAvailablePredicate;
+ this.nodeStoppedPredicate = nodeStoppedPredicate;
+ this.imageTransformer = imageTransformer;
+ }
+
+ @Override
+ public ImageTemplate buildImageTemplateFromNode(String name, String id) {
+ Droplet droplet = api.dropletApi().get(Integer.parseInt(id));
+
+ if (droplet == null) {
+ throw new NoSuchElementException("Cannot find droplet with id: " + id);
+ }
+
+ return new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build();
+ }
+
+ @Override
+ public ListenableFuture<Image> createImage(ImageTemplate template) {
+ checkState(template instanceof CloneImageTemplate, "DigitalOcean only supports creating images through cloning.");
+ final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
+
+ // 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");
+
+ Action snapshotEvent = api.dropletApi().snapshot(Integer.parseInt(cloneTemplate.getSourceNodeId()),
+ cloneTemplate.getName());
+
+ logger.info(">> registered new Image, waiting for it to become available");
+
+ // 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
+ public boolean apply(org.jclouds.digitalocean2.domain.Image input) {
+ return input.name().equals(cloneTemplate.getName());
+ }
+ }).get();
+
+ return immediateFuture(imageTransformer.apply(snapshot));
+ }
+
+ @Override
+ public boolean deleteImage(String id) {
+ try {
+ // The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is
+ // provided. If it can be parsed as a number, use the method to destroy by ID. Otherwise, destroy by slug.
+ Integer imageId = Ints.tryParse(id);
+ if (imageId != null) {
+ logger.debug(">> image does not have a slug. Using the id to delete the image...");
+ api.imageApi().delete(imageId);
+ }
+ return true;
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java
new file mode 100644
index 0000000..6edadb9
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatus.java
@@ -0,0 +1,46 @@
+/*
+ * 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.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.digitalocean2.domain.Droplet;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Transforms an {@link org.jclouds.compute.domain.NodeMetadata.Status} to the jclouds portable model.
+ */
+@Singleton
+public class DropletStatusToStatus implements Function<Droplet.Status, Status> {
+
+ private static final Function<Droplet.Status, Status> toPortableStatus = Functions.forMap(
+ ImmutableMap.<Droplet.Status, Status> builder()
+ .put(Droplet.Status.NEW, Status.PENDING)
+ .put(Droplet.Status.ACTIVE, Status.RUNNING)
+ .put(Droplet.Status.ARCHIVE, Status.TERMINATED)
+ .put(Droplet.Status.OFF, Status.SUSPENDED)
+ .build(),
+ Status.UNRECOGNIZED);
+
+ @Override
+ public Status apply(final Droplet.Status input) {
+ return toPortableStatus.apply(input);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
new file mode 100644
index 0000000..eebc121
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadata.java
@@ -0,0 +1,176 @@
+/*
+ * 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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.find;
+import static com.google.common.collect.Iterables.tryFind;
+
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.Networks;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.logging.Logger;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+
+/**
+ * Transforms an {@link Droplet} to the jclouds portable model.
+ */
+@Singleton
+public class DropletToNodeMetadata implements Function<Droplet, NodeMetadata> {
+
+ @Resource
+ @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+ protected Logger logger = Logger.NULL;
+
+ private final Supplier<Map<String, ? extends Image>> images;
+ private final Supplier<Map<String, ? extends Hardware>> hardwares;
+ private final Supplier<Set<? extends Location>> locations;
+ private final Function<Droplet.Status, Status> toPortableStatus;
+ private final GroupNamingConvention groupNamingConvention;
+ private final Map<String, Credentials> credentialStore;
+
+ @Inject
+ DropletToNodeMetadata(Supplier<Map<String, ? extends Image>> images,
+ Supplier<Map<String, ? extends Hardware>> hardwares, @Memoized Supplier<Set<? extends Location>> locations,
+ Function<Droplet.Status, Status> toPortableStatus, GroupNamingConvention.Factory groupNamingConvention,
+ Map<String, Credentials> credentialStore) {
+ this.images = checkNotNull(images, "images cannot be null");
+ this.hardwares = checkNotNull(hardwares, "hardwares cannot be null");
+ this.locations = checkNotNull(locations, "locations cannot be null");
+ this.toPortableStatus = checkNotNull(toPortableStatus, "toPortableStatus cannot be null");
+ this.groupNamingConvention = checkNotNull(groupNamingConvention, "groupNamingConvention cannot be null")
+ .createWithoutPrefix();
+ this.credentialStore = checkNotNull(credentialStore, "credentialStore cannot be null");
+ }
+
+ @Override
+ public NodeMetadata apply(Droplet input) {
+ NodeMetadataBuilder builder = new NodeMetadataBuilder();
+ builder.ids(String.valueOf(input.id()));
+ builder.name(input.name());
+ builder.hostname(input.name());
+ builder.group(groupNamingConvention.extractGroup(input.name()));
+
+ builder.hardware(getHardware(input.sizeSlug()));
+ builder.location(getLocation(input.region()));
+
+ Optional<? extends Image> image = findImage(input.image().id());
+ if (image.isPresent()) {
+ builder.imageId(image.get().getId());
+ builder.operatingSystem(image.get().getOperatingSystem());
+ } else {
+ logger.info(">> image with id %s for droplet %s was not found. "
+ + "This might be because the image that was used to create the droplet has a new id.",
+ input.image().id(), input.id());
+ }
+
+ builder.status(toPortableStatus.apply(input.status()));
+ builder.backendStatus(input.status().name());
+
+ if (!input.getPublicAddresses().isEmpty()) {
+ builder.publicAddresses(FluentIterable
+ .from(input.getPublicAddresses())
+ .transform(new Function<Networks.Address, String>() {
+ @Override
+ public String apply(final Networks.Address input) {
+ return input.ip();
+ }
+ })
+ );
+ }
+
+ if (!input.getPrivateAddresses().isEmpty()) {
+ builder.privateAddresses(FluentIterable
+ .from(input.getPrivateAddresses())
+ .transform(new Function<Networks.Address, String>() {
+ @Override
+ public String apply(final Networks.Address input) {
+ return input.ip();
+ }
+ })
+ );
+ }
+
+ // DigitalOcean does not provide a way to get the credentials.
+ // Try to return them from the credential store
+ Credentials credentials = credentialStore.get("node#" + input.id());
+ if (credentials instanceof LoginCredentials) {
+ builder.credentials(LoginCredentials.class.cast(credentials));
+ }
+
+ return builder.build();
+ }
+
+ protected Optional<? extends Image> findImage(Integer id) {
+ // Try to find the image by ID in the cache. The cache is indexed by slug (for public images) and by id (for
+ // private ones).
+ final String imageId = String.valueOf(id);
+ Optional<? extends Image> image = Optional.fromNullable(images.get().get(imageId));
+ if (!image.isPresent()) {
+ // If it is a public image (indexed by slug) but the "int" form of the id was provided, try to find it in the
+ // whole list of cached images
+ image = tryFind(images.get().values(), new Predicate<Image>() {
+ @Override
+ public boolean apply(Image input) {
+ return input.getProviderId().equals(imageId);
+ }
+ });
+ }
+ return image;
+ }
+
+ protected Hardware getHardware(final String slug) {
+ return Iterables.find(hardwares.get().values(), new Predicate<Hardware>() {
+ @Override
+ public boolean apply(Hardware input) {
+ return input.getId().equals(slug);
+ }
+ });
+ }
+
+ protected Location getLocation(final Region region) {
+ return find(locations.get(), new Predicate<Location>() {
+ @Override
+ public boolean apply(Location location) {
+ return region != null && region.slug().equals(location.getId());
+ }
+ }, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java
new file mode 100644
index 0000000..8f9ad92
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/ImageToImage.java
@@ -0,0 +1,65 @@
+/*
+ * 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.functions;
+
+import static org.jclouds.compute.domain.OperatingSystem.builder;
+
+import javax.inject.Singleton;
+
+import org.jclouds.compute.domain.Image.Status;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.OperatingSystem;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Transforms an {@link Image} to the jclouds portable model.
+ */
+@Singleton
+public class ImageToImage implements Function<Image, org.jclouds.compute.domain.Image> {
+
+ @Override
+ public org.jclouds.compute.domain.Image apply(final Image input) {
+ String description = input.distribution() + " " + input.name();
+ ImageBuilder builder = new ImageBuilder();
+ // Private images don't have a slug
+ builder.id(input.slug() != null ? input.slug() : String.valueOf(input.id()));
+ builder.providerId(String.valueOf(input.id()));
+ builder.name(input.name());
+ builder.description(description);
+ builder.status(Status.AVAILABLE);
+
+ OperatingSystem os = OperatingSystem.create(input.name(), input.distribution());
+
+ builder.operatingSystem(builder()
+ .name(os.distribution().value())
+ .family(os.distribution().osFamily())
+ .description(description)
+ .arch(os.arch())
+ .version(os.version())
+ .is64Bit(os.is64bit())
+ .build());
+
+ ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
+ metadata.put("publicImage", String.valueOf(input.isPublic()));
+ builder.userMetadata(metadata.build());
+
+ return builder.build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
new file mode 100644
index 0000000..4adf240
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/RegionToLocation.java
@@ -0,0 +1,57 @@
+/*
+ * 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.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.location.suppliers.all.JustProvider;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Transforms an {@link Region} to the jclouds portable model.
+ */
+@Singleton
+public class RegionToLocation implements Function<Region, Location> {
+
+ private final JustProvider justProvider;
+
+ @Inject
+ RegionToLocation(JustProvider justProvider) {
+ this.justProvider = checkNotNull(justProvider, "justProvider cannot be null");
+ }
+
+ @Override
+ public Location apply(Region input) {
+ LocationBuilder builder = new LocationBuilder();
+ builder.id(input.slug());
+ builder.description(input.name());
+ builder.scope(LocationScope.REGION);
+ builder.parent(getOnlyElement(justProvider.get()));
+ builder.iso3166Codes(ImmutableSet.<String> of());
+ return builder.build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.java
new file mode 100644
index 0000000..5645d3b
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/SizeToHardware.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.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.digitalocean2.domain.Size;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Transforms an {@link Size} to the jclouds portable model.
+ */
+@Singleton
+public class SizeToHardware implements Function<Size, Hardware> {
+
+ @Override
+ public Hardware apply(Size input) {
+ HardwareBuilder builder = new HardwareBuilder();
+ builder.id(input.slug());
+ builder.providerId(input.slug());
+ builder.name(input.slug());
+ builder.ram(input.memory());
+ // No cpu speed from DigitalOcean API, so assume more cores == faster
+ builder.processor(new Processor(input.vcpus(), input.vcpus()));
+
+ builder.volume(new VolumeBuilder()
+ .size(Float.valueOf(input.disk()))
+ .type(Type.LOCAL)
+ .build());
+
+ ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
+ metadata.put("costPerHour", String.valueOf(input.priceHourly()));
+ metadata.put("costPerMonth", String.valueOf(input.priceMonthly()));
+ builder.userMetadata(metadata.build());
+
+ return builder.build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java
new file mode 100644
index 0000000..52dcb0e
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java
@@ -0,0 +1,59 @@
+/*
+ * 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.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.compute.functions.TemplateOptionsToStatement;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.scriptbuilder.InitScript;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Convert the template options into a statement, but ignoring the public key.
+ * <p>
+ * The {@link org.jclouds.DigitalOcean2ComputeServiceAdapter.compute.strategy.DigitalOceanComputeServiceAdapter} already takes care of
+ * installing it using the {@link org.jclouds.digitalocean.features.KeyPairApi}.
+ */
+@Singleton
+public class TemplateOptionsToStatementWithoutPublicKey extends TemplateOptionsToStatement {
+
+ @Override
+ public Statement apply(TemplateOptions options) {
+ ImmutableList.Builder<Statement> builder = ImmutableList.builder();
+ if (options.getRunScript() != null) {
+ builder.add(options.getRunScript());
+ }
+ if (options.getPrivateKey() != null) {
+ builder.add(new InstallRSAPrivateKey(options.getPrivateKey()));
+ }
+
+ ImmutableList<Statement> bootstrap = builder.build();
+ if (!bootstrap.isEmpty()) {
+ if (options.getTaskName() == null && !(options.getRunScript() instanceof InitScript)) {
+ options.nameTask("bootstrap");
+ }
+ return bootstrap.size() == 1 ? bootstrap.get(0) : new StatementList(bootstrap);
+ }
+
+ return null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java
new file mode 100644
index 0000000..cafcdb1
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptions.java
@@ -0,0 +1,174 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import org.jclouds.compute.options.TemplateOptions;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Custom options for the DigitalOcean API.
+ */
+public class DigitalOcean2TemplateOptions extends TemplateOptions implements Cloneable {
+
+ private Set<Integer> sshKeyIds = ImmutableSet.of();
+ private boolean privateNetworking = false;
+ private boolean backupsEnabled = false;
+ private boolean autoCreateKeyPair = true;
+
+ /**
+ * Enables a private network interface if the region supports private networking.
+ */
+ public DigitalOcean2TemplateOptions privateNetworking(boolean privateNetworking) {
+ this.privateNetworking = privateNetworking;
+ return this;
+ }
+
+ /**
+ * Enabled backups for the droplet.
+ */
+ public DigitalOcean2TemplateOptions backupsEnabled(boolean backupsEnabled) {
+ this.backupsEnabled = backupsEnabled;
+ return this;
+ }
+
+ /**
+ * Sets the ssh key ids to be added to the droplet.
+ */
+ public DigitalOcean2TemplateOptions sshKeyIds(Iterable<Integer> sshKeyIds) {
+ this.sshKeyIds = ImmutableSet.copyOf(checkNotNull(sshKeyIds, "sshKeyIds cannot be null"));
+ return this;
+ }
+
+ /**
+ * Sets whether an SSH key pair should be created automatically.
+ */
+ public DigitalOcean2TemplateOptions autoCreateKeyPair(boolean autoCreateKeyPair) {
+ this.autoCreateKeyPair = autoCreateKeyPair;
+ return this;
+ }
+
+ public Set<Integer> getSshKeyIds() {
+ return sshKeyIds;
+ }
+
+ public boolean getPrivateNetworking() {
+ return privateNetworking;
+ }
+
+ public boolean getBackupsEnabled() {
+ return backupsEnabled;
+ }
+
+ public boolean getAutoCreateKeyPair() {
+ return autoCreateKeyPair;
+ }
+
+ @Override
+ public DigitalOcean2TemplateOptions clone() {
+ DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
+ copyTo(options);
+ return options;
+ }
+
+ @Override
+ public void copyTo(TemplateOptions to) {
+ super.copyTo(to);
+ if (to instanceof DigitalOcean2TemplateOptions) {
+ DigitalOcean2TemplateOptions eTo = DigitalOcean2TemplateOptions.class.cast(to);
+ eTo.privateNetworking(privateNetworking);
+ eTo.backupsEnabled(backupsEnabled);
+ eTo.autoCreateKeyPair(autoCreateKeyPair);
+ eTo.sshKeyIds(sshKeyIds);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(super.hashCode(), backupsEnabled, privateNetworking, autoCreateKeyPair, sshKeyIds);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DigitalOcean2TemplateOptions other = (DigitalOcean2TemplateOptions) obj;
+ return super.equals(other) && equal(this.backupsEnabled, other.backupsEnabled)
+ && equal(this.privateNetworking, other.privateNetworking)
+ && equal(this.autoCreateKeyPair, other.autoCreateKeyPair) && equal(this.sshKeyIds, other.sshKeyIds);
+ }
+
+ @Override
+ public ToStringHelper string() {
+ ToStringHelper toString = super.string().omitNullValues();
+ toString.add("privateNetworking", privateNetworking);
+ toString.add("backupsEnabled", backupsEnabled);
+ if (!sshKeyIds.isEmpty()) {
+ toString.add("sshKeyIds", sshKeyIds);
+ }
+ toString.add("autoCreateKeyPair", autoCreateKeyPair);
+ return toString;
+ }
+
+ public static class Builder {
+
+ /**
+ * @see DigitalOcean2TemplateOptions#privateNetworking
+ */
+ public static DigitalOcean2TemplateOptions privateNetworking(boolean privateNetworking) {
+ DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
+ return options.privateNetworking(privateNetworking);
+ }
+
+ /**
+ * @see DigitalOcean2TemplateOptions#backupsEnabled
+ */
+ public static DigitalOcean2TemplateOptions backupsEnabled(boolean backupsEnabled) {
+ DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
+ return options.backupsEnabled(backupsEnabled);
+ }
+
+ /**
+ * @see DigitalOcean2TemplateOptions#sshKeyIds
+ */
+ public static DigitalOcean2TemplateOptions sshKeyIds(Iterable<Integer> sshKeyIds) {
+ DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
+ return options.sshKeyIds(sshKeyIds);
+ }
+
+ /**
+ * @see DigitalOcean2TemplateOptions#autoCreateKeyPair
+ */
+ public static DigitalOcean2TemplateOptions autoCreateKeyPair(boolean autoCreateKeyPair) {
+ DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
+ return options.autoCreateKeyPair(autoCreateKeyPair);
+ }
+ }
+}
[13/19] jclouds git commit: Prefer polling the status of the node
Posted by na...@apache.org.
Prefer polling the status of the node
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/3fbd399f
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/3fbd399f
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/3fbd399f
Branch: refs/heads/master
Commit: 3fbd399f6b3c35f1cb2e0b0af797c9db5f72fe39
Parents: 83ff38e
Author: Ignasi Barrera <na...@apache.org>
Authored: Wed Oct 28 18:14:42 2015 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Oct 29 15:55:23 2015 +0100
----------------------------------------------------------------------
.../DigitalOcean2ComputeServiceAdapter.java | 3 +--
.../DigitalOcean2TemplateBuilderLiveTest.java | 2 +-
.../features/DropletApiLiveTest.java | 27 +++++++-------------
.../digitalocean2/features/KeyApiLiveTest.java | 2 --
.../internal/BaseDigitalOcean2ApiLiveTest.java | 27 +++++++++++++++-----
5 files changed, 31 insertions(+), 30 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/3fbd399f/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
index 2d76176..f520f45 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceAdapter.java
@@ -42,7 +42,6 @@ import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
-import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.DropletCreate;
import org.jclouds.digitalocean2.domain.Image;
@@ -237,7 +236,7 @@ public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
int dropletId = Integer.parseInt(id);
- Action action = api.dropletApi().powerOff(dropletId);
+ api.dropletApi().powerOff(dropletId);
checkState(nodeStoppedPredicate.apply(dropletId), "node did not stop in the configured timeout");
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/3fbd399f/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
index 8480cc1..ee7b962 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2TemplateBuilderLiveTest.java
@@ -40,7 +40,7 @@ public class DigitalOcean2TemplateBuilderLiveTest extends BaseTemplateBuilderLiv
@Override
public void testDefaultTemplateBuilder() throws IOException {
Template defaultTemplate = view.getComputeService().templateBuilder().build();
- assert defaultTemplate.getImage().getOperatingSystem().getVersion().equals("15.04") : defaultTemplate
+ assert defaultTemplate.getImage().getOperatingSystem().getVersion().equals("15.10") : defaultTemplate
.getImage().getOperatingSystem().getVersion();
assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true);
assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.UBUNTU);
http://git-wip-us.apache.org/repos/asf/jclouds/blob/3fbd399f/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
index f451d2e..c10fa54 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
@@ -16,10 +16,7 @@
*/
package org.jclouds.digitalocean2.features;
-import static com.google.common.collect.Iterables.getOnlyElement;
import static java.util.logging.Logger.getAnonymousLogger;
-import static org.jclouds.digitalocean2.domain.Droplet.Status.ACTIVE;
-import static org.jclouds.digitalocean2.domain.Droplet.Status.OFF;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
@@ -77,8 +74,8 @@ public class DropletApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
public void testCreate() {
DropletCreate dropletCreate = api().create(prefix + "-droplet-livetest", region.slug(), size.slug(), image.slug(),
CreateDropletOptions.builder().backupsEnabled(true).addSshKeyId(key.id()).build());
- assertActionCompleted(getOnlyElement(dropletCreate.links().actions()).id());
dropletId = dropletCreate.droplet().id();
+ assertNodeRunning(dropletId);
Droplet droplet = api().get(dropletId);
assertNotNull(droplet, "Droplet should not be null");
}
@@ -101,10 +98,8 @@ public class DropletApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
@Test(dependsOnMethods = "testListKernels")
public void testPowerOff() {
- Action action = api().powerOff(dropletId);
- assertActionCompleted(action.id());
- Droplet droplet = api().get(dropletId);
- assertEquals(droplet.status(), OFF, "Droplet should be off");
+ api().powerOff(dropletId);
+ assertNodeStopped(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testPowerOff")
@@ -146,29 +141,25 @@ public class DropletApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
@Test(groups = "live", dependsOnMethods = "testSnapshots")
public void testPowerOn() {
// Apparently droplets are automatically powered on after the snapshot process
- Action action = api().powerOff(dropletId);
- assertActionCompleted(action.id());
+ api().powerOff(dropletId);
+ assertNodeStopped(dropletId);
- action = api().powerOn(dropletId);
- assertActionCompleted(action.id());
- Droplet droplet = api().get(dropletId);
- assertEquals(droplet.status(), ACTIVE, "Droplet should be Active");
+ api().powerOn(dropletId);
+ assertNodeRunning(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testPowerOn")
public void testReboot() {
Action action = api().reboot(dropletId);
assertActionCompleted(action.id());
- Droplet droplet = api().get(dropletId);
- assertEquals(droplet.status(), ACTIVE, "Droplet should be off");
+ assertNodeRunning(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testReboot")
public void testPowerCycle() {
Action action = api().powerCycle(dropletId);
assertActionCompleted(action.id());
- Droplet droplet = api().get(dropletId);
- assertEquals(droplet.status(), ACTIVE, "Droplet should be off");
+ assertNodeRunning(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testPowerCycle")
http://git-wip-us.apache.org/repos/asf/jclouds/blob/3fbd399f/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
index 2911d79..e0ec1c9 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
@@ -69,8 +69,6 @@ public class KeyApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
public void testUpdateKey() {
api().update(dsa.id(), "jclouds-test-dsa-updated");
assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated");
- api().update(dsa.fingerprint(), "jclouds-test-dsa-updated2");
- assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated2");
}
@AfterClass(alwaysRun = true)
http://git-wip-us.apache.org/repos/asf/jclouds/blob/3fbd399f/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
index b210c93..ee5bb55 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
@@ -17,16 +17,17 @@
package org.jclouds.digitalocean2.internal;
import static com.google.common.base.Preconditions.checkState;
+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.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
import static org.testng.util.Strings.isNullOrEmpty;
import java.util.Properties;
-import java.util.concurrent.atomic.AtomicReference;
import org.jclouds.apis.BaseApiLiveTest;
import org.jclouds.compute.config.ComputeServiceProperties;
-import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.config.DigitalOcean2RateLimitModule;
import org.jclouds.digitalocean2.domain.Action;
@@ -46,10 +47,10 @@ import com.google.inject.name.Names;
public class BaseDigitalOcean2ApiLiveTest extends BaseApiLiveTest<DigitalOcean2Api> {
- protected Predicate<Integer> actionCompleted;
- protected Predicate<AtomicReference<NodeMetadata>> nodeRunning;
- protected Predicate<Integer> nodeTerminated;
- protected Predicate<Integer> dropletOff;
+ private Predicate<Integer> actionCompleted;
+ private Predicate<Integer> nodeTerminated;
+ private Predicate<Integer> nodeStopped;
+ private Predicate<Integer> nodeRunning;
public BaseDigitalOcean2ApiLiveTest() {
provider = "digitalocean2";
@@ -67,6 +68,10 @@ public class BaseDigitalOcean2ApiLiveTest extends BaseApiLiveTest<DigitalOcean2A
actionCompleted = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){}));
nodeTerminated = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
Names.named(TIMEOUT_NODE_TERMINATED)));
+ nodeStopped = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
+ Names.named(TIMEOUT_NODE_SUSPENDED)));
+ nodeRunning = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
+ Names.named(TIMEOUT_NODE_RUNNING)));
return injector.getInstance(DigitalOcean2Api.class);
}
@@ -81,8 +86,16 @@ public class BaseDigitalOcean2ApiLiveTest extends BaseApiLiveTest<DigitalOcean2A
assertEquals(action.status(), Action.Status.COMPLETED);
}
+ protected void assertNodeStopped(int dropletId) {
+ assertTrue(nodeStopped.apply(dropletId), String.format("Droplet %s did not stop in the configured timeout", dropletId));
+ }
+
+ protected void assertNodeRunning(int dropletId) {
+ assertTrue(nodeRunning.apply(dropletId), String.format("Droplet %s did not start in the configured timeout", dropletId));
+ }
+
protected void assertNodeTerminated(int dropletId) {
- assertEquals(nodeTerminated.apply(dropletId), true, String.format("Timeout waiting for dropletId: %s", dropletId));
+ assertTrue(nodeTerminated.apply(dropletId), String.format("Droplet %s was not terminated in the configured timeout", dropletId));
}
protected Region firstAvailableRegion() {
[09/19] jclouds git commit: JCLOUDS-1024: ImageExtension can take
snapshots of stopped droplets
Posted by na...@apache.org.
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 <na...@apache.org>
Authored: Thu Oct 22 11:14:09 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
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);
+ }
+}
[15/19] jclouds git commit: JCLOUDS-1052: Fix DigitalOcean2
deleteImage
Posted by na...@apache.org.
JCLOUDS-1052: Fix DigitalOcean2 deleteImage
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/1ac6fa84
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/1ac6fa84
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/1ac6fa84
Branch: refs/heads/master
Commit: 1ac6fa8426d645d74409801464101d85bff03625
Parents: 575d39e
Author: Ignasi Barrera <na...@apache.org>
Authored: Tue Dec 22 23:05:29 2015 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Dec 22 23:12:59 2015 +0100
----------------------------------------------------------------------
.../extensions/DigitalOcean2ImageExtension.java | 24 ++++++++++++--------
1 file changed, 14 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/1ac6fa84/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 524e4d1..56e67de 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
@@ -118,17 +118,21 @@ public class DigitalOcean2ImageExtension implements ImageExtension {
@Override
public boolean deleteImage(String id) {
- try {
- // The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is
- // provided. If it can be parsed as a number, use the method to destroy by ID. Otherwise, destroy by slug.
- Integer imageId = Ints.tryParse(id);
- if (imageId != null) {
- logger.debug(">> image does not have a slug. Using the id to delete the image...");
- api.imageApi().delete(imageId);
+ String imageId = ImageInRegion.extractImageId(id);
+ Integer numericId = Ints.tryParse(imageId); // User images don't have a slug, so we expect a numeric id here
+
+ if (numericId != null) {
+ try {
+ logger.debug(">> deleting image %s...", id);
+ api.imageApi().delete(numericId);
+ return true;
+ } catch (Exception ex) {
+ logger.error(ex, ">> error deleting image %s", id);
}
- return true;
- } catch (Exception ex) {
- return false;
+ } else {
+ logger.warn(">> image %s is not a user image and cannot be deleted", id);
}
+
+ return false;
}
}
[17/19] jclouds git commit: Improved DigitalOcean image extension
Posted by na...@apache.org.
Improved DigitalOcean image extension
Do not block when waiting for the images to become available.
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/c1885704
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/c1885704
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/c1885704
Branch: refs/heads/master
Commit: c188570444961347e05b555f5162570139954a4c
Parents: 62f1c0b
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Dec 24 11:29:46 2015 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Wed Jan 20 12:24:46 2016 +0100
----------------------------------------------------------------------
.../extensions/DigitalOcean2ImageExtension.java | 47 ++++++++++++--------
1 file changed, 29 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/c1885704/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 56e67de..3baf146 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
@@ -17,17 +17,18 @@
package org.jclouds.digitalocean2.compute.extensions;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.util.concurrent.Futures.immediateFuture;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
import java.util.NoSuchElementException;
+import java.util.concurrent.Callable;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
+import org.jclouds.Constants;
import org.jclouds.compute.domain.CloneImageTemplate;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
@@ -45,6 +46,8 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.UncheckedTimeoutException;
/**
* The {@link org.jclouds.compute.extensions.ImageExtension} implementation for the DigitalOcean provider.
@@ -60,15 +63,18 @@ public class DigitalOcean2ImageExtension implements ImageExtension {
private final Predicate<Integer> imageAvailablePredicate;
private final Predicate<Integer> nodeStoppedPredicate;
private final Function<ImageInRegion, Image> imageTransformer;
+ private final ListeningExecutorService userExecutor;
@Inject DigitalOcean2ImageExtension(DigitalOcean2Api api,
@Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<Integer> imageAvailablePredicate,
@Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
- Function<ImageInRegion, Image> imageTransformer) {
+ Function<ImageInRegion, Image> imageTransformer,
+ @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor) {
this.api = api;
this.imageAvailablePredicate = imageAvailablePredicate;
this.nodeStoppedPredicate = nodeStoppedPredicate;
this.imageTransformer = imageTransformer;
+ this.userExecutor = userExecutor;
}
@Override
@@ -89,31 +95,36 @@ public class DigitalOcean2ImageExtension implements ImageExtension {
int dropletId = Integer.parseInt(cloneTemplate.getSourceNodeId());
// Droplet needs to be stopped
- Droplet droplet = api.dropletApi().get(dropletId);
+ final Droplet droplet = api.dropletApi().get(dropletId);
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()),
+ final Action snapshotEvent = api.dropletApi().snapshot(Integer.parseInt(cloneTemplate.getSourceNodeId()),
cloneTemplate.getName());
logger.info(">> registered new Image, waiting for it to become available");
- // 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
- public boolean apply(org.jclouds.digitalocean2.domain.Image input) {
- return input.name().equals(cloneTemplate.getName());
- }
- }).get();
-
- // By default snapshots are only available in the Droplet's region
- return immediateFuture(imageTransformer.apply(ImageInRegion.create(snapshot, droplet.region().slug())));
+ return userExecutor.submit(new Callable<Image>() {
+ @Override
+ public Image call() throws Exception {
+ if (imageAvailablePredicate.apply(snapshotEvent.id())) {
+ org.jclouds.digitalocean2.domain.Image snapshot = api.imageApi().list().concat()
+ .firstMatch(new Predicate<org.jclouds.digitalocean2.domain.Image>() {
+ @Override
+ public boolean apply(org.jclouds.digitalocean2.domain.Image input) {
+ return input.name().equals(cloneTemplate.getName());
+ }
+ }).get();
+
+ return imageTransformer.apply(ImageInRegion.create(snapshot, droplet.region().slug()));
+ }
+
+ throw new UncheckedTimeoutException("Image was not created within the time limit: "
+ + cloneTemplate.getName());
+ }
+ });
}
@Override
[03/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean
v2 API
Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
new file mode 100644
index 0000000..9da855c
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.functions;
+
+import static org.jclouds.compute.domain.NodeMetadata.Status.UNRECOGNIZED;
+import static org.testng.Assert.assertNotEquals;
+
+import org.jclouds.digitalocean2.domain.Droplet.Status;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "DropletStatusToStatusTest")
+public class DropletStatusToStatusTest {
+
+ @Test
+ public void testAllStatesHaveMapping() {
+ DropletStatusToStatus function = new DropletStatusToStatus();
+ for (Status status : Status.values()) {
+ assertNotEquals(function.apply(status), UNRECOGNIZED);
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
new file mode 100644
index 0000000..27dbad9
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
+import static org.jclouds.compute.domain.NodeMetadata.Status.RUNNING;
+import static org.jclouds.digitalocean2.domain.Droplet.Status.ACTIVE;
+import static org.testng.Assert.assertEquals;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.Networks;
+import org.jclouds.digitalocean2.domain.Networks.Address;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.domain.LoginCredentials;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.inject.Guice;
+
+@Test(groups = "unit", testName = "DropletToNodeMetadataTest")
+public class DropletToNodeMetadataTest {
+
+ private org.jclouds.digitalocean2.domain.Image image;
+
+ private Region region;
+
+ private Set<Hardware> hardwares;
+
+ private Set<Image> images;
+
+ private Set<Location> locations;
+
+ private LoginCredentials credentials;
+
+ private DropletToNodeMetadata function;
+
+ @BeforeMethod
+ public void setup() {
+ image = org.jclouds.digitalocean2.domain.Image.create(1, "14.04 x64",
+ "distribution", "Ubuntu", "ubuntu-1404-x86", true, ImmutableList.of("sfo1"), new Date());
+ region = Region.create("sfo1", "San Francisco 1", ImmutableList.of("2gb"), true, ImmutableList.<String> of());
+
+ images = ImmutableSet.of(new ImageBuilder()
+ .id("ubuntu-1404-x86")
+ .providerId("1")
+ .name("mock image")
+ .status(AVAILABLE)
+ .operatingSystem(
+ OperatingSystem.builder().name("Ubuntu 14.04 x86_64").description("Ubuntu").family(OsFamily.UBUNTU)
+ .version("10.04").arch("x86_64").is64Bit(true).build()).build());
+
+ hardwares = ImmutableSet.of(new HardwareBuilder().id("2gb").providerId("2gb").name("mock hardware")
+ .processor(new Processor(1.0, 1.0)).ram(2048)
+ .volume(new VolumeBuilder().size(20f).type(Type.LOCAL).build()).build());
+
+ locations = ImmutableSet.of(new LocationBuilder()
+ .id("sfo1")
+ .description("sfo1/San Francisco 1")
+ .scope(LocationScope.REGION)
+ .parent(
+ new LocationBuilder().id("0").description("mock parent location").scope(LocationScope.PROVIDER)
+ .build()).build());
+
+ credentials = LoginCredentials.builder().user("foo").password("bar").build();
+
+ function = createNodeParser(hardwares, images, locations, ImmutableMap.of("node#1", (Credentials) credentials));
+ }
+
+ @Test
+ public void testConvertDroplet() throws ParseException {
+ Droplet droplet = Droplet.create(
+ 1,
+ "mock-droplet",
+ 1,
+ 1,
+ 1,
+ false,
+ new Date(),
+ Droplet.Status.ACTIVE,
+ ImmutableList.<Integer> of(),
+ ImmutableList.<Integer> of(),
+ ImmutableList.<String> of(),
+ region,
+ image,
+ null,
+ "2gb",
+ Networks.create(
+ ImmutableList.of(Address.create("84.45.69.3", "255.255.255.0", "84.45.69.1", "public"),
+ Address.create("192.168.2.5", "255.255.255.0", "192.168.2.1", "private")),
+ ImmutableList.<Networks.Address> of()), null);
+
+ NodeMetadata expected = new NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares))
+ .imageId("ubuntu-1404-x86").status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet")
+ .hostname("mock-droplet").group("mock").credentials(credentials)
+ .publicAddresses(ImmutableSet.of("84.45.69.3")).privateAddresses(ImmutableSet.of("192.168.2.5"))
+ .providerId("1").backendStatus(ACTIVE.name()).operatingSystem(getOnlyElement(images).getOperatingSystem())
+ .build();
+
+ NodeMetadata actual = function.apply(droplet);
+ assertNodeEquals(actual, expected);
+ }
+
+ @Test
+ public void testConvertDropletOldImage() throws ParseException {
+ // Use an image id that is not in the list of images
+ org.jclouds.digitalocean2.domain.Image image = org.jclouds.digitalocean2.domain.Image.create(2, "14.04 x64",
+ "distribution", "Ubuntu", "ubuntu2-1404-x86", true, ImmutableList.of("sfo1"), new Date());
+
+ Droplet droplet = Droplet.create(
+ 1,
+ "mock-droplet",
+ 1,
+ 1,
+ 1,
+ false,
+ new Date(),
+ Droplet.Status.ACTIVE,
+ ImmutableList.<Integer> of(),
+ ImmutableList.<Integer> of(),
+ ImmutableList.<String> of(),
+ region,
+ image,
+ null,
+ "2gb",
+ Networks.create(
+ ImmutableList.of(Address.create("84.45.69.3", "255.255.255.0", "84.45.69.1", "public"),
+ Address.create("192.168.2.5", "255.255.255.0", "192.168.2.1", "private")),
+ ImmutableList.<Networks.Address> of()), null);
+
+ NodeMetadata expected = new NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares)).imageId(null)
+ .status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet").hostname("mock-droplet")
+ .group("mock").credentials(credentials).publicAddresses(ImmutableSet.of("84.45.69.3"))
+ .privateAddresses(ImmutableSet.of("192.168.2.5")).providerId("1").backendStatus(ACTIVE.name())
+ .operatingSystem(null).build();
+
+ NodeMetadata actual = function.apply(droplet);
+ assertNodeEquals(actual, expected);
+ }
+
+ private static void assertNodeEquals(NodeMetadata actual, NodeMetadata expected) {
+ assertEquals(actual, expected);
+ // NodeMetadata equals method does not use all fields in equals. It assumes that same ids in same locations
+ // determine the equivalence
+ assertEquals(actual.getStatus(), expected.getStatus());
+ assertEquals(actual.getBackendStatus(), expected.getBackendStatus());
+ assertEquals(actual.getLoginPort(), expected.getLoginPort());
+ assertEquals(actual.getPublicAddresses(), expected.getPublicAddresses());
+ assertEquals(actual.getPrivateAddresses(), expected.getPrivateAddresses());
+ assertEquals(actual.getCredentials(), expected.getCredentials());
+ assertEquals(actual.getGroup(), expected.getGroup());
+ assertEquals(actual.getImageId(), expected.getImageId());
+ assertEquals(actual.getHardware(), expected.getHardware());
+ assertEquals(actual.getOperatingSystem(), expected.getOperatingSystem());
+ assertEquals(actual.getHostname(), expected.getHostname());
+ }
+
+ private DropletToNodeMetadata createNodeParser(final Set<Hardware> hardware, final Set<Image> images,
+ final Set<Location> locations, Map<String, Credentials> credentialStore) {
+ Supplier<Set<? extends Location>> locationSupplier = new Supplier<Set<? extends Location>>() {
+ @Override
+ public Set<? extends Location> get() {
+ return locations;
+ }
+ };
+
+ Supplier<Map<String, ? extends Hardware>> hardwareSupplier = new Supplier<Map<String, ? extends Hardware>>() {
+ @Override
+ public Map<String, ? extends Hardware> get() {
+ return Maps.uniqueIndex(hardware, new Function<Hardware, String>() {
+ @Override
+ public String apply(Hardware input) {
+ return input.getId();
+ }
+ });
+ }
+ };
+
+ Supplier<Map<String, ? extends Image>> imageSupplier = new Supplier<Map<String, ? extends Image>>() {
+ @Override
+ public Map<String, ? extends Image> get() {
+ return Maps.uniqueIndex(images, new Function<Image, String>() {
+ @Override
+ public String apply(Image input) {
+ return input.getId();
+ }
+ });
+ }
+ };
+
+ GroupNamingConvention.Factory namingConvention = Guice.createInjector().getInstance(GroupNamingConvention.Factory.class);
+
+ return new DropletToNodeMetadata(imageSupplier, hardwareSupplier, locationSupplier, new DropletStatusToStatus(),
+ namingConvention, credentialStore);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
new file mode 100644
index 0000000..6ab020c
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.functions;
+
+import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Date;
+
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.digitalocean2.domain.Image;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@Test(groups = "unit", testName = "ImageToImageTest")
+public class ImageToImageTest {
+
+ @Test
+ public void testConvertImage() {
+ Image image = Image.create(1, "14.04 x64", "distribution", "Ubuntu", "ubuntu-1404-x86", true,
+ ImmutableList.of("sfo1"), new Date());
+ org.jclouds.compute.domain.Image expected = new ImageBuilder()
+ .id("ubuntu-1404-x86")
+ .providerId("1")
+ .name("14.04 x64")
+ .description("Ubuntu 14.04 x64")
+ .status(AVAILABLE)
+ .operatingSystem(
+ OperatingSystem.builder().name("Ubuntu").description("Ubuntu 14.04 x64").family(OsFamily.UBUNTU)
+ .version("14.04").arch("x64").is64Bit(true).build())
+ .userMetadata(ImmutableMap.of("publicImage", "true")).build();
+
+ org.jclouds.compute.domain.Image result = new ImageToImage().apply(image);
+ assertEquals(result, expected);
+ assertEquals(result.getDescription(), expected.getDescription());
+ assertEquals(result.getOperatingSystem(), expected.getOperatingSystem());
+ assertEquals(result.getStatus(), expected.getStatus());
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
new file mode 100644
index 0000000..879091b
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.DigitalOcean2ProviderMetadata;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.location.suppliers.all.JustProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "unit", testName = "RegionToLocationTest")
+public class RegionToLocationTest {
+
+ @Test
+ public void testConvertRegion() {
+ DigitalOcean2ProviderMetadata metadata = new DigitalOcean2ProviderMetadata();
+ JustProvider locationsSupplier = new JustProvider(metadata.getId(), Suppliers.<URI> ofInstance(URI
+ .create(metadata.getEndpoint())), ImmutableSet.<String> of());
+
+ Region region = Region.create("reg1", "Region1", ImmutableList.<String> of(), true, ImmutableList.<String> of());
+ Location expected = new LocationBuilder().id("reg1").description("reg1/Region 1")
+ .parent(getOnlyElement(locationsSupplier.get())).scope(LocationScope.REGION).build();
+
+ RegionToLocation function = new RegionToLocation(locationsSupplier);
+ assertEquals(function.apply(region), expected);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
new file mode 100644
index 0000000..cba55bf
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.digitalocean2.domain.Size;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Unit tests for the {@link SizeToHardware} class.
+ */
+@Test(groups = "unit", testName = "SizeToHardwareTest")
+public class SizeToHardwareTest {
+
+ @Test
+ public void testConvertSize() {
+ Size size = Size.create("2gb", true, 1.0f, 10f, 0.05f, 2048, 1, 20, ImmutableList.<String> of());
+ Hardware expected = new HardwareBuilder().id("2gb").providerId("2gb").name("2gb")
+ .processor(new Processor(1.0, 1.0)).ram(2048)
+ .volume(new VolumeBuilder().size(20f).type(Type.LOCAL).build())
+ .userMetadata(ImmutableMap.of("costPerHour", "0.05", "costPerMonth", "10")).build();
+
+ SizeToHardware function = new SizeToHardware();
+ assertEquals(function.apply(size), expected);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
new file mode 100644
index 0000000..c3a6cd2
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.functions;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.scriptbuilder.domain.OsFamily;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
+import org.jclouds.ssh.SshKeys;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link TemplateOptionsToStatementWithoutPublicKey} class.
+ */
+@Test(groups = "unit", testName = "TemplateOptionsToStatementWithoutPublicKeyTest")
+public class TemplateOptionsToStatementWithoutPublicKeyTest {
+
+ @Test
+ public void testPublicKeyDoesNotGenerateAuthorizePublicKeyStatementIfOnlyPublicKeyOptionsConfigured() {
+ Map<String, String> keys = SshKeys.generate();
+ TemplateOptions options = TemplateOptions.Builder.authorizePublicKey(keys.get("public"));
+
+ TemplateOptionsToStatementWithoutPublicKey function = new TemplateOptionsToStatementWithoutPublicKey();
+ assertNull(function.apply(options));
+ }
+
+ @Test
+ public void testPublicAndRunScriptKeyDoesNotGenerateAuthorizePublicKeyStatementIfRunScriptPresent() {
+ Map<String, String> keys = SshKeys.generate();
+ TemplateOptions options = TemplateOptions.Builder.authorizePublicKey(keys.get("public")).runScript("uptime");
+
+ TemplateOptionsToStatementWithoutPublicKey function = new TemplateOptionsToStatementWithoutPublicKey();
+ Statement statement = function.apply(options);
+
+ assertEquals(statement.render(OsFamily.UNIX), "uptime\n");
+ }
+
+ @Test
+ public void testPublicAndPrivateKeyAndRunScriptDoesNotGenerateAuthorizePublicKeyStatementIfOtherOptionsPresent() {
+ Map<String, String> keys = SshKeys.generate();
+ TemplateOptions options = TemplateOptions.Builder.authorizePublicKey(keys.get("public"))
+ .installPrivateKey(keys.get("private")).runScript("uptime");
+
+ TemplateOptionsToStatementWithoutPublicKey function = new TemplateOptionsToStatementWithoutPublicKey();
+ Statement statement = function.apply(options);
+
+ assertTrue(statement instanceof StatementList);
+ StatementList statements = (StatementList) statement;
+
+ assertEquals(statements.size(), 2);
+ assertEquals(statements.get(0).render(OsFamily.UNIX), "uptime\n");
+ assertTrue(statements.get(1) instanceof InstallRSAPrivateKey);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
new file mode 100644
index 0000000..982224c
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.options;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.compute.options.TemplateOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "unit", testName = "DigitalOcean2TemplateOptionsTest")
+public class DigitalOcean2TemplateOptionsTest {
+
+ @Test
+ public void testSShKeyIds() {
+ TemplateOptions options = new DigitalOcean2TemplateOptions().sshKeyIds(ImmutableSet.of(1, 2, 3));
+ assertEquals(options.as(DigitalOcean2TemplateOptions.class).getSshKeyIds(), ImmutableSet.of(1, 2, 3));
+ }
+
+ @Test
+ public void testPrivateNetworking() {
+ TemplateOptions options = new DigitalOcean2TemplateOptions().privateNetworking(true);
+ assertEquals(options.as(DigitalOcean2TemplateOptions.class).getPrivateNetworking(), true);
+ }
+
+ @Test
+ public void testBackupsEnabled() {
+ TemplateOptions options = new DigitalOcean2TemplateOptions().backupsEnabled(true);
+ assertEquals(options.as(DigitalOcean2TemplateOptions.class).getBackupsEnabled(), true);
+ }
+
+ @Test
+ public void testAutoCreateKeyPair() {
+ TemplateOptions options = new DigitalOcean2TemplateOptions().autoCreateKeyPair(false);
+ assertEquals(options.as(DigitalOcean2TemplateOptions.class).getAutoCreateKeyPair(), false);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
new file mode 100644
index 0000000..d6bd0fc
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.domain;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "OperatingSystemTest")
+public class OperatingSystemTest {
+
+ public void testParseStandard64bit() {
+ OperatingSystem os = OperatingSystem.create("12.10 x64", "Ubuntu");
+
+ assertEquals(os.distribution(), Distribution.UBUNTU);
+ assertEquals(os.version(), "12.10");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+ }
+
+ public void testLongVersionStandard64bit() {
+ OperatingSystem os = OperatingSystem.create("12.10.1 x64", "Ubuntu");
+
+ assertEquals(os.distribution(), Distribution.UBUNTU);
+ assertEquals(os.version(), "12.10.1");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+ }
+
+ public void testParseStandard64bitWithPrefix() {
+ OperatingSystem os = OperatingSystem.create("Arch Linux 12.10 x64 Desktop", "Arch Linux");
+
+ assertEquals(os.distribution(), Distribution.ARCHLINUX);
+ assertEquals(os.version(), "12.10");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+ }
+
+ public void testParseStandard() {
+ OperatingSystem os = OperatingSystem.create("12.10 x32", "Ubuntu");
+
+ assertEquals(os.distribution(), Distribution.UBUNTU);
+ assertEquals(os.version(), "12.10");
+ assertEquals(os.arch(), "x32");
+ assertFalse(os.is64bit());
+
+ os = OperatingSystem.create("6.5 x64", "CentOS");
+
+ assertEquals(os.distribution(), Distribution.CENTOS);
+ assertEquals(os.version(), "6.5");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+
+ os = OperatingSystem.create("6.5 x64", "Centos");
+
+ assertEquals(os.distribution(), Distribution.CENTOS);
+ assertEquals(os.version(), "6.5");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+ }
+
+ public void testParseNoArch() {
+ OperatingSystem os = OperatingSystem.create("12.10", "Ubuntu");
+
+ assertEquals(os.distribution(), Distribution.UBUNTU);
+ assertEquals(os.version(), "12.10");
+ assertEquals(os.arch(), "");
+ assertFalse(os.is64bit());
+ }
+
+ public void testParseNoVersion() {
+ OperatingSystem os = OperatingSystem.create("x64", "Ubuntu");
+
+ assertEquals(os.distribution(), Distribution.UBUNTU);
+ assertEquals(os.version(), "");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+ }
+
+ public void testParseUnknownDistribution() {
+ OperatingSystem os = OperatingSystem.create("12.04 x64", "Foo");
+
+ assertEquals(os.distribution(), Distribution.UNRECOGNIZED);
+ assertEquals(os.version(), "12.04");
+ assertEquals(os.arch(), "x64");
+ assertTrue(os.is64bit());
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
new file mode 100644
index 0000000..44a9a17
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.features;
+
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "ActionApiLiveTest")
+public class ActionApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+ public void testListActions() {
+ final AtomicInteger found = new AtomicInteger(0);
+ // DigitalOcean return 25 records per page by default. Inspect at most 2 pages
+ assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Action>() {
+ @Override
+ public boolean apply(Action input) {
+ found.incrementAndGet();
+ return !isNullOrEmpty(input.type());
+ }
+ }), "All actions must have the 'type' field populated");
+ assertTrue(found.get() > 0, "Expected some actions to be returned");
+ }
+
+ public void testListActionsOnePage() {
+ final AtomicInteger found = new AtomicInteger(0);
+ assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Action>() {
+ @Override
+ public boolean apply(Action input) {
+ found.incrementAndGet();
+ return !isNullOrEmpty(input.type());
+ }
+ }), "All actions must have the 'type' field populated");
+ assertTrue(found.get() > 0, "Expected some actions to be returned");
+ }
+
+ public void testGetAction() {
+ Optional<Action> first = api().list().concat().first();
+ assertTrue(first.isPresent(), "At least one action was expected to exist");
+ assertNotNull(api().get(first.get().id()));
+ }
+
+ private ActionApi api() {
+ return api.actionApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
new file mode 100644
index 0000000..aa890d5
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "ActionApiMockTest", singleThreaded = true)
+public class ActionApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ public void testListActions() throws InterruptedException {
+ server.enqueue(jsonResponse("/actions-first.json"));
+ server.enqueue(jsonResponse("/actions-last.json"));
+
+ Iterable<Action> actions = api.actionApi().list().concat();
+
+ assertEquals(size(actions), 8); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/actions");
+ assertSent(server, "GET", "/actions?page=2&per_page=5");
+ }
+
+ public void testListActionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Action> actions = api.actionApi().list().concat();
+
+ assertTrue(isEmpty(actions));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/actions");
+ }
+
+ public void testListActionsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/actions-first.json"));
+
+ Iterable<Action> actions = api.actionApi().list(page(1).perPage(5));
+
+ assertEquals(size(actions), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/actions?page=1&per_page=5");
+ }
+
+ public void testListActionsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Action> actions = api.actionApi().list(page(1).perPage(5));
+
+ assertTrue(isEmpty(actions));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/actions?page=1&per_page=5");
+ }
+
+ public void testGetAction() throws InterruptedException {
+ server.enqueue(jsonResponse("/action.json"));
+
+ Action action = api.actionApi().get(1);
+
+ assertEquals(action, actionFromResource("/action.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/actions/1");
+ }
+
+ public void testGetActionReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Action action = api.actionApi().get(1);
+
+ assertNull(action);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/actions/1");
+ }
+
+ private Action actionFromResource(String resource) {
+ return onlyObjectFromResource(resource, new TypeToken<Map<String, Action>>() {
+ private static final long serialVersionUID = 1L;
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
new file mode 100644
index 0000000..f451d2e
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static java.util.logging.Logger.getAnonymousLogger;
+import static org.jclouds.digitalocean2.domain.Droplet.Status.ACTIVE;
+import static org.jclouds.digitalocean2.domain.Droplet.Status.OFF;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.compute.ComputeTestUtils;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Backup;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.DropletCreate;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.Kernel;
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.digitalocean2.domain.Snapshot;
+import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+
+@Test(groups = "live", testName = "DropletApiLiveTest")
+public class DropletApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+ private Region region;
+ private Size size;
+ private Image image;
+ private Key key;
+ private int dropletId = -1;
+
+ @BeforeClass
+ public void setupDroplet() {
+ region = firstAvailableRegion();
+ size = cheapestSizeInRegion(region);
+ image = ubuntuImageInRegion(region);
+
+ Map<String, String> keyPair = ComputeTestUtils.setupKeyPair();
+ key = api.keyApi().create(prefix + "-droplet-livetest", keyPair.get("public"));
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void tearDown() {
+ if (key != null) {
+ api.keyApi().delete(key.id());
+ }
+ }
+
+ public void testCreate() {
+ DropletCreate dropletCreate = api().create(prefix + "-droplet-livetest", region.slug(), size.slug(), image.slug(),
+ CreateDropletOptions.builder().backupsEnabled(true).addSshKeyId(key.id()).build());
+ assertActionCompleted(getOnlyElement(dropletCreate.links().actions()).id());
+ dropletId = dropletCreate.droplet().id();
+ Droplet droplet = api().get(dropletId);
+ assertNotNull(droplet, "Droplet should not be null");
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testCreate")
+ public void testListDroplets() {
+ assertTrue(api().list().concat().anyMatch(new Predicate<Droplet>() {
+ @Override
+ public boolean apply(Droplet input) {
+ return input.id() == dropletId;
+ }
+ }), "The created droplet must be in the list");
+ }
+
+ @Test(dependsOnMethods = "testCreate")
+ public void testListKernels() {
+ Iterable<Kernel> kernels = api().listKernels(dropletId).concat();
+ assertEquals(kernels.iterator().next().name(), "DO-recovery-static-fsck");
+ }
+
+ @Test(dependsOnMethods = "testListKernels")
+ public void testPowerOff() {
+ Action action = api().powerOff(dropletId);
+ assertActionCompleted(action.id());
+ Droplet droplet = api().get(dropletId);
+ assertEquals(droplet.status(), OFF, "Droplet should be off");
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testPowerOff")
+ public void testSnapshots() {
+ Action action = api().snapshot(dropletId, prefix + dropletId + "-snapshot");
+ assertActionCompleted(action.id());
+
+ List<Snapshot> snapshots = api().listSnapshots(dropletId).concat().toList();
+ assertEquals(snapshots.size(), 1, "Must contain 1 snapshot");
+
+ for (Snapshot snapshot : snapshots) {
+ try {
+ api.imageApi().delete(snapshot.id());
+ } catch (Exception ex) {
+ getAnonymousLogger().warning("Could not delete snapshot: " + snapshot.id());
+ }
+ }
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testSnapshots")
+ public void testBackups() {
+ Iterable<Backup> backups = api().listBackups(dropletId).concat();
+ // Backups are automatically taken by DO on a weekly basis, so we can't guarantee
+ // there will be any backup available. Just check that the call succeeds
+ assertNotNull(backups);
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testSnapshots")
+ public void testListActions() {
+ FluentIterable<Action> actions = api().listActions(dropletId).concat();
+ assertTrue(actions.anyMatch(new Predicate<Action>() {
+ @Override
+ public boolean apply(Action input) {
+ return "snapshot".equals(input.type());
+ }
+ }));
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testSnapshots")
+ public void testPowerOn() {
+ // Apparently droplets are automatically powered on after the snapshot process
+ Action action = api().powerOff(dropletId);
+ assertActionCompleted(action.id());
+
+ action = api().powerOn(dropletId);
+ assertActionCompleted(action.id());
+ Droplet droplet = api().get(dropletId);
+ assertEquals(droplet.status(), ACTIVE, "Droplet should be Active");
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testPowerOn")
+ public void testReboot() {
+ Action action = api().reboot(dropletId);
+ assertActionCompleted(action.id());
+ Droplet droplet = api().get(dropletId);
+ assertEquals(droplet.status(), ACTIVE, "Droplet should be off");
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testReboot")
+ public void testPowerCycle() {
+ Action action = api().powerCycle(dropletId);
+ assertActionCompleted(action.id());
+ Droplet droplet = api().get(dropletId);
+ assertEquals(droplet.status(), ACTIVE, "Droplet should be off");
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testPowerCycle")
+ public void testShutdown() {
+ Action action = api().shutdown(dropletId);
+ assertActionCompleted(action.id());
+ // The shutdown action can fail if the shutdown command fails in the guest OS
+ // We can not guarantee that a graceful shutdown action will en up in the droplet
+ // being in OFF state
+ }
+
+ @Test(groups = "live", dependsOnMethods = "testShutdown", alwaysRun = true)
+ public void testDelete() throws InterruptedException {
+ if (dropletId != -1) {
+ api().delete(dropletId);
+ assertNodeTerminated(dropletId);
+ assertNull(api().get(dropletId));
+ }
+ }
+
+ private DropletApi api() {
+ return api.dropletApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
new file mode 100644
index 0000000..dcd6352
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
@@ -0,0 +1,401 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Backup;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.DropletCreate;
+import org.jclouds.digitalocean2.domain.Kernel;
+import org.jclouds.digitalocean2.domain.Snapshot;
+import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "DropletApiMockTest", singleThreaded = true)
+public class DropletApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ public void testListDroplets() throws InterruptedException {
+ server.enqueue(jsonResponse("/droplets-first.json"));
+ server.enqueue(jsonResponse("/droplets-last.json"));
+
+ Iterable<Droplet> droplets = api.dropletApi().list().concat();
+
+ assertEquals(size(droplets), 2); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/droplets");
+ assertSent(server, "GET", "/droplets?page=2&per_page=1");
+ }
+
+ public void testListDropletsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Droplet> droplets = api.dropletApi().list().concat();
+
+ assertTrue(isEmpty(droplets));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets");
+ }
+
+ public void testListDropletsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/droplets-first.json"));
+
+ Iterable<Droplet> droplets = api.dropletApi().list(page(1).perPage(20));
+
+ assertEquals(size(droplets), 1);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/droplets?page=1&per_page=20");
+ }
+
+ public void testListDropletsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Droplet> droplets = api.dropletApi().list(page(1).perPage(20));
+
+ assertTrue(isEmpty(droplets));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets?page=1&per_page=20");
+ }
+
+ public void testGetDroplet() throws InterruptedException {
+ server.enqueue(jsonResponse("/droplet.json"));
+
+ Droplet droplet = api.dropletApi().get(1);
+
+ assertEquals(droplet, dropletFromResource("/droplet.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/1");
+ }
+
+ public void testGetDropletReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Droplet droplet = api.dropletApi().get(1);
+
+ assertNull(droplet);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/1");
+ }
+
+ public void testCreateDroplet() throws InterruptedException {
+ server.enqueue(jsonResponse("/droplet-create-res.json"));
+
+ DropletCreate droplet = api.dropletApi().create("digitalocean2-s-d5e", "sfo1", "512mb", "6374124", CreateDropletOptions.builder().addSshKeyId(421192).build());
+
+ assertEquals(droplet, objectFromResource("/droplet-create-res.json", DropletCreate.class));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets", stringFromResource("/droplet-create-req.json"));
+ }
+
+ public void testListKernels() throws InterruptedException {
+ server.enqueue(jsonResponse("/kernels-first.json"));
+ server.enqueue(jsonResponse("/kernels-last.json"));
+
+ Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561).concat();
+
+ assertEquals(size(kernels), 10); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/droplets/5425561/kernels");
+ assertSent(server, "GET", "/droplets/5425561/kernels?page=2");
+ }
+
+ public void testListKernelsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561).concat();
+
+ assertTrue(isEmpty(kernels));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/5425561/kernels");
+ }
+
+ public void testListKernelsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/kernels-first.json"));
+
+ Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561, page(1).perPage(20));
+
+ assertEquals(size(kernels), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/droplets/5425561/kernels?page=1&per_page=20");
+ }
+
+ public void testListKernelsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561, page(1).perPage(20));
+
+ assertTrue(isEmpty(kernels));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/5425561/kernels?page=1&per_page=20");
+ }
+
+ public void testListActions() throws InterruptedException {
+ server.enqueue(jsonResponse("/actions-first.json"));
+ server.enqueue(jsonResponse("/actions-last.json"));
+
+ Iterable<Action> actions = api.dropletApi().listActions(1).concat();
+
+ assertEquals(size(actions), 8); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/droplets/1/actions");
+ assertSent(server, "GET", "/droplets/1/actions?page=2&per_page=5");
+ }
+
+ public void testListActionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Action> actions = api.dropletApi().listActions(1).concat();
+
+ assertTrue(isEmpty(actions));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/1/actions");
+ }
+
+ public void testListActionsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/actions-first.json"));
+
+ Iterable<Action> actions = api.dropletApi().listActions(1, page(1).perPage(5));
+
+ assertEquals(size(actions), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/droplets/1/actions?page=1&per_page=5");
+ }
+
+ public void testListActionsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Action> actions = api.dropletApi().listActions(1, page(1).perPage(5));
+
+ assertTrue(isEmpty(actions));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/1/actions?page=1&per_page=5");
+ }
+
+ public void testListBackups() throws InterruptedException {
+ server.enqueue(jsonResponse("/backups-first.json"));
+ server.enqueue(jsonResponse("/backups-last.json"));
+
+ Iterable<Backup> backups = api.dropletApi().listBackups(5425561).concat();
+
+ assertEquals(size(backups), 2); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/droplets/5425561/backups");
+ assertSent(server, "GET", "/droplets/5425561/backups?page=2");
+ }
+
+ public void testListBackupsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Backup> backups = api.dropletApi().listBackups(5425561).concat();
+
+ assertTrue(isEmpty(backups));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/5425561/backups");
+ }
+
+ public void testListBackupsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/backups-first.json"));
+
+ Iterable<Backup> backups = api.dropletApi().listBackups(5425561, page(1).perPage(20));
+
+ assertEquals(size(backups), 1);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/droplets/5425561/backups?page=1&per_page=20");
+ }
+
+ public void testListBackupsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Backup> backups = api.dropletApi().listBackups(5425561, page(1).perPage(20));
+
+ assertTrue(isEmpty(backups));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/5425561/backups?page=1&per_page=20");
+ }
+
+ public void testListSnapshots() throws InterruptedException {
+ server.enqueue(jsonResponse("/snapshots-first.json"));
+ server.enqueue(jsonResponse("/snapshots-last.json"));
+
+ Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561).concat();
+
+ assertEquals(size(snapshots), 2); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/droplets/5425561/snapshots");
+ assertSent(server, "GET", "/droplets/5425561/snapshots?page=2");
+ }
+
+ public void testListSnapshotsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561).concat();
+
+ assertTrue(isEmpty(snapshots));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/5425561/snapshots");
+ }
+
+ public void testListSnapshotsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/snapshots-first.json"));
+
+ Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561, page(1).perPage(20));
+
+ assertEquals(size(snapshots), 1);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/droplets/5425561/snapshots?page=1&per_page=20");
+ }
+
+ public void testListSnapshotsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561, page(1).perPage(20));
+
+ assertTrue(isEmpty(snapshots));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/droplets/5425561/snapshots?page=1&per_page=20");
+ }
+
+ public void testDeleteDroplet() throws InterruptedException {
+ server.enqueue(response204());
+
+ api.dropletApi().delete(1);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/droplets/1");
+ }
+
+ public void testDeleteDropletReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ api.dropletApi().delete(1);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/droplets/1");
+ }
+
+ public void testPowerCycleDroplet() throws InterruptedException {
+ server.enqueue(jsonResponse("/power-cycle.json"));
+
+ Action action = api.dropletApi().powerCycle(1);
+
+ assertEquals(action, actionFromResource("/power-cycle.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"power_cycle\"}");
+ }
+
+ public void testPowerOn() throws InterruptedException {
+ server.enqueue(jsonResponse("/power-on.json"));
+
+ Action action = api.dropletApi().powerOn(1);
+
+ assertEquals(action, actionFromResource("/power-on.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"power_on\"}");
+ }
+
+ public void testPowerOff() throws InterruptedException {
+ server.enqueue(jsonResponse("/power-off.json"));
+
+ Action action = api.dropletApi().powerOff(1);
+
+ assertEquals(action, actionFromResource("/power-off.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"power_off\"}");
+ }
+
+ public void testReboot() throws InterruptedException {
+ server.enqueue(jsonResponse("/reboot.json"));
+
+ Action action = api.dropletApi().reboot(1);
+
+ assertEquals(action, actionFromResource("/reboot.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"reboot\"}");
+ }
+
+ public void testShutdown() throws InterruptedException {
+ server.enqueue(jsonResponse("/shutdown.json"));
+
+ Action action = api.dropletApi().shutdown(1);
+
+ assertEquals(action, actionFromResource("/shutdown.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"shutdown\"}");
+ }
+
+ public void testSnapshot() throws InterruptedException {
+ server.enqueue(jsonResponse("/snapshot.json"));
+
+ Action action = api.dropletApi().snapshot(1, "foo");
+
+ assertEquals(action, actionFromResource("/snapshot.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"snapshot\",\"name\":\"foo\"}");
+ }
+
+ private Droplet dropletFromResource(String resource) {
+ return onlyObjectFromResource(resource, new TypeToken<Map<String, Droplet>>() {
+ private static final long serialVersionUID = 1L;
+ });
+ }
+
+ private Action actionFromResource(String resource) {
+ return onlyObjectFromResource(resource, new TypeToken<Map<String, Action>>() {
+ private static final long serialVersionUID = 1L;
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
new file mode 100644
index 0000000..8c4c96e
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.features;
+
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.type;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "ImageApiLiveTest")
+public class ImageApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+ public void testListImages() {
+ final AtomicInteger found = new AtomicInteger(0);
+ // DigitalOcean return 25 records per page by default. Inspect at most 2 pages
+ assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Image>() {
+ @Override
+ public boolean apply(Image input) {
+ found.incrementAndGet();
+ return !isNullOrEmpty(input.name());
+ }
+ }), "All images must have the 'name' field populated");
+ assertTrue(found.get() > 0, "Expected some images to be returned");
+ }
+
+ public void testListImagesOnePage() {
+ final AtomicInteger found = new AtomicInteger(0);
+ assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Image>() {
+ @Override
+ public boolean apply(Image input) {
+ found.incrementAndGet();
+ return !isNullOrEmpty(input.name());
+ }
+ }), "All images must have the 'name' field populated");
+ assertTrue(found.get() > 0, "Expected some images to be returned");
+ }
+
+ public void testListImagesByType() {
+ final AtomicInteger found = new AtomicInteger(0);
+ assertTrue(api().list(type("distribution").perPage(5)).allMatch(new Predicate<Image>() {
+ @Override
+ public boolean apply(Image input) {
+ found.incrementAndGet();
+ return !isNullOrEmpty(input.distribution());
+ }
+ }), "All images must have the 'distribution' field populated");
+ assertTrue(found.get() > 0, "Expected some images to be returned");
+ }
+
+ public void testGetImage() {
+ Optional<Image> first = api().list().concat().first();
+ assertTrue(first.isPresent(), "At least one image was expected to exist");
+ assertNotNull(api().get(first.get().id()));
+ }
+
+ public void testGetImageBySlug() {
+ Optional<Image> first = api().list().concat().firstMatch(new Predicate<Image>() {
+ @Override
+ public boolean apply(Image input) {
+ return !isNullOrEmpty(input.slug());
+ }
+ });
+
+ assertTrue(first.isPresent(), "At least one image with the 'slug' field set was expected to exist");
+ assertNotNull(api().get(first.get().slug()));
+ }
+
+ // TODO: Delete live test once the create/transfer operations are implemented
+
+ private ImageApi api() {
+ return api.imageApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
new file mode 100644
index 0000000..d172174
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "ImageApiMockTest", singleThreaded = true)
+public class ImageApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ public void testListImages() throws InterruptedException {
+ server.enqueue(jsonResponse("/images-first.json"));
+ server.enqueue(jsonResponse("/images-last.json"));
+
+ Iterable<Image> images = api.imageApi().list().concat();
+
+ assertEquals(size(images), 10); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/images");
+ assertSent(server, "GET", "/images?page=2&per_page=5&type=distribution");
+ }
+
+ public void testListImagesReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Image> images = api.imageApi().list().concat();
+
+ assertTrue(isEmpty(images));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/images");
+ }
+
+ public void testListImagesWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/images-first.json"));
+
+ Iterable<Image> images = api.imageApi().list(page(1).perPage(5).type("distribution"));
+
+ assertEquals(size(images), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/images?page=1&per_page=5&type=distribution");
+ }
+
+ public void testListImagesWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Image> images = api.imageApi().list(page(1).perPage(5).type("distribution"));
+
+ assertTrue(isEmpty(images));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/images?page=1&per_page=5&type=distribution");
+ }
+
+ public void testGetImage() throws InterruptedException {
+ server.enqueue(jsonResponse("/image.json"));
+
+ Image image = api.imageApi().get(1);
+
+ assertEquals(image, imageFromResource("/image.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/images/1");
+ }
+
+ public void testGetImageReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Image image = api.imageApi().get(1);
+
+ assertNull(image);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/images/1");
+ }
+
+ public void testGetImageUsingSlug() throws InterruptedException {
+ server.enqueue(jsonResponse("/image.json"));
+
+ Image image = api.imageApi().get("foo");
+
+ assertEquals(image, imageFromResource("/image.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/images/foo");
+ }
+
+ public void testGetImageUsingSlugReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Image image = api.imageApi().get("foo");
+
+ assertNull(image);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/images/foo");
+ }
+
+ public void testDeleteImage() throws InterruptedException {
+ server.enqueue(response204());
+
+ api.imageApi().delete(1);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/images/1");
+ }
+
+ public void testDeleteImageReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ api.imageApi().delete(1);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/images/1");
+ }
+
+ private Image imageFromResource(String resource) {
+ return onlyObjectFromResource(resource, new TypeToken<Map<String, Image>>() {
+ private static final long serialVersionUID = 1L;
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
new file mode 100644
index 0000000..2911d79
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.features;
+
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.io.Resources;
+
+@Test(groups = "live", testName = "KeyApiLiveTest")
+public class KeyApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+ private Key dsa;
+ private Key ecdsa;
+
+ public void testCreateKey() {
+ dsa = api().create("jclouds-test-dsa", loadKey("/ssh-dsa.pub"));
+ ecdsa = api().create("jclouds-test-ecdsa", loadKey("/ssh-ecdsa.pub"));
+
+ assertEquals(dsa.name(), "jclouds-test-dsa");
+ assertEquals(ecdsa.name(), "jclouds-test-ecdsa");
+ }
+
+ @Test(dependsOnMethods = "testCreateKey")
+ public void testListKeys() {
+ FluentIterable<Key> keys = api().list().concat();
+ assertTrue(keys.size() >= 2, "At least the two created keys must exist");
+ }
+
+ @Test(dependsOnMethods = "testCreateKey")
+ public void testListKeysOnePAge() {
+ FluentIterable<Key> keys = api().list(page(1));
+ assertTrue(keys.size() >= 2, "At least the two created keys must exist");
+ }
+
+ @Test(dependsOnMethods = "testCreateKey")
+ public void testGetKey() {
+ assertEquals(api().get(dsa.id()).fingerprint(), dsa.fingerprint());
+ assertEquals(api().get(ecdsa.fingerprint()).id(), ecdsa.id());
+ }
+
+ @Test(dependsOnMethods = "testCreateKey")
+ public void testUpdateKey() {
+ api().update(dsa.id(), "jclouds-test-dsa-updated");
+ assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated");
+ api().update(dsa.fingerprint(), "jclouds-test-dsa-updated2");
+ assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated2");
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void testDeleteKey() {
+ if (dsa != null) {
+ api().delete(dsa.id());
+ FluentIterable<Key> keys = api().list().concat();
+ assertFalse(keys.contains(dsa), "dsa key must not be present in list");
+ }
+ if (ecdsa != null) {
+ api().delete(ecdsa.fingerprint());
+ FluentIterable<Key> keys = api().list().concat();
+ assertFalse(keys.contains(ecdsa), "dsa key must not be present in list");
+ }
+ }
+
+ private String loadKey(String resourceName) {
+ try {
+ return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8);
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private KeyApi api() {
+ return api.keyApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
new file mode 100644
index 0000000..2f9d5d3
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "KeyApiMockTest", singleThreaded = true)
+public class KeyApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ public void testListKeys() throws InterruptedException {
+ server.enqueue(jsonResponse("/keys-first.json"));
+ server.enqueue(jsonResponse("/keys-last.json"));
+
+ Iterable<Key> keys = api.keyApi().list().concat();
+
+ assertEquals(size(keys), 7); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/account/keys");
+ assertSent(server, "GET", "/account/keys?page=2&per_page=5");
+ }
+
+ public void testListKeysReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Key> keys = api.keyApi().list().concat();
+
+ assertTrue(isEmpty(keys));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/account/keys");
+ }
+
+ public void testListKeysWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/keys-first.json"));
+
+ Iterable<Key> keys = api.keyApi().list(page(1).perPage(5));
+
+ assertEquals(size(keys), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/account/keys?page=1&per_page=5");
+ }
+
+ public void testListKeysWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Key> keys = api.keyApi().list(page(1).perPage(5));
+
+ assertTrue(isEmpty(keys));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/account/keys?page=1&per_page=5");
+ }
+
+ public void testCreateKey() throws InterruptedException {
+ server.enqueue(jsonResponse("/key.json").setStatus("HTTP/1.1 201 Created"));
+
+ String dsa = stringFromResource("/ssh-dsa.pub");
+
+ Key key = api.keyApi().create("foo", dsa);
+
+ assertEquals(key, keyFromResource("/key.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "POST", "/account/keys", String.format("{\"name\":\"foo\", \"public_key\":\"%s\"}", dsa));
+ }
+
+ public void testGetKey() throws InterruptedException {
+ server.enqueue(jsonResponse("/key.json"));
+
+ Key key = api.keyApi().get(1);
+
+ assertEquals(key, keyFromResource("/key.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/account/keys/1");
+ }
+
+ public void testGetKeyReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Key key = api.keyApi().get(1);
+
+ assertNull(key);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/account/keys/1");
+ }
+
+ public void testGetKeyUsingFingerprint() throws InterruptedException {
+ server.enqueue(jsonResponse("/key.json"));
+
+ Key key = api.keyApi().get("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+ assertEquals(key, keyFromResource("/key.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+ }
+
+ public void testGetKeyUsingFingerprintReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Key key = api.keyApi().get("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+ assertNull(key);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+ }
+
+ public void testUpdateKey() throws InterruptedException {
+ server.enqueue(jsonResponse("/key.json"));
+
+ Key key = api.keyApi().update(1, "foo");
+
+ assertEquals(key, keyFromResource("/key.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "PUT", "/account/keys/1", "{\"name\":\"foo\"}");
+ }
+
+ public void testUpdateKeyUsingFingerprint() throws InterruptedException {
+ server.enqueue(jsonResponse("/key.json"));
+
+ Key key = api.keyApi().update("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90", "foo");
+
+ assertEquals(key, keyFromResource("/key.json"));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "PUT", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90", "{\"name\":\"foo\"}");
+ }
+
+ public void testDeleteKey() throws InterruptedException {
+ server.enqueue(response204());
+
+ api.keyApi().delete(1);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/account/keys/1");
+ }
+
+ public void testDeleteKeyReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ api.keyApi().delete(1);
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/account/keys/1");
+ }
+
+ public void testDeleteKeyUsingFingerprint() throws InterruptedException {
+ server.enqueue(response204());
+
+ api.keyApi().delete("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+ }
+
+ public void testDeleteKeyUsingfingerprintReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ api.keyApi().delete("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "DELETE", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+ }
+
+ private Key keyFromResource(String resource) {
+ return onlyObjectFromResource(resource, new TypeToken<Map<String, Key>>() {
+ private static final long serialVersionUID = 1L;
+ });
+ }
+}
[11/19] jclouds git commit: JCLOUDS-1022: Automatically handle
DigitalOcean rate limit
Posted by na...@apache.org.
JCLOUDS-1022: Automatically handle DigitalOcean rate limit
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/7e866ad6
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/7e866ad6
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/7e866ad6
Branch: refs/heads/master
Commit: 7e866ad6a134357ce34ba7ae5247a088aac5c79e
Parents: 4596471
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Oct 22 00:31:58 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Oct 27 23:30:43 2015 +0100
----------------------------------------------------------------------
.../config/DigitalOcean2Properties.java | 33 ++++
.../config/DigitalOcean2RateLimitModule.java | 30 ++++
...DigitalOcean2RateLimitExceededException.java | 81 ++++++++++
.../handlers/DigitalOcean2ErrorHandler.java | 9 +-
.../handlers/RateLimitRetryHandler.java | 111 ++++++++++++++
.../DigitalOcean2ComputeServiceLiveTest.java | 8 +
.../exceptions/RateLimitExceptionMockTest.java | 63 ++++++++
.../handlers/RateLimitRetryHandlerTest.java | 153 +++++++++++++++++++
.../internal/BaseDigitalOcean2ApiLiveTest.java | 7 +
.../internal/BaseDigitalOcean2ApiMockTest.java | 7 +-
10 files changed, 499 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2Properties.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2Properties.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2Properties.java
new file mode 100644
index 0000000..d0d1098
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2Properties.java
@@ -0,0 +1,33 @@
+/*
+ * 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.config;
+
+public final class DigitalOcean2Properties {
+
+ /**
+ * Maximum amount of time (in milliseconds) a request will wait until retrying if
+ * the rate limit is exhausted.
+ * <p>
+ * Default value: 2 minutes.
+ */
+ public static final String MAX_RATE_LIMIT_WAIT = "jclouds.max-ratelimit-wait";
+
+ private DigitalOcean2Properties() {
+ throw new AssertionError("intentionally unimplemented");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2RateLimitModule.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2RateLimitModule.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2RateLimitModule.java
new file mode 100644
index 0000000..1b0a95f
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2RateLimitModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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.config;
+
+import org.jclouds.digitalocean2.handlers.RateLimitRetryHandler;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.http.annotation.ClientError;
+
+import com.google.inject.AbstractModule;
+
+public class DigitalOcean2RateLimitModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(RateLimitRetryHandler.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/exceptions/DigitalOcean2RateLimitExceededException.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/exceptions/DigitalOcean2RateLimitExceededException.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/exceptions/DigitalOcean2RateLimitExceededException.java
new file mode 100644
index 0000000..fc54a7c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/exceptions/DigitalOcean2RateLimitExceededException.java
@@ -0,0 +1,81 @@
+/*
+ * 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.exceptions;
+
+import static org.jclouds.digitalocean2.handlers.RateLimitRetryHandler.millisUntilNextAvailableRequest;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rest.RateLimitExceededException;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+
+/**
+ * Provides detailed information for rate limit exceptions.
+ */
+@Beta
+public class DigitalOcean2RateLimitExceededException extends RateLimitExceededException {
+ private static final long serialVersionUID = 1L;
+ private static final String RATE_LIMIT_HEADER_PREFIX = "RateLimit-";
+
+ private Integer totalRequestsPerHour;
+ private Integer remainingRequests;
+ private Long timeToNextAvailableRequest;
+
+ public DigitalOcean2RateLimitExceededException(HttpResponse response) {
+ super(response.getStatusLine() + "\n" + rateLimitHeaders(response));
+ parseRateLimitInfo(response);
+ }
+
+ public DigitalOcean2RateLimitExceededException(HttpResponse response, Throwable cause) {
+ super(response.getStatusLine() + "\n" + rateLimitHeaders(response), cause);
+ parseRateLimitInfo(response);
+ }
+
+ public Integer totalRequestsPerHour() {
+ return totalRequestsPerHour;
+ }
+
+ public Integer remainingRequests() {
+ return remainingRequests;
+ }
+
+ public Long timeToNextAvailableRequest() {
+ return timeToNextAvailableRequest;
+ }
+
+ private void parseRateLimitInfo(HttpResponse response) {
+ String limit = response.getFirstHeaderOrNull("RateLimit-Limit");
+ String remaining = response.getFirstHeaderOrNull("RateLimit-Remaining");
+ String reset = response.getFirstHeaderOrNull("RateLimit-Reset");
+
+ totalRequestsPerHour = limit == null ? null : Integer.valueOf(limit);
+ remainingRequests = remaining == null ? null : Integer.valueOf(remaining);
+ timeToNextAvailableRequest = reset == null ? null : millisUntilNextAvailableRequest(Long.valueOf(reset));
+ }
+
+ private static Multimap<String, String> rateLimitHeaders(HttpResponse response) {
+ return Multimaps.filterKeys(response.getHeaders(), new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ return input.startsWith(RATE_LIMIT_HEADER_PREFIX);
+ }
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
index 5eda6eb..29b7eba 100644
--- a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/DigitalOcean2ErrorHandler.java
@@ -20,6 +20,7 @@ import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import javax.inject.Singleton;
+import org.jclouds.digitalocean2.exceptions.DigitalOcean2RateLimitExceededException;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
@@ -33,15 +34,16 @@ import org.jclouds.rest.ResourceNotFoundException;
*/
@Singleton
public class DigitalOcean2ErrorHandler implements HttpErrorHandler {
+
public void handleError(HttpCommand command, HttpResponse response) {
// it is important to always read fully and close streams
byte[] data = closeClientButKeepContentStream(response);
String message = data != null ? new String(data) : null;
Exception exception = message != null ? new HttpResponseException(command, response, message)
- : new HttpResponseException(command, response);
+ : new HttpResponseException(command, response);
message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
- response.getStatusLine());
+ response.getStatusLine());
switch (response.getStatusCode()) {
case 400:
break;
@@ -61,6 +63,9 @@ public class DigitalOcean2ErrorHandler implements HttpErrorHandler {
case 409:
exception = new IllegalStateException(message, exception);
break;
+ case 429:
+ exception = new DigitalOcean2RateLimitExceededException(response, exception);
+ break;
}
command.setException(exception);
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandler.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandler.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandler.java
new file mode 100644
index 0000000..d72a9fa
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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.handlers;
+
+import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
+import static org.jclouds.digitalocean2.config.DigitalOcean2Properties.MAX_RATE_LIMIT_WAIT;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.Beta;
+import com.google.inject.Inject;
+
+/**
+ * Retry handler that takes into account the DigitalOcean rate limit and delays
+ * the requests until they are known to succeed.
+ */
+@Beta
+@Singleton
+public class RateLimitRetryHandler implements HttpRetryHandler {
+
+ static final String RATE_LIMIT_RESET_HEADER = "RateLimit-Reset";
+
+ @Resource
+ protected Logger logger = Logger.NULL;
+
+ @Inject(optional = true)
+ @Named(PROPERTY_MAX_RETRIES)
+ private int retryCountLimit = 5;
+
+ @Inject(optional = true)
+ @Named(MAX_RATE_LIMIT_WAIT)
+ private int maxRateLimitWait = 120000;
+
+ @Override
+ public boolean shouldRetryRequest(final HttpCommand command, final HttpResponse response) {
+ command.incrementFailureCount();
+
+ // Do not retry client errors that are not rate limit errors
+ if (response.getStatusCode() != 429) {
+ return false;
+ } else if (!command.isReplayable()) {
+ logger.error("Cannot retry after rate limit error, command is not replayable: %1$s", command);
+ return false;
+ } else if (command.getFailureCount() > retryCountLimit) {
+ logger.error("Cannot retry after rate limit error, command has exceeded retry limit %1$d: %2$s",
+ retryCountLimit, command);
+ return false;
+ } else {
+ return delayRequestUntilAllowed(command, response);
+ }
+ }
+
+ private boolean delayRequestUntilAllowed(final HttpCommand command, final HttpResponse response) {
+ // The header is the Unix epoch time when the next request can be done
+ String epochForNextAvailableRequest = response.getFirstHeaderOrNull(RATE_LIMIT_RESET_HEADER);
+ if (epochForNextAvailableRequest == null) {
+ logger.error("Cannot retry after rate limit error, no retry information provided in the response");
+ return false;
+ }
+
+ long waitPeriod = millisUntilNextAvailableRequest(Long.parseLong(epochForNextAvailableRequest));
+
+ if (waitPeriod > 0) {
+ if (waitPeriod > maxRateLimitWait) {
+ logger.error("Max wait for rate limited requests is %s seconds but need to wait %s seconds, aborting",
+ maxRateLimitWait, waitPeriod);
+ return false;
+ }
+
+ try {
+ logger.debug("Waiting %s seconds before retrying, as defined by the rate limit", waitPeriod);
+ // Do not use Uninterrumpibles or similar, to let the jclouds
+ // tiemout configuration interrupt this thread
+ Thread.sleep(waitPeriod);
+ } catch (InterruptedException ex) {
+ // If the request is being executed and has a timeout configured,
+ // the thread may be interrupted when the timeout is reached.
+ logger.error("Request execution was interrupted, aborting");
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static long millisUntilNextAvailableRequest(long epochForNextAvailableRequest) {
+ return (epochForNextAvailableRequest * 1000) - System.currentTimeMillis();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
index b8fbbe7..f45a73f 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/DigitalOcean2ComputeServiceLiveTest.java
@@ -18,8 +18,10 @@ package org.jclouds.digitalocean2.compute;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.digitalocean2.config.DigitalOcean2RateLimitModule;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
@@ -40,6 +42,12 @@ public class DigitalOcean2ComputeServiceLiveTest extends BaseComputeServiceLiveT
}
@Override
+ protected Iterable<Module> setupModules() {
+ return ImmutableSet.<Module> builder().addAll(super.setupModules()).add(new DigitalOcean2RateLimitModule())
+ .build();
+ }
+
+ @Override
public void testOptionToNotBlock() throws Exception {
// DigitalOcean ComputeService implementation has to block until the node
// is provisioned, to be able to return it.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/exceptions/RateLimitExceptionMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/exceptions/RateLimitExceptionMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/exceptions/RateLimitExceptionMockTest.java
new file mode 100644
index 0000000..e7831a5
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/exceptions/RateLimitExceptionMockTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.exceptions;
+
+import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
+import static org.jclouds.digitalocean2.handlers.RateLimitRetryHandler.millisUntilNextAvailableRequest;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Properties;
+
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+@Test(groups = "unit", testName = "RateLimitExceptionMockTest", singleThreaded = true)
+public class RateLimitExceptionMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ @Override
+ protected Properties overrides() {
+ Properties overrides = super.overrides();
+ overrides.put(PROPERTY_MAX_RETRIES, "0"); // Do not retry
+ return overrides;
+ }
+
+ public void testRateLimitExceptionIsThrown() throws InterruptedException {
+ long reset = (System.currentTimeMillis() / 1000) + 3600; // Epoch for one
+ // hour from now
+ long millisToReset = millisUntilNextAvailableRequest(reset);
+
+ server.enqueue(new MockResponse().setResponseCode(429).addHeader("RateLimit-Limit", "5000")
+ .addHeader("RateLimit-Remaining", "1235").addHeader("RateLimit-Reset", String.valueOf(reset)));
+
+ try {
+ api.keyApi().list();
+ fail("Expected a DigitalOcean2RateLimitExceededException to be thrown");
+ } catch (DigitalOcean2RateLimitExceededException ex) {
+ assertEquals(ex.totalRequestsPerHour().intValue(), 5000);
+ assertEquals(ex.remainingRequests().intValue(), 1235);
+ // Can't verify with millisecond precision. Use an interval to have a
+ // consistent test.
+ assertTrue(ex.timeToNextAvailableRequest() < millisToReset
+ && ex.timeToNextAvailableRequest() > millisToReset - 1800000);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandlerTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandlerTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandlerTest.java
new file mode 100644
index 0000000..6c7c87f
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/handlers/RateLimitRetryHandlerTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.handlers;
+
+import static org.jclouds.digitalocean2.handlers.RateLimitRetryHandler.RATE_LIMIT_RESET_HEADER;
+import static org.jclouds.http.HttpUtils.releasePayload;
+import static org.jclouds.io.Payloads.newInputStreamPayload;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.io.Payload;
+import org.testng.annotations.Test;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+
+@Test(groups = "unit", testName = "RateLimitRetryHandlerTest")
+public class RateLimitRetryHandlerTest {
+
+ // Configure a safe timeout of one minute to abort the tests in case they get
+ // stuck
+ private static final long TEST_SAFE_TIMEOUT = 60000;
+
+ private final RateLimitRetryHandler rateLimitRetryHandler = new RateLimitRetryHandler();
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testDoNotRetryIfNoRateLimit() {
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+ HttpResponse response = HttpResponse.builder().statusCode(450).build();
+
+ assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testDoNotRetryIfNotReplayable() {
+ // InputStream payloads are not replayable
+ Payload payload = newInputStreamPayload(new ByteArrayInputStream(new byte[0]));
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost")
+ .payload(payload).build());
+ HttpResponse response = HttpResponse.builder().statusCode(429).build();
+
+ try {
+ assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ } finally {
+ releasePayload(command.getCurrentRequest());
+ }
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testDoNotRetryIfNoRateLimitResetHeader() {
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+ HttpResponse response = HttpResponse.builder().statusCode(429).build();
+
+ assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testDoNotRetryIfTooMuchWait() {
+ // 5 minutes Unix epoch timestamp
+ long rateLimitResetEpoch = (System.currentTimeMillis() + 300000) / 1000;
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+ HttpResponse response = HttpResponse.builder().statusCode(429)
+ .addHeader(RATE_LIMIT_RESET_HEADER, String.valueOf(rateLimitResetEpoch)).build();
+
+ assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testRequestIsDelayed() {
+ // 5 seconds Unix epoch timestamp
+ long rateLimitResetEpoch = (System.currentTimeMillis() + 5000) / 1000;
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+ HttpResponse response = HttpResponse.builder().statusCode(429)
+ .addHeader(RATE_LIMIT_RESET_HEADER, String.valueOf(rateLimitResetEpoch)).build();
+
+ long start = System.currentTimeMillis();
+
+ assertTrue(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ // Should have blocked the amount of time configured in the header. Use a
+ // smaller value to compensate the time it takes to reach the code that
+ // computes the amount of time to wait.
+ assertTrue(System.currentTimeMillis() - start > 2500);
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testDoNotRetryIfRequestIsAborted() throws Exception {
+ // 10 seconds Unix epoch timestamp
+ long rateLimitResetEpoch = (System.currentTimeMillis() + 10000) / 1000;
+ final HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost")
+ .build());
+ final HttpResponse response = HttpResponse.builder().statusCode(429)
+ .addHeader(RATE_LIMIT_RESET_HEADER, String.valueOf(rateLimitResetEpoch)).build();
+
+ final Thread requestThread = Thread.currentThread();
+ Thread killer = new Thread() {
+ @Override
+ public void run() {
+ Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
+ requestThread.interrupt();
+ }
+ };
+
+ // Start the killer thread that will abort the rate limit wait
+ killer.start();
+ assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testIncrementsFailureCount() {
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+ HttpResponse response = HttpResponse.builder().statusCode(429).build();
+
+ rateLimitRetryHandler.shouldRetryRequest(command, response);
+ assertEquals(command.getFailureCount(), 1);
+
+ rateLimitRetryHandler.shouldRetryRequest(command, response);
+ assertEquals(command.getFailureCount(), 2);
+
+ rateLimitRetryHandler.shouldRetryRequest(command, response);
+ assertEquals(command.getFailureCount(), 3);
+ }
+
+ @Test(timeOut = TEST_SAFE_TIMEOUT)
+ public void testDisallowExcessiveRetries() {
+ HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+ HttpResponse response = HttpResponse.builder().statusCode(429).addHeader(RATE_LIMIT_RESET_HEADER, "0").build();
+
+ for (int i = 0; i < 5; i++) {
+ assertTrue(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ }
+ assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
index 18f97c6..b210c93 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
@@ -28,6 +28,7 @@ import org.jclouds.apis.BaseApiLiveTest;
import org.jclouds.compute.config.ComputeServiceProperties;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.config.DigitalOcean2RateLimitModule;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.domain.Region;
@@ -35,6 +36,7 @@ import org.jclouds.digitalocean2.domain.Size;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.inject.Injector;
import com.google.inject.Key;
@@ -68,6 +70,11 @@ public class BaseDigitalOcean2ApiLiveTest extends BaseApiLiveTest<DigitalOcean2A
return injector.getInstance(DigitalOcean2Api.class);
}
+ @Override protected Iterable<Module> setupModules() {
+ return ImmutableSet.<Module> builder().addAll(super.setupModules()).add(new DigitalOcean2RateLimitModule())
+ .build();
+ }
+
protected void assertActionCompleted(int actionId) {
checkState(actionCompleted.apply(actionId), "Timeout waiting for action: %s", actionId);
Action action = api.actionApi().get(actionId);
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7e866ad6/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
index 78550a5..ca0c4bd 100644
--- a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
@@ -23,6 +23,7 @@ import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import org.jclouds.ContextBuilder;
@@ -59,7 +60,6 @@ public class BaseDigitalOcean2ApiMockTest {
// So that we can ignore formatting.
private final JsonParser parser = new JsonParser();
-
@BeforeMethod
public void start() throws IOException {
server = new MockWebServer();
@@ -68,6 +68,7 @@ public class BaseDigitalOcean2ApiMockTest {
.credentials("", MOCK_BEARER_TOKEN)
.endpoint(url(""))
.modules(modules)
+ .overrides(overrides())
.build();
json = ctx.utils().injector().getInstance(Json.class);
api = ctx.getApi();
@@ -79,6 +80,10 @@ public class BaseDigitalOcean2ApiMockTest {
api.close();
}
+ protected Properties overrides() {
+ return new Properties();
+ }
+
protected String url(String path) {
return server.getUrl(path).toString();
}
[02/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean
v2 API
Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiLiveTest.java
new file mode 100644
index 0000000..73c7451
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiLiveTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.features;
+
+import static org.testng.Assert.assertTrue;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.Test;
+import org.testng.util.Strings;
+
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "RegionApiLiveTest")
+public class RegionApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+ public void testListRegions() {
+ final AtomicInteger found = new AtomicInteger(0);
+ // DigitalOcean return 25 records per page by default. Inspect at most 2 pages
+ assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Region>() {
+ @Override
+ public boolean apply(Region input) {
+ found.incrementAndGet();
+ return !Strings.isNullOrEmpty(input.slug());
+ }
+ }), "All regions must have the 'slug' field populated");
+ assertTrue(found.get() > 0, "Expected some regions to be returned");
+ }
+
+ public void testListRegionsOnePage() {
+ final AtomicInteger found = new AtomicInteger(0);
+ assertTrue(api().list(page(1)).allMatch(new Predicate<Region>() {
+ @Override
+ public boolean apply(Region input) {
+ found.incrementAndGet();
+ return !Strings.isNullOrEmpty(input.slug());
+ }
+ }), "All regions must have the 'slug' field populated");
+ assertTrue(found.get() > 0, "Expected some regions to be returned");
+ }
+
+ private RegionApi api() {
+ return api.regionApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiMockTest.java
new file mode 100644
index 0000000..8c8c326
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/RegionApiMockTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "RegionApiMockTest", singleThreaded = true)
+public class RegionApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ public void testListRegions() throws InterruptedException {
+ server.enqueue(jsonResponse("/regions-first.json"));
+ server.enqueue(jsonResponse("/regions-last.json"));
+
+ Iterable<Region> regions = api.regionApi().list().concat();
+
+ assertEquals(size(regions), 10); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/regions");
+ assertSent(server, "GET", "/regions?page=2&per_page=5");
+ }
+
+ public void testListRegionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Region> regions = api.regionApi().list().concat();
+
+ assertTrue(isEmpty(regions));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/regions");
+ }
+
+ public void testListRegionsWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/regions-first.json"));
+
+ Iterable<Region> regions = api.regionApi().list(page(1).perPage(5));
+
+ assertEquals(size(regions), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/regions?page=1&per_page=5");
+ }
+
+ public void testListRegionsWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Region> regions = api.regionApi().list(page(1).perPage(5));
+
+ assertTrue(isEmpty(regions));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/regions?page=1&per_page=5");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiLiveTest.java
new file mode 100644
index 0000000..a9a8566
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiLiveTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.features;
+
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertTrue;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.Test;
+import org.testng.util.Strings;
+
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "SizeApiLiveTest")
+public class SizeApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+ public void testListSizes() {
+ final AtomicInteger found = new AtomicInteger(0);
+ // DigitalOcean return 25 records per page by default. Inspect at most 2 pages
+ assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Size>() {
+ @Override
+ public boolean apply(Size input) {
+ found.incrementAndGet();
+ return !Strings.isNullOrEmpty(input.slug());
+ }
+ }), "All sizes must have the 'slug' field populated");
+ assertTrue(found.get() > 0, "Expected some sizes to be returned");
+ }
+
+ public void testListSizesOnePage() {
+ final AtomicInteger found = new AtomicInteger(0);
+ assertTrue(api().list(page(1)).allMatch(new Predicate<Size>() {
+ @Override
+ public boolean apply(Size input) {
+ found.incrementAndGet();
+ return !Strings.isNullOrEmpty(input.slug());
+ }
+ }), "All sizes must have the 'slug' field populated");
+ assertTrue(found.get() > 0, "Expected some sizes to be returned");
+ }
+
+ private SizeApi api() {
+ return api.sizeApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiMockTest.java
new file mode 100644
index 0000000..7403518
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/SizeApiMockTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "SizeApiMockTest", singleThreaded = true)
+public class SizeApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+ public void testListSizes() throws InterruptedException {
+ server.enqueue(jsonResponse("/sizes-first.json"));
+ server.enqueue(jsonResponse("/sizes-last.json"));
+
+ Iterable<Size> sizes = api.sizeApi().list().concat();
+
+ assertEquals(size(sizes), 9); // Force the PagedIterable to advance
+ assertEquals(server.getRequestCount(), 2);
+
+ assertSent(server, "GET", "/sizes");
+ assertSent(server, "GET", "/sizes?page=2&per_page=5");
+ }
+
+ public void testListSizesReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Size> sizes = api.sizeApi().list().concat();
+
+ assertTrue(isEmpty(sizes));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/sizes");
+ }
+
+ public void testListSizesWithOptions() throws InterruptedException {
+ server.enqueue(jsonResponse("/sizes-first.json"));
+
+ Iterable<Size> sizes = api.sizeApi().list(page(1).perPage(5));
+
+ assertEquals(size(sizes), 5);
+ assertEquals(server.getRequestCount(), 1);
+
+ assertSent(server, "GET", "/sizes?page=1&per_page=5");
+ }
+
+ public void testListSizesWithOptionsReturns404() throws InterruptedException {
+ server.enqueue(response404());
+
+ Iterable<Size> sizes = api.sizeApi().list(page(1).perPage(5));
+
+ assertTrue(isEmpty(sizes));
+
+ assertEquals(server.getRequestCount(), 1);
+ assertSent(server, "GET", "/sizes?page=1&per_page=5");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToImageListOptionsTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToImageListOptionsTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToImageListOptionsTest.java
new file mode 100644
index 0000000..22985fa
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToImageListOptionsTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.PRIVATE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ImageListOptions.TYPE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.domain.options.ImageListOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Multimap;
+
+@Test(groups = "unit", testName = "LinkToImageListOptionsTest")
+public class LinkToImageListOptionsTest {
+
+ public void testNoOptions() {
+ LinkToImageListOptions function = new LinkToImageListOptions();
+
+ ImageListOptions options = function.apply(URI.create("https://api.digitalocean.com/v2/images"));
+ assertNotNull(options);
+
+ Multimap<String, String> params = options.buildQueryParameters();
+ assertFalse(params.containsKey(PAGE_PARAM));
+ assertFalse(params.containsKey(PER_PAGE_PARAM));
+ assertFalse(params.containsKey(TYPE_PARAM));
+ assertFalse(params.containsKey(PRIVATE_PARAM));
+ }
+
+ public void testWithOptions() {
+ LinkToImageListOptions function = new LinkToImageListOptions();
+
+ ImageListOptions options = function.apply(URI
+ .create("https://api.digitalocean.com/v2/images?page=1&per_page=5&type=distribution&private=true"));
+ assertNotNull(options);
+
+ Multimap<String, String> params = options.buildQueryParameters();
+ assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "1");
+ assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
+ assertEquals(getOnlyElement(params.get(TYPE_PARAM)), "distribution");
+ assertEquals(getOnlyElement(params.get(PRIVATE_PARAM)), "true");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToListOptionsTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToListOptionsTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToListOptionsTest.java
new file mode 100644
index 0000000..2bb3544
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/functions/LinkToListOptionsTest.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.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
+import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Multimap;
+
+@Test(groups = "unit", testName = "LinkToListOptionsTest")
+public class LinkToListOptionsTest {
+
+ public void testNoOptions() {
+ LinkToListOptions function = new LinkToListOptions();
+
+ ListOptions options = function.apply(URI.create("https://api.digitalocean.com/v2/actions"));
+ assertNotNull(options);
+
+ Multimap<String, String> params = options.buildQueryParameters();
+ assertFalse(params.containsKey(PAGE_PARAM));
+ assertFalse(params.containsKey(PER_PAGE_PARAM));
+ }
+
+ public void testWithOptions() {
+ LinkToListOptions function = new LinkToListOptions();
+
+ ListOptions options = function.apply(URI.create("https://api.digitalocean.com/v2/actions?page=2&per_page=5"));
+ assertNotNull(options);
+
+ Multimap<String, String> params = options.buildQueryParameters();
+ assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "2");
+ assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
new file mode 100644
index 0000000..18f97c6
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiLiveTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+import static org.testng.Assert.assertEquals;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.compute.config.ComputeServiceProperties;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.domain.Size;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+public class BaseDigitalOcean2ApiLiveTest extends BaseApiLiveTest<DigitalOcean2Api> {
+
+ protected Predicate<Integer> actionCompleted;
+ protected Predicate<AtomicReference<NodeMetadata>> nodeRunning;
+ protected Predicate<Integer> nodeTerminated;
+ protected Predicate<Integer> dropletOff;
+
+ public BaseDigitalOcean2ApiLiveTest() {
+ provider = "digitalocean2";
+ }
+
+ @Override protected Properties setupProperties() {
+ Properties props = super.setupProperties();
+ props.put(ComputeServiceProperties.POLL_INITIAL_PERIOD, 1000);
+ props.put(ComputeServiceProperties.POLL_MAX_PERIOD, 10000);
+ return props;
+ }
+
+ @Override protected DigitalOcean2Api create(Properties props, Iterable<Module> modules) {
+ Injector injector = newBuilder().modules(modules).overrides(props).buildInjector();
+ actionCompleted = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){}));
+ nodeTerminated = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
+ Names.named(TIMEOUT_NODE_TERMINATED)));
+ return injector.getInstance(DigitalOcean2Api.class);
+ }
+
+ protected void assertActionCompleted(int actionId) {
+ checkState(actionCompleted.apply(actionId), "Timeout waiting for action: %s", actionId);
+ Action action = api.actionApi().get(actionId);
+ assertEquals(action.status(), Action.Status.COMPLETED);
+ }
+
+ protected void assertNodeTerminated(int dropletId) {
+ assertEquals(nodeTerminated.apply(dropletId), true, String.format("Timeout waiting for dropletId: %s", dropletId));
+ }
+
+ protected Region firstAvailableRegion() {
+ return api.regionApi().list().concat().firstMatch(new Predicate<Region>() {
+ @Override
+ public boolean apply(Region input) {
+ return input.available();
+ }
+ }).get();
+ }
+
+ protected Size cheapestSizeInRegion(final Region region) {
+ return sizesByPrice().min(api.sizeApi().list().concat().filter(new Predicate<Size>() {
+ @Override
+ public boolean apply(Size input) {
+ return input.available() && input.regions().contains(region.slug());
+ }
+ }));
+ }
+
+ protected Image ubuntuImageInRegion(final Region region) {
+ return api.imageApi().list().concat().firstMatch(new Predicate<Image>() {
+ @Override
+ public boolean apply(Image input) {
+ return "Ubuntu".equalsIgnoreCase(input.distribution()) && !isNullOrEmpty(input.slug())
+ && input.regions().contains(region.slug());
+ }
+ }).get();
+ }
+
+ protected static Ordering<Size> sizesByPrice() {
+ return new Ordering<Size>() {
+ @Override
+ public int compare(Size left, Size right) {
+ return ComparisonChain.start()
+ .compare(left.priceHourly(), right.priceHourly())
+ .compare(left.priceMonthly(), right.priceMonthly())
+ .result();
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
new file mode 100644
index 0000000..78550a5
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/internal/BaseDigitalOcean2ApiMockTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.DigitalOcean2ProviderMetadata;
+import org.jclouds.json.Json;
+import org.jclouds.rest.ApiContext;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.JsonParser;
+import com.google.inject.Module;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+public class BaseDigitalOcean2ApiMockTest {
+
+ private static final String MOCK_BEARER_TOKEN = "c5401990f0c24135e8d6b5d260603fc71696d4738da9aa04a720229a01a2521d";
+ private static final String DEFAULT_ENDPOINT = new DigitalOcean2ProviderMetadata().getEndpoint();
+
+ private final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor()));
+
+ protected MockWebServer server;
+ protected DigitalOcean2Api api;
+ private Json json;
+
+ // So that we can ignore formatting.
+ private final JsonParser parser = new JsonParser();
+
+
+ @BeforeMethod
+ public void start() throws IOException {
+ server = new MockWebServer();
+ server.play();
+ ApiContext<DigitalOcean2Api> ctx = ContextBuilder.newBuilder("digitalocean2")
+ .credentials("", MOCK_BEARER_TOKEN)
+ .endpoint(url(""))
+ .modules(modules)
+ .build();
+ json = ctx.utils().injector().getInstance(Json.class);
+ api = ctx.getApi();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void stop() throws IOException {
+ server.shutdown();
+ api.close();
+ }
+
+ protected String url(String path) {
+ return server.getUrl(path).toString();
+ }
+
+ protected MockResponse jsonResponse(String resource) {
+ return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource));
+ }
+
+ protected MockResponse response404() {
+ return new MockResponse().setStatus("HTTP/1.1 404 Not Found");
+ }
+
+ protected MockResponse response204() {
+ return new MockResponse().setStatus("HTTP/1.1 204 No Content");
+ }
+
+ protected String stringFromResource(String resourceName) {
+ try {
+ return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8)
+ .replace(DEFAULT_ENDPOINT, url(""));
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ protected <T> T onlyObjectFromResource(String resourceName, TypeToken<Map<String, T>> type) {
+ // Assume JSON objects passed here will be in the form: { "entity": { ... } }
+ String text = stringFromResource(resourceName);
+ Map<String, T> object = json.fromJson(text, type.getType());
+ checkArgument(!object.isEmpty(), "The given json does not contain any object: %s", text);
+ checkArgument(object.keySet().size() == 1, "The given json does not contain more than one object: %s", text);
+ return object.get(getOnlyElement(object.keySet()));
+ }
+
+ protected <T> T objectFromResource(String resourceName, Class<T> type) {
+ String text = stringFromResource(resourceName);
+ return json.fromJson(text, type);
+ }
+
+ protected RecordedRequest assertSent(MockWebServer server, String method, String path) throws InterruptedException {
+ RecordedRequest request = server.takeRequest();
+ assertEquals(request.getMethod(), method);
+ assertEquals(request.getPath(), path);
+ assertEquals(request.getHeader("Accept"), "application/json");
+ assertEquals(request.getHeader("Authorization"), "Bearer " + MOCK_BEARER_TOKEN);
+ return request;
+ }
+
+ protected RecordedRequest assertSent(MockWebServer server, String method, String path, String json)
+ throws InterruptedException {
+ RecordedRequest request = assertSent(server, method, path);
+ assertEquals(request.getHeader("Content-Type"), "application/json");
+ assertEquals(parser.parse(new String(request.getBody(), Charsets.UTF_8)), parser.parse(json));
+ return request;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/DSAKeysTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/DSAKeysTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/DSAKeysTest.java
new file mode 100644
index 0000000..91f3c96
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/DSAKeysTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ssh;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link DSAKeys} class.
+ */
+@Test(groups = "unit", testName = "DSAKeysTest")
+public class DSAKeysTest {
+
+ private static final String expectedFingerPrint = "2a:54:bb:8e:ba:44:96:c8:6c:9c:40:34:3c:4d:38:e4";
+
+ @Test
+ public void testCanReadRsaAndCompareFingerprintOnPublicRSAKey() throws IOException {
+ String dsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-dsa.pub"));
+ String fingerPrint = DSAKeys.fingerprintPublicKey(dsa);
+ assertEquals(fingerPrint, expectedFingerPrint);
+ }
+
+ @Test
+ public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+ String dsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-dsa.pub"));
+ DSAPublicKeySpec spec = DSAKeys.publicKeySpecFromOpenSSH(dsa);
+ DSAPublicKey key = (DSAPublicKey) KeyFactory.getInstance("DSA").generatePublic(spec);
+
+ assertEquals(DSAKeys.encodeAsOpenSSH(key), dsa);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/ECDSAKeysTest.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/ECDSAKeysTest.java b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/ECDSAKeysTest.java
new file mode 100644
index 0000000..2053ac7
--- /dev/null
+++ b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/ssh/ECDSAKeysTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ssh;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link ECDSAKeysTest} class.
+ */
+@Test(groups = "unit", testName = "ECDSAKeysTest")
+public class ECDSAKeysTest {
+
+ private static final String expectedFingerPrint = "0e:9f:aa:cc:3e:79:5d:1e:f9:19:58:08:dc:c4:5e:1c";
+
+ @Test
+ public void testCanReadRsaAndCompareFingerprintOnPublicECDSAKey() throws IOException {
+ String ecdsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-ecdsa.pub"));
+ String fingerPrint = ECDSAKeys.fingerprintPublicKey(ecdsa);
+ assertEquals(fingerPrint, expectedFingerPrint);
+ }
+
+ @Test
+ public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+ String ecdsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-ecdsa.pub"));
+ ECPublicKeySpec spec = ECDSAKeys.publicKeySpecFromOpenSSH(ecdsa);
+ ECPublicKey key = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(spec);
+
+ assertTrue(ecdsa.startsWith(ECDSAKeys.encodeAsOpenSSH(key)));
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/action.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/action.json b/providers/digitalocean2/src/test/resources/action.json
new file mode 100644
index 0000000..0202ca0
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/action.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "completed",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "destroy"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/actions-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/actions-first.json b/providers/digitalocean2/src/test/resources/actions-first.json
new file mode 100644
index 0000000..c19fde3
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/actions-first.json
@@ -0,0 +1,168 @@
+{
+ "actions" : [
+ {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "completed",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "destroy"
+ },
+ {
+ "started_at" : "2015-05-19T15:07:55Z",
+ "region" : {
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ],
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true,
+ "slug" : "nyc1",
+ "name" : "New York 1"
+ },
+ "status" : "completed",
+ "resource_type" : "droplet",
+ "region_slug" : "nyc1",
+ "id" : 50899364,
+ "resource_id" : 5346565,
+ "completed_at" : "2015-05-19T15:08:04Z",
+ "type" : "destroy"
+ },
+ {
+ "completed_at" : "2015-05-19T13:39:59Z",
+ "type" : "create",
+ "region" : {
+ "name" : "New York 1",
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ],
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true,
+ "slug" : "nyc1"
+ },
+ "started_at" : "2015-05-19T13:39:12Z",
+ "resource_type" : "droplet",
+ "status" : "completed",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50892713
+ },
+ {
+ "resource_id" : 5346565,
+ "region_slug" : "nyc1",
+ "id" : 50888077,
+ "status" : "completed",
+ "resource_type" : "droplet",
+ "region" : {
+ "name" : "New York 1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true,
+ "slug" : "nyc1",
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T12:37:23Z",
+ "type" : "create",
+ "completed_at" : "2015-05-19T12:38:13Z"
+ },
+ {
+ "completed_at" : "2015-05-19T11:33:00Z",
+ "type" : "destroy",
+ "status" : "completed",
+ "resource_type" : "droplet",
+ "started_at" : "2015-05-19T11:32:55Z",
+ "region" : {
+ "available" : true,
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "slug" : "nyc1",
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ],
+ "name" : "New York 1"
+ },
+ "region_slug" : "nyc1",
+ "id" : 50884032,
+ "resource_id" : 5344505
+ }
+ ],
+ "links" : {
+ "pages" : {
+ "last" : "https://api.digitalocean.com/v2/actions?page=2&per_page=5",
+ "next" : "https://api.digitalocean.com/v2/actions?page=2&per_page=5"
+ }
+ },
+ "meta" : {
+ "total" : 8
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/actions-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/actions-last.json b/providers/digitalocean2/src/test/resources/actions-last.json
new file mode 100644
index 0000000..5d86e7a
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/actions-last.json
@@ -0,0 +1,106 @@
+{
+ "meta" : {
+ "total" : 8
+ },
+ "links" : {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/actions?page=1&per_page=5",
+ "prev" : "https://api.digitalocean.com/v2/actions?page=1&per_page=5"
+ }
+ },
+ "actions" : [
+ {
+ "region" : {
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ],
+ "available" : true,
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "slug" : "nyc1",
+ "name" : "New York 1"
+ },
+ "started_at" : "2014-01-18T22:39:08Z",
+ "type" : "create",
+ "resource_type" : "droplet",
+ "id" : 14115951,
+ "completed_at" : "2014-01-18T22:41:14Z",
+ "region_slug" : "nyc1",
+ "resource_id" : 1010699,
+ "status" : "completed"
+ },
+ {
+ "started_at" : "2014-01-18T22:39:06Z",
+ "type" : "create",
+ "resource_type" : "droplet",
+ "region" : {
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ],
+ "slug" : "nyc1",
+ "name" : "New York 1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true
+ },
+ "resource_id" : 1010697,
+ "region_slug" : "nyc1",
+ "status" : "completed",
+ "id" : 14115948,
+ "completed_at" : "2014-01-18T22:40:43Z"
+ },
+ {
+ "region_slug" : "nyc1",
+ "resource_id" : 1010698,
+ "status" : "completed",
+ "id" : 14115949,
+ "completed_at" : "2014-01-18T22:44:08Z",
+ "type" : "create",
+ "started_at" : "2014-01-18T22:39:06Z",
+ "resource_type" : "droplet",
+ "region" : {
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ],
+ "slug" : "nyc1",
+ "name" : "New York 1",
+ "available" : true,
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ]
+ }
+ }
+ ]
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/backups-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/backups-first.json b/providers/digitalocean2/src/test/resources/backups-first.json
new file mode 100644
index 0000000..f1083f1
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/backups-first.json
@@ -0,0 +1,26 @@
+{
+ "backups": [
+ {
+ "id": 7622989,
+ "name": "example.com 2014-11-14",
+ "distribution": "Ubuntu",
+ "slug": null,
+ "public": false,
+ "regions": [
+ "nyc3"
+ ],
+ "created_at": "2014-11-14T16:07:38Z",
+ "type": "snapshot",
+ "min_disk_size": 20
+ }
+ ],
+ "links" : {
+ "pages" : {
+ "last" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=2",
+ "next" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=2"
+ }
+ },
+ "meta": {
+ "total": 2
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/backups-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/backups-last.json b/providers/digitalocean2/src/test/resources/backups-last.json
new file mode 100644
index 0000000..c927c19
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/backups-last.json
@@ -0,0 +1,26 @@
+{
+ "backups": [
+ {
+ "id": 76229890,
+ "name": "example.com 2014-11-14",
+ "distribution": "Ubuntu",
+ "slug": null,
+ "public": false,
+ "regions": [
+ "nyc3"
+ ],
+ "created_at": "2014-11-14T16:07:38Z",
+ "type": "snapshot",
+ "min_disk_size": 20
+ }
+ ],
+ "links" : {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=1",
+ "prev" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=1"
+ }
+ },
+ "meta": {
+ "total": 2
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/droplet-create-req.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/droplet-create-req.json b/providers/digitalocean2/src/test/resources/droplet-create-req.json
new file mode 100644
index 0000000..3ed5273
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/droplet-create-req.json
@@ -0,0 +1,12 @@
+{
+ "name": "digitalocean2-s-d5e",
+ "region": "sfo1",
+ "size": "512mb",
+ "image": "6374124",
+ "ssh_keys": [
+ 421192
+ ],
+ "backups": false,
+ "ipv6": false,
+ "private_networking": false
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/droplet-create-res.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/droplet-create-res.json b/providers/digitalocean2/src/test/resources/droplet-create-res.json
new file mode 100644
index 0000000..a7d37a5
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/droplet-create-res.json
@@ -0,0 +1,35 @@
+{
+ "droplet": {
+ "id": 2987224,
+ "name": "digitalocean2-s-d5e",
+ "memory": 512,
+ "vcpus": 1,
+ "disk": 20,
+ "locked": true,
+ "status": "new",
+ "kernel": {
+ "id": 70,
+ "name": "Ubuntu 10.04 x64 vmlinuz-2.6.32-41-server",
+ "version": "2.6.32-41-server"
+ },
+ "created_at": "2014-10-27T19:33:34Z",
+ "features": [
+ "virtio"
+ ],
+ "backup_ids": [],
+ "snapshot_ids": [],
+ "image": {},
+ "size_slug": "512mb",
+ "networks": {},
+ "region": {}
+ },
+ "links": {
+ "actions": [
+ {
+ "id": 35383956,
+ "rel": "create",
+ "href": "https://api.digitalocean.com/v2/actions/35383956"
+ }
+ ]
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/droplet.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/droplet.json b/providers/digitalocean2/src/test/resources/droplet.json
new file mode 100644
index 0000000..fb995c5
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/droplet.json
@@ -0,0 +1,105 @@
+{
+ "droplet" :
+ {
+ "created_at" : "2015-05-25T15:50:48Z",
+ "region" : {
+ "name" : "New York 1",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true
+ },
+ "id" : 5425561,
+ "disk" : 20,
+ "networks" : {
+ "v6" : [],
+ "v4" : [
+ {
+ "type" : "public",
+ "ip_address" : "162.243.167.46",
+ "netmask" : "255.255.255.0",
+ "gateway" : "162.243.167.1"
+ }
+ ]
+ },
+ "backup_ids" : [],
+ "image" : {
+ "slug" : "ubuntu-14-10-x32",
+ "public" : true,
+ "created_at" : "2015-01-08T18:41:22Z",
+ "distribution" : "Ubuntu",
+ "id" : 9801951,
+ "type" : "snapshot",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "name" : "14.10 x32",
+ "min_disk_size" : 20
+ },
+ "vcpus" : 1,
+ "next_backup_window" : {
+ "end" : "2015-06-01T23:00:00Z",
+ "start" : "2015-06-01T00:00:00Z"
+ },
+ "locked" : false,
+ "snapshot_ids" : [],
+ "kernel" : {
+ "name" : "Ubuntu 14.10 x32 vmlinuz-3.16.0-28-generic",
+ "id" : 2926,
+ "version" : "3.16.0-28-generic"
+ },
+ "status" : "active",
+ "features" : [
+ "backups",
+ "virtio"
+ ],
+ "size" : {
+ "price_hourly" : 0.00744,
+ "slug" : "512mb",
+ "disk" : 20,
+ "available" : true,
+ "transfer" : 1,
+ "price_monthly" : 5,
+ "regions" : [
+ "nyc1",
+ "sgp1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "ams2",
+ "fra1"
+ ],
+ "memory" : 512,
+ "vcpus" : 1
+ },
+ "name" : "test1",
+ "size_slug" : "512mb",
+ "memory" : 512
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/droplets-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/droplets-first.json b/providers/digitalocean2/src/test/resources/droplets-first.json
new file mode 100644
index 0000000..5493f7f
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/droplets-first.json
@@ -0,0 +1,115 @@
+{
+ "links" : {
+ "pages" : {
+ "next" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1",
+ "last" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1"
+ }
+ },
+ "meta" : {
+ "total" : 2
+ },
+ "droplets" : [
+ {
+ "created_at" : "2015-05-25T15:50:48Z",
+ "region" : {
+ "name" : "New York 1",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true
+ },
+ "id" : 5425561,
+ "disk" : 20,
+ "networks" : {
+ "v6" : [],
+ "v4" : [
+ {
+ "type" : "public",
+ "ip_address" : "162.243.167.46",
+ "netmask" : "255.255.255.0",
+ "gateway" : "162.243.167.1"
+ }
+ ]
+ },
+ "backup_ids" : [],
+ "image" : {
+ "slug" : "ubuntu-14-10-x32",
+ "public" : true,
+ "created_at" : "2015-01-08T18:41:22Z",
+ "distribution" : "Ubuntu",
+ "id" : 9801951,
+ "type" : "snapshot",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "name" : "14.10 x32",
+ "min_disk_size" : 20
+ },
+ "vcpus" : 1,
+ "next_backup_window" : {
+ "end" : "2015-06-01T23:00:00Z",
+ "start" : "2015-06-01T00:00:00Z"
+ },
+ "locked" : false,
+ "snapshot_ids" : [],
+ "kernel" : {
+ "name" : "Ubuntu 14.10 x32 vmlinuz-3.16.0-28-generic",
+ "id" : 2926,
+ "version" : "3.16.0-28-generic"
+ },
+ "status" : "active",
+ "features" : [
+ "backups",
+ "virtio"
+ ],
+ "size" : {
+ "price_hourly" : 0.00744,
+ "slug" : "512mb",
+ "disk" : 20,
+ "available" : true,
+ "transfer" : 1,
+ "price_monthly" : 5,
+ "regions" : [
+ "nyc1",
+ "sgp1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "ams2",
+ "fra1"
+ ],
+ "memory" : 512,
+ "vcpus" : 1
+ },
+ "name" : "test1",
+ "size_slug" : "512mb",
+ "memory" : 512
+ }
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/droplets-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/droplets-last.json b/providers/digitalocean2/src/test/resources/droplets-last.json
new file mode 100644
index 0000000..beeb654
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/droplets-last.json
@@ -0,0 +1,115 @@
+{
+ "links" : {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1",
+ "prev" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1"
+ }
+ },
+ "meta" : {
+ "total" : 2
+ },
+ "droplets" : [
+ {
+ "created_at" : "2015-05-25T15:50:48Z",
+ "region" : {
+ "name" : "New York 1",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true
+ },
+ "id" : 5425561,
+ "disk" : 20,
+ "networks" : {
+ "v6" : [],
+ "v4" : [
+ {
+ "type" : "public",
+ "ip_address" : "162.243.167.46",
+ "netmask" : "255.255.255.0",
+ "gateway" : "162.243.167.1"
+ }
+ ]
+ },
+ "backup_ids" : [],
+ "image" : {
+ "slug" : "ubuntu-14-10-x32",
+ "public" : true,
+ "created_at" : "2015-01-08T18:41:22Z",
+ "distribution" : "Ubuntu",
+ "id" : 9801951,
+ "type" : "snapshot",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "name" : "14.10 x32",
+ "min_disk_size" : 20
+ },
+ "vcpus" : 1,
+ "next_backup_window" : {
+ "end" : "2015-06-01T23:00:00Z",
+ "start" : "2015-06-01T00:00:00Z"
+ },
+ "locked" : false,
+ "snapshot_ids" : [],
+ "kernel" : {
+ "name" : "Ubuntu 14.10 x32 vmlinuz-3.16.0-28-generic",
+ "id" : 2926,
+ "version" : "3.16.0-28-generic"
+ },
+ "status" : "active",
+ "features" : [
+ "backups",
+ "virtio"
+ ],
+ "size" : {
+ "price_hourly" : 0.00744,
+ "slug" : "512mb",
+ "disk" : 20,
+ "available" : true,
+ "transfer" : 1,
+ "price_monthly" : 5,
+ "regions" : [
+ "nyc1",
+ "sgp1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "ams2",
+ "fra1"
+ ],
+ "memory" : 512,
+ "vcpus" : 1
+ },
+ "name" : "test1",
+ "size_slug" : "512mb",
+ "memory" : 512
+ }
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/image.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/image.json b/providers/digitalocean2/src/test/resources/image.json
new file mode 100644
index 0000000..e66fda9
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/image.json
@@ -0,0 +1,24 @@
+{
+ "image" : {
+ "type" : "snapshot",
+ "id" : 11732785,
+ "name" : "Maintenance Mode",
+ "min_disk_size" : 20,
+ "distribution" : "Debian",
+ "created_at" : "2015-05-05T21:21:25Z",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "slug" : null,
+ "public" : true
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/images-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/images-first.json b/providers/digitalocean2/src/test/resources/images-first.json
new file mode 100644
index 0000000..7d98cf9
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/images-first.json
@@ -0,0 +1,108 @@
+{
+ "meta" : {
+ "total" : 54
+ },
+ "links" : {
+ "pages" : {
+ "next" : "https://api.digitalocean.com/v2/images?page=2&per_page=5&type=distribution",
+ "last" : "https://api.digitalocean.com/v2/images?page=11&per_page=5&type=distribution"
+ }
+ },
+ "images" : [
+ {
+ "type" : "snapshot",
+ "id" : 11732785,
+ "name" : "Maintenance Mode",
+ "min_disk_size" : 20,
+ "distribution" : "Debian",
+ "created_at" : "2015-05-05T21:21:25Z",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "slug" : null,
+ "public" : true
+ },
+ {
+ "type" : "snapshot",
+ "id" : 11833262,
+ "distribution" : "CoreOS",
+ "created_at" : "2015-05-12T17:41:36Z",
+ "regions" : [
+ "nyc1",
+ "sfo1",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "public" : true,
+ "slug" : "coreos-stable",
+ "name" : "647.0.0 (stable)",
+ "min_disk_size" : 20
+ },
+ {
+ "min_disk_size" : 20,
+ "name" : "668.3.0 (beta)",
+ "created_at" : "2015-05-18T18:14:12Z",
+ "public" : true,
+ "regions" : [
+ "nyc1",
+ "sfo1",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "slug" : "coreos-beta",
+ "distribution" : "CoreOS",
+ "id" : 11919888,
+ "type" : "snapshot"
+ },
+ {
+ "type" : "snapshot",
+ "id" : 11919908,
+ "distribution" : "CoreOS",
+ "created_at" : "2015-05-18T18:20:08Z",
+ "public" : true,
+ "regions" : [
+ "nyc1",
+ "sfo1",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "slug" : "coreos-alpha",
+ "name" : "681.0.0 (alpha)",
+ "min_disk_size" : 20
+ },
+ {
+ "min_disk_size" : 30,
+ "name" : "vum-easter-move",
+ "slug" : null,
+ "regions" : [
+ "ams1"
+ ],
+ "public" : true,
+ "created_at" : "2015-04-10T07:31:20Z",
+ "distribution" : "Debian",
+ "id" : 11385199,
+ "type" : "snapshot"
+ }
+ ]
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/images-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/images-last.json b/providers/digitalocean2/src/test/resources/images-last.json
new file mode 100644
index 0000000..89642fe
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/images-last.json
@@ -0,0 +1,123 @@
+{
+ "meta" : {
+ "total" : 54
+ },
+ "links" : {
+ "pages" : {
+ "prev" : "https://api.digitalocean.com/v2/images?page=1&per_page=5&type=distribution",
+ "first" : "https://api.digitalocean.com/v2/images?page=1&per_page=5&type=distribution"
+ }
+ },
+ "images" : [
+ {
+ "distribution" : "Fedora",
+ "min_disk_size" : 20,
+ "id" : 6370882,
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "name" : "20 x64",
+ "type" : "snapshot",
+ "slug" : "fedora-20-x64",
+ "public" : true,
+ "created_at" : "2014-09-26T15:29:01Z"
+ },
+ {
+ "name" : "20 x32",
+ "type" : "snapshot",
+ "distribution" : "Fedora",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "id" : 6370885,
+ "min_disk_size" : 20,
+ "created_at" : "2014-09-26T15:29:18Z",
+ "public" : true,
+ "slug" : "fedora-20-x32"
+ },
+ {
+ "created_at" : "2014-09-26T16:40:18Z",
+ "slug" : "centos-5-8-x64",
+ "public" : true,
+ "type" : "snapshot",
+ "name" : "5.10 x64",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "min_disk_size" : 20,
+ "id" : 6372321,
+ "distribution" : "CentOS"
+ },
+ {
+ "public" : true,
+ "slug" : "centos-5-8-x32",
+ "created_at" : "2014-09-26T16:45:29Z",
+ "id" : 6372425,
+ "min_disk_size" : 20,
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "distribution" : "CentOS",
+ "type" : "snapshot",
+ "name" : "5.10 x32"
+ },
+ {
+ "created_at" : "2014-09-26T16:56:00Z",
+ "public" : true,
+ "slug" : "debian-6-0-x64",
+ "type" : "snapshot",
+ "name" : "6.0 x64",
+ "regions" : [
+ "nyc1",
+ "ams1",
+ "sfo1",
+ "nyc2",
+ "ams2",
+ "sgp1",
+ "lon1",
+ "nyc3",
+ "ams3",
+ "fra1"
+ ],
+ "min_disk_size" : 20,
+ "id" : 6372581,
+ "distribution" : "Debian"
+ }
+ ]
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/kernels-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/kernels-first.json b/providers/digitalocean2/src/test/resources/kernels-first.json
new file mode 100644
index 0000000..3ec05e0
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/kernels-first.json
@@ -0,0 +1,38 @@
+{
+ "links" : {
+ "pages" : {
+ "next" : "https://api.digitalocean.com/v2/droplets/5425561/kernels?page=2",
+ "last" : "https://api.digitalocean.com/v2/droplets/5425561/kernels?page=2"
+ }
+ },
+ "meta" : {
+ "total" : 10
+ },
+ "kernels" : [
+ {
+ "id" : 231,
+ "version" : "3.8.0-25-generic",
+ "name" : "DO-recovery-static-fsck"
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-41-generic-pae",
+ "version" : "2.6.32-41-generic-pae",
+ "id" : 61
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-56-generic-pae",
+ "id" : 946,
+ "version" : "2.6.32-56-generic-pae"
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-57-generic-pae",
+ "id" : 987,
+ "version" : "2.6.32-57-generic-pae"
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-58-generic-pae",
+ "id" : 1269,
+ "version" : "2.6.32-58-generic-pae"
+ }
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/kernels-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/kernels-last.json b/providers/digitalocean2/src/test/resources/kernels-last.json
new file mode 100644
index 0000000..ca6c4f6
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/kernels-last.json
@@ -0,0 +1,38 @@
+{
+ "links" : {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/droplets/5425561/kernels?page=2",
+ "prev" : "https://api.digitalocean.com/v2/droplets/5425561/kernels?page=2"
+ }
+ },
+ "meta" : {
+ "total" : 10
+ },
+ "kernels" : [
+ {
+ "id" : 2311,
+ "version" : "3.8.0-25-generic",
+ "name" : "DO-recovery-static-fsck"
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-41-generic-pae",
+ "version" : "2.6.32-41-generic-pae",
+ "id" : 6111
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-56-generic-pae",
+ "id" : 94611,
+ "version" : "2.6.32-56-generic-pae"
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-57-generic-pae",
+ "id" : 98711,
+ "version" : "2.6.32-57-generic-pae"
+ },
+ {
+ "name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-58-generic-pae",
+ "id" : 1269,
+ "version" : "2.6.32-58-generic-pae"
+ }
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/key.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/key.json b/providers/digitalocean2/src/test/resources/key.json
new file mode 100644
index 0000000..db77937
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/key.json
@@ -0,0 +1,8 @@
+{
+ "ssh_key" : {
+ "id" : 767051,
+ "name" : "ubuntu-1204-64bit-338",
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCIshoBsRRK73Kwq9zlKauwNWClpaR99TpAxjtP5DcL3EMCTCDepaNPMHhPYO1rogRyjZrc2nsJ1ImCbZ3eEGFjspyhIyPk1NbYkAFsoSV7eBtqy1V7ddZ8t1ZsDexigAA5GnXZSWL4O0oVyZpBTrkzhZ49lbWUq8ch3Hhulvml1BR4nTx92ZcYIFFr1S7NEtua9xhKvRvUcSmgL/0A8deOBgkc85y5ADcEIt+nrlrOtIOW/agX0VPXNFjRxYW7MCkRGoDObOYEaT/mj7PyKk0kimmemxAH5wd6aOI4C82TmHjQmXTwXZgpVlrsdbxomOGDDGNSy7HoQLI/xMRvEf+9",
+ "fingerprint" : "1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90"
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/keys-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/keys-first.json b/providers/digitalocean2/src/test/resources/keys-first.json
new file mode 100644
index 0000000..93cbdd7
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/keys-first.json
@@ -0,0 +1,43 @@
+{
+ "meta" : {
+ "total" : 17
+ },
+ "ssh_keys" : [
+ {
+ "id" : 767051,
+ "name" : "ubuntu-1204-64bit-338",
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCIshoBsRRK73Kwq9zlKauwNWClpaR99TpAxjtP5DcL3EMCTCDepaNPMHhPYO1rogRyjZrc2nsJ1ImCbZ3eEGFjspyhIyPk1NbYkAFsoSV7eBtqy1V7ddZ8t1ZsDexigAA5GnXZSWL4O0oVyZpBTrkzhZ49lbWUq8ch3Hhulvml1BR4nTx92ZcYIFFr1S7NEtua9xhKvRvUcSmgL/0A8deOBgkc85y5ADcEIt+nrlrOtIOW/agX0VPXNFjRxYW7MCkRGoDObOYEaT/mj7PyKk0kimmemxAH5wd6aOI4C82TmHjQmXTwXZgpVlrsdbxomOGDDGNSy7HoQLI/xMRvEf+9",
+ "fingerprint" : "1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90"
+ },
+ {
+ "id" : 767067,
+ "name" : "ubuntu-1204-64bit-ea4",
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdkjezVub0t+lSNSQYbO+Kcd/SoOJBCYgETqroI+ysHpPjRRNTYp3XSqSB0O3snnXM1GRfjPFatYFBUO9ajsb8cq0pzDPEIh3+cn+ZEoQ5WPg8OpaU6zVt2fPZNwqE2FCWEr2naH2yj2qV7Wzd1T9xYRfFdQ19YU1HuUhs16zvF6cO4BJH0YX2HLtZkxtOHonSbBx78/hBoV1NgSKlzlHVj9rhqmJO0fcEBeia4y/6G/65n8Z3hrM3zwyoN/eBKV7ngeJ70VU8tcP/QK9anB6hx4lOOSnWro2j3GKDwjSADL2vbOGl4OS6kM2y+6I04A6UAUkMu7KWCSr1Zf0zC45N",
+ "fingerprint" : "8f:81:51:d2:91:09:25:8f:c2:9c:b2:c3:6f:67:55:77"
+ },
+ {
+ "name" : "ubuntu-1204-64bit-jdk8-850",
+ "id" : 767363,
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0zjvxYEyNYsVd7lfYXJxLON98OTgdMizD14XYCHwhVdJkS5ht9h+6dpiWbgatVgb/xyB63N2EeW+yZvQGlohWFytd1HvX7C8LFVtS/h4nrFJxtEYc2QXRl+WRRys6ekxYy6cwf/WV8nQPitUIPQru7vKR8bRpMPDBh+2Va/k7ApLd83AM9UYwXgCxDC7wlL/ttdJCV40buwPVvbOot97+qFPyg600/s6MGMX5448VifeBEEZG/qjzHj4SSGpQn+12O0dFLpYxOruzLLBpu2QxwvIqj1Nsm2ij6seMh0J8a5rQPl3ssPYrDCi8QGhL2NyzDSmT/9tYMLOlhBj7x3zN",
+ "fingerprint" : "1c:17:a2:c2:00:42:89:63:23:79:01:d7:b8:e6:a6:b4"
+ },
+ {
+ "name" : "ubuntu-1204-64bit-jdk8-ead",
+ "id" : 767375,
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCTMLrMCDSPF3BBeDjzSIYtVi8ACkxrvG4VT58sI+XjpeE9c3KGekqXqeTKZFwLxRllgbox8j610ABvcdAstBBSooSYmuDjT2nFotiTcw+5iHUe4A8BHG3HSlqXfuPlBCZ9bE4MdDBTPXqaDjiuvUqWdjpUihZeX2W2OtznDXDMFtn846Df0hD2V7ixHqVVQRXgflSfIoajEY5MlVG/Iuh3cjPpoJZ/IN/FcQnHjNe1FdJM8fC7kF+yY8sEE+u10KlfR1Y9WTdJSIiz5QDdlR4RTPXYyb9wyctD/sxCJ3xAgfZOG7Ey8eZF0+0MSbxkUGQll/xLuaP2aA5owIfMkEoj",
+ "fingerprint" : "a1:4d:20:c8:25:ce:2f:fa:43:8d:21:6f:65:77:c9:c4"
+ },
+ {
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCj9vCuDlTrQd+9WqlsQxWLg7VO2oVwq960CyW9C7rEro07wPGDdiOQornhyy7KiSL0mqYzFT5j0vTICSYeiGXIO/4SFPekz7vRN1yjO2GKaPokl/eYnbt1ABqB41jL0YX4ySwTm11O8A5yqWMouyxiGlSr13p61S2LTim5r5Z2C+4dKWHduloxnyCJJsh/56ZquS/ERgcrZusis9ybIP3aP+5DbXmyokGU83qGuNqSjZXaWc7MGFRTZzzzR9dQsbMvTgPwpyFXtSruIC+u7ca8oJ0b7+9WCAj2g/GXzbv/dsiJyJkEOtx6k1NN8n02PmL3o50vM1GGAjBFUN65JghD",
+ "name" : "ubuntu-1204-64bit-jdk8-ce2",
+ "id" : 767390,
+ "fingerprint" : "7f:bc:1c:af:e5:51:b6:1b:f0:bd:93:de:60:1a:e9:25"
+ }
+ ],
+ "links" : {
+ "pages" : {
+ "next" : "https://api.digitalocean.com/v2/account/keys?page=2&per_page=5",
+ "last" : "https://api.digitalocean.com/v2/account/keys?page=2&per_page=5"
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/keys-last.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/keys-last.json b/providers/digitalocean2/src/test/resources/keys-last.json
new file mode 100644
index 0000000..becbed1
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/keys-last.json
@@ -0,0 +1,25 @@
+{
+ "meta" : {
+ "total" : 7
+ },
+ "ssh_keys" : [
+ {
+ "fingerprint" : "a1:df:a0:64:e4:36:10:6b:de:8d:e6:65:55:17:8f:31",
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCHcVV1Rb5+/sCDF0eZ9Hw8vrHF+HYxptZRaNmnWsjzjeupeIiFgq6W1gRrDKB6pjA1t8R0b1xlcfb0361B5fRuwkeQTr2ALior6cipTgUUHT4UKcZMah7n09/YMPj2I7vzxjdX07PyB1z4OkL7FT3zBrsDTaR+ZngeFAVG59WmCj6vD2EqoZ2PeiEKETZOvQnZ3MEGiU5sYX9odIh5mjO5Jg1Q7oLjFzBbmMQ0oo/gkte8nBybHlD20iq1kzvmoSV4ECbTIa48yr8cgsX0d4M29YV9v2WCBfqaSRTRGVhfsalu9sgeSTHZ7F68g1wEkPXOzE3ZCUlZsKpskiYmCcTP",
+ "name" : "ubuntu-1204-64bit-jdk8-77a",
+ "id" : 799690
+ },
+ {
+ "name" : "ubuntu-1204-64bit-jdk8-a14",
+ "public_key" : "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCeU4rK0wlk8YOOAWd9GURgJzOlgNkLEarq/y9RMXKq1zUW9Jf5J4UuztZm6HsSpAbiBfwL/QYBsFEhfDUZiW0B5p6CL0cR93vgUhGVgoi+v5Nl3KDEgMDryja48CdmzkCB3KoX4aRGRkahx6chrFByM5laEyGJETnDl0s4VyxZH07FGgQYqdWj8jg99stIvZ8Ktzlq5RMXPYePEA03vzzc5JrTEtdhuiy/wjxY8piq1j40uch6JEfcEzfPdkWY7LZt5KlOtrYoVFVmu+Mp3QSknhZPyMMZW5GOEtcr1IGtOEBCL3gJWz0E/H4e4Itt+3L2bNpNfHJmISJRXsNanuyn",
+ "fingerprint" : "f3:34:8d:dc:26:31:90:2b:55:f0:d4:77:d4:17:1e:f2",
+ "id" : 802469
+ }
+ ],
+ "links" : {
+ "pages" : {
+ "first" : "https://api.digitalocean.com/v2/account/keys?page=1&per_page=5",
+ "prev" : "https://api.digitalocean.com/v2/account/keys?page=1&per_page=5"
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/logback-test.xml
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/logback-test.xml b/providers/digitalocean2/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..4cac342
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/logback-test.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<configuration scan="false">
+ <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+ <file>target/test-data/jclouds.log</file>
+ <encoder>
+ <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+ </encoder>
+ </appender>
+ <appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
+ <file>target/test-data/jclouds-wire.log</file>
+ <encoder>
+ <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+ </encoder>
+ </appender>
+ <appender name="COMPUTEFILE" class="ch.qos.logback.core.FileAppender">
+ <file>target/jclouds-compute.log</file>
+ <encoder>
+ <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.jclouds">
+ <level value="DEBUG" />
+ <appender-ref ref="FILE" />
+ </logger>
+ <logger name="jclouds.compute">
+ <level value="DEBUG" />
+ <appender-ref ref="COMPUTEFILE" />
+ </logger>
+ <logger name="jclouds.wire">
+ <level value="DEBUG" />
+ <appender-ref ref="WIREFILE" />
+ </logger>
+ <logger name="jclouds.headers">
+ <level value="DEBUG" />
+ <appender-ref ref="WIREFILE" />
+ </logger>
+
+ <root>
+ <level value="INFO" />
+ </root>
+</configuration>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/power-cycle.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/power-cycle.json b/providers/digitalocean2/src/test/resources/power-cycle.json
new file mode 100644
index 0000000..7af5963
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/power-cycle.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "in-progress",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "power_cycle"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/power-off.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/power-off.json b/providers/digitalocean2/src/test/resources/power-off.json
new file mode 100644
index 0000000..7affe0a
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/power-off.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "in-progress",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "power_off"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/power-on.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/power-on.json b/providers/digitalocean2/src/test/resources/power-on.json
new file mode 100644
index 0000000..32e6e83
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/power-on.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "in-progress",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "power_on"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/reboot.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/reboot.json b/providers/digitalocean2/src/test/resources/reboot.json
new file mode 100644
index 0000000..799500a
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/reboot.json
@@ -0,0 +1,33 @@
+{
+ "action": {
+ "region" : {
+ "name" : "New York 1",
+ "available" : true,
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "sizes" : [
+ "512mb",
+ "8gb",
+ "16gb",
+ "32gb",
+ "48gb",
+ "64gb",
+ "1gb",
+ "2gb",
+ "4gb"
+ ]
+ },
+ "started_at" : "2015-05-19T15:17:55Z",
+ "status" : "in-progress",
+ "resource_type" : "droplet",
+ "resource_id" : 5347489,
+ "region_slug" : "nyc1",
+ "id" : 50900149,
+ "completed_at" : "2015-05-19T15:18:01Z",
+ "type" : "reboot"
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/resources/regions-first.json
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/test/resources/regions-first.json b/providers/digitalocean2/src/test/resources/regions-first.json
new file mode 100644
index 0000000..5be4b3f
--- /dev/null
+++ b/providers/digitalocean2/src/test/resources/regions-first.json
@@ -0,0 +1,111 @@
+{
+ "links" : {
+ "pages" : {
+ "next" : "https://api.digitalocean.com/v2/regions?page=2&per_page=5",
+ "last" : "https://api.digitalocean.com/v2/regions?page=2&per_page=5"
+ }
+ },
+ "meta" : {
+ "total" : 10
+ },
+ "regions" : [
+ {
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "name" : "New York 1",
+ "slug" : "nyc1",
+ "features" : [
+ "virtio",
+ "backups",
+ "metadata"
+ ],
+ "available" : true
+ },
+ {
+ "slug" : "ams1",
+ "name" : "Amsterdam 1",
+ "available" : false,
+ "features" : [
+ "virtio",
+ "backups"
+ ],
+ "sizes" : []
+ },
+ {
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "name" : "San Francisco 1",
+ "slug" : "sfo1",
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "available" : true
+ },
+ {
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ],
+ "available" : true,
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups"
+ ],
+ "name" : "New York 2",
+ "slug" : "nyc2"
+ },
+ {
+ "features" : [
+ "virtio",
+ "private_networking",
+ "backups",
+ "ipv6",
+ "metadata"
+ ],
+ "available" : true,
+ "slug" : "ams2",
+ "name" : "Amsterdam 2",
+ "sizes" : [
+ "32gb",
+ "16gb",
+ "2gb",
+ "1gb",
+ "4gb",
+ "8gb",
+ "512mb",
+ "64gb",
+ "48gb"
+ ]
+ }
+ ]
+}
\ No newline at end of file
[05/19] jclouds git commit: JCLOUDS-613: Implement the DigitalOcean
v2 API
Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
new file mode 100644
index 0000000..3e4aae3
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/compute/strategy/CreateKeyPairsThenCreateNodes.java
@@ -0,0 +1,217 @@
+/*
+ * 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.strategy;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.ssh.DSAKeys;
+import org.jclouds.digitalocean2.ssh.ECDSAKeys;
+import org.jclouds.logging.Logger;
+import org.jclouds.ssh.SshKeyPairGenerator;
+import org.jclouds.ssh.SshKeys;
+
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+@Singleton
+public class CreateKeyPairsThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
+
+ @Resource
+ @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+ protected Logger logger = Logger.NULL;
+
+ private final DigitalOcean2Api api;
+ private final SshKeyPairGenerator keyGenerator;
+ private final Function<String, PublicKey> sshKeyToPublicKey;
+
+ @Inject
+ protected CreateKeyPairsThenCreateNodes(
+ CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+ ListNodesStrategy listNodesStrategy,
+ GroupNamingConvention.Factory namingConvention,
+ @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+ CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
+ DigitalOcean2Api api, SshKeyPairGenerator keyGenerator, Function<String, PublicKey> sshKeyToPublicKey) {
+ super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
+ customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+ this.api = checkNotNull(api, "api cannot be null");
+ this.keyGenerator = checkNotNull(keyGenerator, "keyGenerator cannot be null");
+ checkNotNull(userExecutor, "userExecutor cannot be null");
+ this.sshKeyToPublicKey = checkNotNull(sshKeyToPublicKey, "sshKeyToPublicKey cannot be null");
+ }
+
+ @Override
+ public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
+ Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
+ Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+ DigitalOcean2TemplateOptions options = template.getOptions().as(DigitalOcean2TemplateOptions.class);
+ Set<Integer> generatedSshKeyIds = Sets.newHashSet();
+
+ // If no key has been configured and the auto-create option is set, then generate a key pair
+ if (options.getSshKeyIds().isEmpty() && options.getAutoCreateKeyPair()
+ && Strings.isNullOrEmpty(options.getPublicKey())) {
+ generateKeyPairAndAddKeyToSet(options, generatedSshKeyIds, group);
+ }
+
+ // If there is a script to run in the node, make sure a private key has been configured so jclouds will be able to
+ // access the node
+ if (options.getRunScript() != null) {
+ checkArgument(!Strings.isNullOrEmpty(options.getLoginPrivateKey()),
+ "no private key configured for: %s; please use options.overrideLoginPrivateKey(rsa_private_text)", group);
+ }
+
+ // If there is a key configured, then make sure there is a key pair for it
+ if (!Strings.isNullOrEmpty(options.getPublicKey())) {
+ createKeyPairForPublicKeyInOptionsAndAddToSet(options, generatedSshKeyIds);
+ }
+
+ // Set all keys (the provided and the auto-generated) in the options object so the
+ // DigitalOceanComputeServiceAdapter adds them all
+ options.sshKeyIds(Sets.union(generatedSshKeyIds, options.getSshKeyIds()));
+
+ Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
+ customizationResponses);
+
+ // Key pairs in DigitalOcean are only required to create the Droplets. They aren't used anymore so it is better
+ // to delete the auto-generated key pairs at this point where we know exactly which ones have been
+ // auto-generated by jclouds.
+ registerAutoGeneratedKeyPairCleanupCallbacks(responses, generatedSshKeyIds);
+
+ return responses;
+ }
+
+ private void createKeyPairForPublicKeyInOptionsAndAddToSet(DigitalOcean2TemplateOptions options,
+ Set<Integer> generatedSshKeyIds) {
+ logger.debug(">> checking if the key pair already exists...");
+
+ PublicKey userKey = sshKeyToPublicKey.apply(options.getPublicKey());
+ String userFingerprint = computeFingerprint(userKey);
+ Key key = api.keyApi().get(userFingerprint);
+
+ if (key == null) {
+ logger.debug(">> key pair not found. creating a new one...");
+
+ Key newKey = api.keyApi().create(userFingerprint, options.getPublicKey());
+
+ generatedSshKeyIds.add(newKey.id());
+ logger.debug(">> key pair created! %s", newKey);
+ } else {
+ logger.debug(">> key pair found! %s", key);
+ generatedSshKeyIds.add(key.id());
+ }
+ }
+
+ private void generateKeyPairAndAddKeyToSet(DigitalOcean2TemplateOptions options, Set<Integer> generatedSshKeyIds, String prefix) {
+ logger.debug(">> creating default keypair for node...");
+
+ Map<String, String> defaultKeys = keyGenerator.get();
+
+ Key defaultKey = api.keyApi().create(prefix + "-" + System.getProperty("user.name"), defaultKeys.get("public"));
+ generatedSshKeyIds.add(defaultKey.id());
+
+ logger.debug(">> keypair created! %s", defaultKey);
+
+ // If a private key has not been explicitly set, configure the auto-generated one
+ if (Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+ options.overrideLoginPrivateKey(defaultKeys.get("private"));
+ }
+ }
+
+ private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
+ final Set<Integer> generatedSshKeyIds) {
+ // The Futures.allAsList fails immediately if some of the futures fail. The Futures.successfulAsList, however,
+ // returns a list containing the results or 'null' for those futures that failed. We want to wait for all them
+ // (even if they fail), so better use the latter form.
+ ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+
+ // Key pairs must be cleaned up after all futures completed (even if some failed).
+ Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
+ @Override
+ public void onSuccess(List<Void> result) {
+ cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+ }
+
+ private void cleanupAutoGeneratedKeyPairs(Set<Integer> generatedSshKeyIds) {
+ logger.debug(">> cleaning up auto-generated key pairs...");
+ for (Integer sshKeyId : generatedSshKeyIds) {
+ try {
+ api.keyApi().delete(sshKeyId);
+ } catch (Exception ex) {
+ logger.warn(">> could not delete key pair %s: %s", sshKeyId, ex.getMessage());
+ }
+ }
+ }
+
+ }, userExecutor);
+ }
+
+ private static String computeFingerprint(PublicKey key) {
+ if (key instanceof RSAPublicKey) {
+ RSAPublicKey rsaKey = (RSAPublicKey) key;
+ return SshKeys.fingerprint(rsaKey.getPublicExponent(), rsaKey.getModulus());
+ } else if (key instanceof DSAPublicKey) {
+ DSAPublicKey dsaKey = (DSAPublicKey) key;
+ return DSAKeys.fingerprint(dsaKey.getParams().getP(), dsaKey.getParams().getQ(), dsaKey.getParams().getG(),
+ dsaKey.getY());
+ } else if (key instanceof ECPublicKey) {
+ ECPublicKey ecdsaKey = (ECPublicKey) key;
+ return ECDSAKeys.fingerprint(ecdsaKey);
+ } else {
+ throw new IllegalArgumentException("Only RSA and DSA keys are supported");
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java
new file mode 100644
index 0000000..8bfe266
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOcean2HttpApiModule.java
@@ -0,0 +1,57 @@
+/*
+ * 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.config;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.options.ImageListOptions;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.LinkToImageListOptions;
+import org.jclouds.digitalocean2.functions.LinkToListOptions;
+import org.jclouds.digitalocean2.handlers.DigitalOcean2ErrorHandler;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.HttpApiModule;
+
+import com.google.common.base.Function;
+import com.google.inject.TypeLiteral;
+
+@ConfiguresHttpApi
+public class DigitalOcean2HttpApiModule extends HttpApiModule<DigitalOcean2Api> {
+
+ @Override
+ protected void bindErrorHandlers() {
+ bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(DigitalOcean2ErrorHandler.class);
+ bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(DigitalOcean2ErrorHandler.class);
+ bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(DigitalOcean2ErrorHandler.class);
+ }
+
+ @Override
+ protected void configure() {
+ super.configure();
+ bind(OAuthScopes.class).toInstance(OAuthScopes.ReadOrWriteScopes.create("read", "read write"));
+ bind(new TypeLiteral<Function<URI, ListOptions>>() {
+ }).to(LinkToListOptions.class);
+ bind(new TypeLiteral<Function<URI, ImageListOptions>>() {
+ }).to(LinkToImageListOptions.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java
new file mode 100644
index 0000000..e4bb9bc
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/config/DigitalOceanParserModule.java
@@ -0,0 +1,144 @@
+/*
+ * 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.config;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.size;
+import static com.google.inject.Scopes.SINGLETON;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.digitalocean2.ssh.DSAKeys;
+import org.jclouds.digitalocean2.ssh.ECDSAKeys;
+import org.jclouds.json.config.GsonModule.DateAdapter;
+import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
+import org.jclouds.ssh.SshKeys;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+/**
+ * Custom parser bindings.
+ */
+public class DigitalOceanParserModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(DateAdapter.class).to(Iso8601DateAdapter.class).in(SINGLETON);
+ }
+
+ @Singleton
+ public static class SshPublicKeyAdapter extends TypeAdapter<PublicKey> {
+
+ private final Function<PublicKey, String> publicKeyToSshKey;
+ private final Function<String, PublicKey> sshKeyToPublicKey;
+
+ @Inject
+ public SshPublicKeyAdapter(Function<PublicKey, String> publicKeyToSshKey,
+ Function<String, PublicKey> sshKeyToPublicKey) {
+ this.publicKeyToSshKey = checkNotNull(publicKeyToSshKey, "publicKeyToSshKey cannot be null");
+ this.sshKeyToPublicKey = checkNotNull(sshKeyToPublicKey, "sshKeyToPublicKey cannot be null");
+ }
+
+ @Override
+ public void write(JsonWriter out, PublicKey value) throws IOException {
+ out.value(publicKeyToSshKey.apply(value));
+ }
+
+ @Override
+ public PublicKey read(JsonReader in) throws IOException {
+ return sshKeyToPublicKey.apply(in.nextString().trim());
+ }
+ }
+
+ @Provides
+ @Singleton
+ public Function<PublicKey, String> publicKeyToSshKey() {
+ return new Function<PublicKey, String>() {
+ @Override
+ public String apply(PublicKey input) {
+ if (input instanceof RSAPublicKey) {
+ return SshKeys.encodeAsOpenSSH((RSAPublicKey) input);
+ } else if (input instanceof DSAPublicKey) {
+ return DSAKeys.encodeAsOpenSSH((DSAPublicKey) input);
+ } else {
+ throw new IllegalArgumentException("Only RSA and DSA keys are supported");
+ }
+ }
+ };
+ }
+
+ @Provides
+ @Singleton
+ public Function<String, PublicKey> sshKeyToPublicKey() {
+ return new Function<String, PublicKey>() {
+ @Override
+ public PublicKey apply(String input) {
+ Iterable<String> parts = Splitter.on(' ').split(input);
+ checkArgument(size(parts) >= 2, "bad format, should be: [ssh-rsa|ssh-dss] AAAAB3...");
+ String type = get(parts, 0);
+
+ try {
+ if ("ssh-rsa".equals(type)) {
+ RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(input);
+ return KeyFactory.getInstance("RSA").generatePublic(spec);
+ } else if ("ssh-dss".equals(type)) {
+ DSAPublicKeySpec spec = DSAKeys.publicKeySpecFromOpenSSH(input);
+ return KeyFactory.getInstance("DSA").generatePublic(spec);
+ } else if (type.startsWith("ecdsa-sha2-")) {
+ ECPublicKeySpec spec = ECDSAKeys.publicKeySpecFromOpenSSH(input);
+ return KeyFactory.getInstance("EC").generatePublic(spec);
+ } else {
+ throw new IllegalArgumentException("bad format, jclouds supports ssh-rsa, ssh-dss, ecdsa-sha2-nistp[256|384|521]");
+ }
+ } catch (InvalidKeySpecException ex) {
+ throw propagate(ex);
+ } catch (NoSuchAlgorithmException ex) {
+ throw propagate(ex);
+ }
+ }
+ };
+ }
+
+ @Provides
+ @Singleton
+ public Map<Type, Object> provideCustomAdapterBindings(SshPublicKeyAdapter sshPublicKeyAdapter) {
+ return ImmutableMap.<Type, Object> of(PublicKey.class, sshPublicKeyAdapter);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java
new file mode 100644
index 0000000..b800105
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Action.java
@@ -0,0 +1,71 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Date;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+
+@AutoValue
+public abstract class Action {
+
+ public enum Status {
+ COMPLETED, IN_PROGRESS, ERRORED;
+
+ Status() {}
+
+ public static Status fromValue(String value) {
+ Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+ if (!status.isPresent()) {
+ String upperCamelValue = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value.toLowerCase());
+ status = Enums.getIfPresent(Status.class, upperCamelValue);
+ }
+ checkArgument(status.isPresent(), "Expected one of %s but was", Joiner.on(',').join(Status.values()), value);
+ return status.get();
+ }
+ }
+
+ public abstract int id();
+ public abstract Status status();
+ public abstract String type();
+ public abstract Date startedAt();
+ @Nullable public abstract Date completedAt();
+ public abstract Integer resourceId();
+ public abstract String resourceType();
+ @Nullable public abstract Region region();
+ @Nullable public abstract String regionSlug();
+
+ @SerializedNames({ "id", "status", "type", "started_at", "completed_at", "resource_id", "resource_type",
+ "region", "region_slug" })
+ public static Action create(int id, Status status, String type, Date startedAt, Date completedAt, int resourceId,
+ String resourceType, Region region, String regionSlug) {
+ return new AutoValue_Action(id, status, type, startedAt, completedAt, resourceId, resourceType, region,
+ regionSlug);
+ }
+
+ Action() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java
new file mode 100644
index 0000000..536a187
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Backup.java
@@ -0,0 +1,43 @@
+/*
+ * 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.domain;
+
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Backup {
+ public abstract int id();
+ public abstract String name();
+ public abstract String distribution();
+ @Nullable public abstract String slug();
+ public abstract boolean isPublic();
+ public abstract List<String> regions();
+ public abstract int minDiskSize();
+
+ @SerializedNames({ "id", "name", "distribution", "slug", "public", "regions", "min_disk_size" })
+ public static Backup create(int id, String name, String distribution, String slug, boolean isPublic,
+ List<String> regions, int minDiskSize) {
+ return new AutoValue_Backup(id, name, distribution, slug, isPublic, regions, minDiskSize);
+ }
+
+ Backup() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java
new file mode 100644
index 0000000..1ea0f0e
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Distribution.java
@@ -0,0 +1,69 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.tryFind;
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import org.jclouds.compute.domain.OsFamily;
+import com.google.common.base.Predicate;
+
+/**
+ * DigitalOcean image distributions.
+ */
+public enum Distribution {
+ ARCHLINUX(OsFamily.ARCH, "Arch Linux"),
+ CENTOS(OsFamily.CENTOS, "CentOS"),
+ DEBIAN(OsFamily.DEBIAN, "Debian"),
+ FEDORA(OsFamily.FEDORA, "Fedora"),
+ UBUNTU(OsFamily.UBUNTU, "Ubuntu"),
+ UNRECOGNIZED(OsFamily.UNRECOGNIZED, "");
+
+ private static final List<Distribution> values = asList(Distribution.values());
+
+ private final OsFamily osFamily;
+ private final String value;
+
+ private Distribution(OsFamily osFamily, String value) {
+ this.osFamily = checkNotNull(osFamily, "osFamily cannot be null");
+ this.value = checkNotNull(value, "value cannot be null");
+ }
+
+ public OsFamily osFamily() {
+ return this.osFamily;
+ }
+
+ public String value() {
+ return this.value;
+ }
+
+ public static Distribution fromValue(String value) {
+ return tryFind(values, hasValue(value)).or(UNRECOGNIZED);
+ }
+
+ private static Predicate<Distribution> hasValue(final String value) {
+ return new Predicate<Distribution>() {
+ @Override
+ public boolean apply(Distribution input) {
+ return input.value.equalsIgnoreCase(value);
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java
new file mode 100644
index 0000000..4d0dd4c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Droplet.java
@@ -0,0 +1,92 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.copyOf;
+import static com.google.common.collect.Iterables.concat;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class Droplet {
+
+ public enum Status {
+ NEW, ACTIVE, ARCHIVE, OFF;
+
+ public static Status fromValue(String value) {
+ Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+ checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
+ return status.get();
+ }
+ }
+
+ public abstract int id();
+ public abstract String name();
+ public abstract int memory();
+ public abstract int vcpus();
+ public abstract int disk();
+ public abstract boolean locked();
+ public abstract Date createdAt();
+ public abstract Status status();
+ public abstract List<Integer> backupsIds();
+ public abstract List<Integer> snapshotIds();
+ public abstract List<String> features();
+ @Nullable public abstract Region region();
+ @Nullable public abstract Image image();
+ @Nullable public abstract Size size();
+ public abstract String sizeSlug();
+ @Nullable public abstract Networks networks();
+ @Nullable public abstract Kernel kernel();
+
+ @SerializedNames({ "id", "name", "memory", "vcpus", "disk", "locked", "created_at", "status", "backup_ids",
+ "snapshot_ids", "features", "region", "image", "size", "size_slug", "networks", "kernel" })
+ public static Droplet create(int id, String name, int memory, int vcpus, int disk, boolean locked, Date createdAt,
+ Status status, List<Integer> backupIds, List<Integer> snapshotIds, List<String> features, Region region,
+ Image image, Size size, String sizeSlug, Networks network, Kernel kernel) {
+ return new AutoValue_Droplet(id, name, memory, vcpus, disk, locked, createdAt, status,
+ backupIds == null ? ImmutableList.<Integer> of() : copyOf(backupIds),
+ snapshotIds == null ? ImmutableList.<Integer> of() : copyOf(snapshotIds), copyOf(features), region, image,
+ size, sizeSlug, network, kernel);
+ }
+
+ public Set<Networks.Address> getPublicAddresses() {
+ return FluentIterable.from(concat(networks().ipv4(), networks().ipv6()))
+ .filter(Networks.Predicates.publicNetworks())
+ .toSet();
+ }
+
+ public Set<Networks.Address> getPrivateAddresses() {
+ return FluentIterable.from(concat(networks().ipv4(), networks().ipv6()))
+ .filter(Networks.Predicates.privateNetworks())
+ .toSet();
+ }
+
+ Droplet() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java
new file mode 100644
index 0000000..06ed12b
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/DropletCreate.java
@@ -0,0 +1,66 @@
+/*
+ * 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.domain;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+
+import java.net.URI;
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class DropletCreate {
+ public abstract Droplet droplet();
+ public abstract Links links();
+
+ @AutoValue
+ public abstract static class Links {
+
+ @AutoValue
+ public abstract static class ActionLink {
+ public abstract int id();
+ public abstract String rel();
+ public abstract URI href();
+
+ @SerializedNames({"id", "rel", "href"})
+ public static ActionLink create(int id, String rel, URI href) {
+ return new AutoValue_DropletCreate_Links_ActionLink(id, rel, href);
+ }
+
+ ActionLink() {}
+ }
+
+ public abstract List<ActionLink> actions();
+
+ @SerializedNames({ "actions" })
+ public static Links create(List<ActionLink> actions) {
+ return new AutoValue_DropletCreate_Links(copyOf(actions));
+ }
+
+ Links() {}
+ }
+
+ @SerializedNames({ "droplet", "links" })
+ public static DropletCreate create(Droplet droplet, Links links) {
+ return new AutoValue_DropletCreate(droplet, links);
+ }
+
+ DropletCreate() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java
new file mode 100644
index 0000000..dd2c3b6
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Image.java
@@ -0,0 +1,48 @@
+/*
+ * 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.domain;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+
+import java.util.Date;
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Image {
+
+ public abstract int id();
+ public abstract String name();
+ public abstract String type();
+ public abstract String distribution();
+ @Nullable public abstract String slug();
+ public abstract boolean isPublic();
+ public abstract List<String> regions();
+ public abstract Date createdAt();
+
+ @SerializedNames({ "id", "name", "type", "distribution", "slug", "public", "regions", "created_at" })
+ public static Image create(int id, String name, String type, String distribution, String slug, boolean isPublic,
+ List<String> regions, Date createdAt) {
+ return new AutoValue_Image(id, name, type, distribution, slug, isPublic, copyOf(regions), createdAt);
+ }
+
+ Image() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java
new file mode 100644
index 0000000..7eb5467
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Kernel.java
@@ -0,0 +1,35 @@
+/*
+ * 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.domain;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Kernel {
+ public abstract int id();
+ public abstract String name();
+ public abstract String version();
+
+ @SerializedNames({ "id", "name", "version" })
+ public static Kernel create(int id, String name, String version) {
+ return new AutoValue_Kernel(id, name, version);
+ }
+
+ Kernel() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java
new file mode 100644
index 0000000..c1a7ae3
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Key.java
@@ -0,0 +1,39 @@
+/*
+ * 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.domain;
+
+import java.security.PublicKey;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Key {
+
+ public abstract int id();
+ public abstract String name();
+ public abstract String fingerprint();
+ public abstract PublicKey publicKey();
+
+ @SerializedNames({ "id", "name", "fingerprint", "public_key" })
+ public static Key create(int id, String name, String fingerprint, PublicKey publicKey) {
+ return new AutoValue_Key(id, name, fingerprint, publicKey);
+ }
+
+ Key() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java
new file mode 100644
index 0000000..cd3dbd4
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Networks.java
@@ -0,0 +1,77 @@
+/*
+ * 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.domain;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Predicate;
+
+@AutoValue
+public abstract class Networks {
+
+ @AutoValue
+ public abstract static class Address {
+ public abstract String ip();
+ public abstract String netmask();
+ public abstract String gateway();
+ public abstract String type();
+
+ @SerializedNames({ "ip_address", "netmask", "gateway", "type"})
+ public static Address create(String ip, String netmask, String gateway, String type) {
+ return new AutoValue_Networks_Address(ip, netmask, gateway, type);
+ }
+
+ Address() {}
+ }
+
+ public abstract List<Address> ipv4();
+ public abstract List<Address> ipv6();
+
+ @SerializedNames({ "v4", "v6" })
+ public static Networks create(List<Address> ipv4, List<Address> ipv6) {
+ return new AutoValue_Networks(copyOf(ipv4), copyOf(ipv6));
+ }
+
+ Networks() {}
+
+ public static class Predicates {
+
+ public static Predicate<Address> publicNetworks() {
+ return new Predicate<Address>() {
+ @Override
+ public boolean apply(Address network) {
+ return network.type().equals("public");
+ }
+ };
+ }
+
+ public static Predicate<Address> privateNetworks() {
+ return new Predicate<Address>() {
+ @Override
+ public boolean apply(Address network) {
+ return network.type().equals("private");
+ }
+ };
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java
new file mode 100644
index 0000000..041ea20
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/OperatingSystem.java
@@ -0,0 +1,60 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.Strings.nullToEmpty;
+import static java.util.regex.Pattern.compile;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * The operating system of an image.
+ * <p>
+ * This class parses the <code>name</code> string (e.g. "Ubuntu 12.10 x64") of the images and properly sets each field
+ * to the right value.
+ */
+@AutoValue
+public abstract class OperatingSystem {
+
+ // Parse something like "12.10 x64" or "Ubuntu 12.10.1 x64" and matches the version and architecture
+ private static final Pattern VERSION_PATTERN = compile("(?:[a-zA-Z\\s]*\\s+)?(\\d+(?:\\.?\\d+)*)?(?:\\s*(x\\d{2}))?.*");
+ private static final String IS_64_BIT = "x64";
+
+ public abstract Distribution distribution();
+ public abstract String version();
+ public abstract String arch();
+
+ public static OperatingSystem create(String name, String distribution) {
+ return new AutoValue_OperatingSystem(Distribution.fromValue(distribution), match(VERSION_PATTERN, name, 1),
+ match(VERSION_PATTERN, name, 2));
+ }
+
+ public boolean is64bit() {
+ return IS_64_BIT.equals(arch());
+ }
+
+ OperatingSystem() {}
+
+ private static String match(final Pattern pattern, final String input, int group) {
+ Matcher m = pattern.matcher(input);
+ return m.find() ? nullToEmpty(m.group(group)) : "";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java
new file mode 100644
index 0000000..2b3441c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Region.java
@@ -0,0 +1,39 @@
+/*
+ * 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.domain;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Region {
+ public abstract String slug();
+ public abstract String name();
+ public abstract List<String> sizes();
+ public abstract boolean available();
+ public abstract List<String> features();
+
+ @SerializedNames({ "slug", "name", "sizes", "available", "features" })
+ public static Region create(String slug, String name, List<String> sizes, boolean available, List<String> features) {
+ return new AutoValue_Region(slug, name, sizes, available, features);
+ }
+
+ Region() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java
new file mode 100644
index 0000000..03d9492
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Size.java
@@ -0,0 +1,46 @@
+/*
+ * 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.domain;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Size {
+ public abstract String slug();
+ public abstract boolean available();
+ public abstract float transfer();
+ public abstract float priceMonthly();
+ public abstract float priceHourly();
+ public abstract int memory();
+ public abstract int vcpus();
+ public abstract int disk();
+ public abstract List<String> regions();
+
+ @SerializedNames({ "slug", "available", "transfer", "price_monthly", "price_hourly", "memory", "vcpus", "disk",
+ "regions" })
+ public static Size create(String slug, boolean available, float transfer, float priceMonthly, float priceHourly,
+ int memory, int vcpus, int disk, List<String> regions) {
+ return new AutoValue_Size(slug, available, transfer, priceMonthly, priceHourly, memory, vcpus, disk, regions);
+ }
+
+ Size() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java
new file mode 100644
index 0000000..12daaa2
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/Snapshot.java
@@ -0,0 +1,47 @@
+/*
+ * 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.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Snapshot {
+ public abstract int id();
+ public abstract String name();
+ public abstract String type();
+ public abstract String distribution();
+ @Nullable public abstract String slug();
+ public abstract boolean isPublic();
+ public abstract List<String> regions();
+ public abstract int minDiskSize();
+ public abstract Date createdAt();
+
+ @SerializedNames({ "id", "name", "type", "distribution", "slug", "public", "regions", "min_disk_size", "created_at"})
+ public static Snapshot create(int id, String name, String type, String distribution, String slug, boolean isPublic,
+ List<String> regions, int minDiskSize, Date createdAt) {
+ return new AutoValue_Snapshot(id, name, type, distribution, slug, isPublic, regions, minDiskSize, createdAt);
+ }
+
+ Snapshot() {}
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java
new file mode 100644
index 0000000..8ca7b21
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/internal/PaginatedCollection.java
@@ -0,0 +1,111 @@
+/*
+ * 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.domain.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Base class for all collections that return paginated results.
+ */
+public abstract class PaginatedCollection<T> extends IterableWithMarker<T> {
+
+ @AutoValue
+ public abstract static class Meta {
+ public abstract long total();
+
+ @SerializedNames({ "total" })
+ public static Meta create(long total) {
+ return new AutoValue_PaginatedCollection_Meta(total);
+ }
+
+ Meta() { }
+ }
+
+ @AutoValue
+ public abstract static class Links {
+
+ @AutoValue
+ public abstract static class Pages {
+ @Nullable public abstract URI first();
+ @Nullable public abstract URI prev();
+ @Nullable public abstract URI next();
+ @Nullable public abstract URI last();
+
+ @SerializedNames({ "first", "prev", "next", "last" })
+ public static Pages create(URI first, URI next, URI prev, URI last) {
+ return new AutoValue_PaginatedCollection_Links_Pages(first, next, prev, last);
+ }
+
+ Pages() { }
+ }
+
+ @Nullable public abstract Pages pages();
+
+ @SerializedNames({ "pages" })
+ public static Links create(Pages pages) {
+ return new AutoValue_PaginatedCollection_Links(pages);
+ }
+
+ Links() { }
+ }
+
+ private final List<T> items;
+ private final Meta meta;
+ private final Links links;
+
+ protected PaginatedCollection(List<T> items, Meta meta, Links links) {
+ this.items = ImmutableList.copyOf(checkNotNull(items, "items cannot be null"));
+ this.meta = checkNotNull(meta, "meta cannot be null");
+ this.links = checkNotNull(links, "links cannot be null");
+ }
+
+ public List<T> items() {
+ return items;
+ }
+
+ public Meta meta() {
+ return meta;
+ }
+
+ public Links links() {
+ return links;
+ }
+
+ @Override public Iterator<T> iterator() {
+ return items.iterator();
+ }
+
+ @Override public Optional<Object> nextMarker() {
+ if (links.pages() == null) {
+ return Optional.absent();
+ }
+ return Optional.fromNullable((Object) links.pages().next());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
new file mode 100644
index 0000000..b20fc96
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/CreateDropletOptions.java
@@ -0,0 +1,155 @@
+/*
+ * 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.domain.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Options to customize droplet creation.
+ */
+public class CreateDropletOptions implements MapBinder {
+
+ @Inject private BindToJsonPayload jsonBinder;
+
+ private final Set<Integer> sshKeys;
+ private final boolean backupsEnabled;
+ private final boolean ipv6Enabled;
+ private final boolean privateNetworking;
+ private final String userData;
+
+ private CreateDropletOptions(Set<Integer> sshKeys, boolean backupsEnabled, boolean ipv6Enabled,
+ boolean privateNetworking, @Nullable String userData) {
+ this.sshKeys = sshKeys;
+ this.backupsEnabled = backupsEnabled;
+ this.ipv6Enabled = ipv6Enabled;
+ this.privateNetworking = privateNetworking;
+ this.userData = userData;
+ }
+
+ @AutoValue
+ abstract static class DropletRequest {
+ abstract String name();
+ abstract String region();
+ abstract String size();
+ abstract String image();
+ abstract Set<Integer> sshKeys();
+ abstract Boolean backups();
+ abstract Boolean ipv6();
+ abstract Boolean privateNetworking();
+ @Nullable abstract String userData();
+
+ @SerializedNames({"name", "region", "size", "image", "ssh_keys", "backups", "ipv6", "private_networking", "user_data"})
+ static DropletRequest create(String name, String region, String size, String image, Set<Integer> sshKeys,
+ Boolean backups, Boolean ipv6, Boolean privateNetworking, String userData) {
+ return new AutoValue_CreateDropletOptions_DropletRequest(name, region, size, image, sshKeys, backups, ipv6,
+ privateNetworking, userData);
+ }
+
+ DropletRequest() {}
+ }
+
+ @Override
+ public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+ DropletRequest droplet = DropletRequest.create(checkNotNull(postParams.get("name"), "name parameter not present").toString(),
+ checkNotNull(postParams.get("region"), "region parameter not present").toString(),
+ checkNotNull(postParams.get("size"), "size parameter not present").toString(),
+ checkNotNull(postParams.get("image"), "image parameter not present").toString(),
+ sshKeys, backupsEnabled, ipv6Enabled, privateNetworking, userData);
+
+ return bindToRequest(request, droplet);
+ }
+
+ @Override
+ public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+ return jsonBinder.bindToRequest(request, input);
+ }
+
+ public Set<Integer> getSshKeys() {
+ return sshKeys;
+ }
+
+ public Boolean getPrivateNetworking() {
+ return privateNetworking;
+ }
+
+ public Boolean getBackupsEnabled() {
+ return backupsEnabled;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private ImmutableSet.Builder<Integer> sshKeyIds = ImmutableSet.builder();
+ private boolean backupsEnabled;
+ private boolean ipv6Enabled;
+ private boolean privateNetworking;
+ private String userData;
+
+ /**
+ * Adds a set of ssh key ids to be added to the droplet.
+ */
+ public Builder addSshKeyIds(Iterable<Integer> sshKeyIds) {
+ this.sshKeyIds.addAll(sshKeyIds);
+ return this;
+ }
+
+ /**
+ * Adds an ssh key id to be added to the droplet.
+ */
+ public Builder addSshKeyId(int sshKeyId) {
+ this.sshKeyIds.add(sshKeyId);
+ return this;
+ }
+
+ /**
+ * Enables a private network interface if the region supports private
+ * networking.
+ */
+ public Builder privateNetworking(boolean privateNetworking) {
+ this.privateNetworking = privateNetworking;
+ return this;
+ }
+
+ /**
+ * Enabled backups for the droplet.
+ */
+ public Builder backupsEnabled(boolean backupsEnabled) {
+ this.backupsEnabled = backupsEnabled;
+ return this;
+ }
+
+ public CreateDropletOptions build() {
+ return new CreateDropletOptions(sshKeyIds.build(), backupsEnabled, ipv6Enabled, privateNetworking, userData);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java
new file mode 100644
index 0000000..9f6415d
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ImageListOptions.java
@@ -0,0 +1,74 @@
+/*
+ * 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.domain.options;
+
+/**
+ * Custom options to filter the list of images.
+ */
+public class ImageListOptions extends ListOptions {
+ public static final String TYPE_PARAM = "type";
+ public static final String PRIVATE_PARAM = "private";
+
+ /**
+ * Configures the type of the images to be retrieved.
+ */
+ public ImageListOptions type(String type) {
+ queryParameters.put(TYPE_PARAM, type);
+ return this;
+ }
+
+ /**
+ * Get the images of the current user.
+ */
+ public ImageListOptions privateImages(boolean privateImages) {
+ queryParameters.put(PRIVATE_PARAM, String.valueOf(privateImages));
+ return this;
+ }
+
+ @Override public ImageListOptions perPage(int perPage) {
+ super.perPage(perPage);
+ return this;
+ }
+
+ @Override public ImageListOptions page(int page) {
+ super.page(page);
+ return this;
+ }
+
+ public static final class Builder {
+
+ /**
+ * @see {@link ImageListOptions#type(String)}
+ */
+ public static ImageListOptions type(String type) {
+ return new ImageListOptions().type(type);
+ }
+
+ /**
+ * @see {@link ImageListOptions#privateImages(boolean)}
+ */
+ public static ImageListOptions privateImages(boolean privateImages) {
+ return new ImageListOptions().privateImages(privateImages);
+ }
+ /**
+ * @see {@link ImageListOptions#page(int)}
+ */
+ public static ImageListOptions page(int page) {
+ return new ImageListOptions().page(page);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java
new file mode 100644
index 0000000..f859c1c
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/domain/options/ListOptions.java
@@ -0,0 +1,60 @@
+/*
+ * 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.domain.options;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+/**
+ * Options to customize how paginated lists are returned.
+ */
+public class ListOptions extends BaseHttpRequestOptions {
+ public static final String PAGE_PARAM = "page";
+ public static final String PER_PAGE_PARAM = "per_page";
+
+ /**
+ * Configures the number of entries to return in each page.
+ */
+ public ListOptions perPage(int perPage) {
+ queryParameters.put(PER_PAGE_PARAM, String.valueOf(perPage));
+ return this;
+ }
+
+ /**
+ * Configures the number of the page to be returned.
+ */
+ public ListOptions page(int page) {
+ queryParameters.put(PAGE_PARAM, String.valueOf(page));
+ return this;
+ }
+
+ public static final class Builder {
+
+ /**
+ * @see {@link ListOptions#perPage(int)}
+ */
+ public static ListOptions perPage(int perPage) {
+ return new ListOptions().perPage(perPage);
+ }
+
+ /**
+ * @see {@link ListOptions#page(int)}
+ */
+ public static ListOptions page(int page) {
+ return new ListOptions().page(page);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java
----------------------------------------------------------------------
diff --git a/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java
new file mode 100644
index 0000000..4a7a8bd
--- /dev/null
+++ b/providers/digitalocean2/src/main/java/org/jclouds/digitalocean2/features/ActionApi.java
@@ -0,0 +1,113 @@
+/*
+ * 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.features;
+
+import java.beans.ConstructorProperties;
+import java.io.Closeable;
+import java.net.URI;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.digitalocean2.DigitalOcean2Api;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
+import org.jclouds.digitalocean2.domain.options.ListOptions;
+import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Provides access to Actions via the REST API.
+ *
+ * @see <a href="https://developers.digitalocean.com/v2/#actions"/>
+ * @see ActionApi
+ */
+@Path("/actions")
+@RequestFilters(OAuthFilter.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface ActionApi extends Closeable {
+
+ @Named("action:list")
+ @GET
+ @ResponseParser(ParseActions.class)
+ @Transform(ParseActions.ToPagedIterable.class)
+ @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+ PagedIterable<Action> list();
+
+ @Named("action:list")
+ @GET
+ @ResponseParser(ParseActions.class)
+ @Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
+ IterableWithMarker<Action> list(ListOptions options);
+
+ static final class ParseActions extends ParseJson<ParseActions.Actions> {
+ @Inject ParseActions(Json json) {
+ super(json, TypeLiteral.get(Actions.class));
+ }
+
+ private static class Actions extends PaginatedCollection<Action> {
+ @ConstructorProperties({ "actions", "meta", "links" })
+ public Actions(List<Action> items, Meta meta, Links links) {
+ super(items, meta, links);
+ }
+ }
+
+ private static class ToPagedIterable extends BaseToPagedIterable<Action, ListOptions> {
+ @Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
+ super(api, linkToOptions);
+ }
+
+ @Override
+ protected IterableWithMarker<Action> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+ return api.actionApi().list(options);
+ }
+ }
+ }
+
+ @Named("action:get")
+ @GET
+ @SelectJson("action")
+ @Path("/{id}")
+ @Fallback(NullOnNotFoundOr404.class)
+ @Nullable
+ Action get(@PathParam("id") int id);
+
+}
+