[JCLOUDS-1430] Aliyun ECS initial skeleton
diff --git a/aliyun-ecs/pom.xml b/aliyun-ecs/pom.xml
new file mode 100644
index 0000000..da1b900
--- /dev/null
+++ b/aliyun-ecs/pom.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.jclouds.labs</groupId>
+        <artifactId>jclouds-labs</artifactId>
+        <version>2.2.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>aliyun-ecs</artifactId>
+    <name>jclouds Alibaba Elastic Compute Service API</name>
+    <description>jclouds components to access an implementation of Alibaba Elastic Compute Service</description>
+    <packaging>bundle</packaging>
+
+    <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/>
+
+        <jclouds.osgi.export>org.jclouds.aliyun.ecs.*;version="${project.version}"</jclouds.osgi.export>
+        <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import>
+        <jclouds.osgi.dynamic>*</jclouds.osgi.dynamic>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.jclouds</groupId>
+            <artifactId>jclouds-compute</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.jclouds</groupId>
+            <artifactId>jclouds-core</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jclouds</groupId>
+            <artifactId>jclouds-compute</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jclouds.driver</groupId>
+            <artifactId>jclouds-sshj</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jclouds.driver</groupId>
+            <artifactId>jclouds-slf4j</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <!-- provided by the jclouds-bouncycastle driver -->
+                <exclusion>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15on</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.google.auto.value</groupId>
+            <artifactId>auto-value</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.auto.service</groupId>
+            <artifactId>auto-service</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>live</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>integration</id>
+                                <phase>integration-test</phase>
+                                <goals>
+                                    <goal>test</goal>
+                                </goals>
+                                <configuration>
+                                    <systemPropertyVariables>
+                                        <test.aliyun-ecs.endpoint>${test.aliyun-ecs.endpoint}</test.aliyun-ecs.endpoint>
+                                        <test.aliyun-ecs.api-version>${test.aliyun-ecs.api-version}
+                                        </test.aliyun-ecs.api-version>
+                                        <test.aliyun-ecs.build-version>${test.aliyun-ecs.build-version}
+                                        </test.aliyun-ecs.build-version>
+                                        <test.aliyun-ecs.identity>${test.aliyun-ecs.identity}</test.aliyun-ecs.identity>
+                                        <test.aliyun-ecs.credential>${test.aliyun-ecs.credential}
+                                        </test.aliyun-ecs.credential>
+                                        <test.aliyun-ecs.template>${test.aliyun-ecs.template}</test.aliyun-ecs.template>
+                                    </systemPropertyVariables>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+
+</project>
+
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
new file mode 100644
index 0000000..268d773
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs;
+
+import org.jclouds.aliyun.ecs.features.ImageApi;
+import org.jclouds.rest.annotations.Delegate;
+
+import java.io.Closeable;
+
+public interface ECSComputeServiceApi extends Closeable {
+
+   @Delegate
+   ImageApi imageApi();
+
+}
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
new file mode 100644
index 0000000..61b4df9
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs;
+
+import com.google.auto.service.AutoService;
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.providers.internal.BaseProviderMetadata;
+
+import java.net.URI;
+import java.util.Properties;
+
+@AutoService(ProviderMetadata.class)
+public class ECSComputeServiceProviderMetadata extends BaseProviderMetadata {
+
+   public ECSComputeServiceProviderMetadata() {
+      super(builder());
+   }
+
+   public ECSComputeServiceProviderMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static Properties defaultProperties() {
+      final Properties properties = ECSServiceApiMetadata.defaultProperties();
+      return properties;
+   }
+
+   @Override
+   public Builder toBuilder() {
+      return builder().fromProviderMetadata(this);
+   }
+
+   public static class Builder extends BaseProviderMetadata.Builder {
+
+      protected Builder() {
+         id("aliyun-ecs")
+               .name("Alibaba Elastic Compute Service")
+               .apiMetadata(new ECSServiceApiMetadata())
+               .homepage(URI.create("https://www.alibabacloud.com"))
+               .console(URI.create("https://ecs.console.aliyun.com"))
+               .endpoint("https://ecs.aliyuncs.com")
+               .iso3166Codes("US-CA", "US-VA", "DE", "JP", "ID-JK", "SG", "IN", "AU-NSW", "MY", "CN-HE", "CN-SH", "CN-ZJ", "CN-GD", "HK", "AE-DU") // TODO
+               .defaultProperties(ECSServiceApiMetadata.defaultProperties());
+      }
+
+      @Override
+      public ECSComputeServiceProviderMetadata build() {
+         return new ECSComputeServiceProviderMetadata(this);
+      }
+
+      @Override
+      public Builder fromProviderMetadata(ProviderMetadata in) {
+         super.fromProviderMetadata(in);
+         return this;
+      }
+   }
+}
+
+
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
new file mode 100644
index 0000000..d81ef96
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+import org.jclouds.aliyun.ecs.config.ECSComputeServiceHttpApiModule;
+import org.jclouds.aliyun.ecs.config.ECSComputeServiceParserModule;
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import java.net.URI;
+import java.util.Properties;
+
+import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.reflect.Reflection2.typeToken;
+
+public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeServiceApi> {
+
+   public static final String DEFAULT_API_VERSION = "2014-05-26";
+
+   public ECSServiceApiMetadata() {
+      this(new Builder());
+   }
+
+   protected ECSServiceApiMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Properties defaultProperties() {
+      Properties properties = BaseHttpApiMetadata.defaultProperties();
+      properties.put(TEMPLATE, "osFamily=CENTOS,os64Bit=true,osVersionMatches=7.4");
+      properties.put(TIMEOUT_NODE_RUNNING, 900000); // 15 mins
+      properties.put(TIMEOUT_NODE_SUSPENDED, 900000); // 15 mins
+      return properties;
+   }
+
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromApiMetadata(this);
+   }
+
+   public static class Builder extends BaseHttpApiMetadata.Builder<ECSComputeServiceApi, Builder> {
+
+      protected Builder() {
+         id("aliyun-ecs")
+                 .name("Alibaba Elastic Compute Service API")
+                 .identityName("user name")
+                 .credentialName("user password")
+                 .version(DEFAULT_API_VERSION)
+                 .documentation(URI.create("https://www.alibabacloud.com/help"))
+                 .defaultEndpoint("https://ecs.aliyuncs.com")
+                 .defaultProperties(ECSServiceApiMetadata.defaultProperties())
+                 .view(typeToken(ComputeServiceContext.class))
+                 .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
+                         .add(ECSComputeServiceHttpApiModule.class)
+                         .add(ECSComputeServiceParserModule.class)
+                         .build());
+      }
+
+      @Override
+      public ECSServiceApiMetadata build() {
+         return new ECSServiceApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+
+      @Override
+      public Builder fromApiMetadata(ApiMetadata in) {
+         return this;
+      }
+   }
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java
new file mode 100644
index 0000000..05ebc9f
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java
@@ -0,0 +1,38 @@
+/*
+ * 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.config;
+
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.handlers.ECSComputeServiceErrorHandler;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.HttpApiModule;
+
+@ConfiguresHttpApi
+public class ECSComputeServiceHttpApiModule extends HttpApiModule<ECSComputeServiceApi> {
+
+   @Override
+   protected void bindErrorHandlers() {
+      bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ECSComputeServiceErrorHandler.class);
+      bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ECSComputeServiceErrorHandler.class);
+      bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ECSComputeServiceErrorHandler.class);
+   }
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java
new file mode 100644
index 0000000..3e63fd1
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.config;
+
+import com.google.common.base.CharMatcher;
+import com.google.gson.stream.JsonReader;
+import com.google.inject.AbstractModule;
+import org.jclouds.date.DateService;
+import org.jclouds.json.config.GsonModule;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.Date;
+
+public class ECSComputeServiceParserModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      bind(GsonModule.DateAdapter.class).to(AliyunDateAdapter.class);
+   }
+
+   /**
+    * Data adapter for the date formats used by Aliyun.
+    * <p>
+    * Essentially this is a workaround for the Aliyun getUsage() API call returning a corrupted form of ISO-8601
+    * dates, which doesn't have seconds 2018-06-20T13:39Z
+    */
+   public static class AliyunDateAdapter extends GsonModule.Iso8601DateAdapter {
+
+      @Inject
+      AliyunDateAdapter(DateService dateService) {
+         super(dateService);
+      }
+
+      public Date read(JsonReader reader) throws IOException {
+         String date = reader.nextString();
+         int count = CharMatcher.is(':').countIn(date);
+         if (count < 2) {
+            date = date.replaceAll("Z", ":00Z");
+         }
+         return parseDate(date);
+      }
+
+   }
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java
new file mode 100644
index 0000000..1e257e1
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java
@@ -0,0 +1,37 @@
+/*
+ * 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.domain;
+
+import com.google.auto.value.AutoValue;
+import org.jclouds.json.SerializedNames;
+
+@AutoValue
+public abstract class DiskDeviceMapping {
+
+   DiskDeviceMapping() {
+   }
+
+   @SerializedNames({"Device", "Size"})
+   public static DiskDeviceMapping create(String device, String size) {
+      return new AutoValue_DiskDeviceMapping(device, size);
+   }
+
+   public abstract String device();
+
+   public abstract String size();
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java
new file mode 100644
index 0000000..a65bb8b
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java
@@ -0,0 +1,94 @@
+/*
+ * 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.domain;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.json.SerializedNames;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@AutoValue
+public abstract class Image {
+
+   Image() {}
+
+   @SerializedNames({"ImageId", "Description", "ProductCode", "OSType", "Architecture", "OSName", "DiskDeviceMappings",
+           "ImageOwnerAlias", "Progress", "IsSupportCloudinit", "Usage", "CreationTime", "Tags",
+           "ImageVersion", "Status", "ImageName", "IsSupportIoOptimized", "IsSelfShared", "IsCopied",
+           "IsSubscribed", "Platform", "Size"})
+   public static Image create(String imageId, String description, String productCode, String osType,
+                              String architecture, String osName, Map<String, List<DiskDeviceMapping>> diskDeviceMappings,
+                              String imageOwnerAlias, String progress, Boolean isSupportCloudinit, String usage, Date creationTime,
+                              Map<String, List<Tag>> tags, String imageVersion, String status, String imageName,
+                              Boolean isSupportIoOptimized, Boolean isSelfShared, Boolean isCopied, Boolean isSubscribed, String platform,
+                              String size) {
+      return new AutoValue_Image(imageId, description, productCode, osType, architecture, osName,
+              diskDeviceMappings == null ?
+                      ImmutableMap.<String, List<DiskDeviceMapping>>of() :
+                      ImmutableMap.copyOf(diskDeviceMappings), imageOwnerAlias, progress, isSupportCloudinit, usage,
+              creationTime, tags == null ? ImmutableMap.<String, List<Tag>>of() : ImmutableMap.copyOf(tags), imageVersion,
+              status, imageName, isSupportIoOptimized, isSelfShared, isCopied, isSubscribed, platform, size);
+   }
+
+   public abstract String imageId();
+
+   public abstract String description();
+
+   public abstract String productCode();
+
+   public abstract String osType();
+
+   public abstract String architecture();
+
+   public abstract String osName();
+
+   public abstract Map<String, List<DiskDeviceMapping>> diskDeviceMappings();
+
+   public abstract String imageOwnerAlias();
+
+   public abstract String progress();
+
+   public abstract Boolean isSupportCloudinit();
+
+   public abstract String usage();
+
+   public abstract Date creationTime();
+
+   public abstract Map<String, List<Tag>> tags();
+
+   public abstract String imageVersion();
+
+   public abstract String status();
+
+   public abstract String imageName();
+
+   public abstract Boolean isSupportIoOptimizeds();
+
+   public abstract Boolean isSelfShared();
+
+   public abstract Boolean isCopied();
+
+   public abstract Boolean isSubscribed();
+
+   public abstract String platform();
+
+   public abstract String size();
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java
new file mode 100644
index 0000000..b0e3af3
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain;
+
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+
+import java.beans.ConstructorProperties;
+import java.util.Map;
+
+/**
+ * A collection of Image
+ */
+public class Images extends PaginatedCollection<Image> {
+
+   @ConstructorProperties({ "Images", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+   public Images(Map<String, Iterable<Image>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+      super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+   }
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java
new file mode 100644
index 0000000..e0a89c1
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain;
+
+/**
+ * Enumeration of region names
+ */
+public enum Regions {
+
+   US_EAST_1("us-east-1", "US (Virginia)"),
+   US_WEST_1("us-west-1", "US West (Silicon Valley)"),
+   EU_CENTRAL_1("eu-central-1", "Germany (Frankfurt)"),
+   AP_NORTHEAST_1("ap-northeast-1", "Japan (Tokyo)"),
+   AP_SOUTH_1("ap-south-1", "India (Mumbai)"),
+   AP_SOUTHEAST_1("ap-southeast-1", "Singapore"),
+   AP_SOUTHEAST_2("ap-southeast-2", "Australia (Sydney)"),
+   AP_SOUTHEAST_3("ap-southeast-3", "Malaysia (Kuala Lumpur)"),
+   AP_SOUTHEAST_5("ap-southeast-5", "Indonesia (Jakarta)"),
+   CN_NORTH_1("cn-qingdao", "China (Qingdao)"),
+   CN_NORTH_2("cn-beijing", "China (Beijing)"),
+   CN_NORTH_3("cn-zhangjiakou", "China (Zhangjiakou)"),
+   CN_NORTH_5("cn-huhehaote", "China (Huhehaote)"),
+   CN_EAST_1("cn-hangzhou", "China (Hangzou)"),
+   CN_EAST_2("cn-shanghai", "China (Shanghai)"),
+   CN_SOUTH_1("cn-shenzhen", "China (Shenzhen)"),
+   CN_SOUTH_2("cn-hongkong", "China (Hongkong)"),
+   ME_EAST_1("me-east-1", "UAE (Dubai)");
+
+   private final String name;
+   private final String description;
+
+   Regions(String name, String description) {
+      this.name = name;
+      this.description = description;
+   }
+
+   /**
+    * The name of this region, used in the regions.xml file to identify it.
+    */
+   public String getName() {
+      return name;
+   }
+
+   /**
+    * Descriptive readable name for this region.
+    */
+   public String getDescription() {
+      return description;
+   }
+
+   /**
+    * Returns a region enum corresponding to the given region name.
+    *
+    * @param regionName
+    *            The name of the region. Ex.: eu-west-1
+    * @return Region enum representing the given region name.
+    */
+   public static Regions fromName(String regionName) {
+      for (Regions region : Regions.values()) {
+         if (region.getName().equals(regionName)) {
+            return region;
+         }
+      }
+      throw new IllegalArgumentException("Cannot create enum from " + regionName + " value!");
+   }
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java
new file mode 100644
index 0000000..1b34226
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain;
+
+import com.google.auto.value.AutoValue;
+import org.jclouds.json.SerializedNames;
+
+@AutoValue
+public abstract class Tag {
+
+   Tag() {}
+
+   @SerializedNames({ "TagKey", "TagValue" })
+   public static Tag create(String tagKey, String tagValue) {
+      return new AutoValue_Tag(tagKey, tagValue);
+   }
+
+   public abstract String tagKey();
+
+   public abstract String tagValue();
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java
new file mode 100644
index 0000000..6cffd72
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java
@@ -0,0 +1,109 @@
+/*
+ * 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.domain.internal;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.domain.options.PaginationOptions;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.javax.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Base class for a paginated collection in Aliyun ECS.
+ */
+public class PaginatedCollection<T> extends IterableWithMarker<T> {
+
+   private final Map<String, Iterable<T>> resources;
+
+   /**
+    * The page number requested by the user or the default page (1) if none supplied.
+    */
+   private final int pageNumber;
+
+   /**
+    * The total number of result records matching request criteria.
+    */
+   private final int totalCount;
+
+   /**
+    * The page size used; either specified in the request or the default for the API function being requested.
+    */
+   private final int pageSize;
+
+   private final String regionId;
+
+   private final String requestId;
+
+   protected PaginatedCollection(@Nullable Map<String, Iterable<T>> resources, int pageNumber, int totalCount,
+                                 int pageSize, String regionId, String requestId) {
+      this.resources = resources != null ? resources : ImmutableMap.<String, Iterable<T>>of();
+      this.pageNumber = pageNumber;
+      this.totalCount = totalCount;
+      this.pageSize = pageSize;
+      this.regionId = regionId;
+      this.requestId = requestId;
+   }
+
+   public Iterable<T> getResources() {
+      return resources.entrySet().iterator().next().getValue();
+   }
+
+   public int getPageNumber() {
+      return pageNumber;
+   }
+
+   public int getTotalCount() {
+      return totalCount;
+   }
+
+   public int getPageSize() {
+      return pageSize;
+   }
+
+   public String getRegionId() {
+      return regionId;
+   }
+
+   public String getRequestId() {
+      return requestId;
+   }
+
+   @Override
+   public Iterator<T> iterator() {
+      return resources.entrySet().iterator().next().getValue().iterator();
+   }
+
+   @Override
+   public Optional<Object> nextMarker() {
+      if (totalCount < pageSize) {
+         return Optional.absent();
+      }
+
+      if ((float) pageNumber < ((float) totalCount / (float) pageSize)) {
+         return Optional.of(toPaginationOptions(pageNumber + 1));
+      }
+      return Optional.absent();
+   }
+
+   private Object toPaginationOptions(Integer pageNumber) {
+      return PaginationOptions.Builder.pageNumber(pageNumber);
+   }
+}
+
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java
new file mode 100644
index 0000000..d30c350
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java
@@ -0,0 +1,127 @@
+/*
+ * 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.domain.options;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+import java.util.Arrays;
+
+public class ListImagesOptions extends BaseHttpRequestOptions {
+   public static final String IMAGE_ID_PARAM = "ImageId";
+   public static final String STATUS_PARAM = "Status";
+   public static final String SNAPSHOT_ID_PARAM = "SnapshotId";
+   public static final String IMAGE_NAME_PARAM = "ImageName";
+   public static final String IMAGE_OWNER_ALIAS_PARAM = "ImageOwnerAlias";
+   public static final String USAGE_PARAM = "Usage";
+
+   public ListImagesOptions imageIds(String... instanceIds) {
+      String instanceIdsAsString = Joiner.on(",")
+            .join(Iterables.transform(Arrays.asList(instanceIds), new Function<String, String>() {
+               @Override
+               public String apply(String s) {
+                  return new StringBuilder(s.length() + 1).append('"').append(s).append('"').toString();
+               }
+            }));
+      queryParameters.put(IMAGE_ID_PARAM, String.format("[%s]", instanceIdsAsString));
+      return this;
+   }
+
+   public ListImagesOptions status(String status) {
+      queryParameters.put(STATUS_PARAM, status);
+      return this;
+   }
+
+   public ListImagesOptions snapshotId(String snapshotId) {
+      queryParameters.put(SNAPSHOT_ID_PARAM, snapshotId);
+      return this;
+   }
+
+   public ListImagesOptions imageName(String imageName) {
+      queryParameters.put(IMAGE_NAME_PARAM, imageName);
+      return this;
+   }
+
+   public ListImagesOptions imageOwnerAlias(String imageOwnerAlias) {
+      queryParameters.put(IMAGE_OWNER_ALIAS_PARAM, imageOwnerAlias);
+      return this;
+   }
+
+   public ListImagesOptions usage(String usage) {
+      queryParameters.put(USAGE_PARAM, usage);
+      return this;
+   }
+
+   public ListImagesOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListImagesOptions#imageIds(String...)}
+       */
+      public static ListImagesOptions imageIds(String... imageIds) {
+         return new ListImagesOptions().imageIds(imageIds);
+      }
+
+      /**
+       * @see {@link ListImagesOptions#status(String)}
+       */
+      public static ListImagesOptions status(String status) {
+         return new ListImagesOptions().status(status);
+      }
+
+      /**
+       * @see {@link ListImagesOptions#snapshotId(String)}
+       */
+      public static ListImagesOptions snapshotId(String snapshotId) {
+         return new ListImagesOptions().snapshotId(snapshotId);
+      }
+
+      /**
+       * @see {@link ListImagesOptions#imageName(String)}
+       */
+      public static ListImagesOptions imageName(String imageName) {
+         return new ListImagesOptions().imageName(imageName);
+      }
+
+      /**
+       * @see {@link ListImagesOptions#imageOwnerAlias(String)}
+       */
+      public static ListImagesOptions imageOwnerAlias(String imageOwnerAlias) {
+         return new ListImagesOptions().imageOwnerAlias(imageOwnerAlias);
+      }
+
+      /**
+       * @see {@link ListImagesOptions#usage(String)}
+       */
+      public static ListImagesOptions usage(String usage) {
+         return new ListImagesOptions().usage(usage);
+      }
+
+      /**
+       * @see ListImagesOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListImagesOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListImagesOptions().paginationOptions(paginationOptions);
+      }
+   }
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java
new file mode 100644
index 0000000..2a7a0ab
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain.options;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public class PaginationOptions extends BaseHttpRequestOptions {
+
+   public static final String PAGE_NUMBER = "pageNumber";
+   public static final String PAGE_SIZE = "pageSize";
+
+   public PaginationOptions pageNumber(int pageNumber) {
+      checkState(pageNumber > 0, "pageSize must be > 0");
+      checkState(pageNumber <= 50, "limit must be <= 50");
+      this.queryParameters.put(PAGE_NUMBER, Integer.toString(pageNumber));
+      return this;
+   }
+
+   public String pageNumber() {
+      return getFirstQueryOrNull(PAGE_NUMBER);
+   }
+
+   public PaginationOptions pageSize(int pageSize) {
+      checkState(pageSize >= 0, "pageSize must be >= 0");
+      checkState(pageSize <= 100, "pageSize must be <= 100");
+      queryParameters.put(PAGE_SIZE, Integer.toString(pageSize));
+      return this;
+   }
+
+   public String pageSize() {
+      return getFirstQueryOrNull(PAGE_SIZE);
+   }
+
+   public static class Builder {
+
+      /**
+       * @see PaginationOptions#pageNumber(int)
+       */
+      public static PaginationOptions pageNumber(Integer pageNumber) {
+         PaginationOptions options = new PaginationOptions();
+         return options.pageNumber(pageNumber);
+      }
+
+      /**
+       * @see PaginationOptions#pageSize(int)
+       */
+      public static PaginationOptions pageSize(int pageSize) {
+         PaginationOptions options = new PaginationOptions();
+         return options.pageSize(pageSize);
+      }
+
+   }
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java
new file mode 100644
index 0000000..68572ef
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.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.features;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+import org.jclouds.Constants;
+import org.jclouds.Fallbacks;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.domain.Image;
+import org.jclouds.aliyun.ecs.domain.Images;
+import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions;
+import org.jclouds.aliyun.ecs.domain.options.PaginationOptions;
+import org.jclouds.aliyun.ecs.filters.FormSign;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.collect.internal.Arg0ToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.QueryParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * https://www.alibabacloud.com/help/doc-detail/25534.htm?spm=a2c63.p38356.b99.330.79eb59abhmnMDE
+ */
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestFilters(FormSign.class)
+@QueryParams(keys = {"Version", "Format", "SignatureVersion", "ServiceCode", "SignatureMethod"},
+             values = {"{" + Constants.PROPERTY_API_VERSION + "}", "JSON", "1.0", "ecs", "HMAC-SHA1"})
+public interface ImageApi {
+
+   @Named("image:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeImages")
+   @ResponseParser(ParseImages.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<Image> list(@QueryParam("RegionId") String region, ListImagesOptions options);
+
+   @Named("image:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeImages")
+   @ResponseParser(ParseImages.class)
+   @Transform(ParseImages.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<Image> list(@QueryParam("RegionId") String region);
+
+   @Singleton
+   final class ParseImages extends ParseJson<Images> {
+
+      @Inject
+      ParseImages(final Json json) {
+         super(json, TypeLiteral.get(Images.class));
+      }
+
+      static class ToPagedIterable extends Arg0ToPagedIterable<Image, ToPagedIterable> {
+
+         private final ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<Image>> markerToNextForArg0(final Optional<Object> arg0) {
+            return new Function<Object, IterableWithMarker<Image>>() {
+               @Override
+               public IterableWithMarker<Image> apply(Object input) {
+                  String regionId = arg0.get().toString();
+                  ListImagesOptions listImagesOptions = ListImagesOptions.Builder.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.imageApi().list(regionId, listImagesOptions);
+               }
+            };
+         }
+      }
+   }
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java
new file mode 100644
index 0000000..ceede09
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java
@@ -0,0 +1,134 @@
+/*
+ * 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.filters;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.io.ByteProcessor;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.location.Provider;
+import org.jclouds.util.Strings2;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.SimpleTimeZone;
+import java.util.SortedMap;
+import java.util.UUID;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.io.BaseEncoding.base64;
+import static com.google.common.io.ByteStreams.readBytes;
+import static org.jclouds.crypto.Macs.asByteProcessor;
+import static org.jclouds.http.Uris.uriBuilder;
+import static org.jclouds.http.utils.Queries.queryParser;
+import static org.jclouds.util.Strings2.toInputStream;
+
+@Singleton
+public class FormSign implements HttpRequestFilter {
+
+   private static final String SEPARATOR = "&";
+   public static final String ECS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+   private final Supplier<Credentials> creds;
+   private final Crypto crypto;
+
+   @Inject
+   FormSign(@Provider Supplier<Credentials> creds, Crypto crypto) {
+      this.creds = creds;
+      this.crypto = crypto;
+   }
+
+   public HttpRequest filter(HttpRequest request) throws HttpException {
+      Credentials currentCreds = checkNotNull(creds.get(), "credential supplier returned null");
+
+      Multimap<String, String> decodedParams = queryParser().apply(request.getEndpoint().getQuery());
+
+      SimpleDateFormat df = new SimpleDateFormat(ECS_DATE_FORMAT);
+      df.setTimeZone(new SimpleTimeZone(0, "GMT"));
+
+      String timestamp = df.format(new Date());
+      String signatureNonce = UUID.randomUUID().toString();
+
+      decodedParams.put("AccessKeyId", currentCreds.identity);
+      decodedParams.put("Timestamp", timestamp);
+      decodedParams.put("SignatureNonce", signatureNonce);
+
+      String stringToSign = createStringToSign(request.getMethod(), decodedParams);
+
+      String signature = sign(stringToSign, creds.get().credential);
+      decodedParams.put("Signature", signature);
+
+      request = request.toBuilder().endpoint(uriBuilder(request.getEndpoint()).query(decodedParams).build()).build();
+      return request;
+   }
+
+   protected String createStringToSign(String method, Multimap<String, String> params) {
+
+      StringBuilder toSign = new StringBuilder();
+      toSign.append(method).append(SEPARATOR).append(Strings2.urlEncode("/")).append(SEPARATOR);
+      toSign.append(getCanonicalizedQueryString(params));
+      return toSign.toString();
+   }
+
+   /**
+    * Examines the specified query string parameters and returns a
+    * canonicalized form.
+    * <p/>
+    * The canonicalized query string is formed by first sorting all the query
+    * string parameters, then URI encoding both the key and value and then
+    * joining them, in order, separating key value pairs with an '&'.
+    *
+    * @return A canonicalized form for the specified query string parameters.
+    */
+   protected String getCanonicalizedQueryString(Multimap<String, String> params) {
+      SortedMap<String, String> sorted = Maps.newTreeMap();
+      if (params == null) {
+         return "";
+      }
+      Iterator<Map.Entry<String, String>> pairs = params.entries().iterator();
+      while (pairs.hasNext()) {
+         Map.Entry<String, String> pair = pairs.next();
+         String key = pair.getKey();
+         String value = pair.getValue();
+         sorted.put(Strings2.urlEncode(key), Strings2.urlEncode(value));
+      }
+
+      return Strings2.urlEncode(Joiner.on("&").withKeyValueSeparator("=").join(sorted));
+   }
+
+   public String sign(String toSign, String credentials) {
+      try {
+         ByteProcessor<byte[]> hmacSHA1 = asByteProcessor(
+                 crypto.hmacSHA1(String.format("%s&", credentials).getBytes(UTF_8)));
+         return base64().encode(readBytes(toInputStream(toSign), hmacSHA1));
+      } catch (Exception e) {
+         throw new HttpException("error signing request", e);
+      }
+   }
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java
new file mode 100644
index 0000000..9f2ddcf
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.internal.Arg0ToPagedIterable;
+
+/**
+ * Base class to implement the functions that build the
+ * <code>PagedIterable</code>. Subclasses just need to override the
+ * {@link #fetchPageUsingOptions(ListImagesOptions, Optional)} to invoke the right API
+ * method with the given options parameter to get the next page.
+ */
+public abstract class BaseToPagedIterable<T, O extends ListImagesOptions> extends
+        Arg0ToPagedIterable<T, BaseToPagedIterable<T, O>> {
+   private final Function<Integer, O> pageNumberToOptions;
+   protected final ECSComputeServiceApi api;
+
+   protected BaseToPagedIterable(ECSComputeServiceApi api, Function<Integer, O> pageNumberToOptions) {
+      this.api = api;
+      this.pageNumberToOptions = pageNumberToOptions;
+   }
+
+   protected abstract IterableWithMarker<T> fetchPageUsingOptions(O options, Optional<Object> arg0);
+
+   @Override
+   protected Function<Object, IterableWithMarker<T>> markerToNextForArg0(final Optional<Object> arg0) {
+      return new Function<Object, IterableWithMarker<T>>() {
+         @Override
+         public IterableWithMarker<T> apply(Object input) {
+            O nextOptions = pageNumberToOptions.apply(Integer.class.cast(input));
+            return fetchPageUsingOptions(nextOptions, arg0);
+         }
+      };
+   }
+
+}
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java
new file mode 100644
index 0000000..660a880
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.handlers;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.ResourceNotFoundException;
+
+import javax.inject.Singleton;
+
+import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
+
+/**
+ * This will parse and set an appropriate exception on the command object.
+ */
+@Singleton
+public class ECSComputeServiceErrorHandler implements HttpErrorHandler {
+
+   public void handleError(HttpCommand command, HttpResponse response) {
+      // it is important to always read fully and close streams
+      byte[] data = closeClientButKeepContentStream(response);
+      String message = data != null ? new String(data) : null;
+
+      Exception exception = message != null ?
+            new HttpResponseException(command, response, message) :
+            new HttpResponseException(command, response);
+      message = message != null ?
+            message :
+            String.format("%s -> %s", command.getCurrentRequest().getRequestLine(), response.getStatusLine());
+      switch (response.getStatusCode()) {
+         case 400:
+            exception = new IllegalArgumentException(message, exception);
+            break;
+         case 401:
+         case 403:
+            exception = new AuthorizationException(message, exception);
+            break;
+         case 404:
+            if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
+               exception = new ResourceNotFoundException(message, exception);
+            }
+            break;
+         case 409:
+            exception = new IllegalStateException(message, exception);
+            break;
+      }
+      command.setException(exception);
+   }
+}
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java
new file mode 100644
index 0000000..f663b77
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute;
+
+import org.jclouds.aliyun.ecs.ECSComputeServiceProviderMetadata;
+import org.jclouds.aliyun.ecs.ECSServiceApiMetadata;
+import org.jclouds.providers.internal.BaseProviderMetadataTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "ECSComputeProviderMetadataTest")
+public class ECSComputeProviderMetadataTest extends BaseProviderMetadataTest {
+
+   public ECSComputeProviderMetadataTest() {
+      super(new ECSComputeServiceProviderMetadata(), new ECSServiceApiMetadata());
+   }
+}
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java
new file mode 100644
index 0000000..957d9cc
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.features;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiLiveTest;
+import org.jclouds.aliyun.ecs.domain.Image;
+import org.jclouds.aliyun.ecs.domain.Regions;
+import org.jclouds.aliyun.ecs.features.ImageApi;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "ImageApiLiveTest")
+public class ImageApiLiveTest extends BaseECSComputeServiceApiLiveTest {
+
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list(Regions.EU_CENTRAL_1.getName()).concat(), new Predicate<Image>() {
+         @Override
+         public boolean apply(Image input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.imageId());
+         }
+      }), "All images must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some image to be returned");
+   }
+
+   private ImageApi api() {
+      return api.imageApi();
+   }
+}
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java
new file mode 100644
index 0000000..a29c067
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.features;
+
+import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
+import org.jclouds.aliyun.ecs.domain.Image;
+import org.jclouds.aliyun.ecs.domain.Regions;
+import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions;
+import org.jclouds.aliyun.ecs.domain.options.PaginationOptions;
+import org.jclouds.collect.IterableWithMarker;
+import org.testng.annotations.Test;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "ImageApiMockTest", singleThreaded = true)
+public class ImageApiMockTest extends BaseECSComputeServiceApiMockTest {
+
+   public void testListImages() throws InterruptedException {
+      server.enqueue(jsonResponse("/images-first.json"));
+      server.enqueue(jsonResponse("/images-second.json"));
+      server.enqueue(jsonResponse("/images-last.json"));
+
+      Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      assertEquals(size(images), 28); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 3);
+      assertSent(server, "GET", "DescribeImages");
+      assertSent(server, "GET", "DescribeImages", 2);
+      assertSent(server, "GET", "DescribeImages", 3);
+   }
+
+   public void testListImagesReturns404() {
+      server.enqueue(response404());
+      Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      assertTrue(isEmpty(images));
+      assertEquals(server.getRequestCount(), 1);
+   }
+
+   public void testListImagesWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/images-first.json"));
+
+      IterableWithMarker<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName(), ListImagesOptions.Builder
+              .paginationOptions(PaginationOptions.Builder.pageNumber(1)));
+
+      assertEquals(size(images), 10);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "DescribeImages", 1);
+   }
+
+   public void testListImagesWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      IterableWithMarker<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName(), ListImagesOptions.Builder
+              .paginationOptions(PaginationOptions.Builder.pageNumber(2)));
+
+      assertTrue(isEmpty(images));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeImages", 2);
+   }
+
+}
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java
new file mode 100644
index 0000000..f7b9526
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.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.internal;
+
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.compute.config.ComputeServiceProperties;
+
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+public class BaseECSComputeServiceApiLiveTest extends BaseApiLiveTest<ECSComputeServiceApi> {
+
+   public BaseECSComputeServiceApiLiveTest() {
+      provider = "aliyun-ecs";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      props.put(ComputeServiceProperties.POLL_INITIAL_PERIOD, 1000);
+      props.put(ComputeServiceProperties.POLL_MAX_PERIOD, 10000);
+      props.put(ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE, TimeUnit.MINUTES.toMillis(45));
+      return props;
+   }
+
+   @Override
+   protected ECSComputeServiceApi create(Properties props, Iterable<Module> modules) {
+      Injector injector = newBuilder().modules(modules).overrides(props).buildInjector();
+      return injector.getInstance(ECSComputeServiceApi.class);
+   }
+
+}
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java
new file mode 100644
index 0000000..5845339
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.internal;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
+import com.google.inject.Module;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import org.jclouds.ContextBuilder;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.ECSComputeServiceProviderMetadata;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.json.Json;
+import org.jclouds.rest.ApiContext;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
+import static org.testng.Assert.assertEquals;
+
+public class BaseECSComputeServiceApiMockTest {
+
+   private static final String DEFAULT_ENDPOINT = new ECSComputeServiceProviderMetadata().getEndpoint();
+
+   private final Set<Module> modules = ImmutableSet.<Module>of(new ExecutorServiceModule(newDirectExecutorService()));
+   protected MockWebServer server;
+   protected ECSComputeServiceApi api;
+   private Json json;
+   private ApiContext<ECSComputeServiceApi> ctx;
+
+   @BeforeMethod
+   public void start() throws IOException {
+      server = new MockWebServer();
+      server.play();
+      ctx = ContextBuilder.newBuilder("aliyun-ecs").credentials("user", "password").endpoint(url("")).modules(modules)
+            .overrides(overrides()).build();
+      json = ctx.utils().injector().getInstance(Json.class);
+      api = ctx.getApi();
+   }
+
+   @AfterMethod(alwaysRun = true)
+   public void stop() throws IOException {
+      server.shutdown();
+      api.close();
+   }
+
+   protected Properties overrides() {
+      Properties properties = new Properties();
+      properties.put(PROPERTY_MAX_RETRIES, "0"); // Do not retry
+      return properties;
+   }
+
+   protected String url(String path) {
+      return server.getUrl(path).toString();
+   }
+
+   protected MockResponse jsonResponse(String resource) {
+      return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource));
+   }
+
+   protected MockResponse response404() {
+      return new MockResponse().setStatus("HTTP/1.1 404 Not Found");
+   }
+
+   protected String stringFromResource(String resourceName) {
+      try {
+         return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8)
+               .replace(DEFAULT_ENDPOINT, url(""));
+      } catch (IOException e) {
+         throw Throwables.propagate(e);
+      }
+   }
+
+   protected RecordedRequest assertSent(MockWebServer server, String method, String action) throws InterruptedException {
+      RecordedRequest request = server.takeRequest();
+      assertEquals(request.getMethod(), method);
+      Map<String, String> queryParameters = Splitter.on('&').trimResults().withKeyValueSeparator("=").split(request.getPath());
+      assertEquals(queryParameters.get("Action"), action);
+      assertEquals(request.getHeader("Accept"), "application/json");
+      return request;
+   }
+
+   protected RecordedRequest assertSent(MockWebServer server, String method, String action, Integer page) throws InterruptedException {
+      RecordedRequest request = server.takeRequest();
+      assertEquals(request.getMethod(), method);
+      Map<String, String> queryParameters = Splitter.on('&').trimResults().withKeyValueSeparator("=").split(request.getPath());
+      assertEquals(queryParameters.get("Action"), action);
+      assertEquals(Integer.valueOf(queryParameters.get("pageNumber")), page);
+      assertEquals(request.getHeader("Accept"), "application/json");
+      return request;
+   }
+
+}
diff --git a/aliyun-ecs/src/test/resources/images-first.json b/aliyun-ecs/src/test/resources/images-first.json
new file mode 100644
index 0000000..1b9a16d
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/images-first.json
@@ -0,0 +1,291 @@
+{
+  "PageNumber": 1,
+  "TotalCount": 28,
+  "PageSize": 10,
+  "RegionId": "eu-central-1",
+  "RequestId": "28D4319D-1F9B-4531-B317-6AA88350C65C",
+  "Images": {
+    "Image": [
+      {
+        "ImageId": "centos_6_09_64_20G_alibase_20180326.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "CentOS  6.9 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2018-05-10T12:40:55Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "centos_6_09_64_20G_alibase_20180326.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CentOS",
+        "Size": 20
+      },
+      {
+        "ImageId": "ubuntu_16_0402_32_20G_alibase_20180409.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "i386",
+        "OSName": "Ubuntu  16.04 32位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2018-05-10T08:34:55Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "ubuntu_16_0402_32_20G_alibase_20180409.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Ubuntu",
+        "Size": 20
+      },
+      {
+        "ImageId": "ubuntu_16_0402_64_20G_alibase_20180409.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "Ubuntu  16.04 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2018-05-10T04:24:19Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "ubuntu_16_0402_64_20G_alibase_20180409.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Ubuntu",
+        "Size": 20
+      },
+      {
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "CentOS  7.4 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2018-05-02T02:07:58Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CentOS",
+        "Size": 20
+      },
+      {
+        "ImageId": "alinux_17_01_64_20G_cloudinit_20171222.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "Aliyun Linux  17.1 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-12-22T05:56:16Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "alinux_17_01_64_20G_cloudinit_20171222.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Aliyun",
+        "Size": 20
+      },
+      {
+        "ImageId": "winsvr_64_dtcC_1709_zh-cn_40G_alibase_20171115.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  Version 1709 数据中心版 64位中文版(不含UI)",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-11-22T03:51:54Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "winsvr_64_dtcC_1709_zh-cn_40G_alibase_20171115.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2016",
+        "Size": 40
+      },
+      {
+        "ImageId": "winsvr_64_dtcC_1709_en-us_40G_alibase_20171115.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  Version 1709 数据中心版 64位英文版(不含UI)",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-11-22T03:50:24Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "winsvr_64_dtcC_1709_en-us_40G_alibase_20171115.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2016",
+        "Size": 40
+      },
+      {
+        "ImageId": "opensuse_42_03_64_20G_alibase_20171031.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "OpenSUSE  42.3 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": false,
+        "Usage": "instance",
+        "CreationTime": "2017-11-01T01:29:54Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "opensuse_42_03_64_20G_alibase_20171031.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "openSUSE",
+        "Size": 20
+      },
+      {
+        "ImageId": "debian_9_02_64_20G_alibase_20171023.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "Debian  9.2 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-10-26T01:56:09Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "debian_9_02_64_20G_alibase_20171023.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Debian",
+        "Size": 20
+      },
+      {
+        "ImageId": "coreos_1465_8_0_64_30G_alibase_20171024.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "CoreOS  1465.8.0 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": false,
+        "Usage": "instance",
+        "CreationTime": "2017-10-26T01:52:22Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "coreos_1465_8_0_64_30G_alibase_20171024.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CoreOS",
+        "Size": 30
+      }
+    ]
+  }
+}
diff --git a/aliyun-ecs/src/test/resources/images-last.json b/aliyun-ecs/src/test/resources/images-last.json
new file mode 100644
index 0000000..a737492
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/images-last.json
@@ -0,0 +1,235 @@
+{
+  "PageNumber": 3,
+  "TotalCount": 28,
+  "PageSize": 10,
+  "RegionId": "eu-central-1",
+  "RequestId": "AE24C500-9A3F-4460-9E4A-19C65D4EA990",
+  "Images": {
+    "Image": [
+      {
+        "ImageId": "debian_8_09_64_20G_alibase_20170824.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "Debian  8.9 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-08-28T03:28:22Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "debian_8_09_64_20G_alibase_20170824.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Debian",
+        "Size": 20
+      },
+      {
+        "ImageId": "ubuntu_14_0405_64_20G_alibase_20170824.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "Ubuntu  14.04 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-08-28T03:27:44Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "ubuntu_14_0405_64_20G_alibase_20170824.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Ubuntu",
+        "Size": 20
+      },
+      {
+        "ImageId": "centos_7_02_64_20G_alibase_20170818.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "CentOS  7.2 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-08-22T03:37:18Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "centos_7_02_64_20G_alibase_20170818.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CentOS",
+        "Size": 20
+      },
+      {
+        "ImageId": "centos_7_03_64_20G_alibase_20170818.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "CentOS  7.3 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-08-22T03:36:43Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "centos_7_03_64_20G_alibase_20170818.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CentOS",
+        "Size": 20
+      },
+      {
+        "ImageId": "ubuntu_14_0405_32_40G_alibase_20170711.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "i386",
+        "OSName": "Ubuntu  14.04 32位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-07-18T07:13:44Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "ubuntu_14_0405_32_40G_alibase_20170711.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Ubuntu",
+        "Size": 40
+      },
+      {
+        "ImageId": "centos_6_08_32_40G_alibase_20170710.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "i386",
+        "OSName": "CentOS  6.8 32位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-07-17T10:00:57Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "centos_6_08_32_40G_alibase_20170710.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CentOS",
+        "Size": 40
+      },
+      {
+        "ImageId": "win2008_32_std_sp2_zh-cn_40G_alibase_20170622.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "i386",
+        "OSName": "Windows Server  2008 标准版 SP2 32位中文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": false,
+        "Usage": "instance",
+        "CreationTime": "2017-07-06T10:10:56Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2008_32_std_sp2_zh-cn_40G_alibase_20170622.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2008",
+        "Size": 40
+      },
+      {
+        "ImageId": "gentoo13_64_40G_aliaegis_20160222.vhd",
+        "Description": "修复了 glibc cve-2015-7547 漏洞,修复部分镜像 ssh host key 问题 (Ubuntu/Debian 除外",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "Gentoo  13  64bit",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": false,
+        "Usage": "instance",
+        "CreationTime": "2016-10-20T09:07:52Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "gentoo13_64_40G_aliaegis_20160222.vhd",
+        "IsSupportIoOptimized": false,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Gentoo",
+        "Size": 40
+      }
+    ]
+  }
+}
diff --git a/aliyun-ecs/src/test/resources/images-second.json b/aliyun-ecs/src/test/resources/images-second.json
new file mode 100644
index 0000000..8e9f6a5
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/images-second.json
@@ -0,0 +1,291 @@
+{
+  "PageNumber": 2,
+  "TotalCount": 28,
+  "PageSize": 10,
+  "RegionId": "eu-central-1",
+  "RequestId": "2B1EAA68-1570-490C-A17C-5A7F7255576F",
+  "Images": {
+    "Image": [
+      {
+        "ImageId": "win2008r2_64_ent_sp1_en-us_40G_alibase_20170915.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  2008 R2 企业版 64位英文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-29T02:25:18Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2008r2_64_ent_sp1_en-us_40G_alibase_20170915.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2008",
+        "Size": 40
+      },
+      {
+        "ImageId": "win2008r2_64_ent_sp1_zh-cn_40G_alibase_20170915.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  2008 R2 企业版 64位中文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-29T02:24:45Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2008r2_64_ent_sp1_zh-cn_40G_alibase_20170915.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2008",
+        "Size": 40
+      },
+      {
+        "ImageId": "win2012r2_64_dtc_17196_en-us_40G_alibase_20170915.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  2012 R2 数据中心版 64位英文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-29T02:23:41Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2012r2_64_dtc_17196_en-us_40G_alibase_20170915.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2012",
+        "Size": 40
+      },
+      {
+        "ImageId": "win2012r2_64_dtc_17196_zh-cn_40G_alibase_20170915.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  2012  R2 数据中心版 64位中文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-29T02:23:05Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2012r2_64_dtc_17196_zh-cn_40G_alibase_20170915.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2012",
+        "Size": 40
+      },
+      {
+        "ImageId": "win2016_64_dtc_1607_zh-cn_40G_alibase_20170915.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  2016 数据中心版 64位中文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-29T02:21:27Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2016_64_dtc_1607_zh-cn_40G_alibase_20170915.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2016",
+        "Size": 40
+      },
+      {
+        "ImageId": "win2016_64_dtc_1607_en-us_40G_alibase_20170915.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "windows",
+        "Architecture": "x86_64",
+        "OSName": "Windows Server  2016 数据中心版 64位英文版",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-29T02:20:20Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "win2016_64_dtc_1607_en-us_40G_alibase_20170915.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Windows Server 2016",
+        "Size": 40
+      },
+      {
+        "ImageId": "sles_11_sp4_64_20G_alibase_20170907.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "SUSE Linux  Enterprise Server 11 SP4 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-07T07:22:40Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "sles_11_sp4_64_20G_alibase_20170907.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "SUSE",
+        "Size": 20
+      },
+      {
+        "ImageId": "sles_12_sp2_64_20G_alibase_20170907.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "SUSE Linux  Enterprise Server 12 SP2 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-09-07T07:22:05Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "sles_12_sp2_64_20G_alibase_20170907.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "SUSE",
+        "Size": 20
+      },
+      {
+        "ImageId": "freebsd_11_03_64_20G_alibase_20170901.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "FreeBSD  11.1 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": false,
+        "Usage": "instance",
+        "CreationTime": "2017-09-04T10:07:59Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "freebsd_11_03_64_20G_alibase_20170901.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "Freebsd",
+        "Size": 20
+      },
+      {
+        "ImageId": "centos_6_08_64_20G_alibase_20170824.vhd",
+        "Description": "",
+        "ProductCode": "",
+        "OSType": "linux",
+        "Architecture": "x86_64",
+        "OSName": "CentOS  6.8 64位",
+        "DiskDeviceMappings": {
+          "DiskDeviceMapping": []
+        },
+        "ImageOwnerAlias": "system",
+        "Progress": "100%",
+        "IsSupportCloudinit": true,
+        "Usage": "instance",
+        "CreationTime": "2017-08-28T03:29:04Z",
+        "Tags": {
+          "Tag": []
+        },
+        "ImageVersion": "",
+        "Status": "Available",
+        "ImageName": "centos_6_08_64_20G_alibase_20170824.vhd",
+        "IsSupportIoOptimized": true,
+        "IsSelfShared": "",
+        "IsCopied": false,
+        "IsSubscribed": false,
+        "Platform": "CentOS",
+        "Size": 20
+      }
+    ]
+  }
+}
diff --git a/pom.xml b/pom.xml
index 3926ace..cddd17e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
     <module>profitbricks-rest</module>
     <module>oneandone</module>
     <module>vagrant</module>
+    <module>aliyun-ecs</module>
   </modules>
 
   <build>