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

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

Repository: jclouds-labs
Updated Branches:
  refs/heads/master a5dbf0065 -> 2c7db7e80


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/instances-first.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/instances-first.json b/aliyun-ecs/src/test/resources/instances-first.json
new file mode 100644
index 0000000..23265a0
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/instances-first.json
@@ -0,0 +1,960 @@
+{
+  "PageNumber": 1,
+  "TotalCount": 20,
+  "PageSize": 10,
+  "RequestId": "D29E9D37-C372-41C2-8E9D-1E5F49042DE4",
+  "Instances": {
+    "Instance": [
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea1",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea2",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea3",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea4",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea5",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea6",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea7",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea8",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea9",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea10",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      }
+    ]
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/instances-last.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/instances-last.json b/aliyun-ecs/src/test/resources/instances-last.json
new file mode 100644
index 0000000..75df755
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/instances-last.json
@@ -0,0 +1,960 @@
+{
+  "PageNumber": 2,
+  "TotalCount": 20,
+  "PageSize": 10,
+  "RequestId": "D29E9D37-1S72-41C2-8E9D-1E5F49042DE1",
+  "Instances": {
+    "Instance": [
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea11",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea12",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea13",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea14",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea15",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea16",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea17",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea18",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea19",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      },
+      {
+        "InnerIpAddress": {
+          "IpAddress": []
+        },
+        "ImageId": "centos_7_04_64_20G_alibase_20180419.vhd",
+        "InstanceTypeFamily": "ecs.t5",
+        "VlanId": "",
+        "NetworkInterfaces": {
+          "NetworkInterface": [
+            {
+              "MacAddress": "00:16:3e:00:75:cc",
+              "PrimaryIpAddress": "192.168.0.141",
+              "NetworkInterfaceId": "eni-gw8d5xjpg9ses2u4jhx3"
+            }
+          ]
+        },
+        "InstanceId": "i-gw8d5xjpg9ses2u0tcea20",
+        "EipAddress": {
+          "IpAddress": "",
+          "AllocationId": "",
+          "InternetChargeType": ""
+        },
+        "InternetMaxBandwidthIn": 0,
+        "ZoneId": "eu-central-1a",
+        "InternetChargeType": "PayByTraffic",
+        "SpotStrategy": "NoSpot",
+        "StoppedMode": "Not-applicable",
+        "SerialNumber": "6061c6ea-b7c1-49f0-9fe7-4444377df09a",
+        "IoOptimized": true,
+        "Memory": 512,
+        "Cpu": 1,
+        "VpcAttributes": {
+          "NatIpAddress": "",
+          "PrivateIpAddress": {
+            "IpAddress": [
+              "192.168.0.141"
+            ]
+          },
+          "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+          "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+        },
+        "InternetMaxBandwidthOut": 5,
+        "DeviceAvailable": true,
+        "SecurityGroupIds": {
+          "SecurityGroupId": [
+            "sg-gw8a9zlvmuyirwxlwsc1"
+          ]
+        },
+        "SaleCycle": "",
+        "SpotPriceLimit": 0.0,
+        "AutoReleaseTime": "",
+        "StartTime": "2018-06-25T19:02Z",
+        "KeyPairName": "jclouds",
+        "InstanceName": "jclouds",
+        "Description": "",
+        "ResourceGroupId": "",
+        "OSType": "linux",
+        "OSName": "CentOS  7.4 64[0xe4][0xbd][0x8d]",
+        "InstanceNetworkType": "vpc",
+        "PublicIpAddress": {
+          "IpAddress": [
+            "47.254.152.220"
+          ]
+        },
+        "HostName": "jclouds",
+        "InstanceType": "ecs.t5-lc2m1.nano",
+        "CreationTime": "2018-06-25T19:01Z",
+        "Tags": {
+          "Tag": [
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "jclouds_tags"
+            },
+            {
+              "TagValue": "aliyun-ecss",
+              "TagKey": "test"
+            }
+          ]
+        },
+        "Status": "Running",
+        "ClusterId": "",
+        "Recyclable": false,
+        "RegionId": "eu-central-1",
+        "GPUSpec": "",
+        "DedicatedHostAttribute": {
+          "DedicatedHostId": "",
+          "DedicatedHostName": ""
+        },
+        "OperationLocks": {
+          "LockReason": []
+        },
+        "InstanceChargeType": "PostPaid",
+        "GPUAmount": 0,
+        "ExpiredTime": "2099-12-31T15:59Z"
+      }
+    ]
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/logback-test.xml
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/logback-test.xml b/aliyun-ecs/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..eecf22d
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/logback-test.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<configuration scan="false">
+    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+        <file>./target/test-data/jclouds.log</file>
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+    <appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
+        <file>./target/test-data/jclouds-wire.log</file>
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+    <appender name="COMPUTEFILE" class="ch.qos.logback.core.FileAppender">
+        <file>./target/test-data/jclouds-compute.log</file>
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.jclouds">
+        <level value="DEBUG" />
+        <appender-ref ref="FILE" />
+    </logger>
+    <logger name="jclouds.compute">
+        <level value="DEBUG" />
+        <appender-ref ref="COMPUTEFILE" />
+    </logger>
+    <logger name="jclouds.wire">
+        <level value="DEBUG" />
+        <appender-ref ref="WIREFILE" />
+    </logger>
+    <logger name="jclouds.headers">
+        <level value="DEBUG" />
+        <appender-ref ref="WIREFILE" />
+    </logger>
+
+    <root>
+        <level value="INFO" />
+    </root>
+</configuration>

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vpc-create-res.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vpc-create-res.json b/aliyun-ecs/src/test/resources/vpc-create-res.json
new file mode 100644
index 0000000..7c17ad3
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vpc-create-res.json
@@ -0,0 +1,6 @@
+{
+  "RequestId": "461D0C42-D5D1-4009-9B6A-B3D5888A19A9",
+  "RouteTableId": "vtb-25wm68mnh",
+  "VRouterId": "vrt-25bezkd03",
+  "VpcId": "vpc-257gq642n"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vpc-delete-res.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vpc-delete-res.json b/aliyun-ecs/src/test/resources/vpc-delete-res.json
new file mode 100644
index 0000000..81b74f1
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vpc-delete-res.json
@@ -0,0 +1,3 @@
+{
+  "RequestId": "0ED8D006-F706-4D23-88ED-E11ED28DCAC0"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vpcs-first.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vpcs-first.json b/aliyun-ecs/src/test/resources/vpcs-first.json
new file mode 100644
index 0000000..9d229a0
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vpcs-first.json
@@ -0,0 +1,29 @@
+{
+  "PageNumber": 1,
+  "TotalCount": 2,
+  "Vpcs": {
+    "Vpc": [
+      {
+        "CreationTime": "2018-06-19T12:43:22Z",
+        "CidrBlock": "192.168.0.0/16",
+        "VpcName": "default",
+        "Status": "Available",
+        "Description": "",
+        "VSwitchIds": {
+          "VSwitchId": [
+            "vsw-gw8c79bsp4ezbe34ij3w8"
+          ]
+        },
+        "IsDefault": false,
+        "UserCidrs": {
+          "UserCidr": []
+        },
+        "RegionId": "eu-central-1",
+        "VRouterId": "vrt-gw8rs2qwhlasfwtqozz3q",
+        "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+      }
+    ]
+  },
+  "PageSize": 1,
+  "RequestId": "F237893B-BC7A-4A3D-B952-6795D6590C4B"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vpcs-last.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vpcs-last.json b/aliyun-ecs/src/test/resources/vpcs-last.json
new file mode 100644
index 0000000..cb377ac
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vpcs-last.json
@@ -0,0 +1,29 @@
+{
+  "PageNumber": 2,
+  "TotalCount": 2,
+  "Vpcs": {
+    "Vpc": [
+      {
+        "CreationTime": "2018-06-19T12:43:23Z",
+        "CidrBlock": "192.168.1.0/16",
+        "VpcName": "test",
+        "Status": "Available",
+        "Description": "",
+        "VSwitchIds": {
+          "VSwitchId": [
+            "vsw-gw8c79bsp4ezbe34ij3w8"
+          ]
+        },
+        "IsDefault": false,
+        "UserCidrs": {
+          "UserCidr": []
+        },
+        "RegionId": "eu-central-1",
+        "VRouterId": "vrt-gw8rs2qwhlasfwtqozz3a",
+        "VpcId": "vpc-gw8vbir5z9j9lncyrjjhe"
+      }
+    ]
+  },
+  "PageSize": 1,
+  "RequestId": "F237893B-BC7A-4A3D-B952-6795D6590C4A"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vswitch-create-res.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vswitch-create-res.json b/aliyun-ecs/src/test/resources/vswitch-create-res.json
new file mode 100644
index 0000000..759faca
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vswitch-create-res.json
@@ -0,0 +1,4 @@
+{
+  "RequestId": "861E6630-AEC0-4B2D-B214-6CB5E44B7F04",
+  "VSwitchId": "vsw-25naue4gz"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vswitch-delete-res.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vswitch-delete-res.json b/aliyun-ecs/src/test/resources/vswitch-delete-res.json
new file mode 100644
index 0000000..971156c
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vswitch-delete-res.json
@@ -0,0 +1,3 @@
+{
+  "RequestId": "AF083E3D-7E29-4B77-A937-1F129802D5F3"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vswitches-first.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vswitches-first.json b/aliyun-ecs/src/test/resources/vswitches-first.json
new file mode 100644
index 0000000..3250ff0
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vswitches-first.json
@@ -0,0 +1,22 @@
+{
+  "PageNumber": 1,
+  "VSwitches": {
+    "VSwitch": [
+      {
+        "CreationTime": "2018-07-17T09:38:54Z",
+        "CidrBlock": "172.16.1.0/24",
+        "Status": "Available",
+        "Description": "",
+        "IsDefault": false,
+        "AvailableIpAddressCount": 252,
+        "VSwitchName": "jclouds-vswitch",
+        "ZoneId": "eu-central-1a",
+        "VSwitchId": "vsw-gw8rg87kqcw1kh2qms0pj",
+        "VpcId": "vpc-gw8myavcsj64kfpp92kd0"
+      }
+    ]
+  },
+  "TotalCount": 2,
+  "PageSize": 1,
+  "RequestId": "58A5DDE8-DFAE-42E8-AAC8-61D385CCA8A0"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/vswitches-last.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/vswitches-last.json b/aliyun-ecs/src/test/resources/vswitches-last.json
new file mode 100644
index 0000000..14258ed
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/vswitches-last.json
@@ -0,0 +1,22 @@
+{
+  "PageNumber": 2,
+  "VSwitches": {
+    "VSwitch": [
+      {
+        "CreationTime": "2018-06-19T12:44:58Z",
+        "CidrBlock": "192.168.0.0/24",
+        "Status": "Available",
+        "Description": "",
+        "IsDefault": false,
+        "AvailableIpAddressCount": 252,
+        "VSwitchName": "default-fra1-a",
+        "ZoneId": "eu-central-1a",
+        "VSwitchId": "vsw-gw8c79bsp4ezbe34ij3w8",
+        "VpcId": "vpc-gw8vbir5z9j9lncyrjjhx"
+      }
+    ]
+  },
+  "TotalCount": 2,
+  "PageSize": 1,
+  "RequestId": "58A5DDE8-DFAE-42E8-AAC8-61D385CCA8A1"
+}
\ No newline at end of file


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSTemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSTemplateBuilderLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSTemplateBuilderLiveTest.java
new file mode 100644
index 0000000..6c0343b
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSTemplateBuilderLiveTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute;
+
+import com.google.common.collect.ImmutableSet;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.internal.BaseTemplateBuilderLiveTest;
+import org.testng.annotations.Test;
+
+import java.util.Set;
+
+import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = "live", testName = "ECSTemplateBuilderLiveTest")
+public class ECSTemplateBuilderLiveTest extends BaseTemplateBuilderLiveTest {
+
+   public ECSTemplateBuilderLiveTest() {
+      provider = "alibaba-ecs";
+   }
+
+   @Test
+   @Override
+   public void testDefaultTemplateBuilder() {
+      Template defaultTemplate = view.getComputeService().templateBuilder().build();
+      assert defaultTemplate.getImage().getOperatingSystem().getVersion().startsWith("7.") : defaultTemplate.getImage()
+            .getOperatingSystem().getVersion();
+      assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true);
+      assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.CENTOS);
+      assertEquals(getCores(defaultTemplate.getHardware()), 1.0d);
+   }
+
+   @Override
+   protected Set<String> getIso3166Codes() {
+      return ImmutableSet.of("US-CA", "US-VA", "DE", "JP", "ID-JK", "SG", "IN", "AU-NSW", "MY", "CN-HE", "CN-SH", "CN-ZJ", "CN-GD", "HK", "AE-DU");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java
----------------------------------------------------------------------
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
index 5fe5c3f..f9e0e66 100644
--- 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
@@ -20,14 +20,13 @@ 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.internal.Regions;
+import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions;
 import org.jclouds.aliyun.ecs.features.ImageApi;
 import org.testng.annotations.Test;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static org.jclouds.aliyun.ecs.domain.options.ListImagesOptions.Builder.imageIds;
-import static org.jclouds.aliyun.ecs.domain.options.PaginationOptions.Builder.pageNumber;
+import static org.jclouds.aliyun.ecs.domain.options.PaginationOptions.Builder.pageSize;
 import static org.testng.Assert.assertTrue;
 import static org.testng.util.Strings.isNullOrEmpty;
 
@@ -36,7 +35,7 @@ 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>() {
+      assertTrue(Iterables.all(api().list(TEST_REGION).concat(), new Predicate<Image>() {
          @Override
          public boolean apply(Image input) {
             found.incrementAndGet();
@@ -48,9 +47,9 @@ public class ImageApiLiveTest extends BaseECSComputeServiceApiLiveTest {
 
    public void testListWithOptions() {
       final AtomicInteger found = new AtomicInteger(0);
-      assertTrue(api().list(Regions.EU_CENTRAL_1.getName(),
-              imageIds("debian_8_09_64_20G_alibase_20170824.vhd")
-              .paginationOptions(pageNumber(3)))
+      assertTrue(api().list(TEST_REGION,
+              ListImagesOptions.Builder.imageName("debian_8_09_64_20G_alibase_20170824.vhd")
+              .paginationOptions(pageSize(1)))
               .firstMatch(new Predicate<Image>() {
                  @Override
                  public boolean apply(Image input) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java
----------------------------------------------------------------------
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
index e61b910..d206e8d 100644
--- 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
@@ -19,7 +19,6 @@ package org.jclouds.aliyun.ecs.compute.features;
 import com.google.common.collect.ImmutableMap;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
 import org.jclouds.aliyun.ecs.domain.Image;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.testng.annotations.Test;
 
 import static com.google.common.collect.Iterables.isEmpty;
@@ -36,35 +35,35 @@ public class ImageApiMockTest extends BaseECSComputeServiceApiMockTest {
       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();
+      Iterable<Image> images = api.imageApi().list(TEST_REGION).concat();
       assertEquals(size(images), 28); // Force the PagedIterable to advance
       assertEquals(server.getRequestCount(), 3);
-      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
-      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 2);
-      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 3);
+      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", TEST_REGION));
+      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", TEST_REGION), 2);
+      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", TEST_REGION), 3);
    }
 
    public void testListImagesReturns404() {
       server.enqueue(response404());
-      Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<Image> images = api.imageApi().list(TEST_REGION).concat();
       assertTrue(isEmpty(images));
       assertEquals(server.getRequestCount(), 1);
    }
 
    public void testListImagesWithOptions() throws InterruptedException {
       server.enqueue(jsonResponse("/images-first.json"));
-      Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName(), paginationOptions(pageNumber(1)));
+      Iterable<Image> images = api.imageApi().list(TEST_REGION, paginationOptions(pageNumber(1)));
       assertEquals(size(images), 10);
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testListImagesWithOptionsReturns404() throws InterruptedException {
       server.enqueue(response404());
-      Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName(), paginationOptions(pageNumber(2)));
+      Iterable<Image> images = api.imageApi().list(TEST_REGION, paginationOptions(pageNumber(2)));
       assertTrue(isEmpty(images));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 2);
+      assertSent(server, "GET", "DescribeImages", ImmutableMap.of("RegionId", TEST_REGION), 2);
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiLiveTest.java
new file mode 100644
index 0000000..1b987fd
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiLiveTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Uninterruptibles;
+import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiLiveTest;
+import org.jclouds.aliyun.ecs.domain.AvailableZone;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.InstanceRequest;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest;
+import org.jclouds.aliyun.ecs.domain.VPC;
+import org.jclouds.aliyun.ecs.domain.VPCRequest;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.VSwitchRequest;
+import org.jclouds.aliyun.ecs.domain.options.CreateInstanceOptions;
+import org.jclouds.aliyun.ecs.domain.options.CreateSecurityGroupOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListInstancesOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVPCsOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.aliyun.ecs.features.InstanceApi;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.DEFAULT_CIDR_BLOCK;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "InstanceApiLiveTest")
+public class InstanceApiLiveTest extends BaseECSComputeServiceApiLiveTest {
+
+   private String imageId = "ubuntu_16_0402_32_20G_alibase_20180409.vhd";
+   private String hostname = "jclouds-test";
+   private String instanceType = "ecs.t5-lc2m1.nano";
+   private String vpcId;
+   private String vSwitchId;
+   private String securityGroupId;
+   private String instanceId;
+   private String slashEncodedInstanceId;
+
+   @BeforeClass
+   public void setUp() {
+      VPCRequest vpcRequest = api.vpcApi().create(TEST_REGION);
+      vpcId = vpcRequest.getVpcId();
+      VPC vpc = api.vpcApi().list(TEST_REGION, ListVPCsOptions.Builder.vpcId(vpcId)).firstMatch(Predicates.notNull()).orNull();
+      while (vpc == null || vpc.status() == VPC.Status.PENDING) {
+         vpc = api.vpcApi().list(TEST_REGION, ListVPCsOptions.Builder.vpcId(vpcId)).firstMatch(Predicates.notNull()).orNull();
+         Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
+      }
+
+      VSwitchRequest vSwitchRequest = api.vSwitchApi().create(TEST_ZONE, DEFAULT_CIDR_BLOCK, vpcId);
+      vSwitchId = vSwitchRequest.getVSwitchId();
+      VSwitch vSwitch = api.vSwitchApi().list(TEST_REGION, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)).firstMatch(Predicates.notNull()).orNull();
+      while (vSwitch == null || vSwitch.status() == VSwitch.Status.PENDING) {
+         vSwitch = api.vSwitchApi().list(TEST_REGION, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)).firstMatch(Predicates.notNull()).orNull();
+         Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
+      }
+
+      SecurityGroupRequest request = api.securityGroupApi().create(TEST_REGION,
+              CreateSecurityGroupOptions.Builder
+                      .securityGroupName(InstanceApiLiveTest.class.getSimpleName())
+              .vpcId(vpcId)
+      );
+      securityGroupId = request.getSecurityGroupId();
+   }
+
+   @AfterClass(alwaysRun = true)
+   public void tearDown() {
+      if (instanceId != null) {
+         api().delete(instanceId);
+      }
+
+      if (securityGroupId != null) {
+         api.securityGroupApi().delete(TEST_REGION, securityGroupId);
+      }
+
+      if (vSwitchId != null) {
+         api.vSwitchApi().delete(TEST_REGION, vSwitchId);
+      }
+
+      if (vpcId != null) {
+         api.vpcApi().delete(TEST_REGION, vpcId);
+      }
+   }
+
+   public void testListInstanceTypeByAvailableZone() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().listInstanceTypesByAvailableZone(TEST_REGION), new Predicate<AvailableZone>() {
+         @Override
+         public boolean apply(AvailableZone input) {
+            found.incrementAndGet();
+            return !input.availableResources().isEmpty();
+         }
+      }), "All available zones must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some instance type to be returned");
+   }
+
+   public void testListInstanceType() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().listTypes(), new Predicate<InstanceType>() {
+         @Override
+         public boolean apply(InstanceType input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All instance types must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some instance type to be returned");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testListInstanceType")
+   public void testListInstance() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list(TEST_REGION).concat(), new Predicate<Instance>() {
+         @Override
+         public boolean apply(Instance input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All instances must have the 'id' field populated");
+   }
+
+   @Test(groups = "live")
+   public void testCreate() {
+      InstanceRequest instanceRequest = api().create(TEST_REGION, imageId, securityGroupId, hostname, instanceType,
+            CreateInstanceOptions.Builder.vSwitchId(vSwitchId));
+      instanceId = instanceRequest.getInstanceId();
+      slashEncodedInstanceId = RegionAndId.slashEncodeRegionAndId(TEST_REGION, instanceId);
+      assertNotNull(instanceId, "Instance id must not be null");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testCreate")
+   public void testGet() {
+      Instance instance = Iterables.getOnlyElement(api().list(TEST_REGION,
+              ListInstancesOptions.Builder.instanceIds(instanceId)));
+      assertNotNull(instance.id(), "Instance must not be null");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testCreate")
+   public void testListInstanceStatus() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().listInstanceStatus(TEST_REGION).concat(), new Predicate<InstanceStatus>() {
+         @Override
+         public boolean apply(InstanceStatus input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.instanceId());
+         }
+      }), "All instance status must have the 'instance id' field populated");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testGet")
+   public void testStartInstance() {
+      api().powerOn(instanceId);
+      assertTrue(instanceRunningPredicate.apply(slashEncodedInstanceId));
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testStartInstance")
+   public void testStopInstance() {
+      api().powerOff(instanceId);
+      assertTrue(instanceSuspendedPredicate.apply(slashEncodedInstanceId));
+   }
+
+   private InstanceApi api() {
+      return api.instanceApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiMockTest.java
new file mode 100644
index 0000000..95609b9
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/InstanceApiMockTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.collect.Iterables;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
+import org.jclouds.aliyun.ecs.domain.AvailableZone;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.aliyun.ecs.domain.options.CreateInstanceOptions;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+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.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "InstanceApiMockTest", singleThreaded = true)
+public class InstanceApiMockTest extends BaseECSComputeServiceApiMockTest {
+
+   public void testListInstances() throws InterruptedException {
+      server.enqueue(jsonResponse("/instances-first.json"));
+      server.enqueue(jsonResponse("/instances-last.json"));
+
+      Iterable<Instance> instances = api.instanceApi().list(TEST_REGION).concat();
+      assertEquals(size(instances), 20); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+      assertSent(server, "GET", "DescribeInstances");
+      assertSent(server, "GET", "DescribeInstances", 2);
+   }
+
+   public void testListInstancesReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      Iterable<Instance> instances = api.instanceApi().list(TEST_REGION).concat();
+      assertTrue(isEmpty(instances));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeInstances");
+   }
+
+   public void testListInstanceTypes() throws InterruptedException {
+      server.enqueue(jsonResponse("/instanceTypes.json"));
+
+      List<InstanceType> instanceTypes = api.instanceApi().listTypes();
+      assertEquals(size(instanceTypes), 308);
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeInstanceTypes");
+   }
+
+   public void testListInstanceTypesReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      List<InstanceType> instanceTypes = api.instanceApi().listTypes();
+      assertTrue(isEmpty(instanceTypes));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeInstanceTypes");
+   }
+
+   public void testListInstanceTypesByAvailableZone() throws InterruptedException {
+      server.enqueue(jsonResponse("/availableZones.json"));
+
+      List<AvailableZone> availableZones = api.instanceApi().listInstanceTypesByAvailableZone(TEST_REGION);
+      assertEquals(size(availableZones), 2);
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeAvailableResource");
+   }
+
+   public void testListInstanceTypesByAvailableZoneReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      List<AvailableZone> availableZones = api.instanceApi().listInstanceTypesByAvailableZone(TEST_REGION);
+      assertTrue(isEmpty(availableZones));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeAvailableResource");
+   }
+
+   public void testCreateInstance() throws InterruptedException {
+      server.enqueue(new MockResponse().setResponseCode(200).setBody(
+                      "{" +
+                      "    \"RequestId\": \"04F0F334-1335-436C-A1D7-6C044FE73368\"," +
+                      "    \"InstanceId\": \"i-instance1\"" +
+                      "}"));
+      api.instanceApi().create(TEST_REGION,
+              "test",
+              "SecurityGroupId",
+              "Hostname",
+              "InstanceType"
+      );
+      assertSent(server, "POST", "CreateInstance");
+   }
+
+   public void testCreateInstanceWithInstanceName() throws InterruptedException {
+      server.enqueue(new MockResponse().setResponseCode(200).setBody(
+                      "{" +
+                      "    \"RequestId\": \"04F0F334-1335-436C-A1D7-6C044FE73368\"," +
+                      "    \"InstanceId\": \"i-instance1\"" +
+                      "}"));
+      api.instanceApi().create(TEST_REGION,
+              "test",
+              "SecurityGroupId",
+              "Hostname",
+              "InstanceType",
+              CreateInstanceOptions.Builder.instanceName("jclouds")
+      );
+      assertSent(server, "POST", "CreateInstance");
+   }
+
+   public void testAllocatePublicIpAddress() throws InterruptedException {
+      server.enqueue(new MockResponse().setResponseCode(200).setBody(
+                      "{" +
+                      "    \"RequestId\": \"F2EF6A3B-E345-46B9-931E-0EA094818567\"," +
+                      "    \"IpAddress\": \"10.1.149.159\"" +
+                      "}"));
+      api.instanceApi().create(TEST_REGION,
+              "test",
+              "SecurityGroupId",
+              "Hostname",
+              "InstanceType"
+      );
+      assertSent(server, "POST", "CreateInstance");
+   }
+
+   public void testGetStatus() throws Exception {
+      server.enqueue(jsonResponse("/instanceStatus.json"));
+      Iterable<InstanceStatus> instanceStatuses = api.instanceApi().listInstanceStatus("12345").concat();
+      assertSent(server, "GET", "DescribeInstanceStatus");
+      assertNotNull(instanceStatuses);
+   }
+
+   public void testGetStatusReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      Iterable<InstanceStatus> instanceStatuses = api.instanceApi().listInstanceStatus("12345").concat();
+      assertTrue(Iterables.isEmpty(instanceStatuses));
+      assertSent(server, "GET", "DescribeInstanceStatus");
+   }
+
+   public void testDeleteInstance() throws Exception {
+      server.enqueue(new MockResponse().setResponseCode(200).setBody(
+              "{" +
+              "    \"RequestId\": \"928E2273-5715-46B9-A730-238DC996A533\"" +
+              "}"));
+      api.instanceApi().delete("instanceId");
+      assertSent(server, "POST", "DeleteInstance");
+   }
+
+   public void testDeleteInstanceReturns404() throws Exception {
+      server.enqueue(response404());
+      api.instanceApi().delete("instanceId");
+      assertSent(server, "POST", "DeleteInstance");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/RegionAndZoneApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/RegionAndZoneApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/RegionAndZoneApiMockTest.java
index e116845..46badaf 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/RegionAndZoneApiMockTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/RegionAndZoneApiMockTest.java
@@ -19,7 +19,6 @@ package org.jclouds.aliyun.ecs.compute.features;
 import com.google.common.collect.ImmutableMap;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
 import org.jclouds.aliyun.ecs.domain.Region;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.domain.Zone;
 import org.testng.annotations.Test;
 
@@ -51,18 +50,18 @@ public class RegionAndZoneApiMockTest extends BaseECSComputeServiceApiMockTest {
 
    public void testListZones() throws InterruptedException {
       server.enqueue(jsonResponse("/zones.json"));
-      List<Zone> zones = api.regionAndZoneApi().describeZones(Regions.EU_CENTRAL_1.getName());
+      List<Zone> zones = api.regionAndZoneApi().describeZones(TEST_REGION);
       assertEquals(size(zones), 2);
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeZones", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "GET", "DescribeZones", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testListZonesReturns404() throws InterruptedException {
       server.enqueue(response404());
-      List<Zone> zones = api.regionAndZoneApi().describeZones(Regions.EU_CENTRAL_1.getName());
+      List<Zone> zones = api.regionAndZoneApi().describeZones(TEST_REGION);
       assertTrue(isEmpty(zones));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeZones", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "GET", "DescribeZones", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiLiveTest.java
index 5c76fc5..6fde29e 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiLiveTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiLiveTest.java
@@ -21,7 +21,6 @@ import com.google.common.collect.Iterables;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiLiveTest;
 import org.jclouds.aliyun.ecs.domain.IpProtocol;
 import org.jclouds.aliyun.ecs.domain.Permission;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.domain.SecurityGroup;
 import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest;
 import org.jclouds.aliyun.ecs.domain.options.CreateSecurityGroupOptions;
@@ -46,7 +45,7 @@ public class SecurityGroupApiLiveTest extends BaseECSComputeServiceApiLiveTest {
 
    @BeforeClass
    public void setUp() {
-      SecurityGroupRequest request = api().create(Regions.EU_CENTRAL_1.getName(),
+      SecurityGroupRequest request = api().create(TEST_REGION,
             CreateSecurityGroupOptions.Builder
                   .securityGroupName("jclouds-test")
       );
@@ -56,24 +55,24 @@ public class SecurityGroupApiLiveTest extends BaseECSComputeServiceApiLiveTest {
    @AfterClass
    public void tearDown() {
       if (securityGroupId != null) {
-         api().delete(Regions.EU_CENTRAL_1.getName(), securityGroupId);
+         api().delete(TEST_REGION, securityGroupId);
       }
    }
 
    public void testAddRules() {
-      api().addInboundRule(Regions.EU_CENTRAL_1.getName(), securityGroupId, IpProtocol.TCP, TEST_PORT_RANGE, INTERNET);
+      api().addInboundRule(TEST_REGION, securityGroupId, IpProtocol.TCP, TEST_PORT_RANGE, INTERNET);
    }
 
    @Test(groups = "live", dependsOnMethods = "testAddRules")
    public void testGet() {
-      Permission permission = Iterables.getOnlyElement(api().get(Regions.EU_CENTRAL_1.getName(), securityGroupId));
+      Permission permission = Iterables.getOnlyElement(api().get(TEST_REGION, securityGroupId));
       checkPermission(permission);
    }
 
    @Test(groups = "live", dependsOnMethods = "testGet")
    public void testList() {
       final AtomicInteger found = new AtomicInteger(0);
-      assertTrue(Iterables.all(api().list(Regions.EU_CENTRAL_1.getName()).concat(), new Predicate<SecurityGroup>() {
+      assertTrue(Iterables.all(api().list(TEST_REGION).concat(), new Predicate<SecurityGroup>() {
          @Override
          public boolean apply(SecurityGroup input) {
             found.incrementAndGet();

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiMockTest.java
index 46aa76b..a7999a3 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiMockTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SecurityGroupApiMockTest.java
@@ -18,7 +18,6 @@ package org.jclouds.aliyun.ecs.compute.features;
 
 import com.google.common.collect.ImmutableMap;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.domain.SecurityGroup;
 import org.testng.annotations.Test;
 
@@ -35,35 +34,35 @@ public class SecurityGroupApiMockTest extends BaseECSComputeServiceApiMockTest {
    public void testListSecurityGroups() throws InterruptedException {
       server.enqueue(jsonResponse("/securitygroups-first.json"));
       server.enqueue(jsonResponse("/securitygroups-last.json"));
-      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(TEST_REGION).concat();
       assertEquals(size(securitygroups), 7); // Force the PagedIterable to advance
       assertEquals(server.getRequestCount(), 2);
-      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
-      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 2);
+      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", TEST_REGION));
+      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", TEST_REGION), 2);
    }
 
    public void testListSecurityGroupsReturns404() throws InterruptedException {
       server.enqueue(response404());
-      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(TEST_REGION).concat();
       assertTrue(isEmpty(securitygroups));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testListSecurityGroupsWithOptions() throws InterruptedException {
       server.enqueue(jsonResponse("/securitygroups-first.json"));
-      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(Regions.EU_CENTRAL_1.getName(), paginationOptions(pageNumber(1).pageSize(5)));
+      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(TEST_REGION, paginationOptions(pageNumber(1).pageSize(5)));
       assertEquals(size(securitygroups), 5);
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 1);
+      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", TEST_REGION), 1);
    }
 
    public void testListSecurityGroupsWithOptionsReturns404() throws InterruptedException {
       server.enqueue(response404());
-      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(Regions.EU_CENTRAL_1.getName(), paginationOptions(pageNumber(2)));
+      Iterable<SecurityGroup> securitygroups = api.securityGroupApi().list(TEST_REGION, paginationOptions(pageNumber(2)));
       assertTrue(isEmpty(securitygroups));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 2);
+      assertSent(server, "GET", "DescribeSecurityGroups", ImmutableMap.of("RegionId", TEST_REGION), 2);
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiLiveTest.java
index 85ceee5..632e06e 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiLiveTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiLiveTest.java
@@ -21,9 +21,8 @@ import com.google.common.collect.Iterables;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiLiveTest;
 import org.jclouds.aliyun.ecs.domain.KeyPair;
 import org.jclouds.aliyun.ecs.domain.KeyPairRequest;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.features.SshKeyPairApi;
-import org.jclouds.ssh.SshKeys;
+import org.jclouds.compute.ComputeTestUtils;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -43,22 +42,22 @@ public class SshKeyPairApiLiveTest extends BaseECSComputeServiceApiLiveTest {
 
    @BeforeClass
    public void setUp() {
-      KeyPairRequest request = api().create(Regions.EU_CENTRAL_1.getName(), keyPairName);
+      KeyPairRequest request = api().create(TEST_REGION, keyPairName);
       assertNotNull(request.getRequestId());
    }
 
    @AfterClass
    public void tearDown() {
       if (keyPairName != null) {
-         api().delete(Regions.EU_CENTRAL_1.getName(), keyPairName);
+         api().delete(TEST_REGION, keyPairName);
       }
    }
 
    public void testImport() {
       String importedKeyPairName = keyPairName  + new Random().nextInt(1024);
       KeyPair imported = api().importKeyPair(
-              Regions.EU_CENTRAL_1.getName(),
-              SshKeys.generate().get("public"),
+              TEST_REGION,
+              ComputeTestUtils.setupKeyPair().get("public"), //SshKeys.generate().get("public"),
               importedKeyPairName);
       assertEquals(imported.name(), importedKeyPairName);
       assertNotNull(imported.privateKeyBody());
@@ -67,7 +66,7 @@ public class SshKeyPairApiLiveTest extends BaseECSComputeServiceApiLiveTest {
 
    public void testList() {
       final AtomicInteger found = new AtomicInteger(0);
-      assertTrue(Iterables.all(api().list(Regions.EU_CENTRAL_1.getName()).concat(), new Predicate<KeyPair>() {
+      assertTrue(Iterables.all(api().list(TEST_REGION).concat(), new Predicate<KeyPair>() {
          @Override
          public boolean apply(KeyPair input) {
             found.incrementAndGet();

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiMockTest.java
index 23c21ad..78f5a39 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiMockTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/SshKeyPairApiMockTest.java
@@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableMap;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
 import org.jclouds.aliyun.ecs.domain.KeyPair;
 import org.jclouds.aliyun.ecs.domain.KeyPairRequest;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.domain.Request;
 import org.jclouds.aliyun.ecs.domain.options.ListKeyPairsOptions;
 import org.jclouds.aliyun.ecs.domain.options.PaginationOptions;
@@ -37,37 +36,37 @@ public class SshKeyPairApiMockTest extends BaseECSComputeServiceApiMockTest {
 
    public void testCreateSshKey() throws InterruptedException {
       server.enqueue(jsonResponse("/keypair-create-res.json"));
-      KeyPairRequest keyPairRequest = api.sshKeyPairApi().create(Regions.EU_CENTRAL_1.getName(), "jclouds");
+      KeyPairRequest keyPairRequest = api.sshKeyPairApi().create(TEST_REGION, "jclouds");
       assertEquals(keyPairRequest, objectFromResource("/keypair-create-res.json", KeyPairRequest.class));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "CreateKeyPair", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "POST", "CreateKeyPair", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testDeleteSshKey() throws InterruptedException {
       server.enqueue(jsonResponse("/keypair-delete-res.json"));
-      Request delete = api.sshKeyPairApi().delete(Regions.EU_CENTRAL_1.getName());
+      Request delete = api.sshKeyPairApi().delete(TEST_REGION);
       assertEquals(delete, objectFromResource("/keypair-delete-res.json", Request.class));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "DeleteKeyPairs", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "POST", "DeleteKeyPairs", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testImportSshKey() throws InterruptedException {
       server.enqueue(jsonResponse("/keypair-import-res.json"));
       KeyPair keyPair = api.sshKeyPairApi().importKeyPair(
-              Regions.EU_CENTRAL_1.getName(),
+              TEST_REGION,
                             "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdgcoNzH4hCc0j3b4MuG503L/J54uyFvwCAOu8vSsYuLpJ4AEyEOv+T0SfdF605fK6GYXA16Rxk3lrPt7mfKGNtXR0Ripbv7Zc6PvCRorwgj/cjh/45miozjrkXAiHD1GFZycfbi4YsoWAqZj7W4mwtctmhrYM0FPdya2XoRpVy89N+A5Xo4Xtd6EZn6JGEKQM5+kF2aL3ggy0od/DqjuEVYwZoyTe1RgUTXZSU/Woh7WMhsRHbqd3eYz4s6ac8n8IJPGKtUaQeqUtH7OK6NRYXVypUrkqNlwdNYZAwrjXg/x5T3D+bo11LENASRt9OJ2OkmRSTqRxBeDkhnVauWK/",
               "jclouds"
       );
       assertEquals(keyPair, objectFromResource("/keypair-import-res.json", KeyPair.class));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "ImportKeyPair", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "POST", "ImportKeyPair", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testListImages() throws InterruptedException {
       server.enqueue(jsonResponse("/keypairs-first.json"));
       server.enqueue(jsonResponse("/keypairs-last.json"));
 
-      Iterable<KeyPair> keypairs = api.sshKeyPairApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<KeyPair> keypairs = api.sshKeyPairApi().list(TEST_REGION).concat();
       assertEquals(size(keypairs), 12);
       assertEquals(server.getRequestCount(), 2);
       assertSent(server, "GET", "DescribeKeyPairs");
@@ -76,7 +75,7 @@ public class SshKeyPairApiMockTest extends BaseECSComputeServiceApiMockTest {
 
    public void testListKeyPairsReturns404() {
       server.enqueue(response404());
-      Iterable<KeyPair> keypairs = api.sshKeyPairApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<KeyPair> keypairs = api.sshKeyPairApi().list(TEST_REGION).concat();
       assertTrue(isEmpty(keypairs));
       assertEquals(server.getRequestCount(), 1);
    }
@@ -84,7 +83,7 @@ public class SshKeyPairApiMockTest extends BaseECSComputeServiceApiMockTest {
    public void testListKeyPairsWithOptions() throws InterruptedException {
       server.enqueue(jsonResponse("/keypairs-first.json"));
 
-      IterableWithMarker<KeyPair> keypairs = api.sshKeyPairApi().list(Regions.EU_CENTRAL_1.getName(), ListKeyPairsOptions.Builder
+      IterableWithMarker<KeyPair> keypairs = api.sshKeyPairApi().list(TEST_REGION, ListKeyPairsOptions.Builder
               .paginationOptions(PaginationOptions.Builder.pageNumber(1)));
 
       assertEquals(size(keypairs), 10);
@@ -96,7 +95,7 @@ public class SshKeyPairApiMockTest extends BaseECSComputeServiceApiMockTest {
    public void testListKeyPairsWithOptionsReturns404() throws InterruptedException {
       server.enqueue(response404());
 
-      IterableWithMarker<KeyPair> keypairs = api.sshKeyPairApi().list(Regions.EU_CENTRAL_1.getName(), ListKeyPairsOptions.Builder
+      IterableWithMarker<KeyPair> keypairs = api.sshKeyPairApi().list(TEST_REGION, ListKeyPairsOptions.Builder
               .paginationOptions(PaginationOptions.Builder.pageNumber(2)));
 
       assertTrue(isEmpty(keypairs));

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiLiveTest.java
index 854b494..cd07ae2 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiLiveTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiLiveTest.java
@@ -18,7 +18,6 @@ package org.jclouds.aliyun.ecs.compute.features;
 
 import com.google.common.base.Predicate;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiLiveTest;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.domain.Request;
 import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest;
 import org.jclouds.aliyun.ecs.domain.Tag;
@@ -32,6 +31,7 @@ import org.testng.annotations.Test;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.jclouds.aliyun.ecs.domain.ResourceType.SECURITYGROUP;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
@@ -40,38 +40,36 @@ import static org.testng.util.Strings.isNullOrEmpty;
 @Test(groups = "live", testName = "TagApiLiveTest")
 public class TagApiLiveTest extends BaseECSComputeServiceApiLiveTest {
 
-   public static final String RESOURCE_TYPE = "securitygroup";
-
    private String securityGroupName = "pre-test-security";
    private String securityGroupId;
 
    @BeforeClass
    public void setUp() {
-      SecurityGroupRequest preRequisite = api.securityGroupApi().create(Regions.EU_CENTRAL_1.getName(),
+      SecurityGroupRequest preRequisite = api.securityGroupApi().create(TEST_REGION,
               CreateSecurityGroupOptions.Builder.securityGroupName(securityGroupName)
       );
       securityGroupId = preRequisite.getSecurityGroupId();
-      Request request = api().add(Regions.EU_CENTRAL_1.getName(), securityGroupId, RESOURCE_TYPE,
+      Request request = api().add(TEST_REGION, securityGroupId, SECURITYGROUP,
               TagOptions.Builder.tag(1, "owner"));
       assertNotNull(request.getRequestId());
    }
 
    @AfterClass
    public void tearDown() {
-      api().remove(Regions.EU_CENTRAL_1.getName(), securityGroupId, RESOURCE_TYPE);
+      api().remove(TEST_REGION, securityGroupId, SECURITYGROUP);
       if (securityGroupId != null) {
-         api.securityGroupApi().delete(Regions.EU_CENTRAL_1.getName(), securityGroupId);
+         api.securityGroupApi().delete(TEST_REGION, securityGroupId);
       }
    }
 
    public void testList() {
       final AtomicInteger found = new AtomicInteger(0);
-      assertFalse(api().list(Regions.EU_CENTRAL_1.getName(), ListTagsOptions.Builder.resourceId(securityGroupId))
+      assertFalse(api().list(TEST_REGION, ListTagsOptions.Builder.resourceId(securityGroupId))
               .filter(new Predicate<Tag>() {
                  @Override
                  public boolean apply(Tag input) {
                     found.incrementAndGet();
-                    return !isNullOrEmpty(input.tagKey());
+                    return !isNullOrEmpty(input.key());
                  }
               }).isEmpty(), "All tags must have the 'key' field populated");
       assertTrue(found.get() > 0, "Expected some tags to be returned");

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiMockTest.java
index deb492d..350a218 100644
--- a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiMockTest.java
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/TagApiMockTest.java
@@ -18,7 +18,6 @@ package org.jclouds.aliyun.ecs.compute.features;
 
 import com.google.common.collect.ImmutableMap;
 import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
-import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.aliyun.ecs.domain.Tag;
 import org.testng.annotations.Test;
 
@@ -35,35 +34,35 @@ public class TagApiMockTest extends BaseECSComputeServiceApiMockTest {
    public void testListTags() throws InterruptedException {
       server.enqueue(jsonResponse("/tags-first.json"));
       server.enqueue(jsonResponse("/tags-last.json"));
-      Iterable<Tag> tags = api.tagApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<Tag> tags = api.tagApi().list(TEST_REGION).concat();
       assertEquals(size(tags), 10); // Force the PagedIterable to advance
       assertEquals(server.getRequestCount(), 2);
-      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
-      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 2);
+      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", TEST_REGION));
+      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", TEST_REGION), 2);
    }
 
    public void testListTagsReturns404() throws InterruptedException {
       server.enqueue(response404());
-      Iterable<Tag> tags = api.tagApi().list(Regions.EU_CENTRAL_1.getName()).concat();
+      Iterable<Tag> tags = api.tagApi().list(TEST_REGION).concat();
       assertTrue(isEmpty(tags));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()));
+      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", TEST_REGION));
    }
 
    public void testListTagsWithOptions() throws InterruptedException {
       server.enqueue(jsonResponse("/tags-first.json"));
-      Iterable<Tag> tags = api.tagApi().list(Regions.EU_CENTRAL_1.getName(), paginationOptions(pageNumber(1).pageSize(5)));
+      Iterable<Tag> tags = api.tagApi().list(TEST_REGION, paginationOptions(pageNumber(1).pageSize(5)));
       assertEquals(size(tags), 8);
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 1);
+      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", TEST_REGION), 1);
    }
 
    public void testListTagsWithOptionsReturns404() throws InterruptedException {
       server.enqueue(response404());
-      Iterable<Tag> tags = api.tagApi().list(Regions.EU_CENTRAL_1.getName(), paginationOptions(pageNumber(2)));
+      Iterable<Tag> tags = api.tagApi().list(TEST_REGION, paginationOptions(pageNumber(2)));
       assertTrue(isEmpty(tags));
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", Regions.EU_CENTRAL_1.getName()), 2);
+      assertSent(server, "GET", "DescribeTags", ImmutableMap.of("RegionId", TEST_REGION), 2);
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiLiveTest.java
new file mode 100644
index 0000000..8d2639e
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiLiveTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.VPC;
+import org.jclouds.aliyun.ecs.domain.VPCRequest;
+import org.jclouds.aliyun.ecs.domain.options.CreateVPCOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVPCsOptions;
+import org.jclouds.aliyun.ecs.features.VPCApi;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.jclouds.aliyun.ecs.domain.options.PaginationOptions.Builder.pageSize;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "VPCApiLiveTest")
+public class VPCApiLiveTest extends BaseECSComputeServiceApiLiveTest {
+
+   public static final String VPC_NAME = "jclouds-vpc";
+
+   private String vpcId;
+
+   @BeforeClass
+   public void setUp() {
+      VPCRequest vpcRequest = api().create(TEST_REGION, CreateVPCOptions.Builder.vpcName(VPC_NAME));
+      assertNotNull(vpcRequest.getRequestId());
+      assertNotNull(vpcRequest.getVpcId());
+      vpcId = vpcRequest.getVpcId();
+   }
+
+   @AfterClass(alwaysRun = true)
+   public void tearDown() {
+      if (vpcId != null) {
+         assertNotNull(api().delete(TEST_REGION, vpcId));
+      }
+   }
+
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list(TEST_REGION).concat(), new Predicate<VPC>() {
+         @Override
+         public boolean apply(VPC input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All vpcs must have at least the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some vpc to be returned");
+   }
+
+   public void testListWithOptions() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(TEST_REGION, ListVPCsOptions.Builder.vpcId(vpcId)
+                  .paginationOptions(pageSize(50)))
+            .firstMatch(new Predicate<VPC>() {
+               @Override
+               public boolean apply(VPC input) {
+                  found.incrementAndGet();
+                  return !isNullOrEmpty(input.id());
+               }
+            }).isPresent(), "All vpcs must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some image to be returned");
+   }
+
+   private VPCApi api() {
+      return api.vpcApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiMockTest.java
new file mode 100644
index 0000000..80de7f3
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VPCApiMockTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.features;
+
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
+import org.jclouds.aliyun.ecs.domain.Request;
+import org.jclouds.aliyun.ecs.domain.VPC;
+import org.jclouds.aliyun.ecs.domain.VPCRequest;
+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.jclouds.aliyun.ecs.domain.options.ListVPCsOptions.Builder.paginationOptions;
+import static org.jclouds.aliyun.ecs.domain.options.PaginationOptions.Builder.pageNumber;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "VPCApiMockTest", singleThreaded = true)
+public class VPCApiMockTest extends BaseECSComputeServiceApiMockTest {
+
+   public void testListVPCs() throws InterruptedException {
+      server.enqueue(jsonResponse("/vpcs-first.json"));
+      server.enqueue(jsonResponse("/vpcs-last.json"));
+      Iterable<VPC> vpcs = api.vpcApi().list(TEST_REGION).concat();
+      assertEquals(size(vpcs), 2); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+      assertSent(server, "GET", "DescribeVpcs", ImmutableMap.of("RegionId", TEST_REGION));
+      assertSent(server, "GET", "DescribeVpcs", ImmutableMap.of("RegionId", TEST_REGION), 2);
+   }
+
+   public void testListVPCsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      Iterable<VPC> vpcs = api.vpcApi().list(TEST_REGION).concat();
+      assertTrue(isEmpty(vpcs));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeVpcs", ImmutableMap.of("RegionId", TEST_REGION));
+   }
+
+   public void testListVPCsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/vpcs-first.json"));
+      IterableWithMarker<VPC> vpcs = api.vpcApi().list(TEST_REGION, paginationOptions(pageNumber(1).pageSize(5)));
+      assertEquals(size(vpcs), 1);
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeVpcs", ImmutableMap.of("RegionId", TEST_REGION), 1);
+   }
+
+   public void testListVPCsWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      Iterable<VPC> vpcs = api.vpcApi().list(TEST_REGION, paginationOptions(pageNumber(2)));
+      assertTrue(isEmpty(vpcs));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeVpcs", ImmutableMap.of("RegionId", TEST_REGION), 2);
+   }
+
+   public void testCreateVPC() throws InterruptedException {
+      server.enqueue(jsonResponse("/vpc-create-res.json"));
+      VPCRequest vpcRequest = api.vpcApi().create(TEST_REGION);
+      assertEquals(vpcRequest, objectFromResource("/vpc-create-res.json", VPCRequest.class));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "CreateVpc", ImmutableMap.of("RegionId", TEST_REGION));
+   }
+
+   public void testDeleteVPC() throws InterruptedException {
+      server.enqueue(jsonResponse("/vpc-delete-res.json"));
+      Request delete = api.vpcApi().delete(TEST_REGION, "vpc-123456789");
+      assertEquals(delete, objectFromResource("/vpc-delete-res.json", Request.class));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "DeleteVpc", ImmutableMap.of("RegionId", TEST_REGION, "VpcId", "vpc-123456789"));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiLiveTest.java
new file mode 100644
index 0000000..c7abc40
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiLiveTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.VPCRequest;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.VSwitchRequest;
+import org.jclouds.aliyun.ecs.domain.options.CreateVSwitchOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+import org.jclouds.aliyun.ecs.features.VSwitchApi;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.jclouds.aliyun.ecs.domain.options.PaginationOptions.Builder.pageSize;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "VSwitchApiLiveTest")
+public class VSwitchApiLiveTest extends BaseECSComputeServiceApiLiveTest {
+
+   public static final String VSWITCH_NAME = "jclouds-vswitch";
+   public static final String DEFAULT_CIDR_BLOCK = "172.16.1.0/24";
+   private String vpcId;
+   private String vSwitchId;
+
+   @BeforeClass
+   public void setUp() {
+      VPCRequest preRequisite = api.vpcApi().create(TEST_REGION);
+      vpcId = preRequisite.getVpcId();
+      VSwitchRequest vpcRequest = api().create(
+              TEST_ZONE,
+              DEFAULT_CIDR_BLOCK,
+              vpcId,
+              CreateVSwitchOptions.Builder.vSwitchName(VSWITCH_NAME));
+      assertNotNull(vpcRequest.getRequestId());
+      assertNotNull(vpcRequest.getVSwitchId());
+      vSwitchId = vpcRequest.getVSwitchId();
+   }
+
+   @AfterClass(alwaysRun = true)
+   public void tearDown() {
+      if (vSwitchId != null) {
+         assertNotNull(api().delete(TEST_REGION, vSwitchId));
+      }
+      if (vpcId != null) {
+         assertNotNull(api.vpcApi().delete(TEST_REGION, vpcId));
+      }
+   }
+
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list(TEST_REGION).concat(), new Predicate<VSwitch>() {
+         @Override
+         public boolean apply(VSwitch input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All vSwitches must have at least the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some vSwitch to be returned");
+   }
+
+   public void testListWithOptions() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(TEST_REGION, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)
+            .paginationOptions(pageSize(50)))
+            .firstMatch(new Predicate<VSwitch>() {
+               @Override
+               public boolean apply(VSwitch input) {
+                  found.incrementAndGet();
+                  return !isNullOrEmpty(input.id());
+               }
+            }).isPresent(), "All vSwitches must have at least the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some vSwitch to be returned");
+   }
+
+   private VSwitchApi api() {
+      return api.vSwitchApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiMockTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiMockTest.java
new file mode 100644
index 0000000..b80111d
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/VSwitchApiMockTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.features;
+
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest;
+import org.jclouds.aliyun.ecs.domain.Request;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.VSwitchRequest;
+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.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions.Builder.paginationOptions;
+import static org.jclouds.aliyun.ecs.domain.options.PaginationOptions.Builder.pageNumber;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "VSwitchApiMockTest", singleThreaded = true)
+public class VSwitchApiMockTest extends BaseECSComputeServiceApiMockTest {
+
+   public void testListVSwitches() throws InterruptedException {
+      server.enqueue(jsonResponse("/vswitches-first.json"));
+      server.enqueue(jsonResponse("/vswitches-last.json"));
+      Iterable<VSwitch> vSwitches = api.vSwitchApi().list(TEST_REGION).concat();
+      assertEquals(size(vSwitches), 2); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+      assertSent(server, "GET", "DescribeVSwitches", ImmutableMap.of("RegionId", TEST_REGION));
+      assertSent(server, "GET", "DescribeVSwitches", ImmutableMap.of("RegionId", TEST_REGION), 2);
+   }
+
+   public void testListVSwitchesReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      Iterable<VSwitch> vSwitches = api.vSwitchApi().list(TEST_REGION).concat();
+      assertTrue(isEmpty(vSwitches));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeVSwitches", ImmutableMap.of("RegionId", TEST_REGION));
+   }
+
+   public void testListVSwitchesWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/vswitches-first.json"));
+      IterableWithMarker<VSwitch> vSwitches = api.vSwitchApi().list(TEST_REGION, paginationOptions(pageNumber(1).pageSize(5)));
+      assertEquals(size(vSwitches), 1);
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeVSwitches", ImmutableMap.of("RegionId", TEST_REGION), 1);
+   }
+
+   public void testListVSwitchesWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+      IterableWithMarker<VSwitch> vSwitches = api.vSwitchApi().list(TEST_REGION, paginationOptions(pageNumber(2)));
+      assertTrue(isEmpty(vSwitches));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "DescribeVSwitches", ImmutableMap.of("RegionId", TEST_REGION), 2);
+   }
+
+   public void testCreateVSwitch() throws InterruptedException {
+      server.enqueue(jsonResponse("/vswitch-create-res.json"));
+      VSwitchRequest vSwitchRequest = api.vSwitchApi().create(TEST_ZONE, "192.168.1.0/24", "vsw-25naue4gz");
+      assertEquals(vSwitchRequest, objectFromResource("/vswitch-create-res.json", VSwitchRequest.class));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "CreateVSwitch", ImmutableMap.of("ZoneId", TEST_ZONE, "CidrBlock", "192.168.1.0/24", "VpcId", "vsw-25naue4gz"));
+   }
+
+   public void testDeletVSwitch() throws InterruptedException {
+      server.enqueue(jsonResponse("/vswitch-delete-res.json"));
+      Request delete = api.vSwitchApi().delete(TEST_REGION, "vsw-123456789");
+      assertEquals(delete, objectFromResource("/vswitch-delete-res.json", Request.class));
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "DeleteVSwitch", ImmutableMap.of("RegionId", TEST_REGION, "VSwitchId", "vsw-123456789"));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImageTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImageTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImageTest.java
new file mode 100644
index 0000000..c9cf1ab
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImageTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.jclouds.aliyun.ecs.domain.DiskDeviceMapping;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.internal.Regions;
+import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.xml.bind.DatatypeConverter;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import static org.jclouds.aliyun.ecs.domain.Image.Status.AVAILABLE;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+
+@Test(groups = "unit", testName = "ImageInRegionToImageTest")
+public class ImageInRegionToImageTest {
+
+   private final Location region = new LocationBuilder()
+           .id(Regions.EU_CENTRAL_1.getName())
+           .description(Regions.EU_CENTRAL_1.getDescription())
+           .scope(LocationScope.REGION).build();
+   private final Supplier<Set<? extends Location>> locations = Suppliers.<Set<? extends Location>> ofInstance(ImmutableSet.of(region));
+
+   private ImageInRegionToImage imageInRegionToImage;
+
+   @BeforeMethod
+   public void setUp() {
+      imageInRegionToImage = new ImageInRegionToImage(locations);
+   }
+
+   @Test
+   public void testOsImageToImage() {
+      final org.jclouds.aliyun.ecs.domain.Image ecsImage = org.jclouds.aliyun.ecs.domain.Image.builder()
+              .id("centos_6_09_64_20G_alibase_20180326.vhd")
+              .description("")
+              .productCode("")
+              .osType("linux")
+              .architecture("x86_64")
+              .osName("CentOS  6.9 64位")
+              .imageOwnerAlias("system")
+              .progress("100%")
+              .isSupportCloudinit(true)
+              .usage("instance")
+              .creationTime(parseDate("2018-05-10T12:40:55Z"))
+              .imageVersion("")
+              .status(AVAILABLE)
+              .name("centos_6_09_64_20G_alibase_20180326.vhd")
+              .isSupportIoOptimizeds(true)
+              .isCopied(false)
+              .isSubscribed(false)
+              .isSelfShared(false)
+              .platform("CentOS")
+              .size(20)
+              .diskDeviceMappings(ImmutableMap.<String, List<DiskDeviceMapping>>of())
+              .tags(ImmutableMap.<String, List<Tag>>of())
+              .build();
+
+      final Image image = imageInRegionToImage.apply(ImageInRegion.create(Regions.EU_CENTRAL_1.getName(), ecsImage));
+      assertEquals(ecsImage.id(), image.getProviderId());
+      assertEquals(ecsImage.name(), image.getName());
+      assertEquals(Image.Status.AVAILABLE, image.getStatus());
+      final org.jclouds.compute.domain.OperatingSystem operatingSystem = image.getOperatingSystem();
+
+      assertEquals(ecsImage.osName(), operatingSystem.getName());
+      assertEquals(ecsImage.description(), operatingSystem.getDescription());
+      assertTrue(operatingSystem.is64Bit());
+      assertEquals(region, image.getLocation());
+   }
+
+   @Test
+   public void testOsImageFromOtherOSMapToImage() {
+      final org.jclouds.aliyun.ecs.domain.Image ecsImage = org.jclouds.aliyun.ecs.domain.Image.builder()
+              .id("alinux_17_01_64_20G_cloudinit_20171222.vhd")
+              .description("")
+              .productCode("")
+              .osType("linux")
+              .architecture("x86_64")
+              .osName("Aliyun Linux  17.1 64位")
+              .imageOwnerAlias("system")
+              .progress("100%")
+              .isSupportCloudinit(true)
+              .usage("instance")
+              .creationTime(parseDate("2017-12-22T05:56:16Z"))
+              .imageVersion("")
+              .status(AVAILABLE)
+              .name("alinux_17_01_64_20G_cloudinit_20171222.vhd")
+              .isSupportIoOptimizeds(true)
+              .isCopied(false)
+              .isSubscribed(false)
+              .isSelfShared(false)
+              .platform("Aliyun")
+              .size(20)
+              .diskDeviceMappings(ImmutableMap.<String, List<DiskDeviceMapping>>of())
+              .tags(ImmutableMap.<String, List<Tag>>of())
+              .build();
+
+      final Image image = imageInRegionToImage.apply(ImageInRegion.create(Regions.EU_CENTRAL_1.getName(), ecsImage));
+      assertEquals(ecsImage.id(), image.getProviderId());
+      assertEquals(ecsImage.name(), image.getName());
+      assertEquals(Image.Status.AVAILABLE, image.getStatus());
+      final org.jclouds.compute.domain.OperatingSystem operatingSystem = image.getOperatingSystem();
+
+      assertEquals(ecsImage.osName(), operatingSystem.getName());
+      assertEquals(ecsImage.description(), operatingSystem.getDescription());
+      assertTrue(operatingSystem.is64Bit());
+      assertEquals(region, image.getLocation());
+   }
+
+   Date parseDate(final String dateString) {
+      return DatatypeConverter.parseDateTime(dateString).getTime();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatusTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatusTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatusTest.java
new file mode 100644
index 0000000..62f22e5
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatusTest.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.compute.functions;
+
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * Unit tests for the {@link InstanceStatusToStatus} class.
+ */
+@Test(groups = "unit", testName = "InstanceStatusToStatusTest")
+public class InstanceStatusToStatusTest {
+   private InstanceStatusToStatus function;
+
+   @BeforeMethod
+   public void setup() {
+      function = new InstanceStatusToStatus();
+   }
+
+   public void testStatusRunningToStatusRunning() {
+      NodeMetadata.Status status = function.apply(Instance.Status.RUNNING);
+      assertEquals(status, NodeMetadata.Status.RUNNING);
+   }
+
+   public void testStatusStartingToStatusPending() {
+      NodeMetadata.Status status = function.apply(Instance.Status.STARTING);
+      assertEquals(status, NodeMetadata.Status.PENDING);
+   }
+
+   public void testStatusStoppingToStatusPending() {
+      NodeMetadata.Status status = function.apply(Instance.Status.STOPPING);
+      assertEquals(status, NodeMetadata.Status.PENDING);
+   }
+
+   public void testStatusStoppedToStatusSuspended() {
+      NodeMetadata.Status status = function.apply(Instance.Status.STOPPED);
+      assertEquals(status, NodeMetadata.Status.SUSPENDED);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToHardwareTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToHardwareTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToHardwareTest.java
new file mode 100644
index 0000000..910703c
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToHardwareTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.compute.domain.Hardware;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+@Test(groups = "unit", testName = "InstanceToHardwareTest")
+public class InstanceToHardwareTest {
+
+   private InstanceTypeToHardware instanceTypeToHardware;
+
+   @BeforeMethod
+   public void setUp() {
+      instanceTypeToHardware = new InstanceTypeToHardware();
+   }
+
+   @Test
+   public void testApplyServer() {
+      final InstanceType instanceType = InstanceType.builder()
+              .id("ecs.t1.small")
+              .cpuCoreCount(1)
+              .instanceTypeFamily("ecs.t1")
+              .eniQuantity(1)
+              .memorySize(1.0)
+              .gpuAmount(0d)
+              .localStorageCategory("")
+              .gpuSpec("")
+              .build();
+      applyAndAssert(instanceType);
+   }
+
+   private void applyAndAssert(InstanceType instanceType) {
+      final Hardware hardware = instanceTypeToHardware.apply(instanceType);
+      assertEquals(instanceType.memorySize().intValue() * 1024, hardware.getRam());
+      assertEquals(instanceType.id(), hardware.getId());
+      assertEquals(instanceType.id(), hardware.getProviderId());
+      assertEquals(instanceType.id(), hardware.getName());
+      assertEquals(instanceType.cpuCoreCount().intValue(), hardware.getProcessors().size());
+      assertEquals(Double.valueOf(instanceType.cpuCoreCount().intValue()), hardware.getProcessors().get(0).getCores());
+   }
+
+}


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java
new file mode 100644
index 0000000..2cefe83
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Guice;
+import org.jclouds.aliyun.ecs.domain.DedicatedHostAttribute;
+import org.jclouds.aliyun.ecs.domain.EipAddress;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.NetworkInterface;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.VpcAttributes;
+import org.jclouds.aliyun.ecs.domain.internal.Regions;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+@Test(groups = "unit", testName = "InstanceToNodeMetadataTest")
+public class InstanceToNodeMetadataTest {
+
+   private InstanceToNodeMetadata instanceToNodeMetadata;
+   private Image image;
+   private Hardware hardware;
+   private Location location;
+   private Instance instance;
+   private OperatingSystem os;
+   private Map<String, List<Tag>> tags;
+
+   private String imageId = "centos_6_09_64_20G_alibase_20180326.vhd";
+   private String hardwareId = "ecs.t1.xsmall";
+   private String regionId = Regions.EU_CENTRAL_1.getName();
+
+   @BeforeMethod
+   public void setUp() {
+
+      location = new LocationBuilder().id(regionId)
+              .description(Regions.EU_CENTRAL_1.getDescription())
+              .scope(LocationScope.PROVIDER)
+              .build();
+      Supplier<Set<? extends Location>> locations = new Supplier<Set<? extends Location>>() {
+         @Override
+         public Set<? extends Location> get() {
+            return ImmutableSet.of(location);
+         }
+      };
+
+      GroupNamingConvention.Factory namingConvention = Guice.createInjector().getInstance(GroupNamingConvention.Factory.class);
+
+      hardware = new HardwareBuilder()
+              .ids(hardwareId)
+              .name(hardwareId)
+              .ram(1024)
+              .processor(new Processor(1, 1d))
+              .location(location)
+              .build();
+
+      os = OperatingSystem.builder()
+              .description("CentOS 6.9 64bit")
+              .family(OsFamily.CENTOS)
+              .version("6.9")
+              .is64Bit(true)
+              .build();
+
+      image = new ImageBuilder()
+              .id(RegionAndId.slashEncodeRegionAndId(regionId, imageId))
+              .providerId(imageId)
+              .name("CentOS  6.9 64位")
+              .description("")
+              .operatingSystem(os)
+              .status(Image.Status.AVAILABLE)
+              .build();
+
+      tags = ImmutableMap.<String, List<Tag>>of("Tag", ImmutableList.of(Tag.create("hello", "")));
+
+      instance = Instance.builder()
+              .id("serverId")
+              .name("instanceName")
+              .regionId(regionId)
+              .imageId(imageId)
+              .instanceType(hardwareId)
+              .instanceTypeFamily("linux")
+              .vlanId("vlanId")
+              .eipAddress(EipAddress.create("ipAddress", "allocationId", EipAddress.InternetChargeType.ECS_INSTANCE))
+              .internetMaxBandwidthIn(1)
+              .zoneId("zoneId")
+              .internetChargeType(Instance.InternetChargeType.PAY_BY_TRAFFIC)
+              .spotStrategy("spotStrategy")
+              .stoppedMode("stoppedMode")
+              .serialNumber("serialNumber")
+              .ioOptimized(true)
+              .memory(1024)
+              .cpu(1)
+              .vpcAttributes(VpcAttributes.create("natIpAddress", ImmutableMap.<String, List<String>>of(), "vSwitchId", "vpcId"))
+              .internetMaxBandwidthOut(1)
+              .deviceAvailable(true)
+              .saleCycle("saleCycle")
+              .spotPriceLimit(1d)
+              .autoReleaseTime("")
+              .startTime(new SimpleDateFormatDateService().iso8601DateParse("2014-03-22T07:16:45.784120972Z"))
+              .description("desc")
+              .resourceGroupId("resourceGroupId")
+              .osType("osType")
+              .osName("osName")
+              .instanceNetworkType("instanceNetworkType")
+              .hostname("hostname")
+              .creationTime(new SimpleDateFormatDateService().iso8601DateParse("2014-03-22T05:16:45.784120972Z"))
+              .status(Instance.Status.RUNNING)
+              .clusterId("clusterId")
+              .recyclable(false)
+              .gpuSpec("")
+              .dedicatedHostAttribute(DedicatedHostAttribute.create("id", "name"))
+              .instanceChargeType("instanceChargeType")
+              .gpuAmount(1)
+              .expiredTime(new SimpleDateFormatDateService().iso8601DateParse("2014-03-22T09:16:45.784120972Z"))
+              .innerIpAddress(ImmutableMap.<String, List<String>>of("IpAddress", ImmutableList.of("192.168.0.1", "192.168.0.2")))
+              .publicIpAddress(ImmutableMap.<String, List<String>>of("IpAddress", ImmutableList.of("47.254.152.220", "47.254.153.230")))
+              .securityGroupIds(ImmutableMap.<String, List<String>>of())
+              .networkInterfaces(ImmutableMap.<String, List<NetworkInterface>>of())
+              .operationLocks(ImmutableMap.<String, List<String>>of())
+              .tags(tags)
+              .build();
+
+      Supplier<Map<String, ? extends Image>> images = new Supplier<Map<String, ? extends Image>>() {
+         @Override
+         public Map<String, ? extends Image> get() {
+            return ImmutableMap.of(imageId, image);
+         }
+      };
+
+      Supplier<Map<String, ? extends Hardware>> hardwares = new Supplier<Map<String, ? extends Hardware>>() {
+         @Override
+         public Map<String, ? extends Hardware> get() {
+            return ImmutableMap.of(hardwareId, hardware);
+         }
+      };
+
+      instanceToNodeMetadata = new InstanceToNodeMetadata(images, hardwares, locations,
+              new InstanceStatusToStatus(), namingConvention);
+   }
+
+   @Test
+   public void testInstanceToNodeMetadata() {
+      NodeMetadata node = instanceToNodeMetadata.apply(instance);
+
+      List<String> privateIpAddresses = instance.innerIpAddress().entrySet().iterator().next().getValue();
+      List<String> publicIpAddresses = instance.publicIpAddress().entrySet().iterator().next().getValue();
+
+      assertNotNull(node);
+      assertEquals(node.getProviderId(), instance.id());
+      assertEquals(node.getName(), instance.name());
+      assertEquals(node.getHostname(), instance.hostname());
+      assertEquals(node.getGroup(), instance.name());
+      assertEquals(node.getHardware(), hardware);
+      assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId));
+      assertEquals(node.getOperatingSystem(), os);
+      assertEquals(node.getLocation(), location);
+      assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId));
+      assertEquals(node.getStatus(), NodeMetadata.Status.RUNNING);
+      assertEquals(node.getPrivateAddresses(), privateIpAddresses);
+      assertEquals(node.getPublicAddresses(), publicIpAddresses);
+      assertEquals(node.getTags(), ImmutableSet.of("hello"));
+   }
+
+   @Test
+   public void testInstanceWithInvalidHardwareToNodeMetadata() {
+      Instance instanceWithoutValidHardwareId = instance.toBuilder().instanceType("not.valid").build();
+      NodeMetadata node = instanceToNodeMetadata.apply(instanceWithoutValidHardwareId);
+
+      List<String> privateIpAddresses = instanceWithoutValidHardwareId.innerIpAddress().entrySet().iterator().next().getValue();
+      List<String> publicIpAddresses = instanceWithoutValidHardwareId.publicIpAddress().entrySet().iterator().next().getValue();
+
+      assertNotNull(node);
+      assertEquals(node.getProviderId(), instanceWithoutValidHardwareId.id());
+      assertEquals(node.getName(), instanceWithoutValidHardwareId.name());
+      assertEquals(node.getHostname(), instanceWithoutValidHardwareId.hostname());
+      assertEquals(node.getGroup(), instanceWithoutValidHardwareId.name());
+      assertEquals(node.getHardware(), null);
+      assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId));
+      assertEquals(node.getOperatingSystem(), os);
+      assertEquals(node.getLocation(), location);
+      assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId));
+      assertEquals(node.getStatus(), NodeMetadata.Status.RUNNING);
+      assertEquals(node.getPrivateAddresses(), privateIpAddresses);
+      assertEquals(node.getPublicAddresses(), publicIpAddresses);
+      assertEquals(node.getTags(), ImmutableSet.of("hello"));
+   }
+
+   @Test
+   public void testInstanceWithInvalidImageToNodeMetadata() {
+      Instance instanceWithoutValidHardwareId = instance.toBuilder().imageId("not.valid").build();
+      NodeMetadata node = instanceToNodeMetadata.apply(instanceWithoutValidHardwareId);
+
+      List<String> privateIpAddresses = instanceWithoutValidHardwareId.innerIpAddress().entrySet().iterator().next().getValue();
+      List<String> publicIpAddresses = instanceWithoutValidHardwareId.publicIpAddress().entrySet().iterator().next().getValue();
+
+      assertNotNull(node);
+      assertEquals(node.getProviderId(), instanceWithoutValidHardwareId.id());
+      assertEquals(node.getName(), instanceWithoutValidHardwareId.name());
+      assertEquals(node.getHostname(), instanceWithoutValidHardwareId.hostname());
+      assertEquals(node.getGroup(), instanceWithoutValidHardwareId.name());
+      assertEquals(node.getHardware(), hardware);
+      assertEquals(node.getImageId(), null);
+      assertEquals(node.getOperatingSystem(), null);
+      assertEquals(node.getImageId(), null);
+      assertEquals(node.getLocation(), location);
+      assertEquals(node.getStatus(), NodeMetadata.Status.RUNNING);
+      assertEquals(node.getPrivateAddresses(), privateIpAddresses);
+      assertEquals(node.getPublicAddresses(), publicIpAddresses);
+      assertEquals(node.getTags(), ImmutableSet.of("hello"));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java
----------------------------------------------------------------------
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
index f7b9526..c18d2b6 100644
--- 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
@@ -16,19 +16,33 @@
  */
 package org.jclouds.aliyun.ecs.compute.internal;
 
+import com.google.common.base.Predicate;
 import com.google.inject.Injector;
+import com.google.inject.Key;
 import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
 import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.apis.BaseApiLiveTest;
 import org.jclouds.compute.config.ComputeServiceProperties;
 
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+
 public class BaseECSComputeServiceApiLiveTest extends BaseApiLiveTest<ECSComputeServiceApi> {
 
+   protected static final String TEST_REGION = System.getProperty("test.alibaba-ecs.region", Regions.EU_CENTRAL_1.getName());
+   protected static final String TEST_ZONE = System.getProperty("test.alibaba-ecs.zone", TEST_REGION + "a");
+
+   protected Predicate<String> instanceRunningPredicate;
+   protected Predicate<String> instanceSuspendedPredicate;
+
    public BaseECSComputeServiceApiLiveTest() {
-      provider = "aliyun-ecs";
+      provider = "alibaba-ecs";
    }
 
    @Override
@@ -43,6 +57,12 @@ public class BaseECSComputeServiceApiLiveTest extends BaseApiLiveTest<ECSCompute
    @Override
    protected ECSComputeServiceApi create(Properties props, Iterable<Module> modules) {
       Injector injector = newBuilder().modules(modules).overrides(props).buildInjector();
+
+      instanceRunningPredicate = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>() {
+      }, Names.named(TIMEOUT_NODE_RUNNING)));
+      instanceSuspendedPredicate = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>() {
+      }, Names.named(TIMEOUT_NODE_SUSPENDED)));
+
       return injector.getInstance(ECSComputeServiceApi.class);
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java
----------------------------------------------------------------------
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
index dd3938d..e25b08f 100644
--- 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
@@ -29,6 +29,7 @@ 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.aliyun.ecs.domain.internal.Regions;
 import org.jclouds.concurrent.config.ExecutorServiceModule;
 import org.jclouds.json.Json;
 import org.jclouds.rest.ApiContext;
@@ -48,6 +49,8 @@ import static org.testng.Assert.assertTrue;
 public class BaseECSComputeServiceApiMockTest {
 
    private static final String DEFAULT_ENDPOINT = new ECSComputeServiceProviderMetadata().getEndpoint();
+   protected static final String TEST_REGION = Regions.EU_CENTRAL_1.getName();
+   protected static final String TEST_ZONE = TEST_REGION + "a";
 
    private final Set<Module> modules = ImmutableSet.<Module>of(new ExecutorServiceModule(newDirectExecutorService()));
    protected MockWebServer server;
@@ -59,7 +62,7 @@ public class BaseECSComputeServiceApiMockTest {
    public void start() throws IOException {
       server = new MockWebServer();
       server.play();
-      ctx = ContextBuilder.newBuilder("aliyun-ecs").credentials("user", "password").endpoint(url("")).modules(modules)
+      ctx = ContextBuilder.newBuilder("alibaba-ecs").credentials("user", "password").endpoint(url("")).modules(modules)
             .overrides(overrides()).build();
       json = ctx.utils().injector().getInstance(Json.class);
       api = ctx.getApi();

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java
new file mode 100644
index 0000000..4bff6d3
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.strategy;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.easymock.EasyMock;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions;
+import org.jclouds.aliyun.ecs.domain.IpProtocol;
+import org.jclouds.aliyun.ecs.domain.KeyPairRequest;
+import org.jclouds.aliyun.ecs.domain.Permission;
+import org.jclouds.aliyun.ecs.domain.Request;
+import org.jclouds.aliyun.ecs.domain.ResourceInfo;
+import org.jclouds.aliyun.ecs.domain.ResourceType;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.VPCRequest;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.VSwitchRequest;
+import org.jclouds.aliyun.ecs.domain.Zone;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.internal.Regions;
+import org.jclouds.aliyun.ecs.domain.options.CreateSecurityGroupOptions;
+import org.jclouds.aliyun.ecs.domain.options.CreateVPCOptions;
+import org.jclouds.aliyun.ecs.domain.options.CreateVSwitchOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+import org.jclouds.aliyun.ecs.domain.options.TagOptions;
+import org.jclouds.aliyun.ecs.features.RegionAndZoneApi;
+import org.jclouds.aliyun.ecs.features.SecurityGroupApi;
+import org.jclouds.aliyun.ecs.features.SshKeyPairApi;
+import org.jclouds.aliyun.ecs.features.TagApi;
+import org.jclouds.aliyun.ecs.features.VPCApi;
+import org.jclouds.aliyun.ecs.features.VSwitchApi;
+import org.jclouds.collect.IterableWithMarkers;
+import org.jclouds.collect.PagedIterables;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.domain.Location;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMockSupport.injectMocks;
+import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.DEFAULT_CIDR_BLOCK;
+import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.DEFAULT_DESCRIPTION_SUFFIX;
+import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.VSWITCH_PREFIX;
+import static org.testng.AssertJUnit.assertEquals;
+
+/**
+ *
+ * User can specify security group and vSwitch.
+ * 1. security group and vSwitch
+ * 2. only security group  -> impossible to determine which vSwitch the user wants to use or create
+ * 3. only vswitch ID -> create a securitygroup in the same vpc
+ * 4. none of them -> create vpc, vswitch and securitygroup
+ *
+ * Case 1 is tested with testExecuteWithSecurityGroupsVSwitchId
+ * Case 2 testExecuteOnlySecurityGroup
+ * Case 3 is tested with testExecuteOnlyVSwitchId
+ * Case 4 is tested with testExecuteNoSecurityGroupsVSwitchId
+ */
+@Test(groups = "unit", testName = "CreateResourcesThenCreateNodesTest")
+public class CreateResourcesThenCreateNodesTest {
+
+   private CreateResourcesThenCreateNodes createResourcesThenCreateNodes;
+   private SecurityGroupApi securityGroupApi;
+   private VSwitchApi vSwitchApi;
+   private TagApi tagApi;
+   private SshKeyPairApi sshKeyPairApi;
+   private VPCApi vpcApi;
+   private RegionAndZoneApi regionAndZoneApi;
+   private ECSComputeServiceApi api;
+   private Template template;
+   private ECSServiceTemplateOptions templateOptions;
+   private CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy;
+   private ListNodesStrategy listNodesStrategy;
+   private GroupNamingConvention.Factory factory;
+   private GroupNamingConvention namingConvention;
+   private ListeningExecutorService userExecutor;
+   private CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory;
+   private ComputeServiceConstants.Timeouts timeouts;
+   private Location location;
+   private String regionId;
+   private SecurityGroup securityGroup;
+   private Permission permission;
+   private VSwitch vswitch;
+   private Zone zone;
+
+   @BeforeMethod
+   public void setUp() {
+      securityGroupApi = EasyMock.createMock(SecurityGroupApi.class);
+      vSwitchApi = EasyMock.createMock(VSwitchApi.class);
+      tagApi = EasyMock.createMock(TagApi.class);
+      sshKeyPairApi = EasyMock.createMock(SshKeyPairApi.class);
+      vpcApi = EasyMock.createMock(VPCApi.class);
+      regionAndZoneApi = EasyMock.createMock(RegionAndZoneApi.class);
+      api = EasyMock.createMock(ECSComputeServiceApi.class);
+      template = EasyMock.createNiceMock(Template.class);
+      addNodeWithGroupStrategy = EasyMock.createNiceMock(CreateNodeWithGroupEncodedIntoName.class);
+      listNodesStrategy = EasyMock.createNiceMock(ListNodesStrategy.class);
+      factory = EasyMock.createNiceMock(GroupNamingConvention.Factory.class);
+      namingConvention = EasyMock.createNiceMock(GroupNamingConvention.class);
+
+      userExecutor = EasyMock.createNiceMock(ListeningExecutorService.class);
+      customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory = EasyMock
+            .createNiceMock(CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory.class);
+      location = createNiceMock(Location.class);
+      templateOptions = new ECSServiceTemplateOptions();
+      regionId = Regions.EU_CENTRAL_1.getName();
+
+      timeouts = new ComputeServiceConstants.Timeouts();
+
+      createResourcesThenCreateNodes = new CreateResourcesThenCreateNodes(addNodeWithGroupStrategy,
+            listNodesStrategy, factory, userExecutor,
+            customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, api);
+
+      permission = Permission.create(
+              "",
+              "",
+              "",
+              Permission.NicType.INTERNET,
+              "",
+              "",
+              "",
+              Permission.Direction.ALL,
+              "",
+              IpProtocol.ALL,
+              "",
+         Permission.Policy.ACCEPT,
+              new Date(),
+              "",
+              "",
+              ""
+              );
+
+      zone = Zone.create("id",
+              "localName",
+              ImmutableMap.<String, List<Object>>of(),
+              ImmutableMap.<String, List<String>>of(),
+              ImmutableMap.<String, List<String>>of(),
+              ImmutableMap.<String, List<ResourceInfo>>of(),
+              ImmutableMap.<String, List<String>>of(),
+              ImmutableMap.<String, List<String>>of(),
+              ImmutableMap.<String, List<String>>of()
+              );
+
+      injectMocks(api);
+      injectMocks(template);
+      injectMocks(securityGroupApi);
+      injectMocks(vSwitchApi);
+      injectMocks(tagApi);
+      injectMocks(sshKeyPairApi);
+      injectMocks(vpcApi);
+      injectMocks(regionAndZoneApi);
+
+      expect(template.getLocation()).andReturn(location).anyTimes();
+      expect(location.getId()).andReturn(regionId).anyTimes();
+
+      expect(api.securityGroupApi()).andReturn(securityGroupApi).anyTimes();
+      expect(api.vSwitchApi()).andReturn(vSwitchApi).anyTimes();
+      expect(api.tagApi()).andReturn(tagApi).anyTimes();
+      expect(api.sshKeyPairApi()).andReturn(sshKeyPairApi).anyTimes();
+      expect(api.vpcApi()).andReturn(vpcApi).anyTimes();
+      expect(api.regionAndZoneApi()).andReturn(regionAndZoneApi).anyTimes();
+   }
+
+
+   @Test
+   public void testExecuteWithSecurityGroupsVSwitchId() {
+      String vpcId = "vpc-1";
+      String vSwitchId = "vs-1";
+      String securityGroupId = "sg-1";
+      securityGroup = createSecurityGroup(securityGroupId, vpcId);
+      vswitch = createVSwitch(vSwitchId, vpcId);
+
+      templateOptions.vSwitchId(vSwitchId).securityGroups(securityGroupId);
+
+      expect(template.getOptions()).andReturn(templateOptions).anyTimes();
+
+      expect(securityGroupApi.list(regionId))
+              .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.newArrayList(securityGroup))));
+
+      expect(securityGroupApi.get(regionId, securityGroupId))
+              .andReturn(Lists.newArrayList(permission));
+
+      // found VSwitch specified by user in VPC_PREFIX
+      expect(vSwitchApi.list(regionId, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId).vpcId(vpcId)))
+              .andReturn(new PaginatedCollection(ImmutableMap.<String, Iterable<VSwitch>>of("VSwitch", Lists.<VSwitch> newArrayList(vswitch)), 1, 1, 1, regionId, "requestId"));
+
+      expect(factory.create()).andReturn(namingConvention).anyTimes();
+      expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes();
+      expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes();
+
+      expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId)))
+              .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes();
+      expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0"))
+              .andReturn(new Request("requestId")).anyTimes();
+      expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP,
+              TagOptions.Builder.tag(1,  Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group"))
+      ).andReturn(new Request("requestId")).anyTimes();
+      expect(sshKeyPairApi.create(regionId, "prefix"))
+              .andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes();
+
+      replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location);
+
+      executeAndAssert(vSwitchId, securityGroupId);
+   }
+
+   @Test(dependsOnMethods = "testExecuteWithSecurityGroupsVSwitchId")
+   public void testExecuteNoSecurityGroupsNoVSwitchId() {
+      String vpcId = "vpc-1";
+      String vSwitchId = "vs-1";
+      String securityGroupId = "sg-1";
+
+      vswitch = createVSwitch(vSwitchId, vpcId);
+
+      expect(template.getOptions()).andReturn(templateOptions).anyTimes();
+
+      expect(securityGroupApi.list(regionId))
+      .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<SecurityGroup>newArrayList())));
+
+      expect(vSwitchApi.list(regionId))
+              .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<VSwitch>newArrayList())));
+
+      expect(regionAndZoneApi.describeZones(regionId)).andReturn(ImmutableList.of(zone));
+      expect(vpcApi.create(regionId, CreateVPCOptions.Builder.vpcName(anyString()).description(anyString()))).andReturn(new VPCRequest("reqId", "routeId", "vRoutId", vpcId));
+
+      String vSwitchName = String.format("%s-%s", VSWITCH_PREFIX, "group");
+      String vSwitchDescription = String.format("%s - %s", vSwitchName, DEFAULT_DESCRIPTION_SUFFIX);
+      expect(vSwitchApi.create(zone.id(), DEFAULT_CIDR_BLOCK, vpcId,
+              CreateVSwitchOptions.Builder.vSwitchName(vSwitchName).description(vSwitchDescription))).andReturn(new VSwitchRequest("reqId", vSwitchId));
+
+      expect(factory.create()).andReturn(namingConvention).anyTimes();
+      expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes();
+      expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes();
+
+      expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId)))
+              .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes();
+      expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0")).andReturn(new Request("requestId")).anyTimes();
+      expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP,
+              TagOptions.Builder.tag(1,  Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group"))
+      ).andReturn(new Request("requestId")).anyTimes();
+      expect(sshKeyPairApi.create(regionId, "prefix")).andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes();
+
+      replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location);
+
+      executeAndAssert(vSwitchId, securityGroupId);
+   }
+
+   @Test(dependsOnMethods = "testExecuteNoSecurityGroupsNoVSwitchId", expectedExceptions = IllegalStateException.class)
+   public void testExecuteOnlySecurityGroup() {
+      String vpcId = "vpc-2";
+      String vSwitchId = "";
+      String securityGroupId = "sg-2";
+      templateOptions.vSwitchId(vSwitchId).securityGroups(securityGroupId);
+      securityGroup = createSecurityGroup(securityGroupId, vpcId);
+      vswitch = createVSwitch(vSwitchId, vpcId);
+
+      expect(template.getOptions()).andReturn(templateOptions).anyTimes();
+
+      expect(securityGroupApi.list(regionId))
+            .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.newArrayList(securityGroup))));
+
+      expect(securityGroupApi.get(regionId, securityGroupId))
+            .andReturn(Lists.newArrayList(permission));
+
+      // at least a VSwitch is available in regionId
+      expect(vSwitchApi.list(regionId, ListVSwitchesOptions.Builder.vpcId(vpcId)))
+              .andReturn(new PaginatedCollection(ImmutableMap.of("VSwitch", Lists.newArrayList(vswitch)), 1, 1, 1, regionId, "requestId"));
+
+      expect(factory.create()).andReturn(namingConvention).anyTimes();
+      expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes();
+      expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes();
+
+      expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId)))
+              .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes();
+      expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0"))
+              .andReturn(new Request("requestId")).anyTimes();
+      expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP,
+              TagOptions.Builder.tag(1,  Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group"))
+      ).andReturn(new Request("requestId")).anyTimes();
+      expect(sshKeyPairApi.create(regionId, "prefix"))
+              .andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes();
+
+      replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location);
+
+      executeAndAssert(vSwitchId, securityGroupId);
+   }
+
+   @Test(dependsOnMethods = "testExecuteOnlySecurityGroup")
+   public void testExecuteOnlyVSwitchId() {
+      String vpcId = "vpc-3";
+      String vSwitchId = "vs-3";
+      String securityGroupId = "sg-3";
+      vswitch = createVSwitch(vSwitchId, vpcId);
+
+      templateOptions.vSwitchId(vSwitchId).securityGroups(securityGroupId);
+
+      expect(template.getOptions()).andReturn(templateOptions).anyTimes();
+
+      expect(securityGroupApi.list(regionId))
+              .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<SecurityGroup> newArrayList())));
+
+      expect(securityGroupApi.get(regionId, securityGroupId))
+              .andReturn(Lists.newArrayList(permission));
+
+      // found VSwitch specified by user in VPC_PREFIX
+      expect(vSwitchApi.list(regionId, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)))
+              .andReturn(new PaginatedCollection(ImmutableMap.<String, Iterable<VSwitch>>of("VSwitch", Lists.<VSwitch> newArrayList(vswitch)), 1, 1, 1, regionId, "requestId"));
+
+      expect(factory.create()).andReturn(namingConvention).anyTimes();
+      expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes();
+      expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes();
+
+      expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId)))
+              .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes();
+      expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0"))
+              .andReturn(new Request("requestId")).anyTimes();
+      expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP,
+              TagOptions.Builder.tag(1,  Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group"))
+      ).andReturn(new Request("requestId")).anyTimes();
+      expect(sshKeyPairApi.create(regionId, "prefix"))
+              .andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes();
+
+      replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location);
+
+      executeAndAssert(vSwitchId, securityGroupId);
+   }
+
+   private void executeAndAssert(String vSwitchId, String securityGroupId) {
+      createResourcesThenCreateNodes.execute("group", 0, template, Collections.<NodeMetadata>emptySet(),
+              Collections.<NodeMetadata, Exception>emptyMap(),
+              ArrayListMultimap.<NodeMetadata, CustomizationResponse>create());
+
+      assertEquals(vSwitchId, templateOptions.getVSwitchId());
+      assertEquals(securityGroupId, templateOptions.getGroups().iterator().next());
+   }
+
+   private SecurityGroup createSecurityGroup(String securityGroupId, String vpcId) {
+      return SecurityGroup.create(
+              securityGroupId,
+              "",
+              "securityGroupName",
+              vpcId,
+              ImmutableMap.<String, List<Tag>>of()
+      );
+   }
+
+   private VSwitch createVSwitch(String vSwitchId, String vpcId) {
+      return VSwitch.create(
+              "",
+              new Date(),
+              "vSwitch",
+              "",
+              VSwitch.Status.AVAILABLE,
+              1,
+              vpcId,
+              vSwitchId,
+              ""
+      );
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/availableZones.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/availableZones.json b/aliyun-ecs/src/test/resources/availableZones.json
new file mode 100644
index 0000000..0b2e092
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/availableZones.json
@@ -0,0 +1,423 @@
+{
+  "RequestId": "C51117E9-1989-4407-AAB6-5E3E433E9A75",
+  "AvailableZones": {
+    "AvailableZone": [
+      {
+        "Status": "Available",
+        "RegionId": "eu-central-1",
+        "AvailableResources": {
+          "AvailableResource": [
+            {
+              "Type": "InstanceType",
+              "SupportedResources": {
+                "SupportedResource": [
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m1.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m4.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m4.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m2.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m2.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m4.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m1.small"
+                  },
+                  {
+                    "Status": "SoldOut",
+                    "Value": "ecs.hfg5.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m2.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m1.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc2m1.nano"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m2.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.large"
+                  }
+                ]
+              }
+            }
+          ]
+        },
+        "ZoneId": "eu-central-1b"
+      },
+      {
+        "Status": "Available",
+        "RegionId": "eu-central-1",
+        "AvailableResources": {
+          "AvailableResource": [
+            {
+              "Type": "InstanceType",
+              "SupportedResources": {
+                "SupportedResource": [
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.n4.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m2.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m4.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.mn4.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.i1.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.mn4.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m2.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.e4.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m4.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.mn4.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.n4.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.i1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2.large"
+                  },
+                  {
+                    "Status": "SoldOut",
+                    "Value": "ecs.ce4.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.ce4.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.mn4.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m2.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m1.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.d1.4xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m2.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.gn5-c4g1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m1.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2.medium"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-c1m4.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1.medium"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.n4.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfg5.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.gn5-c8g1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.hfc5.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.i1.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1ne.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.i2.xlarge"
+                  },
+                  {
+                    "Status": "SoldOut",
+                    "Value": "ecs.d1.3xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.xn4.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc1m1.small"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.d1.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.t5-lc2m1.nano"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.i2.2xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.gn5-c4g1.xlarge"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.se1.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.n4.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn1ne.large"
+                  },
+                  {
+                    "Status": "Available",
+                    "Value": "ecs.sn2ne.large"
+                  }
+                ]
+              }
+            }
+          ]
+        },
+        "ZoneId": "eu-central-1a"
+      }
+    ]
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/instanceStatus.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/instanceStatus.json b/aliyun-ecs/src/test/resources/instanceStatus.json
new file mode 100644
index 0000000..592ef16
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/instanceStatus.json
@@ -0,0 +1,18 @@
+{
+  "PageNumber": 1,
+  "InstanceStatuses": {
+    "InstanceStatus": [
+      {
+        "Status": "Stopped",
+        "InstanceId": "i-gw8dvsswhc7iifle1hp7"
+      },
+      {
+        "Status": "Stopped",
+        "InstanceId": "i-gw8g25hfbxhyykn3jevq"
+      }
+    ]
+  },
+  "TotalCount": 2,
+  "PageSize": 10,
+  "RequestId": "BDE228AF-8B12-439B-8C1C-FB42CCEB4A65"
+}
\ No newline at end of file


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitchRequest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitchRequest.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitchRequest.java
new file mode 100644
index 0000000..da16205
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitchRequest.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;
+
+public class VSwitchRequest extends Request {
+
+   private final String vSwitchId;
+
+   @ConstructorProperties({ "RequestId", "VSwitchId" })
+   public VSwitchRequest(String requestId, String vSwitchId) {
+      super(requestId);
+      this.vSwitchId = vSwitchId;
+   }
+
+   public String getVSwitchId() {
+      return vSwitchId;
+   }
+
+   @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;
+      VSwitchRequest that = (VSwitchRequest) o;
+      return Objects.equal(vSwitchId, that.vSwitchId);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), vSwitchId);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this)
+              .add("vSwitchId", vSwitchId)
+              .toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VpcAttributes.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VpcAttributes.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VpcAttributes.java
new file mode 100644
index 0000000..f35d6ad
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VpcAttributes.java
@@ -0,0 +1,48 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+@AutoValue
+public abstract class VpcAttributes {
+
+   VpcAttributes() {
+   }
+
+   @SerializedNames({ "NatIpAddress", "PrivateIpAddress", "VSwitchId", "VpcId" })
+   public static VpcAttributes create(String natIpAddress, Map<String, List<String>> privateIpAddress, String vSwitchId,
+                                      String vpcId) {
+      return new AutoValue_VpcAttributes(natIpAddress,
+            privateIpAddress == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(privateIpAddress),
+            vSwitchId, vpcId);
+   }
+
+   public abstract String natIpAddress();
+
+   public abstract Map<String, List<String>> privateIpAddress();
+
+   public abstract String vSwitchId();
+
+   public abstract String vpcId();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java
----------------------------------------------------------------------
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
index 6cffd72..501bed7 100644
--- 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
@@ -51,8 +51,8 @@ public class PaginatedCollection<T> extends IterableWithMarker<T> {
 
    private final String requestId;
 
-   protected PaginatedCollection(@Nullable Map<String, Iterable<T>> resources, int pageNumber, int totalCount,
-                                 int pageSize, String regionId, String requestId) {
+   public 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;

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateInstanceOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateInstanceOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateInstanceOptions.java
new file mode 100644
index 0000000..e1c6956
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateInstanceOptions.java
@@ -0,0 +1,161 @@
+/*
+ * 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 CreateInstanceOptions extends BaseHttpRequestOptions {
+   public static final String VSWITCH_PARAM = "VSwitchId";
+   public static final String INSTANCE_NAME_PARAM = "InstanceName";
+   public static final String KEYPAIR_PARAM = "KeyPairName";
+   public static final String INTERNET_CHARGE_TYPE_PARAM = "InternetChargeType";
+   public static final String INTERNET_MAX_BANDWITH_IN_PARAM = "InternetMaxBandwidthIn";
+   public static final String INTERNET_MAX_BANDWITH_OUT_PARAM = "InternetMaxBandwidthOut";
+   public static final String INSTANCE_CHARGE_TYPE_PARAM = "InstanceChargeType";
+
+   /**
+    * Configures the name of the instance to be used.
+    */
+   public CreateInstanceOptions instanceName(String instanceName) {
+      queryParameters.put(INSTANCE_NAME_PARAM, instanceName);
+      return this;
+   }
+
+   /**
+    * Configures the vSwitch Id to be used.
+    */
+   public CreateInstanceOptions vSwitchId(String vSwitchId) {
+      queryParameters.put(VSWITCH_PARAM, vSwitchId);
+      return this;
+   }
+
+   /**
+    * Configures the keyPairName to be used.
+    */
+   public CreateInstanceOptions keyPairName(String keyPairName) {
+      queryParameters.put(KEYPAIR_PARAM, keyPairName);
+      return this;
+   }
+
+   /**
+    * Configures the internet charge type of the instances to be used.
+    */
+   public CreateInstanceOptions internetChargeType(String internetChargeType) {
+      queryParameters.put(INTERNET_CHARGE_TYPE_PARAM, internetChargeType);
+      return this;
+   }
+
+   /**
+    * Configures the internet max bandwidth in of the instances to be used.
+    */
+   public CreateInstanceOptions internetMaxBandwidthIn(int internetMaxBandwidthIn) {
+      queryParameters.put(INTERNET_MAX_BANDWITH_IN_PARAM, String.valueOf(internetMaxBandwidthIn));
+      return this;
+   }
+
+   /**
+    * Configures the internet max bandwidth out of the instances to be used.
+    */
+   public CreateInstanceOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) {
+      queryParameters.put(INTERNET_MAX_BANDWITH_OUT_PARAM, String.valueOf(internetMaxBandwidthOut));
+      return this;
+   }
+
+   /**
+    * Configures the instance charge type of the instances to be used.
+    */
+   public CreateInstanceOptions instanceChargeType(String instanceChargeType) {
+      queryParameters.put(INSTANCE_CHARGE_TYPE_PARAM, instanceChargeType);
+      return this;
+   }
+
+   public CreateInstanceOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public CreateInstanceOptions tagOptions(final TagOptions tagOptions) {
+      this.queryParameters.putAll(tagOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link CreateInstanceOptions#instanceName(String)}
+       */
+      public static CreateInstanceOptions instanceName(String instanceName) {
+         return new CreateInstanceOptions().instanceName(instanceName);
+      }
+
+      /**
+       * @see {@link CreateInstanceOptions#vSwitchId(String)}
+       */
+      public static CreateInstanceOptions vSwitchId(String vSwitchId) {
+         return new CreateInstanceOptions().vSwitchId(vSwitchId);
+      }
+
+      /**
+       * @see {@link CreateInstanceOptions#keyPairName(String)}
+       */
+      public static CreateInstanceOptions keyPairName(String keyPairName) {
+         return new CreateInstanceOptions().keyPairName(keyPairName);
+      }
+
+      /**
+       * @see {@link CreateInstanceOptions#internetChargeType(String)}
+       */
+      public static CreateInstanceOptions internetChargeType(String internetChargeType) {
+         return new CreateInstanceOptions().internetChargeType(internetChargeType);
+      }
+
+      /**
+       * @see {@link CreateInstanceOptions#internetMaxBandwidthIn(int)}
+       */
+      public static CreateInstanceOptions internetMaxBandwidthIn(int internetMaxBandwidthIn) {
+         return new CreateInstanceOptions().internetMaxBandwidthIn(internetMaxBandwidthIn);
+      }
+
+      /**
+       * @see {@link CreateInstanceOptions#internetMaxBandwidthOut(int)}
+       */
+      public static CreateInstanceOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) {
+         return new CreateInstanceOptions().internetMaxBandwidthOut(internetMaxBandwidthOut);
+      }
+
+      /**
+       * @see {@link CreateInstanceOptions#instanceChargeType(String)}
+       */
+      public static CreateInstanceOptions instanceChargeType(String instanceChargeType) {
+         return new CreateInstanceOptions().instanceChargeType(instanceChargeType);
+      }
+
+      /**
+       * @see CreateInstanceOptions#paginationOptions(PaginationOptions)
+       */
+      public static CreateInstanceOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new CreateInstanceOptions().paginationOptions(paginationOptions);
+      }
+
+      /**
+       * @see CreateInstanceOptions#tagOptions(TagOptions)
+       */
+      public static CreateInstanceOptions tagOptions(TagOptions tagOptions) {
+         return new CreateInstanceOptions().tagOptions(tagOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVPCOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVPCOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVPCOptions.java
new file mode 100644
index 0000000..b2841b0
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVPCOptions.java
@@ -0,0 +1,106 @@
+/*
+ * 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 CreateVPCOptions extends BaseHttpRequestOptions {
+   public static final String CIDR_BLOCK_PARAM = "CidrBlock";
+   public static final String VPC_NAME_PARAM = "VpcName";
+   public static final String DESCRIPTION_PARAM = "Description";
+   public static final String CLIENT_TOKEN_PARAM = "ClientToken";
+   public static final String USER_CIDR_PARAM = "UserCidr";
+
+   /**
+    * Configures the IP address range of the VPC_PREFIX in the CIDR block form.
+    */
+   public CreateVPCOptions cidrBlock(String cidrBlock) {
+      queryParameters.put(CIDR_BLOCK_PARAM, cidrBlock);
+      return this;
+   }
+
+   /**
+    * Configures the name of the VPC_PREFIX.
+    */
+   public CreateVPCOptions vpcName(String vpcName) {
+      queryParameters.put(VPC_NAME_PARAM, vpcName);
+      return this;
+   }
+
+   /**
+    * Configures the description of the VPC_PREFIX.
+    */
+   public CreateVPCOptions description(String description) {
+      queryParameters.put(DESCRIPTION_PARAM, description);
+      return this;
+   }
+
+   /**
+    * Configures a client token used to guarantee the idempotence of request.
+    */
+   public CreateVPCOptions clientToken(String clientToken) {
+      queryParameters.put(CLIENT_TOKEN_PARAM, clientToken);
+      return this;
+   }
+
+   /**
+    * Configures the user CIDR.
+    */
+   public CreateVPCOptions userCidr(String userCidr) {
+      queryParameters.put(USER_CIDR_PARAM, userCidr);
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link CreateVPCOptions#cidrBlock(String)}
+       */
+      public static CreateVPCOptions cidrBlock(String cidrBlock) {
+         return new CreateVPCOptions().cidrBlock(cidrBlock);
+      }
+
+      /**
+       * @see {@link CreateVPCOptions#vpcName(String)}
+       */
+      public static CreateVPCOptions vpcName(String vpcName) {
+         return new CreateVPCOptions().vpcName(vpcName);
+      }
+
+      /**
+       * @see {@link CreateVPCOptions#description(String)}
+       */
+      public static CreateVPCOptions description(String description) {
+         return new CreateVPCOptions().description(description);
+      }
+
+      /**
+       * @see {@link CreateVPCOptions#clientToken(String)}
+       */
+      public static CreateVPCOptions clientToken(String clientToken) {
+         return new CreateVPCOptions().clientToken(clientToken);
+      }
+
+      /**
+       * @see {@link CreateVPCOptions#userCidr(String)}
+       */
+      public static CreateVPCOptions userCidr(String userCidr) {
+         return new CreateVPCOptions().userCidr(userCidr);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVSwitchOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVSwitchOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVSwitchOptions.java
new file mode 100644
index 0000000..fe10fe5
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/CreateVSwitchOptions.java
@@ -0,0 +1,74 @@
+/*
+ * 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 CreateVSwitchOptions extends BaseHttpRequestOptions {
+   public static final String VSWITCH_NAME_PARAM = "VSwitchName";
+   public static final String DESCRIPTION_PARAM = "Description";
+   public static final String CLIENT_TOKEN_PARAM = "ClientToken";
+
+   /**
+    * Configures the name of the VSwitch.
+    */
+   public CreateVSwitchOptions vSwitchName(String vSwitchName) {
+      queryParameters.put(VSWITCH_NAME_PARAM, vSwitchName);
+      return this;
+   }
+
+   /**
+    * Configures the description of the VPC_PREFIX.
+    */
+   public CreateVSwitchOptions description(String description) {
+      queryParameters.put(DESCRIPTION_PARAM, description);
+      return this;
+   }
+
+   /**
+    * Configures a client token used to guarantee the idempotence of request.
+    */
+   public CreateVSwitchOptions clientToken(String clientToken) {
+      queryParameters.put(CLIENT_TOKEN_PARAM, clientToken);
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link CreateVSwitchOptions#vSwitchName(String)}
+       */
+      public static CreateVSwitchOptions vSwitchName(String vSwitchName) {
+         return new CreateVSwitchOptions().vSwitchName(vSwitchName);
+      }
+
+      /**
+       * @see {@link CreateVSwitchOptions#description(String)}
+       */
+      public static CreateVSwitchOptions description(String description) {
+         return new CreateVSwitchOptions().description(description);
+      }
+
+      /**
+       * @see {@link CreateVSwitchOptions#clientToken(String)}
+       */
+      public static CreateVSwitchOptions clientToken(String clientToken) {
+         return new CreateVSwitchOptions().clientToken(clientToken);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 34972a4..0eec663 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,9 +16,11 @@
  */
 package org.jclouds.aliyun.ecs.domain.options;
 
-import org.jclouds.aliyun.ecs.functions.ArrayToCommaSeparatedString;
+import com.google.common.base.Joiner;
 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";
@@ -28,7 +30,7 @@ public class ListImagesOptions extends BaseHttpRequestOptions {
    public static final String USAGE_PARAM = "Usage";
 
    public ListImagesOptions imageIds(String... instanceIds) {
-      queryParameters.put(IMAGE_ID_PARAM, String.format("[%s]", new ArrayToCommaSeparatedString().apply(instanceIds)));
+      queryParameters.put(IMAGE_ID_PARAM, Joiner.on(",").join(Arrays.asList(instanceIds)));
       return this;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstanceStatusOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstanceStatusOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstanceStatusOptions.java
new file mode 100644
index 0000000..9d10467
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstanceStatusOptions.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.domain.options;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+public class ListInstanceStatusOptions extends BaseHttpRequestOptions {
+   public static final String ZONE_ID_PARAM = "ZoneId";
+
+   public ListInstanceStatusOptions zoneId(String zoneId) {
+      queryParameters.put(ZONE_ID_PARAM, zoneId);
+      return this;
+   }
+
+   public ListInstanceStatusOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListInstanceStatusOptions#zoneId(String)}
+       */
+      public static ListInstanceStatusOptions zoneId(String zoneId) {
+         return new ListInstanceStatusOptions().zoneId(zoneId);
+      }
+
+      /**
+       * @see ListInstanceStatusOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListInstanceStatusOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListInstanceStatusOptions().paginationOptions(paginationOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstancesOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstancesOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstancesOptions.java
new file mode 100644
index 0000000..ec39383
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListInstancesOptions.java
@@ -0,0 +1,239 @@
+/*
+ * 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.Joiner;
+import com.google.common.collect.Iterables;
+import org.jclouds.aliyun.ecs.functions.PutStringInDoubleQuotes;
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+import java.util.Arrays;
+
+public class ListInstancesOptions extends BaseHttpRequestOptions {
+   public static final String VPC_ID_PARAM = "VpcId";
+   public static final String VSWITCH_ID_PARAM = "VSwitchId";
+   public static final String ZONE_ID_PARAM = "ZoneId";
+   public static final String INSTANCE_IDS_PARAM = "InstanceIds";
+   public static final String INSTANCE_TYPE_PARAM = "InstanceType";
+   public static final String INSTANCE_TYPE_FAMILY_PARAM = "InstanceTypeFamily";
+   public static final String INSTANCE_NETWORK_TYPE_PARAM = "InstanceNetworkType";
+   public static final String PRIVATE_IP_ADDRESSES_PARAM = "PrivateIpAddresses";
+   public static final String INNER_IP_ADDRESSES_PARAM = "InnerIpAddresses";
+   public static final String PUBLIC_IP_ADDRESSES_PARAM = "PublicIpAddresses";
+   public static final String SECURITY_GROUP_ID_PARAM = "SecurityGroupId";
+   public static final String INSTANCE_CHARGE_TYPE_PARAM = "InstanceChargeType";
+   public static final String SPOT_STRATEGY_PARAM = "SpotStrategy";
+   public static final String INTERNET_CHARGE_TYPE_PARAM = "InternetChargeType";
+   public static final String INSTANCE_NAME_PARAM = "InstanceName";
+   public static final String IMAGE_ID_PARAM = "ImageId";
+   public static final String DEPLOYMENT_SET_ID_PARAM = "DeploymentSetId";
+   public static final String STATUS_PARAM = "Status";
+   public static final String IO_OPTIMIZED_PARAM = "IoOptimized";
+
+   public ListInstancesOptions vpcId(String vpcId) {
+      queryParameters.put(VPC_ID_PARAM, vpcId);
+      return this;
+   }
+
+   public ListInstancesOptions vSwitchId(String vSwitchId) {
+      queryParameters.put(VSWITCH_ID_PARAM, vSwitchId);
+      return this;
+   }
+
+   public ListInstancesOptions zoneId(String zoneId) {
+      queryParameters.put(ZONE_ID_PARAM, zoneId);
+      return this;
+   }
+
+   public ListInstancesOptions instanceIds(String... instanceIds) {
+      String instanceIdsAsString = Joiner.on(",")
+              .join(Iterables.transform(Arrays.asList(instanceIds), new PutStringInDoubleQuotes()));
+      queryParameters.put(INSTANCE_IDS_PARAM, String.format("[%s]", instanceIdsAsString));
+      return this;
+   }
+
+   public ListInstancesOptions instanceType(String instanceType) {
+      queryParameters.put(INSTANCE_TYPE_PARAM, instanceType);
+      return this;
+   }
+
+   public ListInstancesOptions instanceTypeFamily(String instanceTypeFamily) {
+      queryParameters.put(INSTANCE_TYPE_FAMILY_PARAM, instanceTypeFamily);
+      return this;
+   }
+
+   public ListInstancesOptions instanceNetworkType(String instanceNetworkType) {
+      queryParameters.put(INSTANCE_NETWORK_TYPE_PARAM, instanceNetworkType);
+      return this;
+   }
+
+   public ListInstancesOptions privateIpAddresses(String... privateIpAddresses) {
+      String instanceIdsAsString = Joiner.on(",")
+              .join(Iterables.transform(Arrays.asList(privateIpAddresses), new PutStringInDoubleQuotes()));
+      queryParameters.put(PRIVATE_IP_ADDRESSES_PARAM, String.format("[%s]", instanceIdsAsString));
+      return this;
+   }
+
+   public ListInstancesOptions innerIpAddresses(String... innerIpAddresses) {
+      String instanceIdsAsString = Joiner.on(",")
+              .join(Iterables.transform(Arrays.asList(innerIpAddresses), new PutStringInDoubleQuotes()));
+      queryParameters.put(INNER_IP_ADDRESSES_PARAM, String.format("[%s]", instanceIdsAsString));
+      return this;
+   }
+
+   public ListInstancesOptions publicIpAddresses(String... publicIpAddresses) {
+      String instanceIdsAsString = Joiner.on(",")
+              .join(Iterables.transform(Arrays.asList(publicIpAddresses), new PutStringInDoubleQuotes()));
+      queryParameters.put(PUBLIC_IP_ADDRESSES_PARAM, String.format("[%s]", instanceIdsAsString));
+      return this;
+   }
+
+   public ListInstancesOptions securityGroupId(String securityGroupId) {
+      queryParameters.put(SECURITY_GROUP_ID_PARAM, securityGroupId);
+      return this;
+   }
+
+   public ListInstancesOptions instanceChargeType(String instanceChargeType) {
+      queryParameters.put(INSTANCE_CHARGE_TYPE_PARAM, instanceChargeType);
+      return this;
+   }
+
+   public ListInstancesOptions spotStrategy(String spotStrategy) {
+      queryParameters.put(SPOT_STRATEGY_PARAM, spotStrategy);
+      return this;
+   }
+
+   public ListInstancesOptions internetChargeType(String internetChargeType) {
+      queryParameters.put(INTERNET_CHARGE_TYPE_PARAM, internetChargeType);
+      return this;
+   }
+
+   public ListInstancesOptions instanceName(String instanceName) {
+      queryParameters.put(INSTANCE_NAME_PARAM, instanceName);
+      return this;
+   }
+
+   public ListInstancesOptions imageId(String imageId) {
+      queryParameters.put(IMAGE_ID_PARAM, imageId);
+      return this;
+   }
+
+   public ListInstancesOptions deploymentSetId(String deploymentSetId) {
+      queryParameters.put(DEPLOYMENT_SET_ID_PARAM, deploymentSetId);
+      return this;
+   }
+
+   public ListInstancesOptions status(String status) {
+      queryParameters.put(STATUS_PARAM, status);
+      return this;
+   }
+
+   public ListInstancesOptions ioOptimized(String ioOptimized) {
+      queryParameters.put(IO_OPTIMIZED_PARAM, ioOptimized);
+      return this;
+   }
+
+   public ListInstancesOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public ListInstancesOptions tagOptions(final TagOptions tagOptions) {
+      this.queryParameters.putAll(tagOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      public static ListInstancesOptions vpcId(String vpcId) {
+         return new ListInstancesOptions().vpcId(vpcId);
+      }
+
+      public static ListInstancesOptions vSwitchId(String vSwitchId) {
+         return new ListInstancesOptions().vSwitchId(vSwitchId);
+      }
+
+      public static ListInstancesOptions zoneId(String zoneId) {
+         return new ListInstancesOptions().zoneId(zoneId);
+      }
+
+      public static ListInstancesOptions instanceIds(String... instanceIds) {
+         return new ListInstancesOptions().instanceIds(instanceIds);
+      }
+
+      public static ListInstancesOptions instanceType(String instanceType) {
+         return new ListInstancesOptions().instanceType(instanceType);
+      }
+
+      public static ListInstancesOptions instanceTypeFamily(String instanceTypeFamily) {
+         return new ListInstancesOptions().instanceTypeFamily(instanceTypeFamily);
+      }
+
+      public static ListInstancesOptions instanceNetworkType(String instanceNetworkType) {
+         return new ListInstancesOptions().instanceNetworkType(instanceNetworkType);
+      }
+
+      public static ListInstancesOptions privateIpAddresses(String... privateIpAddresses) {
+         return new ListInstancesOptions().privateIpAddresses(privateIpAddresses);
+      }
+
+      public static ListInstancesOptions innerIpAddresses(String... innerIpAddresses) {
+         return new ListInstancesOptions().innerIpAddresses(innerIpAddresses);
+      }
+
+      public static ListInstancesOptions publicIpAddresses(String... publicIpAddresses) {
+         return new ListInstancesOptions().publicIpAddresses(publicIpAddresses);
+      }
+
+      public static ListInstancesOptions securityGroupId(String securityGroupId) {
+         return new ListInstancesOptions().securityGroupId(securityGroupId);
+      }
+
+      public static ListInstancesOptions instanceChargeType(String instanceChargeType) {
+         return new ListInstancesOptions().instanceChargeType(instanceChargeType);
+      }
+
+      public static ListInstancesOptions instanceName(String instanceName) {
+         return new ListInstancesOptions().instanceName(instanceName);
+      }
+
+      public static ListInstancesOptions imageId(String imageId) {
+         return new ListInstancesOptions().imageId(imageId);
+      }
+
+      public static ListInstancesOptions deploymentSetId(String deploymentSetId) {
+         return new ListInstancesOptions().deploymentSetId(deploymentSetId);
+      }
+
+      public static ListInstancesOptions status(String status) {
+         return new ListInstancesOptions().status(status);
+      }
+
+      public static ListInstancesOptions ioOptimized(String ioOptimized) {
+         return new ListInstancesOptions().ioOptimized(ioOptimized);
+      }
+
+      public static ListInstancesOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListInstancesOptions().paginationOptions(paginationOptions);
+      }
+
+      public static ListInstancesOptions tagOptions(TagOptions tagOptions) {
+         return new ListInstancesOptions().tagOptions(tagOptions);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVPCsOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVPCsOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVPCsOptions.java
new file mode 100644
index 0000000..8915251
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVPCsOptions.java
@@ -0,0 +1,63 @@
+/*
+ * 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 ListVPCsOptions extends BaseHttpRequestOptions {
+   public static final String VPC_ID_PARAM = "VpcId";
+   public static final String IS_DEFAULT_PARAM = "IsDefault";
+
+   public ListVPCsOptions vpcId(String vpcId) {
+      queryParameters.put(VPC_ID_PARAM, vpcId);
+      return this;
+   }
+
+   public ListVPCsOptions isDefault(Boolean isDefault) {
+      queryParameters.put(IS_DEFAULT_PARAM, isDefault.toString());
+      return this;
+   }
+
+   public ListVPCsOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListVSwitchesOptions#vpcId(String)}
+       */
+      public static ListVPCsOptions vpcId(String vpcId) {
+         return new ListVPCsOptions().vpcId(vpcId);
+      }
+
+      /**
+       * @see {@link ListVPCsOptions#isDefault(Boolean)}
+       */
+      public static ListVPCsOptions isDefault(Boolean isDefault) {
+         return new ListVPCsOptions().isDefault(isDefault);
+      }
+
+      /**
+       * @see ListVPCsOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListVPCsOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListVPCsOptions().paginationOptions(paginationOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVSwitchesOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVSwitchesOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVSwitchesOptions.java
new file mode 100644
index 0000000..8661175
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListVSwitchesOptions.java
@@ -0,0 +1,89 @@
+/*
+ * 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 ListVSwitchesOptions extends BaseHttpRequestOptions {
+   public static final String VPC_ID_PARAM = "VpcId";
+   public static final String VSWITCH_ID_PARAM = "VSwitchId";
+   public static final String ZONE_ID_PARAM = "ZoneId";
+   public static final String IS_DEFAULT_PARAM = "IsDefault";
+
+   public ListVSwitchesOptions vpcId(String vpcId) {
+      queryParameters.put(VPC_ID_PARAM, vpcId);
+      return this;
+   }
+
+   public ListVSwitchesOptions vSwitchId(String vSwitchId) {
+      queryParameters.put(VSWITCH_ID_PARAM, vSwitchId);
+      return this;
+   }
+
+   public ListVSwitchesOptions zoneId(String zoneId) {
+      queryParameters.put(ZONE_ID_PARAM, zoneId);
+      return this;
+   }
+
+   public ListVSwitchesOptions isDefault(Boolean isDefault) {
+      queryParameters.put(IS_DEFAULT_PARAM, isDefault.toString());
+      return this;
+   }
+
+   public ListVSwitchesOptions paginationOptions(final PaginationOptions paginationOptions) {
+      this.queryParameters.putAll(paginationOptions.buildQueryParameters());
+      return this;
+   }
+
+   public static final class Builder {
+
+      /**
+       * @see {@link ListVSwitchesOptions#vpcId(String)}
+       */
+      public static ListVSwitchesOptions vpcId(String vpcId) {
+         return new ListVSwitchesOptions().vpcId(vpcId);
+      }
+
+      /**
+       * @see {@link ListVSwitchesOptions#vSwitchId(String)}
+       */
+      public static ListVSwitchesOptions vSwitchId(String vSwitchId) {
+         return new ListVSwitchesOptions().vSwitchId(vSwitchId);
+      }
+
+      /**
+       * @see {@link ListVSwitchesOptions#zoneId(String)}
+       */
+      public static ListVSwitchesOptions zoneId(String zoneId) {
+         return new ListVSwitchesOptions().zoneId(zoneId);
+      }
+
+      /**
+       * @see {@link ListVSwitchesOptions#isDefault(Boolean)}
+       */
+      public static ListVSwitchesOptions isDefault(Boolean isDefault) {
+         return new ListVSwitchesOptions().isDefault(isDefault);
+      }
+
+      /**
+       * @see ListVSwitchesOptions#paginationOptions(PaginationOptions)
+       */
+      public static ListVSwitchesOptions paginationOptions(PaginationOptions paginationOptions) {
+         return new ListVSwitchesOptions().paginationOptions(paginationOptions);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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
index 4e3068e..f220fcd 100644
--- 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
@@ -21,8 +21,8 @@ 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.Map;
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -66,6 +66,16 @@ public class TagOptions extends BaseHttpRequestOptions {
       return this;
    }
 
+   public TagOptions tags(Map<String, String> tags) {
+      checkState(tags.size() <= 5, "tags size must be <= 5");
+      int i = 1;
+      for (Map.Entry<String, String> entry : tags.entrySet()) {
+         tag(i, entry.getKey(), entry.getValue());
+         i++;
+      }
+      return this;
+   }
+
    public static class Builder {
 
       public static TagOptions tag(int pos, String key, String value) {
@@ -79,6 +89,10 @@ public class TagOptions extends BaseHttpRequestOptions {
       public static TagOptions keys(Set<String> keys) {
          return new TagOptions().keys(keys);
       }
+
+      public static TagOptions tags(Map<String, String> tags) {
+         return new TagOptions().tags(tags);
+      }
    }
 
    private void validateInput(final String input, int maxLength) {
@@ -86,10 +100,10 @@ public class TagOptions extends BaseHttpRequestOptions {
       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);
+         public boolean apply(String forbiddenPrefix) {
+            return input.startsWith(forbiddenPrefix);
          }
-      }), "Cannot starts with " + Iterables.toString(FORBIDDEN_PREFIX));
+      }), input + " cannot starts with any of " + Iterables.toString(FORBIDDEN_PREFIX));
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/ImageInRegion.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/ImageInRegion.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/ImageInRegion.java
new file mode 100644
index 0000000..5a5bf4f
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/ImageInRegion.java
@@ -0,0 +1,36 @@
+/*
+ * 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.regionscoped;
+
+import com.google.auto.value.AutoValue;
+import org.jclouds.aliyun.ecs.domain.Image;
+
+
+@AutoValue
+public abstract class ImageInRegion {
+
+   ImageInRegion() {}
+
+   public abstract String regionId();
+   public abstract Image image();
+
+   public static ImageInRegion create(String regionId, Image image) {
+      return new AutoValue_ImageInRegion(regionId, image);
+   }
+
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/RegionAndId.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/RegionAndId.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/RegionAndId.java
new file mode 100644
index 0000000..6d2a392
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/regionscoped/RegionAndId.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain.regionscoped;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@AutoValue
+public abstract class RegionAndId {
+
+   RegionAndId() {}
+
+   public static RegionAndId create(String regionId, String id) {
+      return new AutoValue_RegionAndId(regionId, id);
+   }
+
+   public static RegionAndId fromSlashEncoded(String id) {
+      Iterable<String> parts = Splitter.on('/').split(checkNotNull(id, "id"));
+      checkArgument(Iterables.size(parts) == 2, "id must be in format regionId/id");
+      return RegionAndId.create(Iterables.get(parts, 0), Iterables.get(parts, 1));
+   }
+
+   public static String slashEncodeRegionAndId(String regionId, String id) {
+      return slashEncodeRegionAndId(RegionAndId.create(regionId, id));
+   }
+
+   public static String slashEncodeRegionAndId(RegionAndId regionAndId) {
+      checkNotNull(regionAndId, "regionAndId");
+      return String.format("%s/%s", regionAndId.regionId(), regionAndId.id());
+   }
+
+   public abstract String regionId();
+
+   public abstract String id();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/InstanceApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/InstanceApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/InstanceApi.java
new file mode 100644
index 0000000..457a3ac
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/InstanceApi.java
@@ -0,0 +1,264 @@
+/*
+ * 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.AllocatePublicIpAddressRequest;
+import org.jclouds.aliyun.ecs.domain.AvailableZone;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.InstanceRequest;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.aliyun.ecs.domain.Request;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.options.CreateInstanceOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListInstanceStatusOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListInstancesOptions;
+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.Produces;
+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/25500.htm?spm=a2c63.p38356.b99.287.129a44a8RBMBLH
+ */
+@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 InstanceApi {
+
+   @Named("instance:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeInstances")
+   @ResponseParser(ParseInstances.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<Instance> list(@QueryParam("RegionId") String region, ListInstancesOptions options);
+
+   @Named("instance:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeInstances")
+   @ResponseParser(ParseInstances.class)
+   @Transform(ParseInstances.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<Instance> list(@QueryParam("RegionId") String region);
+
+   @Named("instanceType:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeInstanceTypes")
+   @SelectJson("InstanceType")
+   @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class)
+   List<InstanceType> listTypes();
+
+   @Named("instanceType:list")
+   @GET
+   @QueryParams(keys = { "Action", "DestinationResource", "IoOptimized" },
+                values = { "DescribeAvailableResource", "InstanceType", "optimized" })
+   @SelectJson("AvailableZone")
+   @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class)
+   List<AvailableZone> listInstanceTypesByAvailableZone(@QueryParam("RegionId") String regionId);
+
+   @Named("instance:create")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "CreateInstance")
+   InstanceRequest create(@QueryParam("RegionId") String regionId,
+                          @QueryParam("ImageId") String imageId,
+                          @QueryParam("SecurityGroupId") String securityGroupId,
+                          @QueryParam("HostName") String hostname,
+                          @QueryParam("InstanceType") String instanceType);
+
+   @Named("instance:create")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "CreateInstance")
+   InstanceRequest create(@QueryParam("RegionId") String regionId,
+                          @QueryParam("ImageId") String imageId,
+                          @QueryParam("SecurityGroupId") String securityGroupId,
+                          @QueryParam("HostName") String hostname,
+                          @QueryParam("InstanceType") String instanceType,
+                          CreateInstanceOptions options);
+
+   @Named("instance:allocatePublicIpAddress")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "AllocatePublicIpAddress")
+   AllocatePublicIpAddressRequest allocatePublicIpAddress(@QueryParam("RegionId") String regionId,
+                                                          @QueryParam("InstanceId") String instanceId);
+
+   @Named("instance:listInstanceStatus")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeInstanceStatus")
+   @ResponseParser(ParseInstanceStatus.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<InstanceStatus> listInstanceStatus(@QueryParam("RegionId") String region, ListInstanceStatusOptions options);
+
+   @Named("instance:listInstanceStatus")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeInstanceStatus")
+   @ResponseParser(ParseInstanceStatus.class)
+   @Transform(ParseInstanceStatus.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<InstanceStatus> listInstanceStatus(@QueryParam("RegionId") String region);
+
+   /**
+    * You can only release an instance that is in the Stopped (Stopped) status.
+    *
+    * @param instanceId
+    */
+   @Named("instance:delete")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "DeleteInstance")
+   @Fallback(Fallbacks.NullOnNotFoundOr404.class)
+   Request delete(@QueryParam("InstanceId") String instanceId);
+
+   @Named("instance:powerOff")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "StopInstance")
+   Request powerOff(@QueryParam("InstanceId") String instanceId);
+
+   @Named("instance:powerOn")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "StartInstance")
+   Request powerOn(@QueryParam("InstanceId") String instanceId);
+
+   @Named("instance:reboot")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @QueryParams(keys = "Action", values = "RebootInstance")
+   Request reboot(@QueryParam("InstanceId") String instanceId);
+
+   @Singleton
+   final class ParseInstances extends ParseJson<ParseInstances.Instances> {
+
+      @Inject
+      ParseInstances(final Json json) {
+         super(json, TypeLiteral.get(Instances.class));
+      }
+
+      private static class Instances extends PaginatedCollection<Instance> {
+
+         @ConstructorProperties({ "Instances", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+         public Instances(Map<String, Iterable<Instance>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+
+      private static class ToPagedIterable extends ArgsToPagedIterable<Instance, ToPagedIterable> {
+
+         private ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(final ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<Instance>> 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 ListInstancesOptions original = (ListInstancesOptions) Iterables.tryFind(args, Predicates.instanceOf(ListInstancesOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<Instance>>() {
+               @Override
+               public IterableWithMarker<Instance> apply(Object input) {
+                  ListInstancesOptions options = original == null ?
+                          ListInstancesOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.instanceApi().list(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+   @Singleton
+   final class ParseInstanceStatus extends ParseJson<ParseInstanceStatus.InstanceStatuses> {
+
+      @Inject
+      ParseInstanceStatus(final Json json) {
+         super(json, TypeLiteral.get(InstanceStatuses.class));
+      }
+
+      private static class InstanceStatuses extends PaginatedCollection<InstanceStatus> {
+
+         @ConstructorProperties({ "InstanceStatuses", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+         public InstanceStatuses(Map<String, Iterable<InstanceStatus>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+
+      private static class ToPagedIterable extends ArgsToPagedIterable<InstanceStatus, ToPagedIterable> {
+
+         private ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(final ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<InstanceStatus>> 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 ListInstanceStatusOptions original = (ListInstanceStatusOptions) Iterables.tryFind(args, Predicates.instanceOf(ListInstanceStatusOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<InstanceStatus>>() {
+               @Override
+               public IterableWithMarker<InstanceStatus> apply(Object input) {
+                  ListInstanceStatusOptions options = original == null ?
+                          ListInstanceStatusOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.instanceApi().listInstanceStatus(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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
index c70bcb6..9b36bf8 100644
--- 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
@@ -57,6 +57,9 @@ import java.beans.ConstructorProperties;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * https://www.alibabacloud.com/help/doc-detail/25553.htm?spm=a2c63.p38356.b99.323.1a3b59abPkInRB
+ */
 @Consumes(MediaType.APPLICATION_JSON)
 @RequestFilters(FormSign.class)
 @QueryParams(keys = {"Version", "Format", "SignatureVersion", "ServiceCode", "SignatureMethod"},

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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
index b207335..41e9937 100644
--- 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
@@ -55,6 +55,9 @@ import java.beans.ConstructorProperties;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * https://www.alibabacloud.com/help/doc-detail/51771.htm?spm=a2c63.p38356.b99.338.1da53569DqQAVv
+ */
 @Consumes(MediaType.APPLICATION_JSON)
 @RequestFilters(FormSign.class)
 @QueryParams(keys = { "Version", "Format", "SignatureVersion", "ServiceCode", "SignatureMethod" },
@@ -135,6 +138,7 @@ public interface SshKeyPairApi {
    @Named("sshKeyPair:delete")
    @POST
    @QueryParams(keys = "Action", values = "DeleteKeyPairs")
+   @Fallback(Fallbacks.NullOnNotFoundOr404.class)
    Request delete(@QueryParam("RegionId") String region,
                   @ParamParser(ArrayToCommaSeparatedString.class) @QueryParam("KeyPairNames") String... keyPairNames);
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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
index 67c3b82..e75c086 100644
--- 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
@@ -24,6 +24,7 @@ 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.ResourceType;
 import org.jclouds.aliyun.ecs.domain.Tag;
 import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
 import org.jclouds.aliyun.ecs.domain.options.ListTagsOptions;
@@ -53,6 +54,9 @@ import java.beans.ConstructorProperties;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * https://www.alibabacloud.com/help/doc-detail/25616.htm?spm=a2c63.p38356.b99.382.580b30373FFIDb
+ */
 @Consumes(MediaType.APPLICATION_JSON)
 @RequestFilters(FormSign.class)
 @QueryParams(keys = { "Version", "Format", "SignatureVersion", "ServiceCode", "SignatureMethod" },
@@ -122,7 +126,7 @@ public interface TagApi {
    @POST
    @QueryParams(keys = "Action", values = "AddTags")
    Request add(@QueryParam("RegionId") String region, @QueryParam("ResourceId") String resourceId,
-                            @QueryParam("ResourceType") String resourceType,
+                            @QueryParam("ResourceType") ResourceType resourceType,
                             TagOptions tagOptions);
 
    @Named("tag:remove")
@@ -130,14 +134,14 @@ public interface TagApi {
    @QueryParams(keys = "Action", values = "RemoveTags")
    Request remove(@QueryParam("RegionId") String region,
                   @QueryParam("ResourceId") String resourceId,
-                  @QueryParam("ResourceType") String resourceType);
+                  @QueryParam("ResourceType") ResourceType resourceType);
 
    @Named("tag:remove")
    @POST
    @QueryParams(keys = "Action", values = "RemoveTags")
    Request remove(@QueryParam("RegionId") String region,
                   @QueryParam("ResourceId") String resourceId,
-                  @QueryParam("ResourceType") String resourceType,
+                  @QueryParam("ResourceType") ResourceType resourceType,
                   TagOptions options);
 }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VPCApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VPCApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VPCApi.java
new file mode 100644
index 0000000..f2e9e5f
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VPCApi.java
@@ -0,0 +1,140 @@
+/*
+ * 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.VPC;
+import org.jclouds.aliyun.ecs.domain.VPCRequest;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.options.CreateVPCOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVPCsOptions;
+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.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;
+
+/**
+ * https://www.alibabacloud.com/help/doc-detail/35737.htm?spm=a2c63.p38356.b99.44.2554c880ZhTTkh
+ */
+@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 VPCApi {
+
+   @Named("vpc:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeVpcs")
+   @ResponseParser(ParseVPCs.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<VPC> list(@QueryParam("RegionId") String region, ListVPCsOptions options);
+
+   @Named("vpc:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeVpcs")
+   @ResponseParser(ParseVPCs.class)
+   @Transform(ParseVPCs.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<VPC> list(@QueryParam("RegionId") String region);
+
+   @Singleton
+   final class ParseVPCs extends ParseJson<ParseVPCs.VPCs> {
+
+      private static class VPCs extends PaginatedCollection<VPC> {
+
+         @ConstructorProperties({ "Vpcs", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+         public VPCs(Map<String, Iterable<VPC>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+      @Inject
+      ParseVPCs(final Json json) {
+         super(json, TypeLiteral.get(VPCs.class));
+      }
+
+      static class ToPagedIterable extends ArgsToPagedIterable<VPC, ToPagedIterable> {
+
+         private final ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<VPC>> 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 ListVPCsOptions original = (ListVPCsOptions) Iterables.tryFind(args, Predicates.instanceOf(ListVPCsOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<VPC>>() {
+               @Override
+               public IterableWithMarker<VPC> apply(Object input) {
+                  ListVPCsOptions options = original == null ?
+                          ListVPCsOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.vpcApi().list(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+   @Named("vpc:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateVpc")
+   VPCRequest create(@QueryParam("RegionId") String region);
+
+   @Named("vpc:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateVpc")
+   VPCRequest create(@QueryParam("RegionId") String region, CreateVPCOptions vpcOptions);
+
+   @Named("vpc:delete")
+   @POST
+   @QueryParams(keys = "Action", values = "DeleteVpc")
+   Request delete(@QueryParam("RegionId") String region,
+                  @QueryParam("VpcId") String vpcId);
+}
+

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VSwitchApi.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VSwitchApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VSwitchApi.java
new file mode 100644
index 0000000..5508ea1
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/VSwitchApi.java
@@ -0,0 +1,145 @@
+/*
+ * 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.VSwitch;
+import org.jclouds.aliyun.ecs.domain.VSwitchRequest;
+import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection;
+import org.jclouds.aliyun.ecs.domain.options.CreateVSwitchOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+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.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;
+
+/**
+ * https://www.alibabacloud.com/help/doc-detail/35745.htm
+ */
+@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 VSwitchApi {
+
+   @Named("vswitch:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeVSwitches")
+   @ResponseParser(ParseVSwitches.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<VSwitch> list(@QueryParam("RegionId") String region, ListVSwitchesOptions options);
+
+   @Named("vswitch:list")
+   @GET
+   @QueryParams(keys = "Action", values = "DescribeVSwitches")
+   @ResponseParser(ParseVSwitches.class)
+   @Transform(ParseVSwitches.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<VSwitch> list(@QueryParam("RegionId") String region);
+
+   @Singleton
+   final class ParseVSwitches extends ParseJson<ParseVSwitches.VSwitches> {
+
+      private static class VSwitches extends PaginatedCollection<VSwitch> {
+
+         @ConstructorProperties({ "VSwitches", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" })
+         public VSwitches(Map<String, Iterable<VSwitch>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) {
+            super(content, pageNumber, totalCount, pageSize, regionId, requestId);
+         }
+      }
+      @Inject
+      ParseVSwitches(final Json json) {
+         super(json, TypeLiteral.get(VSwitches.class));
+      }
+
+      static class ToPagedIterable extends ArgsToPagedIterable<VSwitch, ParseVSwitches.ToPagedIterable> {
+
+         private final ECSComputeServiceApi api;
+
+         @Inject
+         ToPagedIterable(ECSComputeServiceApi api) {
+            this.api = api;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<VSwitch>> 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 ListVSwitchesOptions original = (ListVSwitchesOptions) Iterables.tryFind(args, Predicates.instanceOf(ListVSwitchesOptions.class)).orNull();
+
+            return new Function<Object, IterableWithMarker<VSwitch>>() {
+               @Override
+               public IterableWithMarker<VSwitch> apply(Object input) {
+                  ListVSwitchesOptions options = original == null ?
+                          ListVSwitchesOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)) :
+                          original.paginationOptions(PaginationOptions.class.cast(input));
+                  return api.vSwitchApi().list(regionId, options);
+               }
+            };
+         }
+      }
+   }
+
+   @Named("vswitch:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateVSwitch")
+   VSwitchRequest create(@QueryParam("ZoneId") String zone,
+                         @QueryParam("CidrBlock") String cidrBlock,
+                         @QueryParam("VpcId") String vpcId);
+
+   @Named("vswitch:create")
+   @POST
+   @QueryParams(keys = "Action", values = "CreateVSwitch")
+   VSwitchRequest create(@QueryParam("ZoneId") String zone,
+                         @QueryParam("CidrBlock") String cidrBlock,
+                         @QueryParam("VpcId") String vpcId,
+                         CreateVSwitchOptions vSwitchOptions);
+
+   @Named("vswitch:delete")
+   @POST
+   @QueryParams(keys = "Action", values = "DeleteVSwitch")
+   Request delete(@QueryParam("RegionId") String region,
+                  @QueryParam("VSwitchId") String vSwitchId);
+}
+

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/PutStringInDoubleQuotes.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/PutStringInDoubleQuotes.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/PutStringInDoubleQuotes.java
new file mode 100644
index 0000000..98efc8d
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/PutStringInDoubleQuotes.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public class PutStringInDoubleQuotes implements Function<String, String> {
+
+   @Override
+   public String apply(String input) {
+      return new StringBuilder(input.length() + 1).append('"').append(input).append('"').toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSErrorRetryHandler.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSErrorRetryHandler.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSErrorRetryHandler.java
new file mode 100644
index 0000000..0a32145
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSErrorRetryHandler.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.google.inject.Inject;
+import org.jclouds.aliyun.ecs.domain.ErrorMessage;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
+import org.jclouds.json.Json;
+
+import java.io.ByteArrayInputStream;
+import java.util.Set;
+
+import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
+
+/**
+ * Handles Retryable responses with error codes in the 4xx range
+ */
+public class ECSErrorRetryHandler implements HttpRetryHandler {
+
+   private final Json json;
+   private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
+   private final Set<String> retryableCodes;
+
+   @Inject
+   public ECSErrorRetryHandler(Json json, BackoffLimitedRetryHandler backoffLimitedRetryHandler,
+         @ClientError Set<String> retryableCodes) {
+      this.json = json;
+      this.backoffLimitedRetryHandler = backoffLimitedRetryHandler;
+      this.retryableCodes = retryableCodes;
+   }
+
+   @Override
+   public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
+      byte[] content = HttpUtils.closeClientButKeepContentStream(response);
+
+      if (response.getStatusCode() == 400 || response.getStatusCode() == 403 || response.getStatusCode() == 409) {
+         // Content can be null in the case of HEAD requests
+         if (response.getPayload() != null) {
+            closeClientButKeepContentStream(response);
+            ErrorMessage error = json.fromJson(new ByteArrayInputStream(content), ErrorMessage.class);
+            if (error != null) {
+               return shouldRetryRequestOnError(command, response, error);
+            }
+         }
+      }
+      return false;
+   }
+
+   protected boolean shouldRetryRequestOnError(HttpCommand command, HttpResponse response, ErrorMessage error) {
+      if (retryableCodes.contains(error.code()))
+         return backoffLimitedRetryHandler.shouldRetryRequest(command, response);
+      return false;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/predicates/InstanceStatusPredicate.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/predicates/InstanceStatusPredicate.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/predicates/InstanceStatusPredicate.java
new file mode 100644
index 0000000..9a42261
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/predicates/InstanceStatusPredicate.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.predicates;
+
+import com.google.common.base.Predicate;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+
+public class InstanceStatusPredicate implements Predicate<InstanceStatus> {
+   private final String instanceId;
+
+   public InstanceStatusPredicate(String instanceId) {
+      this.instanceId = instanceId;
+   }
+
+   @Override
+   public boolean apply(InstanceStatus instanceStatus) {
+      return instanceStatus.instanceId().equalsIgnoreCase(instanceId);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceLiveTest.java
new file mode 100644
index 0000000..74e635c
--- /dev/null
+++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceLiveTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Module;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.Test;
+
+/**
+ * Live tests for the {@link org.jclouds.compute.ComputeService} integration.
+ */
+@Test(groups = "live", singleThreaded = true, testName = "ECSComputeServiceLiveTest")
+public class ECSComputeServiceLiveTest extends BaseComputeServiceLiveTest {
+
+   public ECSComputeServiceLiveTest() {
+      provider = "alibaba-ecs";
+   }
+
+   @Override
+   protected Module getSshModule() {
+      return new SshjSshClientModule();
+   }
+
+   @Override
+   protected void checkUserMetadataContains(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
+      // The ECS API does not return the user data
+   }
+
+}


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/instanceTypes.json
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/test/resources/instanceTypes.json b/aliyun-ecs/src/test/resources/instanceTypes.json
new file mode 100644
index 0000000..7a9a81a
--- /dev/null
+++ b/aliyun-ecs/src/test/resources/instanceTypes.json
@@ -0,0 +1,4281 @@
+{
+  "InstanceTypes": {
+    "InstanceType": [
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.t1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t1.xsmall",
+        "GPUSpec": "",
+        "MemorySize": 0.5,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.t1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t1.small",
+        "GPUSpec": "",
+        "MemorySize": 1.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.s2",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s2.small",
+        "GPUSpec": "",
+        "MemorySize": 2.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.s3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.s3.medium",
+        "GPUSpec": "",
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.c1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.c1.small",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.c2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.c2.medium",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.s1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s1.small",
+        "GPUSpec": "",
+        "MemorySize": 2.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.s2",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s2.large",
+        "GPUSpec": "",
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.s3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.s3.large",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.c1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.c1.large",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.c2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.c2.large",
+        "GPUSpec": "",
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.s1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s1.medium",
+        "GPUSpec": "",
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.s2",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s2.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.m1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.m1.medium",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.m1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.m1.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.c2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.c2.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.s1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s1.large",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.s2",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.s2.2xlarge",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.m2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.m2.medium",
+        "GPUSpec": "",
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.m2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.m2.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.n1.tiny",
+        "GPUSpec": "",
+        "MemorySize": 1.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.n1.small",
+        "GPUSpec": "",
+        "MemorySize": 2.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n1.medium",
+        "GPUSpec": "",
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n1.large",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n1.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n1.3xlarge",
+        "GPUSpec": "",
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.n1",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n1.7xlarge",
+        "GPUSpec": "",
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.n2",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.n2.small",
+        "GPUSpec": "",
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.n2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n2.medium",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.n2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n2.large",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.n2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n2.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.n2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n2.3xlarge",
+        "GPUSpec": "",
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.n2",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n2.7xlarge",
+        "GPUSpec": "",
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.e3",
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.e3.small",
+        "GPUSpec": "",
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.e3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.e3.medium",
+        "GPUSpec": "",
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.e3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.e3.large",
+        "GPUSpec": "",
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.e3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.e3.xlarge",
+        "GPUSpec": "",
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.e3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.e3.3xlarge",
+        "GPUSpec": "",
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.e3",
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.e3.7xlarge",
+        "GPUSpec": "",
+        "MemorySize": 256.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.sn1",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.sn1.medium",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.sn2",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.sn2.medium",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.sn1",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.sn1.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.sn1",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.sn1.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.sn1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn1.3xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.sn1",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn1.7xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.sn2",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.sn2.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.sn2",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.sn2.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.sn2",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn2.3xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.sn2",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn2.7xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.n4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 50000,
+        "InstancePpsTx": 50000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.n4.small",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 2.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.n4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.n4.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.n4",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 150000,
+        "InstancePpsTx": 150000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.n4",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n4.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.n4",
+        "InstanceBandwidthRx": 2560000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n4.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2560000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.n4",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.n4.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.mn4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 50000,
+        "InstancePpsTx": 50000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.mn4.small",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.mn4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.mn4.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.mn4",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 150000,
+        "InstancePpsTx": 150000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.mn4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.mn4",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.mn4.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.mn4",
+        "InstanceBandwidthRx": 2560000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.mn4.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2560000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.mn4",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.mn4.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.xn4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 50000,
+        "InstancePpsTx": 50000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.xn4.small",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 1.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.e4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.e4.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.e4",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 150000,
+        "InstancePpsTx": 150000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.e4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.e4",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.e4.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.e4",
+        "InstanceBandwidthRx": 2560000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.e4.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2560000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 12,
+        "InstanceTypeFamily": "ecs.gn3",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 200000,
+        "LocalStorageCapacity": 350,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.gn3.2xlarge",
+        "GPUSpec": "NVidia K2",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 24.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.sn2",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1200000,
+        "InstancePpsTx": 1200000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn2.13xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 224.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.e4",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 50000,
+        "InstancePpsTx": 50000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.e4.small",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.se1",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.se1.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.se1",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.se1.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.se1",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.se1.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.se1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.se1.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.se1",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.se1.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 256.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.se1",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1200000,
+        "InstancePpsTx": 1200000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.se1.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 480.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.ga1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 500000,
+        "LocalStorageCapacity": 350,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.ga1.4xlarge",
+        "GPUSpec": "AMD S7150",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 40.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.ga1",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "LocalStorageCapacity": 700,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.ga1.8xlarge",
+        "GPUSpec": "AMD S7150",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 80.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.ga1",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1200000,
+        "LocalStorageCapacity": 1400,
+        "InstancePpsTx": 1200000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.ga1.14xlarge",
+        "GPUSpec": "AMD S7150",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 160.0,
+        "GPUAmount": 4,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.gn4",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn4.8xlarge",
+        "GPUSpec": "NVIDIA M40",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 48.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.gn4",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1200000,
+        "InstancePpsTx": 1200000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn4.14xlarge",
+        "GPUSpec": "NVIDIA M40",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 96.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "LocalStorageCapacity": 104,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.i1.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 400000,
+        "LocalStorageCapacity": 208,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.i1.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 500000,
+        "LocalStorageCapacity": 416,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i1.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "LocalStorageCapacity": 832,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i1.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1200000,
+        "LocalStorageCapacity": 1456,
+        "InstancePpsTx": 1200000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i1.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 224.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.cm4",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.cm4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.cm4",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.cm4.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.cm4",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.cm4.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.ce4",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.ce4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.c4",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.c4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.c4",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.c4.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.c4",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.c4.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.cm4",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1200000,
+        "InstancePpsTx": 1200000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.cm4.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 96.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.sc1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sc1.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 4,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 300000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.d1.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 8,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 600000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 12,
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 8192000,
+        "InstancePpsRx": 800000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 8192000,
+        "MemorySize": 96.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 16,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1000000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 28,
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 17408000,
+        "InstancePpsRx": 1800000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 1800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 17408000,
+        "MemorySize": 224.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.ga1",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 300000,
+        "LocalStorageCapacity": 175,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.ga1.2xlarge",
+        "GPUSpec": "AMD S7150/2",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 20.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.ga1",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 200000,
+        "LocalStorageCapacity": 87,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.ga1.xlarge",
+        "GPUSpec": "AMD S7150/4",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 10.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.sn1ne",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.sn1ne.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.sn1ne",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.sn1ne.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.sn1ne",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.sn1ne.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.sn1ne",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 1600000,
+        "InstancePpsTx": 1600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn1ne.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.sn1ne",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 2500000,
+        "InstancePpsTx": 2500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn1ne.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.sn2ne",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.sn2ne.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.sn2ne",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.sn2ne.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.sn2ne",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.sn2ne.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.sn2ne",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 1600000,
+        "InstancePpsTx": 1600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn2ne.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.sn2ne",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 2500000,
+        "InstancePpsTx": 2500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn2ne.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.sn2ne",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.sn2ne.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 224.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.se1ne",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.se1ne.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.se1ne",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.se1ne.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.se1ne",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.se1ne.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.se1ne",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 1600000,
+        "InstancePpsTx": 1600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.se1ne.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.se1ne",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 2500000,
+        "InstancePpsTx": 2500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.se1ne.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 256.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.se1ne",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.se1ne.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 480.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 800000,
+        "LocalStorageCapacity": 1456,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i1-c10d1.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.i1",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 400000,
+        "LocalStorageCapacity": 1456,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i1-c5d1.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.f1",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.f1-c8f1.2xlarge",
+        "GPUSpec": "INTEL ARRIA 10 GX 1150",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 60.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 300000,
+        "LocalStorageCapacity": 440,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.gn5-c4g1.xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 30.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 400000,
+        "LocalStorageCapacity": 440,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.gn5-c8g1.2xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 60.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 1000000,
+        "LocalStorageCapacity": 880,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.gn5-c4g1.2xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 60.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 1000000,
+        "LocalStorageCapacity": 880,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5-c8g1.4xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 120.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 2000000,
+        "LocalStorageCapacity": 1760,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5-c8g1.8xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 240.0,
+        "GPUAmount": 4,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 54,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 25600000,
+        "InstancePpsRx": 4000000,
+        "LocalStorageCapacity": 1760,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5-c8g1.14xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 25600000,
+        "MemorySize": 480.0,
+        "GPUAmount": 8,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.gn4",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.gn4-c4g1.xlarge",
+        "GPUSpec": "NVIDIA M40",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 30.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.gn4",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.gn4-c8g1.2xlarge",
+        "GPUSpec": "NVIDIA M40",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 30.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.gn4",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.gn4-c4g1.2xlarge",
+        "GPUSpec": "NVIDIA M40",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 60.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.gn4",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn4-c8g1.4xlarge",
+        "GPUSpec": "NVIDIA M40",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 60.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 4,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.d1ne",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 1000000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.d1ne.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 8,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.d1ne",
+        "InstanceBandwidthRx": 12288000,
+        "InstancePpsRx": 1600000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 1600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1ne.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 12288000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 12,
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.d1ne",
+        "InstanceBandwidthRx": 16384000,
+        "InstancePpsRx": 2000000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1ne.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 16384000,
+        "MemorySize": 96.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 16,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.d1ne",
+        "InstanceBandwidthRx": 20480000,
+        "InstancePpsRx": 2500000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 2500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1ne.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 20480000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 28,
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.d1ne",
+        "InstanceBandwidthRx": 35840000,
+        "InstancePpsRx": 4500000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1ne.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 35840000,
+        "MemorySize": 224.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 12,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 1000000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1-c8d3.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "LocalStorageAmount": 12,
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.d1",
+        "InstanceBandwidthRx": 17408000,
+        "InstancePpsRx": 1400000,
+        "LocalStorageCapacity": 5500,
+        "InstancePpsTx": 1400000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.d1-c14d3.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 17408000,
+        "MemorySize": 160.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_hdd_pro"
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.c5.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.c5.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 2560000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.c5.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2560000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.c5.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 7680000,
+        "InstancePpsRx": 1500000,
+        "InstancePpsTx": 1500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.c5.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 7680000,
+        "MemorySize": 48.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.c5.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.c5",
+        "InstanceBandwidthRx": 20480000,
+        "InstancePpsRx": 4000000,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.c5.16xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 20480000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.g5.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.g5.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 2560000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.g5.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2560000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.g5.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 7680000,
+        "InstancePpsRx": 1500000,
+        "InstancePpsTx": 1500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.g5.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 7680000,
+        "MemorySize": 96.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.g5.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.g5",
+        "InstanceBandwidthRx": 20480000,
+        "InstancePpsRx": 4000000,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.g5.16xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 20480000,
+        "MemorySize": 256.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.r5.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.r5.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 2560000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.r5.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2560000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.r5.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 7680000,
+        "InstancePpsRx": 1500000,
+        "InstancePpsTx": 1500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.r5.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 7680000,
+        "MemorySize": 192.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.r5.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 256.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 20480000,
+        "InstancePpsRx": 4000000,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.r5.16xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 20480000,
+        "MemorySize": 512.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 88,
+        "InstanceTypeFamily": "ecs.r5",
+        "InstanceBandwidthRx": 30720000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 15,
+        "InstanceTypeId": "ecs.r5.22xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 30720000,
+        "MemorySize": 704.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.hfc5",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.hfc5.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.hfc5",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.hfc5.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.hfc5",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.hfc5.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.hfc5",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 1600000,
+        "InstancePpsTx": 1600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfc5.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.hfc5",
+        "InstanceBandwidthRx": 4608000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfc5.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 4608000,
+        "MemorySize": 48.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.hfc5",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 2500000,
+        "InstancePpsTx": 2500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfc5.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 300000,
+        "InstancePpsTx": 300000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.hfg5.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 500000,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.hfg5.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 1000000,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.hfg5.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 1600000,
+        "InstancePpsTx": 1600000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfg5.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 24,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 4608000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfg5.6xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 4608000,
+        "MemorySize": 96.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 2500000,
+        "InstancePpsTx": 2500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfg5.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.hfg5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4000000,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.hfg5.14xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 160.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.i2",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 500000,
+        "LocalStorageCapacity": 894,
+        "InstancePpsTx": 500000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.i2.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.i2",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 1000000,
+        "LocalStorageCapacity": 1788,
+        "InstancePpsTx": 1000000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.i2.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 64.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 2,
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.i2",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 1500000,
+        "LocalStorageCapacity": 1788,
+        "InstancePpsTx": 1500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i2.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 128.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 4,
+        "CpuCoreCount": 32,
+        "InstanceTypeFamily": "ecs.i2",
+        "InstanceBandwidthRx": 6144000,
+        "InstancePpsRx": 2000000,
+        "LocalStorageCapacity": 1788,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i2.8xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 6144000,
+        "MemorySize": 256.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 8,
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.i2",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4000000,
+        "LocalStorageCapacity": 1788,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.i2.16xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 512.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 28,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 2250000,
+        "LocalStorageCapacity": 440,
+        "InstancePpsTx": 2250000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5-c28g1.7xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 112.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "LocalStorageAmount": 1,
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.gn5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "LocalStorageCapacity": 880,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5-c28g1.14xlarge",
+        "GPUSpec": "NVIDIA P100",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 224.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": "local_ssd_pro"
+      },
+      {
+        "CpuCoreCount": 80,
+        "InstanceTypeFamily": "ecs.re4",
+        "InstanceBandwidthRx": 15360000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.re4.20xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 15360000,
+        "MemorySize": 960.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 160,
+        "InstanceTypeFamily": "ecs.re4",
+        "InstanceBandwidthRx": 30720000,
+        "InstancePpsRx": 4000000,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.re4.40xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 30720000,
+        "MemorySize": 1920.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.gn5i",
+        "InstanceBandwidthRx": 1024000,
+        "InstancePpsRx": 100000,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.gn5i-c2g1.large",
+        "GPUSpec": "Nvidia Tesla P4",
+        "InstanceBandwidthTx": 1024000,
+        "MemorySize": 8.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.gn5i",
+        "InstanceBandwidthRx": 1536000,
+        "InstancePpsRx": 200000,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 3,
+        "InstanceTypeId": "ecs.gn5i-c4g1.xlarge",
+        "GPUSpec": "Nvidia Tesla P4",
+        "InstanceBandwidthTx": 1536000,
+        "MemorySize": 16.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.gn5i",
+        "InstanceBandwidthRx": 2048000,
+        "InstancePpsRx": 400000,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 4,
+        "InstanceTypeId": "ecs.gn5i-c8g1.2xlarge",
+        "GPUSpec": "Nvidia Tesla P4",
+        "InstanceBandwidthTx": 2048000,
+        "MemorySize": 32.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.gn5i",
+        "InstanceBandwidthRx": 3072000,
+        "InstancePpsRx": 800000,
+        "InstancePpsTx": 800000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5i-c16g1.4xlarge",
+        "GPUSpec": "Nvidia Tesla P4",
+        "InstanceBandwidthTx": 3072000,
+        "MemorySize": 64.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.gn5i",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5i-c28g1.14xlarge",
+        "GPUSpec": "Nvidia Tesla P4",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 224.0,
+        "GPUAmount": 2,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 28,
+        "InstanceTypeFamily": "ecs.gn5t",
+        "InstanceBandwidthRx": 13312000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5t.7xlarge",
+        "GPUSpec": "Nvidia GPU",
+        "InstanceBandwidthTx": 13312000,
+        "MemorySize": 112.0,
+        "GPUAmount": 4,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 56,
+        "InstanceTypeFamily": "ecs.gn5t",
+        "InstanceBandwidthRx": 25600000,
+        "InstancePpsRx": 4000000,
+        "InstancePpsTx": 4000000,
+        "EniQuantity": 8,
+        "InstanceTypeId": "ecs.gn5t.14xlarge",
+        "GPUSpec": "Nvidia GPU",
+        "InstanceBandwidthTx": 25600000,
+        "MemorySize": 224.0,
+        "GPUAmount": 8,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InitialCredit": 180,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-c1m1.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 2.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 30
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "InitialCredit": 360,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m1.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 60
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 400000,
+        "InitialCredit": 720,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m1.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 120
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 600000,
+        "InitialCredit": 1440,
+        "InstancePpsTx": 600000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m1.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 240
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InitialCredit": 180,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-c1m2.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 30
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "InitialCredit": 360,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m2.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 60
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 400000,
+        "InitialCredit": 720,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m2.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 120
+      },
+      {
+        "CpuCoreCount": 16,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 600000,
+        "InitialCredit": 1440,
+        "InstancePpsTx": 600000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m2.4xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 240
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 512000,
+        "InstancePpsRx": 100000,
+        "InitialCredit": 180,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-c1m4.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 512000,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 30
+      },
+      {
+        "CpuCoreCount": 4,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 819200,
+        "InstancePpsRx": 200000,
+        "InitialCredit": 360,
+        "InstancePpsTx": 200000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m4.xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 819200,
+        "MemorySize": 16.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 60
+      },
+      {
+        "CpuCoreCount": 8,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 1228800,
+        "InstancePpsRx": 400000,
+        "InitialCredit": 720,
+        "InstancePpsTx": 400000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.t5-c1m4.2xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 1228800,
+        "MemorySize": 32.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 120
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 102400,
+        "InstancePpsRx": 40000,
+        "InitialCredit": 90,
+        "InstancePpsTx": 40000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-lc2m1.nano",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 102400,
+        "MemorySize": 0.5,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 10
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 204800,
+        "InstancePpsRx": 60000,
+        "InitialCredit": 90,
+        "InstancePpsTx": 60000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-lc1m1.small",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 204800,
+        "MemorySize": 1.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 10
+      },
+      {
+        "CpuCoreCount": 1,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 204800,
+        "InstancePpsRx": 60000,
+        "InitialCredit": 90,
+        "InstancePpsTx": 60000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-lc1m2.small",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 204800,
+        "MemorySize": 2.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 10
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 409600,
+        "InstancePpsRx": 100000,
+        "InitialCredit": 180,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-lc1m2.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 409600,
+        "MemorySize": 4.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 20
+      },
+      {
+        "CpuCoreCount": 2,
+        "InstanceTypeFamily": "ecs.t5",
+        "InstanceBandwidthRx": 409600,
+        "InstancePpsRx": 100000,
+        "InitialCredit": 180,
+        "InstancePpsTx": 100000,
+        "EniQuantity": 1,
+        "InstanceTypeId": "ecs.t5-lc1m4.large",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 409600,
+        "MemorySize": 8.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": "",
+        "BaselineCredit": 20
+      },
+      {
+        "CpuCoreCount": 28,
+        "InstanceTypeFamily": "ecs.f1",
+        "InstanceBandwidthRx": 5120000,
+        "InstancePpsRx": 2000000,
+        "InstancePpsTx": 2000000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.f1-c28f1.7xlarge",
+        "GPUSpec": "INTEL ARRIA 10 GX 1150",
+        "InstanceBandwidthTx": 5120000,
+        "MemorySize": 112.0,
+        "GPUAmount": 1,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 96,
+        "InstanceTypeFamily": "ecs.sccg5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 32,
+        "InstanceTypeId": "ecs.sccg5.24xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 384.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.scch5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 32,
+        "InstanceTypeId": "ecs.scch5.16xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 192.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.sccgn5d",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.sccgn5d.16xlarge",
+        "GPUSpec": "Nvidia GTX 1080Ti",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 256.0,
+        "GPUAmount": 8,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 64,
+        "InstanceTypeFamily": "ecs.sccgn5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 2,
+        "InstanceTypeId": "ecs.sccgn5.16xlarge",
+        "GPUSpec": "Nvidia P100",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 512.0,
+        "GPUAmount": 8,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 96,
+        "InstanceTypeFamily": "ecs.ebmg5",
+        "InstanceBandwidthRx": 10240000,
+        "InstancePpsRx": 4500000,
+        "InstancePpsTx": 4500000,
+        "EniQuantity": 32,
+        "InstanceTypeId": "ecs.ebmg5.24xlarge",
+        "GPUSpec": "",
+        "InstanceBandwidthTx": 10240000,
+        "MemorySize": 384.0,
+        "GPUAmount": 0,
+        "LocalStorageCategory": ""
+      },
+      {
+        "CpuCoreCount": 96,
+        "InstanceTypeFamily": "e

<TRUNCATED>

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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodes.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodes.java
new file mode 100644
index 0000000..8d302a2
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodes.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.strategy;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.Constants;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions;
+import org.jclouds.aliyun.ecs.domain.IpProtocol;
+import org.jclouds.aliyun.ecs.domain.KeyPair;
+import org.jclouds.aliyun.ecs.domain.KeyPairRequest;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.VPCRequest;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.VSwitchRequest;
+import org.jclouds.aliyun.ecs.domain.Zone;
+import org.jclouds.aliyun.ecs.domain.options.CreateSecurityGroupOptions;
+import org.jclouds.aliyun.ecs.domain.options.CreateVPCOptions;
+import org.jclouds.aliyun.ecs.domain.options.CreateVSwitchOptions;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+import org.jclouds.aliyun.ecs.domain.options.TagOptions;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.logging.Logger;
+import org.jclouds.ssh.SshKeys;
+
+import javax.annotation.Nullable;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.aliyun.ecs.domain.ResourceType.SECURITYGROUP;
+import static org.jclouds.compute.util.ComputeServiceUtils.getPortRangesFromList;
+
+@Singleton
+public class CreateResourcesThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
+
+   public static final String INTERNET = "0.0.0.0/0";
+   public static final String DEFAULT_CIDR_BLOCK = "172.16.1.0/24";
+   public static final String JCLOUDS_KEYPAIR_IMPORTED = "jclouds-imported";
+   public static final String PORT_RANGE_FORMAT = "%d/%d";
+   protected static final String DEFAULT_DESCRIPTION_SUFFIX = "created by jclouds";
+   protected static final String VSWITCH_PREFIX = "vswitch";
+   protected static final String VPC_PREFIX = "vpc";
+
+   private final ECSComputeServiceApi api;
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   protected CreateResourcesThenCreateNodes(CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+                                          ListNodesStrategy listNodesStrategy, GroupNamingConvention.Factory namingConvention,
+                                          @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+                                          CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
+                                          ECSComputeServiceApi api) {
+      super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
+            customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+      this.api = api;
+   }
+
+   @Override
+   public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
+                                                 Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
+                                                 Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+      String regionId = template.getLocation().getId();
+      ECSServiceTemplateOptions options = template.getOptions().as(ECSServiceTemplateOptions.class);
+
+      Optional<SecurityGroup> securityGroupOptional = tryFindSecurityGroupInRegion(regionId, options.getGroups());
+
+      String vpcIdFromSecurityGroup;
+      String vpcId;
+
+      if (securityGroupOptional.isPresent()) {
+         vpcIdFromSecurityGroup = securityGroupOptional.get().vpcId();
+         if (!Strings.isNullOrEmpty(options.getVSwitchId())) {
+            validateVSwitchId(regionId, options.getVSwitchId(), securityGroupOptional.get().name(), vpcIdFromSecurityGroup);
+         } else {
+            String message = String.format("Security group (%s) belongs to VPC (%s). Please specify a vSwitch Id of that VPC (%s) using ECSServiceTemplateOptions.vSwitchId",
+            securityGroupOptional.get().name(),
+                    vpcIdFromSecurityGroup,
+                    vpcIdFromSecurityGroup);
+            throw new IllegalStateException(message);
+         }
+      } else {
+         if (!Strings.isNullOrEmpty(options.getVSwitchId())) {
+            VSwitch vSwitch = tryFindVSwitch(regionId, options.getVSwitchId());
+            vpcId = vSwitch.vpcId();
+         } else {
+            vpcId = createDefaultVPC(regionId, group);
+            String vSwitchId = createDefaultVSwitch(regionId, vpcId, group);
+            options.vSwitchId(vSwitchId);
+         }
+         String createdSecurityGroupId = createSecurityGroupForOptions(group, regionId, vpcId, options);
+         options.securityGroups(createdSecurityGroupId);
+      }
+
+
+      // If keys haven't been configured, generate a key pair
+      if (Strings.isNullOrEmpty(options.getPublicKey()) &&
+          Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+         String uniqueNameForGroup = namingConvention.create().uniqueNameForGroup(group);
+         KeyPairRequest keyPairRequest = generateKeyPair(regionId, uniqueNameForGroup);
+         options.keyPairName(keyPairRequest.getKeyPairName());
+         options.overrideLoginPrivateKey(keyPairRequest.getPrivateKeyBody());
+      }
+
+      // If there is a script to run in the node, make sure a private key has
+      // been configured so jclouds will be able to access the node
+      if (options.getRunScript() != null && Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+         logger.warn(">> A runScript has been configured but no SSH key has been provided. Authentication will delegate to the ssh-agent");
+      }
+
+      // If there is a public key configured, then make sure there is a key pair for it
+      if (!Strings.isNullOrEmpty(options.getPublicKey())) {
+         KeyPair keyPair = getOrImportKeyPairForPublicKey(options, regionId);
+         options.keyPairName(keyPair.name());
+      }
+
+      Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes, customizationResponses);
+
+      // Key pairs are only required to create the devices.
+      // Better to delete the auto-generated key pairs when they are mo more required
+      registerAutoGeneratedKeyPairCleanupCallbacks(responses, regionId, options.getKeyPairName());
+
+      return responses;
+   }
+
+   private void validateVSwitchId(String regionId,
+                                  String vSwitchId,
+                                  String securityGroupName,
+                                  String vpcIdFromSecurityGroup) {
+      Optional<VSwitch> optionalVSwitch = tryFindVSwitchInVPC(regionId, vpcIdFromSecurityGroup, vSwitchId);
+      if (!optionalVSwitch.isPresent()) {
+         String message = String.format("security group (%s) and vSwitch (%s) must be in the same VPC_PREFIX (%s)",
+                 securityGroupName,
+                 optionalVSwitch.get().name(),
+                 vpcIdFromSecurityGroup);
+
+         throw new IllegalStateException(message);
+      }
+   }
+
+   private String createDefaultVPC(String regionId, String group) {
+      String vpcName = String.format("%s-%s", VPC_PREFIX, group);
+      VPCRequest vpcRequest = api.vpcApi().create(regionId, CreateVPCOptions.Builder.vpcName(vpcName).description(String.format("%s - %s", VPC_PREFIX, DEFAULT_DESCRIPTION_SUFFIX)));
+      return vpcRequest.getVpcId();
+   }
+
+   private String createDefaultVSwitch(String regionId, String vpcId, String name) {
+      String vSwitchName = String.format("%s-%s", VSWITCH_PREFIX, name);
+      Zone zone = Iterables.getFirst(api.regionAndZoneApi().describeZones(regionId), null);
+      VSwitchRequest vSwitchRequest = api.vSwitchApi().create(zone.id(), DEFAULT_CIDR_BLOCK, vpcId,
+              CreateVSwitchOptions.Builder.vSwitchName(vSwitchName).description(String.format("%s - %s", vSwitchName, DEFAULT_DESCRIPTION_SUFFIX)));
+      return vSwitchRequest.getVSwitchId();
+   }
+
+   private KeyPair getOrImportKeyPairForPublicKey(ECSServiceTemplateOptions options, String regionId) {
+      logger.debug(">> checking if the key pair already exists...");
+      PublicKey userKey = readPublicKey(options.getPublicKey());
+      final String fingerprint = computeFingerprint(userKey);
+      KeyPair keyPair;
+
+      synchronized (CreateResourcesThenCreateNodes.class) {
+         Optional<KeyPair> keyPairOptional = Iterables
+               .tryFind(api.sshKeyPairApi().list(regionId).concat(), new Predicate<KeyPair>() {
+                  @Override
+                  public boolean apply(KeyPair input) {
+                     return input.keyPairFingerPrint().equals(fingerprint.replace(":", ""));
+                  }
+               });
+         if (!keyPairOptional.isPresent()) {
+            logger.debug(">> key pair not found. Importing a new key pair %s ...", fingerprint);
+            keyPair = api.sshKeyPairApi().importKeyPair(
+                    regionId,
+                    options.getPublicKey(),
+                    namingConvention.create().uniqueNameForGroup(JCLOUDS_KEYPAIR_IMPORTED));
+            logger.debug(">> key pair imported! %s", keyPair);
+         } else {
+            logger.debug(">> key pair found for key %s", fingerprint);
+            keyPair = keyPairOptional.get();
+         }
+         return keyPair;
+      }
+   }
+
+   private KeyPairRequest generateKeyPair(String regionId, String uniqueNameForGroup) {
+      logger.debug(">> creating default keypair for node...");
+      KeyPairRequest keyPairRequest = api.sshKeyPairApi().create(regionId, uniqueNameForGroup);
+      logger.debug(">> keypair created! %s", keyPairRequest);
+      return keyPairRequest;
+   }
+
+   private Optional<SecurityGroup> tryFindSecurityGroupInRegion(String regionId, final Set<String> securityGroups) {
+      checkArgument(securityGroups.size() <= 1, "Only one security group can be configured for each network interface");
+      final String securityGroupId = Iterables.get(securityGroups, 0, null);
+
+      if (securityGroupId != null) {
+         return api.securityGroupApi().list(regionId).concat().firstMatch(new Predicate<SecurityGroup>() {
+            @Override
+            public boolean apply(@Nullable SecurityGroup input) {
+               return securityGroupId.equals(input.id());
+            }
+         });
+      }
+      return Optional.absent();
+   }
+
+   private VSwitch tryFindVSwitch(String regionId, String vSwitchId) {
+      ListVSwitchesOptions listVSwitchesOptions = ListVSwitchesOptions.Builder.vSwitchId(vSwitchId);
+      Optional<VSwitch> optionalVSwitch = api.vSwitchApi().list(regionId, listVSwitchesOptions).first();
+      if (!optionalVSwitch.isPresent()) {
+         String message = String.format("Cannot find a valid vSwitch with id (%s) within region (%s)",
+                 vSwitchId,
+                 regionId);
+         throw new IllegalStateException(message);
+      }
+      return optionalVSwitch.get();
+   }
+
+   private Optional<VSwitch> tryFindVSwitchInVPC(String regionId, String vpcId, String vSwitchId) {
+      ListVSwitchesOptions listVSwitchesOptions = ListVSwitchesOptions.Builder.vpcId(vpcId).vSwitchId(vSwitchId);
+      return api.vSwitchApi().list(regionId, listVSwitchesOptions).first();
+   }
+
+   private String createSecurityGroupForOptions(String group, String regionId, String vpcId,
+                                                ECSServiceTemplateOptions options) {
+      String name = namingConvention.create().sharedNameForGroup(group);
+      SecurityGroupRequest securityGroupRequest = api.securityGroupApi().create(regionId,
+              CreateSecurityGroupOptions.Builder
+                      .securityGroupName(name)
+                      .vpcId(vpcId));
+      // add rules
+      Map<Integer, Integer> portRanges = getPortRangesFromList(options.getInboundPorts());
+      for (Map.Entry<Integer, Integer> portRange : portRanges.entrySet()) {
+         String range = String.format(PORT_RANGE_FORMAT, portRange.getKey(), portRange.getValue());
+         // TODO makes protocol and source CIDR configurable?
+         api.securityGroupApi().addInboundRule(
+                 regionId,
+                 securityGroupRequest.getSecurityGroupId(),
+                 IpProtocol.TCP,
+                 range,
+                 INTERNET);
+      }
+      api.tagApi().add(regionId, securityGroupRequest.getSecurityGroupId(), SECURITYGROUP,
+              TagOptions.Builder
+                      .tag(1, Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE)
+                      .tag(2, Tag.GROUP, group));
+      return securityGroupRequest.getSecurityGroupId();
+   }
+
+   private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
+         final String regionId, final String keyPairName) {
+      // The Futures.allAsList fails immediately if some of the futures fail.
+      // The Futures.successfulAsList, however,
+      // returns a list containing the results or 'null' for those futures that
+      // failed. We want to wait for all them
+      // (even if they fail), so better use the latter form.
+      ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+
+      // Key pairs must be cleaned up after all futures completed (even if some
+      // failed).
+      Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
+         @Override
+         public void onSuccess(List<Void> result) {
+            cleanupAutoGeneratedKeyPairs(keyPairName);
+         }
+
+         @Override
+         public void onFailure(Throwable t) {
+            cleanupAutoGeneratedKeyPairs(keyPairName);
+         }
+
+         private void cleanupAutoGeneratedKeyPairs(String keyPairName) {
+            logger.debug(">> cleaning up auto-generated key pairs...");
+            try {
+               api.sshKeyPairApi().delete(regionId, keyPairName);
+            } catch (Exception ex) {
+               logger.warn(">> could not delete key pair %s: %s", keyPairName, ex.getMessage());
+            }
+         }
+      }, userExecutor);
+   }
+
+
+   private static PublicKey readPublicKey(String publicKey) {
+      Iterable<String> parts = Splitter.on(' ').split(publicKey);
+      checkArgument(size(parts) >= 2, "bad format, should be: ssh-rsa AAAAB3...");
+      String type = get(parts, 0);
+
+      try {
+         if ("ssh-rsa".equals(type)) {
+            RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(publicKey);
+            return KeyFactory.getInstance("RSA").generatePublic(spec);
+         } else {
+            throw new IllegalArgumentException("bad format, ssh-rsa is only supported");
+         }
+      } catch (InvalidKeySpecException ex) {
+         throw new RuntimeException(ex);
+      } catch (NoSuchAlgorithmException ex) {
+         throw new RuntimeException(ex);
+      }
+   }
+
+   private static String computeFingerprint(PublicKey key) {
+      if (key instanceof RSAPublicKey) {
+         RSAPublicKey rsaKey = (RSAPublicKey) key;
+         return SshKeys.fingerprint(rsaKey.getPublicExponent(), rsaKey.getModulus());
+      } else {
+         throw new IllegalArgumentException("Only RSA keys are supported");
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java
----------------------------------------------------------------------
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
index 05ebc9f..1e84c6b 100644
--- 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
@@ -16,15 +16,22 @@
  */
 package org.jclouds.aliyun.ecs.config;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Provides;
 import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
 import org.jclouds.aliyun.ecs.handlers.ECSComputeServiceErrorHandler;
+import org.jclouds.aliyun.ecs.handlers.ECSErrorRetryHandler;
 import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpRetryHandler;
 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;
 
+import javax.inject.Singleton;
+import java.util.Set;
+
 @ConfiguresHttpApi
 public class ECSComputeServiceHttpApiModule extends HttpApiModule<ECSComputeServiceApi> {
 
@@ -35,4 +42,21 @@ public class ECSComputeServiceHttpApiModule extends HttpApiModule<ECSComputeServ
       bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ECSComputeServiceErrorHandler.class);
    }
 
+   @Override
+   protected void bindRetryHandlers() {
+      bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(ECSErrorRetryHandler.class);
+   }
+
+   /**
+    * It combines the error codes explicitly described as retryable from ECS and VPC
+    * https://error-center.alibabacloud.com/status/product/Ecs?spm=a2c63.p38356.a3.5.2a9859c1Fzi5nr
+    * https://error-center.alibabacloud.com/status/product/Vpc?spm=a2c63.p38356.a3.1.1442dd2f4qFMSW
+    */
+   @Provides
+   @ClientError
+   @Singleton
+   protected final Set<String> provideRetryableCodes() {
+      return ImmutableSet.of("InstanceNotReady", "IncorrectInstanceStatus.Initializing", "DependencyViolation", "IncorrectVpcStatus", "IncorrectStatus");
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AllocatePublicIpAddressRequest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AllocatePublicIpAddressRequest.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AllocatePublicIpAddressRequest.java
new file mode 100644
index 0000000..8f03160
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AllocatePublicIpAddressRequest.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 AllocatePublicIpAddressRequest extends Request {
+
+   private final String ipAddress;
+
+   @ConstructorProperties({ "RequestId", "IpAddress" })
+   public AllocatePublicIpAddressRequest(String requestId, String ipAddress) {
+      super(requestId);
+      this.ipAddress = ipAddress;
+   }
+
+   public String getInstanceId() {
+      return ipAddress;
+   }
+
+   @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;
+      AllocatePublicIpAddressRequest that = (AllocatePublicIpAddressRequest) o;
+      return Objects.equal(ipAddress, that.ipAddress);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), ipAddress);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this).add("ipAddress", ipAddress).toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableResource.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableResource.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableResource.java
new file mode 100644
index 0000000..338663c
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableResource.java
@@ -0,0 +1,43 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+@AutoValue
+public abstract class AvailableResource {
+
+   AvailableResource() {
+   }
+
+   @SerializedNames({ "Type", "SupportedResources" })
+   public static AvailableResource create(String type, Map<String, List<SupportedResource>> supportedResources) {
+      return new AutoValue_AvailableResource(type, supportedResources == null ?
+            ImmutableMap.<String, List<SupportedResource>>of() :
+            ImmutableMap.copyOf(supportedResources));
+   }
+
+   public abstract String type();
+
+   public abstract Map<String, List<SupportedResource>> supportedResources();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableZone.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableZone.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableZone.java
new file mode 100644
index 0000000..08e65b3
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/AvailableZone.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.json.SerializedNames;
+
+import java.util.List;
+import java.util.Map;
+
+@AutoValue
+public abstract class AvailableZone {
+
+   AvailableZone() {
+   }
+
+   @SerializedNames({ "Status", "RegionId", "AvailableResources", "ZoneId" })
+   public static AvailableZone create(String status, String regionId,
+                                      Map<String, List<AvailableResource>> availableResources, String zoneId) {
+      return new AutoValue_AvailableZone(status, regionId, availableResources == null ?
+            ImmutableMap.<String, List<AvailableResource>>of() :
+            ImmutableMap.copyOf(availableResources), zoneId);
+   }
+
+   public abstract String status();
+
+   public abstract String regionId();
+
+   public abstract Map<String, List<AvailableResource>> availableResources();
+
+   public abstract String zoneId();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DedicatedHostAttribute.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DedicatedHostAttribute.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DedicatedHostAttribute.java
new file mode 100644
index 0000000..dff8e3f
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DedicatedHostAttribute.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 DedicatedHostAttribute {
+
+   DedicatedHostAttribute() {
+   }
+
+   @SerializedNames({ "DedicatedHostId", "DedicatedHostName" })
+   public static DedicatedHostAttribute create(String id, String name) {
+      return new AutoValue_DedicatedHostAttribute(id, name);
+   }
+
+   public abstract String id();
+
+   public abstract String name();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/EipAddress.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/EipAddress.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/EipAddress.java
new file mode 100644
index 0000000..10c158d
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/EipAddress.java
@@ -0,0 +1,67 @@
+/*
+ * 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.CaseFormat;
+import com.google.common.base.Enums;
+import org.jclouds.json.SerializedNames;
+
+@AutoValue
+public abstract class EipAddress {
+
+   public enum InternetChargeType {
+      ECS_INSTANCE("EcsInstance"),
+      SLB_INSTANCE ("SlbInstance"),
+      NAT ("Nat"),
+      HA_VIP("HaVip"),
+      DEFAULT("");
+
+      private final String internetChargeType;
+
+      InternetChargeType(String internetChargeType) {
+         this.internetChargeType = internetChargeType;
+      }
+
+      public static InternetChargeType fromValue(String value) {
+         return Enums.getIfPresent(InternetChargeType.class, CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, value)).or(InternetChargeType.DEFAULT);
+      }
+
+      public String internetChargeType() {
+         return internetChargeType;
+      }
+
+      @Override
+      public String toString() {
+         return internetChargeType();
+      }
+   }
+
+   EipAddress() {}
+
+   @SerializedNames({ "IpAddress", "AllocationId", "InternetChargeType" })
+   public static EipAddress create(String ipAddress, String allocationId, InternetChargeType internetChargeType) {
+      return new AutoValue_EipAddress(ipAddress, allocationId, internetChargeType);
+   }
+
+   public abstract String ipAddress();
+
+   public abstract String allocationId();
+
+   public abstract InternetChargeType internetChargeType();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ErrorMessage.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ErrorMessage.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ErrorMessage.java
new file mode 100644
index 0000000..aa3f684
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ErrorMessage.java
@@ -0,0 +1,39 @@
+/*
+ * 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 ErrorMessage {
+
+   ErrorMessage() {}
+
+   @SerializedNames({ "RequestId", "HostId", "Code", "Message" })
+   public static ErrorMessage create(String requestId, String hostId, String code, String message) {
+      return new AutoValue_ErrorMessage(requestId, hostId, code, message);
+   }
+
+   public abstract String requestId();
+
+   public abstract String hostId();
+
+   public abstract String code();
+
+   public abstract String message();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 328d6fe..1aee2c1 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
@@ -17,6 +17,10 @@
 package org.jclouds.aliyun.ecs.domain;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
 import org.jclouds.json.SerializedNames;
 
@@ -24,9 +28,31 @@ import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 @AutoValue
 public abstract class Image {
 
+   public enum Status {
+      AVAILABLE, UNAVAILABLE;
+
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
+         return status.get();
+      }
+
+      public String value() {
+         return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
+      }
+
+      @Override
+      public String toString() {
+         return value();
+      }
+   }
+
    Image() {}
 
    @SerializedNames({"ImageId", "Description", "ProductCode", "OSType", "Architecture", "OSName", "DiskDeviceMappings",
@@ -36,15 +62,15 @@ public abstract class Image {
    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 name,
+                              Map<String, List<Tag>> tags, String imageVersion, Status status, String name,
                               Boolean isSupportIoOptimized, Boolean isSelfShared, Boolean isCopied, Boolean isSubscribed, String platform,
-                              String size) {
-      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, name, isSupportIoOptimized, isSelfShared, isCopied, isSubscribed, platform, size);
+                              int size) {
+      return builder().id(id).description(description).productCode(productCode).osType(osType)
+              .architecture(architecture).osName(osName).diskDeviceMappings(diskDeviceMappings).imageOwnerAlias(imageOwnerAlias)
+              .progress(progress).isSupportCloudinit(isSupportCloudinit).usage(usage).creationTime(creationTime)
+              .tags(tags).imageVersion(imageVersion).status(status).name(name).isSupportIoOptimizeds(isSupportIoOptimized)
+              .isSelfShared(isSelfShared).isCopied(isCopied).isSubscribed(isSubscribed).platform(platform).size(size)
+              .build();
    }
 
    public abstract String id();
@@ -75,7 +101,7 @@ public abstract class Image {
 
    public abstract String imageVersion();
 
-   public abstract String status();
+   public abstract Status status();
 
    public abstract String name();
 
@@ -89,6 +115,72 @@ public abstract class Image {
 
    public abstract String platform();
 
-   public abstract String size();
+   public abstract int size();
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Image.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+
+      public abstract Builder id(String id);
+
+      public abstract Builder description(String description);
+
+      public abstract Builder productCode(String productCode);
+
+      public abstract Builder osType(String osType);
+
+      public abstract Builder architecture(String architecture);
+
+      public abstract Builder osName(String osName);
+
+      public abstract Builder diskDeviceMappings(Map<String, List<DiskDeviceMapping>> diskDeviceMappings);
+
+      public abstract Builder imageOwnerAlias(String imageOwnerAlias);
+
+      public abstract Builder progress(String progress);
+
+      public abstract Builder isSupportCloudinit(Boolean isSupportCloudinit);
+
+      public abstract Builder usage(String usage);
+
+      public abstract Builder creationTime(Date creationTime);
+
+      public abstract Builder tags(Map<String, List<Tag>> tags);
+
+      public abstract Builder imageVersion(String imageVersion);
+
+      public abstract Builder status(Status status);
+
+      public abstract Builder name(String name);
+
+      public abstract Builder isSupportIoOptimizeds(Boolean isSupportIoOptimizeds);
+
+      public abstract Builder isSelfShared(Boolean isSelfShared);
+
+      public abstract Builder isCopied(Boolean isCopied);
+
+      public abstract Builder isSubscribed(Boolean isSubscribed);
+
+      public abstract Builder platform(String platform);
+
+      public abstract Builder size(int size);
+
+      abstract Image autoBuild();
+
+      abstract Map<String, List<DiskDeviceMapping>> diskDeviceMappings();
+
+      abstract Map<String, List<Tag>> tags();
+
+      public Image build() {
+         diskDeviceMappings(diskDeviceMappings() != null ? ImmutableMap.copyOf(diskDeviceMappings()) : ImmutableMap.<String, List<DiskDeviceMapping>>of());
+         tags(tags() != null ? ImmutableMap.copyOf(tags()) : ImmutableMap.<String, List<Tag>>of());
+         return autoBuild();
+      }
+   }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Instance.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Instance.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Instance.java
new file mode 100644
index 0000000..ddf2036
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Instance.java
@@ -0,0 +1,321 @@
+/*
+ * 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.CaseFormat;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@AutoValue
+public abstract class Instance {
+
+   public enum InternetChargeType {
+      PAY_BY_TRAFFIC("PayByTraffic"),
+      DEFAULT("");
+
+      private final String internetChargeType;
+
+      InternetChargeType(String internetChargeType) {
+         this.internetChargeType = internetChargeType;
+      }
+
+      public static InternetChargeType fromValue(String value) {
+         return Enums.getIfPresent(InternetChargeType.class, CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, value)).or(InternetChargeType.DEFAULT);
+      }
+
+      public String internetChargeType() {
+         return internetChargeType;
+      }
+
+      @Override
+      public String toString() {
+         return internetChargeType();
+      }
+   }
+
+
+   public enum Status {
+      STARTING, RUNNING, STOPPING, STOPPED;
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()),
+                 value);
+         return status.get();
+      }
+   }
+
+   Instance() {}
+
+   @SerializedNames(
+         { "InnerIpAddress", "ImageId", "InstanceTypeFamily", "VlanId", "NetworkInterfaces", "InstanceId", "EipAddress",
+               "InternetMaxBandwidthIn", "ZoneId", "InternetChargeType", "SpotStrategy", "StoppedMode", "SerialNumber",
+               "IoOptimized", "Memory", "Cpu", "VpcAttributes", "InternetMaxBandwidthOut", "DeviceAvailable",
+               "SecurityGroupIds", "SaleCycle", "SpotPriceLimit", "AutoReleaseTime", "StartTime", "InstanceName",
+               "Description", "ResourceGroupId", "OSType", "OSName", "InstanceNetworkType", "PublicIpAddress",
+               "HostName", "InstanceType", "CreationTime", "Status", "Tags", "ClusterId", "Recyclable", "RegionId",
+               "GPUSpec", "DedicatedHostAttribute", "OperationLocks", "InstanceChargeType", "GPUAmount",
+               "ExpiredTime" })
+   public static Instance create(Map<String, List<String>> innerIpAddress, String imageId, String instanceTypeFamily,
+                                 String vlanId, Map<String, List<NetworkInterface>> networkInterfaces, String id, EipAddress eipAddress,
+                                 Integer internetMaxBandwidthIn, String zoneId, InternetChargeType internetChargeType, String spotStrategy,
+                                 String stoppedMode, String serialNumber, Boolean ioOptimized, Integer memory, Integer cpu,
+                                 VpcAttributes vpcAttributes, Integer internetMaxBandwidthOut, Boolean deviceAvailable,
+                                 Map<String, List<String>> securityGroupIds, String saleCycle, Double spotPriceLimit, String autoReleaseTime,
+                                 Date startTime, String name, String description, String resourceGroupId, String osType, String osName,
+                                 String instanceNetworkType, Map<String, List<String>> publicIpAddress, String hostname, String instanceType,
+                                 Date creationTime, Status status, Map<String, List<Tag>> tags, String clusterId, Boolean recyclable,
+                                 String regionId, String gpuSpec, DedicatedHostAttribute dedicatedHostAttribute,
+                                 Map<String, List<String>> operationLocks, String instanceChargeType, Integer gpuAmount, Date expiredTime) {
+      return builder().innerIpAddress(innerIpAddress).imageId(imageId).instanceTypeFamily(instanceTypeFamily).vlanId(vlanId)
+              .networkInterfaces(networkInterfaces).id(id).eipAddress(eipAddress).internetMaxBandwidthIn(internetMaxBandwidthIn)
+              .zoneId(zoneId).internetChargeType(internetChargeType).spotStrategy(spotStrategy).stoppedMode(stoppedMode).serialNumber(serialNumber)
+              .ioOptimized(ioOptimized).memory(memory).cpu(cpu).vpcAttributes(vpcAttributes).internetMaxBandwidthOut(internetMaxBandwidthOut).deviceAvailable(deviceAvailable)
+              .securityGroupIds(securityGroupIds).saleCycle(saleCycle).spotPriceLimit(spotPriceLimit).autoReleaseTime(autoReleaseTime).startTime(startTime).name(name)
+              .description(description).resourceGroupId(resourceGroupId).osType(osType).osName(osName).instanceNetworkType(instanceNetworkType).publicIpAddress(publicIpAddress)
+              .hostname(hostname).instanceType(instanceType).creationTime(creationTime).status(status).tags(tags).clusterId(clusterId).recyclable(recyclable).regionId(regionId)
+              .gpuSpec(gpuSpec).dedicatedHostAttribute(dedicatedHostAttribute).operationLocks(operationLocks).instanceChargeType(instanceChargeType).gpuAmount(gpuAmount)
+              .expiredTime(expiredTime).build();
+   }
+
+   public abstract Map<String, List<String>> innerIpAddress();
+
+   public abstract String imageId();
+
+   public abstract String instanceTypeFamily();
+
+   public abstract String vlanId();
+
+   public abstract Map<String, List<NetworkInterface>> networkInterfaces();
+
+   public abstract String id();
+
+   public abstract EipAddress eipAddress();
+
+   public abstract Integer internetMaxBandwidthIn();
+
+   public abstract String zoneId();
+
+   public abstract InternetChargeType internetChargeType();
+
+   public abstract String spotStrategy();
+
+   public abstract String stoppedMode();
+
+   public abstract String serialNumber();
+
+   public abstract Boolean ioOptimized();
+
+   public abstract Integer memory();
+
+   public abstract Integer cpu();
+
+   public abstract VpcAttributes vpcAttributes();
+
+   public abstract Integer internetMaxBandwidthOut();
+
+   public abstract Boolean deviceAvailable();
+
+   public abstract Map<String, List<String>> securityGroupIds();
+
+   public abstract String saleCycle();
+
+   public abstract Double spotPriceLimit();
+
+   public abstract String autoReleaseTime();
+
+   public abstract Date startTime();
+
+   public abstract String name();
+
+   public abstract String description();
+
+   public abstract String resourceGroupId();
+
+   public abstract String osType();
+
+   public abstract String osName();
+
+   public abstract String instanceNetworkType();
+
+   public abstract Map<String, List<String>> publicIpAddress();
+
+   public abstract String hostname();
+
+   public abstract String instanceType();
+
+   public abstract Date creationTime();
+
+   public abstract Status status();
+
+   @Nullable
+   public abstract Map<String, List<Tag>> tags();
+
+   public abstract String clusterId();
+
+   public abstract Boolean recyclable();
+
+   public abstract String regionId();
+
+   public abstract String gpuSpec();
+
+   public abstract DedicatedHostAttribute dedicatedHostAttribute();
+
+   public abstract Map<String, List<String>> operationLocks();
+
+   public abstract String instanceChargeType();
+
+   public abstract Integer gpuAmount();
+
+   public abstract Date expiredTime();
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Instance.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+
+      public abstract Builder innerIpAddress(Map<String, List<String>> innerIpAddress);
+
+      public abstract Builder imageId(String imageId);
+
+      public abstract Builder instanceTypeFamily(String instanceTypeFamily);
+
+      public abstract Builder vlanId(String vlanId);
+
+      public abstract Builder networkInterfaces(Map<String, List<NetworkInterface>> networkInterfaces);
+
+      public abstract Builder id(String id);
+
+      public abstract Builder eipAddress(EipAddress eipAddress);
+
+      public abstract Builder internetMaxBandwidthIn(Integer internetMaxBandwidthIn);
+
+      public abstract Builder zoneId(String zoneId);
+
+      public abstract Builder internetChargeType(InternetChargeType internetChargeType);
+
+      public abstract Builder spotStrategy(String spotStrategy);
+
+      public abstract Builder stoppedMode(String stoppedMode);
+
+      public abstract Builder serialNumber(String serialNumber);
+
+      public abstract Builder ioOptimized(Boolean ioOptimized);
+
+      public abstract Builder memory(Integer memory);
+
+      public abstract Builder cpu(Integer cpu);
+
+      public abstract Builder vpcAttributes(VpcAttributes vpcAttributes);
+
+      public abstract Builder internetMaxBandwidthOut(Integer internetMaxBandwidthOut);
+
+      public abstract Builder deviceAvailable(Boolean deviceAvailable);
+
+      public abstract Builder securityGroupIds(Map<String, List<String>> securityGroupIds);
+
+      public abstract Builder saleCycle(String saleCycle);
+
+      public abstract Builder spotPriceLimit(Double spotPriceLimit);
+
+      public abstract Builder autoReleaseTime(String autoReleaseTime);
+
+      public abstract Builder startTime(Date startTime);
+
+      public abstract Builder name(String name);
+
+      public abstract Builder description(String description);
+
+      public abstract Builder resourceGroupId(String resourceGroupId);
+
+      public abstract Builder osType(String osType);
+
+      public abstract Builder osName(String osName);
+
+      public abstract Builder instanceNetworkType(String instanceNetworkType);
+
+      public abstract Builder publicIpAddress(Map<String, List<String>> publicIpAddress);
+
+      public abstract Builder hostname(String hostname);
+
+      public abstract Builder instanceType(String instanceType);
+
+      public abstract Builder creationTime(Date creationTime);
+
+      public abstract Builder status(Status status);
+
+      public abstract Builder tags(Map<String, List<Tag>> tags);
+
+      public abstract Builder clusterId(String clusterId);
+
+      public abstract Builder recyclable(Boolean recyclable);
+
+      public abstract Builder regionId(String regionId);
+
+      public abstract Builder gpuSpec(String gpuSpec);
+
+      public abstract Builder dedicatedHostAttribute(DedicatedHostAttribute dedicatedHostAttribute);
+
+      public abstract Builder operationLocks(Map<String, List<String>> operationLocks);
+
+      public abstract Builder instanceChargeType(String InstanceChargeType);
+
+      public abstract Builder gpuAmount(Integer gpuAmount);
+
+      public abstract Builder expiredTime(Date expiredTime);
+
+      abstract Instance autoBuild();
+
+      abstract Map<String, List<String>> innerIpAddress();
+
+      abstract Map<String, List<NetworkInterface>> networkInterfaces();
+
+      abstract Map<String, List<String>> securityGroupIds();
+
+      abstract Map<String, List<String>> publicIpAddress();
+
+      abstract Map<String, List<String>> operationLocks();
+
+      abstract Map<String, List<Tag>> tags();
+
+      public Instance build() {
+         innerIpAddress(innerIpAddress() == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(innerIpAddress()));
+         securityGroupIds(securityGroupIds() == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(securityGroupIds()));
+         networkInterfaces(networkInterfaces() == null ? ImmutableMap.<String, List<NetworkInterface>>of() : ImmutableMap.copyOf(networkInterfaces()));
+         publicIpAddress(publicIpAddress() == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(publicIpAddress()));
+         operationLocks(operationLocks() == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(operationLocks()));
+         tags(tags() != null ? ImmutableMap.copyOf(tags()) : ImmutableMap.<String, List<Tag>>of());
+         return autoBuild();
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceRequest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceRequest.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceRequest.java
new file mode 100644
index 0000000..ca615d0
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceRequest.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 InstanceRequest extends Request {
+
+   private final String instanceId;
+
+   @ConstructorProperties({ "RequestId", "InstanceId" })
+   public InstanceRequest(String requestId, String instanceId) {
+      super(requestId);
+      this.instanceId = instanceId;
+   }
+
+   public String getInstanceId() {
+      return instanceId;
+   }
+
+   @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;
+      InstanceRequest that = (InstanceRequest) o;
+      return Objects.equal(instanceId, that.instanceId);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), instanceId);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this).add("instanceId", instanceId).toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceStatus.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceStatus.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceStatus.java
new file mode 100644
index 0000000..4ee6777
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceStatus.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.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 static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * https://www.alibabacloud.com/help/doc-detail/25687.htm?spm=a2c63.p38356.a3.3.25a15566k0U5A4
+ */
+@AutoValue
+public abstract class InstanceStatus {
+
+   InstanceStatus() {
+   }
+
+   @SerializedNames({ "InstanceId", "Status" })
+   public static InstanceStatus create(String instanceId, Status status) {
+      return new AutoValue_InstanceStatus(instanceId, status);
+   }
+
+   public abstract String instanceId();
+
+   public abstract Status status();
+
+   public enum Status {
+      PENDING, STARTING, RUNNING, STOPPING, STOPPED;
+
+      public static InstanceStatus.Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(InstanceStatus.Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s",
+               Joiner.on(',').join(InstanceStatus.Status.values()), value);
+         return status.get();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceType.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceType.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceType.java
new file mode 100644
index 0000000..1802084
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/InstanceType.java
@@ -0,0 +1,85 @@
+/*
+ * 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 InstanceType {
+
+   InstanceType() {
+   }
+
+   @SerializedNames(
+           {"InstanceTypeId", "InstanceTypeFamily", "EniQuantity",
+            "GPUSpec", "CpuCoreCount", "MemorySize", "GPUAmount", "LocalStorageCategory" })
+   public static InstanceType create(String id, String instanceTypeFamily, Integer eniQuantity,
+                                     String gpuSpec, Integer cpuCoreCount, Double memorySize, Double gpuAmount, String localStorageCategory) {
+      return builder()
+              .id(id).instanceTypeFamily(instanceTypeFamily).eniQuantity(eniQuantity)
+              .gpuSpec(gpuSpec).cpuCoreCount(cpuCoreCount).memorySize(memorySize)
+              .gpuAmount(gpuAmount).localStorageCategory(localStorageCategory)
+              .build();
+   }
+
+   public abstract String id();
+
+   public abstract Integer cpuCoreCount();
+
+   public abstract String instanceTypeFamily();
+
+   public abstract Integer eniQuantity();
+
+   public abstract String gpuSpec();
+
+   public abstract Double memorySize();
+
+   public abstract Double gpuAmount();
+
+   public abstract String localStorageCategory();
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_InstanceType.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+
+      public abstract Builder id(String id);
+
+      public abstract Builder cpuCoreCount(Integer cpuCoreCount);
+
+      public abstract Builder instanceTypeFamily(String instanceTypeFamily);
+
+      public abstract Builder eniQuantity(Integer eniQuantity);
+
+      public abstract Builder gpuSpec(String gpuSpec);
+
+      public abstract Builder memorySize(Double memorySize);
+
+      public abstract Builder gpuAmount(Double gpuAmount);
+
+      public abstract Builder localStorageCategory(String localStorageCategory);
+
+      public abstract InstanceType build();
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/NetworkInterface.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/NetworkInterface.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/NetworkInterface.java
new file mode 100644
index 0000000..6a3b6e9
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/NetworkInterface.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.auto.value.AutoValue;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+@AutoValue
+public abstract class NetworkInterface {
+
+   NetworkInterface() {
+   }
+
+   @SerializedNames({ "MacAddress", "PrimaryIpAddress", "NetworkInterfaceId" })
+   public static NetworkInterface create(String macAddress, String primaryIpAddress, String id) {
+      return new AutoValue_NetworkInterface(macAddress, primaryIpAddress, id);
+   }
+
+   @Nullable
+   public abstract String macAddress();
+
+   public abstract String primaryIpAddress();
+
+   public abstract String id();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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
index 003ccf1..bda9cb5 100644
--- 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
@@ -50,7 +50,7 @@ public abstract class Permission {
    }
 
    public enum Direction {
-      EGRESS, ALL;
+      EGRESS, INGRESS, ALL;
 
       public static Direction fromValue(String value) {
          Optional<Direction> direction = Enums.getIfPresent(Direction.class, value.toUpperCase());

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ResourceType.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ResourceType.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ResourceType.java
new file mode 100644
index 0000000..9807d83
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/ResourceType.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.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * The type of the ECS resource. All values must be lowercase.
+ */
+public enum ResourceType {
+   DISK, INSTANCE, IMAGE, SECURITYGROUP, SNAPSHOT;
+
+   public static ResourceType fromValue(String value) {
+      Optional<ResourceType> resourceType = Enums.getIfPresent(ResourceType.class, value.toUpperCase());
+      checkArgument(resourceType.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(ResourceType.values()), value);
+      return resourceType.get();
+   }
+
+   @Override
+   public String toString() {
+      return name().toLowerCase();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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
index 3fd0658..8709948 100644
--- 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
@@ -17,18 +17,24 @@
 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.List;
+import java.util.Map;
+
 @AutoValue
 public abstract class SecurityGroup {
 
    SecurityGroup() {
    }
 
-   @SerializedNames({ "SecurityGroupId", "Description", "SecurityGroupName", "VpcId" })
+   @SerializedNames({ "SecurityGroupId", "Description", "SecurityGroupName", "VpcId", "Tags" })
    public static SecurityGroup create(String id, String description, String name,
-                                      String vpcId) {
-      return new AutoValue_SecurityGroup(id, description, name, vpcId);
+                                      String vpcId, Map<String, List<Tag>> tags) {
+      return new AutoValue_SecurityGroup(id, description, name, vpcId, tags == null ?
+              ImmutableMap.<String, List<Tag>>of() :
+              ImmutableMap.copyOf(tags));
    }
 
    public abstract String id();
@@ -39,4 +45,6 @@ public abstract class SecurityGroup {
 
    public abstract String vpcId();
 
+   public abstract Map<String, List<Tag>> tags();
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SupportedResource.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SupportedResource.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SupportedResource.java
new file mode 100644
index 0000000..3547004
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/SupportedResource.java
@@ -0,0 +1,52 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkArgument;
+
+@AutoValue
+public abstract class SupportedResource {
+
+   public enum Status {
+      AVAILABLE, SOLDOUT;
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
+         return status.get();
+      }
+   }
+
+   SupportedResource() {
+   }
+
+   @SerializedNames({ "Status", "Value" })
+   public static SupportedResource create(Status status, String value) {
+      return new AutoValue_SupportedResource(status, value);
+   }
+
+   public abstract Status status();
+
+   public abstract String value();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java
----------------------------------------------------------------------
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
index 1b34226..33bc688 100644
--- 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
@@ -17,19 +17,25 @@
 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 Tag {
 
+   public static final String DEFAULT_OWNER_KEY = "owner";
+   public static final String DEFAULT_OWNER_VALUE = "jclouds";
+   public static final String GROUP = "group";
+
    Tag() {}
 
    @SerializedNames({ "TagKey", "TagValue" })
-   public static Tag create(String tagKey, String tagValue) {
-      return new AutoValue_Tag(tagKey, tagValue);
+   public static Tag create(String key, String value) {
+      return new AutoValue_Tag(key, value);
    }
 
-   public abstract String tagKey();
+   public abstract String key();
 
-   public abstract String tagValue();
+   @Nullable
+   public abstract String value();
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/UserCidr.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/UserCidr.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/UserCidr.java
new file mode 100644
index 0000000..ff91eae
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/UserCidr.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+// FIXME not possible to find properties from either the API or the doc
+@AutoValue
+public abstract class UserCidr {
+
+   UserCidr() {}
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPC.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPC.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPC.java
new file mode 100644
index 0000000..a1ed1d6
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPC.java
@@ -0,0 +1,131 @@
+/*
+ * 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.CaseFormat;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.json.SerializedNames;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@AutoValue
+public abstract class VPC {
+
+   public enum Status {
+      AVAILABLE, UNAVAILABLE, PENDING;
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
+         return status.get();
+      }
+
+      public String value() {
+         return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
+      }
+
+      @Override
+      public String toString() {
+         return value();
+      }
+   }
+
+   VPC() {}
+
+   @SerializedNames({"CidrBlock", "CreationTime", "Description", "RegionId", "Status",
+           "UserCidrs", "VRouterId", "VSwitchIds", "VpcId", "VpcName" })
+   public static VPC create(String cidrBlock, Date creationTime, String description, String regionId, Status status,
+                            Map<String, List<UserCidr>> userCidrs,
+                            String vRouterId,
+                            Map<String, List<String>> vSwitchIds,
+                            String id, String name) {
+      return builder().cidrBlock(cidrBlock).creationTime(creationTime).description(description).regionId(regionId)
+              .status(status).userCidrs(userCidrs).vRouterId(vRouterId).vSwitchIds(vSwitchIds)
+              .id(id).name(name).build();
+   }
+
+   public abstract String cidrBlock();
+
+   public abstract Date creationTime();
+
+   public abstract String description();
+
+   public abstract String regionId();
+
+   public abstract Status status();
+
+   public abstract Map<String, List<UserCidr>> userCidrs();
+
+   public abstract String vRouterId();
+
+   public abstract Map<String, List<String>> vSwitchIds();
+
+   public abstract String id();
+
+   public abstract String name();
+
+   public abstract VPC.Builder toBuilder();
+
+   public static VPC.Builder builder() {
+      return new AutoValue_VPC.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+
+      public abstract Builder cidrBlock(String cidrBlock);
+
+      public abstract Builder creationTime(Date creationTime);
+
+      public abstract Builder description(String description);
+
+      public abstract Builder regionId(String regionId);
+
+      public abstract Builder status(Status status);
+
+      public abstract Builder userCidrs(Map<String, List<UserCidr>> userCidrs);
+
+      public abstract Builder vRouterId(String vRouterId);
+
+      public abstract Builder vSwitchIds(Map<String, List<String>> vSwitchIds);
+
+      public abstract Builder id(String id);
+
+      public abstract Builder name(String name);
+
+      abstract VPC autoBuild();
+
+      abstract Map<String, List<UserCidr>> userCidrs();
+
+      abstract Map<String, List<String>> vSwitchIds();
+
+      public VPC build() {
+         userCidrs(userCidrs() == null ? ImmutableMap.<String, List<UserCidr>>of() : ImmutableMap.copyOf(userCidrs()));
+         vSwitchIds(vSwitchIds() == null ? ImmutableMap.<String, List<String>>of() : ImmutableMap.copyOf(vSwitchIds()));
+         return autoBuild();
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPCRequest.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPCRequest.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPCRequest.java
new file mode 100644
index 0000000..868c550
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VPCRequest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 VPCRequest extends Request {
+
+   private final String routeTableId;
+   private final String vRouterId;
+   private final String vpcId;
+
+   @ConstructorProperties({ "RequestId", "RouteTableId", "VRouterId", "VpcId" })
+   public VPCRequest(String requestId, String routeTableId, String vRouterId, String vpcId) {
+      super(requestId);
+      this.routeTableId = routeTableId;
+      this.vRouterId = vRouterId;
+      this.vpcId = vpcId;
+   }
+
+   public String getRouteTableId() {
+      return routeTableId;
+   }
+
+   public String getvRouterId() {
+      return vRouterId;
+   }
+
+   public String getVpcId() {
+      return vpcId;
+   }
+
+   @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;
+      VPCRequest that = (VPCRequest) o;
+      return Objects.equal(routeTableId, that.routeTableId) &&
+              Objects.equal(vRouterId, that.vRouterId) &&
+              Objects.equal(vpcId, that.vpcId);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), routeTableId, vRouterId, vpcId);
+   }
+
+   @Override
+   public String toString() {
+      return MoreObjects.toStringHelper(this)
+              .add("routeTableId", routeTableId)
+              .add("vRouterId", vRouterId)
+              .add("vpcId", vpcId)
+              .toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitch.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitch.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitch.java
new file mode 100644
index 0000000..9f73e30
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/VSwitch.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.domain;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.CaseFormat;
+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 VSwitch {
+
+   public enum Status {
+      AVAILABLE, UNAVAILABLE, PENDING;
+
+      public static Status fromValue(String value) {
+         Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
+         checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
+         return status.get();
+      }
+
+      public String value() {
+         return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
+      }
+
+      @Override
+      public String toString() {
+         return value();
+      }
+   }
+
+   VSwitch() {}
+
+   @SerializedNames({"CidrBlock", "CreationTime", "Description", "ZoneId", "Status",
+           "AvailableIpAddressCount", "VpcId", "VSwitchId", "VSwitchName" })
+   public static VSwitch create(String cidrBlock, Date creationTime, String description, String zoneId, Status status,
+                                int availableIpAddressCount,
+                                String vpcId, String id, String name) {
+      return builder().cidrBlock(cidrBlock).creationTime(creationTime).description(description).zoneId(zoneId).status(status)
+              .availableIpAddressCount(availableIpAddressCount).vpcId(vpcId).id(id).name(name).build();
+   }
+
+   public abstract String cidrBlock();
+
+   public abstract Date creationTime();
+
+   public abstract String description();
+
+   public abstract String zoneId();
+
+   public abstract Status status();
+
+   public abstract int availableIpAddressCount();
+
+   public abstract String vpcId();
+
+   public abstract String id();
+
+   public abstract String name();
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_VSwitch.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+
+      public abstract Builder cidrBlock(String cidrBlock);
+
+      public abstract Builder creationTime(Date creationTime);
+
+      public abstract Builder description(String description);
+
+      public abstract Builder zoneId(String regionId);
+
+      public abstract Builder status(Status status);
+
+      public abstract Builder availableIpAddressCount(int availableIpAddressCount);
+
+      public abstract Builder vpcId(String vpcId);
+
+      public abstract Builder id(String id);
+
+      public abstract Builder name(String name);
+
+      abstract VSwitch build();
+   }
+
+
+}


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

Posted by an...@apache.org.
[JCLOUDS-1430] Aliyun ECS

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


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

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

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


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

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

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

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

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

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java
new file mode 100644
index 0000000..b3c6802
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.Constants;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.strategy.CleanupResources;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.VSwitch;
+import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.callables.RunScriptOnNode;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.extensions.internal.DelegatingImageExtension;
+import org.jclouds.compute.internal.BaseComputeService;
+import org.jclouds.compute.internal.PersistNodeCredentials;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.compute.strategy.DestroyNodeStrategy;
+import org.jclouds.compute.strategy.GetImageStrategy;
+import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
+import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.RebootNodeStrategy;
+import org.jclouds.compute.strategy.ResumeNodeStrategy;
+import org.jclouds.compute.strategy.SuspendNodeStrategy;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.scriptbuilder.functions.InitAdminAccess;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+
+@Singleton
+public class ECSComputeService extends BaseComputeService {
+   private final CleanupResources cleanupResources;
+
+   @Inject
+   protected ECSComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
+                               @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
+                               @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
+                               GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy,
+                               CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
+                               DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy,
+                               SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
+                               @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider,
+                               @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
+                               @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
+                               @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
+                               InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
+                               RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
+                               PersistNodeCredentials persistNodeCredentials,
+                               @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+                               CleanupResources cleanupResources, Optional<ImageExtension> imageExtension,
+                               Optional<SecurityGroupExtension> securityGroupExtension,
+                               DelegatingImageExtension.Factory delegatingImageExtension) {
+      super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
+              getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
+              startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
+              nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
+              persistNodeCredentials, userExecutor, imageExtension, securityGroupExtension, delegatingImageExtension);
+      this.cleanupResources = cleanupResources;
+   }
+
+   @Override
+   protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
+      for (NodeMetadata deadNode : deadNodes) {
+         RegionAndId regionAndId = RegionAndId.fromSlashEncoded(deadNode.getId());
+         Set<String> tags = deadNode.getTags();
+         String vSwitchId = extractVSwitchId(tags);
+         VSwitch vSwitch = context.unwrapApi(ECSComputeServiceApi.class).vSwitchApi().list(deadNode.getLocation().getId(), ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)).first().orNull();
+         String vpcId = vSwitch.vpcId();
+
+         try {
+            cleanupResources.cleanupNode(regionAndId);
+         } catch (Exception ex) {
+            logger.warn(ex, "Error cleaning up resources for node %s", deadNode);
+         }
+
+         List<SecurityGroup> securityGroups = cleanupResources.findOrphanedSecurityGroups(regionAndId.regionId(), deadNode.getGroup());
+         for (SecurityGroup securityGroup : securityGroups) {
+            logger.debug(">> destroying security group %s ...", securityGroup.id());
+            if (cleanupResources.cleanupSecurityGroupIfOrphaned(regionAndId.regionId(), securityGroup.id())) {
+               logger.debug(">> security group: (%s) has been deleted.", securityGroup.id());
+            } else {
+               logger.warn(">> security group: (%s) has not been deleted.", securityGroup.id());
+            }
+         }
+
+         // FIXME not sure it is correct to always delete vSwitch and VPC_PREFIX
+         logger.debug(">> destroying vSwitch %s ...", vSwitchId);
+         if (cleanupResources.cleanupVSwitchIfOrphaned(regionAndId.regionId(), vSwitchId)) {
+            logger.debug(">> vSwitch: (%s) has been deleted.", vSwitchId);
+         } else {
+            logger.warn(">> vSwitch: (%s) has not been deleted.", vSwitchId);
+         }
+
+         logger.debug(">> destroying vpc %s ...", vpcId);
+         try {
+            cleanupResources.cleanupVPCIfOrphaned(regionAndId.regionId(), vpcId);
+            logger.debug(">> VPC_PREFIX: (%s) has been deleted.", vpcId);
+         } catch (IllegalArgumentException e) {
+            logger.warn(">> VPC_PREFIX: (%s) has not been deleted.", vpcId);
+         }
+      }
+   }
+
+   private String extractVSwitchId(Set<String> tags) {
+      String vSwitchIdTag = Iterables.tryFind(tags, new Predicate<String>() {
+         @Override
+         public boolean apply(@Nullable String input) {
+            return input.startsWith("vsw-");
+         }
+      }).orNull();
+      return vSwitchIdTag;
+   }
+}

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

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java
new file mode 100644
index 0000000..ace98e9
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.compute.ECSComputeService;
+import org.jclouds.aliyun.ecs.compute.ECSComputeServiceAdapter;
+import org.jclouds.aliyun.ecs.compute.functions.ImageInRegionToImage;
+import org.jclouds.aliyun.ecs.compute.functions.InstanceStatusToStatus;
+import org.jclouds.aliyun.ecs.compute.functions.InstanceToNodeMetadata;
+import org.jclouds.aliyun.ecs.compute.functions.InstanceTypeToHardware;
+import org.jclouds.aliyun.ecs.compute.functions.RegionToLocation;
+import org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.InstanceType;
+import org.jclouds.aliyun.ecs.domain.Region;
+import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions;
+import org.jclouds.aliyun.ecs.predicates.InstanceStatusPredicate;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatement;
+import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatementWithoutPublicKey;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.domain.Location;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+import static org.jclouds.util.Predicates2.retry;
+
+public class ECSServiceContextModule extends ComputeServiceAdapterContextModule<Instance, InstanceType, ImageInRegion, Region> {
+
+   @Override
+   protected void configure() {
+      super.configure();
+
+      bind(new TypeLiteral<ComputeServiceAdapter<Instance, InstanceType, ImageInRegion, Region>>() {
+      }).to(ECSComputeServiceAdapter.class);
+      bind(ComputeService.class).to(ECSComputeService.class);
+
+      bind(new TypeLiteral<Function<Instance, NodeMetadata>>() {
+      }).to(InstanceToNodeMetadata.class);
+      bind(new TypeLiteral<Function<InstanceType, Hardware>>() {
+      }).to(InstanceTypeToHardware.class);
+      bind(new TypeLiteral<Function<ImageInRegion, org.jclouds.compute.domain.Image>>() {
+      }).to(ImageInRegionToImage.class);
+      bind(new TypeLiteral<Function<Region, Location>>() {
+      }).to(RegionToLocation.class);
+      bind(new TypeLiteral<Function<Instance.Status, NodeMetadata.Status>>() {
+      }).to(InstanceStatusToStatus.class);
+      install(new LocationsFromComputeServiceAdapterModule<Instance, InstanceType, ImageInRegion, Region>() {
+      });
+      bind(TemplateOptions.class).to(ECSServiceTemplateOptions.class);
+      bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourcesThenCreateNodes.class);
+      bind(NodeAndTemplateOptionsToStatement.class).to(NodeAndTemplateOptionsToStatementWithoutPublicKey.class);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_RUNNING)
+   protected Predicate<String> provideInstanceRunningPredicate(final ECSComputeServiceApi api,
+                                                               ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new InstanceInStatusPredicate(api, InstanceStatus.Status.RUNNING), timeouts.nodeRunning,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_SUSPENDED)
+   protected Predicate<String> provideInstanceSuspendedPredicate(final ECSComputeServiceApi api,
+                                                                 ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new InstanceInStatusPredicate(api, InstanceStatus.Status.STOPPED), timeouts.nodeSuspended,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_TERMINATED)
+   protected Predicate<String> provideInstanceTerminatedPredicate(final ECSComputeServiceApi api,
+                                                                  ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new InstanceTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
+              pollPeriod.pollMaxPeriod);
+   }
+
+   @VisibleForTesting
+   static class InstanceInStatusPredicate implements Predicate<String> {
+
+      private final ECSComputeServiceApi api;
+      private final InstanceStatus.Status desiredStatus;
+
+      public InstanceInStatusPredicate(ECSComputeServiceApi api, InstanceStatus.Status desiredStatus) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.desiredStatus = checkNotNull(desiredStatus, "instance status must not be null");
+      }
+
+      @Override
+      public boolean apply(String id) {
+         checkNotNull(id, "id");
+         RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+         String regionId = regionAndId.regionId();
+         String instanceId = regionAndId.id();
+         InstanceStatus instanceStatus = api.instanceApi().listInstanceStatus(regionId)
+                 .concat()
+                 .firstMatch(new InstanceStatusPredicate(instanceId))
+                 .orNull();
+         return instanceStatus != null && desiredStatus == instanceStatus.status();
+      }
+   }
+
+   @VisibleForTesting
+   static class InstanceTerminatedPredicate implements Predicate<String> {
+
+      private final ECSComputeServiceApi api;
+
+      public InstanceTerminatedPredicate(ECSComputeServiceApi api) {
+         this.api = checkNotNull(api, "api must not be null");
+      }
+
+      @Override
+      public boolean apply(String id) {
+         checkNotNull(id, "id");
+         RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+         String regionId = regionAndId.regionId();
+         final String instanceId = regionAndId.id();
+         InstanceStatus instanceStatus = api.instanceApi().listInstanceStatus(regionId)
+                 .concat()
+                 .firstMatch(new InstanceStatusPredicate(instanceId))
+                 .orNull();
+         return instanceStatus == null;
+      }
+
+   }
+
+}

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

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java
new file mode 100644
index 0000000..8fcaf18
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.compute.domain.NodeMetadata;
+
+import javax.inject.Singleton;
+
+/**
+ * Transforms an {@link Instance.Status} to the jclouds portable model.
+ */
+@Singleton
+public class InstanceStatusToStatus implements Function<Instance.Status, NodeMetadata.Status> {
+
+   private static final Function<Instance.Status, NodeMetadata.Status> toPortableStatus = Functions.forMap(
+         ImmutableMap.<Instance.Status, NodeMetadata.Status>builder()
+                 .put(Instance.Status.STARTING, NodeMetadata.Status.PENDING)
+                 .put(Instance.Status.STOPPING, NodeMetadata.Status.PENDING)
+                 .put(Instance.Status.STOPPED, NodeMetadata.Status.SUSPENDED)
+                 .put(Instance.Status.RUNNING, NodeMetadata.Status.RUNNING).build(), NodeMetadata.Status.UNRECOGNIZED);
+
+   @Override
+   public NodeMetadata.Status apply(Instance.Status input) {
+      return toPortableStatus.apply(input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java
new file mode 100644
index 0000000..1ead7ce
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.aliyun.ecs.domain.Instance;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.location.predicates.LocationPredicates;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.FluentIterable.from;
+import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromValuesOfEmptyString;
+
+/**
+ * Transforms an {@link Instance} to the jclouds portable model.
+ */
+@Singleton
+public class InstanceToNodeMetadata implements Function<Instance, NodeMetadata> {
+
+   private final Supplier<Map<String, ? extends Image>> images;
+   private final Supplier<Map<String, ? extends Hardware>> hardwares;
+   private final Supplier<Set<? extends Location>> locations;
+   private final Function<Instance.Status, NodeMetadata.Status> toPortableStatus;
+   private final GroupNamingConvention groupNamingConvention;
+
+   @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   InstanceToNodeMetadata(Supplier<Map<String, ? extends Image>> images,
+                          Supplier<Map<String, ? extends Hardware>> hardwares,
+                          @Memoized Supplier<Set<? extends Location>> locations,
+                          Function<Instance.Status, NodeMetadata.Status> toPortableStatus,
+                          GroupNamingConvention.Factory groupNamingConvention) {
+      this.images = images;
+      this.hardwares = hardwares;
+      this.locations = locations;
+      this.toPortableStatus = toPortableStatus;
+      this.groupNamingConvention = groupNamingConvention.createWithoutPrefix();
+   }
+
+   @Override
+   public NodeMetadata apply(Instance from) {
+      NodeMetadataBuilder builder = new NodeMetadataBuilder();
+
+      Optional<? extends Image> image = findImage(from.imageId());
+      if (image.isPresent()) {
+         builder.imageId(image.get().getId());
+         builder.operatingSystem(image.get().getOperatingSystem());
+      } else {
+         logger.info(">> image with id %s for instance %s was not found. "
+                     + "This might be because the image that was used to create the instance has a new id.",
+               from.instanceType(), from.id());
+      }
+      Optional<? extends Hardware> hardware = findHardware(from.instanceType());
+      if (hardware.isPresent()) {
+         builder.hardware(hardware.get());
+      } else {
+         logger.info(">> hardware with id %s for instance %s was not found. "
+                         + "This might be because the image that was used to create the instance has a new id.",
+                 from.instanceType(), from.id());
+      }
+
+      builder.id(RegionAndId.slashEncodeRegionAndId(from.regionId(), from.id()));
+      builder.providerId(from.id());
+      builder.name(from.name());
+      builder.hostname(String.format("%s", from.hostname()));
+      builder.group(groupNamingConvention.extractGroup(from.name()));
+      builder.status(toPortableStatus.apply(from.status()));
+      builder.privateAddresses(from.innerIpAddress().entrySet().iterator().next().getValue());
+      builder.publicAddresses(from.publicIpAddress().entrySet().iterator().next().getValue());
+      builder.location(from(locations.get()).firstMatch(LocationPredicates.idEquals(from.regionId())).orNull());
+      if (from.tags() != null && !from.tags().isEmpty()) {
+         ImmutableMap.Builder tagsBuilder = new ImmutableMap.Builder();
+         for (Tag tag : from.tags().entrySet().iterator().next().getValue()) {
+            tagsBuilder.put(tag.key(), tag.value());
+         }
+         addMetadataAndParseTagsFromValuesOfEmptyString(builder, tagsBuilder.build());
+      }
+
+      NodeMetadata nodeMetadata = builder.build();
+      return nodeMetadata;
+   }
+
+   private Optional<? extends Image> findImage(String imageId) {
+      return Optional.fromNullable(images.get().get(imageId));
+   }
+
+   private Optional<? extends Hardware> findHardware(String instanceType) {
+      return Optional.fromNullable(hardwares.get().get(instanceType));
+   }
+
+}

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

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java
new file mode 100644
index 0000000..2bca519
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions;
+
+import com.google.common.base.Function;
+import org.jclouds.aliyun.ecs.domain.Region;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.location.suppliers.all.JustProvider;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+/**
+ * Transforms an {@link Region} to the jclouds portable model.
+ */
+@Singleton
+public class RegionToLocation implements Function<Region, Location> {
+
+   private final JustProvider justProvider;
+
+   // allow us to lazy discover the provider of a resource
+   @Inject
+   RegionToLocation(JustProvider justProvider) {
+      this.justProvider = justProvider;
+   }
+
+   @Override
+   public Location apply(final Region region) {
+      final LocationBuilder builder = new LocationBuilder();
+      builder.id(region.id());
+      builder.description(region.localName());
+      builder.parent(getOnlyElement(justProvider.get()));
+      builder.scope(LocationScope.REGION);
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java
new file mode 100644
index 0000000..93681b2
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.functions.internal;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import org.jclouds.aliyun.ecs.domain.Image;
+import org.jclouds.compute.domain.OsFamily;
+
+public class OperatingSystems {
+
+   public static Function<Image, String> version() {
+      return new Function<Image, String>() {
+         @Override
+         public String apply(final Image image) {
+            return parseVersion(image);
+         }
+      };
+   }
+
+   private static String parseVersion(Image image) {
+      String sequence = image.osName().trim().replaceAll("\\s+", " ");
+      int offset = 2;
+      if (isWindows(image)) {
+         sequence = image.platform();
+         offset = 1;
+      }
+      Iterable<String> splitted = Splitter.on(" ").split(sequence);
+      return Iterables.get(splitted, Iterables.size(splitted) - offset);
+   }
+
+   public static boolean isWindows(Image image) {
+      return image.platform().toUpperCase().startsWith(OsFamily.WINDOWS.name());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java
new file mode 100644
index 0000000..c378cbd
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.options;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.jclouds.compute.options.TemplateOptions;
+
+import static com.google.common.base.Objects.equal;
+
+/**
+ * Custom options for the Alibaba Elastic Compute Service API.
+ */
+public class ECSServiceTemplateOptions extends TemplateOptions implements Cloneable {
+
+   private String keyPairName = "";
+   private String vSwitchId = "";
+   private String internetChargeType = "PayByTraffic";
+   private String instanceChargeType = "PostPaid";
+   private int internetMaxBandwidthOut = 5;
+
+   public ECSServiceTemplateOptions keyPairName(String keyPairName) {
+      this.keyPairName = keyPairName;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions vSwitchId(String vSwitchId) {
+      this.vSwitchId = vSwitchId;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions internetChargeType(String internetChargeType) {
+      this.internetChargeType = internetChargeType;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions instanceChargeType(String instanceChargeType) {
+      this.instanceChargeType = instanceChargeType;
+      return this;
+   }
+
+   public ECSServiceTemplateOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) {
+      this.internetMaxBandwidthOut = internetMaxBandwidthOut;
+      return this;
+   }
+
+   public String getKeyPairName() {
+      return keyPairName;
+   }
+
+   public String getVSwitchId() {
+      return vSwitchId;
+   }
+
+   public String getInternetChargeType() {
+      return internetChargeType;
+   }
+
+   public String getInstanceChargeType() {
+      return instanceChargeType;
+   }
+
+   public int getInternetMaxBandwidthOut() {
+      return internetMaxBandwidthOut;
+   }
+
+   @Override
+   public ECSServiceTemplateOptions clone() {
+      ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+      copyTo(options);
+      return options;
+   }
+
+   @Override
+   public void copyTo(TemplateOptions to) {
+      super.copyTo(to);
+      if (to instanceof ECSServiceTemplateOptions) {
+         ECSServiceTemplateOptions eTo = ECSServiceTemplateOptions.class.cast(to);
+         eTo.keyPairName(keyPairName);
+         eTo.vSwitchId(vSwitchId);
+         eTo.internetChargeType(internetChargeType);
+         eTo.instanceChargeType(instanceChargeType);
+         eTo.internetMaxBandwidthOut(internetMaxBandwidthOut);
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), keyPairName, vSwitchId, internetChargeType, instanceChargeType, internetMaxBandwidthOut);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (getClass() != obj.getClass()) {
+         return false;
+      }
+      ECSServiceTemplateOptions other = (ECSServiceTemplateOptions) obj;
+      return super.equals(other) &&
+              equal(this.keyPairName, other.keyPairName) &&
+              equal(this.vSwitchId, other.vSwitchId) &&
+              equal(this.internetChargeType, other.internetChargeType) &&
+              equal(this.instanceChargeType, other.instanceChargeType) &&
+              equal(this.internetMaxBandwidthOut, other.internetMaxBandwidthOut);
+   }
+
+   @Override
+   public MoreObjects.ToStringHelper string() {
+      MoreObjects.ToStringHelper toString = super.string().omitNullValues();
+      toString.add("keyPairName", keyPairName);
+      toString.add("vSwitchId", vSwitchId);
+      toString.add("internetChargeType", internetChargeType);
+      toString.add("instanceChargeType", instanceChargeType);
+      toString.add("internetMaxBandwidthOut", internetMaxBandwidthOut);
+      return toString;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see ECSServiceTemplateOptions#keyPairName
+       */
+      public static ECSServiceTemplateOptions keyPairName(String keyPairName) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.keyPairName(keyPairName);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#vSwitchId
+       */
+      public static ECSServiceTemplateOptions vSwitchId(String vSwitchId) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.vSwitchId(vSwitchId);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#internetChargeType
+       */
+      public static ECSServiceTemplateOptions internetChargeType(String internetChargeType) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.internetChargeType(internetChargeType);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#instanceChargeType
+       */
+      public static ECSServiceTemplateOptions instanceChargeType(String instanceChargeType) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.instanceChargeType(instanceChargeType);
+      }
+
+      /**
+       * @see ECSServiceTemplateOptions#internetMaxBandwidthOut
+       */
+      public static ECSServiceTemplateOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) {
+         ECSServiceTemplateOptions options = new ECSServiceTemplateOptions();
+         return options.internetMaxBandwidthOut(internetMaxBandwidthOut);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java
----------------------------------------------------------------------
diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java
new file mode 100644
index 0000000..8fb5730
--- /dev/null
+++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.aliyun.ecs.compute.strategy;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import org.jclouds.aliyun.ecs.ECSComputeServiceApi;
+import org.jclouds.aliyun.ecs.domain.InstanceStatus;
+import org.jclouds.aliyun.ecs.domain.SecurityGroup;
+import org.jclouds.aliyun.ecs.domain.Tag;
+import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId;
+import org.jclouds.aliyun.ecs.predicates.InstanceStatusPredicate;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.List;
+
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+
+/**
+ * This utility takes care of cleaning up all the resources created to deploy the node
+ *
+ * Specifically, it tries to delete the security group created for the group of nodes.
+ * In case a VPC_PREFIX and a vSwitch were created for the node, it tries to remove them
+ */
+@Singleton
+public class CleanupResources {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ECSComputeServiceApi api;
+   private final Predicate<String> instanceSuspendedPredicate;
+   private final Predicate<String> instanceTerminatedPredicate;
+
+   @Inject
+   public CleanupResources(ECSComputeServiceApi api,
+                           @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> instanceSuspendedPredicate,
+                           @Named(TIMEOUT_NODE_TERMINATED) Predicate<String> instanceTerminatedPredicate
+   ) {
+      this.api = api;
+      this.instanceSuspendedPredicate = instanceSuspendedPredicate;
+      this.instanceTerminatedPredicate = instanceTerminatedPredicate;
+   }
+
+   /**
+    * @param regionAndId
+    * @return whether the node and its resources have been deleted
+    */
+   public boolean cleanupNode(final RegionAndId regionAndId) {
+      String instanceId = regionAndId.id();
+      InstanceStatus instanceStatus = Iterables.tryFind(api.instanceApi().listInstanceStatus(regionAndId.regionId()).concat(),
+              new InstanceStatusPredicate(instanceId)).orNull();
+      if (instanceStatus == null) return true;
+      if (InstanceStatus.Status.STOPPED != instanceStatus.status()) {
+         logger.debug(">> powering off %s ...", RegionAndId.slashEncodeRegionAndId(regionAndId));
+         api.instanceApi().powerOff(instanceId);
+         instanceSuspendedPredicate.apply(RegionAndId.slashEncodeRegionAndId(regionAndId));
+      }
+      logger.debug(">> destroying %s ...", RegionAndId.slashEncodeRegionAndId(regionAndId));
+      api.instanceApi().delete(instanceId);
+      return instanceTerminatedPredicate.apply(RegionAndId.slashEncodeRegionAndId(regionAndId));
+   }
+
+   public List<SecurityGroup> findOrphanedSecurityGroups(final String regionId, final String group) {
+      return api.securityGroupApi().list(regionId).concat().filter(new Predicate<SecurityGroup>() {
+         @Override
+         public boolean apply(@Nullable SecurityGroup input) {
+            List<Tag> actual = input.tags().entrySet().iterator().next().getValue();
+            List<Tag> expected = ImmutableList.of(
+                    Tag.create(Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE), Tag.create(Tag.GROUP, group)
+            );
+            return actual.containsAll(expected) && expected.containsAll(actual);
+         }
+      }).toList();
+   }
+
+   public boolean cleanupSecurityGroupIfOrphaned(final String regionId, String securityGroupId) {
+      return api.securityGroupApi().delete(regionId, securityGroupId) != null;
+   }
+
+   public boolean cleanupVSwitchIfOrphaned(final String regionId, String vSwitchId) {
+      return api.vSwitchApi().delete(regionId, vSwitchId) != null;
+   }
+
+   public boolean cleanupVPCIfOrphaned(final String regionId, String vpcId) {
+      return api.vpcApi().delete(regionId, vpcId) != null;
+   }
+
+}