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

[2/2] jclouds-labs git commit: [JCLOUDS-1430] - add more features

[JCLOUDS-1430] - add more features

- add securitygroup-api
- add keypair-api
- add tag-api
- refactor paginations
- refactor tagOptions


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

Branch: refs/heads/master
Commit: a5dbf0065d8fa8cabcaf020b7c10fe2f7ccf8d6a
Parents: d74d7f6
Author: andreaturli <an...@gmail.com>
Authored: Thu Jul 5 15:28:48 2018 +0200
Committer: andreaturli <an...@gmail.com>
Committed: Tue Jul 31 21:30:18 2018 +0200

----------------------------------------------------------------------
 .../aliyun/ecs/ECSComputeServiceApi.java        |  12 ++
 .../org/jclouds/aliyun/ecs/domain/Image.java    |  12 +-
 .../org/jclouds/aliyun/ecs/domain/Images.java   |  33 ----
 .../jclouds/aliyun/ecs/domain/IpProtocol.java   |  41 +++++
 .../org/jclouds/aliyun/ecs/domain/KeyPair.java  |  40 +++++
 .../aliyun/ecs/domain/KeyPairRequest.java       |  76 +++++++++
 .../jclouds/aliyun/ecs/domain/Permission.java   | 110 +++++++++++++
 .../org/jclouds/aliyun/ecs/domain/Region.java   |   6 +-
 .../org/jclouds/aliyun/ecs/domain/Regions.java  |  81 ----------
 .../org/jclouds/aliyun/ecs/domain/Request.java  |  58 +++++++
 .../aliyun/ecs/domain/SecurityGroup.java        |  42 +++++
 .../aliyun/ecs/domain/SecurityGroupRequest.java |  59 +++++++
 .../org/jclouds/aliyun/ecs/domain/Zone.java     |   6 +-
 .../aliyun/ecs/domain/internal/Regions.java     |  81 ++++++++++
 .../ecs/domain/options/AddTagsOptions.java      |  64 ++++++++
 .../options/CreateSecurityGroupOptions.java     |  78 +++++++++
 .../domain/options/DeleteKeyPairOptions.java    |  51 ++++++
 .../ecs/domain/options/ListImagesOptions.java   |  15 +-
 .../ecs/domain/options/ListKeyPairsOptions.java |  64 ++++++++
 .../options/ListSecurityGroupsOptions.java      |  62 ++++++++
 .../ecs/domain/options/ListTagsOptions.java     |  75 +++++++++
 .../aliyun/ecs/domain/options/TagOptions.java   |  95 +++++++++++
 .../jclouds/aliyun/ecs/features/ImageApi.java   |  35 +++--
 .../aliyun/ecs/features/SecurityGroupApi.java   | 157 +++++++++++++++++++
 .../aliyun/ecs/features/SshKeyPairApi.java      | 142 +++++++++++++++++
 .../org/jclouds/aliyun/ecs/features/TagApi.java | 143 +++++++++++++++++
 .../functions/ArrayToCommaSeparatedString.java  |  49 ++++++
 .../ecs/functions/BaseToPagedIterable.java      |  55 -------
 .../ecs/compute/features/ImageApiLiveTest.java  |  21 ++-
 .../ecs/compute/features/ImageApiMockTest.java  |   9 +-
 .../features/RegionAndZoneApiLiveTest.java      |   4 +-
 .../features/RegionAndZoneApiMockTest.java      |   2 +-
 .../features/SecurityGroupApiLiveTest.java      |  95 +++++++++++
 .../features/SecurityGroupApiMockTest.java      |  69 ++++++++
 .../compute/features/SshKeyPairApiLiveTest.java |  83 ++++++++++
 .../compute/features/SshKeyPairApiMockTest.java | 108 +++++++++++++
 .../ecs/compute/features/TagApiLiveTest.java    |  83 ++++++++++
 .../ecs/compute/features/TagApiMockTest.java    |  69 ++++++++
 .../BaseECSComputeServiceApiMockTest.java       |  13 ++
 .../ArrayToCommaSeparatedStringTest.java        |  57 +++++++
 .../src/test/resources/keypair-create-res.json  |   6 +
 .../src/test/resources/keypair-delete-res.json  |   3 +
 .../src/test/resources/keypair-import-res.json  |   6 +
 .../src/test/resources/keypairs-first.json      |  50 ++++++
 .../src/test/resources/keypairs-last.json       |  18 +++
 .../test/resources/securitygroups-first.json    |  61 +++++++
 .../src/test/resources/securitygroups-last.json |  31 ++++
 aliyun-ecs/src/test/resources/tags-first.json   |  66 ++++++++
 aliyun-ecs/src/test/resources/tags-last.json    |  24 +++
 49 files changed, 2407 insertions(+), 213 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
index 140b098..bb24cf0 100644
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java
@@ -18,6 +18,9 @@ package org.jclouds.aliyun.ecs;
 
 import org.jclouds.aliyun.ecs.features.ImageApi;
 import org.jclouds.aliyun.ecs.features.RegionAndZoneApi;
+import org.jclouds.aliyun.ecs.features.SecurityGroupApi;
+import org.jclouds.aliyun.ecs.features.SshKeyPairApi;
+import org.jclouds.aliyun.ecs.features.TagApi;
 import org.jclouds.rest.annotations.Delegate;
 
 import java.io.Closeable;
@@ -30,4 +33,13 @@ public interface ECSComputeServiceApi extends Closeable {
    @Delegate
    RegionAndZoneApi regionAndZoneApi();
 
+   @Delegate
+   SecurityGroupApi securityGroupApi();
+
+   @Delegate
+   SshKeyPairApi sshKeyPairApi();
+
+   @Delegate
+   TagApi tagApi();
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java
----------------------------------------------------------------------
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
index a65bb8b..328d6fe 100644
--- 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
@@ -33,21 +33,21 @@ public abstract class Image {
            "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,
+   public static Image create(String id, 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,
+                              Map<String, List<Tag>> tags, String imageVersion, String status, String name,
                               Boolean isSupportIoOptimized, Boolean isSelfShared, Boolean isCopied, Boolean isSubscribed, String platform,
                               String size) {
-      return new AutoValue_Image(imageId, description, productCode, osType, architecture, osName,
+      return new AutoValue_Image(id, 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);
+              status, name, isSupportIoOptimized, isSelfShared, isCopied, isSubscribed, platform, size);
    }
 
-   public abstract String imageId();
+   public abstract String id();
 
    public abstract String description();
 
@@ -77,7 +77,7 @@ public abstract class Image {
 
    public abstract String status();
 
-   public abstract String imageName();
+   public abstract String name();
 
    public abstract Boolean isSupportIoOptimizeds();
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java
----------------------------------------------------------------------
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
deleted file mode 100644
index b0e3af3..0000000
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/IpProtocol.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/IpProtocol.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/IpProtocol.java
new file mode 100644
index 0000000..ca02d9e
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/IpProtocol.java
@@ -0,0 +1,41 @@
+/*
+ * 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.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * IP protocol. Not case sensitive. Optional values:
+ * icmp
+ * gre
+ * tcp
+ * udp
+ * all: Support four protocols at the same time
+ */
+public enum IpProtocol {
+   ICMP, GRE, TCP, UDP, ALL;
+
+   public static IpProtocol fromValue(String value) {
+      Optional<IpProtocol> ipProtocol = Enums.getIfPresent(IpProtocol.class, value.toUpperCase());
+      checkArgument(ipProtocol.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(IpProtocol.values()), value);
+      return ipProtocol.get();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPair.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPair.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPair.java
new file mode 100644
index 0000000..83e148f
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPair.java
@@ -0,0 +1,40 @@
+/*
+ * 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.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+@AutoValue
+public abstract class KeyPair {
+
+   KeyPair() {}
+
+   @SerializedNames({ "KeyPairName", "KeyPairFingerPrint", "PrivateKeyBody" })
+   public static KeyPair create(String name, String keyPairFingerPrint, String privateKeyBody) {
+      return new AutoValue_KeyPair(name, keyPairFingerPrint, privateKeyBody);
+   }
+
+   public abstract String name();
+
+   public abstract String keyPairFingerPrint();
+
+   @Nullable
+   public abstract String privateKeyBody();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPairRequest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPairRequest.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPairRequest.java
new file mode 100644
index 0000000..a55fe74
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/KeyPairRequest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import java.beans.ConstructorProperties;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class KeyPairRequest extends Request {
+
+   private final String keyPairName;
+   private final String keyPairFingerPrint;
+   private final String privateKeyBody;
+
+   @ConstructorProperties({ "RequestId", "KeyPairName", "KeyPairFingerPrint", "PrivateKeyBody" })
+   public KeyPairRequest(String requestId, String keyPairName, String keyPairFingerPrint, String privateKeyBody) {
+      super(requestId);
+      this.keyPairName = checkNotNull(keyPairName, "name");
+      this.keyPairFingerPrint = checkNotNull(keyPairFingerPrint, "keyPairFingerPrint");
+      this.privateKeyBody = checkNotNull(privateKeyBody, "privateKeyBody");
+   }
+
+   public String getKeyPairName() {
+      return keyPairName;
+   }
+
+   public String getKeyPairFingerPrint() {
+      return keyPairFingerPrint;
+   }
+
+   public String getPrivateKeyBody() {
+      return privateKeyBody;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      if (!super.equals(o)) return false;
+      KeyPairRequest that = (KeyPairRequest) o;
+      return Objects.equal(keyPairName, that.keyPairName) &&
+              Objects.equal(keyPairFingerPrint, that.keyPairFingerPrint) &&
+              Objects.equal(privateKeyBody, that.privateKeyBody);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), keyPairName, keyPairFingerPrint, privateKeyBody);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this)
+              .add("name", keyPairName)
+              .add("keyPairFingerPrint", keyPairFingerPrint)
+              .add("privateKeyBody", privateKeyBody)
+              .toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Permission.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Permission.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Permission.java
new file mode 100644
index 0000000..003ccf1
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Permission.java
@@ -0,0 +1,110 @@
+/*
+ * 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.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import org.jclouds.json.SerializedNames;
+
+import java.util.Date;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@AutoValue
+public abstract class Permission {
+
+   public enum NicType {
+      INTERNET, INTRANET;
+
+      public static NicType fromValue(String value) {
+         Optional<NicType> nicType = Enums.getIfPresent(NicType.class, value.toUpperCase());
+         checkArgument(nicType.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(NicType.values()), value);
+         return nicType.get();
+      }
+   }
+
+   public enum Policy {
+      ACCEPT, DROP;
+
+      public static Policy fromValue(String value) {
+         Optional<Policy> policy = Enums.getIfPresent(Policy.class, value.toUpperCase());
+         checkArgument(policy.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Policy.values()), value);
+         return policy.get();
+      }
+   }
+
+   public enum Direction {
+      EGRESS, ALL;
+
+      public static Direction fromValue(String value) {
+         Optional<Direction> direction = Enums.getIfPresent(Direction.class, value.toUpperCase());
+         checkArgument(direction.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Direction.values()), value);
+         return direction.get();
+      }
+   }
+
+   Permission() {}
+
+   @SerializedNames({"SourceCidrIp", "DestCidrIp", "Description", "NicType",
+                   "DestGroupName", "PortRange", "DestGroupId", "Direction", "Priority",
+                   "IpProtocol", "SourceGroupOwnerAccount", "Policy", "CreateTime",
+                   "SourceGroupId", "DestGroupOwnerAccount", "SourceGroupName"})
+   public static Permission create(String sourceCidrIp, String destCidrIp, String description, NicType nicType,
+                                   String destGroupName, String portRange, String destGroupId, Direction direction,
+                                   String priority,
+                                   IpProtocol ipProtocol, String sourceGroupOwnerAccount, Policy policy,
+                                   Date createTime, String sourceGroupId, String destGroupOwnerAccount, String sourceGroupName) {
+      return new AutoValue_Permission(sourceCidrIp, destCidrIp, description, nicType, destGroupName, portRange,
+              destGroupId, direction, priority, ipProtocol, sourceGroupOwnerAccount, policy, createTime, sourceGroupId,
+              destGroupOwnerAccount, sourceGroupName);
+   }
+
+   public abstract String sourceCidrIp();
+
+   public abstract String destCidrIp();
+
+   public abstract String description();
+
+   public abstract NicType nicType();
+
+   public abstract String destGroupName();
+
+   public abstract String portRange();
+
+   public abstract String destGroupId();
+
+   public abstract Direction direction();
+
+   public abstract String priority();
+
+   public abstract IpProtocol ipProtocol();
+
+   public abstract String sourceGroupOwnerAccount();
+
+   public abstract Policy policy();
+
+   public abstract Date createTime();
+
+   public abstract String sourceGroupId();
+
+   public abstract String destGroupOwnerAccount();
+
+   public abstract String sourceGroupName();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Region.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Region.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Region.java
index ce2f6b7..cacdf4b 100644
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Region.java
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Region.java
@@ -25,11 +25,11 @@ public abstract class Region {
    Region() {}
 
    @SerializedNames({ "RegionId", "LocalName" })
-   public static Region create(String regionId, String localName) {
-      return new AutoValue_Region(regionId, localName);
+   public static Region create(String id, String localName) {
+      return new AutoValue_Region(id, localName);
    }
 
-   public abstract String regionId();
+   public abstract String id();
 
    public abstract String localName();
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java
----------------------------------------------------------------------
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
deleted file mode 100644
index e0a89c1..0000000
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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!");
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Request.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Request.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Request.java
new file mode 100644
index 0000000..9ab7535
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Request.java
@@ -0,0 +1,58 @@
+/*
+ * 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.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import java.beans.ConstructorProperties;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class Request {
+
+   private final String requestId;
+
+   @ConstructorProperties({ "RequestId" })
+   public Request(String requestId) {
+      this.requestId = checkNotNull(requestId, "requestId");
+   }
+
+   public String getRequestId() {
+      return requestId;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (this == o)
+         return true;
+      if (o == null || getClass() != o.getClass())
+         return false;
+      Request request = (Request) o;
+      return Objects.equal(requestId, request.requestId);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(requestId);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this).add("requestId", requestId).toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroup.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroup.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroup.java
new file mode 100644
index 0000000..3fd0658
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroup.java
@@ -0,0 +1,42 @@
+/*
+ * 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 SecurityGroup {
+
+   SecurityGroup() {
+   }
+
+   @SerializedNames({ "SecurityGroupId", "Description", "SecurityGroupName", "VpcId" })
+   public static SecurityGroup create(String id, String description, String name,
+                                      String vpcId) {
+      return new AutoValue_SecurityGroup(id, description, name, vpcId);
+   }
+
+   public abstract String id();
+
+   public abstract String description();
+
+   public abstract String name();
+
+   public abstract String vpcId();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroupRequest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroupRequest.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroupRequest.java
new file mode 100644
index 0000000..0a2606a
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SecurityGroupRequest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import java.beans.ConstructorProperties;
+
+public class SecurityGroupRequest extends Request {
+
+   private final String securityGroupId;
+
+   @ConstructorProperties({ "RequestId", "SecurityGroupId" })
+   public SecurityGroupRequest(String requestId, String securityGroupId) {
+      super(requestId);
+      this.securityGroupId = securityGroupId;
+   }
+
+   public String getSecurityGroupId() {
+      return securityGroupId;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (this == o)
+         return true;
+      if (o == null || getClass() != o.getClass())
+         return false;
+      if (!super.equals(o))
+         return false;
+      SecurityGroupRequest that = (SecurityGroupRequest) o;
+      return Objects.equal(securityGroupId, that.securityGroupId);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), securityGroupId);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this).add("id", securityGroupId).toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Zone.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Zone.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Zone.java
index 87709e2..9e66651 100644
--- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Zone.java
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Zone.java
@@ -31,7 +31,7 @@ public abstract class Zone {
    @SerializedNames({ "ZoneId", "LocalName", "DedicatedHostGenerations", "AvailableResourceCreation",
                           "AvailableDedicatedHostTypes", "AvailableResources", "AvailableInstanceTypes",
                           "AvailableVolumeCategories", "AvailableDiskCategories" })
-   public static Zone create(String zoneId, String localName,
+   public static Zone create(String id, String localName,
                              Map<String, List<Object>> dedicatedHostGenerations, // FIXME neither doc nor example showed the type in the list
                              Map<String, List<String>> availableResourceCreation,
                              Map<String, List<String>> availableDedicatedHostTypes,
@@ -39,7 +39,7 @@ public abstract class Zone {
                              Map<String, List<String>> availableInstanceTypes,
                              Map<String, List<String>> availableVolumeCategories,
                              Map<String, List<String>> availableDiskCategories) {
-      return new AutoValue_Zone(zoneId, localName,
+      return new AutoValue_Zone(id, localName,
               dedicatedHostGenerations == null ? ImmutableMap.<String, List<Object>>of() : ImmutableMap.copyOf(dedicatedHostGenerations),
               availableResourceCreation == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(availableResourceCreation),
               availableDedicatedHostTypes == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(availableDedicatedHostTypes),
@@ -50,7 +50,7 @@ public abstract class Zone {
       );
    }
 
-   public abstract String zoneId();
+   public abstract String id();
 
    public abstract String localName();
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/Regions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/Regions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/Regions.java
new file mode 100644
index 0000000..e353b47
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/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.internal;
+
+/**
+ * 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!");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/AddTagsOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/AddTagsOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/AddTagsOptions.java
new file mode 100644
index 0000000..700f41d
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/AddTagsOptions.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+public class AddTagsOptions extends BaseHttpRequestOptions {
+   public static final String RESOURCE_ID_PARAM = "ResourceId";
+   public static final String RESOURCE_TYPE_PARAM = "ResourceType";
+
+   public AddTagsOptions resourceId(String resourceId) {
+      queryParameters.put(RESOURCE_ID_PARAM, resourceId);
+      return this;
+   }
+
+   public AddTagsOptions resourceType(String resourceType) {
+      queryParameters.put(RESOURCE_TYPE_PARAM, resourceType);
+      return this;
+   }
+
+   public AddTagsOptions tagOptions(final TagOptions tagOptions) {
+      this.queryParameters.putAll(tagOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link AddTagsOptions#resourceId(String)}
+       */
+      public static AddTagsOptions resourceId(String resourceId) {
+         return new AddTagsOptions().resourceId(resourceId);
+      }
+
+      /**
+       * @see {@link AddTagsOptions#resourceType(String)}
+       */
+      public static AddTagsOptions resourceType(String resourceType) {
+         return new AddTagsOptions().resourceType(resourceType);
+      }
+
+      /**
+       * @see ListTagsOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListTagsOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListTagsOptions().paginationOptions(paginationOptions);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateSecurityGroupOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateSecurityGroupOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateSecurityGroupOptions.java
new file mode 100644
index 0000000..b98be4f
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateSecurityGroupOptions.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+public class CreateSecurityGroupOptions extends BaseHttpRequestOptions {
+   public static final String SECURITY_GROUP_NAME_PARAM = "SecurityGroupName";
+   public static final String DESCRIPTION_PARAM = "Description";
+   public static final String VPC_ID_PARAM = "VpcId";
+   public static final String CLIENT_TOKEN_PARAM = "ClientToken";
+
+   public CreateSecurityGroupOptions securityGroupName(String securityGroupName) {
+      queryParameters.put(SECURITY_GROUP_NAME_PARAM, securityGroupName);
+      return this;
+   }
+
+   public CreateSecurityGroupOptions description(String description) {
+      queryParameters.put(DESCRIPTION_PARAM, description);
+      return this;
+   }
+
+   public CreateSecurityGroupOptions vpcId(String vpcId) {
+      queryParameters.put(VPC_ID_PARAM, vpcId);
+      return this;
+   }
+
+   public CreateSecurityGroupOptions clientToken(String clientToken) {
+      queryParameters.put(CLIENT_TOKEN_PARAM, clientToken);
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link CreateSecurityGroupOptions#securityGroupName(String)}
+       */
+      public static CreateSecurityGroupOptions securityGroupName(String securityGroupName) {
+         return new CreateSecurityGroupOptions().securityGroupName(securityGroupName);
+      }
+
+      /**
+       * @see {@link CreateSecurityGroupOptions#description(String)}
+       */
+      public static CreateSecurityGroupOptions description(String description) {
+         return new CreateSecurityGroupOptions().description(description);
+      }
+
+      /**
+       * @see {@link CreateSecurityGroupOptions#vpcId(String)}
+       */
+      public static CreateSecurityGroupOptions vpcId(String vpcId) {
+         return new CreateSecurityGroupOptions().vpcId(vpcId);
+      }
+
+      /**
+       * @see {@link CreateSecurityGroupOptions#clientToken(String)}
+       */
+      public static CreateSecurityGroupOptions clientToken(String clientToken) {
+         return new CreateSecurityGroupOptions().clientToken(clientToken);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/DeleteKeyPairOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/DeleteKeyPairOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/DeleteKeyPairOptions.java
new file mode 100644
index 0000000..c372032
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/DeleteKeyPairOptions.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.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 DeleteKeyPairOptions extends BaseHttpRequestOptions {
+
+   public static final String KEYPAIR_NAMES_PARAM = "KeyPairNames";
+
+   public DeleteKeyPairOptions keyPairNames(String... keyPairNames) {
+      String keyPairNamesAsString = Joiner.on(",")
+            .join(Iterables.transform(Arrays.asList(keyPairNames), new Function<String, String>() {
+               @Override
+               public String apply(String s) {
+                  return new StringBuilder(s.length() + 1).append('"').append(s).append('"').toString();
+               }
+            }));
+      queryParameters.put(KEYPAIR_NAMES_PARAM, String.format("[%s]", keyPairNamesAsString));
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link DeleteKeyPairOptions#keyPairNames(String...)}
+       */
+      public static DeleteKeyPairOptions keyPairNames(String... keyPairNames) {
+         return new DeleteKeyPairOptions().keyPairNames(keyPairNames);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java
----------------------------------------------------------------------
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
index d30c350..34972a4 100644
--- 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
@@ -16,13 +16,9 @@
  */
 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.aliyun.ecs.functions.ArrayToCommaSeparatedString;
 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";
@@ -32,14 +28,7 @@ public class ListImagesOptions extends BaseHttpRequestOptions {
    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));
+      queryParameters.put(IMAGE_ID_PARAM, String.format("[%s]", new ArrayToCommaSeparatedString().apply(instanceIds)));
       return this;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListKeyPairsOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListKeyPairsOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListKeyPairsOptions.java
new file mode 100644
index 0000000..9a7eb09
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListKeyPairsOptions.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+public class ListKeyPairsOptions extends BaseHttpRequestOptions {
+   public static final String KEY_PAIR_FINGERPRINT_PARAM = "KeyPairFingerPrint";
+   public static final String KEY_PAIR_NAME_PARAM = "KeyPairName";
+
+   public ListKeyPairsOptions keyPairFingerPrint(String keyPairFingerPrint) {
+      queryParameters.put(KEY_PAIR_FINGERPRINT_PARAM, keyPairFingerPrint);
+      return this;
+   }
+
+
+   public ListKeyPairsOptions keyPairName(String keyPairName) {
+      queryParameters.put(KEY_PAIR_NAME_PARAM, keyPairName);
+      return this;
+   }
+
+   public ListKeyPairsOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListKeyPairsOptions#keyPairFingerPrint(String)}
+       */
+      public static ListKeyPairsOptions keyPairFingerPrint(String keyPairFingerPrint) {
+         return new ListKeyPairsOptions().keyPairFingerPrint(keyPairFingerPrint);
+      }
+
+      /**
+       * @see {@link ListKeyPairsOptions#keyPairName(String)}
+       */
+      public static ListKeyPairsOptions keyPairName(String keyPairName) {
+         return new ListKeyPairsOptions().keyPairName(keyPairName);
+      }
+
+      /**
+       * @see ListKeyPairsOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListKeyPairsOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListKeyPairsOptions().paginationOptions(paginationOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListSecurityGroupsOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListSecurityGroupsOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListSecurityGroupsOptions.java
new file mode 100644
index 0000000..3f7d215
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListSecurityGroupsOptions.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain.options;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+public class ListSecurityGroupsOptions extends BaseHttpRequestOptions {
+   public static final String VPC_ID_PARAM = "VpcId";
+
+   public ListSecurityGroupsOptions vpcId(String vpcId) {
+      queryParameters.put(VPC_ID_PARAM, vpcId);
+      return this;
+   }
+
+   public ListSecurityGroupsOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public ListSecurityGroupsOptions tagOptions(final TagOptions tagOptions) {
+      this.queryParameters.putAll(tagOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListSecurityGroupsOptions#vpcId(String)}
+       */
+      public static ListSecurityGroupsOptions vpcId(String vpcId) {
+         return new ListSecurityGroupsOptions().vpcId(vpcId);
+      }
+
+      /**
+       * @see ListSecurityGroupsOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListSecurityGroupsOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListSecurityGroupsOptions().paginationOptions(paginationOptions);
+      }
+
+      /**
+       * @see ListSecurityGroupsOptions#tagOptions(TagOptions)
+       */
+      public static ListSecurityGroupsOptions tagOptions(TagOptions tagOptions) {
+         return new ListSecurityGroupsOptions().tagOptions(tagOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListTagsOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListTagsOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListTagsOptions.java
new file mode 100644
index 0000000..05a4621
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListTagsOptions.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+public class ListTagsOptions extends BaseHttpRequestOptions {
+   public static final String RESOURCE_ID_PARAM = "ResourceId";
+   public static final String RESOURCE_TYPE_PARAM = "ResourceType";
+
+   public ListTagsOptions resourceId(String resourceId) {
+      queryParameters.put(RESOURCE_ID_PARAM, resourceId);
+      return this;
+   }
+
+   public ListTagsOptions resourceType(String resourceType) {
+      queryParameters.put(RESOURCE_TYPE_PARAM, resourceType);
+      return this;
+   }
+
+   public ListTagsOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public ListTagsOptions tagOptions(final TagOptions tagOptions) {
+      this.queryParameters.putAll(tagOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListTagsOptions#resourceId(String)}
+       */
+      public static ListTagsOptions resourceId(String resourceId) {
+         return new ListTagsOptions().resourceId(resourceId);
+      }
+
+      /**
+       * @see {@link ListTagsOptions#resourceType(String)}
+       */
+      public static ListTagsOptions resourceType(String resourceType) {
+         return new ListTagsOptions().resourceType(resourceType);
+      }
+
+      /**
+       * @see ListTagsOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListTagsOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListTagsOptions().paginationOptions(paginationOptions);
+      }
+
+      /**
+       * @see ListTagsOptions#tagOptions(TagOptions)
+       */
+      public static ListTagsOptions tagOptions(TagOptions tagOptions) {
+         return new ListTagsOptions().tagOptions(tagOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/TagOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/TagOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/TagOptions.java
new file mode 100644
index 0000000..4e3068e
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/TagOptions.java
@@ -0,0 +1,95 @@
+/*
+ * 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.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+public class TagOptions extends BaseHttpRequestOptions {
+
+   private static final List<String> FORBIDDEN_PREFIX = ImmutableList.of("aliyun", "acs", "http://", "https://");
+   private static final String TAG_KEY_TEMPLATE = "Tag.%d.Key";
+   private static final String TAG_VALUE_TEMPLATE = "Tag.%d.Value";
+
+   /**
+    * Tag keys can be up to 64 characters in length.
+    * Cannot begin with aliyun, acs:, http://, or https://. Cannot be a null string.
+    *
+    * Tag values can be up to 128 characters in length.
+    * Cannot begin with aliyun, http://, or https://. Can be a null string.
+    */
+   public TagOptions tag(int pos, final String key, final String value) {
+      validateInput(key, 64);
+      validateInput(value, 128);
+      queryParameters.put(String.format(TAG_KEY_TEMPLATE, pos), key);
+      queryParameters.put(String.format(TAG_VALUE_TEMPLATE, pos), value);
+      return this;
+   }
+
+   public TagOptions tag(int pos, String key) {
+      validateInput(key, 64);
+      queryParameters.put(String.format(TAG_KEY_TEMPLATE, pos), key);
+      queryParameters.put(String.format(TAG_VALUE_TEMPLATE, pos), "");
+      return this;
+   }
+
+   public TagOptions keys(Set<String> keys) {
+      checkState(keys.size() <= 5, "keys must be <= 5");
+      int i = 1;
+      for (String key : keys) {
+         tag(i, key);
+         i++;
+      }
+      return this;
+   }
+
+   public static class Builder {
+
+      public static TagOptions tag(int pos, String key, String value) {
+         return new TagOptions().tag(pos, key, value);
+      }
+
+      public static TagOptions tag(int pos, String key) {
+         return new TagOptions().tag(pos, key);
+      }
+
+      public static TagOptions keys(Set<String> keys) {
+         return new TagOptions().keys(keys);
+      }
+   }
+
+   private void validateInput(final String input, int maxLength) {
+      checkNotNull(input);
+      checkState(input.length() <= maxLength, String.format("input must be <= %d chars", maxLength));
+      checkState(!Iterables.any(FORBIDDEN_PREFIX, new Predicate<String>() {
+         @Override
+         public boolean apply(@Nullable String input) {
+            return input.startsWith(input);
+         }
+      }), "Cannot starts with " + Iterables.toString(FORBIDDEN_PREFIX));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java
----------------------------------------------------------------------
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
index 68572ef..e45e8d4 100644
--- 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
@@ -17,19 +17,20 @@
 package org.jclouds.aliyun.ecs.features;
 
 import com.google.common.base.Function;
-import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
 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.internal.PaginatedCollection;
 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.collect.internal.ArgsToPagedIterable;
 import org.jclouds.http.functions.ParseJson;
 import org.jclouds.json.Json;
 import org.jclouds.rest.annotations.Fallback;
@@ -45,6 +46,9 @@ import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Map;
 
 /**
  * https://www.alibabacloud.com/help/doc-detail/25534.htm?spm=a2c63.p38356.b99.330.79eb59abhmnMDE
@@ -71,14 +75,22 @@ public interface ImageApi {
    PagedIterable<Image> list(@QueryParam("RegionId") String region);
 
    @Singleton
-   final class ParseImages extends ParseJson<Images> {
+   final class ParseImages extends ParseJson<ParseImages.Images> {
 
       @Inject
       ParseImages(final Json json) {
          super(json, TypeLiteral.get(Images.class));
       }
 
-      static class ToPagedIterable extends Arg0ToPagedIterable<Image, ToPagedIterable> {
+      private static 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);
+         }
+      }
+
+      private static class ToPagedIterable extends ArgsToPagedIterable<Image, ToPagedIterable> {
 
          private final ECSComputeServiceApi api;
 
@@ -88,13 +100,18 @@ public interface ImageApi {
          }
 
          @Override
-         protected Function<Object, IterableWithMarker<Image>> markerToNextForArg0(final Optional<Object> arg0) {
+         protected Function<Object, IterableWithMarker<Image>> markerToNextForArgs(final List<Object> args) {
+            if (args == null || args.isEmpty()) throw new IllegalStateException("Can't advance the PagedIterable");
+            final String regionId = args.get(0).toString();
+            final ListImagesOptions original = (ListImagesOptions) Iterables.tryFind(args, Predicates.instanceOf(ListImagesOptions.class)).orNull();
+
             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);
+                  ListImagesOptions options = original == null ?
+                     ListImagesOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                     original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.imageApi().list(regionId, options);
                }
             };
          }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SecurityGroupApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SecurityGroupApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SecurityGroupApi.java
new file mode 100644
index 0000000..c70bcb6
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SecurityGroupApi.java
@@ -0,0 +1,157 @@
+/*
+ * 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.Predicates;
+import com.google.common.collect.Iterables;
+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.IpProtocol;
+import org.jclouds.aliyun.ecs.domain.Permission;
+import org.jclouds.aliyun.ecs.domain.Request;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.options.CreateSecurityGroupOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListSecurityGroupsOptions;
+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.ArgsToPagedIterable;
+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.SelectJson;
+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.POST;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Map;
+
+@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 SecurityGroupApi {
+
+   @Named("securityGroup:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeSecurityGroups")
+   @ResponseParser(ParseSecurityGroups.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<SecurityGroup> list(@QueryParam("RegionId") String region, ListSecurityGroupsOptions options);
+
+   @Named("securityGroup:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeSecurityGroups")
+   @ResponseParser(ParseSecurityGroups.class)
+   @Transform(ParseSecurityGroups.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<SecurityGroup> list(@QueryParam("RegionId") String region);
+
+   @Singleton
+   final class ParseSecurityGroups extends ParseJson<ParseSecurityGroups.SecurityGroups> {
+
+      @Inject
+      ParseSecurityGroups(final Json json) {
+         super(json, TypeLiteral.get(SecurityGroups.class));
+      }
+
+      private static class SecurityGroups extends PaginatedCollection<SecurityGroup> {
+
+         @ConstructorProperties({"SecurityGroups", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId"})
+         public SecurityGroups(Map<String, Iterable<SecurityGroup>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+
+      private static class ToPagedIterable extends ArgsToPagedIterable<SecurityGroup, ToPagedIterable> {
+
+         private final ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<SecurityGroup>> markerToNextForArgs(List<Object> args) {
+            if (args == null || args.isEmpty()) throw new IllegalStateException("Can't advance the PagedIterable");
+            final String regionId = args.get(0).toString();
+            final ListSecurityGroupsOptions original = (ListSecurityGroupsOptions) Iterables.tryFind(args, Predicates.instanceOf(ListSecurityGroupsOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<SecurityGroup>>() {
+               @Override
+               public IterableWithMarker<SecurityGroup> apply(Object input) {
+                  ListSecurityGroupsOptions options = original == null ?
+                          ListSecurityGroupsOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.securityGroupApi().list(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+   @Named("securityGroup:get")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeSecurityGroupAttribute")
+   @SelectJson("Permission")
+   List<Permission> get(@QueryParam("RegionId") String region, @QueryParam("SecurityGroupId") String securityGroupId);
+
+   @Named("securityGroup:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateSecurityGroup")
+   @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class)
+   SecurityGroupRequest create(@QueryParam("RegionId") String region);
+
+   @Named("securityGroup:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateSecurityGroup")
+   @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class)
+   SecurityGroupRequest create(@QueryParam("RegionId") String region, CreateSecurityGroupOptions options);
+
+   @Named("securityGroup:addInbound")
+   @POST
+   @QueryParams(keys = "Action", values = "AuthorizeSecurityGroup")
+   @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class)
+   Request addInboundRule(@QueryParam("RegionId") String region, @QueryParam("SecurityGroupId") String securityGroupId,
+                          @QueryParam("IpProtocol") IpProtocol ipProtocol, @QueryParam("PortRange") String portRange,
+                          @QueryParam("SourceCidrIp") String sourceCidrIp);
+
+   @Named("securityGroup:delete")
+   @POST
+   @QueryParams(keys = "Action", values = "DeleteSecurityGroup")
+   @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class)
+   Request delete(@QueryParam("RegionId") String region, @QueryParam("SecurityGroupId") String securityGroupId);
+}
+

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SshKeyPairApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SshKeyPairApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SshKeyPairApi.java
new file mode 100644
index 0000000..b207335
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/SshKeyPairApi.java
@@ -0,0 +1,142 @@
+/*
+ * 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.Predicates;
+import com.google.common.collect.Iterables;
+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.KeyPair;
+import org.jclouds.aliyun.ecs.domain.KeyPairRequest;
+import org.jclouds.aliyun.ecs.domain.Request;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.options.ListKeyPairsOptions;
+import org.jclouds.aliyun.ecs.domain.options.PaginationOptions;
+import org.jclouds.aliyun.ecs.filters.FormSign;
+import org.jclouds.aliyun.ecs.functions.ArrayToCommaSeparatedString;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.collect.internal.ArgsToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.ParamParser;
+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.POST;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Map;
+
+@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 SshKeyPairApi {
+
+   @Named("sshKeyPair:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeKeyPairs")
+   @ResponseParser(ParseKeyPairs.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<KeyPair> list(@QueryParam("RegionId") String region, ListKeyPairsOptions options);
+
+   @Named("sshKeyPair:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeKeyPairs")
+   @ResponseParser(ParseKeyPairs.class)
+   @Transform(ParseKeyPairs.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<KeyPair> list(@QueryParam("RegionId") String region);
+
+   @Singleton
+   final class ParseKeyPairs extends ParseJson<ParseKeyPairs.KeyPairs> {
+
+      @Inject
+      ParseKeyPairs(final Json json) {
+         super(json, TypeLiteral.get(KeyPairs.class));
+      }
+
+      private static class KeyPairs extends PaginatedCollection<KeyPair> {
+
+         @ConstructorProperties({ "KeyPairs", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+         public KeyPairs(Map<String, Iterable<KeyPair>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+
+      private static class ToPagedIterable extends ArgsToPagedIterable<KeyPair, ToPagedIterable> {
+
+         private final ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<KeyPair>> markerToNextForArgs(List<Object> args) {
+            if (args == null || args.isEmpty()) throw new IllegalStateException("Can't advance the PagedIterable");
+            final String regionId = args.get(0).toString();
+            final ListKeyPairsOptions original = (ListKeyPairsOptions) Iterables.tryFind(args, Predicates.instanceOf(ListKeyPairsOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<KeyPair>>() {
+               @Override
+               public IterableWithMarker<KeyPair> apply(Object input) {
+                  ListKeyPairsOptions options = original == null ?
+                          ListKeyPairsOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.sshKeyPairApi().list(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+   @Named("sshKeyPair:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateKeyPair")
+   KeyPairRequest create(@QueryParam("RegionId") String region, @QueryParam("KeyPairName") String keyPairName);
+
+   @Named("sshKeyPair:import")
+   @POST
+   @QueryParams(keys = "Action", values = "ImportKeyPair")
+   KeyPair importKeyPair(@QueryParam("RegionId") String region,
+                         @QueryParam("PublicKeyBody") String publicKeyBody,
+                         @QueryParam("KeyPairName") String keyPairName);
+
+   @Named("sshKeyPair:delete")
+   @POST
+   @QueryParams(keys = "Action", values = "DeleteKeyPairs")
+   Request delete(@QueryParam("RegionId") String region,
+                  @ParamParser(ArrayToCommaSeparatedString.class) @QueryParam("KeyPairNames") String... keyPairNames);
+
+}
+

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a5dbf006/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/TagApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/TagApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/TagApi.java
new file mode 100644
index 0000000..67c3b82
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/TagApi.java
@@ -0,0 +1,143 @@
+/*
+ * 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.Predicates;
+import com.google.common.collect.Iterables;
+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.Request;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.options.ListTagsOptions;
+import org.jclouds.aliyun.ecs.domain.options.PaginationOptions;
+import org.jclouds.aliyun.ecs.domain.options.TagOptions;
+import org.jclouds.aliyun.ecs.filters.FormSign;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.collect.internal.ArgsToPagedIterable;
+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.POST;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Map;
+
+@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 TagApi {
+
+   @Named("tag:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeTags")
+   @ResponseParser(ParseTags.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<Tag> list(@QueryParam("RegionId") String region, ListTagsOptions options);
+
+   @Named("tag:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeTags")
+   @ResponseParser(ParseTags.class)
+   @Transform(ParseTags.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<Tag> list(@QueryParam("RegionId") String region);
+
+   @Singleton
+   final class ParseTags extends ParseJson<ParseTags.Tags> {
+
+      @Inject
+      ParseTags(final Json json) {
+         super(json, TypeLiteral.get(Tags.class));
+      }
+
+      private static class Tags extends PaginatedCollection<Tag> {
+
+         @ConstructorProperties({ "Tags", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+         public Tags(Map<String, Iterable<Tag>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+
+      private static class ToPagedIterable extends ArgsToPagedIterable<Tag, ToPagedIterable> {
+
+         private final ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<Tag>> markerToNextForArgs(List<Object> args) {
+            if (args == null || args.isEmpty()) throw new IllegalStateException("Can't advance the PagedIterable");
+            final String regionId = args.get(0).toString();
+            final ListTagsOptions original = (ListTagsOptions) Iterables.tryFind(args, Predicates.instanceOf(ListTagsOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<Tag>>() {
+               @Override
+               public IterableWithMarker<Tag> apply(Object input) {
+                  ListTagsOptions options = original == null ?
+                          ListTagsOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.tagApi().list(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+   @Named("tag:add")
+   @POST
+   @QueryParams(keys = "Action", values = "AddTags")
+   Request add(@QueryParam("RegionId") String region, @QueryParam("ResourceId") String resourceId,
+                            @QueryParam("ResourceType") String resourceType,
+                            TagOptions tagOptions);
+
+   @Named("tag:remove")
+   @POST
+   @QueryParams(keys = "Action", values = "RemoveTags")
+   Request remove(@QueryParam("RegionId") String region,
+                  @QueryParam("ResourceId") String resourceId,
+                  @QueryParam("ResourceType") String resourceType);
+
+   @Named("tag:remove")
+   @POST
+   @QueryParams(keys = "Action", values = "RemoveTags")
+   Request remove(@QueryParam("RegionId") String region,
+                  @QueryParam("ResourceId") String resourceId,
+                  @QueryParam("ResourceType") String resourceType,
+                  TagOptions options);
+}
+