You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by an...@apache.org on 2018/09/12 04:25:24 UTC

[7/7] jclouds-labs git commit: [JCLOUDS-1430] Aliyun ECS

[JCLOUDS-1430] Aliyun ECS

- add instance API
- add compute abstraction
- add validation for vpc and vSwitch IDs
- add builders for Image and Instance
- add unit tests for compute/functions
- add pagination to instanceStatus api
- rename provider id
- clean up code
- add network apis
- vpc api + tests
- vswitch api + tests
- improve CreateResourcesThenCreateNodes
- create default vpc and vswitch in case needed
- fix InstanceApiLiveTest
- add ECSDependencyViolationRetryHandler
- add ErrorRetryHandler
- fix ListImagesOptions.imageId
- fix enums in Instance and EIPAddress


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

Branch: refs/heads/master
Commit: 2c7db7e809085a7738d76f5714663231dff0a9f3
Parents: a5dbf00
Author: andreaturli <an...@gmail.com>
Authored: Tue Jul 3 14:47:43 2018 +0200
Committer: Andrea Turli <an...@gmail.com>
Committed: Tue Sep 11 21:19:59 2018 -0700

----------------------------------------------------------------------
 aliyun-ecs/README.md                            |   43 +
 aliyun-ecs/pom.xml                              |    3 -
 .../aliyun/ecs/ECSComputeServiceApi.java        |   11 +
 .../ecs/ECSComputeServiceProviderMetadata.java  |    4 +-
 .../aliyun/ecs/ECSServiceApiMetadata.java       |    6 +-
 .../aliyun/ecs/compute/ECSComputeService.java   |  154 +
 .../ecs/compute/ECSComputeServiceAdapter.java   |  278 ++
 .../compute/config/ECSServiceContextModule.java |  160 +
 .../compute/functions/ImageInRegionToImage.java |  104 +
 .../functions/InstanceStatusToStatus.java       |   44 +
 .../functions/InstanceToNodeMetadata.java       |  126 +
 .../functions/InstanceTypeToHardware.java       |   49 +
 .../ecs/compute/functions/RegionToLocation.java |   54 +
 .../functions/internal/OperatingSystems.java    |   51 +
 .../options/ECSServiceTemplateOptions.java      |  180 +
 .../ecs/compute/strategy/CleanupResources.java  |  112 +
 .../CreateResourcesThenCreateNodes.java         |  360 ++
 .../config/ECSComputeServiceHttpApiModule.java  |   24 +
 .../domain/AllocatePublicIpAddressRequest.java  |   59 +
 .../aliyun/ecs/domain/AvailableResource.java    |   43 +
 .../aliyun/ecs/domain/AvailableZone.java        |   47 +
 .../ecs/domain/DedicatedHostAttribute.java      |   37 +
 .../jclouds/aliyun/ecs/domain/EipAddress.java   |   67 +
 .../jclouds/aliyun/ecs/domain/ErrorMessage.java |   39 +
 .../org/jclouds/aliyun/ecs/domain/Image.java    |  112 +-
 .../org/jclouds/aliyun/ecs/domain/Instance.java |  321 ++
 .../aliyun/ecs/domain/InstanceRequest.java      |   59 +
 .../aliyun/ecs/domain/InstanceStatus.java       |   55 +
 .../jclouds/aliyun/ecs/domain/InstanceType.java |   85 +
 .../aliyun/ecs/domain/NetworkInterface.java     |   41 +
 .../jclouds/aliyun/ecs/domain/Permission.java   |    2 +-
 .../jclouds/aliyun/ecs/domain/ResourceType.java |   42 +
 .../aliyun/ecs/domain/SecurityGroup.java        |   14 +-
 .../aliyun/ecs/domain/SupportedResource.java    |   52 +
 .../java/org/jclouds/aliyun/ecs/domain/Tag.java |   14 +-
 .../org/jclouds/aliyun/ecs/domain/UserCidr.java |   27 +
 .../java/org/jclouds/aliyun/ecs/domain/VPC.java |  131 +
 .../jclouds/aliyun/ecs/domain/VPCRequest.java   |   74 +
 .../org/jclouds/aliyun/ecs/domain/VSwitch.java  |  112 +
 .../aliyun/ecs/domain/VSwitchRequest.java       |   58 +
 .../aliyun/ecs/domain/VpcAttributes.java        |   48 +
 .../domain/internal/PaginatedCollection.java    |    4 +-
 .../domain/options/CreateInstanceOptions.java   |  161 +
 .../ecs/domain/options/CreateVPCOptions.java    |  106 +
 .../domain/options/CreateVSwitchOptions.java    |   74 +
 .../ecs/domain/options/ListImagesOptions.java   |    6 +-
 .../options/ListInstanceStatusOptions.java      |   50 +
 .../domain/options/ListInstancesOptions.java    |  239 +
 .../ecs/domain/options/ListVPCsOptions.java     |   63 +
 .../domain/options/ListVSwitchesOptions.java    |   89 +
 .../aliyun/ecs/domain/options/TagOptions.java   |   22 +-
 .../ecs/domain/regionscoped/ImageInRegion.java  |   36 +
 .../ecs/domain/regionscoped/RegionAndId.java    |   54 +
 .../aliyun/ecs/features/InstanceApi.java        |  264 ++
 .../aliyun/ecs/features/SecurityGroupApi.java   |    3 +
 .../aliyun/ecs/features/SshKeyPairApi.java      |    4 +
 .../org/jclouds/aliyun/ecs/features/TagApi.java |   10 +-
 .../org/jclouds/aliyun/ecs/features/VPCApi.java |  140 +
 .../jclouds/aliyun/ecs/features/VSwitchApi.java |  145 +
 .../ecs/functions/PutStringInDoubleQuotes.java  |   27 +
 .../ecs/handlers/ECSErrorRetryHandler.java      |   74 +
 .../ecs/predicates/InstanceStatusPredicate.java |   33 +
 .../ecs/compute/ECSComputeServiceLiveTest.java  |   46 +
 .../ecs/compute/ECSTemplateBuilderLiveTest.java |   53 +
 .../ecs/compute/features/ImageApiLiveTest.java  |   13 +-
 .../ecs/compute/features/ImageApiMockTest.java  |   19 +-
 .../compute/features/InstanceApiLiveTest.java   |  189 +
 .../compute/features/InstanceApiMockTest.java   |  168 +
 .../features/RegionAndZoneApiMockTest.java      |    9 +-
 .../features/SecurityGroupApiLiveTest.java      |   11 +-
 .../features/SecurityGroupApiMockTest.java      |   19 +-
 .../compute/features/SshKeyPairApiLiveTest.java |   13 +-
 .../compute/features/SshKeyPairApiMockTest.java |   21 +-
 .../ecs/compute/features/TagApiLiveTest.java    |   16 +-
 .../ecs/compute/features/TagApiMockTest.java    |   19 +-
 .../ecs/compute/features/VPCApiLiveTest.java    |   89 +
 .../ecs/compute/features/VPCApiMockTest.java    |   87 +
 .../compute/features/VSwitchApiLiveTest.java    |  100 +
 .../compute/features/VSwitchApiMockTest.java    |   87 +
 .../functions/ImageInRegionToImageTest.java     |  141 +
 .../functions/InstanceStatusToStatusTest.java   |   58 +
 .../functions/InstanceToHardwareTest.java       |   61 +
 .../functions/InstanceToNodeMetadataTest.java   |  249 +
 .../BaseECSComputeServiceApiLiveTest.java       |   22 +-
 .../BaseECSComputeServiceApiMockTest.java       |    5 +-
 .../CreateResourcesThenCreateNodesTest.java     |  395 ++
 .../src/test/resources/availableZones.json      |  423 ++
 .../src/test/resources/instanceStatus.json      |   18 +
 .../src/test/resources/instanceTypes.json       | 4281 ++++++++++++++++++
 .../src/test/resources/instances-first.json     |  960 ++++
 .../src/test/resources/instances-last.json      |  960 ++++
 aliyun-ecs/src/test/resources/logback-test.xml  |   42 +
 .../src/test/resources/vpc-create-res.json      |    6 +
 .../src/test/resources/vpc-delete-res.json      |    3 +
 aliyun-ecs/src/test/resources/vpcs-first.json   |   29 +
 aliyun-ecs/src/test/resources/vpcs-last.json    |   29 +
 .../src/test/resources/vswitch-create-res.json  |    4 +
 .../src/test/resources/vswitch-delete-res.json  |    3 +
 .../src/test/resources/vswitches-first.json     |   22 +
 .../src/test/resources/vswitches-last.json      |   22 +
 100 files changed, 13485 insertions(+), 113 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/README.md
----------------------------------------------------------------------
diff --git a/aliyun-ecs/README.md b/aliyun-ecs/README.md
new file mode 100644
index 0000000..5bb8723
--- /dev/null
+++ b/aliyun-ecs/README.md
@@ -0,0 +1,43 @@
+alibaba Elastic Compute Service Provider
+==========================
+
+# How to use it
+
+alibaba ECS provider works exactly as any other jclouds provider.
+Notice that as alibaba supports dozens of locations and to limit the scope of some operations, one may want to use:
+
+and
+```bash
+jclouds.regions
+```
+which is by default `null`. If you want to target only the `north europe` region, you can use
+
+```bash
+jclouds.regions="eu-central-1"
+```
+
+# Setting Up Test Environment
+
+Get or create the `User Access Key` and `Access Key Secret` for your account at `https://usercenter.console.alibaba.com/#/manage/ak`
+
+# Run Live Tests
+
+Use the following to run one live test:
+
+```bash
+mvn -Dtest=<name of the live test> \
+    -Dtest.alibaba-ecs.identity="<AccessKey ID>" \
+    -Dtest.alibaba-ecs.credential="<Access Key Secret>" 
+    integration-test -Plive
+```
+
+Use the following to run all the live tests:
+
+```bash
+
+mvn clean verify -Plive \
+    -Dtest.alibaba-ecs.identity="<AccessKey ID>" \
+    -Dtest.alibaba-ecs.credential="<Access Key Secret>" 
+```
+
+

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/pom.xml
----------------------------------------------------------------------
diff --git a/aliyun-ecs/pom.xml b/aliyun-ecs/pom.xml
index da1b900..7b81aaf 100644
--- a/aliyun-ecs/pom.xml
+++ b/aliyun-ecs/pom.xml
@@ -32,8 +32,6 @@
 
     <properties>
         <test.aliyun-ecs.endpoint>https://ecs.aliyuncs.com/</test.aliyun-ecs.endpoint>
-        <test.aliyun-ecs.api-version></test.aliyun-ecs.api-version>
-        <test.aliyun-ecs.build-version/>
         <test.aliyun-ecs.identity>FIXME_IDENTITY</test.aliyun-ecs.identity>
         <test.aliyun-ecs.credential>FIXME_CREDENTIALS</test.aliyun-ecs.credential>
         <test.aliyun-ecs.template/>
@@ -142,6 +140,5 @@
         </profile>
     </profiles>
 
-
 </project>
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
index bb24cf0..2b0c9f8 100644
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
@@ -17,10 +17,13 @@
 package org.jclouds.aliyun.ecs;
 
 import org.jclouds.aliyun.ecs.features.ImageApi;
+import org.jclouds.aliyun.ecs.features.InstanceApi;
 import org.jclouds.aliyun.ecs.features.RegionAndZoneApi;
 import org.jclouds.aliyun.ecs.features.SecurityGroupApi;
 import org.jclouds.aliyun.ecs.features.SshKeyPairApi;
 import org.jclouds.aliyun.ecs.features.TagApi;
+import org.jclouds.aliyun.ecs.features.VPCApi;
+import org.jclouds.aliyun.ecs.features.VSwitchApi;
 import org.jclouds.rest.annotations.Delegate;
 
 import java.io.Closeable;
@@ -42,4 +45,12 @@ public interface ECSComputeServiceApi extends Closeable {
    @Delegate
    TagApi tagApi();
 
+   @Delegate
+   InstanceApi instanceApi();
+
+   @Delegate
+   VPCApi vpcApi();
+
+   @Delegate
+   VSwitchApi vSwitchApi();
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java
index fbd7206..3ffed6d 100644
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java
@@ -51,8 +51,8 @@ public class ECSComputeServiceProviderMetadata extends BaseProviderMetadata {
    public static class Builder extends BaseProviderMetadata.Builder {
 
       protected Builder() {
-         id("aliyun-ecs")
-               .name("Alibaba Elastic Compute Service")
+         id("alibaba-ecs")
+               .name("Alibaba Cloud Elastic Compute Service")
                .apiMetadata(new ECSServiceApiMetadata())
                .homepage(URI.create("https://www.alibabacloud.com"))
                .console(URI.create("https://ecs.console.aliyun.com"))

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java
index d81ef96..54e725c 100644
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java
@@ -18,6 +18,7 @@ package org.jclouds.aliyun.ecs;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Module;
+import org.jclouds.aliyun.ecs.compute.config.ECSServiceContextModule;
 import org.jclouds.aliyun.ecs.config.ECSComputeServiceHttpApiModule;
 import org.jclouds.aliyun.ecs.config.ECSComputeServiceParserModule;
 import org.jclouds.apis.ApiMetadata;
@@ -46,7 +47,7 @@ public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeService
 
    public static Properties defaultProperties() {
       Properties properties = BaseHttpApiMetadata.defaultProperties();
-      properties.put(TEMPLATE, "osFamily=CENTOS,os64Bit=true,osVersionMatches=7.4");
+      properties.put(TEMPLATE, "osFamily=CENTOS,os64Bit=true,osVersionMatches=7.*");
       properties.put(TIMEOUT_NODE_RUNNING, 900000); // 15 mins
       properties.put(TIMEOUT_NODE_SUSPENDED, 900000); // 15 mins
       return properties;
@@ -60,7 +61,7 @@ public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeService
    public static class Builder extends BaseHttpApiMetadata.Builder<ECSComputeServiceApi, Builder> {
 
       protected Builder() {
-         id("aliyun-ecs")
+         id("alibaba-ecs")
                  .name("Alibaba Elastic Compute Service API")
                  .identityName("user name")
                  .credentialName("user password")
@@ -72,6 +73,7 @@ public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeService
                  .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
                          .add(ECSComputeServiceHttpApiModule.class)
                          .add(ECSComputeServiceParserModule.class)
+                         .add(ECSServiceContextModule.class)
                          .build());
       }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java
new file mode 100644
index 0000000..b3c6802
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java
@@ -0,0 +1,154 @@
+/*
+ * 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.aliyun.ecs.compute;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.Constants;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.strategy.CleanupResources;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.callables.RunScriptOnNode;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.extensions.internal.DelegatingImageExtension;
+import org.jclouds.compute.internal.BaseComputeService;
+import org.jclouds.compute.internal.PersistNodeCredentials;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.compute.strategy.DestroyNodeStrategy;
+import org.jclouds.compute.strategy.GetImageStrategy;
+import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
+import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.RebootNodeStrategy;
+import org.jclouds.compute.strategy.ResumeNodeStrategy;
+import org.jclouds.compute.strategy.SuspendNodeStrategy;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.scriptbuilder.functions.InitAdminAccess;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+
+@Singleton
+public class ECSComputeService extends BaseComputeService {
+   private final CleanupResources cleanupResources;
+
+   @Inject
+   protected ECSComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
+                               @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
+                               @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
+                               GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy,
+                               CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
+                               DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy,
+                               SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
+                               @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider,
+                               @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
+                               @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
+                               @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
+                               InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
+                               RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
+                               PersistNodeCredentials persistNodeCredentials,
+                               @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+                               CleanupResources cleanupResources, Optional<ImageExtension> imageExtension,
+                               Optional<SecurityGroupExtension> securityGroupExtension,
+                               DelegatingImageExtension.Factory delegatingImageExtension) {
+      super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
+              getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
+              startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
+              nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
+              persistNodeCredentials, userExecutor, imageExtension, securityGroupExtension, delegatingImageExtension);
+      this.cleanupResources = cleanupResources;
+   }
+
+   @Override
+   protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
+      for (NodeMetadata deadNode : deadNodes) {
+         RegionAndId regionAndId = RegionAndId.fromSlashEncoded(deadNode.getId());
+         Set<String> tags = deadNode.getTags();
+         String vSwitchId = extractVSwitchId(tags);
+         VSwitch vSwitch = context.unwrapApi(ECSComputeServiceApi.class).vSwitchApi().list(deadNode.getLocation().getId(), ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)).first().orNull();
+         String vpcId = vSwitch.vpcId();
+
+         try {
+            cleanupResources.cleanupNode(regionAndId);
+         } catch (Exception ex) {
+            logger.warn(ex, "Error cleaning up resources for node %s", deadNode);
+         }
+
+         List<SecurityGroup> securityGroups = cleanupResources.findOrphanedSecurityGroups(regionAndId.regionId(), deadNode.getGroup());
+         for (SecurityGroup securityGroup : securityGroups) {
+            logger.debug(">> destroying security group %s ...", securityGroup.id());
+            if (cleanupResources.cleanupSecurityGroupIfOrphaned(regionAndId.regionId(), securityGroup.id())) {
+               logger.debug(">> security group: (%s) has been deleted.", securityGroup.id());
+            } else {
+               logger.warn(">> security group: (%s) has not been deleted.", securityGroup.id());
+            }
+         }
+
+         // FIXME not sure it is correct to always delete vSwitch and VPC_PREFIX
+         logger.debug(">> destroying vSwitch %s ...", vSwitchId);
+         if (cleanupResources.cleanupVSwitchIfOrphaned(regionAndId.regionId(), vSwitchId)) {
+            logger.debug(">> vSwitch: (%s) has been deleted.", vSwitchId);
+         } else {
+            logger.warn(">> vSwitch: (%s) has not been deleted.", vSwitchId);
+         }
+
+         logger.debug(">> destroying vpc %s ...", vpcId);
+         try {
+            cleanupResources.cleanupVPCIfOrphaned(regionAndId.regionId(), vpcId);
+            logger.debug(">> VPC_PREFIX: (%s) has been deleted.", vpcId);
+         } catch (IllegalArgumentException e) {
+            logger.warn(">> VPC_PREFIX: (%s) has not been deleted.", vpcId);
+         }
+      }
+   }
+
+   private String extractVSwitchId(Set<String> tags) {
+      String vSwitchIdTag = Iterables.tryFind(tags, new Predicate<String>() {
+         @Override
+         public boolean apply(@Nullable String input) {
+            return input.startsWith("vsw-");
+         }
+      }).orNull();
+      return vSwitchIdTag;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java
new file mode 100644
index 0000000..7e12ed8
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java
@@ -0,0 +1,278 @@
+/*
+ * 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.aliyun.ecs.compute;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.strategy.CleanupResources;
+import org.jclouds.aliyun.ecs.domain.AvailableResource;
+import org.jclouds.aliyun.ecs.domain.AvailableZone;
+import org.jclouds.aliyun.ecs.domain.Image;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.InstanceRequest;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.aliyun.ecs.domain.Region;
+import org.jclouds.aliyun.ecs.domain.SupportedResource;
+import org.jclouds.aliyun.ecs.domain.options.CreateInstanceOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListInstancesOptions;
+import org.jclouds.aliyun.ecs.domain.options.TagOptions;
+import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions;
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.util.ComputeServiceUtils;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.contains;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.lang.String.format;
+import static org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId.fromSlashEncoded;
+import static org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId.slashEncodeRegionAndId;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+
+/**
+ * defines the connection between the {@link ECSComputeServiceApi} implementation and
+ * the jclouds {@link org.jclouds.compute.ComputeService}
+ */
+@Singleton
+public class ECSComputeServiceAdapter implements ComputeServiceAdapter<Instance, InstanceType, ImageInRegion, Region> {
+
+   private final ECSComputeServiceApi api;
+   private final Predicate<String> instanceSuspendedPredicate;
+
+   private final Supplier<Set<String>> regionIds;
+   private final CleanupResources cleanupResources;
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   ECSComputeServiceAdapter(ECSComputeServiceApi api,
+                            @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> instanceSuspendedPredicate,
+                            @org.jclouds.location.Region Supplier<Set<String>> regionIds,
+                            CleanupResources cleanupResources) {
+      this.api = api;
+      this.instanceSuspendedPredicate = instanceSuspendedPredicate;
+      this.regionIds = regionIds;
+      this.cleanupResources = cleanupResources;
+   }
+
+   @Override
+   public NodeAndInitialCredentials<Instance> createNodeWithGroupEncodedIntoName(String group, String name, Template template) {
+      String instanceType = template.getHardware().getId();
+      String regionId = template.getLocation().getId();
+      String imageId = template.getImage().getId();
+
+      ECSServiceTemplateOptions templateOptions = template.getOptions().as(ECSServiceTemplateOptions.class);
+
+      String keyPairName = templateOptions.getKeyPairName();
+      String securityGroupId = Iterables.getOnlyElement(templateOptions.getGroups());
+      String vSwitchId = templateOptions.getVSwitchId();
+      Instance.InternetChargeType internetChargeType = Instance.InternetChargeType.fromValue(templateOptions.getInternetChargeType());
+      int internetMaxBandwidthOut = templateOptions.getInternetMaxBandwidthOut();
+      String instanceChargeType = templateOptions.getInstanceChargeType();
+
+      Map<String, String> tags = ComputeServiceUtils.metadataAndTagsAsValuesOfEmptyString(templateOptions);
+      tags = new ImmutableMap.Builder()
+              .putAll(tags)
+              .put(vSwitchId, "")
+              .build();
+      TagOptions tagOptions = TagOptions.Builder.tags(tags);
+
+      InstanceRequest instanceRequest = api.instanceApi().create(regionId, RegionAndId.fromSlashEncoded(imageId).id(), securityGroupId, name, instanceType,
+              CreateInstanceOptions.Builder
+                      .vSwitchId(vSwitchId)
+                      .internetChargeType(internetChargeType.toString())
+                      .internetMaxBandwidthOut(internetMaxBandwidthOut)
+                      .instanceChargeType(instanceChargeType)
+                      .instanceName(name)
+                      .keyPairName(keyPairName)
+                      .tagOptions(tagOptions)
+      );
+
+      String regionAndInstanceId = slashEncodeRegionAndId(regionId, instanceRequest.getInstanceId());
+      if (!instanceSuspendedPredicate.apply(regionAndInstanceId)) {
+         final String message = format("Instance %s was not created correctly. The associated resources created for it will be destroyed", instanceRequest.getInstanceId());
+         logger.warn(message);
+         cleanupResources.cleanupNode(RegionAndId.create(regionId, instanceRequest.getInstanceId()));
+         cleanupResources.cleanupSecurityGroupIfOrphaned(regionId, securityGroupId);
+      }
+
+      api.instanceApi().allocatePublicIpAddress(regionId, instanceRequest.getInstanceId());
+      api.instanceApi().powerOn(instanceRequest.getInstanceId());
+      Instance instance = Iterables.get(api.instanceApi().list(regionId, ListInstancesOptions.Builder.instanceIds(instanceRequest.getInstanceId())), 0);
+
+      // Safe to pass null credentials here, as jclouds will default populate
+      // the node with the default credentials from the image, or the ones in
+      // the options, if provided.
+      return new NodeAndInitialCredentials(instance,
+              slashEncodeRegionAndId(regionId, instanceRequest.getInstanceId()), null);
+   }
+
+   @Override
+   public Iterable<InstanceType> listHardwareProfiles() {
+      final ImmutableSet.Builder<String> instanceTypeIdsBuilder = ImmutableSet.builder();
+      for (String regionId : getAvailableLocationNames()) {
+         instanceTypeIdsBuilder.addAll(getInstanceTypeIds(regionId));
+      }
+      final Set<String> ids = instanceTypeIdsBuilder.build();
+
+      List<InstanceType> instanceTypes = FluentIterable.from(api.instanceApi().listTypes())
+              .filter(new Predicate<InstanceType>() {
+                 @Override
+                 public boolean apply(@Nullable InstanceType input) {
+                    return contains(ids, input.id());
+                 }
+              }).toList();
+
+      return instanceTypes;
+   }
+
+   private List<String> getInstanceTypeIds(String regionId) {
+      List<String> instanceTypeIds = Lists.newArrayList();
+      for (AvailableZone availableZone : api.instanceApi().listInstanceTypesByAvailableZone(regionId)) {
+         for (AvailableResource availableResource : availableZone.availableResources().get("AvailableResource")) {
+            for (SupportedResource supportedResource : availableResource.supportedResources()
+                    .get("SupportedResource")) {
+               if (SupportedResource.Status.AVAILABLE == supportedResource.status()) {
+                  instanceTypeIds.add(supportedResource.value());
+               }
+            }
+         }
+      }
+      return instanceTypeIds;
+   }
+
+   @Override
+   public Iterable<ImageInRegion> listImages() {
+      final ImmutableList.Builder<ImageInRegion> imagesInRegion = ImmutableList.builder();
+
+      for (final String regionId : getAvailableLocationNames()) {
+               imagesInRegion.addAll(api.imageApi().list(regionId).concat()
+                       .transform(new Function<Image, ImageInRegion>() {
+                           @Override
+                           public ImageInRegion apply(Image image) {
+                              return ImageInRegion.create(regionId, image);
+                           }
+                        })
+               );
+      }
+      return imagesInRegion.build();
+   }
+
+   @Override
+   public ImageInRegion getImage(final String id) {
+      RegionAndId regionAndId = fromSlashEncoded(id);
+      Image image = api.imageApi().list(regionAndId.regionId(), ListImagesOptions.Builder.imageIds(regionAndId.id()))
+              .firstMatch(Predicates.<Image>notNull())
+              .orNull();
+      if (image == null) return null;
+      return ImageInRegion.create(regionAndId.regionId(), image);
+   }
+
+   @Override
+   public Iterable<Region> listLocations() {
+      return FluentIterable.from(api.regionAndZoneApi().describeRegions()).filter(new Predicate<Region>() {
+         @Override
+         public boolean apply(Region region) {
+            return regionIds.get().isEmpty() ? true : regionIds.get().contains(region.id());
+         }
+      }).toList();
+   }
+
+   @Override
+   public Instance getNode(final String id) {
+      RegionAndId regionAndId = fromSlashEncoded(id);
+      return api.instanceApi().list(regionAndId.regionId(),
+              ListInstancesOptions.Builder.instanceIds(regionAndId.id()))
+              .firstMatch(Predicates.<Instance>notNull())
+              .orNull();
+   }
+
+   @Override
+   public void destroyNode(String id) {
+      checkState(cleanupResources.cleanupNode(RegionAndId.fromSlashEncoded(id)), "server(%s) and its resources still there after deleting!?", id);
+   }
+
+   @Override
+   public void rebootNode(String id) {
+      api.instanceApi().reboot(id);
+   }
+
+   @Override
+   public void resumeNode(String id) {
+      api.instanceApi().powerOn(id);
+   }
+
+   @Override
+   public void suspendNode(String id) {
+      api.instanceApi().powerOff(id);
+   }
+
+   @Override
+   public Iterable<Instance> listNodes() {
+      final ImmutableList.Builder<Instance> instances = ImmutableList.builder();
+      for (String regionId : getAvailableLocationNames()) {
+         instances.addAll(api.instanceApi().list(regionId).concat());
+      }
+      return instances.build();
+   }
+
+   @Override
+   public Iterable<Instance> listNodesByIds(final Iterable<String> ids) {
+
+      final ImmutableList.Builder<Instance> instancesBuilder = ImmutableList.builder();
+      for (String regionId : getAvailableLocationNames()) {
+         instancesBuilder.addAll(api.instanceApi().list(regionId, ListInstancesOptions.Builder.instanceIds(Iterables.toArray(ids, String.class))));
+      }
+      return instancesBuilder.build();
+   }
+
+   private List<String> getAvailableLocationNames() {
+      return newArrayList(
+              Iterables.transform(listLocations(), new Function<Region, String>() {
+                 @Override
+                 public String apply(Region location) {
+                    return location.id();
+                 }
+              }));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java
new file mode 100644
index 0000000..ace98e9
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java
@@ -0,0 +1,160 @@
+/*
+ * 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.aliyun.ecs.compute.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.ECSComputeService;
+import org.jclouds.aliyun.ecs.compute.ECSComputeServiceAdapter;
+import org.jclouds.aliyun.ecs.compute.functions.ImageInRegionToImage;
+import org.jclouds.aliyun.ecs.compute.functions.InstanceStatusToStatus;
+import org.jclouds.aliyun.ecs.compute.functions.InstanceToNodeMetadata;
+import org.jclouds.aliyun.ecs.compute.functions.InstanceTypeToHardware;
+import org.jclouds.aliyun.ecs.compute.functions.RegionToLocation;
+import org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.aliyun.ecs.domain.Region;
+import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions;
+import org.jclouds.aliyun.ecs.predicates.InstanceStatusPredicate;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatement;
+import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatementWithoutPublicKey;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.domain.Location;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+import static org.jclouds.util.Predicates2.retry;
+
+public class ECSServiceContextModule extends ComputeServiceAdapterContextModule<Instance, InstanceType, ImageInRegion, Region> {
+
+   @Override
+   protected void configure() {
+      super.configure();
+
+      bind(new TypeLiteral<ComputeServiceAdapter<Instance, InstanceType, ImageInRegion, Region>>() {
+      }).to(ECSComputeServiceAdapter.class);
+      bind(ComputeService.class).to(ECSComputeService.class);
+
+      bind(new TypeLiteral<Function<Instance, NodeMetadata>>() {
+      }).to(InstanceToNodeMetadata.class);
+      bind(new TypeLiteral<Function<InstanceType, Hardware>>() {
+      }).to(InstanceTypeToHardware.class);
+      bind(new TypeLiteral<Function<ImageInRegion, org.jclouds.compute.domain.Image>>() {
+      }).to(ImageInRegionToImage.class);
+      bind(new TypeLiteral<Function<Region, Location>>() {
+      }).to(RegionToLocation.class);
+      bind(new TypeLiteral<Function<Instance.Status, NodeMetadata.Status>>() {
+      }).to(InstanceStatusToStatus.class);
+      install(new LocationsFromComputeServiceAdapterModule<Instance, InstanceType, ImageInRegion, Region>() {
+      });
+      bind(TemplateOptions.class).to(ECSServiceTemplateOptions.class);
+      bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourcesThenCreateNodes.class);
+      bind(NodeAndTemplateOptionsToStatement.class).to(NodeAndTemplateOptionsToStatementWithoutPublicKey.class);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_RUNNING)
+   protected Predicate<String> provideInstanceRunningPredicate(final ECSComputeServiceApi api,
+                                                               ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new InstanceInStatusPredicate(api, InstanceStatus.Status.RUNNING), timeouts.nodeRunning,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_SUSPENDED)
+   protected Predicate<String> provideInstanceSuspendedPredicate(final ECSComputeServiceApi api,
+                                                                 ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new InstanceInStatusPredicate(api, InstanceStatus.Status.STOPPED), timeouts.nodeSuspended,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_TERMINATED)
+   protected Predicate<String> provideInstanceTerminatedPredicate(final ECSComputeServiceApi api,
+                                                                  ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new InstanceTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
+              pollPeriod.pollMaxPeriod);
+   }
+
+   @VisibleForTesting
+   static class InstanceInStatusPredicate implements Predicate<String> {
+
+      private final ECSComputeServiceApi api;
+      private final InstanceStatus.Status desiredStatus;
+
+      public InstanceInStatusPredicate(ECSComputeServiceApi api, InstanceStatus.Status desiredStatus) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.desiredStatus = checkNotNull(desiredStatus, "instance status must not be null");
+      }
+
+      @Override
+      public boolean apply(String id) {
+         checkNotNull(id, "id");
+         RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+         String regionId = regionAndId.regionId();
+         String instanceId = regionAndId.id();
+         InstanceStatus instanceStatus = api.instanceApi().listInstanceStatus(regionId)
+                 .concat()
+                 .firstMatch(new InstanceStatusPredicate(instanceId))
+                 .orNull();
+         return instanceStatus != null && desiredStatus == instanceStatus.status();
+      }
+   }
+
+   @VisibleForTesting
+   static class InstanceTerminatedPredicate implements Predicate<String> {
+
+      private final ECSComputeServiceApi api;
+
+      public InstanceTerminatedPredicate(ECSComputeServiceApi api) {
+         this.api = checkNotNull(api, "api must not be null");
+      }
+
+      @Override
+      public boolean apply(String id) {
+         checkNotNull(id, "id");
+         RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+         String regionId = regionAndId.regionId();
+         final String instanceId = regionAndId.id();
+         InstanceStatus instanceStatus = api.instanceApi().listInstanceStatus(regionId)
+                 .concat()
+                 .firstMatch(new InstanceStatusPredicate(instanceId))
+                 .orNull();
+         return instanceStatus == null;
+      }
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java
new file mode 100644
index 0000000..72c45cf
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+import org.jclouds.aliyun.ecs.compute.functions.internal.OperatingSystems;
+import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.domain.Location;
+import org.jclouds.location.predicates.LocationPredicates;
+
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Iterables.tryFind;
+import static java.util.Arrays.asList;
+import static org.jclouds.compute.domain.OperatingSystem.builder;
+
+public class ImageInRegionToImage implements Function<ImageInRegion, Image> {
+
+   private final Supplier<Set<? extends Location>> locations;
+
+   private static final Map<String, OsFamily> OTHER_OS_MAP = ImmutableMap.<String, OsFamily>builder()
+         .put("Aliyun", OsFamily.LINUX).build();
+
+   private static Optional<OsFamily> findInStandardFamilies(final String platform) {
+      return tryFind(asList(OsFamily.values()), new Predicate<OsFamily>() {
+         @Override
+         public boolean apply(OsFamily input) {
+            return platform.toUpperCase().startsWith(input.name());
+         }
+      });
+   }
+
+   private static Optional<OsFamily> findInOtherOSMap(final String label) {
+      return tryFind(OTHER_OS_MAP.keySet(), new Predicate<String>() {
+         @Override
+         public boolean apply(String input) {
+            return label.contains(input);
+         }
+      }).transform(new Function<String, OsFamily>() {
+         @Override
+         public OsFamily apply(String input) {
+            return OTHER_OS_MAP.get(input);
+         }
+      });
+   }
+
+   @Inject
+   ImageInRegionToImage(@Memoized Supplier<Set<? extends Location>> locations) {
+      this.locations = locations;
+   }
+
+   @Override
+   public Image apply(ImageInRegion from) {
+      ImageBuilder builder = new ImageBuilder();
+      builder.id(RegionAndId.slashEncodeRegionAndId(from.regionId(), from.image().id()));
+      builder.providerId(from.image().id());
+      builder.name(from.image().name());
+      builder.description(from.image().description());
+      builder.status(from.image().status() == org.jclouds.aliyun.ecs.domain.Image.Status.AVAILABLE ?
+              Image.Status.AVAILABLE : Image.Status.PENDING);
+
+      OsFamily family = findInStandardFamilies(from.image().platform())
+              .or(findInOtherOSMap(from.image().platform()))
+              .or(OsFamily.UNRECOGNIZED);
+
+      String osVersion = OperatingSystems.version().apply(from.image());
+
+      builder.operatingSystem(
+            builder().name(from.image().osName()).family(family)
+                    .description(from.image().description())
+                    .version(osVersion)
+                  .is64Bit("x86_64".equals(from.image().architecture()) ? true : false).build());
+
+      builder.location(from(locations.get()).firstMatch(LocationPredicates.idEquals(from.regionId())).orNull());
+      return builder.build();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java
new file mode 100644
index 0000000..8fcaf18
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.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.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.compute.domain.NodeMetadata;
+
+import javax.inject.Singleton;
+
+/**
+ * Transforms an {@link Instance.Status} to the jclouds portable model.
+ */
+@Singleton
+public class InstanceStatusToStatus implements Function<Instance.Status, NodeMetadata.Status> {
+
+   private static final Function<Instance.Status, NodeMetadata.Status> toPortableStatus = Functions.forMap(
+         ImmutableMap.<Instance.Status, NodeMetadata.Status>builder()
+                 .put(Instance.Status.STARTING, NodeMetadata.Status.PENDING)
+                 .put(Instance.Status.STOPPING, NodeMetadata.Status.PENDING)
+                 .put(Instance.Status.STOPPED, NodeMetadata.Status.SUSPENDED)
+                 .put(Instance.Status.RUNNING, NodeMetadata.Status.RUNNING).build(), NodeMetadata.Status.UNRECOGNIZED);
+
+   @Override
+   public NodeMetadata.Status apply(Instance.Status input) {
+      return toPortableStatus.apply(input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java
new file mode 100644
index 0000000..1ead7ce
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java
@@ -0,0 +1,126 @@
+/*
+ * 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.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.location.predicates.LocationPredicates;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.FluentIterable.from;
+import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromValuesOfEmptyString;
+
+/**
+ * Transforms an {@link Instance} to the jclouds portable model.
+ */
+@Singleton
+public class InstanceToNodeMetadata implements Function<Instance, NodeMetadata> {
+
+   private final Supplier<Map<String, ? extends Image>> images;
+   private final Supplier<Map<String, ? extends Hardware>> hardwares;
+   private final Supplier<Set<? extends Location>> locations;
+   private final Function<Instance.Status, NodeMetadata.Status> toPortableStatus;
+   private final GroupNamingConvention groupNamingConvention;
+
+   @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   InstanceToNodeMetadata(Supplier<Map<String, ? extends Image>> images,
+                          Supplier<Map<String, ? extends Hardware>> hardwares,
+                          @Memoized Supplier<Set<? extends Location>> locations,
+                          Function<Instance.Status, NodeMetadata.Status> toPortableStatus,
+                          GroupNamingConvention.Factory groupNamingConvention) {
+      this.images = images;
+      this.hardwares = hardwares;
+      this.locations = locations;
+      this.toPortableStatus = toPortableStatus;
+      this.groupNamingConvention = groupNamingConvention.createWithoutPrefix();
+   }
+
+   @Override
+   public NodeMetadata apply(Instance from) {
+      NodeMetadataBuilder builder = new NodeMetadataBuilder();
+
+      Optional<? extends Image> image = findImage(from.imageId());
+      if (image.isPresent()) {
+         builder.imageId(image.get().getId());
+         builder.operatingSystem(image.get().getOperatingSystem());
+      } else {
+         logger.info(">> image with id %s for instance %s was not found. "
+                     + "This might be because the image that was used to create the instance has a new id.",
+               from.instanceType(), from.id());
+      }
+      Optional<? extends Hardware> hardware = findHardware(from.instanceType());
+      if (hardware.isPresent()) {
+         builder.hardware(hardware.get());
+      } else {
+         logger.info(">> hardware with id %s for instance %s was not found. "
+                         + "This might be because the image that was used to create the instance has a new id.",
+                 from.instanceType(), from.id());
+      }
+
+      builder.id(RegionAndId.slashEncodeRegionAndId(from.regionId(), from.id()));
+      builder.providerId(from.id());
+      builder.name(from.name());
+      builder.hostname(String.format("%s", from.hostname()));
+      builder.group(groupNamingConvention.extractGroup(from.name()));
+      builder.status(toPortableStatus.apply(from.status()));
+      builder.privateAddresses(from.innerIpAddress().entrySet().iterator().next().getValue());
+      builder.publicAddresses(from.publicIpAddress().entrySet().iterator().next().getValue());
+      builder.location(from(locations.get()).firstMatch(LocationPredicates.idEquals(from.regionId())).orNull());
+      if (from.tags() != null && !from.tags().isEmpty()) {
+         ImmutableMap.Builder tagsBuilder = new ImmutableMap.Builder();
+         for (Tag tag : from.tags().entrySet().iterator().next().getValue()) {
+            tagsBuilder.put(tag.key(), tag.value());
+         }
+         addMetadataAndParseTagsFromValuesOfEmptyString(builder, tagsBuilder.build());
+      }
+
+      NodeMetadata nodeMetadata = builder.build();
+      return nodeMetadata;
+   }
+
+   private Optional<? extends Image> findImage(String imageId) {
+      return Optional.fromNullable(images.get().get(imageId));
+   }
+
+   private Optional<? extends Hardware> findHardware(String instanceType) {
+      return Optional.fromNullable(hardwares.get().get(instanceType));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java
new file mode 100644
index 0000000..8197979
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+
+import javax.inject.Singleton;
+
+@Singleton
+public class InstanceTypeToHardware implements Function<InstanceType, Hardware> {
+
+   private static final int GB_TO_MB_MULTIPLIER = 1024;
+
+   @Override
+   public Hardware apply(InstanceType input) {
+      HardwareBuilder builder = new HardwareBuilder()
+              .ids(input.id())
+              .name(input.id())
+              .hypervisor("none")
+              .processors(getProcessors(input.cpuCoreCount()))
+              .ram(input.memorySize().intValue() * GB_TO_MB_MULTIPLIER);
+      return builder.build();
+   }
+
+   private Iterable<Processor> getProcessors(Integer cpuCoreCount) {
+      // No cpu speed from API, so assume more cores == faster
+      return ImmutableList.of(new Processor(cpuCoreCount, cpuCoreCount));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java
new file mode 100644
index 0000000..2bca519
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.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.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import org.jclouds.aliyun.ecs.domain.Region;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.location.suppliers.all.JustProvider;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+/**
+ * Transforms an {@link Region} to the jclouds portable model.
+ */
+@Singleton
+public class RegionToLocation implements Function<Region, Location> {
+
+   private final JustProvider justProvider;
+
+   // allow us to lazy discover the provider of a resource
+   @Inject
+   RegionToLocation(JustProvider justProvider) {
+      this.justProvider = justProvider;
+   }
+
+   @Override
+   public Location apply(final Region region) {
+      final LocationBuilder builder = new LocationBuilder();
+      builder.id(region.id());
+      builder.description(region.localName());
+      builder.parent(getOnlyElement(justProvider.get()));
+      builder.scope(LocationScope.REGION);
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java
new file mode 100644
index 0000000..93681b2
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java
@@ -0,0 +1,51 @@
+/*
+ * 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.aliyun.ecs.compute.functions.internal;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import org.jclouds.aliyun.ecs.domain.Image;
+import org.jclouds.compute.domain.OsFamily;
+
+public class OperatingSystems {
+
+   public static Function<Image, String> version() {
+      return new Function<Image, String>() {
+         @Override
+         public String apply(final Image image) {
+            return parseVersion(image);
+         }
+      };
+   }
+
+   private static String parseVersion(Image image) {
+      String sequence = image.osName().trim().replaceAll("\\s+", " ");
+      int offset = 2;
+      if (isWindows(image)) {
+         sequence = image.platform();
+         offset = 1;
+      }
+      Iterable<String> splitted = Splitter.on(" ").split(sequence);
+      return Iterables.get(splitted, Iterables.size(splitted) - offset);
+   }
+
+   public static boolean isWindows(Image image) {
+      return image.platform().toUpperCase().startsWith(OsFamily.WINDOWS.name());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java
new file mode 100644
index 0000000..c378cbd
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.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.aliyun.ecs.compute.options;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.jclouds.compute.options.TemplateOptions;
+
+import static com.google.common.base.Objects.equal;
+
+/**
+ * Custom options for the Alibaba Elastic Compute Service API.
+ */
+public class ECSServiceTemplateOptions extends TemplateOptions implements Cloneable {
+
+   private String keyPairName = "";
+   private String vSwitchId = "";
+   private String internetChargeType = "PayByTraffic";
+   private String instanceChargeType = "PostPaid";
+   private int internetMaxBandwidthOut = 5;
+
+   public ECSServiceTemplateOptions keyPairName(String keyPairName) {
+      this.keyPairName = keyPairName;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions vSwitchId(String vSwitchId) {
+      this.vSwitchId = vSwitchId;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions internetChargeType(String internetChargeType) {
+      this.internetChargeType = internetChargeType;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions instanceChargeType(String instanceChargeType) {
+      this.instanceChargeType = instanceChargeType;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) {
+      this.internetMaxBandwidthOut = internetMaxBandwidthOut;
+      return this;
+   }
+
+   public String getKeyPairName() {
+      return keyPairName;
+   }
+
+   public String getVSwitchId() {
+      return vSwitchId;
+   }
+
+   public String getInternetChargeType() {
+      return internetChargeType;
+   }
+
+   public String getInstanceChargeType() {
+      return instanceChargeType;
+   }
+
+   public int getInternetMaxBandwidthOut() {
+      return internetMaxBandwidthOut;
+   }
+
+   @Override
+   public ECSServiceTemplateOptions clone() {
+      ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+      copyTo(options);
+      return options;
+   }
+
+   @Override
+   public void copyTo(TemplateOptions to) {
+      super.copyTo(to);
+      if (to instanceof ECSServiceTemplateOptions) {
+         ECSServiceTemplateOptions eTo = ECSServiceTemplateOptions.class.cast(to);
+         eTo.keyPairName(keyPairName);
+         eTo.vSwitchId(vSwitchId);
+         eTo.internetChargeType(internetChargeType);
+         eTo.instanceChargeType(instanceChargeType);
+         eTo.internetMaxBandwidthOut(internetMaxBandwidthOut);
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), keyPairName, vSwitchId, internetChargeType, instanceChargeType, internetMaxBandwidthOut);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (getClass() != obj.getClass()) {
+         return false;
+      }
+      ECSServiceTemplateOptions other = (ECSServiceTemplateOptions) obj;
+      return super.equals(other) &&
+              equal(this.keyPairName, other.keyPairName) &&
+              equal(this.vSwitchId, other.vSwitchId) &&
+              equal(this.internetChargeType, other.internetChargeType) &&
+              equal(this.instanceChargeType, other.instanceChargeType) &&
+              equal(this.internetMaxBandwidthOut, other.internetMaxBandwidthOut);
+   }
+
+   @Override
+   public MoreObjects.ToStringHelper string() {
+      MoreObjects.ToStringHelper toString = super.string().omitNullValues();
+      toString.add("keyPairName", keyPairName);
+      toString.add("vSwitchId", vSwitchId);
+      toString.add("internetChargeType", internetChargeType);
+      toString.add("instanceChargeType", instanceChargeType);
+      toString.add("internetMaxBandwidthOut", internetMaxBandwidthOut);
+      return toString;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see ECSServiceTemplateOptions#keyPairName
+       */
+      public static ECSServiceTemplateOptions keyPairName(String keyPairName) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.keyPairName(keyPairName);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#vSwitchId
+       */
+      public static ECSServiceTemplateOptions vSwitchId(String vSwitchId) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.vSwitchId(vSwitchId);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#internetChargeType
+       */
+      public static ECSServiceTemplateOptions internetChargeType(String internetChargeType) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.internetChargeType(internetChargeType);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#instanceChargeType
+       */
+      public static ECSServiceTemplateOptions instanceChargeType(String instanceChargeType) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.instanceChargeType(instanceChargeType);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#internetMaxBandwidthOut
+       */
+      public static ECSServiceTemplateOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.internetMaxBandwidthOut(internetMaxBandwidthOut);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java
new file mode 100644
index 0000000..8fb5730
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java
@@ -0,0 +1,112 @@
+/*
+ * 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.aliyun.ecs.compute.strategy;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.aliyun.ecs.predicates.InstanceStatusPredicate;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.List;
+
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+
+/**
+ * This utility takes care of cleaning up all the resources created to deploy the node
+ *
+ * Specifically, it tries to delete the security group created for the group of nodes.
+ * In case a VPC_PREFIX and a vSwitch were created for the node, it tries to remove them
+ */
+@Singleton
+public class CleanupResources {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ECSComputeServiceApi api;
+   private final Predicate<String> instanceSuspendedPredicate;
+   private final Predicate<String> instanceTerminatedPredicate;
+
+   @Inject
+   public CleanupResources(ECSComputeServiceApi api,
+                           @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> instanceSuspendedPredicate,
+                           @Named(TIMEOUT_NODE_TERMINATED) Predicate<String> instanceTerminatedPredicate
+   ) {
+      this.api = api;
+      this.instanceSuspendedPredicate = instanceSuspendedPredicate;
+      this.instanceTerminatedPredicate = instanceTerminatedPredicate;
+   }
+
+   /**
+    * @param regionAndId
+    * @return whether the node and its resources have been deleted
+    */
+   public boolean cleanupNode(final RegionAndId regionAndId) {
+      String instanceId = regionAndId.id();
+      InstanceStatus instanceStatus = Iterables.tryFind(api.instanceApi().listInstanceStatus(regionAndId.regionId()).concat(),
+              new InstanceStatusPredicate(instanceId)).orNull();
+      if (instanceStatus == null) return true;
+      if (InstanceStatus.Status.STOPPED != instanceStatus.status()) {
+         logger.debug(">> powering off %s ...", RegionAndId.slashEncodeRegionAndId(regionAndId));
+         api.instanceApi().powerOff(instanceId);
+         instanceSuspendedPredicate.apply(RegionAndId.slashEncodeRegionAndId(regionAndId));
+      }
+      logger.debug(">> destroying %s ...", RegionAndId.slashEncodeRegionAndId(regionAndId));
+      api.instanceApi().delete(instanceId);
+      return instanceTerminatedPredicate.apply(RegionAndId.slashEncodeRegionAndId(regionAndId));
+   }
+
+   public List<SecurityGroup> findOrphanedSecurityGroups(final String regionId, final String group) {
+      return api.securityGroupApi().list(regionId).concat().filter(new Predicate<SecurityGroup>() {
+         @Override
+         public boolean apply(@Nullable SecurityGroup input) {
+            List<Tag> actual = input.tags().entrySet().iterator().next().getValue();
+            List<Tag> expected = ImmutableList.of(
+                    Tag.create(Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE), Tag.create(Tag.GROUP, group)
+            );
+            return actual.containsAll(expected) && expected.containsAll(actual);
+         }
+      }).toList();
+   }
+
+   public boolean cleanupSecurityGroupIfOrphaned(final String regionId, String securityGroupId) {
+      return api.securityGroupApi().delete(regionId, securityGroupId) != null;
+   }
+
+   public boolean cleanupVSwitchIfOrphaned(final String regionId, String vSwitchId) {
+      return api.vSwitchApi().delete(regionId, vSwitchId) != null;
+   }
+
+   public boolean cleanupVPCIfOrphaned(final String regionId, String vpcId) {
+      return api.vpcApi().delete(regionId, vpcId) != null;
+   }
+
+}