ComputeService implementation for ProfitBricks Rest

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

Branch: refs/heads/master
Commit: 5742745e029cf765137b03f246415739a5feb4e3
Parents: 4e39720
Author: Ali Bazlamit <[email protected]>
Authored: Fri Sep 16 12:29:00 2016 +0200
Committer: Ignasi Barrera <[email protected]>
Committed: Thu Oct 6 13:07:36 2016 +0200

----------------------------------------------------------------------
 profitbricks-rest/pom.xml                       |  25 +-
 .../profitbricks/rest/ProfitBricksApi.java      |  25 +-
 .../rest/ProfitBricksApiMetadata.java           |  26 +-
 .../rest/ProfitBricksProviderMetadata.java      |  45 +-
 .../ProfitBricksComputeServiceAdapter.java      | 576 +++++++++++++++++++
 .../compute/concurrent/ProvisioningJob.java     |  62 ++
 .../compute/concurrent/ProvisioningManager.java |  87 +++
 ...ProfitBricksComputeServiceContextModule.java | 330 +++++++++++
 .../compute/function/ProvisionableToImage.java  | 245 ++++++++
 .../ServerInDataCenterToNodeMetadata.java       | 174 ++++++
 .../rest/compute/function/VolumeToVolume.java   |  44 ++
 .../strategy/AssignDataCenterToTemplate.java    | 100 ++++
 .../strategy/TemplateWithDataCenter.java        | 108 ++++
 .../config/ProfitBricksComputeProperties.java   |   5 +-
 .../profitbricks/rest/domain/CpuFamily.java     |  28 +
 .../profitbricks/rest/domain/DataCenter.java    |   2 +
 .../profitbricks/rest/domain/Firewall.java      |  16 +-
 .../jclouds/profitbricks/rest/domain/Image.java | 133 +++--
 .../profitbricks/rest/domain/IpBlock.java       | 104 ++++
 .../profitbricks/rest/domain/Location.java      |  20 +-
 .../profitbricks/rest/domain/Provisionable.java |  13 +-
 .../profitbricks/rest/domain/Server.java        | 109 ++--
 .../profitbricks/rest/domain/Snapshot.java      |  99 ++--
 .../profitbricks/rest/domain/VolumeType.java    |   2 +-
 .../rest/domain/zonescoped/DataCenterAndId.java |  90 +++
 .../domain/zonescoped/ServerInDataCenter.java   |  46 ++
 .../profitbricks/rest/features/IpBlockApi.java  |  89 +++
 .../profitbricks/rest/features/ServerApi.java   |  50 +-
 .../jclouds/profitbricks/rest/ids/NicRef.java   |  34 ++
 .../rest/util/ApiPredicatesModule.java          |  34 +-
 .../ProfitBricksComputeServiceLiveTest.java     | 106 ++++
 .../ProfitBricksTemplateBuilderLiveTest.java    |  36 ++
 .../concurrent/ProvisioningManagerTest.java     | 118 ++++
 .../compute/config/StatusPredicateTest.java     | 120 ++++
 .../function/ProvisionableToImageTest.java      | 198 +++++++
 .../ServerInDataCenterToNodeMetadataTest.java   | 157 +++++
 .../compute/function/VolumeToVolumeTest.java    |  58 ++
 .../rest/features/DataCenterApiMockTest.java    |  67 +--
 .../rest/features/IpBlockApiMockTest.java       | 150 +++++
 .../rest/features/IpblockApiLiveTest.java       |  96 ++++
 .../rest/features/ServerApiLiveTest.java        |  73 +--
 .../rest/features/SnapshotApiLiveTest.java      |  76 +--
 .../rest/features/SnapshotApiMockTest.java      |  77 ++-
 .../rest/features/VolumeApiLiveTest.java        |  66 +--
 .../rest/internal/BaseProfitBricksLiveTest.java |  93 +--
 .../src/test/resources/compute/datacenter.json  | 123 ++++
 .../src/test/resources/compute/image.json       |  32 ++
 .../src/test/resources/compute/image1.json      |  32 ++
 .../src/test/resources/compute/image2.json      |  32 ++
 .../src/test/resources/compute/image3.json      |  32 ++
 .../compute/predicate/datacenter-inprocess.json |  41 ++
 .../resources/compute/predicate/datacenter.json |  41 ++
 .../compute/predicate/server-inprocess.json     | 173 ++++++
 .../resources/compute/predicate/server.json     | 173 ++++++
 .../compute/predicate/snapshot-inprocess.json   |  30 +
 .../resources/compute/predicate/snapshot.json   |  30 +
 .../src/test/resources/compute/server.json      | 140 +++++
 .../src/test/resources/compute/snapshot1.json   |  30 +
 .../src/test/resources/compute/snapshot2.json   |  30 +
 .../src/test/resources/compute/volume.json      |  33 ++
 .../src/test/resources/ipblock/get.json         |  19 +
 .../test/resources/ipblock/list.depth-5.json    |  42 ++
 .../src/test/resources/ipblock/list.json        |  14 +
 63 files changed, 4797 insertions(+), 462 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5742745e/profitbricks-rest/pom.xml
----------------------------------------------------------------------
diff --git a/profitbricks-rest/pom.xml b/profitbricks-rest/pom.xml
index d676d88..0f1b41d 100644
--- a/profitbricks-rest/pom.xml
+++ b/profitbricks-rest/pom.xml
@@ -51,6 +51,11 @@
             <version>${jclouds.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.jclouds</groupId>
+            <artifactId>jclouds-compute</artifactId>
+            <version>${jclouds.version}</version>
+        </dependency>
+        <dependency>
             <groupId>com.google.auto.service</groupId>
             <artifactId>auto-service</artifactId>
             <scope>provided</scope>
@@ -61,9 +66,9 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-           <groupId>org.apache.jclouds.driver</groupId>
-           <artifactId>jclouds-okhttp</artifactId>
-           <version>${jclouds.version}</version>
+            <groupId>org.apache.jclouds.driver</groupId>
+            <artifactId>jclouds-okhttp</artifactId>
+            <version>${jclouds.version}</version>
         </dependency>
         <!-- Test dependencies -->
         <dependency>
@@ -74,6 +79,13 @@
             <scope>test</scope>
         </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.driver</groupId>
             <artifactId>jclouds-sshj</artifactId>
             <version>${jclouds.version}</version>
@@ -108,6 +120,13 @@
             <artifactId>logback-classic</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.jclouds.driver</groupId>
+            <artifactId>jclouds-log4j</artifactId>
+            <version>${jclouds.version}</version>
+            <scope>test</scope>
+            <type>jar</type>
+        </dependency>
     </dependencies>
   
     <profiles>

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApi.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApi.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApi.java
index a97dd02..7c4b2b9 100644
--- 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApi.java
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApi.java
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.jclouds.profitbricks.rest;
 
 import com.google.common.annotations.Beta;
@@ -22,6 +21,7 @@ import java.io.Closeable;
 import org.apache.jclouds.profitbricks.rest.features.DataCenterApi;
 import org.apache.jclouds.profitbricks.rest.features.FirewallApi;
 import org.apache.jclouds.profitbricks.rest.features.ImageApi;
+import org.apache.jclouds.profitbricks.rest.features.IpBlockApi;
 import org.apache.jclouds.profitbricks.rest.features.LanApi;
 import org.apache.jclouds.profitbricks.rest.features.NicApi;
 import org.apache.jclouds.profitbricks.rest.features.ServerApi;
@@ -31,29 +31,32 @@ import org.jclouds.rest.annotations.Delegate;
 
 @Beta
 public interface ProfitBricksApi extends Closeable {
-   
+
    @Delegate
    DataCenterApi dataCenterApi();
-   
+
+   @Delegate
+   LanApi lanApi();
+
+   @Delegate
+   FirewallApi firewallApi();
+
    @Delegate
    ServerApi serverApi();
-   
+
    @Delegate
    VolumeApi volumeApi();
 
    @Delegate
    ImageApi imageApi();
-   
+
    @Delegate
    SnapshotApi snapshotApi();
-   
+
    @Delegate
    NicApi nicApi();
-   
-   @Delegate
-   LanApi lanApi();
-   
+
    @Delegate
-   FirewallApi firewallApi();
+   IpBlockApi ipBlockApi();
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApiMetadata.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApiMetadata.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApiMetadata.java
index f5ac5e1..6bb680c 100644
--- 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApiMetadata.java
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksApiMetadata.java
@@ -20,8 +20,9 @@ import com.google.common.collect.ImmutableSet;
 import com.google.inject.Module;
 import java.net.URI;
 import java.util.Properties;
-import org.apache.jclouds.profitbricks.rest.util.ApiPredicatesModule;
+import 
org.apache.jclouds.profitbricks.rest.compute.config.ProfitBricksComputeServiceContextModule;
 import org.apache.jclouds.profitbricks.rest.config.ProfitBricksHttpApiModule;
+import org.jclouds.compute.ComputeServiceContext;
 import org.jclouds.http.okhttp.config.OkHttpCommandExecutorServiceModule;
 import org.jclouds.rest.internal.BaseHttpApiMetadata;
 
@@ -49,17 +50,18 @@ public class ProfitBricksApiMetadata extends 
BaseHttpApiMetadata<ProfitBricksApi
 
       protected Builder() {
          id("profitbricks-rest")
-            .name("ProfitBricks REST API")
-            .identityName("API Username")
-            .credentialName("API Password")
-            
.documentation(URI.create("https://devops.profitbricks.com/api/rest/";))
-            .defaultEndpoint("https://api.profitbricks.com/rest/v2/";)
-            .defaultProperties(ProfitBricksApiMetadata.defaultProperties())
-            .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
-               .add(OkHttpCommandExecutorServiceModule.class)
-               .add(ProfitBricksHttpApiModule.class)
-               .add(ApiPredicatesModule.class)
-               .build());
+                 .name("ProfitBricks REST API")
+                 .identityName("API Username")
+                 .credentialName("API Password")
+                 
.documentation(URI.create("https://devops.profitbricks.com/api/rest/";))
+                 .defaultEndpoint("https://api.profitbricks.com/rest/v2/";)
+                 .view(ComputeServiceContext.class)
+                 
.defaultProperties(ProfitBricksApiMetadata.defaultProperties())
+                 .defaultModules(ImmutableSet.<Class<? extends 
Module>>builder()
+                         .add(OkHttpCommandExecutorServiceModule.class)
+                         .add(ProfitBricksHttpApiModule.class)
+                         .add(ProfitBricksComputeServiceContextModule.class)
+                         .build());
       }
 
       @Override

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksProviderMetadata.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksProviderMetadata.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksProviderMetadata.java
index 133e7f6..477fc24 100644
--- 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksProviderMetadata.java
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/ProfitBricksProviderMetadata.java
@@ -55,19 +55,22 @@ public class ProfitBricksProviderMetadata extends 
BaseProviderMetadata {
 
    public static Properties defaultProperties() {
       Properties properties = ProfitBricksApiMetadata.defaultProperties();
-            
-      properties.setProperty(PROPERTY_ISO3166_CODES, "US-NV,DE-HE,DE-BW");
-
-      properties.setProperty(PROPERTY_REGIONS, "DE,US");
-      properties.setProperty(PROPERTY_REGION + ".DE.zones", "FKB,FRA");
-      properties.setProperty(PROPERTY_REGION + ".US.zones", "LAS,LASDEV");
-
-      properties.setProperty(PROPERTY_ZONES, "FKB,FRA,LAS,LASDEV");
-      properties.setProperty(PROPERTY_ZONE + ".FKB." + ISO3166_CODES, "DE-BW");
-      properties.setProperty(PROPERTY_ZONE + ".FRA." + ISO3166_CODES, "DE-HE");
-      properties.setProperty(PROPERTY_ZONE + ".LAS." + ISO3166_CODES, "US-NV");
-      properties.setProperty(PROPERTY_ZONE + ".LASDEV." + ISO3166_CODES, 
"US-NV");
-      
+
+      properties.setProperty(PROPERTY_REGIONS, "de,us");
+      properties.setProperty(PROPERTY_REGION + ".de.zones", "de/fkb,de/fra");
+      properties.setProperty(PROPERTY_REGION + ".us.zones", 
"us/las,us/lasdev");
+      properties.setProperty(PROPERTY_ZONES, "de/fkb,de/fra,us/las,us/lasdev");
+      properties.setProperty(PROPERTY_ISO3166_CODES, "DE-BW,DE-HE,US_NV");
+      properties.setProperty(PROPERTY_REGION + ".de." + ISO3166_CODES, 
"DE-BW,DE-HE");
+      properties.setProperty(PROPERTY_REGION + ".us." + ISO3166_CODES, 
"US-NV");
+      properties.setProperty(PROPERTY_ZONE + ".de/fkb." + ISO3166_CODES, 
"DE-BW");
+      properties.setProperty(PROPERTY_ZONE + ".de/fra." + ISO3166_CODES, 
"DE-HE");
+      properties.setProperty(PROPERTY_ZONE + ".us/las." + ISO3166_CODES, 
"US-NV");
+      properties.setProperty(PROPERTY_ZONE + ".us/lasdev." + ISO3166_CODES, 
"US-NV");
+
+      properties.put("jclouds.ssh.max-retries", "7");
+      properties.put("jclouds.ssh.retry-auth", "true");
+
       long defaultTimeout = 60l * 60l; // 1 hour
       properties.put(POLL_TIMEOUT, defaultTimeout);
       properties.put(POLL_PERIOD, 2l);
@@ -75,7 +78,7 @@ public class ProfitBricksProviderMetadata extends 
BaseProviderMetadata {
 
       properties.put(PROPERTY_SO_TIMEOUT, 60000 * 5);
       properties.put(PROPERTY_CONNECTION_TIMEOUT, 60000 * 5);
-      
+
       return properties;
    }
 
@@ -83,13 +86,13 @@ public class ProfitBricksProviderMetadata extends 
BaseProviderMetadata {
 
       protected Builder() {
          id("profitbricks-rest")
-            .name("ProfitBricks REST Compute")
-            .apiMetadata(new ProfitBricksApiMetadata())
-            .homepage(URI.create("https://www.profitbricks.com/";))
-            .console(URI.create("https://my.profitbricks.com/dashboard/dcdr2";))
-            .iso3166Codes("DE-FKB", "DE-FRA", "US-LAS", "US-LASDEV")
-            .endpoint("https://api.profitbricks.com/rest/v2/";)
-            
.defaultProperties(ProfitBricksProviderMetadata.defaultProperties());
+                 .name("ProfitBricks REST Compute")
+                 .apiMetadata(new ProfitBricksApiMetadata())
+                 .homepage(URI.create("https://www.profitbricks.com/";))
+                 
.console(URI.create("https://my.profitbricks.com/dashboard/dcdr2";))
+                 .iso3166Codes("DE-BW", "DE-HE", "US-NV")
+                 .endpoint("https://api.profitbricks.com/rest/v2/";)
+                 
.defaultProperties(ProfitBricksProviderMetadata.defaultProperties());
       }
 
       @Override

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
new file mode 100644
index 0000000..f7f4145
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
@@ -0,0 +1,576 @@
+/*
+ * 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.apache.jclouds.profitbricks.rest.compute;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import com.google.common.base.Predicate;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import static com.google.common.collect.Iterables.contains;
+import static com.google.common.collect.Iterables.filter;
+import com.google.common.collect.Lists;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+import static java.lang.String.format;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.apache.jclouds.profitbricks.rest.ProfitBricksApi;
+import org.apache.jclouds.profitbricks.rest.compute.concurrent.ProvisioningJob;
+import 
org.apache.jclouds.profitbricks.rest.compute.concurrent.ProvisioningManager;
+import 
org.apache.jclouds.profitbricks.rest.compute.function.ProvisionableToImage;
+import 
org.apache.jclouds.profitbricks.rest.compute.strategy.TemplateWithDataCenter;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_NIC;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_SERVER;
+import org.apache.jclouds.profitbricks.rest.domain.DataCenter;
+import org.apache.jclouds.profitbricks.rest.domain.Image;
+import org.apache.jclouds.profitbricks.rest.domain.Lan;
+import org.apache.jclouds.profitbricks.rest.domain.Nic;
+import org.apache.jclouds.profitbricks.rest.domain.Provisionable;
+import org.apache.jclouds.profitbricks.rest.domain.Server;
+import org.apache.jclouds.profitbricks.rest.domain.Snapshot;
+import org.apache.jclouds.profitbricks.rest.domain.VolumeType;
+import org.apache.jclouds.profitbricks.rest.domain.options.DepthOptions;
+import org.apache.jclouds.profitbricks.rest.domain.zonescoped.DataCenterAndId;
+import 
org.apache.jclouds.profitbricks.rest.domain.zonescoped.ServerInDataCenter;
+import org.apache.jclouds.profitbricks.rest.features.ServerApi;
+import org.apache.jclouds.profitbricks.rest.ids.NicRef;
+import org.apache.jclouds.profitbricks.rest.ids.ServerRef;
+import org.apache.jclouds.profitbricks.rest.ids.VolumeRef;
+import org.apache.jclouds.profitbricks.rest.util.Passwords;
+import static org.jclouds.Constants.PROPERTY_USER_THREADS;
+import org.jclouds.compute.ComputeServiceAdapter;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+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.Location;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.logging.Logger;
+import org.jclouds.rest.ResourceNotFoundException;
+
+@Singleton
+public class ProfitBricksComputeServiceAdapter implements 
ComputeServiceAdapter<ServerInDataCenter, Hardware, Provisionable, Location> {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ProfitBricksApi api;
+   private final Predicate<String> waitDcUntilAvailable;
+   private final Predicate<VolumeRef> waitVolumeUntilAvailable;
+   private final Predicate<ServerRef> waitServerUntilAvailable;
+   private final Predicate<ServerRef> waitServerUntilRunning;
+   private final Predicate<ServerRef> waitServerUntilSuspended;
+   private final Predicate<NicRef> waitNICUntilAvailable;
+   private final ListeningExecutorService executorService;
+   private final ProvisioningJob.Factory jobFactory;
+   private final ProvisioningManager provisioningManager;
+   private List<DataCenter> datacetners;
+
+   private static final Integer DEFAULT_LAN_ID = 1;
+
+   @Inject
+   ProfitBricksComputeServiceAdapter(ProfitBricksApi api,
+           @Named(POLL_PREDICATE_DATACENTER) Predicate<String> 
waitDcUntilAvailable,
+           @Named(TIMEOUT_NODE_RUNNING) Predicate<VolumeRef> 
waitVolumeUntilAvailable,
+           @Named(PROPERTY_USER_THREADS) ListeningExecutorService 
executorService,
+           @Named(POLL_PREDICATE_SERVER) Predicate<ServerRef> 
waitServerUntilAvailable,
+           @Named(TIMEOUT_NODE_RUNNING) Predicate<ServerRef> 
waitServerUntilRunning,
+           @Named(TIMEOUT_NODE_SUSPENDED) Predicate<ServerRef> 
waitServerUntilSuspended,
+           @Named(POLL_PREDICATE_NIC) Predicate<NicRef> waitNICUntilAvailable,
+           ProvisioningJob.Factory jobFactory,
+           ProvisioningManager provisioningManager) {
+      this.api = api;
+      this.waitDcUntilAvailable = waitDcUntilAvailable;
+      this.waitVolumeUntilAvailable = waitVolumeUntilAvailable;
+      this.waitServerUntilAvailable = waitServerUntilAvailable;
+      this.waitNICUntilAvailable = waitNICUntilAvailable;
+      this.waitServerUntilSuspended = waitServerUntilSuspended;
+      this.waitServerUntilRunning = waitServerUntilRunning;
+      this.executorService = executorService;
+      this.jobFactory = jobFactory;
+      this.provisioningManager = provisioningManager;
+      this.datacetners = ImmutableList.of();
+   }
+
+   @Override
+   public NodeAndInitialCredentials<ServerInDataCenter> 
createNodeWithGroupEncodedIntoName(String group, String name, Template 
template) {
+      checkArgument(template instanceof TemplateWithDataCenter, "This 
implementation requires a TemplateWithDataCenter");
+      return createNodeWithGroupEncodedIntoName(group, name, 
TemplateWithDataCenter.class.cast(template));
+   }
+
+   protected NodeAndInitialCredentials<ServerInDataCenter> 
createNodeWithGroupEncodedIntoName(String group, String name, 
TemplateWithDataCenter template) {
+      checkArgument(template.getLocation().getScope() == LocationScope.ZONE, 
"Template must use a ZONE-scoped location");
+      final String dataCenterId = template.getDataCenter().id();
+      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 volumes based on hardware
+      List<? extends Volume> volumes = hardware.getVolumes();
+      List<String> volumeIds = 
Lists.newArrayListWithExpectedSize(volumes.size());
+
+      int i = 1;
+      for (final Volume volume : volumes) {
+         try {
+            logger.trace("<< provisioning volume '%s'", volume);
+            final 
org.apache.jclouds.profitbricks.rest.domain.Volume.Request.CreatePayload.Builder
 request = 
org.apache.jclouds.profitbricks.rest.domain.Volume.Request.creatingBuilder();
+            if (i == 1) {
+               request.image(image.getId());
+               // we don't need to pass password to the API if we're using a 
snapshot
+               Provisionable.Type provisionableType = 
Provisionable.Type.fromValue(
+                       
image.getUserMetadata().get(ProvisionableToImage.KEY_PROVISIONABLE_TYPE));
+               if (provisionableType == Provisionable.Type.IMAGE) {
+                  request.imagePassword(password);
+               }
+
+            }
+            request.dataCenterId(dataCenterId).
+                    name(format("%s-disk-%d", name, i++)).
+                    size(volume.getSize().intValue()).
+                    type(VolumeType.HDD);
+
+            String volumeId = (String) 
provisioningManager.provision(jobFactory.create(dataCenterId, new 
Supplier<Object>() {
+
+               @Override
+               public Object get() {
+                  return api.volumeApi().createVolume(request.build()).id();
+               }
+            }));
+
+            volumeIds.add(volumeId);
+            logger.trace(">> provisioning complete for volume. returned 
id='%s'", volumeId);
+         } catch (Exception ex) {
+            if (i - 1 == 1) // if first volume (one with image) provisioning 
fails; stop method
+            {
+               throw Throwables.propagate(ex);
+            }
+            logger.warn(ex, ">> failed to provision volume. skipping..");
+         }
+      }
+
+      String volumeBootDeviceId = Iterables.get(volumeIds, 0); // must have 
atleast 1
+      waitVolumeUntilAvailable.apply(VolumeRef.create(dataCenterId, 
volumeBootDeviceId));
+
+      // provision server
+      final String serverId;
+      Double cores = ComputeServiceUtils.getCores(hardware);
+
+      Server.BootVolume bootVolume = 
Server.BootVolume.create(volumeBootDeviceId);
+
+      try {
+         final Server.Request.CreatePayload serverRequest = 
Server.Request.creatingBuilder()
+                 .dataCenterId(dataCenterId)
+                 .name(name)
+                 .bootVolume(bootVolume)
+                 .cores(cores.intValue())
+                 .ram(hardware.getRam())
+                 .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).id();
+            }
+         }));
+         logger.trace(">> provisioning complete for server. returned id='%s'", 
serverId);
+
+      } catch (Exception ex) {
+         logger.error(ex, ">> failed to provision server. rollbacking..");
+         destroyVolumes(volumeIds, dataCenterId);
+         throw Throwables.propagate(ex);
+      }
+
+      waitServerUntilAvailable.apply(ServerRef.create(dataCenterId, serverId));
+      waitDcUntilAvailable.apply(dataCenterId);
+
+      //attach bootVolume to Server
+      api.serverApi().attachVolume(Server.Request.attachVolumeBuilder()
+              .dataCenterId(dataCenterId)
+              .serverId(serverId)
+              .volumeId(bootVolume.id())
+              .build());
+
+      waitServerUntilAvailable.apply(ServerRef.create(dataCenterId, serverId));
+      waitDcUntilAvailable.apply(dataCenterId);
+
+      //fetch an existing lan and creat if non was found
+      Lan lan = null;
+
+      List<Lan> lans = api.lanApi().list(dataCenterId);
+      if (lans != null && !lans.isEmpty()) {
+         lan = FluentIterable.from(lans).firstMatch(new Predicate<Lan>() {
+            @Override
+            public boolean apply(Lan input) {
+               input = api.lanApi().get(dataCenterId, input.id(), new 
DepthOptions().depth(3));
+               return input.properties().isPublic();
+            }
+         }).orNull();
+      }
+      if (lan == null) {
+         logger.warn("Could not find an existing lan Creating one....");
+         lan = api.lanApi().create(Lan.Request.creatingBuilder()
+                 .dataCenterId(dataCenterId)
+                 .isPublic(Boolean.TRUE)
+                 .name("lan " + name)
+                 .build());
+      }
+
+      //add a NIC to the server
+      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);
+         }
+      }
+
+      Nic nic = api.nicApi().create(Nic.Request.creatingBuilder()
+              .dataCenterId(dataCenterId)
+              .name("jclouds" + name)
+              .dhcp(Boolean.TRUE)
+              .lan(lanId)
+              .firewallActive(Boolean.FALSE)
+              .serverId(serverId).
+              build());
+
+      waitNICUntilAvailable.apply(NicRef.create(dataCenterId, serverId, 
nic.id()));
+      waitDcUntilAvailable.apply(dataCenterId);
+      waitServerUntilAvailable.apply(ServerRef.create(dataCenterId, serverId));
+
+      //connect the rest of volumes to server;delete if fails
+      final int volumeCount = volumeIds.size();
+      for (int j = 1; j < volumeCount; j++) { // skip first; already connected
+         final String volumeId = volumeIds.get(j);
+         try {
+            logger.trace("<< connecting volume '%s' to server '%s'", volumeId, 
serverId);
+            provisioningManager.provision(jobFactory.create(group, new 
Supplier<Object>() {
+
+               @Override
+               public Object get() {
+                  return api.serverApi().attachVolume(
+                          Server.Request.attachVolumeBuilder()
+                          .dataCenterId(dataCenterId)
+                          .serverId(serverId)
+                          .volumeId(volumeId)
+                          .build()
+                  );
+               }
+            }));
+
+            logger.trace(">> volume connected.");
+         } catch (Exception ex) {
+            try {
+               // delete unconnected volume
+               logger.warn(ex, ">> failed to connect volume '%s'. deleting..", 
volumeId);
+               destroyVolume(volumeId, dataCenterId);
+               logger.warn(ex, ">> rolling back server..", serverId);
+               destroyServer(serverId, dataCenterId);
+               throw ex;
+            } catch (Exception ex1) {
+               logger.error(ex, ">> failed to rollback");
+            }
+         }
+      }
+      waitDcUntilAvailable.apply(dataCenterId);
+      waitServerUntilAvailable.apply(ServerRef.create(dataCenterId, serverId));
+
+      LoginCredentials serverCredentials = LoginCredentials.builder()
+              .user(loginUser)
+              .password(password)
+              .build();
+
+      String serverInDataCenterId = 
DataCenterAndId.fromDataCenterAndId(dataCenterId, serverId).slashEncode();
+      ServerInDataCenter server = getNode(serverInDataCenterId);
+
+      return new NodeAndInitialCredentials<ServerInDataCenter>(server, 
serverInDataCenterId, serverCredentials);
+   }
+
+   @Override
+   public Iterable<Hardware> listHardwareProfiles() {
+      // Max [cores=48] [disk size per volume=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().getList(new DepthOptions().depth(1)), new 
Predicate<Image>() {
+
+               @Override
+               public boolean apply(Image image) {
+                  return image.properties().imageType() == 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().list(new 
DepthOptions().depth(1));
+            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.properties().name());
+         return image;
+      }
+      // try search snapshots
+      logger.trace("<< not found from images. searching for snapshot with 
id=%s", id);
+      Snapshot snapshot = api.snapshotApi().get(id);
+      if (snapshot != null) {
+         logger.trace(">> found snapshot [%s]", snapshot.properties().name());
+         return snapshot;
+      }
+      throw new ResourceNotFoundException("No image/snapshot with id '" + id + 
"' was found");
+   }
+
+   @Override
+   public Iterable<Location> listLocations() {
+      // Will never be called
+      throw new UnsupportedOperationException("Locations are configured in 
jclouds properties");
+   }
+
+   @Override
+   public ServerInDataCenter getNode(String id) {
+      DataCenterAndId datacenterAndId = DataCenterAndId.fromSlashEncoded(id);
+      logger.trace("<< searching for server with id=%s", id);
+
+      Server server = 
api.serverApi().getServer(datacenterAndId.getDataCenter(), 
datacenterAndId.getId(), new DepthOptions().depth(3));
+      if (server != null) {
+         logger.trace(">> found server [%s]", server.properties().name());
+      }
+      return server == null ? null : new ServerInDataCenter(server, 
datacenterAndId.getDataCenter());
+   }
+
+   @Override
+   public void destroyNode(String nodeId) {
+      DataCenterAndId datacenterAndId = 
DataCenterAndId.fromSlashEncoded(nodeId);
+      ServerApi serverApi = api.serverApi();
+      Server server = serverApi.getServer(datacenterAndId.getDataCenter(), 
datacenterAndId.getId(), new DepthOptions().depth(5));
+      if (server != null) {
+         for (org.apache.jclouds.profitbricks.rest.domain.Volume volume : 
server.entities().volumes().items()) {
+            destroyVolume(volume.id(), datacenterAndId.getDataCenter());
+         }
+
+         try {
+            destroyServer(datacenterAndId.getId(), 
datacenterAndId.getDataCenter());
+         } catch (Exception ex) {
+            logger.warn(ex, ">> failed to delete server with id=%s", 
datacenterAndId.getId());
+         }
+      }
+   }
+
+   @Override
+   public void rebootNode(final String id) {
+      final DataCenterAndId datacenterAndId = 
DataCenterAndId.fromSlashEncoded(id);
+      // Fail pre-emptively if not found
+      final ServerInDataCenter node = getRequiredNode(id);
+      
provisioningManager.provision(jobFactory.create(datacenterAndId.getDataCenter(),
 new Supplier<Object>() {
+
+         @Override
+         public Object get() {
+            api.serverApi().rebootServer(datacenterAndId.getDataCenter(), 
datacenterAndId.getId());
+            
waitServerUntilRunning.apply(ServerRef.create(datacenterAndId.getDataCenter(), 
datacenterAndId.getId()));
+            return node;
+         }
+      }));
+   }
+
+   @Override
+   public void resumeNode(final String id) {
+      final DataCenterAndId datacenterAndId = 
DataCenterAndId.fromSlashEncoded(id);
+      final ServerInDataCenter node = getRequiredNode(id);
+      if (node.getServer().properties().vmState() == Server.Status.RUNNING) {
+         return;
+      }
+
+      
provisioningManager.provision(jobFactory.create(datacenterAndId.getDataCenter(),
 new Supplier<Object>() {
+
+         @Override
+         public Object get() {
+            api.serverApi().startServer(datacenterAndId.getDataCenter(), 
datacenterAndId.getId());
+            
waitServerUntilRunning.apply(ServerRef.create(datacenterAndId.getDataCenter(), 
datacenterAndId.getId()));
+            return node;
+         }
+      }));
+   }
+
+   @Override
+   public void suspendNode(final String id) {
+      final DataCenterAndId datacenterAndId = 
DataCenterAndId.fromSlashEncoded(id);
+      final ServerInDataCenter 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.getServer().properties().vmState() == Server.Status.SHUTOFF) {
+         return;
+      }
+      
provisioningManager.provision(jobFactory.create(datacenterAndId.getDataCenter(),
 new Supplier<Object>() {
+         @Override
+         public Object get() {
+            api.serverApi().stopServer(datacenterAndId.getDataCenter(), 
datacenterAndId.getId());
+            
waitServerUntilSuspended.apply(ServerRef.create(datacenterAndId.getDataCenter(),
 datacenterAndId.getId()));
+            return node;
+         }
+      }));
+   }
+
+   @Override
+   public Iterable<ServerInDataCenter> listNodes() {
+      logger.trace("<< fetching servers..");
+      datacetners = api.dataCenterApi().list();
+      List<ServerInDataCenter> servers = new ArrayList<ServerInDataCenter>();
+      for (DataCenter dataCenter : datacetners) {
+
+         List<Server> serversInDataCenter = 
api.serverApi().getList(dataCenter.id(), new DepthOptions().depth(4));
+         for (Server server : serversInDataCenter) {
+            servers.add(new ServerInDataCenter(server, dataCenter.id()));
+         }
+      }
+      logger.trace("<< fetching servers..");
+      return servers;
+   }
+
+   @Override
+   public Iterable<ServerInDataCenter> listNodesByIds(final Iterable<String> 
ids) {
+      return filter(listNodes(), new Predicate<ServerInDataCenter>() {
+         @Override
+         public boolean apply(ServerInDataCenter server) {
+            return contains(ids, server.slashEncode());
+         }
+      });
+   }
+
+   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(dataCenterId, 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 destroyVolumes(List<String> volumeIds, String dataCenterId) {
+      for (String volumeId : volumeIds) {
+         destroyVolume(volumeId, dataCenterId);
+      }
+   }
+
+   private void destroyVolume(final String volumeId, final String 
dataCenterId) {
+      try {
+         logger.trace("<< deleting volume with id=%s", volumeId);
+         provisioningManager.provision(jobFactory.create(dataCenterId, new 
Supplier<Object>() {
+
+            @Override
+            public Object get() {
+               api.volumeApi().deleteVolume(dataCenterId, volumeId);
+
+               return volumeId;
+            }
+         }));
+         logger.trace(">> volume '%s' deleted.", volumeId);
+      } catch (Exception ex) {
+         logger.warn(ex, ">> failed to delete volume with id=%s", volumeId);
+      }
+   }
+
+   private ServerInDataCenter getRequiredNode(String nodeId) {
+      ServerInDataCenter 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-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningJob.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningJob.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningJob.java
new file mode 100644
index 0000000..af089fa
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/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.apache.jclouds.profitbricks.rest.compute.concurrent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+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;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+
+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-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningManager.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningManager.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningManager.java
new file mode 100644
index 0000000..061628c
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/concurrent/ProvisioningManager.java
@@ -0,0 +1,87 @@
+/*
+ * 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.apache.jclouds.profitbricks.rest.compute.concurrent;
+
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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;
+
+/**
+ * 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-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/config/ProfitBricksComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/config/ProfitBricksComputeServiceContextModule.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/config/ProfitBricksComputeServiceContextModule.java
new file mode 100644
index 0000000..15f2112
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/config/ProfitBricksComputeServiceContextModule.java
@@ -0,0 +1,330 @@
+/*
+ * 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.apache.jclouds.profitbricks.rest.compute.config;
+
+import com.google.common.base.Function;
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.base.Predicate;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import java.util.concurrent.TimeUnit;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.apache.jclouds.profitbricks.rest.ProfitBricksApi;
+import 
org.apache.jclouds.profitbricks.rest.compute.ProfitBricksComputeServiceAdapter;
+import org.apache.jclouds.profitbricks.rest.compute.concurrent.ProvisioningJob;
+import 
org.apache.jclouds.profitbricks.rest.compute.concurrent.ProvisioningManager;
+import 
org.apache.jclouds.profitbricks.rest.compute.function.ProvisionableToImage;
+import 
org.apache.jclouds.profitbricks.rest.compute.function.ServerInDataCenterToNodeMetadata;
+import org.apache.jclouds.profitbricks.rest.compute.function.VolumeToVolume;
+import 
org.apache.jclouds.profitbricks.rest.compute.strategy.AssignDataCenterToTemplate;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_MAX_PERIOD;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PERIOD;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_NIC;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_SERVER;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_SNAPSHOT;
+import static 
org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_TIMEOUT;
+import org.apache.jclouds.profitbricks.rest.domain.DataCenter;
+import org.apache.jclouds.profitbricks.rest.domain.Nic;
+import org.apache.jclouds.profitbricks.rest.domain.Provisionable;
+import org.apache.jclouds.profitbricks.rest.domain.Server;
+import org.apache.jclouds.profitbricks.rest.domain.State;
+import 
org.apache.jclouds.profitbricks.rest.domain.zonescoped.ServerInDataCenter;
+import org.apache.jclouds.profitbricks.rest.ids.NicRef;
+import org.apache.jclouds.profitbricks.rest.ids.ServerRef;
+import org.apache.jclouds.profitbricks.rest.ids.VolumeRef;
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+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.compute.domain.internal.ArbitraryCpuRamTemplateBuilderImpl;
+import org.jclouds.compute.domain.internal.TemplateBuilderImpl;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+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 static org.jclouds.util.Predicates2.retry;
+
+public class ProfitBricksComputeServiceContextModule extends
+        ComputeServiceAdapterContextModule<ServerInDataCenter, Hardware, 
Provisionable, Location> {
+
+   @Override
+   protected void configure() {
+      super.configure();
+
+      install(new FactoryModuleBuilder().build(ProvisioningJob.Factory.class));
+
+      
bind(ImplicitLocationSupplier.class).to(OnlyLocationOrFirstZone.class).in(Scopes.SINGLETON);
+
+      
bind(CreateNodesInGroupThenAddToSet.class).to(AssignDataCenterToTemplate.class).in(Scopes.SINGLETON);
+
+      bind(new TypeLiteral<ComputeServiceAdapter<ServerInDataCenter, Hardware, 
Provisionable, Location>>() {
+      }).to(ProfitBricksComputeServiceAdapter.class);
+      
+      
bind(TemplateBuilderImpl.class).to(ArbitraryCpuRamTemplateBuilderImpl.class);
+      
+      bind(new TypeLiteral<Function<ServerInDataCenter, NodeMetadata>>() {
+      }).to(ServerInDataCenterToNodeMetadata.class);
+
+      bind(new TypeLiteral<Function<Provisionable, Image>>() {
+      }).to(ProvisionableToImage.class);
+
+      bind(new 
TypeLiteral<Function<org.apache.jclouds.profitbricks.rest.domain.Volume, 
Volume>>() {
+      }).to(VolumeToVolume.class);
+
+      bind(new TypeLiteral<Function<Hardware, Hardware>>() {
+      }).to(Class.class.cast(IdentityFunction.class));
+   }
+
+   @Provides
+   @Singleton
+   @Named(POLL_PREDICATE_DATACENTER)
+   Predicate<String> provideDataCenterAvailablePredicate(final ProfitBricksApi 
api, ComputeConstants constants) {
+      return retry(new DataCenterProvisioningStatePredicate(
+              api, State.AVAILABLE),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_RUNNING)
+   Predicate<ServerRef> provideServerRunningPredicate(final ProfitBricksApi 
api, ComputeConstants constants) {
+      return retry(new ServerStatusPredicate(
+              api, Server.Status.RUNNING),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_SUSPENDED)
+   Predicate<ServerRef> provideServerSuspendedPredicate(final ProfitBricksApi 
api, ComputeConstants constants) {
+      return retry(new ServerStatusPredicate(
+              api, Server.Status.SHUTOFF),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Named(POLL_PREDICATE_SERVER)
+   Predicate<ServerRef> provideServerAvailablePredicate(final ProfitBricksApi 
api, ComputeConstants constants) {
+      return retry(new ServerAvaiblablePredicate(
+              api, State.AVAILABLE),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Singleton
+   ProvisioningManager provideProvisioningManager(Closer closer) {
+      ProvisioningManager provisioningManager = new ProvisioningManager();
+      closer.addToClose(provisioningManager);
+
+      return provisioningManager;
+   }
+
+   @Provides
+   @Singleton
+   @Named(POLL_PREDICATE_SNAPSHOT)
+   Predicate<String> provideSnapshotAvailablePredicate(final ProfitBricksApi 
api, ComputeConstants constants) {
+      return retry(new SnapshotProvisioningStatePredicate(
+              api, State.AVAILABLE),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Singleton
+   @Named(TIMEOUT_NODE_RUNNING)
+   Predicate<VolumeRef> provideVolumeAvailablePredicate(final ProfitBricksApi 
api, ComputeConstants constants) {
+      return retry(new VolumeProvisoningStatusPredicate(
+              api, State.AVAILABLE),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Singleton
+   @Named(POLL_PREDICATE_NIC)
+   Predicate<NicRef> provideNicAvailablePredicate(final ProfitBricksApi api, 
ProfitBricksComputeServiceContextModule.ComputeConstants constants) {
+      return retry(new NicAvailable(
+              api, State.AVAILABLE),
+              constants.pollTimeout(), constants.pollPeriod(), 
constants.pollMaxPeriod(), TimeUnit.SECONDS);
+   }
+
+   static class DataCenterProvisioningStatePredicate implements 
Predicate<String> {
+
+      private final ProfitBricksApi api;
+      private final State expectedState;
+
+      public DataCenterProvisioningStatePredicate(ProfitBricksApi api, State 
expectedState) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.expectedState = checkNotNull(expectedState, "expectedState must 
not be null");
+      }
+
+      @Override
+      public boolean apply(String input) {
+         checkNotNull(input, "datacenter id");
+         DataCenter dataCenter = api.dataCenterApi().getDataCenter(input);
+         return dataCenter.metadata().state() == expectedState;
+      }
+
+   }
+
+   static class ServerAvaiblablePredicate implements Predicate<ServerRef> {
+
+      private final ProfitBricksApi api;
+      private final State expectedState;
+
+      public ServerAvaiblablePredicate(ProfitBricksApi api, State 
expectedState) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.expectedState = checkNotNull(expectedState, "expectedState must 
not be null");
+      }
+
+      @Override
+      public boolean apply(ServerRef serverRef) {
+
+         checkNotNull(serverRef, "serverRef");
+         //give time for the operation to actually start
+         Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
+         Server server = api.serverApi().getServer(serverRef.dataCenterId(), 
serverRef.serverId());
+
+         if (server == null || server.metadata() == null) {
+            return false;
+         }
+         return 
server.metadata().state().toString().equals(expectedState.toString());
+      }
+
+   }
+
+   static class ServerStatusPredicate implements Predicate<ServerRef> {
+
+      private final ProfitBricksApi api;
+      private final Server.Status expectedStatus;
+
+      public ServerStatusPredicate(ProfitBricksApi api, Server.Status 
expectedStatus) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.expectedStatus = checkNotNull(expectedStatus, "expectedStatus 
must not be null");
+      }
+
+      @Override
+      public boolean apply(ServerRef serverRef) {
+         checkNotNull(serverRef, "serverRef");
+         //give time for the operation to actually start
+         Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
+         Server server = api.serverApi().getServer(serverRef.dataCenterId(), 
serverRef.serverId());
+
+         if (server == null || server.properties().vmState() == null) {
+            return false;
+         }
+
+         return server.properties().vmState() == expectedStatus;
+      }
+
+   }
+
+   static class SnapshotProvisioningStatePredicate implements 
Predicate<String> {
+
+      private final ProfitBricksApi api;
+      private final State expectedState;
+
+      public SnapshotProvisioningStatePredicate(ProfitBricksApi api, State 
expectedState) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.expectedState = checkNotNull(expectedState, "expectedState must 
not be null");
+      }
+
+      @Override
+      public boolean apply(String input) {
+         checkNotNull(input, "snapshot id");
+         return 
api.snapshotApi().get(input).metadata().state().toString().equals(expectedState.toString());
+      }
+   }
+
+   static class VolumeProvisoningStatusPredicate implements 
Predicate<VolumeRef> {
+
+      private final ProfitBricksApi api;
+      private final State expectedState;
+
+      public VolumeProvisoningStatusPredicate(ProfitBricksApi api, State 
expectedState) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.expectedState = checkNotNull(expectedState, "expectedState must 
not be null");
+      }
+
+      @Override
+      public boolean apply(VolumeRef input) {
+         checkNotNull(input, "Volume REF");
+         org.apache.jclouds.profitbricks.rest.domain.Volume volume = 
api.volumeApi().getVolume(input.dataCenterId(), input.volumeId());
+         if (volume == null || volume.metadata() == null || 
volume.metadata().state() == null) {
+            return false;
+         }
+         return 
volume.metadata().state().toString().equals(expectedState.toString());
+      }
+   }
+
+   static class NicAvailable implements Predicate<NicRef> {
+
+      private final ProfitBricksApi api;
+      private final State expectedState;
+
+      public NicAvailable(ProfitBricksApi api, State expectedState) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.expectedState = checkNotNull(expectedState, "expectedState must 
not be null");
+      }
+
+      @Override
+      public boolean apply(NicRef input) {
+         checkNotNull(input, "NicRef ");
+         Nic nice = api.nicApi().get(input.dataCenterId(), input.serverId(), 
input.nicId());
+         if (nice == null || nice.metadata() == null || 
nice.metadata().state() == null) {
+            return false;
+         }
+         return 
nice.metadata().state().toString().equals(expectedState.toString());
+      }
+   }
+
+   @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-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ProvisionableToImage.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ProvisionableToImage.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ProvisionableToImage.java
new file mode 100644
index 0000000..3071b92
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ProvisionableToImage.java
@@ -0,0 +1,245 @@
+/*
+ * 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.apache.jclouds.profitbricks.rest.compute.function;
+
+import com.google.common.base.Function;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.base.Strings;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import static com.google.common.collect.Iterables.find;
+import com.google.inject.Inject;
+import java.util.Set;
+import java.util.regex.Pattern;
+import static org.apache.jclouds.profitbricks.rest.domain.Image.Type.CDROM;
+import org.apache.jclouds.profitbricks.rest.domain.LicenceType;
+import org.apache.jclouds.profitbricks.rest.domain.Provisionable;
+import org.apache.jclouds.profitbricks.rest.domain.Snapshot;
+import org.jclouds.collect.Memoized;
+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 static org.jclouds.location.predicates.LocationPredicates.idEquals;
+
+public class ProvisionableToImage implements Function<Provisionable, Image> {
+
+   public static final String KEY_PROVISIONABLE_TYPE = "provisionableType";
+
+   private final ImageToImage fnImageToImage;
+   private final SnapshotToImage fnSnapshotToImage;
+
+   @Inject
+   ProvisionableToImage(@Memoized Supplier<Set<? extends Location>> locations) 
{
+      this.fnImageToImage = new ImageToImage(locations);
+      this.fnSnapshotToImage = new SnapshotToImage(locations);
+   }
+
+   @Override
+   public Image apply(Provisionable input) {
+      checkNotNull(input, "Cannot convert null input");
+
+      if (input instanceof org.apache.jclouds.profitbricks.rest.domain.Image) {
+         return 
fnImageToImage.apply((org.apache.jclouds.profitbricks.rest.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(LicenceType 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 
ImageFunction<org.apache.jclouds.profitbricks.rest.domain.Image> {
+
+      private static final Pattern HAS_NUMBERS = Pattern.compile(".*\\d+.*");
+
+      private final Supplier<Set<? extends Location>> locations;
+
+      ImageToImage(Supplier<Set<? extends Location>> locations) {
+         this.locations = locations;
+      }
+
+      @Override
+      public Image apply(org.apache.jclouds.profitbricks.rest.domain.Image 
from) {
+         String desc = from.properties().name();
+         OsFamily osFamily = parseOsFamily(desc, 
from.properties().licenceType());
+         Location location = find(locations.get(), 
idEquals(from.properties().location().getId()));
+
+         OperatingSystem os = OperatingSystem.builder()
+                 .description(osFamily.value())
+                 .family(osFamily)
+                 .version(parseVersion(desc))
+                 .is64Bit(is64Bit(desc, from.properties().imageType()))
+                 .build();
+
+         return addTypeMetadata(new ImageBuilder()
+                 .ids(from.id())
+                 .name(desc)
+                 .location(location)
+                 .status(Image.Status.AVAILABLE)
+                 .operatingSystem(os))
+                 .build();
+      }
+
+      private OsFamily parseOsFamily(String from, LicenceType 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.apache.jclouds.profitbricks.rest.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;
+         }
+      }
+
+      @Override
+      public ImageBuilder addTypeMetadata(ImageBuilder builder) {
+         return builder.userMetadata(ImmutableMap.of(KEY_PROVISIONABLE_TYPE, 
Provisionable.Type.IMAGE.toString()));
+      }
+   }
+
+   private static class SnapshotToImage implements ImageFunction<Snapshot> {
+
+      private final Supplier<Set<? extends Location>> locations;
+
+      SnapshotToImage(Supplier<Set<? extends Location>> locations) {
+         this.locations = locations;
+      }
+
+      @Override
+      public Image apply(Snapshot from) {
+         String textToParse = from.properties().name() + 
from.properties().description();
+         OsFamily osFamily = parseOsFamily(textToParse, 
from.properties().licenceType());
+         Location location = find(locations.get(), 
idEquals(from.properties().location().getId()));
+
+         OperatingSystem os = OperatingSystem.builder()
+                 .description(osFamily.value())
+                 .family(osFamily)
+                 .is64Bit(true)
+                 .version("00.00")
+                 .build();
+
+         return addTypeMetadata(new ImageBuilder()
+                 .ids(from.id())
+                 .name(from.properties().name())
+                 .description(from.properties().description())
+                 .location(location)
+                 .status(mapStatus(from.metadata().state()))
+                 .operatingSystem(os))
+                 .build();
+      }
+
+      private OsFamily parseOsFamily(String text, LicenceType 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(org.apache.jclouds.profitbricks.rest.domain.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;
+         }
+      }
+
+      @Override
+      public ImageBuilder addTypeMetadata(ImageBuilder builder) {
+         return builder.userMetadata(ImmutableMap.of(KEY_PROVISIONABLE_TYPE, 
org.apache.jclouds.profitbricks.rest.domain.Provisionable.Type.SNAPSHOT.toString()));
+      }
+   }
+
+   private interface ImageFunction<T extends Provisionable> extends 
Function<T, Image> {
+
+      ImageBuilder addTypeMetadata(ImageBuilder builder);
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ServerInDataCenterToNodeMetadata.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ServerInDataCenterToNodeMetadata.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ServerInDataCenterToNodeMetadata.java
new file mode 100644
index 0000000..e7f0cf0
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/ServerInDataCenterToNodeMetadata.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.apache.jclouds.profitbricks.rest.compute.function;
+
+import com.google.common.base.Function;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.not;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+import static com.google.common.collect.Iterables.find;
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import java.util.List;
+import java.util.Set;
+import org.apache.jclouds.profitbricks.rest.ProfitBricksApi;
+import org.apache.jclouds.profitbricks.rest.domain.DataCenter;
+import org.apache.jclouds.profitbricks.rest.domain.LicenceType;
+import org.apache.jclouds.profitbricks.rest.domain.Nic;
+import org.apache.jclouds.profitbricks.rest.domain.Server;
+import 
org.apache.jclouds.profitbricks.rest.domain.zonescoped.ServerInDataCenter;
+import org.jclouds.collect.Memoized;
+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.compute.functions.GroupNamingConvention;
+import org.jclouds.domain.Location;
+import static org.jclouds.location.predicates.LocationPredicates.idEquals;
+import org.jclouds.util.InetAddresses2;
+
+public class ServerInDataCenterToNodeMetadata implements 
Function<ServerInDataCenter, NodeMetadata> {
+
+   private final Function<org.apache.jclouds.profitbricks.rest.domain.Volume, 
Volume> fnVolume;
+   private final Supplier<Set<? extends Location>> locations;
+   private final Function<List<Nic>, List<String>> fnCollectIps;
+   private final ProfitBricksApi api;
+
+   private final GroupNamingConvention groupNamingConvention;
+
+   @Inject
+   public 
ServerInDataCenterToNodeMetadata(Function<org.apache.jclouds.profitbricks.rest.domain.Volume,
 Volume> fnVolume,
+           @Memoized Supplier<Set<? extends Location>> locations,
+           ProfitBricksApi api,
+           GroupNamingConvention.Factory groupNamingConvention) {
+      this.fnVolume = fnVolume;
+      this.locations = locations;
+      this.api = api;
+      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.properties().ips());
+            }
+            return ips;
+         }
+      };
+   }
+
+   @Override
+   public NodeMetadata apply(final ServerInDataCenter serverInDataCenter) {
+      Server server = serverInDataCenter.getServer();
+      checkNotNull(server, "Null server");
+
+      // Location is not populated in the datacenter on a server response
+      DataCenter dataCenter = 
api.dataCenterApi().getDataCenter(serverInDataCenter.getDataCenter());
+      Location location = find(locations.get(), 
idEquals(dataCenter.properties().location().getId()));
+
+      float size = 0f;
+      List<Volume> volumes = Lists.newArrayList();
+      List<org.apache.jclouds.profitbricks.rest.domain.Volume> storages = 
server.entities().volumes().items();
+      if (storages != null) {
+         for (org.apache.jclouds.profitbricks.rest.domain.Volume storage : 
storages) {
+            size += storage.properties().size();
+            volumes.add(fnVolume.apply(storage));
+         }
+      }
+
+      // Build hardware
+      String id = String.format("cpu=%d,ram=%d,disk=%.0f", 
server.properties().cores(), server.properties().ram(), size);
+      Hardware hardware = new HardwareBuilder()
+              .ids(id)
+              .name(id)
+              .ram(server.properties().ram())
+              .processor(new Processor(server.properties().cores(), 1d))
+              .hypervisor("kvm")
+              .volumes(volumes)
+              .location(location)
+              .build();
+
+      // Collect ips
+      List<String> addresses = 
fnCollectIps.apply(server.entities().nics().items());
+      OperatingSystem os = null;
+      if (server.properties() != null && server.properties().bootVolume() != 
null && server.properties().bootVolume().properties() != null) {
+         os = 
mapOsType(server.properties().bootVolume().properties().licenceType());
+
+      }
+      // Build node
+      NodeMetadataBuilder nodeBuilder = new NodeMetadataBuilder();
+      nodeBuilder.ids(serverInDataCenter.slashEncode())
+              
.group(groupNamingConvention.extractGroup(server.properties().name()))
+              .name(server.properties().name())
+              .backendStatus(server.metadata().state().toString())
+              .status(mapStatus(server.properties().vmState()))
+              .hardware(hardware)
+              .operatingSystem(os)
+              .location(location)
+              .privateAddresses(Iterables.filter(addresses, 
InetAddresses2.IsPrivateIPAddress.INSTANCE))
+              .publicAddresses(Iterables.filter(addresses, 
not(InetAddresses2.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(LicenceType 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-labs/blob/5742745e/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/VolumeToVolume.java
----------------------------------------------------------------------
diff --git 
a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/VolumeToVolume.java
 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/VolumeToVolume.java
new file mode 100644
index 0000000..5e2e8ee
--- /dev/null
+++ 
b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/function/VolumeToVolume.java
@@ -0,0 +1,44 @@
+/*
+ * 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.apache.jclouds.profitbricks.rest.compute.function;
+
+import com.google.common.base.Function;
+import static com.google.common.base.Preconditions.checkNotNull;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.VolumeBuilder;
+
+public class VolumeToVolume implements 
Function<org.apache.jclouds.profitbricks.rest.domain.Volume, Volume> {
+
+   @Override
+   public Volume apply(org.apache.jclouds.profitbricks.rest.domain.Volume 
volume) {
+      checkNotNull(volume, "Null storage");
+
+      String device = "";
+      if (volume.properties().deviceNumber() != null) {
+         device = volume.properties().deviceNumber().toString();
+      }
+
+      return new VolumeBuilder()
+              .id(volume.id())
+              .size((float) volume.properties().size())
+              .device(device)
+              .durable(true)
+              .type(Volume.Type.LOCAL)
+              .build();
+
+   }
+}

Reply via email to