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 <[email protected]> Authored: Mon Jun 29 11:13:21 2015 +0800 Committer: Ignasi Barrera <[email protected]> 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; } }
