You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by de...@apache.org on 2016/02/19 16:33:35 UTC

[25/35] jclouds git commit: JCLOUDS-702: JClouds ProfitBricks provider - ComputeServiceAdapter

JCLOUDS-702: JClouds ProfitBricks provider - ComputeServiceAdapter


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

Branch: refs/heads/master
Commit: ed247e7dea753daa94c8f0b228804f6947b694e7
Parents: cd91e00
Author: Reijhanniel Jearl Campos <rj...@toro.io>
Authored: Mon Jun 29 11:13:21 2015 +0800
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Jun 30 12:42:51 2015 +0200

----------------------------------------------------------------------
 providers/profitbricks/README.md                |  63 +++
 providers/profitbricks/pom.xml                  |   1 -
 .../profitbricks/ProfitBricksApiMetadata.java   |   7 +-
 .../ProfitBricksProviderMetadata.java           |  25 +-
 .../CreateDataCenterRequestBinder.java          |   2 +-
 .../CreateLoadBalancerRequestBinder.java        |   3 +-
 .../DeregisterLoadBalancerRequestBinder.java    |   3 +-
 .../RegisterLoadBalancerRequestBinder.java      |   3 +-
 .../snapshot/CreateSnapshotRequestBinder.java   |  12 +-
 .../snapshot/RollbackSnapshotRequestBinder.java |  30 +-
 .../snapshot/UpdateSnapshotRequestBinder.java   |  32 +-
 .../ProfitBricksComputeServiceAdapter.java      | 488 +++++++++++++++++++
 .../compute/concurrent/ProvisioningJob.java     |  62 +++
 .../compute/concurrent/ProvisioningManager.java |  88 ++++
 ...ProfitBricksComputeServiceContextModule.java | 147 ++++++
 .../compute/function/DataCenterToLocation.java  |  54 ++
 .../compute/function/LocationToLocation.java    |  47 ++
 .../compute/function/ProvisionableToImage.java  | 215 ++++++++
 .../compute/function/ServerToNodeMetadata.java  | 180 +++++++
 .../compute/function/StorageToVolume.java       |  47 ++
 .../ProvisioningStatusPollingPredicate.java     |  32 +-
 .../config/ProfitBricksComputeProperties.java   |  31 ++
 .../jclouds/profitbricks/domain/Firewall.java   |   6 +-
 .../org/jclouds/profitbricks/domain/Image.java  | 129 +----
 .../profitbricks/domain/LoadBalancer.java       |  46 +-
 .../jclouds/profitbricks/domain/Location.java   |  20 +-
 .../org/jclouds/profitbricks/domain/Nic.java    |   8 +-
 .../org/jclouds/profitbricks/domain/Server.java | 116 ++---
 .../jclouds/profitbricks/domain/Snapshot.java   | 429 ++++++----------
 .../jclouds/profitbricks/domain/Storage.java    |  13 +-
 .../domain/internal/HotPluggable.java           | 102 ++++
 .../domain/internal/Provisionable.java          |  67 +++
 .../domain/internal/ServerCommonProperties.java |  22 +-
 .../profitbricks/features/LoadBalancerApi.java  |   2 +-
 .../firewall/FirewallListResponseHandler.java   |   6 +-
 .../ipblock/IpBlockListResponseHandler.java     |   6 +-
 .../BaseLoadBalancerResponseHandler.java        |  33 +-
 .../LoadBalancerListResponseHandler.java        |  25 +-
 .../LoadBalancerResponseHandler.java            |  13 +-
 .../server/BaseServerResponseHandler.java       |  40 +-
 .../server/ServerInfoResponseHandler.java       |   7 +-
 .../server/ServerListResponseHandler.java       |  24 +-
 .../snapshot/BaseSnapshotResponseHandler.java   |  48 +-
 .../snapshot/SnapshotListResponseHandler.java   |  12 +-
 .../snapshot/SnapshotResponseHandler.java       |  11 +-
 .../storage/BaseStorageResponseHandler.java     |  12 +-
 .../storage/StorageInfoResponseHandler.java     |   7 +-
 .../storage/StorageListResponseHandler.java     |  12 +-
 .../jclouds/profitbricks/util/Passwords.java    |  64 +++
 .../CreateSnapshotRequestBinderTest.java        |  14 +-
 .../RollbackSnapshotRequestBinderTest.java      |  16 +-
 ...ofitBricksComputeServiceAdapterLiveTest.java |  74 +++
 .../ProfitBricksTemplateBuilderLiveTest.java    |  37 ++
 .../concurrent/ProvisioningManagerTest.java     | 118 +++++
 .../function/DataCenterToLocationTest.java      |  77 +++
 .../function/LocationToLocationTest.java        |  62 +++
 .../function/ProvisionableToImageTest.java      | 260 ++++++++++
 .../function/ServerToNodeMetadataTest.java      | 184 +++++++
 .../compute/function/StorageToVolumeTest.java   |  61 +++
 .../ProvisioningStatusPollingPredicateTest.java |   6 +-
 .../features/DrivesApiLiveTest.java             |   9 +-
 .../features/FirewallApiLiveTest.java           |   2 +-
 .../features/IpBlockApiLiveTest.java            |   2 +-
 .../features/IpBlockApiMockTest.java            |   4 +-
 .../features/LoadbalancerApiLiveTest.java       |   2 +-
 .../features/LoadbalancerApiMockTest.java       |   2 +-
 .../features/SnapshotApiLiveTest.java           |  32 +-
 .../features/SnapshotApiMockTest.java           | 174 +++----
 .../DataCenterInfoResponseHandlerTest.java      |  22 +-
 .../image/ImageListResponseHandlerTest.java     |   6 +-
 .../LoadBalancerListResponseHandlerTest.java    |  75 ++-
 .../LoadBalancerResponseHandlerTest.java        |  28 +-
 .../server/ServerInfoResponseHandlerTest.java   |  20 +-
 .../server/ServerListResponseHandlerTest.java   |  32 +-
 .../SnapshotListResponseHandlerTest.java        |  89 ++--
 .../snapshot/SnapshotResponseHandlerTest.java   |  49 +-
 .../storage/StorageInfoResponseHandlerTest.java |  13 +-
 .../storage/StorageListResponseHandlerTest.java |  17 +-
 .../profitbricks/util/PasswordsTest.java        |  53 ++
 .../resources/datacenter/datacenter-deleted.xml |  14 +-
 .../datacenter/datacenter-not-found.xml         |  28 +-
 .../datacenter/datacenter-state-inprocess.xml   |  10 +-
 .../resources/datacenter/datacenter-state.xml   |  10 +-
 .../src/test/resources/drives/drives-add.xml    |  18 +-
 .../src/test/resources/drives/drives-remove.xml |  18 +-
 .../resources/firewall/firewall-activate.xml    |  18 +-
 .../resources/firewall/firewall-addtonic.xml    |  40 +-
 .../resources/firewall/firewall-deactivate.xml  |  18 +-
 .../test/resources/firewall/firewall-delete.xml |  18 +-
 .../test/resources/firewall/firewall-remove.xml |  18 +-
 .../src/test/resources/firewall/firewall.xml    |  40 +-
 .../test/resources/ipblock/ipblock-addtonic.xml |  18 +-
 .../test/resources/ipblock/ipblock-release.xml  |  14 +-
 .../resources/ipblock/ipblock-removefromnic.xml |  18 +-
 .../test/resources/ipblock/ipblock-reserve.xml  |  20 +-
 .../src/test/resources/ipblock/ipblock.xml      |  30 +-
 .../loadbalancer/loadbalancer-create.xml        |   4 +-
 .../loadbalancer/loadbalancer-delete.xml        |  18 +-
 .../loadbalancer/loadbalancer-deregister.xml    |   8 +-
 .../loadbalancer/loadbalancer-register.xml      |   8 +-
 .../loadbalancer/loadbalancer-update.xml        |   4 +-
 .../resources/loadbalancer/loadbalancer.xml     |   2 +-
 .../resources/loadbalancer/loadbalancers.xml    |   4 +-
 .../src/test/resources/server/server.xml        |   4 +-
 .../src/test/resources/snapshot/snapshots.xml   |  38 +-
 105 files changed, 3643 insertions(+), 1189 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/README.md
----------------------------------------------------------------------
diff --git a/providers/profitbricks/README.md b/providers/profitbricks/README.md
new file mode 100644
index 0000000..09c367a
--- /dev/null
+++ b/providers/profitbricks/README.md
@@ -0,0 +1,63 @@
+# jclouds ProfitBricks
+
+## Terms
+Like any cloud provider, ProfitBricks has its own set of terms in cloud computing. To abstract this into jclouds' Compute interface, these terms were associated:
+
+- Node - composite instance of `Server` and `Storage`
+- Image - both *user-uploaded* and *provided* `Images`; and `Snapshots`
+- Location - `DataCenters` and `Region` (Las Vegas, Frankfurt, etc.)
+- Hardware - number of cores, RAM size and storage size
+
+## Getting Started
+
+Assuming that there's **atleast one** datacenter existing in your account, the provider needs only an *identity* (your ProfitBricks email), and *credentials* (password) to provision a `Node`, by using a ProfitBricks-provided ubuntu-12.04 image as a template. 
+
+```java
+ComputeService compute = ContextBuilder.newBuilder( "profitbricks" )
+					.credentials( "profitbricks email", "password" )
+					.buildView( ComputeServiceContext.class )
+					.getComputeService();
+```
+
+
+This works well; however, we won't be able to use jclouds' ability to execute *scripts* on a remote node. This is because, ProfitBricks' default images require users to change passwords upon first log in.
+
+To enable jclouds to execute script, we need to use a custom image. The easiest way to do this is via ProfitBricks snapshot:
+
+-  Go to your [DCD](https://my.profitbricks.com/dashboard/).
+-  Provision a server + storage, and connect it to the internet. Upon success, you will receive an email containing the credentials needed to login to your server.
+-  Login to your server, and change the password, as requested.
+
+```
+~ ssh root@<remote-ip>
+...
+Changing password for root.
+(current) UNIX password: 
+Enter new UNIX password: 
+Retype new UNIX password: 
+~ root@ubuntu:~# exit
+
+```
+
+- Go back to the DCD, and *make a snapshot* of the storage. Put a descriptive name.
+- Configure jclouds to use this *snapshot*.
+
+```java 
+Template template = compute.templateBuilder()
+	.imageNameMatches( "<ideally-unique-snapshot-name>" )
+	.options( compute.templateOptions()
+				.overrideLoginUser( "root" ) // unless you changed the user
+				.overrideLoginPassword( "<changed-password>" ))
+	// more options, as you need
+	.build();
+	
+compute.createNodesInGroup( "cluster1", 1, template );
+```
+> If no `locationId` is specified in the template, jclouds will look for a `DataCenter` that is of same scope as the `Image`.
+
+
+## Limitations
+
+- There's no direct way of specifying arbitrary number of cores, RAM size, and storage size via the compute interface, at least until after [JCLOUDS-482](https://issues.apache.org/jira/browse/JCLOUDS-482) is resolved. The adapter uses a predefined list hardware profiles instead.
+
+> Take note that these features are still accessible by *unwraping* the ProfitBricks API, but this'll reduce portability of your code. See [Concepts](https://jclouds.apache.org/start/concepts/).
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/pom.xml
----------------------------------------------------------------------
diff --git a/providers/profitbricks/pom.xml b/providers/profitbricks/pom.xml
index 84f49b4..9311e48 100644
--- a/providers/profitbricks/pom.xml
+++ b/providers/profitbricks/pom.xml
@@ -36,7 +36,6 @@
         <test.profitbricks.identity>FIXME</test.profitbricks.identity>
         <test.profitbricks.credential>FIXME</test.profitbricks.credential>
         <test.profitbricks.api-version>1.3</test.profitbricks.api-version>
-        <test.profitbricks.template />
         <jclouds.osgi.export>org.jclouds.profitbricks*;version="${project.version}"</jclouds.osgi.export>
         <jclouds.osgi.import>
             org.jclouds.labs*;version="${project.version}",

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksApiMetadata.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksApiMetadata.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksApiMetadata.java
index 205b246..2973f4a 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksApiMetadata.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksApiMetadata.java
@@ -19,8 +19,10 @@ package org.jclouds.profitbricks;
 import java.net.URI;
 import java.util.Properties;
 
+import org.jclouds.profitbricks.compute.config.ProfitBricksComputeServiceContextModule;
 import org.jclouds.profitbricks.config.ProfitBricksHttpApiModule;
 import org.jclouds.apis.ApiMetadata;
+import org.jclouds.compute.ComputeServiceContext;
 import org.jclouds.profitbricks.config.ProfitBricksHttpApiModule.ProfitBricksHttpCommandExecutorServiceModule;
 import org.jclouds.rest.internal.BaseHttpApiMetadata;
 
@@ -60,11 +62,12 @@ public class ProfitBricksApiMetadata extends BaseHttpApiMetadata<ProfitBricksApi
                  .documentation(URI.create("https://www.profitbricks.com/sites/default/files/profitbricks_api_1_3.pdf"))
                  .defaultEndpoint("https://api.profitbricks.com/1.3")
                  .version("1.3")
-                 // .view(ComputeServiceContext.class)
+                 .view(ComputeServiceContext.class)
                  .defaultProperties(ProfitBricksApiMetadata.defaultProperties())
                  .defaultModules(ImmutableSet.<Class<? extends Module>>of(
                                  ProfitBricksHttpApiModule.class,
-                                 ProfitBricksHttpCommandExecutorServiceModule.class
+                                 ProfitBricksHttpCommandExecutorServiceModule.class,
+                                 ProfitBricksComputeServiceContextModule.class
                          ));
       }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksProviderMetadata.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksProviderMetadata.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksProviderMetadata.java
index 9ecfbc1..ed6c556 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksProviderMetadata.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/ProfitBricksProviderMetadata.java
@@ -16,8 +16,17 @@
  */
 package org.jclouds.profitbricks;
 
+import static org.jclouds.Constants.PROPERTY_CONNECTION_TIMEOUT;
+import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_PERIOD;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_MAX_PERIOD;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_TIMEOUT;
+
 import com.google.auto.service.AutoService;
+
 import java.net.URI;
+import java.util.Properties;
+
 import org.jclouds.providers.ProviderMetadata;
 import org.jclouds.providers.internal.BaseProviderMetadata;
 
@@ -41,6 +50,19 @@ public class ProfitBricksProviderMetadata extends BaseProviderMetadata {
       return new Builder();
    }
 
+   public static Properties defaultProperties() {
+      Properties properties = ProfitBricksApiMetadata.defaultProperties();
+      long defaultTimeout = 60l * 60l; // 1 hour
+      properties.put(POLL_TIMEOUT, defaultTimeout);
+      properties.put(POLL_PERIOD, 2l);
+      properties.put(POLL_MAX_PERIOD, 2l * 10l);
+
+      properties.put(PROPERTY_SO_TIMEOUT, 60000 * 5);
+      properties.put(PROPERTY_CONNECTION_TIMEOUT, 60000 * 5);
+
+      return properties;
+   }
+
    public static class Builder extends BaseProviderMetadata.Builder {
 
       protected Builder() {
@@ -49,7 +71,8 @@ public class ProfitBricksProviderMetadata extends BaseProviderMetadata {
                  .homepage(URI.create("http://www.profitbricks.com"))
                  .console(URI.create("https://my.profitbricks.com/dashboard/dcdr2/"))
                  .linkedServices("profitbricks")
-                 .apiMetadata(new ProfitBricksApiMetadata());
+                 .apiMetadata(new ProfitBricksApiMetadata())
+                 .defaultProperties(ProfitBricksProviderMetadata.defaultProperties());
       }
 
       @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/datacenter/CreateDataCenterRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/datacenter/CreateDataCenterRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/datacenter/CreateDataCenterRequestBinder.java
index 8a07b0a..1873f31 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/datacenter/CreateDataCenterRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/datacenter/CreateDataCenterRequestBinder.java
@@ -35,7 +35,7 @@ public class CreateDataCenterRequestBinder extends BaseProfitBricksRequestBinder
       requestBuilder.append("<ws:createDataCenter>")
               .append("<request>")
               .append(format("<dataCenterName>%s</dataCenterName>", payload.name()))
-              .append(format("<location>%s</location>", payload.location().value()))
+              .append(format("<location>%s</location>", payload.location().getId()))
               .append("</request>")
               .append("</ws:createDataCenter>");
       return requestBuilder.toString();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/CreateLoadBalancerRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/CreateLoadBalancerRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/CreateLoadBalancerRequestBinder.java
index 23e121e..90eb93f 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/CreateLoadBalancerRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/CreateLoadBalancerRequestBinder.java
@@ -39,9 +39,8 @@ public class CreateLoadBalancerRequestBinder extends BaseProfitBricksRequestBind
               .append(format("<loadBalancerAlgorithm>%s</loadBalancerAlgorithm>", payload.loadBalancerAlgorithm()))
               .append(format("<ip>%s</ip>", payload.ip()))
               .append(format("<lanId>%s</lanId>", payload.lanId()));
-      for (String serverId : payload.serverIds()) {
+      for (String serverId : payload.serverIds())
          requestBuilder.append(format("<serverIds>%s</serverIds>", serverId));
-      }
       requestBuilder
               .append("</request>")
               .append("</ws:createLoadBalancer>");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/DeregisterLoadBalancerRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/DeregisterLoadBalancerRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/DeregisterLoadBalancerRequestBinder.java
index 92f2868..ba237c4 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/DeregisterLoadBalancerRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/DeregisterLoadBalancerRequestBinder.java
@@ -33,9 +33,8 @@ public class DeregisterLoadBalancerRequestBinder extends BaseProfitBricksRequest
    protected String createPayload(LoadBalancer.Request.DeregisterPayload payload) {
       requestBuilder.append("<ws:deregisterServersOnLoadBalancer>")
               .append("<request>");
-      for (String s : payload.serverIds()) {
+      for (String s : payload.serverIds())
          requestBuilder.append(format("<serverIds>%s</serverIds>", s));
-      }
       requestBuilder.append(format("<loadBalancerId>%s</loadBalancerId>", payload.id()))
               .append("</request>")
               .append("</ws:deregisterServersOnLoadBalancer>");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/RegisterLoadBalancerRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/RegisterLoadBalancerRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/RegisterLoadBalancerRequestBinder.java
index 2e437f0..21f1d84 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/RegisterLoadBalancerRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/loadbalancer/RegisterLoadBalancerRequestBinder.java
@@ -35,9 +35,8 @@ public class RegisterLoadBalancerRequestBinder extends BaseProfitBricksRequestBi
               .append("<ws:registerServersOnLoadBalancer>").append("<request>")
               .append(format("<loadBalancerId>%s</loadBalancerId>", payload.id()));
 
-      for (String s : payload.serverIds()) {
+      for (String s : payload.serverIds())
          requestBuilder.append(format("<serverIds>%s</serverIds>", s));
-      }
       requestBuilder
               .append("</request>")
               .append("</ws:registerServersOnLoadBalancer>");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/CreateSnapshotRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/CreateSnapshotRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/CreateSnapshotRequestBinder.java
index 213a3a8..5ec4644 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/CreateSnapshotRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/CreateSnapshotRequestBinder.java
@@ -33,12 +33,12 @@ public class CreateSnapshotRequestBinder extends BaseProfitBricksRequestBinder<S
    @Override
    protected String createPayload(Snapshot.Request.CreatePayload payload) {
       requestBuilder.append("<ws:createSnapshot>")
-	      .append("<request>")
-	      .append(format("<storageId>%s</storageId>", payload.storageId()))
-	      .append(formatIfNotEmpty("<description>%s</description>", payload.description()))
-	      .append(formatIfNotEmpty("<snapshotName>%s</snapshotName>", payload.name()))
-	      .append("</request>")
-	      .append("</ws:createSnapshot>");
+              .append("<request>")
+              .append(format("<storageId>%s</storageId>", payload.storageId()))
+              .append(formatIfNotEmpty("<description>%s</description>", payload.description()))
+              .append(formatIfNotEmpty("<snapshotName>%s</snapshotName>", payload.name()))
+              .append("</request>")
+              .append("</ws:createSnapshot>");
       return requestBuilder.toString();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/RollbackSnapshotRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/RollbackSnapshotRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/RollbackSnapshotRequestBinder.java
index a9997cb..5099324 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/RollbackSnapshotRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/RollbackSnapshotRequestBinder.java
@@ -23,21 +23,21 @@ import static java.lang.String.format;
 
 public class RollbackSnapshotRequestBinder extends BaseProfitBricksRequestBinder<Snapshot.Request.RollbackPayload> {
 
-    protected final StringBuilder requestBuilder;
+   protected final StringBuilder requestBuilder;
 
-    protected RollbackSnapshotRequestBinder() {
-        super("snapshot");
-        this.requestBuilder = new StringBuilder(128);
-    }
+   protected RollbackSnapshotRequestBinder() {
+      super("snapshot");
+      this.requestBuilder = new StringBuilder(128);
+   }
 
-    @Override
-    protected String createPayload(Snapshot.Request.RollbackPayload payload) {
-        requestBuilder.append("<ws:rollbackSnapshot>")
-                .append("<request>")
-                .append(format("<snapshotId>%s</snapshotId>", payload.snapshotId()))
-                .append(format("<storageId>%s</storageId>", payload.storageId()))
-                .append("</request>")
-                .append("</ws:rollbackSnapshot>");
-        return requestBuilder.toString();
-    }
+   @Override
+   protected String createPayload(Snapshot.Request.RollbackPayload payload) {
+      requestBuilder.append("<ws:rollbackSnapshot>")
+              .append("<request>")
+              .append(format("<snapshotId>%s</snapshotId>", payload.snapshotId()))
+              .append(format("<storageId>%s</storageId>", payload.storageId()))
+              .append("</request>")
+              .append("</ws:rollbackSnapshot>");
+      return requestBuilder.toString();
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/UpdateSnapshotRequestBinder.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/UpdateSnapshotRequestBinder.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/UpdateSnapshotRequestBinder.java
index e396715..df1b7cd 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/UpdateSnapshotRequestBinder.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/binder/snapshot/UpdateSnapshotRequestBinder.java
@@ -32,22 +32,22 @@ public class UpdateSnapshotRequestBinder extends BaseProfitBricksRequestBinder<S
    @Override
    protected String createPayload(Snapshot.Request.UpdatePayload payload) {
       requestBuilder.append("<ws:updateSnapshot>")
-	      .append("<request>")
-	      .append(format("<snapshotId>%s</snapshotId>", payload.snapshotId()))
-	      .append(format("<description>%s</description>", payload.description()))
-	      .append(format("<snapshotName>%s</snapshotName>", payload.name()))
-	      .append(formatIfNotEmpty("<bootable>%s</bootable>", payload.bootable()))
-	      .append(formatIfNotEmpty("<osType>%s</osType>", payload.osType()))
-	      .append(formatIfNotEmpty("<cpuHotPlug>%s</cpuHotPlug>", payload.cpuHotplug()))
-	      .append(formatIfNotEmpty("<cpuHotUnPlug>%s</cpuHotUnPlug>", payload.cpuHotunplug()))
-	      .append(formatIfNotEmpty("<ramHotPlug>%s</ramHotPlug>", payload.ramHotplug()))
-	      .append(formatIfNotEmpty("<ramHotUnPlug>%s</ramHotUnPlug>", payload.ramHotunplug()))
-	      .append(formatIfNotEmpty("<nicHotPlug>%s</nicHotPlug>", payload.nicHotplug()))
-	      .append(formatIfNotEmpty("<nicHotUnPlug>%s</nicHotUnPlug>", payload.nicHotunplug()))
-	      .append(formatIfNotEmpty("<discVirtioHotPlug>%s</discVirtioHotPlug>", payload.discVirtioHotplug()))
-	      .append(formatIfNotEmpty("<discVirtioHotUnPlug>%s</discVirtioHotUnPlug>", payload.discVirtioHotunplug()))
-	      .append("</request>")
-	      .append("</ws:updateSnapshot>");
+              .append("<request>")
+              .append(format("<snapshotId>%s</snapshotId>", payload.snapshotId()))
+              .append(format("<description>%s</description>", payload.description()))
+              .append(format("<snapshotName>%s</snapshotName>", payload.name()))
+              .append(formatIfNotEmpty("<bootable>%s</bootable>", payload.bootable()))
+              .append(formatIfNotEmpty("<osType>%s</osType>", payload.osType()))
+              .append(formatIfNotEmpty("<cpuHotPlug>%s</cpuHotPlug>", payload.isCpuHotPlug()))
+              .append(formatIfNotEmpty("<cpuHotUnPlug>%s</cpuHotUnPlug>", payload.isCpuHotUnPlug()))
+              .append(formatIfNotEmpty("<ramHotPlug>%s</ramHotPlug>", payload.isRamHotPlug()))
+              .append(formatIfNotEmpty("<ramHotUnPlug>%s</ramHotUnPlug>", payload.isRamHotUnPlug()))
+              .append(formatIfNotEmpty("<nicHotPlug>%s</nicHotPlug>", payload.isNicHotPlug()))
+              .append(formatIfNotEmpty("<nicHotUnPlug>%s</nicHotUnPlug>", payload.isNicHotUnPlug()))
+              .append(formatIfNotEmpty("<discVirtioHotPlug>%s</discVirtioHotPlug>", payload.isDiscVirtioHotPlug()))
+              .append(formatIfNotEmpty("<discVirtioHotUnPlug>%s</discVirtioHotUnPlug>", payload.isDiscVirtioHotUnPlug()))
+              .append("</request>")
+              .append("</ws:updateSnapshot>");
       return requestBuilder.toString();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/ProfitBricksComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/ProfitBricksComputeServiceAdapter.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/ProfitBricksComputeServiceAdapter.java
new file mode 100644
index 0000000..add3fb9
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/ProfitBricksComputeServiceAdapter.java
@@ -0,0 +1,488 @@
+/*
+ * 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.profitbricks.compute;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+import static java.lang.String.format;
+import static org.jclouds.Constants.PROPERTY_USER_THREADS;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.internal.VolumeImpl;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.util.ComputeServiceUtils;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.logging.Logger;
+import org.jclouds.profitbricks.ProfitBricksApi;
+import org.jclouds.profitbricks.domain.AvailabilityZone;
+import org.jclouds.profitbricks.domain.DataCenter;
+import org.jclouds.profitbricks.domain.Image;
+import org.jclouds.profitbricks.domain.Server;
+import org.jclouds.profitbricks.domain.Storage;
+import org.jclouds.profitbricks.features.DataCenterApi;
+import org.jclouds.profitbricks.features.ServerApi;
+import org.jclouds.profitbricks.compute.concurrent.ProvisioningJob;
+import org.jclouds.profitbricks.compute.concurrent.ProvisioningManager;
+import org.jclouds.profitbricks.domain.Snapshot;
+import org.jclouds.profitbricks.domain.internal.Provisionable;
+import org.jclouds.profitbricks.util.Passwords;
+import org.jclouds.rest.ResourceNotFoundException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+
+@Singleton
+public class ProfitBricksComputeServiceAdapter implements ComputeServiceAdapter<Server, Hardware, Provisionable, DataCenter> {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ProfitBricksApi api;
+   private final Predicate<String> waitDcUntilAvailable;
+   private final ListeningExecutorService executorService;
+   private final ProvisioningJob.Factory jobFactory;
+   private final ProvisioningManager provisioningManager;
+
+   private static final Integer DEFAULT_LAN_ID = 1;
+
+   @Inject
+   ProfitBricksComputeServiceAdapter(ProfitBricksApi api,
+           @Named(POLL_PREDICATE_DATACENTER) Predicate<String> waitDcUntilAvailable,
+           @Named(PROPERTY_USER_THREADS) ListeningExecutorService executorService,
+           ProvisioningJob.Factory jobFactory,
+           ProvisioningManager provisioningManager) {
+      this.api = api;
+      this.waitDcUntilAvailable = waitDcUntilAvailable;
+      this.executorService = executorService;
+      this.jobFactory = jobFactory;
+      this.provisioningManager = provisioningManager;
+   }
+
+   @Override
+   public NodeAndInitialCredentials<Server> createNodeWithGroupEncodedIntoName(String group, String name, Template template) {
+      final String dataCenterId = template.getLocation().getId();
+      Hardware hardware = template.getHardware();
+
+      TemplateOptions options = template.getOptions();
+      final String loginUser = isNullOrEmpty(options.getLoginUser()) ? "root" : options.getLoginUser();
+      final String password = options.hasLoginPassword() ? options.getLoginPassword() : Passwords.generate();
+
+      final org.jclouds.compute.domain.Image image = template.getImage();
+
+      // provision all storages based on hardware
+      List<? extends Volume> volumes = hardware.getVolumes();
+      List<String> storageIds = Lists.newArrayListWithExpectedSize(volumes.size());
+
+      int i = 1;
+      for (final Volume volume : volumes)
+         try {
+            logger.trace("<< provisioning storage '%s'", volume);
+            final Storage.Request.CreatePayload request = Storage.Request.creatingBuilder()
+                    .dataCenterId(dataCenterId)
+                    // put image to first storage
+                    .mountImageId(i == 1 ? image.getId() : "")
+                    .imagePassword(password)
+                    .name(format("%s-disk-%d", name, i++))
+                    .size(volume.getSize())
+                    .build();
+
+            String storageId = (String) provisioningManager.provision(jobFactory.create(dataCenterId, new Supplier<Object>() {
+
+               @Override
+               public Object get() {
+                  return api.storageApi().createStorage(request);
+               }
+            }));
+
+            storageIds.add(storageId);
+            logger.trace(">> provisioning complete for storage. returned id='%s'", storageId);
+         } catch (Exception ex) {
+            if (i - 1 == 1) // if first storage (one with image) provisioning fails; stop method
+               throw Throwables.propagate(ex);
+            logger.warn(ex, ">> failed to provision storage. skipping..");
+         }
+
+      int lanId = DEFAULT_LAN_ID;
+      if (options.getNetworks() != null)
+         try {
+            String networkId = Iterables.get(options.getNetworks(), 0);
+            lanId = Integer.valueOf(networkId);
+         } catch (Exception ex) {
+            logger.warn("no valid network id found from options. using default id='%d'", DEFAULT_LAN_ID);
+         }
+
+      Double cores = ComputeServiceUtils.getCores(hardware);
+
+      // provision server and connect boot storage (first provisioned)
+      String serverId = null;
+      try {
+         String storageBootDeviceId = Iterables.get(storageIds, 0); // must have atleast 1
+         final Server.Request.CreatePayload serverRequest = Server.Request.creatingBuilder()
+                 .dataCenterId(dataCenterId)
+                 .name(name)
+                 .bootFromStorageId(storageBootDeviceId)
+                 .cores(cores.intValue())
+                 .ram(hardware.getRam())
+                 .availabilityZone(AvailabilityZone.AUTO)
+                 .hasInternetAccess(true)
+                 .lanId(lanId)
+                 .build();
+         logger.trace("<< provisioning server '%s'", serverRequest);
+
+         serverId = (String) provisioningManager.provision(jobFactory.create(dataCenterId, new Supplier<Object>() {
+
+            @Override
+            public Object get() {
+               return api.serverApi().createServer(serverRequest);
+            }
+         }));
+         logger.trace(">> provisioning complete for server. returned id='%s'", serverId);
+
+      } catch (Exception ex) {
+         logger.error(ex, ">> failed to provision server. rollbacking..");
+         destroyStorages(storageIds, dataCenterId);
+         throw Throwables.propagate(ex);
+      }
+
+      // connect the rest of storages to server; delete if fails
+      final int storageCount = storageIds.size();
+      for (int j = 1; j < storageCount; j++) { // skip first; already connected
+         String storageId = storageIds.get(j);
+         try {
+            logger.trace("<< connecting storage '%s' to server '%s'", storageId, serverId);
+            final Storage.Request.ConnectPayload request = Storage.Request.connectingBuilder()
+                    .storageId(storageId)
+                    .serverId(serverId)
+                    .build();
+
+            provisioningManager.provision(jobFactory.create(group, new Supplier<Object>() {
+
+               @Override
+               public Object get() {
+                  return api.storageApi().connectStorageToServer(request);
+               }
+            }));
+
+            logger.trace(">> storage connected.");
+         } catch (Exception ex) {
+            // delete unconnected storage
+            logger.warn(ex, ">> failed to connect storage '%s'. deleting..", storageId);
+            destroyStorage(storageId, dataCenterId);
+         }
+      }
+
+      // Last paranoid check
+      waitDcUntilAvailable.apply(dataCenterId);
+
+      LoginCredentials serverCredentials = LoginCredentials.builder()
+              .user(loginUser)
+              .password(password)
+              .build();
+
+      Server server = getNode(serverId);
+
+      return new NodeAndInitialCredentials<Server>(server, serverId, serverCredentials);
+   }
+
+   @Override
+   public Iterable<Hardware> listHardwareProfiles() {
+      // Max [cores=48] [disk size per storage=2048GB] [ram=200704 MB]
+      List<Hardware> hardwares = Lists.newArrayList();
+      for (int core = 1; core <= 48; core++)
+         for (int ram : new int[]{1024, 2 * 1024, 4 * 1024, 8 * 1024,
+            10 * 1024, 16 * 1024, 24 * 1024, 28 * 1024, 32 * 1024})
+            for (float size : new float[]{10, 20, 30, 50, 80, 100, 150, 200, 250, 500}) {
+               String id = String.format("cpu=%d,ram=%s,disk=%f", core, ram, size);
+               hardwares.add(new HardwareBuilder()
+                       .ids(id)
+                       .ram(ram)
+                       .hypervisor("kvm")
+                       .name(id)
+                       .processor(new Processor(core, 1d))
+                       .volume(new VolumeImpl(size, true, true))
+                       .build());
+            }
+      return hardwares;
+   }
+
+   @Override
+   public Iterable<Provisionable> listImages() {
+      // fetch images..
+      ListenableFuture<List<Image>> images = executorService.submit(new Callable<List<Image>>() {
+
+         @Override
+         public List<Image> call() throws Exception {
+            logger.trace("<< fetching images..");
+            // Filter HDD types only, since JClouds doesn't have a concept of "CD-ROM" anyway
+            Iterable<Image> filteredImages = Iterables.filter(api.imageApi().getAllImages(), new Predicate<Image>() {
+
+               @Override
+               public boolean apply(Image image) {
+                  return image.type() == Image.Type.HDD;
+               }
+            });
+            logger.trace(">> images fetched.");
+
+            return ImmutableList.copyOf(filteredImages);
+         }
+
+      });
+      // and snapshots at the same time
+      ListenableFuture<List<Snapshot>> snapshots = executorService.submit(new Callable<List<Snapshot>>() {
+
+         @Override
+         public List<Snapshot> call() throws Exception {
+            logger.trace("<< fetching snapshots");
+            List<Snapshot> remoteSnapshots = api.snapshotApi().getAllSnapshots();
+            logger.trace(">> snapshots feched.");
+
+            return remoteSnapshots;
+         }
+
+      });
+
+      return Iterables.concat(getUnchecked(images), getUnchecked(snapshots));
+   }
+
+   @Override
+   public Provisionable getImage(String id) {
+      // try search images
+      logger.trace("<< searching for image with id=%s", id);
+      Image image = api.imageApi().getImage(id);
+      if (image != null) {
+         logger.trace(">> found image [%s].", image.name());
+         return image;
+      }
+      // try search snapshots
+      logger.trace("<< not found from images. searching for snapshot with id=%s", id);
+      Snapshot snapshot = api.snapshotApi().getSnapshot(id);
+      if (snapshot != null) {
+         logger.trace(">> found snapshot [%s]", snapshot.name());
+         return snapshot;
+      }
+      throw new ResourceNotFoundException("No image/snapshot with id '" + id + "' was found");
+   }
+
+   @Override
+   public Iterable<DataCenter> listLocations() {
+      logger.trace("<< fetching datacenters..");
+      final DataCenterApi dcApi = api.dataCenterApi();
+
+      // Fetch all datacenters
+      ListenableFuture<List<DataCenter>> futures = allAsList(transform(dcApi.getAllDataCenters(),
+              new Function<DataCenter, ListenableFuture<DataCenter>>() {
+
+                 @Override
+                 public ListenableFuture<DataCenter> apply(final DataCenter input) {
+                    // Fetch more details in parallel
+                    return executorService.submit(new Callable<DataCenter>() {
+                       @Override
+                       public DataCenter call() throws Exception {
+                          logger.trace("<< fetching datacenter with id [%s]", input.id());
+                          return dcApi.getDataCenter(input.id());
+                       }
+
+                    });
+                 }
+              }));
+
+      return getUnchecked(futures);
+   }
+
+   @Override
+   public Server getNode(String id) {
+      logger.trace("<< searching for server with id=%s", id);
+
+      Server server = api.serverApi().getServer(id);
+      if (server != null)
+         logger.trace(">> found server [%s]", server.name());
+      return server;
+   }
+
+   @Override
+   public void destroyNode(String nodeId) {
+      ServerApi serverApi = api.serverApi();
+      Server server = serverApi.getServer(nodeId);
+      if (server != null) {
+         String dataCenterId = server.dataCenter().id();
+         for (Storage storage : server.storages())
+            destroyStorage(storage.id(), dataCenterId);
+
+         try {
+            destroyServer(nodeId, dataCenterId);
+         } catch (Exception ex) {
+            logger.warn(ex, ">> failed to delete server with id=%s", nodeId);
+         }
+      }
+   }
+
+   @Override
+   public void rebootNode(final String id) {
+      // Fail pre-emptively if not found
+      final Server node = getRequiredNode(id);
+      final DataCenter dataCenter = node.dataCenter();
+      provisioningManager.provision(jobFactory.create(dataCenter.id(), new Supplier<Object>() {
+
+         @Override
+         public Object get() {
+            api.serverApi().resetServer(id);
+
+            return node;
+         }
+      }));
+   }
+
+   @Override
+   public void resumeNode(final String id) {
+      final Server node = getRequiredNode(id);
+      if (node.status() == Server.Status.RUNNING)
+         return;
+
+      final DataCenter dataCenter = node.dataCenter();
+      provisioningManager.provision(jobFactory.create(dataCenter.id(), new Supplier<Object>() {
+
+         @Override
+         public Object get() {
+            api.serverApi().startServer(id);
+
+            return node;
+         }
+      }));
+   }
+
+   @Override
+   public void suspendNode(final String id) {
+      final Server node = getRequiredNode(id);
+      // Intentionally didn't include SHUTDOWN (only achieved via UI; soft-shutdown). 
+      // A SHUTOFF server is no longer billed, so we execute method for all other status
+      if (node.status() == Server.Status.SHUTOFF)
+         return;
+
+      final DataCenter dataCenter = node.dataCenter();
+      provisioningManager.provision(jobFactory.create(dataCenter.id(), new Supplier<Object>() {
+
+         @Override
+         public Object get() {
+            api.serverApi().stopServer(id);
+
+            return node;
+         }
+      }));
+   }
+
+   @Override
+   public Iterable<Server> listNodes() {
+      logger.trace(">> fetching all servers..");
+      List<Server> servers = api.serverApi().getAllServers();
+      logger.trace(">> servers fetched.");
+      return servers;
+   }
+
+   @Override
+   public Iterable<Server> listNodesByIds(final Iterable<String> ids) {
+      // Only fetch the requested nodes. Do it in parallel.
+      ListenableFuture<List<Server>> futures = allAsList(transform(ids,
+              new Function<String, ListenableFuture<Server>>() {
+
+                 @Override
+                 public ListenableFuture<Server> apply(final String input) {
+                    return executorService.submit(new Callable<Server>() {
+
+                       @Override
+                       public Server call() throws Exception {
+                          return getNode(input);
+                       }
+                    });
+                 }
+              }));
+
+      return getUnchecked(futures);
+   }
+
+   private void destroyServer(final String serverId, final String dataCenterId) {
+      try {
+         logger.trace("<< deleting server with id=%s", serverId);
+         provisioningManager.provision(jobFactory.create(dataCenterId, new Supplier<Object>() {
+
+            @Override
+            public Object get() {
+               api.serverApi().deleteServer(serverId);
+
+               return serverId;
+            }
+         }));
+         logger.trace(">> server '%s' deleted.", serverId);
+      } catch (Exception ex) {
+         logger.warn(ex, ">> failed to delete server with id=%s", serverId);
+      }
+   }
+
+   private void destroyStorages(List<String> storageIds, String dataCenterId) {
+      for (String storageId : storageIds)
+         destroyStorage(storageId, dataCenterId);
+   }
+
+   private void destroyStorage(final String storageId, final String dataCenterId) {
+      try {
+         logger.trace("<< deleting storage with id=%s", storageId);
+         provisioningManager.provision(jobFactory.create(dataCenterId, new Supplier<Object>() {
+
+            @Override
+            public Object get() {
+               api.storageApi().deleteStorage(storageId);
+
+               return storageId;
+            }
+         }));
+         logger.trace(">> storage '%s' deleted.", storageId);
+      } catch (Exception ex) {
+         logger.warn(ex, ">> failed to delete storage with id=%s", storageId);
+      }
+   }
+
+   private Server getRequiredNode(String nodeId) {
+      Server node = getNode(nodeId);
+      if (node == null)
+         throw new ResourceNotFoundException("Node with id'" + nodeId + "' was not found.");
+      return node;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningJob.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningJob.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningJob.java
new file mode 100644
index 0000000..7da7d3c
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningJob.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.profitbricks.compute.concurrent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+
+import java.util.concurrent.Callable;
+
+import javax.inject.Named;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class ProvisioningJob implements Callable {
+
+   public interface Factory {
+
+      ProvisioningJob create(String group, Supplier<Object> operation);
+   }
+
+   private final Predicate<String> waitDataCenterUntilReady;
+   private final String group;
+   private final Supplier<Object> operation;
+
+   @Inject
+   ProvisioningJob(@Named(POLL_PREDICATE_DATACENTER) Predicate<String> waitDataCenterUntilReady,
+           @Assisted String group, @Assisted Supplier<Object> operation) {
+      this.waitDataCenterUntilReady = waitDataCenterUntilReady;
+      this.group = checkNotNull(group, "group cannot be null");
+      this.operation = checkNotNull(operation, "operation cannot be null");
+   }
+
+   @Override
+   public Object call() throws Exception {
+      waitDataCenterUntilReady.apply(group);
+      Object obj = operation.get();
+      waitDataCenterUntilReady.apply(group);
+
+      return obj;
+   }
+
+   public String getGroup() {
+      return group;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningManager.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningManager.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningManager.java
new file mode 100644
index 0000000..820cafe
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/concurrent/ProvisioningManager.java
@@ -0,0 +1,88 @@
+/*
+ * 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.profitbricks.compute.concurrent;
+
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.Resource;
+
+import org.jclouds.concurrent.config.WithSubmissionTrace;
+import org.jclouds.logging.Logger;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/**
+ * Delegates {@link Job} to single-threaded executor services based on it's group.
+ *
+ */
+public final class ProvisioningManager implements Closeable {
+
+   @Resource
+   private Logger logger = Logger.NULL;
+
+   private final Map<String, ListeningExecutorService> workers
+           = new ConcurrentHashMap<String, ListeningExecutorService>(1);
+
+   private final AtomicBoolean terminated = new AtomicBoolean(false);
+
+   public Object provision(ProvisioningJob job) {
+      if (terminated.get()) {
+         logger.warn("Job(%s) submitted but the provisioning manager is already closed", job);
+         return null;
+      }
+
+      logger.debug("Job(%s) submitted to group '%s'", job, job.getGroup());
+      ListeningExecutorService workerGroup = getWorkerGroup(job.getGroup());
+      return getUnchecked(workerGroup.submit(job));
+   }
+
+   protected ListeningExecutorService newExecutorService() {
+      return WithSubmissionTrace.wrap(listeningDecorator(Executors.newSingleThreadExecutor()));
+   }
+
+   private void newWorkerGroupIfAbsent(String name) {
+      if (!workers.containsKey(name))
+         workers.put(name, newExecutorService());
+   }
+
+   private ListeningExecutorService getWorkerGroup(String name) {
+      newWorkerGroupIfAbsent(name);
+      return workers.get(name);
+   }
+
+   @Override
+   public void close() throws IOException {
+      terminated.set(true); // Do not allow to enqueue more jobs
+      Collection<ListeningExecutorService> executors = workers.values();
+      for (ListeningExecutorService executor : executors) {
+         List<Runnable> runnables = executor.shutdownNow();
+         if (!runnables.isEmpty())
+            logger.warn("when shutting down executor %s, runnables outstanding: %s", executor, runnables);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/config/ProfitBricksComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/config/ProfitBricksComputeServiceContextModule.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/config/ProfitBricksComputeServiceContextModule.java
new file mode 100644
index 0000000..d260caf
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/config/ProfitBricksComputeServiceContextModule.java
@@ -0,0 +1,147 @@
+/*
+ * 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.profitbricks.compute.config;
+
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_PERIOD;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_MAX_PERIOD;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_TIMEOUT;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Named;
+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.Volume;
+import org.jclouds.domain.Location;
+import org.jclouds.functions.IdentityFunction;
+import org.jclouds.lifecycle.Closer;
+import org.jclouds.location.suppliers.ImplicitLocationSupplier;
+import org.jclouds.location.suppliers.implicit.OnlyLocationOrFirstZone;
+import org.jclouds.profitbricks.ProfitBricksApi;
+import org.jclouds.profitbricks.compute.ProfitBricksComputeServiceAdapter;
+import org.jclouds.profitbricks.compute.concurrent.ProvisioningJob;
+import org.jclouds.profitbricks.compute.concurrent.ProvisioningManager;
+import org.jclouds.profitbricks.domain.DataCenter;
+import org.jclouds.profitbricks.domain.Server;
+import org.jclouds.profitbricks.domain.Storage;
+import org.jclouds.profitbricks.compute.function.DataCenterToLocation;
+import org.jclouds.profitbricks.compute.function.LocationToLocation;
+import org.jclouds.profitbricks.compute.function.ProvisionableToImage;
+import org.jclouds.profitbricks.compute.function.ServerToNodeMetadata;
+import org.jclouds.profitbricks.compute.function.StorageToVolume;
+import org.jclouds.profitbricks.compute.internal.ProvisioningStatusAware;
+import org.jclouds.profitbricks.compute.internal.ProvisioningStatusPollingPredicate;
+import org.jclouds.profitbricks.domain.ProvisioningState;
+import org.jclouds.profitbricks.domain.internal.Provisionable;
+import org.jclouds.util.Predicates2;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+public class ProfitBricksComputeServiceContextModule extends
+        ComputeServiceAdapterContextModule<Server, Hardware, Provisionable, DataCenter> {
+
+   @Override
+   protected void configure() {
+      super.configure();
+
+      install(new LocationsFromComputeServiceAdapterModule<Server, Hardware, Provisionable, DataCenter>() {
+      });
+
+      install(new FactoryModuleBuilder().build(ProvisioningJob.Factory.class));
+
+      bind(ImplicitLocationSupplier.class).to(OnlyLocationOrFirstZone.class).in(Singleton.class);
+
+      bind(new TypeLiteral<ComputeServiceAdapter<Server, Hardware, Provisionable, DataCenter>>() {
+      }).to(ProfitBricksComputeServiceAdapter.class);
+
+      bind(new TypeLiteral<Function<org.jclouds.profitbricks.domain.Location, Location>>() {
+      }).to(LocationToLocation.class);
+
+      bind(new TypeLiteral<Function<DataCenter, Location>>() {
+      }).to(DataCenterToLocation.class);
+
+      bind(new TypeLiteral<Function<Server, NodeMetadata>>() {
+      }).to(ServerToNodeMetadata.class);
+
+      bind(new TypeLiteral<Function<Provisionable, Image>>() {
+      }).to(ProvisionableToImage.class);
+
+      bind(new TypeLiteral<Function<Storage, Volume>>() {
+      }).to(StorageToVolume.class);
+
+      bind(new TypeLiteral<Function<Hardware, Hardware>>() {
+      }).to(Class.class.cast(IdentityFunction.class));
+   }
+
+   @Provides
+   @Singleton
+   @Named(POLL_PREDICATE_DATACENTER)
+   Predicate<String> provideWaitDataCenterUntilAvailablePredicate(
+           final ProfitBricksApi api, ComputeConstants constants) {
+      return Predicates2.retry(new ProvisioningStatusPollingPredicate(
+              api, ProvisioningStatusAware.DATACENTER, ProvisioningState.AVAILABLE),
+              constants.pollTimeout(), constants.pollPeriod(), constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Singleton
+   ProvisioningManager provideProvisioningManager(Closer closer) {
+      ProvisioningManager provisioningManager = new ProvisioningManager();
+      closer.addToClose(provisioningManager);
+
+      return provisioningManager;
+   }
+
+   @Singleton
+   public static class ComputeConstants {
+
+      @Inject
+      @Named(POLL_TIMEOUT)
+      private String pollTimeout;
+
+      @Inject
+      @Named(POLL_PERIOD)
+      private String pollPeriod;
+
+      @Inject
+      @Named(POLL_MAX_PERIOD)
+      private String pollMaxPeriod;
+
+      public long pollTimeout() {
+         return Long.parseLong(pollTimeout);
+      }
+
+      public long pollPeriod() {
+         return Long.parseLong(pollPeriod);
+      }
+
+      public long pollMaxPeriod() {
+         return Long.parseLong(pollMaxPeriod);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/DataCenterToLocation.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/DataCenterToLocation.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/DataCenterToLocation.java
new file mode 100644
index 0000000..93fb3a0
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/DataCenterToLocation.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.profitbricks.compute.function;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.profitbricks.domain.DataCenter;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+public class DataCenterToLocation implements Function<DataCenter, Location> {
+
+   private final Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion;
+
+   @Inject
+   DataCenterToLocation(Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion) {
+      this.fnRegion = fnRegion;
+   }
+
+   @Override
+   public Location apply(DataCenter dataCenter) {
+      checkNotNull(dataCenter, "Null dataCenter");
+
+      LocationBuilder builder = new LocationBuilder()
+              .id(dataCenter.id())
+              .description(dataCenter.name())
+              .scope(LocationScope.ZONE)
+              .metadata(ImmutableMap.<String, Object>of(
+                              "version", dataCenter.version(),
+                              "state", dataCenter.state()));
+      if (dataCenter.location() != null)
+         builder.parent(fnRegion.apply(dataCenter.location()));
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/LocationToLocation.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/LocationToLocation.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/LocationToLocation.java
new file mode 100644
index 0000000..999069b
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/LocationToLocation.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.profitbricks.compute.function;
+
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.location.suppliers.all.JustProvider;
+import org.jclouds.profitbricks.domain.Location;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+public class LocationToLocation implements Function<Location, org.jclouds.domain.Location> {
+
+   private final JustProvider justProvider;
+
+   @Inject
+   LocationToLocation(JustProvider justProvider) {
+      this.justProvider = justProvider;
+   }
+
+   @Override
+   public org.jclouds.domain.Location apply(Location in) {
+      return new LocationBuilder()
+              .id(in.getId())
+              .description(in.getDescription())
+              .scope(LocationScope.REGION)
+              .parent(Iterables.getOnlyElement(justProvider.get()))
+              .build();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ProvisionableToImage.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ProvisionableToImage.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ProvisionableToImage.java
new file mode 100644
index 0000000..c5fcd78
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ProvisionableToImage.java
@@ -0,0 +1,215 @@
+/*
+ * 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.profitbricks.compute.function;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.regex.Pattern;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.domain.Location;
+import org.jclouds.profitbricks.domain.OsType;
+import org.jclouds.profitbricks.domain.ProvisioningState;
+import org.jclouds.profitbricks.domain.Snapshot;
+import org.jclouds.profitbricks.domain.internal.Provisionable;
+
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.inject.Inject;
+
+public class ProvisionableToImage implements Function<Provisionable, Image> {
+
+   private final ImageToImage fnImageToImage;
+   private final SnapshotToImage fnSnapshotToImage;
+
+   @Inject
+   ProvisionableToImage(Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion) {
+      this.fnImageToImage = new ImageToImage(fnRegion);
+      this.fnSnapshotToImage = new SnapshotToImage(fnRegion);
+   }
+
+   @Override
+   public Image apply(Provisionable input) {
+      checkNotNull(input, "Cannot convert null input");
+
+      if (input instanceof org.jclouds.profitbricks.domain.Image)
+         return fnImageToImage.apply((org.jclouds.profitbricks.domain.Image) input);
+
+      else if (input instanceof Snapshot)
+         return fnSnapshotToImage.apply((Snapshot) input);
+
+      else
+         throw new UnsupportedOperationException("No implementation found for provisionable of concrete type '"
+                 + input.getClass().getCanonicalName() + "'");
+   }
+
+   private static OsFamily mapOsFamily(OsType osType) {
+      if (osType == null)
+         return OsFamily.UNRECOGNIZED;
+      switch (osType) {
+         case WINDOWS:
+            return OsFamily.WINDOWS;
+         case LINUX:
+            return OsFamily.LINUX;
+         case UNRECOGNIZED:
+         case OTHER:
+         default:
+            return OsFamily.UNRECOGNIZED;
+      }
+   }
+
+   private static class ImageToImage implements Function<org.jclouds.profitbricks.domain.Image, Image> {
+
+      private static final Pattern HAS_NUMBERS = Pattern.compile(".*\\d+.*");
+
+      private final Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion;
+
+      ImageToImage(Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion) {
+         this.fnRegion = fnRegion;
+      }
+
+      @Override
+      public Image apply(org.jclouds.profitbricks.domain.Image from) {
+         String desc = from.name();
+         OsFamily osFamily = parseOsFamily(desc, from.osType());
+
+         OperatingSystem os = OperatingSystem.builder()
+                 .description(osFamily.value())
+                 .family(osFamily)
+                 .version(parseVersion(desc))
+                 .is64Bit(is64Bit(desc, from.type()))
+                 .build();
+
+         return new ImageBuilder()
+                 .ids(from.id())
+                 .name(desc)
+                 .location(fnRegion.apply(from.location()))
+                 .status(Image.Status.AVAILABLE)
+                 .operatingSystem(os)
+                 .build();
+      }
+
+      private OsFamily parseOsFamily(String from, OsType fallbackValue) {
+         if (from != null)
+            try {
+               // ProfitBricks images names are usually in format:
+               // [osType]-[version]-[subversion]-..-[date-created]
+               String desc = from.toUpperCase().split("-")[0];
+               OsFamily osFamily = OsFamily.fromValue(desc);
+               checkArgument(osFamily != OsFamily.UNRECOGNIZED);
+
+               return osFamily;
+            } catch (Exception ex) {
+               // do nothing
+            }
+         return mapOsFamily(fallbackValue);
+      }
+
+      private String parseVersion(String from) {
+         if (from != null) {
+            String[] split = from.toLowerCase().split("-");
+            if (split.length >= 2) {
+               int i = 1; // usually on second token
+               String version = split[i];
+               while (!HAS_NUMBERS.matcher(version).matches())
+                  version = split[++i];
+               return version;
+            }
+         }
+         return "";
+      }
+
+      private boolean is64Bit(String from, org.jclouds.profitbricks.domain.Image.Type type) {
+         switch (type) {
+            case CDROM:
+               if (!Strings.isNullOrEmpty(from))
+                  return from.matches("x86_64|amd64");
+            case HDD: // HDD provided by ProfitBricks are always 64-bit
+            default:
+               return true;
+         }
+      }
+   }
+
+   private static class SnapshotToImage implements Function<Snapshot, Image> {
+
+      private final Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion;
+
+      SnapshotToImage(Function<org.jclouds.profitbricks.domain.Location, Location> fnRegion) {
+         this.fnRegion = fnRegion;
+      }
+
+      @Override
+      public Image apply(Snapshot from) {
+         String textToParse = from.name() + from.description();
+         OsFamily osFamily = parseOsFamily(textToParse, from.osType());
+
+         OperatingSystem os = OperatingSystem.builder()
+                 .description(osFamily.value())
+                 .family(osFamily)
+                 .is64Bit(true)
+                 .version("00.00")
+                 .build();
+
+         return new ImageBuilder()
+                 .ids(from.id())
+                 .name(from.name())
+                 .description(from.description())
+                 .location(fnRegion.apply(from.location()))
+                 .status(mapStatus(from.state()))
+                 .operatingSystem(os)
+                 .build();
+      }
+
+      private OsFamily parseOsFamily(String text, OsType fallbackValue) {
+         if (text != null)
+            try {
+               // Attempt parsing OsFamily by scanning name and description
+               // @see ProfitBricksComputeServiceAdapter#L190
+               OsFamily[] families = OsFamily.values();
+               for (OsFamily family : families)
+                  if (text.contains(family.value()))
+                     return family;
+            } catch (Exception ex) {
+               // do nothing
+            }
+         return mapOsFamily(fallbackValue);
+      }
+
+      static Image.Status mapStatus(ProvisioningState state) {
+         if (state == null)
+            return Image.Status.UNRECOGNIZED;
+         switch (state) {
+            case AVAILABLE:
+               return Image.Status.AVAILABLE;
+            case DELETED:
+               return Image.Status.DELETED;
+            case ERROR:
+               return Image.Status.ERROR;
+            case INACTIVE:
+            case INPROCESS:
+               return Image.Status.PENDING;
+            default:
+               return Image.Status.UNRECOGNIZED;
+         }
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ServerToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ServerToNodeMetadata.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ServerToNodeMetadata.java
new file mode 100644
index 0000000..9a8d551
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/ServerToNodeMetadata.java
@@ -0,0 +1,180 @@
+/*
+ * 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.profitbricks.compute.function;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.not;
+import static org.jclouds.profitbricks.domain.OsType.LINUX;
+import static org.jclouds.profitbricks.domain.OsType.WINDOWS;
+import static org.jclouds.profitbricks.domain.Server.Status.BLOCKED;
+import static org.jclouds.profitbricks.domain.Server.Status.CRASHED;
+import static org.jclouds.profitbricks.domain.Server.Status.PAUSED;
+import static org.jclouds.profitbricks.domain.Server.Status.RUNNING;
+import static org.jclouds.profitbricks.domain.Server.Status.SHUTDOWN;
+import static org.jclouds.profitbricks.domain.Server.Status.SHUTOFF;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+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;
+import org.jclouds.domain.Location;
+import org.jclouds.profitbricks.domain.Nic;
+import org.jclouds.profitbricks.domain.OsType;
+import org.jclouds.profitbricks.domain.Server;
+import org.jclouds.profitbricks.domain.Storage;
+import org.jclouds.util.InetAddresses2.IsPrivateIPAddress;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.functions.GroupNamingConvention;
+
+public class ServerToNodeMetadata implements Function<Server, NodeMetadata> {
+
+   private final Function<Storage, Volume> fnVolume;
+   private final Supplier<Set<? extends Location>> locationSupply;
+   private final Function<List<Nic>, List<String>> fnCollectIps;
+
+   private final GroupNamingConvention groupNamingConvention;
+
+   @Inject
+   ServerToNodeMetadata(Function<Storage, Volume> fnVolume,
+           @Memoized Supplier<Set<? extends Location>> locationsSupply,
+           GroupNamingConvention.Factory groupNamingConvention) {
+      this.fnVolume = fnVolume;
+      this.locationSupply = locationsSupply;
+      this.groupNamingConvention = groupNamingConvention.createWithoutPrefix();
+      this.fnCollectIps = new Function<List<Nic>, List<String>>() {
+
+         @Override
+         public List<String> apply(List<Nic> in) {
+            List<String> ips = Lists.newArrayListWithExpectedSize(in.size());
+            for (Nic nic : in)
+               ips.addAll(nic.ips());
+            return ips;
+         }
+      };
+   }
+
+   @Override
+   public NodeMetadata apply(final Server server) {
+      checkNotNull(server, "Null server");
+
+      // Map fetched dataCenterId with actual populated object
+      Location location = null;
+      if (server.dataCenter() != null)
+         location = Iterables.find(locationSupply.get(), new Predicate<Location>() {
+
+            @Override
+            public boolean apply(Location t) {
+               return t.getId().equals(server.dataCenter().id());
+            }
+         });
+
+      float size = 0f;
+      List<Volume> volumes = Lists.newArrayList();
+      List<Storage> storages = server.storages();
+      if (storages != null)
+         for (Storage storage : storages) {
+            size += storage.size();
+            volumes.add(fnVolume.apply(storage));
+         }
+
+      // Build hardware
+      String id = String.format("cpu=%d,ram=%d,disk=%.0f", server.cores(), server.ram(), size);
+      Hardware hardware = new HardwareBuilder()
+              .ids(id)
+              .name(id)
+              .ram(server.ram())
+              .processor(new Processor(server.cores(), 1d))
+              .hypervisor("kvm")
+              .volumes(volumes)
+              .location(location)
+              .build();
+
+      // Collect ips
+      List<String> addresses = fnCollectIps.apply(server.nics());
+
+      // Build node
+      NodeMetadataBuilder nodeBuilder = new NodeMetadataBuilder();
+      nodeBuilder.ids(server.id())
+              .group(groupNamingConvention.extractGroup(server.name()))
+              .hostname(server.hostname())
+              .name(server.name())
+              .backendStatus(server.state().toString())
+              .status(mapStatus(server.status()))
+              .hardware(hardware)
+              .operatingSystem(mapOsType(server.osType()))
+              .location(location)
+              .privateAddresses(Iterables.filter(addresses, IsPrivateIPAddress.INSTANCE))
+              .publicAddresses(Iterables.filter(addresses, not(IsPrivateIPAddress.INSTANCE)));
+
+      return nodeBuilder.build();
+   }
+
+   static NodeMetadata.Status mapStatus(Server.Status status) {
+      if (status == null)
+         return NodeMetadata.Status.UNRECOGNIZED;
+      switch (status) {
+         case SHUTDOWN:
+         case SHUTOFF:
+         case PAUSED:
+            return NodeMetadata.Status.SUSPENDED;
+         case RUNNING:
+            return NodeMetadata.Status.RUNNING;
+         case BLOCKED:
+            return NodeMetadata.Status.PENDING;
+         case CRASHED:
+            return NodeMetadata.Status.ERROR;
+         default:
+            return NodeMetadata.Status.UNRECOGNIZED;
+      }
+   }
+
+   static OperatingSystem mapOsType(OsType osType) {
+      if (osType != null)
+         switch (osType) {
+            case WINDOWS:
+               return OperatingSystem.builder()
+                       .description(OsFamily.WINDOWS.value())
+                       .family(OsFamily.WINDOWS)
+                       .build();
+            case LINUX:
+               return OperatingSystem.builder()
+                       .description(OsFamily.LINUX.value())
+                       .family(OsFamily.LINUX)
+                       .build();
+         }
+      return OperatingSystem.builder()
+              .description(OsFamily.UNRECOGNIZED.value())
+              .family(OsFamily.UNRECOGNIZED)
+              .build();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/StorageToVolume.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/StorageToVolume.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/StorageToVolume.java
new file mode 100644
index 0000000..5557bca
--- /dev/null
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/function/StorageToVolume.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.profitbricks.compute.function;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.profitbricks.domain.Storage;
+
+import com.google.common.base.Function;
+
+public class StorageToVolume implements Function<Storage, Volume> {
+
+   @Override
+   public Volume apply(Storage storage) {
+      checkNotNull(storage, "Null storage");
+
+      String device = "";
+      if (storage.deviceNumber() != null)
+         device = storage.deviceNumber().toString();
+
+      return new VolumeBuilder()
+              .id(storage.id())
+              .size(storage.size())
+              .bootDevice(storage.bootDevice())
+              .device(device)
+              .durable(true)
+              .type(Volume.Type.LOCAL)
+              .build();
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ed247e7d/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/internal/ProvisioningStatusPollingPredicate.java
----------------------------------------------------------------------
diff --git a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/internal/ProvisioningStatusPollingPredicate.java b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/internal/ProvisioningStatusPollingPredicate.java
index f38abad..41c3e93 100644
--- a/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/internal/ProvisioningStatusPollingPredicate.java
+++ b/providers/profitbricks/src/main/java/org/jclouds/profitbricks/compute/internal/ProvisioningStatusPollingPredicate.java
@@ -22,6 +22,7 @@ import org.jclouds.profitbricks.ProfitBricksApi;
 import org.jclouds.profitbricks.domain.ProvisioningState;
 
 import com.google.common.base.Predicate;
+import org.jclouds.rest.ResourceNotFoundException;
 
 /**
  * A custom predicate for waiting until a virtual resource satisfies the given expected status
@@ -45,19 +46,24 @@ public class ProvisioningStatusPollingPredicate implements Predicate<String> {
    @Override
    public boolean apply(String input) {
       checkNotNull(input, "Virtual item id can't be null.");
-      switch (domain) {
-         case DATACENTER:
-            return expect == api.dataCenterApi().getDataCenterState(input);
-         case SERVER:
-            return expect == api.serverApi().getServer(input).state();
-         case STORAGE:
-            return expect == api.storageApi().getStorage(input).state();
-         case NIC:
-            return expect == api.nicApi().getNic(input).state();
-         case SNAPSHOT:
-            return expect == api.snapshotApi().getSnapshot(input).state();
-         default:
-            throw new IllegalArgumentException("Unknown domain '" + domain + "'");
+      try {
+         switch (domain) {
+            case DATACENTER:
+               return expect == api.dataCenterApi().getDataCenterState(input);
+            case SERVER:
+               return expect == api.serverApi().getServer(input).state();
+            case STORAGE:
+               return expect == api.storageApi().getStorage(input).state();
+            case NIC:
+               return expect == api.nicApi().getNic(input).state();
+            case SNAPSHOT:
+               return expect == api.snapshotApi().getSnapshot(input).state();
+            default:
+               throw new IllegalArgumentException("Unknown domain '" + domain + "'");
+         }
+      } catch (ResourceNotFoundException ex) {
+         // After provisioning, a node might still not be "fetchable" via API
+         return false;
       }
    }