Преглед на файлове

Merge pull request #571 from spacexnice/master

Add ali cloud VPC network support
Tom Denham преди 8 години
родител
ревизия
ebb362ba5e
променени са 60 файла, в които са добавени 6487 реда и са изтрити 0 реда
  1. 67 0
      Documentation/alicloud-vpc-backend.md
  2. BIN
      Documentation/img/ali-create-instance.png
  3. BIN
      Documentation/img/ali-create-key.png
  4. BIN
      Documentation/img/ali-create-switch.png
  5. BIN
      Documentation/img/ali-create-vpc.png
  6. BIN
      Documentation/img/ali-vpc-confirm.png
  7. 86 0
      Documentation/kube-flannel-aliyun.yml
  8. 9 0
      README.md
  9. 198 0
      backend/alivpc/alivpc.go
  10. 1 0
      main.go
  11. 191 0
      vendor/github.com/denverdino/aliyungo/LICENSE.txt
  12. 143 0
      vendor/github.com/denverdino/aliyungo/README.md
  13. 145 0
      vendor/github.com/denverdino/aliyungo/common/client.go
  14. 29 0
      vendor/github.com/denverdino/aliyungo/common/regions.go
  15. 101 0
      vendor/github.com/denverdino/aliyungo/common/request.go
  16. 15 0
      vendor/github.com/denverdino/aliyungo/common/types.go
  17. 3 0
      vendor/github.com/denverdino/aliyungo/common/version.go
  18. 37 0
      vendor/github.com/denverdino/aliyungo/ecs/client.go
  19. 75 0
      vendor/github.com/denverdino/aliyungo/ecs/client_test.go
  20. 34 0
      vendor/github.com/denverdino/aliyungo/ecs/config_test.go
  21. 330 0
      vendor/github.com/denverdino/aliyungo/ecs/disks.go
  22. 151 0
      vendor/github.com/denverdino/aliyungo/ecs/disks_test.go
  23. 267 0
      vendor/github.com/denverdino/aliyungo/ecs/images.go
  24. 114 0
      vendor/github.com/denverdino/aliyungo/ecs/images_test.go
  25. 37 0
      vendor/github.com/denverdino/aliyungo/ecs/instance_types.go
  26. 17 0
      vendor/github.com/denverdino/aliyungo/ecs/instance_types_test.go
  27. 492 0
      vendor/github.com/denverdino/aliyungo/ecs/instances.go
  28. 252 0
      vendor/github.com/denverdino/aliyungo/ecs/instances_test.go
  29. 136 0
      vendor/github.com/denverdino/aliyungo/ecs/monitoring.go
  30. 71 0
      vendor/github.com/denverdino/aliyungo/ecs/monitoring_test.go
  31. 249 0
      vendor/github.com/denverdino/aliyungo/ecs/networks.go
  32. 66 0
      vendor/github.com/denverdino/aliyungo/ecs/networks_test.go
  33. 34 0
      vendor/github.com/denverdino/aliyungo/ecs/regions.go
  34. 162 0
      vendor/github.com/denverdino/aliyungo/ecs/route_tables.go
  35. 51 0
      vendor/github.com/denverdino/aliyungo/ecs/route_tables_test.go
  36. 208 0
      vendor/github.com/denverdino/aliyungo/ecs/security_groups.go
  37. 108 0
      vendor/github.com/denverdino/aliyungo/ecs/security_groups_test.go
  38. 131 0
      vendor/github.com/denverdino/aliyungo/ecs/snapshots.go
  39. 76 0
      vendor/github.com/denverdino/aliyungo/ecs/snapshots_test.go
  40. 120 0
      vendor/github.com/denverdino/aliyungo/ecs/tags.go
  41. 98 0
      vendor/github.com/denverdino/aliyungo/ecs/tags_test.go
  42. 151 0
      vendor/github.com/denverdino/aliyungo/ecs/vpcs.go
  43. 177 0
      vendor/github.com/denverdino/aliyungo/ecs/vpcs_test.go
  44. 68 0
      vendor/github.com/denverdino/aliyungo/ecs/vrouters.go
  45. 40 0
      vendor/github.com/denverdino/aliyungo/ecs/vrouters_test.go
  46. 152 0
      vendor/github.com/denverdino/aliyungo/ecs/vswitches.go
  47. 63 0
      vendor/github.com/denverdino/aliyungo/ecs/vswitches_test.go
  48. 60 0
      vendor/github.com/denverdino/aliyungo/ecs/zones.go
  49. 344 0
      vendor/github.com/denverdino/aliyungo/metadata/client.go
  50. 307 0
      vendor/github.com/denverdino/aliyungo/metadata/client_test.go
  51. 76 0
      vendor/github.com/denverdino/aliyungo/util/attempt.go
  52. 90 0
      vendor/github.com/denverdino/aliyungo/util/attempt_test.go
  53. 152 0
      vendor/github.com/denverdino/aliyungo/util/encoding.go
  54. 81 0
      vendor/github.com/denverdino/aliyungo/util/encoding_test.go
  55. 80 0
      vendor/github.com/denverdino/aliyungo/util/iso6801.go
  56. 91 0
      vendor/github.com/denverdino/aliyungo/util/iso6801_test.go
  57. 40 0
      vendor/github.com/denverdino/aliyungo/util/signature.go
  58. 14 0
      vendor/github.com/denverdino/aliyungo/util/signature_test.go
  59. 147 0
      vendor/github.com/denverdino/aliyungo/util/util.go
  60. 50 0
      vendor/github.com/denverdino/aliyungo/util/util_test.go

+ 67 - 0
Documentation/alicloud-vpc-backend.md

@@ -0,0 +1,67 @@
+# AliCloud VPC Backend for Flannel
+
+When running in an AliCloud VPC, we recommend using the ali-vpc backend to build your network. This prevent from package encapsulation compare to overlay network and achieve maximum performance with IP routes. Also, there is no need for an separate flannel interface to be created.
+In order to run flannel on AliCloud we need first create an [AliCloud VPC Network](https://vpc.console.aliyun.com/#/vpc/cn-hangzhou/list)
+
+### Create VPC network
+Navigate to AliCloud VPC Network list page, then click [create vpc network] button.
+![vpc](img/ali-create-vpc.png)
+
+- Set vpc name with some meaningful string.
+- Choose a subnet for the VPC. There are three subnet for you to select which is 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8. Choose one according to your cluster size.
+- Click create and wait for ready.
+
+### Create switch
+Click manager switch to navigate to switch list page, and create a switch.
+
+- Set switch name to a meaningful string.
+- Choose one AV Zone where you want to run your ECS
+- Set up a subnet which should be contained in your VPC subnet. Here we set subnet as 192.168.0.0/16.
+- Confirm Creating.
+
+### Create instance
+Create an instance whose network type is VPC and then add the instance to your previous VPC network. Note: The ECS you created must sit in the same AV zone with your previous created switch.
+![create instance](img/ali-create-instance.png)
+
+- Select the proper VPC network.
+
+### Get your own ACCESS_KEY_ID and ACCESS_KEY_SECRET. 
+Click [find key](https://ak-console.aliyun.com/#/accesskey)
+
+![create key](img/ali-create-key.png)
+
+- If you havent create any key yet, just click [create key secret] to create a new one.
+- take a note of AccessKeyId and AccessKeySecret for further use.
+
+### Go ahead and launch the instance! 
+
+All that’s left now is to start etcd, publish the network configuration and run the flannel daemon. 
+First, SSH into `instance-1`:
+
+- Start etcd:
+
+```
+$ etcd2 -advertise-client-urls http://$INTERNAL_IP:2379 -listen-client-urls http://0.0.0.0:2379
+```
+- Publish configuration in etcd (ensure that the network range does not overlap with the one configured for the VPC)
+
+```
+$ etcdctl set /coreos.com/network/config '{"Network":"10.24.0.0/16", "Backend": {"Type": "ali-vpc"}}'
+```
+- Fetch the latest release using wget from https://github.com/coreos/flannel/ 
+- make dist/flanneld
+- Run flannel daemon:
+
+```
+sudo ./flanneld --etcd-endpoints=http://127.0.0.1:2379
+```
+
+Next, create and connect to a clone of `instance-1`.
+Run flannel with the `--etcd-endpoints` flag set to the *internal* IP of the instance running etcd.
+
+Confirm that the subnet route table has entries for the lease acquired by each of the subnets.
+
+![router-confirm](img/ali-vpc-confirm.png)
+### Limitations
+
+Keep in mind that the AliCloud VPC [limits](https://vpc.console.aliyun.com/#/vpc/cn-hangzhou/detail/vpc-bp11xpfe5ev6wvhfb14b6/router) the number of entries per route table to 50. If you require more routes, request a quota increase or simply switch to the VXLAN backend.

BIN
Documentation/img/ali-create-instance.png


BIN
Documentation/img/ali-create-key.png


BIN
Documentation/img/ali-create-switch.png


BIN
Documentation/img/ali-create-vpc.png


BIN
Documentation/img/ali-vpc-confirm.png


+ 86 - 0
Documentation/kube-flannel-aliyun.yml

@@ -0,0 +1,86 @@
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: kube-flannel-cfg
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+data:
+  cni-conf.json: |
+    {
+      "name": "cbr0",
+      "type": "flannel",
+      "delegate": {
+        "isDefaultGateway": true
+      }
+    }
+  net-conf.json: |
+    {
+      "Network": "10.24.0.0/16",
+      "Backend": {
+        "Type": "ali-vpc"
+      }
+    }
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-flannel-ds
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: flannel
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      containers:
+      - name: kube-flannel
+        image: registry.cn-hangzhou.aliyuncs.com/google-containers/flannel:0.7.0
+        command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
+        securityContext:
+          privileged: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        - name: ACCESS_KEY_ID
+          value: [replace with your own key]
+        - name: ACCESS_KEY_SECRET
+          value: [replace with your own secret]
+        volumeMounts:
+        - name: run
+          mountPath: /run
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      - name: install-cni
+        image: registry.cn-hangzhou.aliyuncs.com/google-containers/flannel:0.7.0
+        command: [ "/bin/sh", "-c", "set -e -x; cp -f /etc/kube-flannel/cni-conf.json /etc/cni/net.d/10-flannel.conf; while true; do sleep 3600; done" ]
+        volumeMounts:
+        - name: cni
+          mountPath: /etc/cni/net.d
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      volumes:
+        - name: run
+          hostPath:
+            path: /run
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: flannel-cfg
+          configMap:
+            name: kube-flannel-cfg

+ 9 - 0
README.md

@@ -101,6 +101,15 @@ Subnet lease are renewed within 1h of their expiration (can be overridden via `-
 * alloc: only perform subnet allocation (no forwarding of data packets).
   * `Type` (string): `alloc`
 
+* ali-vpc: create IP routes in a [alicloud VPC route table](https://vpc.console.aliyun.com)
+  * Requirements:
+    * Running on an ECS instance that is in an Alicloud VPC.
+    * Permission require accessid and keysecret 
+  * `Type` (string): `ali-vpc`
+  * `AccessKeyID` (string): api access key id. can also be configure with environment ACCESS_KEY_ID
+  * `AccessKeySecret` (string): api access key secret.can also be configure with environment ACCESS_KEY_SECRET
+  Note: Currently, AliVPC limit the number of entries per route table to 50.  
+
 ### Example configuration JSON
 
 The following configuration illustrates the use of most options with `udp` backend.

+ 198 - 0
backend/alivpc/alivpc.go

@@ -0,0 +1,198 @@
+// Copyright 2015 flannel authors
+//
+// Licensed 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 alivpc
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+
+	log "github.com/golang/glog"
+	"golang.org/x/net/context"
+
+	"github.com/coreos/flannel/backend"
+	"github.com/coreos/flannel/pkg/ip"
+	"github.com/coreos/flannel/subnet"
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/ecs"
+	"github.com/denverdino/aliyungo/metadata"
+)
+
+func init() {
+	backend.Register("ali-vpc", New)
+}
+
+type AliVpcBackend struct {
+	sm       subnet.Manager
+	extIface *backend.ExternalInterface
+}
+
+func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backend, error) {
+	be := AliVpcBackend{
+		sm:       sm,
+		extIface: extIface,
+	}
+	return &be, nil
+}
+
+func (be *AliVpcBackend) Run(ctx context.Context) {
+	<-ctx.Done()
+}
+
+func (be *AliVpcBackend) RegisterNetwork(ctx context.Context, network string, config *subnet.Config) (backend.Network, error) {
+	// 1. Parse our configuration
+	cfg := struct {
+		AccessKeyID     string
+		AccessKeySecret string
+	}{}
+
+	if len(config.Backend) > 0 {
+		if err := json.Unmarshal(config.Backend, &cfg); err != nil {
+			return nil, fmt.Errorf("error decoding VPC backend config: %v", err)
+		}
+	}
+	log.Infof("Unmarshal Configure : %v\n", cfg)
+
+	// 2. Acquire the lease form subnet manager
+	attrs := subnet.LeaseAttrs{
+		PublicIP: ip.FromIP(be.extIface.ExtAddr),
+	}
+
+	l, err := be.sm.AcquireLease(ctx, network, &attrs)
+	switch err {
+	case nil:
+
+	case context.Canceled, context.DeadlineExceeded:
+		return nil, err
+
+	default:
+		return nil, fmt.Errorf("failed to acquire lease: %v", err)
+	}
+	if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
+		cfg.AccessKeyID = os.Getenv("ACCESS_KEY_ID")
+		cfg.AccessKeySecret = os.Getenv("ACCESS_KEY_SECRET")
+
+		if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
+			return nil, fmt.Errorf("ACCESS_KEY_ID and ACCESS_KEY_SECRET must be provided! ")
+		}
+	}
+
+	meta := metadata.NewMetaData(nil)
+	REGION, err := meta.Region()
+	if err != nil {
+		return nil, err
+	}
+	instanceid, err := meta.InstanceID()
+	if err != nil {
+		return nil, err
+	}
+	VpcID, err := meta.VpcID()
+	if err != nil {
+		return nil, err
+	}
+
+	c := ecs.NewClient(cfg.AccessKeyID, cfg.AccessKeySecret)
+
+	vpc, _, err := c.DescribeVpcs(&ecs.DescribeVpcsArgs{
+		RegionId: common.Region(REGION),
+		VpcId:    VpcID,
+	})
+	if err != nil || len(vpc) <= 0 {
+		log.Errorf("Error DescribeVpcs: %s . \n", getErrorString(err))
+		return nil, err
+	}
+
+	vroute, _, err := c.DescribeVRouters(&ecs.DescribeVRoutersArgs{
+		VRouterId: vpc[0].VRouterId,
+		RegionId:  common.Region(REGION),
+	})
+	if err != nil || len(vroute) <= 0 {
+		log.Errorf("Error DescribeVRouters: %s .\n", getErrorString(err))
+		return nil, err
+	}
+	vRouterId := vroute[0].VRouterId
+	rTableId := vroute[0].RouteTableIds.RouteTableId[0]
+
+	rtables, _, err := c.DescribeRouteTables(&ecs.DescribeRouteTablesArgs{
+		VRouterId:    vRouterId,
+		RouteTableId: rTableId,
+	})
+	if err != nil || len(rtables) <= 0 {
+		log.Errorf("Error DescribeRouteTables: %s.\n", err.Error())
+		return nil, err
+	}
+
+	route := &ecs.CreateRouteEntryArgs{
+		DestinationCidrBlock: l.Subnet.String(),
+		NextHopType:          ecs.NextHopIntance,
+		NextHopId:            instanceid,
+		ClientToken:          "",
+		RouteTableId:         rTableId,
+	}
+	if err := be.recreateRoute(c, rtables[0], route); err != nil {
+		return nil, err
+	}
+
+	if err := c.WaitForAllRouteEntriesAvailable(vRouterId, rTableId, 60); err != nil {
+		return nil, err
+	}
+	return &backend.SimpleNetwork{
+		SubnetLease: l,
+		ExtIface:    be.extIface,
+	}, nil
+}
+
+func (be *AliVpcBackend) recreateRoute(c *ecs.Client, table ecs.RouteTableSetType, route *ecs.CreateRouteEntryArgs) error {
+	exist := false
+	for _, e := range table.RouteEntrys.RouteEntry {
+		if e.RouteTableId == route.RouteTableId &&
+			e.Type == ecs.RouteTableCustom &&
+			e.InstanceId == route.NextHopId {
+
+			if e.DestinationCidrBlock == route.DestinationCidrBlock &&
+				e.Status == ecs.RouteEntryStatusAvailable {
+				exist = true
+				log.Infof("Keep target entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
+				continue
+			}
+			// Fix: here we delete all the route which targeted to us(instance) except the specified route.
+			// That means only one CIDR was allowed to target to the instance. Think if We need to change this
+			// to adapt to multi CIDR and deal with unavailable route entry.
+			if err := c.DeleteRouteEntry(&ecs.DeleteRouteEntryArgs{
+				RouteTableId:         route.RouteTableId,
+				DestinationCidrBlock: e.DestinationCidrBlock,
+				NextHopId:            route.NextHopId,
+			}); err != nil {
+				return err
+			}
+
+			log.Infof("Remove old route entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
+			continue
+		}
+
+		log.Infof("Keep route entry: rtableid=%s, CIDR=%s, NextHop=%s \n", e.RouteTableId, e.DestinationCidrBlock, e.InstanceId)
+	}
+	if !exist {
+		return c.CreateRouteEntry(route)
+	}
+	return nil
+}
+
+func getErrorString(e error) string {
+	if e == nil {
+		return ""
+	}
+	return e.Error()
+}

+ 1 - 0
main.go

@@ -40,6 +40,7 @@ import (
 	_ "github.com/coreos/flannel/backend/hostgw"
 	_ "github.com/coreos/flannel/backend/udp"
 	_ "github.com/coreos/flannel/backend/vxlan"
+	_ "github.com/coreos/flannel/backend/alivpc"
 )
 
 type CmdLineOpts struct {

+ 191 - 0
vendor/github.com/denverdino/aliyungo/LICENSE.txt

@@ -0,0 +1,191 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        https://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   Copyright 2015-2015 Li Yi (denverdino@gmail.com).
+
+   Licensed 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
+
+       https://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.

+ 143 - 0
vendor/github.com/denverdino/aliyungo/README.md

@@ -0,0 +1,143 @@
+# AliyunGo: Go SDK for Aliyun Services
+
+This is an unofficial Go SDK for Aliyun Services. You are welcome for contribution.
+
+
+## Package Structure
+
+*  ecs: [Elastic Compute Service](https://help.aliyun.com/document_detail/ecs/open-api/summary.html)
+*  oss: [Open Storage Service](https://help.aliyun.com/document_detail/oss/api-reference/abstract.html)
+*  slb: [Server Load Balancer](https://help.aliyun.com/document_detail/slb/api-reference/brief-introduction.html)
+*  dns: [DNS](https://help.aliyun.com/document_detail/dns/api-reference/summary.html)
+*  sls: [Logging Service](https://help.aliyun.com/document_detail/sls/api/overview.html)
+*  ram: [Resource Access Management](https://help.aliyun.com/document_detail/ram/ram-api-reference/intro/intro.html)
+*  rds: [Relational Database Service](https://help.aliyun.com/document_detail/26226.html)
+*  cms: [Cloud Monitor Service](https://help.aliyun.com/document_detail/28615.html)
+*  sts: [Security Token Service](https://help.aliyun.com/document_detail/28756.html)
+*  dm: [Direct Mail]
+(https://help.aliyun.com/document_detail/29414.html)
+*  common: Common libary of Aliyun Go SDK
+*  util: Utility helpers
+
+
+
+## Quick Start
+
+```go
+package main
+
+import (
+	"fmt"
+
+	"github.com/denverdino/aliyungo/ecs"
+)
+
+const ACCESS_KEY_ID = "<YOUR_ID>"
+const ACCESS_KEY_SECRET = "<****>"
+
+func main() {
+	client := ecs.NewClient(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
+	fmt.Print(client.DescribeRegions())
+}
+
+```
+
+## Documentation
+
+  *  ECS: [https://godoc.org/github.com/denverdino/aliyungo/ecs](https://godoc.org/github.com/denverdino/aliyungo/ecs) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/ecs?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/ecs)
+  *  OSS: [https://godoc.org/github.com/denverdino/aliyungo/oss](https://godoc.org/github.com/denverdino/aliyungo/oss) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/oss?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/oss)
+  *  SLB: [https://godoc.org/github.com/denverdino/aliyungo/slb](https://godoc.org/github.com/denverdino/aliyungo/slb) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/slb?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/slb)
+  *  DNS: [https://godoc.org/github.com/denverdino/aliyungo/dns](https://godoc.org/github.com/denverdino/aliyungo/dns) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/dns?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/dns)
+  *  SLS: [https://godoc.org/github.com/denverdino/aliyungo/sls](https://godoc.org/github.com/denverdino/aliyungo/sls) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/sls?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/sls)
+  *  RAM: [https://godoc.org/github.com/denverdino/aliyungo/ram](https://godoc.org/github.com/denverdino/aliyungo/ram) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/ram?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/ram)
+  *  RDS: [https://godoc.org/github.com/denverdino/aliyungo/rds](https://godoc.org/github.com/denverdino/aliyungo/rds) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/rds?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/rds)
+  *  CMS: [https://godoc.org/github.com/denverdino/aliyungo/cms](https://godoc.org/github.com/denverdino/aliyungo/cms) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/cms?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/cms)
+  *  STS: [https://godoc.org/github.com/denverdino/aliyungo/sts](https://godoc.org/github.com/denverdino/aliyungo/sts) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/sts?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/sts)
+  *  DM: [https://godoc.org/github.com/denverdino/aliyungo/dm](https://godoc.org/github.com/denverdino/aliyungo/dm) [![GoDoc](https://godoc.org/github.com/denverdino/aliyungo/dm?status.svg)](https://godoc.org/github.com/denverdino/aliyungo/dm)
+    
+## Build and Install
+
+go get:
+
+```sh
+go get github.com/denverdino/aliyungo
+```
+
+
+## Test ECS
+
+Modify "ecs/config_test.go" 
+
+```sh
+	TestAccessKeyId     = "MY_ACCESS_KEY_ID"
+	TestAccessKeySecret = "MY_ACCESS_KEY_ID"
+	TestInstanceId      = "MY_INSTANCE_ID"
+	TestIAmRich         = false
+```
+
+*  TestAccessKeyId: the Access Key Id
+*  TestAccessKeySecret: the Access Key Secret.
+*  TestInstanceId: the existing instance id for testing. It will be stopped and restarted during testing.
+*  TestIAmRich(Optional): If it is set to true, it will perform tests to create virtual machines and disks under your account. And you will pay the bill. :-)
+
+Under "ecs" and run
+
+```sh
+go test
+```
+
+## Test OSS
+
+Modify "oss/config_test.go" 
+
+```sh
+	TestAccessKeyId     = "MY_ACCESS_KEY_ID"
+	TestAccessKeySecret = "MY_ACCESS_KEY_ID"
+	TestRegion          = oss.Beijing
+	TestBucket          = "denverdino"
+```
+
+*  TestAccessKeyId: the Access Key Id
+*  TestAccessKeySecret: the Access Key Secret.
+*  TestRegion: the region of OSS for testing
+*  TestBucket: the bucket name for testing
+
+
+Under "oss" and run
+
+```sh
+go test
+```
+
+## Contributors
+
+  * Li Yi (denverdino@gmail.com)
+  * tgic (farmer1992@gmail.com)
+  * Yu Zhou (oscarrr110@gmail.com)
+  * Yufei Zhang
+  * linuxlikerqq
+  * Changhai Yan (changhai.ych@alibaba-inc.com)
+  * Jizhong Jiang (jiangjizhong@gmail.com)
+  * Kent Wang (pragkent@gmail.com)
+  * ringtail (zhongwei.lzw@alibaba-inc.com)
+  * aiden0z (aiden0xz@gmail.com)
+  * jimmycmh
+  * menglingwei
+  * mingang.he (dustgle@gmail.com)
+  * chainone (chainone@gmail.com)
+  * johnzeng
+
+## License
+This project is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/denverdino/aliyungo/blob/master/LICENSE.txt) for the full license text.
+
+
+## Related projects
+
+  * Aliyun ECS driver for Docker Machine: [Pull request](https://github.com/docker/machine/pull/1182)
+
+  * Aliyun OSS driver for Docker Registry V2: [Pull request](https://github.com/docker/distribution/pull/514)
+
+
+## References
+
+The GO API design of OSS refer the implementation from [https://github.com/AdRoll/goamz](https://github.com/AdRoll)

+ 145 - 0
vendor/github.com/denverdino/aliyungo/common/client.go

@@ -0,0 +1,145 @@
+package common
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/denverdino/aliyungo/util"
+)
+
+// A Client represents a client of ECS services
+type Client struct {
+	AccessKeyId     string //Access Key Id
+	AccessKeySecret string //Access Key Secret
+	debug           bool
+	httpClient      *http.Client
+	endpoint        string
+	version         string
+}
+
+// NewClient creates a new instance of ECS client
+func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) {
+	client.AccessKeyId = accessKeyId
+	client.AccessKeySecret = accessKeySecret + "&"
+	client.debug = false
+	client.httpClient = &http.Client{}
+	client.endpoint = endpoint
+	client.version = version
+}
+
+// SetEndpoint sets custom endpoint
+func (client *Client) SetEndpoint(endpoint string) {
+	client.endpoint = endpoint
+}
+
+// SetEndpoint sets custom version
+func (client *Client) SetVersion(version string) {
+	client.version = version
+}
+
+// SetAccessKeyId sets new AccessKeyId
+func (client *Client) SetAccessKeyId(id string) {
+	client.AccessKeyId = id
+}
+
+// SetAccessKeySecret sets new AccessKeySecret
+func (client *Client) SetAccessKeySecret(secret string) {
+	client.AccessKeySecret = secret + "&"
+}
+
+// SetDebug sets debug mode to log the request/response message
+func (client *Client) SetDebug(debug bool) {
+	client.debug = debug
+}
+
+// Invoke sends the raw HTTP request for ECS services
+func (client *Client) Invoke(action string, args interface{}, response interface{}) error {
+
+	request := Request{}
+	request.init(client.version, action, client.AccessKeyId)
+
+	query := util.ConvertToQueryValues(request)
+	util.SetQueryValues(args, &query)
+
+	// Sign request
+	signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)
+
+	// Generate the request URL
+	requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)
+
+	httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)
+
+	// TODO move to util and add build val flag
+	httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version)
+
+	if err != nil {
+		return GetClientError(err)
+	}
+
+	t0 := time.Now()
+	httpResp, err := client.httpClient.Do(httpReq)
+	t1 := time.Now()
+	if err != nil {
+		return GetClientError(err)
+	}
+	statusCode := httpResp.StatusCode
+
+	if client.debug {
+		log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
+	}
+
+	defer httpResp.Body.Close()
+	body, err := ioutil.ReadAll(httpResp.Body)
+
+	if err != nil {
+		return GetClientError(err)
+	}
+
+	if client.debug {
+		var prettyJSON bytes.Buffer
+		err = json.Indent(&prettyJSON, body, "", "    ")
+		log.Println(string(prettyJSON.Bytes()))
+	}
+
+	if statusCode >= 400 && statusCode <= 599 {
+		errorResponse := ErrorResponse{}
+		err = json.Unmarshal(body, &errorResponse)
+		ecsError := &Error{
+			ErrorResponse: errorResponse,
+			StatusCode:    statusCode,
+		}
+		return ecsError
+	}
+
+	err = json.Unmarshal(body, response)
+	//log.Printf("%++v", response)
+	if err != nil {
+		return GetClientError(err)
+	}
+
+	return nil
+}
+
+// GenerateClientToken generates the Client Token with random string
+func (client *Client) GenerateClientToken() string {
+	return util.CreateRandomString()
+}
+
+func GetClientErrorFromString(str string) error {
+	return &Error{
+		ErrorResponse: ErrorResponse{
+			Code:    "AliyunGoClientFailure",
+			Message: str,
+		},
+		StatusCode: -1,
+	}
+}
+
+func GetClientError(err error) error {
+	return GetClientErrorFromString(err.Error())
+}

+ 29 - 0
vendor/github.com/denverdino/aliyungo/common/regions.go

@@ -0,0 +1,29 @@
+package common
+
+// Region represents ECS region
+type Region string
+
+// Constants of region definition
+const (
+	Hangzhou     = Region("cn-hangzhou")
+	Qingdao      = Region("cn-qingdao")
+	Beijing      = Region("cn-beijing")
+	Hongkong     = Region("cn-hongkong")
+	Shenzhen     = Region("cn-shenzhen")
+	USWest1      = Region("us-west-1")
+	USEast1      = Region("us-east-1")
+	APSouthEast1 = Region("ap-southeast-1")
+	Shanghai     = Region("cn-shanghai")
+	MEEast1      = Region("me-east-1")
+	APNorthEast1 = Region("ap-northeast-1")
+	APSouthEast2 = Region("ap-southeast-2")
+	EUCentral1   = Region("eu-central-1")
+)
+
+var ValidRegions = []Region{
+	Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai,
+	USWest1, USEast1,
+	APNorthEast1, APSouthEast1, APSouthEast2,
+	MEEast1,
+	EUCentral1,
+}

+ 101 - 0
vendor/github.com/denverdino/aliyungo/common/request.go

@@ -0,0 +1,101 @@
+package common
+
+import (
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/denverdino/aliyungo/util"
+)
+
+// Constants for Aliyun API requests
+const (
+	SignatureVersion   = "1.0"
+	SignatureMethod    = "HMAC-SHA1"
+	JSONResponseFormat = "JSON"
+	XMLResponseFormat  = "XML"
+	ECSRequestMethod   = "GET"
+)
+
+type Request struct {
+	Format               string
+	Version              string
+	AccessKeyId          string
+	Signature            string
+	SignatureMethod      string
+	Timestamp            util.ISO6801Time
+	SignatureVersion     string
+	SignatureNonce       string
+	ResourceOwnerAccount string
+	Action               string
+}
+
+func (request *Request) init(version string, action string, AccessKeyId string) {
+	request.Format = JSONResponseFormat
+	request.Timestamp = util.NewISO6801Time(time.Now().UTC())
+	request.Version = version
+	request.SignatureVersion = SignatureVersion
+	request.SignatureMethod = SignatureMethod
+	request.SignatureNonce = util.CreateRandomString()
+	request.Action = action
+	request.AccessKeyId = AccessKeyId
+}
+
+type Response struct {
+	RequestId string
+}
+
+type ErrorResponse struct {
+	Response
+	HostId  string
+	Code    string
+	Message string
+}
+
+// An Error represents a custom error for Aliyun API failure response
+type Error struct {
+	ErrorResponse
+	StatusCode int //Status Code of HTTP Response
+}
+
+func (e *Error) Error() string {
+	return fmt.Sprintf("Aliyun API Error: RequestId: %s Status Code: %d Code: %s Message: %s", e.RequestId, e.StatusCode, e.Code, e.Message)
+}
+
+type Pagination struct {
+	PageNumber int
+	PageSize   int
+}
+
+func (p *Pagination) SetPageSize(size int) {
+	p.PageSize = size
+}
+
+func (p *Pagination) Validate() {
+	if p.PageNumber < 0 {
+		log.Printf("Invalid PageNumber: %d", p.PageNumber)
+		p.PageNumber = 1
+	}
+	if p.PageSize < 0 {
+		log.Printf("Invalid PageSize: %d", p.PageSize)
+		p.PageSize = 10
+	} else if p.PageSize > 50 {
+		log.Printf("Invalid PageSize: %d", p.PageSize)
+		p.PageSize = 50
+	}
+}
+
+// A PaginationResponse represents a response with pagination information
+type PaginationResult struct {
+	TotalCount int
+	PageNumber int
+	PageSize   int
+}
+
+// NextPage gets the next page of the result set
+func (r *PaginationResult) NextPage() *Pagination {
+	if r.PageNumber*r.PageSize >= r.TotalCount {
+		return nil
+	}
+	return &Pagination{PageNumber: r.PageNumber + 1, PageSize: r.PageSize}
+}

+ 15 - 0
vendor/github.com/denverdino/aliyungo/common/types.go

@@ -0,0 +1,15 @@
+package common
+
+type InternetChargeType string
+
+const (
+	PayByBandwidth = InternetChargeType("PayByBandwidth")
+	PayByTraffic   = InternetChargeType("PayByTraffic")
+)
+
+type InstanceChargeType string
+
+const (
+	PrePaid  = InstanceChargeType("PrePaid")
+	PostPaid = InstanceChargeType("PostPaid")
+)

+ 3 - 0
vendor/github.com/denverdino/aliyungo/common/version.go

@@ -0,0 +1,3 @@
+package common
+
+const Version = "0.1"

+ 37 - 0
vendor/github.com/denverdino/aliyungo/ecs/client.go

@@ -0,0 +1,37 @@
+package ecs
+
+import (
+	"github.com/denverdino/aliyungo/common"
+	"os"
+)
+
+// Interval for checking status in WaitForXXX method
+const DefaultWaitForInterval = 5
+
+// Default timeout value for WaitForXXX method
+const DefaultTimeout = 60
+
+type Client struct {
+	common.Client
+}
+
+const (
+	// ECSDefaultEndpoint is the default API endpoint of ECS services
+	ECSDefaultEndpoint = "https://ecs-cn-hangzhou.aliyuncs.com"
+	ECSAPIVersion      = "2014-05-26"
+)
+
+// NewClient creates a new instance of ECS client
+func NewClient(accessKeyId, accessKeySecret string) *Client {
+	endpoint := os.Getenv("ECS_ENDPOINT")
+	if endpoint == "" {
+		endpoint = ECSDefaultEndpoint
+	}
+	return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret)
+}
+
+func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client {
+	client := &Client{}
+	client.Init(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret)
+	return client
+}

+ 75 - 0
vendor/github.com/denverdino/aliyungo/ecs/client_test.go

@@ -0,0 +1,75 @@
+package ecs
+
+import (
+	"testing"
+)
+
+func TestGenerateClientToken(t *testing.T) {
+	client := NewTestClient()
+	for i := 0; i < 10; i++ {
+		t.Log("GenerateClientToken: ", client.GenerateClientToken())
+	}
+
+}
+
+func TestECSDescribe(t *testing.T) {
+	if TestQuick {
+		return
+	}
+	client := NewTestClient()
+
+	regions, err := client.DescribeRegions()
+
+	t.Log("regions: ", regions, err)
+
+	for _, region := range regions {
+		zones, err := client.DescribeZones(region.RegionId)
+		t.Log("zones: ", zones, err)
+		for _, zone := range zones {
+			args := DescribeInstanceStatusArgs{
+				RegionId: region.RegionId,
+				ZoneId:   zone.ZoneId,
+			}
+			instanceStatuses, pagination, err := client.DescribeInstanceStatus(&args)
+			t.Logf("instanceStatuses: %v, %++v, %v", instanceStatuses, pagination, err)
+			for _, instanceStatus := range instanceStatuses {
+				instance, err := client.DescribeInstanceAttribute(instanceStatus.InstanceId)
+				t.Logf("Instance: %++v", instance)
+				t.Logf("Error: %++v", err)
+			}
+			args1 := DescribeInstancesArgs{
+				RegionId: region.RegionId,
+				ZoneId:   zone.ZoneId,
+			}
+			instances, _, err := client.DescribeInstances(&args1)
+			if err != nil {
+				t.Errorf("Failed to describe instance by region %s zone %s: %v", region.RegionId, zone.ZoneId, err)
+			} else {
+				for _, instance := range instances {
+					t.Logf("Instance: %++v", instance)
+				}
+			}
+
+		}
+		args := DescribeImagesArgs{RegionId: region.RegionId}
+
+		for {
+
+			images, pagination, err := client.DescribeImages(&args)
+			if err != nil {
+				t.Fatalf("Failed to describe images: %v", err)
+				break
+			} else {
+				t.Logf("Image count for region %s total %d from %d", region.RegionId, pagination.TotalCount, pagination.PageNumber*pagination.PageSize)
+				for _, image := range images {
+					t.Logf("Image: %++v", image)
+				}
+				nextPage := pagination.NextPage()
+				if nextPage == nil {
+					break
+				}
+				args.Pagination = *nextPage
+			}
+		}
+	}
+}

+ 34 - 0
vendor/github.com/denverdino/aliyungo/ecs/config_test.go

@@ -0,0 +1,34 @@
+package ecs
+
+//Modify with your Access Key Id and Access Key Secret
+
+const (
+	TestAccessKeyId     = "MY_ACCESS_KEY_ID"
+	TestAccessKeySecret = "MY_ACCESS_KEY_SECRET"
+	TestInstanceId      = "MY_TEST_INSTANCE_ID"
+	TestSecurityGroupId = "MY_TEST_SECURITY_GROUP_ID"
+	TestImageId         = "MY_TEST_IMAGE_ID"
+	TestAccountId       = "MY_TEST_ACCOUNT_ID" //Get from https://account.console.aliyun.com
+
+	TestIAmRich = false
+	TestQuick   = false
+)
+
+var testClient *Client
+
+func NewTestClient() *Client {
+	if testClient == nil {
+		testClient = NewClient(TestAccessKeyId, TestAccessKeySecret)
+	}
+	return testClient
+}
+
+var testDebugClient *Client
+
+func NewTestClientForDebug() *Client {
+	if testDebugClient == nil {
+		testDebugClient = NewClient(TestAccessKeyId, TestAccessKeySecret)
+		testDebugClient.SetDebug(true)
+	}
+	return testDebugClient
+}

+ 330 - 0
vendor/github.com/denverdino/aliyungo/ecs/disks.go

@@ -0,0 +1,330 @@
+package ecs
+
+import (
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+// Types of disks
+type DiskType string
+
+const (
+	DiskTypeAll       = DiskType("all") //Default
+	DiskTypeAllSystem = DiskType("system")
+	DiskTypeAllData   = DiskType("data")
+)
+
+// Categories of disks
+type DiskCategory string
+
+const (
+	DiskCategoryAll             = DiskCategory("all") //Default
+	DiskCategoryCloud           = DiskCategory("cloud")
+	DiskCategoryEphemeral       = DiskCategory("ephemeral")
+	DiskCategoryEphemeralSSD    = DiskCategory("ephemeral_ssd")
+	DiskCategoryCloudEfficiency = DiskCategory("cloud_efficiency")
+	DiskCategoryCloudSSD        = DiskCategory("cloud_ssd")
+)
+
+// Status of disks
+type DiskStatus string
+
+const (
+	DiskStatusInUse     = DiskStatus("In_use")
+	DiskStatusAvailable = DiskStatus("Available")
+	DiskStatusAttaching = DiskStatus("Attaching")
+	DiskStatusDetaching = DiskStatus("Detaching")
+	DiskStatusCreating  = DiskStatus("Creating")
+	DiskStatusReIniting = DiskStatus("ReIniting")
+	DiskStatusAll       = DiskStatus("All") //Default
+)
+
+// Charge type of disks
+type DiskChargeType string
+
+const (
+	PrePaid  = DiskChargeType("PrePaid")
+	PostPaid = DiskChargeType("PostPaid")
+)
+
+// A DescribeDisksArgs defines the arguments to describe disks
+type DescribeDisksArgs struct {
+	RegionId           common.Region
+	ZoneId             string
+	DiskIds            []string
+	InstanceId         string
+	DiskType           DiskType     //enum for all(default) | system | data
+	Category           DiskCategory //enum for all(default) | cloud | ephemeral
+	Status             DiskStatus   //enum for In_use | Available | Attaching | Detaching | Creating | ReIniting | All(default)
+	SnapshotId         string
+	Name               string
+	Portable           *bool //optional
+	DeleteWithInstance *bool //optional
+	DeleteAutoSnapshot *bool //optional
+	EnableAutoSnapshot *bool //optional
+	DiskChargeType     DiskChargeType
+	Tag                map[string]string
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&diskitemtype
+type DiskItemType struct {
+	DiskId             string
+	RegionId           common.Region
+	ZoneId             string
+	DiskName           string
+	Description        string
+	Type               DiskType
+	Category           DiskCategory
+	Size               int
+	ImageId            string
+	SourceSnapshotId   string
+	ProductCode        string
+	Portable           bool
+	Status             DiskStatus
+	OperationLocks     OperationLocksType
+	InstanceId         string
+	Device             string
+	DeleteWithInstance bool
+	DeleteAutoSnapshot bool
+	EnableAutoSnapshot bool
+	CreationTime       util.ISO6801Time
+	AttachedTime       util.ISO6801Time
+	DetachedTime       util.ISO6801Time
+	DiskChargeType     DiskChargeType
+}
+
+type DescribeDisksResponse struct {
+	common.Response
+	common.PaginationResult
+	RegionId common.Region
+	Disks    struct {
+		Disk []DiskItemType
+	}
+}
+
+// DescribeDisks describes Disks
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&describedisks
+func (client *Client) DescribeDisks(args *DescribeDisksArgs) (disks []DiskItemType, pagination *common.PaginationResult, err error) {
+	response := DescribeDisksResponse{}
+
+	err = client.Invoke("DescribeDisks", args, &response)
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return response.Disks.Disk, &response.PaginationResult, err
+}
+
+type CreateDiskArgs struct {
+	RegionId     common.Region
+	ZoneId       string
+	DiskName     string
+	Description  string
+	DiskCategory DiskCategory
+	Size         int
+	SnapshotId   string
+	ClientToken  string
+}
+
+type CreateDisksResponse struct {
+	common.Response
+	DiskId string
+}
+
+// CreateDisk creates a new disk
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&createdisk
+func (client *Client) CreateDisk(args *CreateDiskArgs) (diskId string, err error) {
+	response := CreateDisksResponse{}
+	err = client.Invoke("CreateDisk", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.DiskId, err
+}
+
+type DeleteDiskArgs struct {
+	DiskId string
+}
+
+type DeleteDiskResponse struct {
+	common.Response
+}
+
+// DeleteDisk deletes disk
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&deletedisk
+func (client *Client) DeleteDisk(diskId string) error {
+	args := DeleteDiskArgs{
+		DiskId: diskId,
+	}
+	response := DeleteDiskResponse{}
+	err := client.Invoke("DeleteDisk", &args, &response)
+	return err
+}
+
+type ReInitDiskArgs struct {
+	DiskId string
+}
+
+type ReInitDiskResponse struct {
+	common.Response
+}
+
+// ReInitDisk reinitizes disk
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&reinitdisk
+func (client *Client) ReInitDisk(diskId string) error {
+	args := ReInitDiskArgs{
+		DiskId: diskId,
+	}
+	response := ReInitDiskResponse{}
+	err := client.Invoke("ReInitDisk", &args, &response)
+	return err
+}
+
+type AttachDiskArgs struct {
+	InstanceId         string
+	DiskId             string
+	Device             string
+	DeleteWithInstance bool
+}
+
+type AttachDiskResponse struct {
+	common.Response
+}
+
+// AttachDisk attaches disk to instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&attachdisk
+func (client *Client) AttachDisk(args *AttachDiskArgs) error {
+	response := AttachDiskResponse{}
+	err := client.Invoke("AttachDisk", args, &response)
+	return err
+}
+
+type DetachDiskArgs struct {
+	InstanceId string
+	DiskId     string
+}
+
+type DetachDiskResponse struct {
+	common.Response
+}
+
+// DetachDisk detaches disk from instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&detachdisk
+func (client *Client) DetachDisk(instanceId string, diskId string) error {
+	args := DetachDiskArgs{
+		InstanceId: instanceId,
+		DiskId:     diskId,
+	}
+	response := DetachDiskResponse{}
+	err := client.Invoke("DetachDisk", &args, &response)
+	return err
+}
+
+type ResetDiskArgs struct {
+	DiskId     string
+	SnapshotId string
+}
+
+type ResetDiskResponse struct {
+	common.Response
+}
+
+// ResetDisk resets disk to original status
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&resetdisk
+func (client *Client) ResetDisk(diskId string, snapshotId string) error {
+	args := ResetDiskArgs{
+		SnapshotId: snapshotId,
+		DiskId:     diskId,
+	}
+	response := ResetDiskResponse{}
+	err := client.Invoke("ResetDisk", &args, &response)
+	return err
+}
+
+type ModifyDiskAttributeArgs struct {
+	DiskId             string
+	DiskName           string
+	Description        string
+	DeleteWithInstance *bool
+	DeleteAutoSnapshot *bool
+	EnableAutoSnapshot *bool
+}
+
+type ModifyDiskAttributeResponse struct {
+	common.Response
+}
+
+// ModifyDiskAttribute modifies disk attribute
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&modifydiskattribute
+func (client *Client) ModifyDiskAttribute(args *ModifyDiskAttributeArgs) error {
+	response := ModifyDiskAttributeResponse{}
+	err := client.Invoke("ModifyDiskAttribute", &args, &response)
+	return err
+}
+
+type ReplaceSystemDiskArgs struct {
+	InstanceId  string
+	ImageId     string
+	SystemDisk  SystemDiskType
+	ClientToken string
+}
+
+type ReplaceSystemDiskResponse struct {
+	common.Response
+	DiskId string
+}
+
+// ReplaceSystemDisk replace system disk
+//
+// You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/disk/replacesystemdisk.html
+func (client *Client) ReplaceSystemDisk(args *ReplaceSystemDiskArgs) (diskId string, err error) {
+	response := ReplaceSystemDiskResponse{}
+	err = client.Invoke("ReplaceSystemDisk", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.DiskId, nil
+}
+
+// WaitForDisk waits for disk to given status
+func (client *Client) WaitForDisk(regionId common.Region, diskId string, status DiskStatus, timeout int) error {
+	if timeout <= 0 {
+		timeout = DefaultTimeout
+	}
+	args := DescribeDisksArgs{
+		RegionId: regionId,
+		DiskIds:  []string{diskId},
+	}
+
+	for {
+		disks, _, err := client.DescribeDisks(&args)
+		if err != nil {
+			return err
+		}
+		if disks == nil || len(disks) == 0 {
+			return common.GetClientErrorFromString("Not found")
+		}
+		if disks[0].Status == status {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}

+ 151 - 0
vendor/github.com/denverdino/aliyungo/ecs/disks_test.go

@@ -0,0 +1,151 @@
+package ecs
+
+import (
+	"testing"
+)
+
+func TestDisks(t *testing.T) {
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := DescribeDisksArgs{}
+
+	args.InstanceId = TestInstanceId
+	args.RegionId = instance.RegionId
+	disks, _, err := client.DescribeDisks(&args)
+
+	if err != nil {
+		t.Fatalf("Failed to DescribeDisks for instance %s: %v", TestInstanceId, err)
+	}
+
+	for _, disk := range disks {
+		t.Logf("Disk of instance %s: %++v", TestInstanceId, disk)
+	}
+}
+
+func TestDiskCreationAndDeletion(t *testing.T) {
+
+	if TestIAmRich == false { //Avoid payment
+		return
+	}
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := CreateDiskArgs{
+		RegionId: instance.RegionId,
+		ZoneId:   instance.ZoneId,
+		DiskName: "test-disk",
+		Size:     5,
+	}
+
+	diskId, err := client.CreateDisk(&args)
+	if err != nil {
+		t.Fatalf("Failed to create disk: %v", err)
+	}
+	t.Logf("Create disk %s successfully", diskId)
+
+	attachArgs := AttachDiskArgs{
+		InstanceId: instance.InstanceId,
+		DiskId:     diskId,
+	}
+
+	err = client.AttachDisk(&attachArgs)
+	if err != nil {
+		t.Errorf("Failed to create disk: %v", err)
+	} else {
+		t.Logf("Attach disk %s to instance %s successfully", diskId, instance.InstanceId)
+
+		instance, err = client.DescribeInstanceAttribute(TestInstanceId)
+		if err != nil {
+			t.Errorf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+		} else {
+			t.Logf("Instance: %++v  %v", instance, err)
+		}
+		err = client.WaitForDisk(instance.RegionId, diskId, DiskStatusInUse, 0)
+		if err != nil {
+			t.Fatalf("Failed to wait for disk %s to status %s: %v", diskId, DiskStatusInUse, err)
+		}
+		err = client.DetachDisk(instance.InstanceId, diskId)
+		if err != nil {
+			t.Errorf("Failed to detach disk: %v", err)
+		} else {
+			t.Logf("Detach disk %s to instance %s successfully", diskId, instance.InstanceId)
+		}
+
+		err = client.WaitForDisk(instance.RegionId, diskId, DiskStatusAvailable, 0)
+		if err != nil {
+			t.Fatalf("Failed to wait for disk %s to status %s: %v", diskId, DiskStatusAvailable, err)
+		}
+	}
+	err = client.DeleteDisk(diskId)
+	if err != nil {
+		t.Fatalf("Failed to delete disk %s: %v", diskId, err)
+	}
+	t.Logf("Delete disk %s successfully", diskId)
+}
+
+func TestReplaceSystemDiskUsingSizeParam(t *testing.T) {
+	client := NewTestClientForDebug()
+
+	args := ReplaceSystemDiskArgs{
+		InstanceId: TestInstanceId,
+		ImageId:    TestImageId,
+		SystemDisk: SystemDiskType{
+			Size: 192,
+		},
+		ClientToken: client.GenerateClientToken(),
+	}
+
+	diskId, err := client.ReplaceSystemDisk(&args)
+	if err != nil {
+		t.Errorf("Failed to replace system disk %v", err)
+	} else {
+		t.Logf("diskId is %s", diskId)
+	}
+}
+
+func TestReplaceSystemDisk(t *testing.T) {
+	client := NewTestClient()
+
+	err := client.WaitForInstance(TestInstanceId, Running, 0)
+	err = client.StopInstance(TestInstanceId, true)
+	if err != nil {
+		t.Errorf("Failed to stop instance %s: %v", TestInstanceId, err)
+	}
+	err = client.WaitForInstance(TestInstanceId, Stopped, 0)
+	if err != nil {
+		t.Errorf("Instance %s is failed to stop: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance %s is stopped successfully.", TestInstanceId)
+
+	args := ReplaceSystemDiskArgs{
+		InstanceId: TestInstanceId,
+		ImageId:    TestImageId,
+	}
+
+	diskId, err := client.ReplaceSystemDisk(&args)
+	if err != nil {
+		t.Errorf("Failed to replace system disk %v", err)
+	}
+	err = client.WaitForInstance(TestInstanceId, Stopped, 60)
+	err = client.StartInstance(TestInstanceId)
+	if err != nil {
+		t.Errorf("Failed to start instance %s: %v", TestInstanceId, err)
+	} else {
+		err = client.WaitForInstance(TestInstanceId, Running, 0)
+		if err != nil {
+			t.Errorf("Failed to wait for instance %s running: %v", TestInstanceId, err)
+		}
+	}
+	t.Logf("Replace system disk %s successfully ", diskId)
+}

+ 267 - 0
vendor/github.com/denverdino/aliyungo/ecs/images.go

@@ -0,0 +1,267 @@
+package ecs
+
+import (
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+// ImageOwnerAlias represents image owner
+type ImageOwnerAlias string
+
+// Constants of image owner
+const (
+	ImageOwnerSystem      = ImageOwnerAlias("system")
+	ImageOwnerSelf        = ImageOwnerAlias("self")
+	ImageOwnerOthers      = ImageOwnerAlias("others")
+	ImageOwnerMarketplace = ImageOwnerAlias("marketplace")
+	ImageOwnerDefault     = ImageOwnerAlias("") //Return the values for system, self, and others
+)
+
+type ImageStatus string
+
+const (
+	ImageStatusAvailable    = ImageStatus("Available")
+	ImageStatusUnAvailable  = ImageStatus("UnAvailable")
+	ImageStatusCreating     = ImageStatus("Creating")
+	ImageStatusCreateFailed = ImageStatus("CreateFailed")
+)
+
+// DescribeImagesArgs repsents arguements to describe images
+type DescribeImagesArgs struct {
+	RegionId        common.Region
+	ImageId         string
+	SnapshotId      string
+	ImageName       string
+	Status          ImageStatus
+	ImageOwnerAlias ImageOwnerAlias
+	common.Pagination
+}
+
+type DescribeImagesResponse struct {
+	common.Response
+	common.PaginationResult
+
+	RegionId common.Region
+	Images   struct {
+		Image []ImageType
+	}
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&diskdevicemapping
+type DiskDeviceMapping struct {
+	SnapshotId string
+	//Why Size Field is string-type.
+	Size   string
+	Device string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&imagetype
+type ImageType struct {
+	ImageId            string
+	ImageVersion       string
+	Architecture       string
+	ImageName          string
+	Description        string
+	Size               int
+	ImageOwnerAlias    string
+	OSName             string
+	DiskDeviceMappings struct {
+		DiskDeviceMapping []DiskDeviceMapping
+	}
+	ProductCode  string
+	IsSubscribed bool
+	Progress     string
+	Status       ImageStatus
+	CreationTime util.ISO6801Time
+}
+
+// DescribeImages describes images
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/image&describeimages
+func (client *Client) DescribeImages(args *DescribeImagesArgs) (images []ImageType, pagination *common.PaginationResult, err error) {
+
+	args.Validate()
+	response := DescribeImagesResponse{}
+	err = client.Invoke("DescribeImages", args, &response)
+	if err != nil {
+		return nil, nil, err
+	}
+	return response.Images.Image, &response.PaginationResult, nil
+}
+
+// CreateImageArgs repsents arguements to create image
+type CreateImageArgs struct {
+	RegionId     common.Region
+	SnapshotId   string
+	ImageName    string
+	ImageVersion string
+	Description  string
+	ClientToken  string
+}
+
+type CreateImageResponse struct {
+	common.Response
+
+	ImageId string
+}
+
+// CreateImage creates a new image
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/image&createimage
+func (client *Client) CreateImage(args *CreateImageArgs) (imageId string, err error) {
+	response := &CreateImageResponse{}
+	err = client.Invoke("CreateImage", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.ImageId, nil
+}
+
+type DeleteImageArgs struct {
+	RegionId common.Region
+	ImageId  string
+}
+
+type DeleteImageResponse struct {
+	common.Response
+}
+
+// DeleteImage deletes Image
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/image&deleteimage
+func (client *Client) DeleteImage(regionId common.Region, imageId string) error {
+	args := DeleteImageArgs{
+		RegionId: regionId,
+		ImageId:  imageId,
+	}
+
+	response := &DeleteImageResponse{}
+	return client.Invoke("DeleteImage", &args, &response)
+}
+
+// ModifyImageSharePermission repsents arguements to share image
+type ModifyImageSharePermissionArgs struct {
+	RegionId      common.Region
+	ImageId       string
+	AddAccount    []string
+	RemoveAccount []string
+}
+
+// You can read doc at http://help.aliyun.com/document_detail/ecs/open-api/image/modifyimagesharepermission.html
+func (client *Client) ModifyImageSharePermission(args *ModifyImageSharePermissionArgs) error {
+	req := url.Values{}
+	req.Add("RegionId", string(args.RegionId))
+	req.Add("ImageId", args.ImageId)
+
+	for i, item := range args.AddAccount {
+		req.Add("AddAccount."+strconv.Itoa(i+1), item)
+	}
+	for i, item := range args.RemoveAccount {
+		req.Add("RemoveAccount."+strconv.Itoa(i+1), item)
+	}
+
+	return client.Invoke("ModifyImageSharePermission", req, &common.Response{})
+}
+
+type AccountType struct {
+	AliyunId string
+}
+type ImageSharePermissionResponse struct {
+	common.Response
+	ImageId  string
+	RegionId string
+	Accounts struct {
+		Account []AccountType
+	}
+	TotalCount int
+	PageNumber int
+	PageSize   int
+}
+
+func (client *Client) DescribeImageSharePermission(args *ModifyImageSharePermissionArgs) (*ImageSharePermissionResponse, error) {
+	response := ImageSharePermissionResponse{}
+	err := client.Invoke("DescribeImageSharePermission", args, &response)
+	return &response, err
+}
+
+type CopyImageArgs struct {
+	RegionId               common.Region
+	ImageId                string
+	DestinationRegionId    common.Region
+	DestinationImageName   string
+	DestinationDescription string
+	ClientToken            string
+}
+
+type CopyImageResponse struct {
+	common.Response
+	ImageId string
+}
+
+// You can read doc at https://help.aliyun.com/document_detail/25538.html
+func (client *Client) CopyImage(args *CopyImageArgs) (string, error) {
+	response := &CopyImageResponse{}
+	err := client.Invoke("CopyImage", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.ImageId, nil
+}
+
+// Default timeout value for WaitForImageReady method
+const ImageDefaultTimeout = 120
+
+//Wait Image ready
+func (client *Client) WaitForImageReady(regionId common.Region, imageId string, timeout int) error {
+	if timeout <= 0 {
+		timeout = ImageDefaultTimeout
+	}
+	for {
+		args := DescribeImagesArgs{
+			RegionId: regionId,
+			ImageId:  imageId,
+			Status:   ImageStatusCreating,
+		}
+
+		images, _, err := client.DescribeImages(&args)
+		if err != nil {
+			return err
+		}
+		if images == nil || len(images) == 0 {
+			args.Status = ImageStatusAvailable
+			images, _, er := client.DescribeImages(&args)
+			if er == nil && len(images) == 1 {
+				break
+			} else {
+				return common.GetClientErrorFromString("Not found")
+			}
+		}
+		if images[0].Progress == "100%" {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}
+
+type CancelCopyImageRequest struct {
+	regionId common.Region
+	ImageId  string
+}
+
+// You can read doc at https://help.aliyun.com/document_detail/25539.html
+func (client *Client) CancelCopyImage(regionId common.Region, imageId string) error {
+	response := &common.Response{}
+	err := client.Invoke("CancelCopyImage", &CancelCopyImageRequest{regionId, imageId}, &response)
+	return err
+}

+ 114 - 0
vendor/github.com/denverdino/aliyungo/ecs/images_test.go

@@ -0,0 +1,114 @@
+package ecs
+
+import (
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func TestImageCreationAndDeletion(t *testing.T) {
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := DescribeSnapshotsArgs{}
+
+	args.InstanceId = TestInstanceId
+	args.RegionId = instance.RegionId
+	snapshots, _, err := client.DescribeSnapshots(&args)
+
+	if err != nil {
+		t.Errorf("Failed to DescribeSnapshots for instance %s: %v", TestInstanceId, err)
+	}
+
+	if len(snapshots) > 0 {
+
+		createImageArgs := CreateImageArgs{
+			RegionId:   instance.RegionId,
+			SnapshotId: snapshots[0].SnapshotId,
+
+			ImageName:    "My_Test_Image_for_AliyunGo",
+			ImageVersion: "1.0",
+			Description:  "My Test Image for AliyunGo description",
+			ClientToken:  client.GenerateClientToken(),
+		}
+		imageId, err := client.CreateImage(&createImageArgs)
+		if err != nil {
+			t.Errorf("Failed to CreateImage for SnapshotId %s: %v", createImageArgs.SnapshotId, err)
+		}
+		t.Logf("Image %s is created successfully.", imageId)
+
+		err = client.DeleteImage(instance.RegionId, imageId)
+		if err != nil {
+			t.Errorf("Failed to DeleteImage for %s: %v", imageId, err)
+		}
+		t.Logf("Image %s is deleted successfully.", imageId)
+
+	}
+}
+
+func TestModifyImageSharePermission(t *testing.T) {
+	req := ModifyImageSharePermissionArgs{
+		RegionId:   common.Beijing,
+		ImageId:    TestImageId,
+		AddAccount: []string{TestAccountId},
+	}
+	client := NewTestClient()
+	err := client.ModifyImageSharePermission(&req)
+	if err != nil {
+		t.Errorf("Failed to ShareImage: %v", err)
+	}
+
+	shareInfo, err := client.DescribeImageSharePermission(&req)
+	if err != nil {
+		t.Errorf("Failed to ShareImage: %v", err)
+	}
+	t.Logf("result:image: %++v", shareInfo)
+}
+
+func TestCopyImage(t *testing.T) {
+	client := NewTestClient()
+	req := CopyImageArgs{
+		RegionId:               common.Beijing,
+		ImageId:                TestImageId,
+		DestinationRegionId:    common.Hangzhou,
+		DestinationImageName:   "My_Test_Image_NAME_for_AliyunGo",
+		DestinationDescription: "My Test Image for AliyunGo description",
+		ClientToken:            client.GenerateClientToken(),
+	}
+
+	imageId, err := client.CopyImage(&req)
+	if err != nil {
+		t.Errorf("Failed to CopyImage: %v", err)
+	}
+	t.Logf("result:image: %++v", imageId)
+
+	if err := client.WaitForImageReady(common.Hangzhou, imageId, 600); err != nil {
+		t.Errorf("Failed to WaitImage: %v", err)
+		//return
+	}
+
+	describeReq := DescribeImagesArgs{
+		RegionId:        common.Hangzhou,
+		ImageId:         imageId,
+		Status:          ImageStatusAvailable,
+		ImageOwnerAlias: ImageOwnerSelf,
+	}
+
+	images, _, err := client.DescribeImages(&describeReq)
+	if err != nil {
+		t.Errorf("Failed to describeImage: %v", err)
+	}
+	t.Logf("result: images %++v", images)
+}
+
+func TestCancelCopyImage(t *testing.T) {
+	client := NewTestClient()
+	if err := client.CancelCopyImage(common.Hangzhou, TestImageId); err != nil {
+		t.Errorf("Failed to CancelCopyImage: %v", err)
+	}
+}

+ 37 - 0
vendor/github.com/denverdino/aliyungo/ecs/instance_types.go

@@ -0,0 +1,37 @@
+package ecs
+
+import "github.com/denverdino/aliyungo/common"
+
+type DescribeInstanceTypesArgs struct {
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancetypeitemtype
+type InstanceTypeItemType struct {
+	InstanceTypeId     string
+	CpuCoreCount       int
+	MemorySize         float64
+	InstanceTypeFamily string
+}
+
+type DescribeInstanceTypesResponse struct {
+	common.Response
+	InstanceTypes struct {
+		InstanceType []InstanceTypeItemType
+	}
+}
+
+// DescribeInstanceTypes describes all instance types
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/other&describeinstancetypes
+func (client *Client) DescribeInstanceTypes() (instanceTypes []InstanceTypeItemType, err error) {
+	response := DescribeInstanceTypesResponse{}
+
+	err = client.Invoke("DescribeInstanceTypes", &DescribeInstanceTypesArgs{}, &response)
+
+	if err != nil {
+		return []InstanceTypeItemType{}, err
+	}
+	return response.InstanceTypes.InstanceType, nil
+
+}

+ 17 - 0
vendor/github.com/denverdino/aliyungo/ecs/instance_types_test.go

@@ -0,0 +1,17 @@
+package ecs
+
+import (
+	"testing"
+)
+
+func TestDescribeInstanceTypes(t *testing.T) {
+
+	client := NewTestClient()
+	instanceTypes, err := client.DescribeInstanceTypes()
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceTypes: %v", err)
+	}
+	for _, instanceType := range instanceTypes {
+		t.Logf("InstanceType: %++v", instanceType)
+	}
+}

+ 492 - 0
vendor/github.com/denverdino/aliyungo/ecs/instances.go

@@ -0,0 +1,492 @@
+package ecs
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+// InstanceStatus represents instance status
+type InstanceStatus string
+
+// Constants of InstanceStatus
+const (
+	Creating = InstanceStatus("Creating")
+	Running  = InstanceStatus("Running")
+	Starting = InstanceStatus("Starting")
+
+	Stopped  = InstanceStatus("Stopped")
+	Stopping = InstanceStatus("Stopping")
+)
+
+type LockReason string
+
+const (
+	LockReasonFinancial = LockReason("financial")
+	LockReasonSecurity  = LockReason("security")
+)
+
+type LockReasonType struct {
+	LockReason LockReason
+}
+
+type DescribeInstanceStatusArgs struct {
+	RegionId common.Region
+	ZoneId   string
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancestatusitemtype
+type InstanceStatusItemType struct {
+	InstanceId string
+	Status     InstanceStatus
+}
+
+type DescribeInstanceStatusResponse struct {
+	common.Response
+	common.PaginationResult
+	InstanceStatuses struct {
+		InstanceStatus []InstanceStatusItemType
+	}
+}
+
+// DescribeInstanceStatus describes instance status
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstancestatus
+func (client *Client) DescribeInstanceStatus(args *DescribeInstanceStatusArgs) (instanceStatuses []InstanceStatusItemType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeInstanceStatusResponse{}
+
+	err = client.Invoke("DescribeInstanceStatus", args, &response)
+
+	if err == nil {
+		return response.InstanceStatuses.InstanceStatus, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type StopInstanceArgs struct {
+	InstanceId string
+	ForceStop  bool
+}
+
+type StopInstanceResponse struct {
+	common.Response
+}
+
+// StopInstance stops instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&stopinstance
+func (client *Client) StopInstance(instanceId string, forceStop bool) error {
+	args := StopInstanceArgs{
+		InstanceId: instanceId,
+		ForceStop:  forceStop,
+	}
+	response := StopInstanceResponse{}
+	err := client.Invoke("StopInstance", &args, &response)
+	return err
+}
+
+type StartInstanceArgs struct {
+	InstanceId string
+}
+
+type StartInstanceResponse struct {
+	common.Response
+}
+
+// StartInstance starts instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&startinstance
+func (client *Client) StartInstance(instanceId string) error {
+	args := StartInstanceArgs{InstanceId: instanceId}
+	response := StartInstanceResponse{}
+	err := client.Invoke("StartInstance", &args, &response)
+	return err
+}
+
+type RebootInstanceArgs struct {
+	InstanceId string
+	ForceStop  bool
+}
+
+type RebootInstanceResponse struct {
+	common.Response
+}
+
+// RebootInstance reboot instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&rebootinstance
+func (client *Client) RebootInstance(instanceId string, forceStop bool) error {
+	request := RebootInstanceArgs{
+		InstanceId: instanceId,
+		ForceStop:  forceStop,
+	}
+	response := RebootInstanceResponse{}
+	err := client.Invoke("RebootInstance", &request, &response)
+	return err
+}
+
+type DescribeInstanceAttributeArgs struct {
+	InstanceId string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&operationlockstype
+type OperationLocksType struct {
+	LockReason []LockReasonType //enum for financial, security
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&securitygroupidsettype
+type SecurityGroupIdSetType struct {
+	SecurityGroupId string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&ipaddresssettype
+type IpAddressSetType struct {
+	IpAddress []string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vpcattributestype
+type VpcAttributesType struct {
+	VpcId            string
+	VSwitchId        string
+	PrivateIpAddress IpAddressSetType
+	NatIpAddress     string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&eipaddressassociatetype
+type EipAddressAssociateType struct {
+	AllocationId       string
+	IpAddress          string
+	Bandwidth          int
+	InternetChargeType common.InternetChargeType
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instanceattributestype
+type InstanceAttributesType struct {
+	InstanceId         string
+	InstanceName       string
+	Description        string
+	ImageId            string
+	RegionId           common.Region
+	ZoneId             string
+	CPU                int
+	Memory             int
+	ClusterId          string
+	InstanceType       string
+	InstanceTypeFamily string
+	HostName           string
+	SerialNumber       string
+	Status             InstanceStatus
+	OperationLocks     OperationLocksType
+	SecurityGroupIds   struct {
+		SecurityGroupId []string
+	}
+	PublicIpAddress         IpAddressSetType
+	InnerIpAddress          IpAddressSetType
+	InstanceNetworkType     string //enum Classic | Vpc
+	InternetMaxBandwidthIn  int
+	InternetMaxBandwidthOut int
+	InternetChargeType      common.InternetChargeType
+	CreationTime            util.ISO6801Time //time.Time
+	VpcAttributes           VpcAttributesType
+	EipAddress              EipAddressAssociateType
+	IoOptimized             StringOrBool
+	InstanceChargeType      common.InstanceChargeType
+	ExpiredTime             util.ISO6801Time
+	Tags                    struct {
+		Tag []TagItemType
+	}
+}
+
+type DescribeInstanceAttributeResponse struct {
+	common.Response
+	InstanceAttributesType
+}
+
+// DescribeInstanceAttribute describes instance attribute
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstanceattribute
+func (client *Client) DescribeInstanceAttribute(instanceId string) (instance *InstanceAttributesType, err error) {
+	args := DescribeInstanceAttributeArgs{InstanceId: instanceId}
+
+	response := DescribeInstanceAttributeResponse{}
+	err = client.Invoke("DescribeInstanceAttribute", &args, &response)
+	if err != nil {
+		return nil, err
+	}
+	return &response.InstanceAttributesType, err
+}
+
+type ModifyInstanceAttributeArgs struct {
+	InstanceId   string
+	InstanceName string
+	Description  string
+	Password     string
+	HostName     string
+}
+
+type ModifyInstanceAttributeResponse struct {
+	common.Response
+}
+
+//ModifyInstanceAttribute  modify instance attrbute
+//
+// You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/instance/modifyinstanceattribute.html
+func (client *Client) ModifyInstanceAttribute(args *ModifyInstanceAttributeArgs) error {
+	response := ModifyInstanceAttributeResponse{}
+	err := client.Invoke("ModifyInstanceAttribute", args, &response)
+	return err
+}
+
+// Default timeout value for WaitForInstance method
+const InstanceDefaultTimeout = 120
+
+// WaitForInstance waits for instance to given status
+func (client *Client) WaitForInstance(instanceId string, status InstanceStatus, timeout int) error {
+	if timeout <= 0 {
+		timeout = InstanceDefaultTimeout
+	}
+	for {
+		instance, err := client.DescribeInstanceAttribute(instanceId)
+		if err != nil {
+			return err
+		}
+		if instance.Status == status {
+			//TODO
+			//Sleep one more time for timing issues
+			time.Sleep(DefaultWaitForInterval * time.Second)
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+
+	}
+	return nil
+}
+
+type DescribeInstanceVncUrlArgs struct {
+	RegionId   common.Region
+	InstanceId string
+}
+
+type DescribeInstanceVncUrlResponse struct {
+	common.Response
+	VncUrl string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstancevncurl
+func (client *Client) DescribeInstanceVncUrl(args *DescribeInstanceVncUrlArgs) (string, error) {
+	response := DescribeInstanceVncUrlResponse{}
+
+	err := client.Invoke("DescribeInstanceVncUrl", args, &response)
+
+	if err == nil {
+		return response.VncUrl, nil
+	}
+
+	return "", err
+}
+
+type DescribeInstancesArgs struct {
+	RegionId            common.Region
+	VpcId               string
+	VSwitchId           string
+	ZoneId              string
+	InstanceIds         string
+	InstanceNetworkType string
+	InstanceName        string
+	Status              InstanceStatus
+	PrivateIpAddresses  string
+	InnerIpAddresses    string
+	PublicIpAddresses   string
+	SecurityGroupId     string
+	Tag                 map[string]string
+	common.Pagination
+}
+
+type DescribeInstancesResponse struct {
+	common.Response
+	common.PaginationResult
+	Instances struct {
+		Instance []InstanceAttributesType
+	}
+}
+
+// DescribeInstances describes instances
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstances
+func (client *Client) DescribeInstances(args *DescribeInstancesArgs) (instances []InstanceAttributesType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeInstancesResponse{}
+
+	err = client.Invoke("DescribeInstances", args, &response)
+
+	if err == nil {
+		return response.Instances.Instance, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type DeleteInstanceArgs struct {
+	InstanceId string
+}
+
+type DeleteInstanceResponse struct {
+	common.Response
+}
+
+// DeleteInstance deletes instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&deleteinstance
+func (client *Client) DeleteInstance(instanceId string) error {
+	args := DeleteInstanceArgs{InstanceId: instanceId}
+	response := DeleteInstanceResponse{}
+	err := client.Invoke("DeleteInstance", &args, &response)
+	return err
+}
+
+type DataDiskType struct {
+	Size               int
+	Category           DiskCategory //Enum cloud, ephemeral, ephemeral_ssd
+	SnapshotId         string
+	DiskName           string
+	Description        string
+	Device             string
+	DeleteWithInstance bool
+}
+
+type SystemDiskType struct {
+	Size        int
+	Category    DiskCategory //Enum cloud, ephemeral, ephemeral_ssd
+	DiskName    string
+	Description string
+}
+
+type IoOptimized string
+
+type StringOrBool struct {
+	Value bool
+}
+
+// UnmarshalJSON implements the json.Unmarshaller interface.
+func (io *StringOrBool) UnmarshalJSON(value []byte) error {
+	if value[0] == '"' {
+		var str string
+		err := json.Unmarshal(value, &str)
+		if err == nil {
+			io.Value = (str == "true" || str == "optimized")
+		}
+		return err
+	}
+	var boolVal bool
+	err := json.Unmarshal(value, &boolVal)
+	if err == nil {
+		io.Value = boolVal
+	}
+	return err
+}
+
+func (io StringOrBool) Bool() bool {
+	return io.Value
+}
+
+func (io StringOrBool) String() string {
+	return strconv.FormatBool(io.Value)
+}
+
+var (
+	IoOptimizedNone      = IoOptimized("none")
+	IoOptimizedOptimized = IoOptimized("optimized")
+)
+
+type CreateInstanceArgs struct {
+	RegionId                common.Region
+	ZoneId                  string
+	ImageId                 string
+	InstanceType            string
+	SecurityGroupId         string
+	InstanceName            string
+	Description             string
+	InternetChargeType      common.InternetChargeType
+	InternetMaxBandwidthIn  int
+	InternetMaxBandwidthOut int
+	HostName                string
+	Password                string
+	IoOptimized             IoOptimized
+	SystemDisk              SystemDiskType
+	DataDisk                []DataDiskType
+	VSwitchId               string
+	PrivateIpAddress        string
+	ClientToken             string
+	InstanceChargeType      common.InstanceChargeType
+	Period                  int
+	UserData                string
+}
+
+type CreateInstanceResponse struct {
+	common.Response
+	InstanceId string
+}
+
+// CreateInstance creates instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&createinstance
+func (client *Client) CreateInstance(args *CreateInstanceArgs) (instanceId string, err error) {
+	if args.UserData != "" {
+		// Encode to base64 string
+		args.UserData = base64.StdEncoding.EncodeToString([]byte(args.UserData))
+	}
+	response := CreateInstanceResponse{}
+	err = client.Invoke("CreateInstance", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.InstanceId, err
+}
+
+type SecurityGroupArgs struct {
+	InstanceId      string
+	SecurityGroupId string
+}
+
+type SecurityGroupResponse struct {
+	common.Response
+}
+
+//JoinSecurityGroup
+//
+//You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/instance/joinsecuritygroup.html
+func (client *Client) JoinSecurityGroup(instanceId string, securityGroupId string) error {
+	args := SecurityGroupArgs{InstanceId: instanceId, SecurityGroupId: securityGroupId}
+	response := SecurityGroupResponse{}
+	err := client.Invoke("JoinSecurityGroup", &args, &response)
+	return err
+}
+
+//LeaveSecurityGroup
+//
+//You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/instance/leavesecuritygroup.html
+func (client *Client) LeaveSecurityGroup(instanceId string, securityGroupId string) error {
+	args := SecurityGroupArgs{InstanceId: instanceId, SecurityGroupId: securityGroupId}
+	response := SecurityGroupResponse{}
+	err := client.Invoke("LeaveSecurityGroup", &args, &response)
+	return err
+}

+ 252 - 0
vendor/github.com/denverdino/aliyungo/ecs/instances_test.go

@@ -0,0 +1,252 @@
+package ecs
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func ExampleClient_DescribeInstanceStatus() {
+	fmt.Printf("DescribeInstanceStatus Example\n")
+
+	args := DescribeInstanceStatusArgs{
+		RegionId: "cn-beijing",
+		ZoneId:   "cn-beijing-b",
+		Pagination: common.Pagination{
+			PageNumber: 1,
+			PageSize:   1,
+		},
+	}
+
+	client := NewTestClient()
+	instanceStatus, _, err := client.DescribeInstanceStatus(&args)
+
+	if err != nil {
+		fmt.Printf("Failed to describe Instance: %s status:%v \n", TestInstanceId, err)
+	} else {
+		for i := 0; i < len(instanceStatus); i++ {
+			fmt.Printf("Instance %s Status: %s \n", instanceStatus[i].InstanceId, instanceStatus[i].Status)
+		}
+	}
+}
+
+func ExampleClient_DescribeInstanceAttribute() {
+	fmt.Printf("DescribeInstanceAttribute Example\n")
+
+	client := NewTestClient()
+
+	instanceAttributeType, err := client.DescribeInstanceAttribute(TestInstanceId)
+
+	if err != nil {
+		fmt.Printf("Failed to describe Instance %s attribute: %v\n", TestInstanceId, err)
+	} else {
+		fmt.Printf("Instance Information\n")
+		fmt.Printf("InstanceId = %s \n", instanceAttributeType.InstanceId)
+		fmt.Printf("InstanceName = %s \n", instanceAttributeType.InstanceName)
+		fmt.Printf("HostName = %s \n", instanceAttributeType.HostName)
+		fmt.Printf("ZoneId = %s \n", instanceAttributeType.ZoneId)
+		fmt.Printf("RegionId = %s \n", instanceAttributeType.RegionId)
+	}
+}
+
+func ExampleClient_DescribeInstanceVncUrl() {
+	fmt.Printf("DescribeInstanceVncUrl Example\n")
+
+	args := DescribeInstanceVncUrlArgs{
+		RegionId:   "cn-beijing",
+		InstanceId: TestInstanceId,
+	}
+
+	client := NewTestClient()
+
+	instanceVncUrl, err := client.DescribeInstanceVncUrl(&args)
+
+	if err != nil {
+		fmt.Printf("Failed to describe Instance %s vnc url: %v \n", TestInstanceId, err)
+	} else {
+		fmt.Printf("VNC URL = %s \n", instanceVncUrl)
+	}
+}
+
+func ExampleClient_StopInstance() {
+	fmt.Printf("Stop Instance Example\n")
+
+	client := NewTestClient()
+
+	err := client.StopInstance(TestInstanceId, true)
+
+	if err != nil {
+		fmt.Printf("Failed to stop Instance %s vnc url: %v \n", TestInstanceId, err)
+	}
+}
+
+func ExampleClient_DeleteInstance() {
+	fmt.Printf("Delete Instance Example")
+
+	client := NewTestClient()
+
+	err := client.DeleteInstance(TestInstanceId)
+
+	if err != nil {
+		fmt.Printf("Failed to delete Instance %s vnc url: %v \n", TestInstanceId, err)
+	}
+}
+
+func TestECSInstance(t *testing.T) {
+	if TestQuick {
+		return
+	}
+	client := NewTestClient()
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to describe instance %s: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance: %++v  %v", instance, err)
+	err = client.StopInstance(TestInstanceId, true)
+	if err != nil {
+		t.Errorf("Failed to stop instance %s: %v", TestInstanceId, err)
+	}
+	err = client.WaitForInstance(TestInstanceId, Stopped, 0)
+	if err != nil {
+		t.Errorf("Instance %s is failed to stop: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance %s is stopped successfully.", TestInstanceId)
+	err = client.StartInstance(TestInstanceId)
+	if err != nil {
+		t.Errorf("Failed to start instance %s: %v", TestInstanceId, err)
+	}
+	err = client.WaitForInstance(TestInstanceId, Running, 0)
+	if err != nil {
+		t.Errorf("Instance %s is failed to start: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance %s is running successfully.", TestInstanceId)
+	err = client.RebootInstance(TestInstanceId, true)
+	if err != nil {
+		t.Errorf("Failed to restart instance %s: %v", TestInstanceId, err)
+	}
+	err = client.WaitForInstance(TestInstanceId, Running, 0)
+	if err != nil {
+		t.Errorf("Instance %s is failed to restart: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance %s is running successfully.", TestInstanceId)
+}
+
+func TestECSInstanceCreationAndDeletion(t *testing.T) {
+
+	if TestIAmRich == false { // Avoid payment
+		return
+	}
+
+	client := NewTestClient()
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	t.Logf("Instance: %++v  %v", instance, err)
+
+	args := CreateInstanceArgs{
+		RegionId:        instance.RegionId,
+		ImageId:         instance.ImageId,
+		InstanceType:    "ecs.t1.small",
+		SecurityGroupId: instance.SecurityGroupIds.SecurityGroupId[0],
+	}
+
+	instanceId, err := client.CreateInstance(&args)
+	if err != nil {
+		t.Errorf("Failed to create instance from Image %s: %v", args.ImageId, err)
+	}
+	t.Logf("Instance %s is created successfully.", instanceId)
+
+	instance, err = client.DescribeInstanceAttribute(instanceId)
+	t.Logf("Instance: %++v  %v", instance, err)
+
+	err = client.WaitForInstance(instanceId, Stopped, 60)
+
+	err = client.StartInstance(instanceId)
+	if err != nil {
+		t.Errorf("Failed to start instance %s: %v", instanceId, err)
+	}
+	err = client.WaitForInstance(instanceId, Running, 0)
+
+	err = client.StopInstance(instanceId, true)
+	if err != nil {
+		t.Errorf("Failed to stop instance %s: %v", instanceId, err)
+	}
+	err = client.WaitForInstance(instanceId, Stopped, 0)
+	if err != nil {
+		t.Errorf("Instance %s is failed to stop: %v", instanceId, err)
+	}
+	t.Logf("Instance %s is stopped successfully.", instanceId)
+
+	err = client.DeleteInstance(instanceId)
+
+	if err != nil {
+		t.Errorf("Failed to delete instance %s: %v", instanceId, err)
+	}
+	t.Logf("Instance %s is deleted successfully.", instanceId)
+}
+
+func TestModifyInstanceAttribute(t *testing.T) {
+	client := NewTestClient()
+
+	args := ModifyInstanceAttributeArgs{
+		InstanceId: TestInstanceId,
+		Password:   "Just$test",
+	}
+
+	err := client.ModifyInstanceAttribute(&args)
+	if err != nil {
+		t.Errorf("Failed to modify instance attribute %s: %v", TestInstanceId, err)
+	}
+
+	t.Logf("Modify instance attribute successfully")
+}
+
+func TestIoOptimized(t *testing.T) {
+
+	type TestStruct struct {
+		Str  string
+		Flag StringOrBool
+	}
+
+	var test TestStruct
+
+	txt := "{\"Str\":\"abc\", \"Flag\": true}"
+
+	err := json.Unmarshal([]byte(txt), &test)
+	if err != nil {
+		t.Errorf("Failed to Unmarshal IoOptimized: %v", err)
+	} else {
+		if test.Flag.Value != true {
+			t.Errorf("Failed to Unmarshal IoOptimized with expected value: %s", test.Flag)
+		}
+	}
+
+	txt1 := "{\"Str\":\"abc\", \"Flag\": \"false\"}"
+
+	err = json.Unmarshal([]byte(txt1), &test)
+	if err != nil {
+		t.Errorf("Failed to Unmarshal IoOptimized: %v", err)
+	} else {
+		if test.Flag.Value != false {
+			t.Errorf("Failed to Unmarshal IoOptimized with expected value: %s", test.Flag)
+		}
+	}
+}
+
+func TestJoinSecurityGroup(t *testing.T) {
+	client := NewTestClient()
+
+	err := client.JoinSecurityGroup(TestInstanceId, TestSecurityGroupId)
+	if err != nil {
+		t.Errorf("Failed to joinSecurityGroup: %v", err)
+	}
+}
+
+func TestLeaveSecurityGroup(t *testing.T) {
+	client := NewTestClient()
+
+	err := client.LeaveSecurityGroup(TestInstanceId, TestSecurityGroupId)
+	if err != nil {
+		t.Errorf("Failed to LeaveSecurityGroup: %v", err)
+	}
+}

+ 136 - 0
vendor/github.com/denverdino/aliyungo/ecs/monitoring.go

@@ -0,0 +1,136 @@
+package ecs
+
+import (
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type DescribeInstanceMonitorDataArgs struct {
+	InstanceId string
+	StartTime  util.ISO6801Time
+	EndTime    util.ISO6801Time
+	Period     int //Default 60s
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancemonitordatatype
+type InstanceMonitorDataType struct {
+	InstanceId        string
+	CPU               int
+	IntranetRX        int
+	IntranetTX        int
+	IntranetBandwidth int
+	InternetRX        int
+	InternetTX        int
+	InternetBandwidth int
+	IOPSRead          int
+	IOPSWrite         int
+	BPSRead           int
+	BPSWrite          int
+	TimeStamp         util.ISO6801Time
+}
+
+type DescribeInstanceMonitorDataResponse struct {
+	common.Response
+	MonitorData struct {
+		InstanceMonitorData []InstanceMonitorDataType
+	}
+}
+
+// DescribeInstanceMonitorData describes instance monitoring data
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/monitor&describeinstancemonitordata
+func (client *Client) DescribeInstanceMonitorData(args *DescribeInstanceMonitorDataArgs) (monitorData []InstanceMonitorDataType, err error) {
+	if args.Period == 0 {
+		args.Period = 60
+	}
+	response := DescribeInstanceMonitorDataResponse{}
+	err = client.Invoke("DescribeInstanceMonitorData", args, &response)
+	if err != nil {
+		return nil, err
+	}
+	return response.MonitorData.InstanceMonitorData, err
+}
+
+type DescribeEipMonitorDataArgs struct {
+	AllocationId string
+	StartTime    util.ISO6801Time
+	EndTime      util.ISO6801Time
+	Period       int //Default 60s
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&eipmonitordatatype
+type EipMonitorDataType struct {
+	EipRX        int
+	EipTX        int
+	EipFlow      int
+	EipBandwidth int
+	EipPackets   int
+	TimeStamp    util.ISO6801Time
+}
+
+type DescribeEipMonitorDataResponse struct {
+	common.Response
+	EipMonitorDatas struct {
+		EipMonitorData []EipMonitorDataType
+	}
+}
+
+// DescribeEipMonitorData describes EIP monitoring data
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/monitor&describeeipmonitordata
+func (client *Client) DescribeEipMonitorData(args *DescribeEipMonitorDataArgs) (monitorData []EipMonitorDataType, err error) {
+	if args.Period == 0 {
+		args.Period = 60
+	}
+	response := DescribeEipMonitorDataResponse{}
+	err = client.Invoke("DescribeEipMonitorData", args, &response)
+	if err != nil {
+		return nil, err
+	}
+	return response.EipMonitorDatas.EipMonitorData, err
+}
+
+type DescribeDiskMonitorDataArgs struct {
+	DiskId    string
+	StartTime util.ISO6801Time
+	EndTime   util.ISO6801Time
+	Period    int //Default 60s
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&diskmonitordatatype
+type DiskMonitorDataType struct {
+	DiskId    string
+	IOPSRead  int
+	IOPSWrite int
+	IOPSTotal int
+	BPSRead   int
+	BPSWrite  int
+	BPSTotal  int
+	TimeStamp util.ISO6801Time
+}
+
+type DescribeDiskMonitorDataResponse struct {
+	common.Response
+	TotalCount  int
+	MonitorData struct {
+		DiskMonitorData []DiskMonitorDataType
+	}
+}
+
+// DescribeDiskMonitorData describes disk monitoring data
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/monitor&describediskmonitordata
+func (client *Client) DescribeDiskMonitorData(args *DescribeDiskMonitorDataArgs) (monitorData []DiskMonitorDataType, totalCount int, err error) {
+	if args.Period == 0 {
+		args.Period = 60
+	}
+	response := DescribeDiskMonitorDataResponse{}
+	err = client.Invoke("DescribeDiskMonitorData", args, &response)
+	if err != nil {
+		return nil, 0, err
+	}
+	return response.MonitorData.DiskMonitorData, response.TotalCount, err
+}

+ 71 - 0
vendor/github.com/denverdino/aliyungo/ecs/monitoring_test.go

@@ -0,0 +1,71 @@
+package ecs
+
+import (
+	"testing"
+	"time"
+
+	"github.com/denverdino/aliyungo/util"
+)
+
+func TestMonitoring(t *testing.T) {
+	client := NewTestClient()
+	//client.SetDebug(true)
+
+	//Describe test instance
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to describe instance %s: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance: %++v  %v", instance, err)
+
+	//Describe Instance Monitor Data
+	now := time.Now().UTC()
+
+	starting := time.Date(now.Year(), now.Month(), now.Day(), now.Hour()-2, now.Minute(), now.Second(), now.Nanosecond(), now.Location())
+	ending := time.Date(now.Year(), now.Month(), now.Day(), now.Hour()-1, now.Minute(), now.Second(), now.Nanosecond(), now.Location())
+
+	args := DescribeInstanceMonitorDataArgs{
+		InstanceId: TestInstanceId,
+		StartTime:  util.ISO6801Time(starting),
+		EndTime:    util.ISO6801Time(ending),
+	}
+
+	monitorData, err := client.DescribeInstanceMonitorData(&args)
+
+	if err != nil {
+		t.Fatalf("Failed to describe monitoring data for instance %s: %v", TestInstanceId, err)
+	}
+
+	for _, data := range monitorData {
+		t.Logf("Monitor Data: %++v", data)
+	}
+	//Describe EIP monitor data
+
+	//Describe disk monitor data
+	diskArgs := DescribeDisksArgs{
+		InstanceId: TestInstanceId,
+		RegionId:   instance.RegionId,
+	}
+
+	disks, _, err := client.DescribeDisks(&diskArgs)
+	if err != nil {
+		t.Fatalf("Failed to DescribeDisks for instance %s: %v", TestInstanceId, err)
+	}
+
+	for _, disk := range disks {
+		args := DescribeDiskMonitorDataArgs{
+			DiskId:    disk.DiskId,
+			StartTime: util.ISO6801Time(starting),
+			EndTime:   util.ISO6801Time(ending),
+		}
+		monitorData, _, err := client.DescribeDiskMonitorData(&args)
+
+		if err != nil {
+			t.Fatalf("Failed to describe monitoring data for disk %s: %v", disk.DiskId, err)
+		}
+
+		for _, data := range monitorData {
+			t.Logf("Monitor Data: %++v", data)
+		}
+	}
+}

+ 249 - 0
vendor/github.com/denverdino/aliyungo/ecs/networks.go

@@ -0,0 +1,249 @@
+// API on Network
+
+package ecs
+
+import (
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type AllocatePublicIpAddressArgs struct {
+	InstanceId string
+}
+
+type AllocatePublicIpAddressResponse struct {
+	common.Response
+
+	IpAddress string
+}
+
+// AllocatePublicIpAddress allocates Public Ip Address
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&allocatepublicipaddress
+func (client *Client) AllocatePublicIpAddress(instanceId string) (ipAddress string, err error) {
+	args := AllocatePublicIpAddressArgs{
+		InstanceId: instanceId,
+	}
+	response := AllocatePublicIpAddressResponse{}
+	err = client.Invoke("AllocatePublicIpAddress", &args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.IpAddress, nil
+}
+
+type ModifyInstanceNetworkSpec struct {
+	InstanceId              string
+	InternetMaxBandwidthOut *int
+	InternetMaxBandwidthIn  *int
+}
+
+type ModifyInstanceNetworkSpecResponse struct {
+	common.Response
+}
+
+// ModifyInstanceNetworkSpec modifies instance network spec
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&modifyinstancenetworkspec
+func (client *Client) ModifyInstanceNetworkSpec(args *ModifyInstanceNetworkSpec) error {
+
+	response := ModifyInstanceNetworkSpecResponse{}
+	return client.Invoke("ModifyInstanceNetworkSpec", args, &response)
+}
+
+type AllocateEipAddressArgs struct {
+	RegionId           common.Region
+	Bandwidth          int
+	InternetChargeType common.InternetChargeType
+	ClientToken        string
+}
+
+type AllocateEipAddressResponse struct {
+	common.Response
+	EipAddress   string
+	AllocationId string
+}
+
+// AllocateEipAddress allocates Eip Address
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&allocateeipaddress
+func (client *Client) AllocateEipAddress(args *AllocateEipAddressArgs) (EipAddress string, AllocationId string, err error) {
+	if args.Bandwidth == 0 {
+		args.Bandwidth = 5
+	}
+	response := AllocateEipAddressResponse{}
+	err = client.Invoke("AllocateEipAddress", args, &response)
+	if err != nil {
+		return "", "", err
+	}
+	return response.EipAddress, response.AllocationId, nil
+}
+
+type AssociateEipAddressArgs struct {
+	AllocationId string
+	InstanceId   string
+}
+
+type AssociateEipAddressResponse struct {
+	common.Response
+}
+
+// AssociateEipAddress associates EIP address to VM instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&associateeipaddress
+func (client *Client) AssociateEipAddress(allocationId string, instanceId string) error {
+	args := AssociateEipAddressArgs{
+		AllocationId: allocationId,
+		InstanceId:   instanceId,
+	}
+	response := ModifyInstanceNetworkSpecResponse{}
+	return client.Invoke("AssociateEipAddress", &args, &response)
+}
+
+// Status of disks
+type EipStatus string
+
+const (
+	EipStatusAssociating   = EipStatus("Associating")
+	EipStatusUnassociating = EipStatus("Unassociating")
+	EipStatusInUse         = EipStatus("InUse")
+	EipStatusAvailable     = EipStatus("Available")
+)
+
+type DescribeEipAddressesArgs struct {
+	RegionId     common.Region
+	Status       EipStatus //enum Associating | Unassociating | InUse | Available
+	EipAddress   string
+	AllocationId string
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&eipaddresssettype
+type EipAddressSetType struct {
+	RegionId           common.Region
+	IpAddress          string
+	AllocationId       string
+	Status             EipStatus
+	InstanceId         string
+	Bandwidth          string // Why string
+	InternetChargeType common.InternetChargeType
+	OperationLocks     OperationLocksType
+	AllocationTime     util.ISO6801Time
+}
+
+type DescribeEipAddressesResponse struct {
+	common.Response
+	common.PaginationResult
+	EipAddresses struct {
+		EipAddress []EipAddressSetType
+	}
+}
+
+// DescribeInstanceStatus describes instance status
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&describeeipaddresses
+func (client *Client) DescribeEipAddresses(args *DescribeEipAddressesArgs) (eipAddresses []EipAddressSetType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeEipAddressesResponse{}
+
+	err = client.Invoke("DescribeEipAddresses", args, &response)
+
+	if err == nil {
+		return response.EipAddresses.EipAddress, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type ModifyEipAddressAttributeArgs struct {
+	AllocationId string
+	Bandwidth    int
+}
+
+type ModifyEipAddressAttributeResponse struct {
+	common.Response
+}
+
+// ModifyEipAddressAttribute Modifies EIP attribute
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&modifyeipaddressattribute
+func (client *Client) ModifyEipAddressAttribute(allocationId string, bandwidth int) error {
+	args := ModifyEipAddressAttributeArgs{
+		AllocationId: allocationId,
+		Bandwidth:    bandwidth,
+	}
+	response := ModifyEipAddressAttributeResponse{}
+	return client.Invoke("ModifyEipAddressAttribute", &args, &response)
+}
+
+type UnallocateEipAddressArgs struct {
+	AllocationId string
+	InstanceId   string
+}
+
+type UnallocateEipAddressResponse struct {
+	common.Response
+}
+
+// UnassociateEipAddress unallocates Eip Address from instance
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&unassociateeipaddress
+func (client *Client) UnassociateEipAddress(allocationId string, instanceId string) error {
+	args := UnallocateEipAddressArgs{
+		AllocationId: allocationId,
+		InstanceId:   instanceId,
+	}
+	response := UnallocateEipAddressResponse{}
+	return client.Invoke("UnassociateEipAddress", &args, &response)
+}
+
+type ReleaseEipAddressArgs struct {
+	AllocationId string
+}
+
+type ReleaseEipAddressResponse struct {
+	common.Response
+}
+
+// ReleaseEipAddress releases Eip address
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&releaseeipaddress
+func (client *Client) ReleaseEipAddress(allocationId string) error {
+	args := ReleaseEipAddressArgs{
+		AllocationId: allocationId,
+	}
+	response := ReleaseEipAddressResponse{}
+	return client.Invoke("ReleaseEipAddress", &args, &response)
+}
+
+// WaitForVSwitchAvailable waits for VSwitch to given status
+func (client *Client) WaitForEip(regionId common.Region, allocationId string, status EipStatus, timeout int) error {
+	if timeout <= 0 {
+		timeout = DefaultTimeout
+	}
+	args := DescribeEipAddressesArgs{
+		RegionId:     regionId,
+		AllocationId: allocationId,
+	}
+	for {
+		eips, _, err := client.DescribeEipAddresses(&args)
+		if err != nil {
+			return err
+		}
+		if len(eips) == 0 {
+			return common.GetClientErrorFromString("Not found")
+		}
+		if eips[0].Status == status {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}

+ 66 - 0
vendor/github.com/denverdino/aliyungo/ecs/networks_test.go

@@ -0,0 +1,66 @@
+package ecs
+
+import (
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func TestAllocatePublicIpAddress(t *testing.T) {
+
+	client := NewTestClient()
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to describe instance %s: %v", TestInstanceId, err)
+	}
+	t.Logf("Instance: %++v  %v", instance, err)
+	ipAddr, err := client.AllocatePublicIpAddress(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to allocate public IP address for instance %s: %v", TestInstanceId, err)
+	}
+	t.Logf("Public IP address of instance %s: %s", TestInstanceId, ipAddr)
+
+}
+
+func testEipAddress(t *testing.T, client *Client, regionId common.Region, instanceId string) error {
+
+	args := AllocateEipAddressArgs{
+		RegionId:           regionId,
+		Bandwidth:          5,
+		InternetChargeType: common.PayByTraffic,
+		ClientToken:        client.GenerateClientToken(),
+	}
+	ipAddr, allocationId, err := client.AllocateEipAddress(&args)
+	if err != nil {
+		t.Errorf("Failed to allocate EIP address: %v", err)
+		return err
+	}
+	t.Logf("EIP address: %s, AllocationId: %s", ipAddr, allocationId)
+
+	err = client.WaitForEip(regionId, allocationId, EipStatusAvailable, 0)
+	if err != nil {
+		t.Errorf("Failed to wait EIP %s: %v", allocationId, err)
+	}
+
+	err = client.AssociateEipAddress(allocationId, instanceId)
+	if err != nil {
+		t.Errorf("Failed to associate EIP address: %v", err)
+	}
+	err = client.WaitForEip(regionId, allocationId, EipStatusInUse, 0)
+	if err != nil {
+		t.Errorf("Failed to wait EIP %s: %v", allocationId, err)
+	}
+	err = client.UnassociateEipAddress(allocationId, instanceId)
+	if err != nil {
+		t.Errorf("Failed to unassociate EIP address: %v", err)
+	}
+	err = client.WaitForEip(regionId, allocationId, EipStatusAvailable, 0)
+	if err != nil {
+		t.Errorf("Failed to wait EIP %s: %v", allocationId, err)
+	}
+	err = client.ReleaseEipAddress(allocationId)
+	if err != nil {
+		t.Errorf("Failed to release EIP address: %v", err)
+	}
+	return err
+}

+ 34 - 0
vendor/github.com/denverdino/aliyungo/ecs/regions.go

@@ -0,0 +1,34 @@
+package ecs
+
+import "github.com/denverdino/aliyungo/common"
+
+type DescribeRegionsArgs struct {
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&regiontype
+type RegionType struct {
+	RegionId  common.Region
+	LocalName string
+}
+
+type DescribeRegionsResponse struct {
+	common.Response
+	Regions struct {
+		Region []RegionType
+	}
+}
+
+// DescribeRegions describes regions
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/region&describeregions
+func (client *Client) DescribeRegions() (regions []RegionType, err error) {
+	response := DescribeRegionsResponse{}
+
+	err = client.Invoke("DescribeRegions", &DescribeRegionsArgs{}, &response)
+
+	if err != nil {
+		return []RegionType{}, err
+	}
+	return response.Regions.Region, nil
+}

+ 162 - 0
vendor/github.com/denverdino/aliyungo/ecs/route_tables.go

@@ -0,0 +1,162 @@
+package ecs
+
+import (
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type DescribeRouteTablesArgs struct {
+	VRouterId    string
+	RouteTableId string
+	common.Pagination
+}
+
+type RouteTableType string
+
+const (
+	RouteTableSystem = RouteTableType("System")
+	RouteTableCustom = RouteTableType("Custom")
+)
+
+type RouteEntryStatus string
+
+const (
+	RouteEntryStatusPending   = RouteEntryStatus("Pending")
+	RouteEntryStatusAvailable = RouteEntryStatus("Available")
+	RouteEntryStatusModifying = RouteEntryStatus("Modifying")
+)
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&routeentrysettype
+type RouteEntrySetType struct {
+	RouteTableId         string
+	DestinationCidrBlock string
+	Type                 RouteTableType
+	InstanceId           string
+	Status               RouteEntryStatus // enum Pending | Available | Modifying
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&routetablesettype
+type RouteTableSetType struct {
+	VRouterId    string
+	RouteTableId string
+	RouteEntrys  struct {
+		RouteEntry []RouteEntrySetType
+	}
+	RouteTableType RouteTableType
+	CreationTime   util.ISO6801Time
+}
+
+type DescribeRouteTablesResponse struct {
+	common.Response
+	common.PaginationResult
+	RouteTables struct {
+		RouteTable []RouteTableSetType
+	}
+}
+
+// DescribeRouteTables describes Virtual Routers
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/routertable&describeroutetables
+func (client *Client) DescribeRouteTables(args *DescribeRouteTablesArgs) (routeTables []RouteTableSetType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeRouteTablesResponse{}
+
+	err = client.Invoke("DescribeRouteTables", args, &response)
+
+	if err == nil {
+		return response.RouteTables.RouteTable, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type NextHopType string
+
+const (
+	NextHopIntance = NextHopType("Instance") //Default
+	NextHopTunnel  = NextHopType("Tunnel")
+)
+
+type CreateRouteEntryArgs struct {
+	RouteTableId         string
+	DestinationCidrBlock string
+	NextHopType          NextHopType
+	NextHopId            string
+	ClientToken          string
+}
+
+type CreateRouteEntryResponse struct {
+	common.Response
+}
+
+// CreateRouteEntry creates route entry
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/routertable&createrouteentry
+func (client *Client) CreateRouteEntry(args *CreateRouteEntryArgs) error {
+	response := CreateRouteEntryResponse{}
+	return client.Invoke("CreateRouteEntry", args, &response)
+}
+
+type DeleteRouteEntryArgs struct {
+	RouteTableId         string
+	DestinationCidrBlock string
+	NextHopId            string
+}
+
+type DeleteRouteEntryResponse struct {
+	common.Response
+}
+
+// DeleteRouteEntry deletes route entry
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/routertable&deleterouteentry
+func (client *Client) DeleteRouteEntry(args *DeleteRouteEntryArgs) error {
+	response := DeleteRouteEntryResponse{}
+	return client.Invoke("DeleteRouteEntry", args, &response)
+}
+
+// WaitForAllRouteEntriesAvailable waits for all route entries to Available status
+func (client *Client) WaitForAllRouteEntriesAvailable(vrouterId string, routeTableId string, timeout int) error {
+	if timeout <= 0 {
+		timeout = DefaultTimeout
+	}
+	args := DescribeRouteTablesArgs{
+		VRouterId:    vrouterId,
+		RouteTableId: routeTableId,
+	}
+	for {
+
+		routeTables, _, err := client.DescribeRouteTables(&args)
+
+		if err != nil {
+			return err
+		}
+		if len(routeTables) == 0 {
+			return common.GetClientErrorFromString("Not found")
+		}
+		success := true
+
+	loop:
+		for _, routeTable := range routeTables {
+			for _, routeEntry := range routeTable.RouteEntrys.RouteEntry {
+				if routeEntry.Status != RouteEntryStatusAvailable {
+					success = false
+					break loop
+				}
+			}
+		}
+		if success {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}

+ 51 - 0
vendor/github.com/denverdino/aliyungo/ecs/route_tables_test.go

@@ -0,0 +1,51 @@
+package ecs
+
+import (
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func testRouteTable(t *testing.T, client *Client, regionId common.Region, vpcId string, vrouterId string, routeTableId string, instanceId string) {
+	cidrBlock := "0.0.0.0/0"
+	createArgs := CreateRouteEntryArgs{
+		RouteTableId:         routeTableId,
+		DestinationCidrBlock: cidrBlock,
+		NextHopType:          NextHopIntance,
+		NextHopId:            instanceId,
+		ClientToken:          client.GenerateClientToken(),
+	}
+
+	err := client.CreateRouteEntry(&createArgs)
+	if err != nil {
+		t.Errorf("Failed to create route entry: %v", err)
+	}
+
+	describeArgs := DescribeRouteTablesArgs{
+		VRouterId: vrouterId,
+	}
+
+	routeTables, _, err := client.DescribeRouteTables(&describeArgs)
+
+	if err != nil {
+		t.Errorf("Failed to describe route tables: %v", err)
+	} else {
+		t.Logf("RouteTables of VRouter %s: %++v", vrouterId, routeTables)
+	}
+
+	err = client.WaitForAllRouteEntriesAvailable(vrouterId, routeTableId, 60)
+	if err != nil {
+		t.Errorf("Failed to wait route entries: %v", err)
+	}
+	deleteArgs := DeleteRouteEntryArgs{
+		RouteTableId:         routeTableId,
+		DestinationCidrBlock: cidrBlock,
+		NextHopId:            instanceId,
+	}
+
+	err = client.DeleteRouteEntry(&deleteArgs)
+	if err != nil {
+		t.Errorf("Failed to delete route entry: %v", err)
+	}
+
+}

+ 208 - 0
vendor/github.com/denverdino/aliyungo/ecs/security_groups.go

@@ -0,0 +1,208 @@
+package ecs
+
+import (
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type NicType string
+
+const (
+	NicTypeInternet = NicType("internet")
+	NicTypeIntranet = NicType("intranet")
+)
+
+type IpProtocol string
+
+const (
+	IpProtocolAll  = IpProtocol("all")
+	IpProtocolTCP  = IpProtocol("tcp")
+	IpProtocolUDP  = IpProtocol("udp")
+	IpProtocolICMP = IpProtocol("icmp")
+	IpProtocolGRE  = IpProtocol("gre")
+)
+
+type PermissionPolicy string
+
+const (
+	PermissionPolicyAccept = PermissionPolicy("accept")
+	PermissionPolicyDrop   = PermissionPolicy("drop")
+)
+
+type DescribeSecurityGroupAttributeArgs struct {
+	SecurityGroupId string
+	RegionId        common.Region
+	NicType         NicType //enum for internet (default) |intranet
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&permissiontype
+type PermissionType struct {
+	IpProtocol              IpProtocol
+	PortRange               string
+	SourceCidrIp            string
+	SourceGroupId           string
+	SourceGroupOwnerAccount string
+	Policy                  PermissionPolicy
+	NicType                 NicType
+}
+
+type DescribeSecurityGroupAttributeResponse struct {
+	common.Response
+
+	SecurityGroupId   string
+	SecurityGroupName string
+	RegionId          common.Region
+	Description       string
+	Permissions       struct {
+		Permission []PermissionType
+	}
+	VpcId string
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&describesecuritygroupattribute
+func (client *Client) DescribeSecurityGroupAttribute(args *DescribeSecurityGroupAttributeArgs) (response *DescribeSecurityGroupAttributeResponse, err error) {
+	response = &DescribeSecurityGroupAttributeResponse{}
+	err = client.Invoke("DescribeSecurityGroupAttribute", args, response)
+	if err != nil {
+		return nil, err
+	}
+	return response, nil
+}
+
+type DescribeSecurityGroupsArgs struct {
+	RegionId common.Region
+	VpcId    string
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&securitygroupitemtype
+type SecurityGroupItemType struct {
+	SecurityGroupId   string
+	SecurityGroupName string
+	Description       string
+	VpcId             string
+	CreationTime      util.ISO6801Time
+}
+
+type DescribeSecurityGroupsResponse struct {
+	common.Response
+	common.PaginationResult
+
+	RegionId       common.Region
+	SecurityGroups struct {
+		SecurityGroup []SecurityGroupItemType
+	}
+}
+
+// DescribeSecurityGroups describes security groups
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&describesecuritygroups
+func (client *Client) DescribeSecurityGroups(args *DescribeSecurityGroupsArgs) (securityGroupItems []SecurityGroupItemType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeSecurityGroupsResponse{}
+
+	err = client.Invoke("DescribeSecurityGroups", args, &response)
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return response.SecurityGroups.SecurityGroup, &response.PaginationResult, nil
+}
+
+type CreateSecurityGroupArgs struct {
+	RegionId          common.Region
+	SecurityGroupName string
+	Description       string
+	VpcId             string
+	ClientToken       string
+}
+
+type CreateSecurityGroupResponse struct {
+	common.Response
+
+	SecurityGroupId string
+}
+
+// CreateSecurityGroup creates security group
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&createsecuritygroup
+func (client *Client) CreateSecurityGroup(args *CreateSecurityGroupArgs) (securityGroupId string, err error) {
+	response := CreateSecurityGroupResponse{}
+	err = client.Invoke("CreateSecurityGroup", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.SecurityGroupId, err
+}
+
+type DeleteSecurityGroupArgs struct {
+	RegionId        common.Region
+	SecurityGroupId string
+}
+
+type DeleteSecurityGroupResponse struct {
+	common.Response
+}
+
+// DeleteSecurityGroup deletes security group
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&deletesecuritygroup
+func (client *Client) DeleteSecurityGroup(regionId common.Region, securityGroupId string) error {
+	args := DeleteSecurityGroupArgs{
+		RegionId:        regionId,
+		SecurityGroupId: securityGroupId,
+	}
+	response := DeleteSecurityGroupResponse{}
+	err := client.Invoke("DeleteSecurityGroup", &args, &response)
+	return err
+}
+
+type ModifySecurityGroupAttributeArgs struct {
+	RegionId          common.Region
+	SecurityGroupId   string
+	SecurityGroupName string
+	Description       string
+}
+
+type ModifySecurityGroupAttributeResponse struct {
+	common.Response
+}
+
+// ModifySecurityGroupAttribute modifies attribute of security group
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&modifysecuritygroupattribute
+func (client *Client) ModifySecurityGroupAttribute(args *ModifySecurityGroupAttributeArgs) error {
+	response := ModifySecurityGroupAttributeResponse{}
+	err := client.Invoke("ModifySecurityGroupAttribute", args, &response)
+	return err
+}
+
+type AuthorizeSecurityGroupArgs struct {
+	SecurityGroupId         string
+	RegionId                common.Region
+	IpProtocol              IpProtocol
+	PortRange               string
+	SourceGroupId           string
+	SourceGroupOwnerAccount string
+	SourceCidrIp            string           // IPv4 only, default 0.0.0.0/0
+	Policy                  PermissionPolicy // enum of accept (default) | drop
+	Priority                int              // 1 - 100, default 1
+	NicType                 NicType          // enum of internet | intranet (default)
+}
+
+type AuthorizeSecurityGroupResponse struct {
+	common.Response
+}
+
+// AuthorizeSecurityGroup authorize permissions to security group
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&authorizesecuritygroup
+func (client *Client) AuthorizeSecurityGroup(args *AuthorizeSecurityGroupArgs) error {
+	response := AuthorizeSecurityGroupResponse{}
+	err := client.Invoke("AuthorizeSecurityGroup", args, &response)
+	return err
+}

+ 108 - 0
vendor/github.com/denverdino/aliyungo/ecs/security_groups_test.go

@@ -0,0 +1,108 @@
+package ecs
+
+import (
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func TestSecurityGroups(t *testing.T) {
+
+	client := NewTestClient()
+	regions, err := client.DescribeRegions()
+
+	t.Log("regions: ", regions, err)
+
+	for _, region := range regions {
+
+		arg := DescribeSecurityGroupsArgs{
+			RegionId: region.RegionId,
+		}
+
+		sgs, _, err := client.DescribeSecurityGroups(&arg)
+		if err != nil {
+			t.Errorf("Failed to DescribeSecurityGroups for region %s: %v", region.RegionId, err)
+			continue
+		}
+		for _, sg := range sgs {
+			t.Logf("SecurityGroup: %++v", sg)
+
+			args := DescribeSecurityGroupAttributeArgs{
+				SecurityGroupId: sg.SecurityGroupId,
+				RegionId:        region.RegionId,
+			}
+			sga, err := client.DescribeSecurityGroupAttribute(&args)
+			if err != nil {
+				t.Errorf("Failed to DescribeSecurityGroupAttribute %s: %v", sg.SecurityGroupId, err)
+				continue
+			}
+			t.Logf("SecurityGroup Attribute: %++v", sga)
+
+		}
+	}
+}
+
+func TestECSSecurityGroupCreationAndDeletion(t *testing.T) {
+	client := NewTestClient()
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to describe instance attribute %s: %v", TestInstanceId, err)
+	}
+	regionId := instance.RegionId
+
+	_testECSSecurityGroupCreationAndDeletion(t, client, regionId, "")
+
+}
+
+func _testECSSecurityGroupCreationAndDeletion(t *testing.T, client *Client, regionId common.Region, vpcId string) {
+
+	sgName := "test-security-group"
+	args := CreateSecurityGroupArgs{
+		RegionId:          regionId,
+		VpcId:             vpcId,
+		SecurityGroupName: sgName,
+	}
+
+	sgId, err := client.CreateSecurityGroup(&args)
+	if err != nil {
+		t.Fatalf("Failed to create security group %s: %v", sgName, err)
+	}
+	t.Logf("Security group %s is created successfully.", sgId)
+
+	describeArgs := DescribeSecurityGroupAttributeArgs{
+		SecurityGroupId: sgId,
+		RegionId:        regionId,
+	}
+	sg, err := client.DescribeSecurityGroupAttribute(&describeArgs)
+	if err != nil {
+		t.Errorf("Failed to describe security group %s: %v", sgId, err)
+	}
+	t.Logf("Security group %s: %++v", sgId, sg)
+
+	newName := "test-security-group-new"
+	modifyArgs := ModifySecurityGroupAttributeArgs{
+		SecurityGroupId:   sgId,
+		RegionId:          regionId,
+		SecurityGroupName: newName,
+	}
+	err = client.ModifySecurityGroupAttribute(&modifyArgs)
+	if err != nil {
+		t.Errorf("Failed to modify security group %s: %v", sgId, err)
+	} else {
+		sg, err := client.DescribeSecurityGroupAttribute(&describeArgs)
+		if err != nil {
+			t.Errorf("Failed to describe security group %s: %v", sgId, err)
+		}
+		t.Logf("Security group %s: %++v", sgId, sg)
+		if sg.SecurityGroupName != newName {
+			t.Errorf("Failed to modify security group %s with new name %s", sgId, newName)
+		}
+	}
+
+	err = client.DeleteSecurityGroup(regionId, sgId)
+
+	if err != nil {
+		t.Fatalf("Failed to delete security group %s: %v", sgId, err)
+	}
+	t.Logf("Security group %s is deleted successfully.", sgId)
+}

+ 131 - 0
vendor/github.com/denverdino/aliyungo/ecs/snapshots.go

@@ -0,0 +1,131 @@
+package ecs
+
+import (
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type DescribeSnapshotsArgs struct {
+	RegionId    common.Region
+	InstanceId  string
+	DiskId      string
+	SnapshotIds []string //["s-xxxxxxxxx", "s-yyyyyyyyy", ..."s-zzzzzzzzz"]
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&snapshottype
+type SnapshotType struct {
+	SnapshotId     string
+	SnapshotName   string
+	Description    string
+	Progress       string
+	SourceDiskId   string
+	SourceDiskSize int
+	SourceDiskType string //enum for System | Data
+	ProductCode    string
+	CreationTime   util.ISO6801Time
+}
+
+type DescribeSnapshotsResponse struct {
+	common.Response
+	common.PaginationResult
+	Snapshots struct {
+		Snapshot []SnapshotType
+	}
+}
+
+// DescribeSnapshots describe snapshots
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/snapshot&describesnapshots
+func (client *Client) DescribeSnapshots(args *DescribeSnapshotsArgs) (snapshots []SnapshotType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeSnapshotsResponse{}
+
+	err = client.Invoke("DescribeSnapshots", args, &response)
+
+	if err != nil {
+		return nil, nil, err
+	}
+	return response.Snapshots.Snapshot, &response.PaginationResult, nil
+
+}
+
+type DeleteSnapshotArgs struct {
+	SnapshotId string
+}
+
+type DeleteSnapshotResponse struct {
+	common.Response
+}
+
+// DeleteSnapshot deletes snapshot
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/snapshot&deletesnapshot
+func (client *Client) DeleteSnapshot(snapshotId string) error {
+	args := DeleteSnapshotArgs{SnapshotId: snapshotId}
+	response := DeleteSnapshotResponse{}
+
+	return client.Invoke("DeleteSnapshot", &args, &response)
+}
+
+type CreateSnapshotArgs struct {
+	DiskId       string
+	SnapshotName string
+	Description  string
+	ClientToken  string
+}
+
+type CreateSnapshotResponse struct {
+	common.Response
+	SnapshotId string
+}
+
+// CreateSnapshot creates a new snapshot
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/snapshot&createsnapshot
+func (client *Client) CreateSnapshot(args *CreateSnapshotArgs) (snapshotId string, err error) {
+
+	response := CreateSnapshotResponse{}
+
+	err = client.Invoke("CreateSnapshot", args, &response)
+	if err == nil {
+		snapshotId = response.SnapshotId
+	}
+	return snapshotId, err
+}
+
+// Default timeout value for WaitForSnapShotReady method
+const SnapshotDefaultTimeout = 120
+
+// WaitForSnapShotReady waits for snapshot ready
+func (client *Client) WaitForSnapShotReady(regionId common.Region, snapshotId string, timeout int) error {
+	if timeout <= 0 {
+		timeout = SnapshotDefaultTimeout
+	}
+	for {
+		args := DescribeSnapshotsArgs{
+			RegionId:    regionId,
+			SnapshotIds: []string{snapshotId},
+		}
+
+		snapshots, _, err := client.DescribeSnapshots(&args)
+		if err != nil {
+			return err
+		}
+		if snapshots == nil || len(snapshots) == 0 {
+			return common.GetClientErrorFromString("Not found")
+		}
+		if snapshots[0].Progress == "100%" {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}

+ 76 - 0
vendor/github.com/denverdino/aliyungo/ecs/snapshots_test.go

@@ -0,0 +1,76 @@
+package ecs
+
+import (
+	"testing"
+)
+
+func TestSnapshot(t *testing.T) {
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := DescribeSnapshotsArgs{}
+
+	args.InstanceId = TestInstanceId
+	args.RegionId = instance.RegionId
+	snapshots, _, err := client.DescribeSnapshots(&args)
+
+	if err != nil {
+		t.Errorf("Failed to DescribeSnapshots for instance %s: %v", TestInstanceId, err)
+	}
+
+	for _, snapshot := range snapshots {
+		t.Logf("Snapshot of instance %s: %++v", TestInstanceId, snapshot)
+	}
+}
+
+func TestSnapshotCreationAndDeletion(t *testing.T) {
+	if TestQuick {
+		return
+	}
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	//Describe disk monitor data
+	diskArgs := DescribeDisksArgs{
+		InstanceId: TestInstanceId,
+		RegionId:   instance.RegionId,
+	}
+
+	disks, _, err := client.DescribeDisks(&diskArgs)
+	if err != nil {
+		t.Fatalf("Failed to DescribeDisks for instance %s: %v", TestInstanceId, err)
+	}
+
+	diskId := disks[0].DiskId
+
+	args := CreateSnapshotArgs{
+		DiskId:       diskId,
+		SnapshotName: "My_Test_Snapshot",
+		Description:  "My Test Snapshot Description",
+		ClientToken:  client.GenerateClientToken(),
+	}
+
+	snapshotId, err := client.CreateSnapshot(&args)
+	if err != nil {
+		t.Errorf("Failed to CreateSnapshot for disk %s: %v", diskId, err)
+	}
+	client.WaitForSnapShotReady(instance.RegionId, snapshotId, 0)
+
+	err = client.DeleteSnapshot(snapshotId)
+	if err != nil {
+		t.Errorf("Failed to DeleteSnapshot for disk %s: %v", diskId, err)
+	}
+
+	t.Logf("Snapshot %s is deleted successfully.", snapshotId)
+
+}

+ 120 - 0
vendor/github.com/denverdino/aliyungo/ecs/tags.go

@@ -0,0 +1,120 @@
+package ecs
+
+import "github.com/denverdino/aliyungo/common"
+
+type TagResourceType string
+
+const (
+	TagResourceImage    = TagResourceType("image")
+	TagResourceInstance = TagResourceType("instance")
+	TagResourceSnapshot = TagResourceType("snapshot")
+	TagResourceDisk     = TagResourceType("disk")
+)
+
+type AddTagsArgs struct {
+	ResourceId   string
+	ResourceType TagResourceType //image, instance, snapshot or disk
+	RegionId     common.Region
+	Tag          map[string]string
+}
+
+type AddTagsResponse struct {
+	common.Response
+}
+
+// AddTags Add tags to resource
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&addtags
+func (client *Client) AddTags(args *AddTagsArgs) error {
+	response := AddTagsResponse{}
+	err := client.Invoke("AddTags", args, &response)
+	return err
+}
+
+type RemoveTagsArgs struct {
+	ResourceId   string
+	ResourceType TagResourceType //image, instance, snapshot or disk
+	RegionId     common.Region
+	Tag          map[string]string
+}
+
+type RemoveTagsResponse struct {
+	common.Response
+}
+
+// RemoveTags remove tags to resource
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&removetags
+func (client *Client) RemoveTags(args *RemoveTagsArgs) error {
+	response := RemoveTagsResponse{}
+	err := client.Invoke("RemoveTags", args, &response)
+	return err
+}
+
+type ResourceItemType struct {
+	ResourceId   string
+	ResourceType TagResourceType
+	RegionId     common.Region
+}
+
+type DescribeResourceByTagsArgs struct {
+	ResourceType TagResourceType //image, instance, snapshot or disk
+	RegionId     common.Region
+	Tag          map[string]string
+	common.Pagination
+}
+
+type DescribeResourceByTagsResponse struct {
+	common.Response
+	common.PaginationResult
+	Resources struct {
+		Resource []ResourceItemType
+	}
+}
+
+// DescribeResourceByTags describe resource by tags
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&describeresourcebytags
+func (client *Client) DescribeResourceByTags(args *DescribeResourceByTagsArgs) (resources []ResourceItemType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeResourceByTagsResponse{}
+	err = client.Invoke("DescribeResourceByTags", args, &response)
+	if err != nil {
+		return nil, nil, err
+	}
+	return response.Resources.Resource, &response.PaginationResult, nil
+}
+
+type TagItemType struct {
+	TagKey   string
+	TagValue string
+}
+
+type DescribeTagsArgs struct {
+	RegionId     common.Region
+	ResourceType TagResourceType //image, instance, snapshot or disk
+	ResourceId   string
+	Tag          map[string]string
+	common.Pagination
+}
+
+type DescribeTagsResponse struct {
+	common.Response
+	common.PaginationResult
+	Tags struct {
+		Tag []TagItemType
+	}
+}
+
+// DescribeResourceByTags describe resource by tags
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&describeresourcebytags
+func (client *Client) DescribeTags(args *DescribeTagsArgs) (tags []TagItemType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeTagsResponse{}
+	err = client.Invoke("DescribeTags", args, &response)
+	if err != nil {
+		return nil, nil, err
+	}
+	return response.Tags.Tag, &response.PaginationResult, nil
+}

+ 98 - 0
vendor/github.com/denverdino/aliyungo/ecs/tags_test.go

@@ -0,0 +1,98 @@
+package ecs
+
+import (
+	"testing"
+)
+
+var TestTags = map[string]string{
+	"test":  "api",
+	"gosdk": "1.0",
+}
+
+func TestAddTags(t *testing.T) {
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := AddTagsArgs{
+		RegionId:     instance.RegionId,
+		ResourceId:   instance.InstanceId,
+		ResourceType: TagResourceInstance,
+		Tag:          TestTags,
+	}
+	err = client.AddTags(&args)
+
+	if err != nil {
+		t.Errorf("Failed to AddTags for instance %s: %v", TestInstanceId, err)
+	}
+
+}
+
+func TestDescribeResourceByTags(t *testing.T) {
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := DescribeResourceByTagsArgs{
+		RegionId:     instance.RegionId,
+		ResourceType: TagResourceInstance,
+		Tag:          TestTags,
+	}
+	result, _, err := client.DescribeResourceByTags(&args)
+
+	if err != nil {
+		t.Errorf("Failed to DescribeResourceByTags: %v", err)
+	} else {
+		t.Logf("result: %v", result)
+	}
+}
+
+func TestDescribeTags(t *testing.T) {
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := DescribeTagsArgs{
+		RegionId:     instance.RegionId,
+		ResourceType: TagResourceInstance,
+	}
+	result, _, err := client.DescribeTags(&args)
+
+	if err != nil {
+		t.Errorf("Failed to DescribeTags: %v", err)
+	} else {
+		t.Logf("result: %v", result)
+	}
+}
+
+func TestRemoveTags(t *testing.T) {
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to DescribeInstanceAttribute for instance %s: %v", TestInstanceId, err)
+	}
+
+	args := RemoveTagsArgs{
+		RegionId:     instance.RegionId,
+		ResourceId:   instance.InstanceId,
+		ResourceType: TagResourceInstance,
+		Tag:          TestTags,
+	}
+	err = client.RemoveTags(&args)
+
+	if err != nil {
+		t.Errorf("Failed to RemoveTags for instance %s: %v", TestInstanceId, err)
+	}
+
+}

+ 151 - 0
vendor/github.com/denverdino/aliyungo/ecs/vpcs.go

@@ -0,0 +1,151 @@
+package ecs
+
+import (
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type CreateVpcArgs struct {
+	RegionId    common.Region
+	CidrBlock   string //192.168.0.0/16 or 172.16.0.0/16 (default)
+	VpcName     string
+	Description string
+	ClientToken string
+}
+
+type CreateVpcResponse struct {
+	common.Response
+	VpcId        string
+	VRouterId    string
+	RouteTableId string
+}
+
+// CreateVpc creates Virtual Private Cloud
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&createvpc
+func (client *Client) CreateVpc(args *CreateVpcArgs) (resp *CreateVpcResponse, err error) {
+	response := CreateVpcResponse{}
+	err = client.Invoke("CreateVpc", args, &response)
+	if err != nil {
+		return nil, err
+	}
+	return &response, err
+}
+
+type DeleteVpcArgs struct {
+	VpcId string
+}
+
+type DeleteVpcResponse struct {
+	common.Response
+}
+
+// DeleteVpc deletes Virtual Private Cloud
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&deletevpc
+func (client *Client) DeleteVpc(vpcId string) error {
+	args := DeleteVpcArgs{
+		VpcId: vpcId,
+	}
+	response := DeleteVpcResponse{}
+	return client.Invoke("DeleteVpc", &args, &response)
+}
+
+type VpcStatus string
+
+const (
+	VpcStatusPending   = VpcStatus("Pending")
+	VpcStatusAvailable = VpcStatus("Available")
+)
+
+type DescribeVpcsArgs struct {
+	VpcId    string
+	RegionId common.Region
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vpcsettype
+type VpcSetType struct {
+	VpcId      string
+	RegionId   common.Region
+	Status     VpcStatus // enum Pending | Available
+	VpcName    string
+	VSwitchIds struct {
+		VSwitchId []string
+	}
+	CidrBlock    string
+	VRouterId    string
+	Description  string
+	CreationTime util.ISO6801Time
+}
+
+type DescribeVpcsResponse struct {
+	common.Response
+	common.PaginationResult
+	Vpcs struct {
+		Vpc []VpcSetType
+	}
+}
+
+// DescribeInstanceStatus describes instance status
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&describevpcs
+func (client *Client) DescribeVpcs(args *DescribeVpcsArgs) (vpcs []VpcSetType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeVpcsResponse{}
+
+	err = client.Invoke("DescribeVpcs", args, &response)
+
+	if err == nil {
+		return response.Vpcs.Vpc, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type ModifyVpcAttributeArgs struct {
+	VpcId       string
+	VpcName     string
+	Description string
+}
+
+type ModifyVpcAttributeResponse struct {
+	common.Response
+}
+
+// ModifyVpcAttribute modifies attribute of Virtual Private Cloud
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&modifyvpcattribute
+func (client *Client) ModifyVpcAttribute(args *ModifyVpcAttributeArgs) error {
+	response := ModifyVpcAttributeResponse{}
+	return client.Invoke("ModifyVpcAttribute", args, &response)
+}
+
+// WaitForInstance waits for instance to given status
+func (client *Client) WaitForVpcAvailable(regionId common.Region, vpcId string, timeout int) error {
+	if timeout <= 0 {
+		timeout = DefaultTimeout
+	}
+	args := DescribeVpcsArgs{
+		RegionId: regionId,
+		VpcId:    vpcId,
+	}
+	for {
+		vpcs, _, err := client.DescribeVpcs(&args)
+		if err != nil {
+			return err
+		}
+		if len(vpcs) > 0 && vpcs[0].Status == VpcStatusAvailable {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}

+ 177 - 0
vendor/github.com/denverdino/aliyungo/ecs/vpcs_test.go

@@ -0,0 +1,177 @@
+package ecs
+
+import (
+	"testing"
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func TestVPCCreationAndDeletion(t *testing.T) {
+
+	client := NewTestClient()
+
+	instance, err := client.DescribeInstanceAttribute(TestInstanceId)
+	if err != nil {
+		t.Fatalf("Failed to describe instance %s: %v", TestInstanceId, err)
+	}
+
+	//client.SetDebug(true)
+
+	regionId := instance.RegionId
+	zoneId := instance.ZoneId
+
+	args := CreateVpcArgs{
+		RegionId:    regionId,
+		VpcName:     "My_AliyunGO_test_VPC",
+		Description: "My AliyunGO test VPC",
+		CidrBlock:   "172.16.0.0/16",
+		ClientToken: client.GenerateClientToken(),
+	}
+
+	resp, err := client.CreateVpc(&args)
+	if err != nil {
+		t.Fatalf("Failed to create VPC: %v", err)
+	}
+	t.Logf("VPC is created successfully: %++v", resp)
+
+	vpcId := resp.VpcId
+	newName := args.VpcName + "_update"
+	newDesc := args.Description + "_update"
+	modifyArgs := ModifyVpcAttributeArgs{
+		VpcId:       vpcId,
+		VpcName:     newName,
+		Description: newDesc,
+	}
+	err = client.ModifyVpcAttribute(&modifyArgs)
+	if err != nil {
+		t.Errorf("Failed to modify VPC: %v", err)
+	}
+
+	describeArgs := DescribeVpcsArgs{
+		VpcId:    vpcId,
+		RegionId: regionId,
+	}
+	vpcs, _, err := client.DescribeVpcs(&describeArgs)
+	if err != nil {
+		t.Errorf("Failed to describe VPCs: %v", err)
+	}
+	t.Logf("VPCs: %++v", vpcs)
+	if vpcs[0].VpcName != newName {
+		t.Errorf("Failed to modify VPC with new name: %s", newName)
+	}
+
+	err = client.WaitForVpcAvailable(regionId, vpcId, 60)
+	if err != nil {
+		t.Errorf("Failed to wait VPC to available: %v", err)
+	}
+
+	_testECSSecurityGroupCreationAndDeletion(t, client, regionId, vpcId)
+
+	//Test VSwitch
+	vSwitchId, err := testCreateVSwitch(t, client, regionId, zoneId, vpcId, resp.VRouterId)
+	if err != nil {
+		t.Errorf("Failed to create VSwitch: %v", err)
+	} else {
+		if TestIAmRich {
+			instanceId, sgId, err := testCreateInstanceVpc(t, client, regionId, vpcId, vSwitchId, instance.ImageId)
+
+			if err == nil {
+				testEipAddress(t, client, regionId, instanceId)
+
+				//Test VRouter
+				testVRouter(t, client, regionId, vpcId, resp.VRouterId, instanceId)
+
+			}
+
+			if instanceId != "" {
+				err = client.StopInstance(instanceId, true)
+				if err != nil {
+					t.Errorf("Failed to stop instance %s: %v", instanceId, err)
+				} else {
+					err = client.WaitForInstance(instanceId, Stopped, 0)
+					if err != nil {
+						t.Errorf("Instance %s is failed to stop: %v", instanceId, err)
+					}
+					t.Logf("Instance %s is stopped successfully.", instanceId)
+				}
+				err = client.DeleteInstance(instanceId)
+
+				if err != nil {
+					t.Errorf("Failed to delete instance %s: %v", instanceId, err)
+				} else {
+					t.Logf("Instance %s is deleted successfully.", instanceId)
+				}
+			}
+			if sgId != "" {
+				//Wait the instance deleted completedly
+				time.Sleep(10 * time.Second)
+				err = client.DeleteSecurityGroup(regionId, sgId)
+				if err != nil {
+					t.Fatalf("Failed to delete security group %s: %v", sgId, err)
+				}
+				t.Logf("Security group %s is deleted successfully.", sgId)
+			}
+		}
+	}
+
+	if vSwitchId != "" {
+		err = client.DeleteVSwitch(vSwitchId)
+		if err != nil {
+			t.Fatalf("Failed to delete VSwitch: %v", err)
+		}
+		t.Logf("VSwitch %s is deleted successfully.", vSwitchId)
+	}
+
+	time.Sleep(20 * time.Second)
+
+	err = client.DeleteVpc(vpcId)
+	if err != nil {
+		t.Errorf("Failed to delete VPC: %v", err)
+	}
+	t.Logf("VPC %s is deleted successfully.", vpcId)
+
+}
+
+func testCreateInstanceVpc(t *testing.T, client *Client, regionId common.Region, vpcId string, vswitchId, imageId string) (instanceId string, sgId string, err error) {
+	sgName := "test-security-group"
+	args := CreateSecurityGroupArgs{
+		RegionId:          regionId,
+		VpcId:             vpcId,
+		SecurityGroupName: sgName,
+	}
+
+	sgId, err = client.CreateSecurityGroup(&args)
+
+	if err != nil {
+		t.Errorf("Failed to create security group %s: %v", sgName, err)
+		return "", "", err
+	}
+
+	createArgs := CreateInstanceArgs{
+		RegionId:        regionId,
+		ImageId:         imageId,
+		InstanceType:    "ecs.t1.small",
+		SecurityGroupId: sgId,
+		VSwitchId:       vswitchId,
+	}
+
+	instanceId, err = client.CreateInstance(&createArgs)
+	if err != nil {
+		t.Errorf("Failed to create instance from Image %s: %v", imageId, err)
+		return "", sgId, err
+	}
+	t.Logf("Instance %s is created successfully.", instanceId)
+	instance, err := client.DescribeInstanceAttribute(instanceId)
+	t.Logf("Instance: %++v  %v", instance, err)
+	err = client.WaitForInstance(instanceId, Stopped, 0)
+
+	err = client.StartInstance(instanceId)
+	if err != nil {
+		t.Errorf("Failed to start instance %s: %v", instanceId, err)
+		return instanceId, sgId, err
+	}
+	err = client.WaitForInstance(instanceId, Running, 0)
+
+	return instanceId, sgId, err
+}

+ 68 - 0
vendor/github.com/denverdino/aliyungo/ecs/vrouters.go

@@ -0,0 +1,68 @@
+package ecs
+
+import (
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type DescribeVRoutersArgs struct {
+	VRouterId string
+	RegionId  common.Region
+	common.Pagination
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vroutersettype
+type VRouterSetType struct {
+	VRouterId     string
+	RegionId      common.Region
+	VpcId         string
+	RouteTableIds struct {
+		RouteTableId []string
+	}
+	VRouterName  string
+	Description  string
+	CreationTime util.ISO6801Time
+}
+
+type DescribeVRoutersResponse struct {
+	common.Response
+	common.PaginationResult
+	VRouters struct {
+		VRouter []VRouterSetType
+	}
+}
+
+// DescribeVRouters describes Virtual Routers
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vrouter&describevrouters
+func (client *Client) DescribeVRouters(args *DescribeVRoutersArgs) (vrouters []VRouterSetType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeVRoutersResponse{}
+
+	err = client.Invoke("DescribeVRouters", args, &response)
+
+	if err == nil {
+		return response.VRouters.VRouter, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type ModifyVRouterAttributeArgs struct {
+	VRouterId   string
+	VRouterName string
+	Description string
+}
+
+type ModifyVRouterAttributeResponse struct {
+	common.Response
+}
+
+// ModifyVRouterAttribute modifies attribute of Virtual Router
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vrouter&modifyvrouterattribute
+func (client *Client) ModifyVRouterAttribute(args *ModifyVRouterAttributeArgs) error {
+	response := ModifyVRouterAttributeResponse{}
+	return client.Invoke("ModifyVRouterAttribute", args, &response)
+}

+ 40 - 0
vendor/github.com/denverdino/aliyungo/ecs/vrouters_test.go

@@ -0,0 +1,40 @@
+package ecs
+
+import (
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func testVRouter(t *testing.T, client *Client, regionId common.Region, vpcId string, vrouterId string, instanceId string) {
+
+	newName := "My_Aliyun_test_VRouter"
+
+	modifyArgs := ModifyVRouterAttributeArgs{
+		VRouterId:   vrouterId,
+		VRouterName: newName,
+		Description: newName,
+	}
+
+	err := client.ModifyVRouterAttribute(&modifyArgs)
+	if err != nil {
+		t.Errorf("Failed to modify VRouters: %v", err)
+	}
+
+	args := DescribeVRoutersArgs{
+		VRouterId: vrouterId,
+		RegionId:  regionId,
+	}
+
+	vrouters, _, err := client.DescribeVRouters(&args)
+	if err != nil {
+		t.Errorf("Failed to describe VRouters: %v", err)
+	}
+	t.Logf("VRouters: %++v", vrouters)
+	if vrouters[0].VRouterName != newName {
+		t.Errorf("Failed to modify VRouters with new name: %s", newName)
+	}
+
+	testRouteTable(t, client, regionId, vpcId, vrouterId, vrouters[0].RouteTableIds.RouteTableId[0], instanceId)
+
+}

+ 152 - 0
vendor/github.com/denverdino/aliyungo/ecs/vswitches.go

@@ -0,0 +1,152 @@
+package ecs
+
+import (
+	"time"
+
+	"github.com/denverdino/aliyungo/common"
+	"github.com/denverdino/aliyungo/util"
+)
+
+type CreateVSwitchArgs struct {
+	ZoneId      string
+	CidrBlock   string
+	VpcId       string
+	VSwitchName string
+	Description string
+	ClientToken string
+}
+
+type CreateVSwitchResponse struct {
+	common.Response
+	VSwitchId string
+}
+
+// CreateVSwitch creates Virtual Switch
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&createvswitch
+func (client *Client) CreateVSwitch(args *CreateVSwitchArgs) (vswitchId string, err error) {
+	response := CreateVSwitchResponse{}
+	err = client.Invoke("CreateVSwitch", args, &response)
+	if err != nil {
+		return "", err
+	}
+	return response.VSwitchId, err
+}
+
+type DeleteVSwitchArgs struct {
+	VSwitchId string
+}
+
+type DeleteVSwitchResponse struct {
+	common.Response
+}
+
+// DeleteVSwitch deletes Virtual Switch
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&deletevswitch
+func (client *Client) DeleteVSwitch(VSwitchId string) error {
+	args := DeleteVSwitchArgs{
+		VSwitchId: VSwitchId,
+	}
+	response := DeleteVSwitchResponse{}
+	return client.Invoke("DeleteVSwitch", &args, &response)
+}
+
+type DescribeVSwitchesArgs struct {
+	VpcId     string
+	VSwitchId string
+	ZoneId    string
+	common.Pagination
+}
+
+type VSwitchStatus string
+
+const (
+	VSwitchStatusPending   = VSwitchStatus("Pending")
+	VSwitchStatusAvailable = VSwitchStatus("Available")
+)
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vswitchsettype
+type VSwitchSetType struct {
+	VSwitchId               string
+	VpcId                   string
+	Status                  VSwitchStatus // enum Pending | Available
+	CidrBlock               string
+	ZoneId                  string
+	AvailableIpAddressCount int
+	Description             string
+	VSwitchName             string
+	CreationTime            util.ISO6801Time
+}
+
+type DescribeVSwitchesResponse struct {
+	common.Response
+	common.PaginationResult
+	VSwitches struct {
+		VSwitch []VSwitchSetType
+	}
+}
+
+// DescribeVSwitches describes Virtual Switches
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&describevswitches
+func (client *Client) DescribeVSwitches(args *DescribeVSwitchesArgs) (vswitches []VSwitchSetType, pagination *common.PaginationResult, err error) {
+	args.Validate()
+	response := DescribeVSwitchesResponse{}
+
+	err = client.Invoke("DescribeVSwitches", args, &response)
+
+	if err == nil {
+		return response.VSwitches.VSwitch, &response.PaginationResult, nil
+	}
+
+	return nil, nil, err
+}
+
+type ModifyVSwitchAttributeArgs struct {
+	VSwitchId   string
+	VSwitchName string
+	Description string
+}
+
+type ModifyVSwitchAttributeResponse struct {
+	common.Response
+}
+
+// ModifyVSwitchAttribute modifies attribute of Virtual Private Cloud
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&modifyvswitchattribute
+func (client *Client) ModifyVSwitchAttribute(args *ModifyVSwitchAttributeArgs) error {
+	response := ModifyVSwitchAttributeResponse{}
+	return client.Invoke("ModifyVSwitchAttribute", args, &response)
+}
+
+// WaitForVSwitchAvailable waits for VSwitch to given status
+func (client *Client) WaitForVSwitchAvailable(vpcId string, vswitchId string, timeout int) error {
+	if timeout <= 0 {
+		timeout = DefaultTimeout
+	}
+	args := DescribeVSwitchesArgs{
+		VpcId:     vpcId,
+		VSwitchId: vswitchId,
+	}
+	for {
+		vswitches, _, err := client.DescribeVSwitches(&args)
+		if err != nil {
+			return err
+		}
+		if len(vswitches) == 0 {
+			return common.GetClientErrorFromString("Not found")
+		}
+		if vswitches[0].Status == VSwitchStatusAvailable {
+			break
+		}
+		timeout = timeout - DefaultWaitForInterval
+		if timeout <= 0 {
+			return common.GetClientErrorFromString("Timeout")
+		}
+		time.Sleep(DefaultWaitForInterval * time.Second)
+	}
+	return nil
+}

+ 63 - 0
vendor/github.com/denverdino/aliyungo/ecs/vswitches_test.go

@@ -0,0 +1,63 @@
+package ecs
+
+import (
+	"testing"
+
+	"github.com/denverdino/aliyungo/common"
+)
+
+func testCreateVSwitch(t *testing.T, client *Client, regionId common.Region, zoneId string, vpcId string, vrouterId string) (vSwitchId string, err error) {
+
+	args := CreateVSwitchArgs{
+		ZoneId:      zoneId,
+		CidrBlock:   "172.16.10.0/24",
+		VpcId:       vpcId,
+		VSwitchName: "AliyunGo_test_vSwitch",
+		Description: "AliyunGo test vSwitch",
+		ClientToken: client.GenerateClientToken(),
+	}
+
+	vSwitchId, err = client.CreateVSwitch(&args)
+
+	if err != nil {
+		t.Errorf("Failed to create VSwitch: %v", err)
+		return "", err
+	}
+
+	t.Logf("VSwitch is created successfully: %s", vSwitchId)
+
+	err = client.WaitForVSwitchAvailable(vpcId, vSwitchId, 60)
+	if err != nil {
+		t.Errorf("Failed to wait VSwitch %s to available: %v", vSwitchId, err)
+	}
+
+	newName := args.VSwitchName + "_update"
+	modifyArgs := ModifyVSwitchAttributeArgs{
+		VSwitchId:   vSwitchId,
+		VSwitchName: newName,
+		Description: newName,
+	}
+
+	err = client.ModifyVSwitchAttribute(&modifyArgs)
+	if err != nil {
+		t.Errorf("Failed to modify VSwitch %s: %v", vSwitchId, err)
+	}
+
+	argsDescribe := DescribeVSwitchesArgs{
+		VpcId:     vpcId,
+		VSwitchId: vSwitchId,
+	}
+
+	vswitches, _, err := client.DescribeVSwitches(&argsDescribe)
+	if err != nil {
+		t.Errorf("Failed to describe VSwitch: %v", err)
+	}
+
+	if vswitches[0].VSwitchName != newName {
+		t.Errorf("Failed to modify VSwitch with new name: %s", newName)
+	}
+
+	t.Logf("VSwitch is : %++v", vswitches)
+
+	return vSwitchId, err
+}

+ 60 - 0
vendor/github.com/denverdino/aliyungo/ecs/zones.go

@@ -0,0 +1,60 @@
+package ecs
+
+import "github.com/denverdino/aliyungo/common"
+
+type ResourceType string
+
+const (
+	ResourceTypeInstance            = ResourceType("Instance")
+	ResourceTypeDisk                = ResourceType("Disk")
+	ResourceTypeVSwitch             = ResourceType("VSwitch")
+	ResourceTypeIOOptimizedInstance = ResourceType("IoOptimized")
+)
+
+type DescribeZonesArgs struct {
+	RegionId common.Region
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&availableresourcecreationtype
+type AvailableResourceCreationType struct {
+	ResourceTypes []ResourceType //enum for Instance, Disk, VSwitch
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&availablediskcategoriestype
+type AvailableDiskCategoriesType struct {
+	DiskCategories []DiskCategory //enum for cloud, ephemeral, ephemeral_ssd
+}
+
+//
+// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&zonetype
+type ZoneType struct {
+	ZoneId                    string
+	LocalName                 string
+	AvailableResourceCreation AvailableResourceCreationType
+	AvailableDiskCategories   AvailableDiskCategoriesType
+}
+
+type DescribeZonesResponse struct {
+	common.Response
+	Zones struct {
+		Zone []ZoneType
+	}
+}
+
+// DescribeZones describes zones
+func (client *Client) DescribeZones(regionId common.Region) (zones []ZoneType, err error) {
+	args := DescribeZonesArgs{
+		RegionId: regionId,
+	}
+	response := DescribeZonesResponse{}
+
+	err = client.Invoke("DescribeZones", &args, &response)
+
+	if err == nil {
+		return response.Zones.Zone, nil
+	}
+
+	return []ZoneType{}, err
+}

+ 344 - 0
vendor/github.com/denverdino/aliyungo/metadata/client.go

@@ -0,0 +1,344 @@
+package metadata
+
+import (
+	"errors"
+	"fmt"
+	"github.com/denverdino/aliyungo/util"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+)
+
+type Request struct {
+}
+
+const (
+	ENDPOINT = "http://100.100.100.200"
+
+	META_VERSION_LATEST = "latest"
+
+	RS_TYPE_META_DATA = "meta-data"
+	RS_TYPE_USER_DATA = "user-data"
+
+	DNS_NAMESERVERS    = "dns-conf/nameservers"
+	EIPV4              = "eipv4"
+	HOSTNAME           = "hostname"
+	IMAGE_ID           = "image-id"
+	INSTANCE_ID        = "instance-id"
+	MAC                = "mac"
+	NETWORK_TYPE       = "network-type"
+	NTP_CONF_SERVERS   = "ntp-conf/ntp-servers"
+	OWNER_ACCOUNT_ID   = "owner-account-id"
+	PRIVATE_IPV4       = "private-ipv4"
+	REGION             = "region-id"
+	SERIAL_NUMBER      = "serial-number"
+	SOURCE_ADDRESS     = "source-address"
+	VPC_CIDR_BLOCK     = "vpc-cidr-block"
+	VPC_ID             = "vpc-id"
+	VSWITCH_CIDR_BLOCK = "vswitch-cidr-block"
+	VSWITCH_ID         = "vswitch-id"
+)
+
+type IMetaDataClient interface {
+	Version(version string) IMetaDataClient
+	ResourceType(rtype string) IMetaDataClient
+	Resource(resource string) IMetaDataClient
+	Go() ([]string, error)
+	Url() (string, error)
+}
+
+type MetaData struct {
+	c IMetaDataClient
+}
+
+func NewMetaData(client *http.Client) *MetaData {
+	if client == nil {
+		client = &http.Client{}
+	}
+	return &MetaData{
+		c: &MetaDataClient{client: client},
+	}
+}
+
+func (m *MetaData) HostName() (string, error) {
+
+	hostname, err := m.c.Resource(HOSTNAME).Go()
+	if err != nil {
+		return "", err
+	}
+	return hostname[0], nil
+}
+
+func (m *MetaData) ImageID() (string, error) {
+
+	image, err := m.c.Resource(IMAGE_ID).Go()
+	if err != nil {
+		return "", err
+	}
+	return image[0], err
+}
+
+func (m *MetaData) InstanceID() (string, error) {
+
+	instanceid, err := m.c.Resource(INSTANCE_ID).Go()
+	if err != nil {
+		return "", err
+	}
+	return instanceid[0], err
+}
+
+func (m *MetaData) Mac() (string, error) {
+
+	mac, err := m.c.Resource(MAC).Go()
+	if err != nil {
+		return "", err
+	}
+	return mac[0], nil
+}
+
+func (m *MetaData) NetworkType() (string, error) {
+
+	network, err := m.c.Resource(NETWORK_TYPE).Go()
+	if err != nil {
+		return "", err
+	}
+	return network[0], nil
+}
+
+func (m *MetaData) OwnerAccountID() (string, error) {
+
+	owner, err := m.c.Resource(OWNER_ACCOUNT_ID).Go()
+	if err != nil {
+		return "", err
+	}
+	return owner[0], nil
+}
+
+func (m *MetaData) PrivateIPv4() (string, error) {
+
+	private, err := m.c.Resource(PRIVATE_IPV4).Go()
+	if err != nil {
+		return "", err
+	}
+	return private[0], nil
+}
+
+func (m *MetaData) Region() (string, error) {
+
+	region, err := m.c.Resource(REGION).Go()
+	if err != nil {
+		return "", err
+	}
+	return region[0], nil
+}
+
+func (m *MetaData) SerialNumber() (string, error) {
+
+	serial, err := m.c.Resource(SERIAL_NUMBER).Go()
+	if err != nil {
+		return "", err
+	}
+	return serial[0], nil
+}
+
+func (m *MetaData) SourceAddress() (string, error) {
+
+	source, err := m.c.Resource(SOURCE_ADDRESS).Go()
+	if err != nil {
+		return "", err
+	}
+	return source[0], nil
+
+}
+
+func (m *MetaData) VpcCIDRBlock() (string, error) {
+
+	vpcCIDR, err := m.c.Resource(VPC_CIDR_BLOCK).Go()
+	if err != nil {
+		return "", err
+	}
+	return vpcCIDR[0], err
+}
+
+func (m *MetaData) VpcID() (string, error) {
+
+	vpcId, err := m.c.Resource(VPC_ID).Go()
+	if err != nil {
+		return "", err
+	}
+	return vpcId[0], err
+}
+
+func (m *MetaData) VswitchCIDRBlock() (string, error) {
+
+	cidr, err := m.c.Resource(VSWITCH_CIDR_BLOCK).Go()
+	if err != nil {
+		return "", err
+	}
+	return cidr[0], err
+}
+
+func (m *MetaData) VswitchID() (string, error) {
+
+	vswithcid, err := m.c.Resource(VSWITCH_ID).Go()
+	if err != nil {
+		return "", err
+	}
+	return vswithcid[0], err
+}
+
+func (m *MetaData) EIPv4() (string, error) {
+
+	eip, err := m.c.Resource(EIPV4).Go()
+	if err != nil {
+		return "", err
+	}
+	return eip[0], nil
+}
+
+func (m *MetaData) DNSNameServers() ([]string, error) {
+
+	data, err := m.c.Resource(DNS_NAMESERVERS).Go()
+	if err != nil {
+		return []string{}, err
+	}
+	return data, nil
+}
+
+func (m *MetaData) NTPConfigServers() ([]string, error) {
+
+	data, err := m.c.Resource(NTP_CONF_SERVERS).Go()
+	if err != nil {
+		return []string{}, err
+	}
+	return data, nil
+}
+
+//
+type MetaDataClient struct {
+	version      string
+	resourceType string
+	resource     string
+	client       *http.Client
+}
+
+func (vpc *MetaDataClient) Version(version string) IMetaDataClient {
+	vpc.version = version
+	return vpc
+}
+
+func (vpc *MetaDataClient) ResourceType(rtype string) IMetaDataClient {
+	vpc.resourceType = rtype
+	return vpc
+}
+
+func (vpc *MetaDataClient) Resource(resource string) IMetaDataClient {
+	vpc.resource = resource
+	return vpc
+}
+
+var retry = util.AttemptStrategy{
+	Min:   5,
+	Total: 5 * time.Second,
+	Delay: 200 * time.Millisecond,
+}
+
+func (vpc *MetaDataClient) Url() (string, error) {
+	if vpc.version == "" {
+		vpc.version = "latest"
+	}
+	if vpc.resourceType == "" {
+		vpc.resourceType = "meta-data"
+	}
+	if vpc.resource == "" {
+		return "", errors.New("the resource you want to visit must not be nil!")
+	}
+	return fmt.Sprintf("%s/%s/%s/%s", ENDPOINT, vpc.version, vpc.resourceType, vpc.resource), nil
+}
+
+func (vpc *MetaDataClient) Go() (resu []string, err error) {
+	for r := retry.Start(); r.Next(); {
+		resu, err = vpc.send()
+		if !shouldRetry(err) {
+			break
+		}
+	}
+	return resu, err
+}
+
+func (vpc *MetaDataClient) send() ([]string, error) {
+	url, err := vpc.Url()
+	if err != nil {
+		return []string{}, err
+	}
+	requ, err := http.NewRequest(http.MethodGet, url, nil)
+
+	if err != nil {
+		return []string{}, err
+	}
+	resp, err := vpc.client.Do(requ)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode != 200 {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return []string{}, err
+	}
+	if string(data) == "" {
+		return []string{""}, nil
+	}
+	return strings.Split(string(data), "\n"), nil
+}
+
+type TimeoutError interface {
+	error
+	Timeout() bool // Is the error a timeout?
+}
+
+func shouldRetry(err error) bool {
+	if err == nil {
+		return false
+	}
+
+	_, ok := err.(TimeoutError)
+	if ok {
+		return true
+	}
+
+	switch err {
+	case io.ErrUnexpectedEOF, io.EOF:
+		return true
+	}
+	switch e := err.(type) {
+	case *net.DNSError:
+		return true
+	case *net.OpError:
+		switch e.Op {
+		case "read", "write":
+			return true
+		}
+	case *url.Error:
+		// url.Error can be returned either by net/url if a URL cannot be
+		// parsed, or by net/http if the response is closed before the headers
+		// are received or parsed correctly. In that later case, e.Op is set to
+		// the HTTP method name with the first letter uppercased. We don't want
+		// to retry on POST operations, since those are not idempotent, all the
+		// other ones should be safe to retry.
+		switch e.Op {
+		case "Get", "Put", "Delete", "Head":
+			return shouldRetry(e.Err)
+		default:
+			return false
+		}
+	}
+	return false
+}

+ 307 - 0
vendor/github.com/denverdino/aliyungo/metadata/client_test.go

@@ -0,0 +1,307 @@
+package metadata
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"strings"
+	"testing"
+)
+
+func init() {
+	fmt.Println("make sure your ecs is in vpc before you run ```go test```")
+}
+
+type MockMetaDataClient struct {
+	I IMetaDataClient
+}
+
+func (vpc *MockMetaDataClient) Version(version string) IMetaDataClient {
+	vpc.I.Version(version)
+	return vpc
+}
+
+func (vpc *MockMetaDataClient) ResourceType(rtype string) IMetaDataClient {
+	vpc.I.ResourceType(rtype)
+	return vpc
+}
+
+func (vpc *MockMetaDataClient) Resource(resource string) IMetaDataClient {
+	vpc.I.Resource(resource)
+	return vpc
+}
+
+func (vpc *MockMetaDataClient) Url() (string, error) {
+	return vpc.I.Url()
+}
+
+func (m *MockMetaDataClient) Go() ([]string, error) {
+	uri, err := m.Url()
+	if err != nil {
+		return []string{}, errors.New("error retrieve url")
+	}
+	if strings.Contains(uri, HOSTNAME) {
+		return []string{"hostname-test"}, nil
+	}
+
+	if strings.Contains(uri, DNS_NAMESERVERS) {
+		return []string{"8.8.8.8", "8.8.4.4"}, nil
+	}
+
+	if strings.Contains(uri, EIPV4) {
+		return []string{"1.1.1.1-test"}, nil
+	}
+
+	if strings.Contains(uri, IMAGE_ID) {
+		return []string{"image-id-test"}, nil
+	}
+
+	if strings.Contains(uri, INSTANCE_ID) {
+		return []string{"instanceid-test"}, nil
+	}
+
+	if strings.Contains(uri, MAC) {
+		return []string{"mac-test"}, nil
+	}
+
+	if strings.Contains(uri, NETWORK_TYPE) {
+		return []string{"network-type-test"}, nil
+	}
+
+	if strings.Contains(uri, OWNER_ACCOUNT_ID) {
+		return []string{"owner-account-id-test"}, nil
+	}
+
+	if strings.Contains(uri, PRIVATE_IPV4) {
+		return []string{"private-ipv4-test"}, nil
+	}
+
+	if strings.Contains(uri, REGION) {
+		return []string{"region-test"}, nil
+	}
+
+	if strings.Contains(uri, SERIAL_NUMBER) {
+		return []string{"serial-number-test"}, nil
+	}
+
+	if strings.Contains(uri, SOURCE_ADDRESS) {
+		return []string{"source-address-test"}, nil
+	}
+
+	if strings.Contains(uri, VPC_CIDR_BLOCK) {
+		return []string{"vpc-cidr-block-test"}, nil
+	}
+
+	if strings.Contains(uri, VPC_ID) {
+		return []string{"vpc-id-test"}, nil
+	}
+
+	if strings.Contains(uri, VSWITCH_CIDR_BLOCK) {
+		return []string{"vswitch-cidr-block-test"}, nil
+	}
+
+	if strings.Contains(uri, VSWITCH_ID) {
+		return []string{"vswitch-id-test"}, nil
+	}
+
+	if strings.Contains(uri, NTP_CONF_SERVERS) {
+		return []string{"ntp1.server.com", "ntp2.server.com"}, nil
+	}
+
+	return nil, errors.New("unknow resource error.")
+}
+
+func TestOK(t *testing.T) {
+	fmt.Println("ok")
+}
+
+func NewMockMetaData(client *http.Client) *MetaData {
+	if client == nil {
+		client = &http.Client{}
+	}
+	return &MetaData{
+		c: &MetaDataClient{client: client},
+	}
+}
+
+//func NewMockMetaData(client *http.Client)* MetaData{
+//	if client == nil {
+//		client = &http.Client{}
+//	}
+//	return &MetaData{
+//		c: &MockMetaDataClient{&MetaDataClient{client:client}},
+//	}
+//}
+
+func TestHostname(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.HostName()
+	if err != nil {
+		t.Errorf("hostname err: %s", err.Error())
+	}
+	if host != "hostname-test" {
+		t.Error("hostname not equal hostname-test")
+	}
+}
+
+func TestEIPV4(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.EIPv4()
+	if err != nil {
+		t.Errorf("EIPV4 err: %s", err.Error())
+	}
+	if host != "1.1.1.1-test" {
+		t.Error("EIPV4 not equal eipv4-test")
+	}
+}
+func TestImageID(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.ImageID()
+	if err != nil {
+		t.Errorf("IMAGE_ID err: %s", err.Error())
+	}
+	if host != "image-id-test" {
+		t.Error("IMAGE_ID not equal image-id-test")
+	}
+}
+func TestInstanceID(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.InstanceID()
+	if err != nil {
+		t.Errorf("IMAGE_ID err: %s", err.Error())
+	}
+	if host != "instanceid-test" {
+		t.Error("IMAGE_ID not equal instanceid-test")
+	}
+}
+func TestMac(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.Mac()
+	if err != nil {
+		t.Errorf("Mac err: %s", err.Error())
+	}
+	if host != "mac-test" {
+		t.Error("Mac not equal mac-test")
+	}
+}
+func TestNetworkType(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.NetworkType()
+	if err != nil {
+		t.Errorf("NetworkType err: %s", err.Error())
+	}
+	if host != "network-type-test" {
+		t.Error("networktype not equal network-type-test")
+	}
+}
+func TestOwnerAccountID(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.OwnerAccountID()
+	if err != nil {
+		t.Errorf("owneraccountid err: %s", err.Error())
+	}
+	if host != "owner-account-id-test" {
+		t.Error("owner-account-id not equal owner-account-id-test")
+	}
+}
+func TestPrivateIPv4(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.PrivateIPv4()
+	if err != nil {
+		t.Errorf("privateIPv4 err: %s", err.Error())
+	}
+	if host != "private-ipv4-test" {
+		t.Error("privateIPv4 not equal private-ipv4-test")
+	}
+}
+func TestRegion(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.Region()
+	if err != nil {
+		t.Errorf("region err: %s", err.Error())
+	}
+	if host != "region-test" {
+		t.Error("region not equal region-test")
+	}
+}
+func TestSerialNumber(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.SerialNumber()
+	if err != nil {
+		t.Errorf("serial number err: %s", err.Error())
+	}
+	if host != "serial-number-test" {
+		t.Error("serial number not equal serial-number-test")
+	}
+}
+
+func TestSourceAddress(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.SourceAddress()
+	if err != nil {
+		t.Errorf("source address err: %s", err.Error())
+	}
+	if host != "source-address-test" {
+		t.Error("source address not equal source-address-test")
+	}
+}
+func TestVpcCIDRBlock(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.VpcCIDRBlock()
+	if err != nil {
+		t.Errorf("vpcCIDRBlock err: %s", err.Error())
+	}
+	if host != "vpc-cidr-block-test" {
+		t.Error("vpc-cidr-block not equal vpc-cidr-block-test")
+	}
+}
+func TestVpcID(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.VpcID()
+	if err != nil {
+		t.Errorf("vpcID err: %s", err.Error())
+	}
+	if host != "vpc-id-test" {
+		t.Error("vpc-id not equal vpc-id-test")
+	}
+}
+func TestVswitchCIDRBlock(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.VswitchCIDRBlock()
+	if err != nil {
+		t.Errorf("vswitchCIDRBlock err: %s", err.Error())
+	}
+	if host != "vswitch-cidr-block-test" {
+		t.Error("vswitch-cidr-block not equal vswitch-cidr-block-test")
+	}
+}
+func TestVswitchID(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.VswitchID()
+	if err != nil {
+		t.Errorf("vswitch id err: %s", err.Error())
+	}
+	if host != "vswitch-id-test" {
+		t.Error("vswitch-id not equal vswitch-id-test")
+	}
+}
+func TestNTPConfigServers(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.NTPConfigServers()
+	if err != nil {
+		t.Errorf("ntpconfigservers err: %s", err.Error())
+	}
+	if host[0] != "ntp1.server.com" || host[1] != "ntp2.server.com" {
+		t.Error("ntp1.server.com not equal ntp1.server.com")
+	}
+}
+func TestDNSServers(t *testing.T) {
+	meta := NewMockMetaData(nil)
+	host, err := meta.DNSNameServers()
+	if err != nil {
+		t.Errorf("dnsservers err: %s", err.Error())
+	}
+	if host[0] != "8.8.8.8" || host[1] != "8.8.4.4" {
+		t.Error("dns servers not equal 8.8.8.8/8.8.4.4")
+	}
+}

+ 76 - 0
vendor/github.com/denverdino/aliyungo/util/attempt.go

@@ -0,0 +1,76 @@
+package util
+
+import (
+	"time"
+)
+
+// AttemptStrategy is reused from the goamz package
+
+// AttemptStrategy represents a strategy for waiting for an action
+// to complete successfully. This is an internal type used by the
+// implementation of other packages.
+type AttemptStrategy struct {
+	Total time.Duration // total duration of attempt.
+	Delay time.Duration // interval between each try in the burst.
+	Min   int           // minimum number of retries; overrides Total
+}
+
+type Attempt struct {
+	strategy AttemptStrategy
+	last     time.Time
+	end      time.Time
+	force    bool
+	count    int
+}
+
+// Start begins a new sequence of attempts for the given strategy.
+func (s AttemptStrategy) Start() *Attempt {
+	now := time.Now()
+	return &Attempt{
+		strategy: s,
+		last:     now,
+		end:      now.Add(s.Total),
+		force:    true,
+	}
+}
+
+// Next waits until it is time to perform the next attempt or returns
+// false if it is time to stop trying.
+func (a *Attempt) Next() bool {
+	now := time.Now()
+	sleep := a.nextSleep(now)
+	if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
+		return false
+	}
+	a.force = false
+	if sleep > 0 && a.count > 0 {
+		time.Sleep(sleep)
+		now = time.Now()
+	}
+	a.count++
+	a.last = now
+	return true
+}
+
+func (a *Attempt) nextSleep(now time.Time) time.Duration {
+	sleep := a.strategy.Delay - now.Sub(a.last)
+	if sleep < 0 {
+		return 0
+	}
+	return sleep
+}
+
+// HasNext returns whether another attempt will be made if the current
+// one fails. If it returns true, the following call to Next is
+// guaranteed to return true.
+func (a *Attempt) HasNext() bool {
+	if a.force || a.strategy.Min > a.count {
+		return true
+	}
+	now := time.Now()
+	if now.Add(a.nextSleep(now)).Before(a.end) {
+		a.force = true
+		return true
+	}
+	return false
+}

+ 90 - 0
vendor/github.com/denverdino/aliyungo/util/attempt_test.go

@@ -0,0 +1,90 @@
+package util
+
+import (
+	"testing"
+	"time"
+)
+
+func TestAttemptTiming(t *testing.T) {
+	testAttempt := AttemptStrategy{
+		Total: 0.25e9,
+		Delay: 0.1e9,
+	}
+	want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
+	got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
+	t0 := time.Now()
+	for a := testAttempt.Start(); a.Next(); {
+		got = append(got, time.Now().Sub(t0))
+	}
+	got = append(got, time.Now().Sub(t0))
+	if len(got) != len(want) {
+		t.Fatalf("Failed!")
+	}
+	const margin = 0.01e9
+	for i, got := range want {
+		lo := want[i] - margin
+		hi := want[i] + margin
+		if got < lo || got > hi {
+			t.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
+		}
+	}
+}
+
+func TestAttemptNextHasNext(t *testing.T) {
+	a := AttemptStrategy{}.Start()
+	if !a.Next() {
+		t.Fatalf("Failed!")
+	}
+	if a.Next() {
+		t.Fatalf("Failed!")
+	}
+
+	a = AttemptStrategy{}.Start()
+	if !a.Next() {
+		t.Fatalf("Failed!")
+	}
+	if a.HasNext() {
+		t.Fatalf("Failed!")
+	}
+	if a.Next() {
+		t.Fatalf("Failed!")
+	}
+	a = AttemptStrategy{Total: 2e8}.Start()
+
+	if !a.Next() {
+		t.Fatalf("Failed!")
+	}
+	if !a.HasNext() {
+		t.Fatalf("Failed!")
+	}
+	time.Sleep(2e8)
+
+	if !a.HasNext() {
+		t.Fatalf("Failed!")
+	}
+	if !a.Next() {
+		t.Fatalf("Failed!")
+	}
+	if a.Next() {
+		t.Fatalf("Failed!")
+	}
+
+	a = AttemptStrategy{Total: 1e8, Min: 2}.Start()
+	time.Sleep(1e8)
+
+	if !a.Next() {
+		t.Fatalf("Failed!")
+	}
+	if !a.HasNext() {
+		t.Fatalf("Failed!")
+	}
+	if !a.Next() {
+		t.Fatalf("Failed!")
+	}
+	if a.HasNext() {
+		t.Fatalf("Failed!")
+	}
+	if a.Next() {
+		t.Fatalf("Failed!")
+	}
+}

+ 152 - 0
vendor/github.com/denverdino/aliyungo/util/encoding.go

@@ -0,0 +1,152 @@
+package util
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"net/url"
+	"reflect"
+	"strconv"
+	"time"
+)
+
+//ConvertToQueryValues converts the struct to url.Values
+func ConvertToQueryValues(ifc interface{}) url.Values {
+	values := url.Values{}
+	SetQueryValues(ifc, &values)
+	return values
+}
+
+//SetQueryValues sets the struct to existing url.Values following ECS encoding rules
+func SetQueryValues(ifc interface{}, values *url.Values) {
+	setQueryValues(ifc, values, "")
+}
+
+func setQueryValues(i interface{}, values *url.Values, prefix string) {
+	// add to support url.Values
+	mapValues, ok := i.(url.Values)
+	if ok {
+		for k, _ := range mapValues {
+			values.Set(k, mapValues.Get(k))
+		}
+		return
+	}
+
+	elem := reflect.ValueOf(i)
+	if elem.Kind() == reflect.Ptr {
+		elem = elem.Elem()
+	}
+	elemType := elem.Type()
+	for i := 0; i < elem.NumField(); i++ {
+
+		fieldName := elemType.Field(i).Name
+		anonymous := elemType.Field(i).Anonymous
+		field := elem.Field(i)
+		// TODO Use Tag for validation
+		// tag := typ.Field(i).Tag.Get("tagname")
+		kind := field.Kind()
+		if (kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map || kind == reflect.Chan) && field.IsNil() {
+			continue
+		}
+		if kind == reflect.Ptr {
+			field = field.Elem()
+			kind = field.Kind()
+		}
+		var value string
+		//switch field.Interface().(type) {
+		switch kind {
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+			i := field.Int()
+			if i != 0 {
+				value = strconv.FormatInt(i, 10)
+			}
+		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+			i := field.Uint()
+			if i != 0 {
+				value = strconv.FormatUint(i, 10)
+			}
+		case reflect.Float32:
+			value = strconv.FormatFloat(field.Float(), 'f', 4, 32)
+		case reflect.Float64:
+			value = strconv.FormatFloat(field.Float(), 'f', 4, 64)
+		case reflect.Bool:
+			value = strconv.FormatBool(field.Bool())
+		case reflect.String:
+			value = field.String()
+		case reflect.Map:
+			ifc := field.Interface()
+			m := ifc.(map[string]string)
+			if m != nil {
+				j := 0
+				for k, v := range m {
+					j++
+					keyName := fmt.Sprintf("%s.%d.Key", fieldName, j)
+					values.Set(keyName, k)
+					valueName := fmt.Sprintf("%s.%d.Value", fieldName, j)
+					values.Set(valueName, v)
+				}
+			}
+		case reflect.Slice:
+			switch field.Type().Elem().Kind() {
+			case reflect.Uint8:
+				value = string(field.Bytes())
+			case reflect.String:
+				l := field.Len()
+				if l > 0 {
+					strArray := make([]string, l)
+					for i := 0; i < l; i++ {
+						strArray[i] = field.Index(i).String()
+					}
+					bytes, err := json.Marshal(strArray)
+					if err == nil {
+						value = string(bytes)
+					} else {
+						log.Printf("Failed to convert JSON: %v", err)
+					}
+				}
+			default:
+				l := field.Len()
+				for j := 0; j < l; j++ {
+					prefixName := fmt.Sprintf("%s.%d.", fieldName, (j + 1))
+					ifc := field.Index(j).Interface()
+					//log.Printf("%s : %v", prefixName, ifc)
+					if ifc != nil {
+						setQueryValues(ifc, values, prefixName)
+					}
+				}
+				continue
+			}
+
+		default:
+			switch field.Interface().(type) {
+			case ISO6801Time:
+				t := field.Interface().(ISO6801Time)
+				value = t.String()
+			case time.Time:
+				t := field.Interface().(time.Time)
+				value = GetISO8601TimeStamp(t)
+			default:
+				ifc := field.Interface()
+				if ifc != nil {
+					if anonymous {
+						SetQueryValues(ifc, values)
+					} else {
+						prefixName := fieldName + "."
+						setQueryValues(ifc, values, prefixName)
+					}
+					continue
+				}
+			}
+		}
+		if value != "" {
+			name := elemType.Field(i).Tag.Get("ArgName")
+			if name == "" {
+				name = fieldName
+			}
+			if prefix != "" {
+				name = prefix + name
+			}
+			values.Set(name, value)
+		}
+	}
+}

+ 81 - 0
vendor/github.com/denverdino/aliyungo/util/encoding_test.go

@@ -0,0 +1,81 @@
+package util
+
+import (
+	"testing"
+	"time"
+)
+
+type TestString string
+
+type SubStruct struct {
+	A string
+	B int
+}
+
+type MyStruct struct {
+	SubStruct
+}
+
+type YourStruct struct {
+	SubStruct SubStruct
+}
+
+func TestConvertToQueryValues2(t *testing.T) {
+
+	result := ConvertToQueryValues(MyStruct{SubStruct: SubStruct{A: "A", B: 1}}).Encode()
+	const expectedResult = "A=A&B=1"
+	if result != expectedResult {
+		// Sometimes result is not matched for the different orders
+		t.Logf("Incorrect encoding: %s", result)
+	}
+	result2 := ConvertToQueryValues(YourStruct{SubStruct: SubStruct{A: "A2", B: 2}}).Encode()
+	const expectedResult2 = "SubStruct.A=A2&SubStruct.B=2"
+	if result2 != expectedResult2 {
+		// Sometimes result is not matched for the different orders
+		t.Logf("Incorrect encoding: %s", result2)
+	}
+}
+
+type TestStruct struct {
+	Format      string
+	Version     string
+	AccessKeyId string
+	Timestamp   time.Time
+	Empty       string
+	IntValue    int      `ArgName:"int-value"`
+	BoolPtr     *bool    `ArgName:"bool-ptr"`
+	IntPtr      *int     `ArgName:"int-ptr"`
+	StringArray []string `ArgName:"str-array"`
+	StructArray []SubStruct
+	SubStruct   SubStruct
+	test        TestString
+	tests       []TestString
+	Tag         map[string]string
+}
+
+func TestConvertToQueryValues(t *testing.T) {
+	boolValue := true
+	request := TestStruct{
+		Format:      "JSON",
+		Version:     "1.0",
+		Timestamp:   time.Date(2015, time.Month(5), 26, 1, 2, 3, 4, time.UTC),
+		IntValue:    10,
+		BoolPtr:     &boolValue,
+		StringArray: []string{"abc", "xyz"},
+		StructArray: []SubStruct{
+			SubStruct{A: "a", B: 1},
+			SubStruct{A: "x", B: 2},
+		},
+		SubStruct: SubStruct{A: "M", B: 0},
+		test:      TestString("test"),
+		tests:     []TestString{TestString("test1"), TestString("test2")},
+		Tag:       map[string]string{"abc": "xyz", "123": "456"},
+	}
+	result := ConvertToQueryValues(&request).Encode()
+	const expectedResult = "Format=JSON&StructArray.1.A=a&StructArray.1.B=1&StructArray.2.A=x&StructArray.2.B=2&SubStruct.A=M&Tag.1.Key=abc&Tag.1.Value=xyz&Tag.2.Key=123&Tag.2.Value=456&Timestamp=2015-05-26T01%3A02%3A03Z&Version=1.0&bool-ptr=true&int-value=10&str-array=%5B%22abc%22%2C%22xyz%22%5D&test=test&tests=%5B%22test1%22%2C%22test2%22%5D"
+	if result != expectedResult {
+		// Sometimes result is not matched for the different orders
+		t.Logf("Incorrect encoding: %s", result)
+	}
+
+}

+ 80 - 0
vendor/github.com/denverdino/aliyungo/util/iso6801.go

@@ -0,0 +1,80 @@
+package util
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// GetISO8601TimeStamp gets timestamp string in ISO8601 format
+func GetISO8601TimeStamp(ts time.Time) string {
+	t := ts.UTC()
+	return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
+}
+
+const formatISO8601 = "2006-01-02T15:04:05Z"
+const jsonFormatISO8601 = `"` + formatISO8601 + `"`
+const formatISO8601withoutSeconds = "2006-01-02T15:04Z"
+const jsonFormatISO8601withoutSeconds = `"` + formatISO8601withoutSeconds + `"`
+
+// A ISO6801Time represents a time in ISO8601 format
+type ISO6801Time time.Time
+
+// New constructs a new iso8601.Time instance from an existing
+// time.Time instance.  This causes the nanosecond field to be set to
+// 0, and its time zone set to a fixed zone with no offset from UTC
+// (but it is *not* UTC itself).
+func NewISO6801Time(t time.Time) ISO6801Time {
+	return ISO6801Time(time.Date(
+		t.Year(),
+		t.Month(),
+		t.Day(),
+		t.Hour(),
+		t.Minute(),
+		t.Second(),
+		0,
+		time.UTC,
+	))
+}
+
+// IsDefault checks if the time is default
+func (it *ISO6801Time) IsDefault() bool {
+	return *it == ISO6801Time{}
+}
+
+// MarshalJSON serializes the ISO6801Time into JSON string
+func (it ISO6801Time) MarshalJSON() ([]byte, error) {
+	return []byte(time.Time(it).Format(jsonFormatISO8601)), nil
+}
+
+// UnmarshalJSON deserializes the ISO6801Time from JSON string
+func (it *ISO6801Time) UnmarshalJSON(data []byte) error {
+	str := string(data)
+
+	if str == "\"\"" || len(data) == 0 {
+		return nil
+	}
+	var t time.Time
+	var err error
+	if str[0] == '"' {
+		t, err = time.ParseInLocation(jsonFormatISO8601, str, time.UTC)
+		if err != nil {
+			t, err = time.ParseInLocation(jsonFormatISO8601withoutSeconds, str, time.UTC)
+		}
+	} else {
+		var i int64
+		i, err = strconv.ParseInt(str, 10, 64)
+		if err == nil {
+			t = time.Unix(i/1000, i%1000)
+		}
+	}
+	if err == nil {
+		*it = ISO6801Time(t)
+	}
+	return err
+}
+
+// String returns the time in ISO6801Time format
+func (it ISO6801Time) String() string {
+	return time.Time(it).Format(formatISO8601)
+}

+ 91 - 0
vendor/github.com/denverdino/aliyungo/util/iso6801_test.go

@@ -0,0 +1,91 @@
+package util
+
+import (
+	"encoding/json"
+	"testing"
+	"time"
+)
+
+func TestISO8601Time(t *testing.T) {
+	now := NewISO6801Time(time.Now().UTC())
+
+	data, err := json.Marshal(now)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = time.Parse(`"`+formatISO8601+`"`, string(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var now2 ISO6801Time
+	err = json.Unmarshal(data, &now2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if now != now2 {
+		t.Errorf("Time %s does not equal expected %s", now2, now)
+	}
+
+	if now.String() != now2.String() {
+		t.Fatalf("String format for %s does not equal expected %s", now2, now)
+	}
+
+	type TestTimeStruct struct {
+		A int
+		B *ISO6801Time
+	}
+	var testValue TestTimeStruct
+	err = json.Unmarshal([]byte("{\"A\": 1, \"B\":\"\"}"), &testValue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Logf("%v", testValue)
+	if !testValue.B.IsDefault() {
+		t.Fatal("Invaid Unmarshal result for ISO6801Time from empty value")
+	}
+	t.Logf("ISO6801Time String(): %s", now2.String())
+}
+
+func TestISO8601TimeWithoutSeconds(t *testing.T) {
+
+	const dateStr = "\"2015-10-02T12:36Z\""
+
+	var date ISO6801Time
+
+	err := json.Unmarshal([]byte(dateStr), &date)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	const dateStr2 = "\"2015-10-02T12:36:00Z\""
+
+	var date2 ISO6801Time
+
+	err = json.Unmarshal([]byte(dateStr2), &date2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if date != date2 {
+		t.Error("The two dates shoudl be equal.")
+	}
+
+}
+
+func TestISO8601TimeInt(t *testing.T) {
+
+	const dateStr = "1405544146000"
+
+	var date ISO6801Time
+
+	err := json.Unmarshal([]byte(dateStr), &date)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Logf("date: %s", date)
+
+}

+ 40 - 0
vendor/github.com/denverdino/aliyungo/util/signature.go

@@ -0,0 +1,40 @@
+package util
+
+import (
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"net/url"
+	"strings"
+)
+
+//CreateSignature creates signature for string following Aliyun rules
+func CreateSignature(stringToSignature, accessKeySecret string) string {
+	// Crypto by HMAC-SHA1
+	hmacSha1 := hmac.New(sha1.New, []byte(accessKeySecret))
+	hmacSha1.Write([]byte(stringToSignature))
+	sign := hmacSha1.Sum(nil)
+
+	// Encode to Base64
+	base64Sign := base64.StdEncoding.EncodeToString(sign)
+
+	return base64Sign
+}
+
+func percentReplace(str string) string {
+	str = strings.Replace(str, "+", "%20", -1)
+	str = strings.Replace(str, "*", "%2A", -1)
+	str = strings.Replace(str, "%7E", "~", -1)
+
+	return str
+}
+
+// CreateSignatureForRequest creates signature for query string values
+func CreateSignatureForRequest(method string, values *url.Values, accessKeySecret string) string {
+
+	canonicalizedQueryString := percentReplace(values.Encode())
+
+	stringToSign := method + "&%2F&" + url.QueryEscape(canonicalizedQueryString)
+
+	return CreateSignature(stringToSign, accessKeySecret)
+}

+ 14 - 0
vendor/github.com/denverdino/aliyungo/util/signature_test.go

@@ -0,0 +1,14 @@
+package util
+
+import (
+	"testing"
+)
+
+func TestCreateSignature(t *testing.T) {
+
+	str := "GET&%2F&AccessKeyId%3Dtestid%26Action%3DDescribeRegions%26Format%3DXML%26RegionId%3Dregion1%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3DNwDAxvLU6tFE0DVb%26SignatureVersion%3D1.0%26TimeStamp%3D2012-12-26T10%253A33%253A56Z%26Version%3D2014-05-26"
+
+	signature := CreateSignature(str, "testsecret")
+
+	t.Log(signature)
+}

+ 147 - 0
vendor/github.com/denverdino/aliyungo/util/util.go

@@ -0,0 +1,147 @@
+package util
+
+import (
+	"bytes"
+	srand "crypto/rand"
+	"encoding/binary"
+	"math/rand"
+	"net/http"
+	"net/url"
+	"sort"
+	"time"
+)
+
+const dictionary = "_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+
+//CreateRandomString create random string
+func CreateRandomString() string {
+	b := make([]byte, 32)
+	l := len(dictionary)
+
+	_, err := srand.Read(b)
+
+	if err != nil {
+		// fail back to insecure rand
+		rand.Seed(time.Now().UnixNano())
+		for i := range b {
+			b[i] = dictionary[rand.Int()%l]
+		}
+	} else {
+		for i, v := range b {
+			b[i] = dictionary[v%byte(l)]
+		}
+	}
+
+	return string(b)
+}
+
+// Encode encodes the values into ``URL encoded'' form
+// ("acl&bar=baz&foo=quux") sorted by key.
+func Encode(v url.Values) string {
+	if v == nil {
+		return ""
+	}
+	var buf bytes.Buffer
+	keys := make([]string, 0, len(v))
+	for k := range v {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		vs := v[k]
+		prefix := url.QueryEscape(k)
+		for _, v := range vs {
+			if buf.Len() > 0 {
+				buf.WriteByte('&')
+			}
+			buf.WriteString(prefix)
+			if v != "" {
+				buf.WriteString("=")
+				buf.WriteString(url.QueryEscape(v))
+			}
+		}
+	}
+	return buf.String()
+}
+
+func GetGMTime() string {
+	return time.Now().UTC().Format(http.TimeFormat)
+}
+
+//
+
+func randUint32() uint32 {
+	return randUint32Slice(1)[0]
+}
+
+func randUint32Slice(c int) []uint32 {
+	b := make([]byte, c*4)
+
+	_, err := srand.Read(b)
+
+	if err != nil {
+		// fail back to insecure rand
+		rand.Seed(time.Now().UnixNano())
+		for i := range b {
+			b[i] = byte(rand.Int())
+		}
+	}
+
+	n := make([]uint32, c)
+
+	for i := range n {
+		n[i] = binary.BigEndian.Uint32(b[i*4 : i*4+4])
+	}
+
+	return n
+}
+
+func toByte(n uint32, st, ed byte) byte {
+	return byte(n%uint32(ed-st+1) + uint32(st))
+}
+
+func toDigit(n uint32) byte {
+	return toByte(n, '0', '9')
+}
+
+func toLowerLetter(n uint32) byte {
+	return toByte(n, 'a', 'z')
+}
+
+func toUpperLetter(n uint32) byte {
+	return toByte(n, 'A', 'Z')
+}
+
+type convFunc func(uint32) byte
+
+var convFuncs = []convFunc{toDigit, toLowerLetter, toUpperLetter}
+
+// tools for generating a random ECS instance password
+// from 8 to 30 char MUST contain digit upper, case letter and upper case letter
+// http://docs.aliyun.com/#/pub/ecs/open-api/instance&createinstance
+func GenerateRandomECSPassword() string {
+
+	// [8, 30]
+	l := int(randUint32()%23 + 8)
+
+	n := randUint32Slice(l)
+
+	b := make([]byte, l)
+
+	b[0] = toDigit(n[0])
+	b[1] = toLowerLetter(n[1])
+	b[2] = toUpperLetter(n[2])
+
+	for i := 3; i < l; i++ {
+		b[i] = convFuncs[n[i]%3](n[i])
+	}
+
+	s := make([]byte, l)
+	perm := rand.Perm(l)
+	for i, v := range perm {
+		s[v] = b[i]
+	}
+
+	return string(s)
+
+}

+ 50 - 0
vendor/github.com/denverdino/aliyungo/util/util_test.go

@@ -0,0 +1,50 @@
+package util
+
+import (
+	"testing"
+)
+
+func TestCreateRandomString(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		s := CreateRandomString()
+		t.Logf("Generated Random String: %s", s)
+	}
+}
+
+func TestGenerateRandomECSPassword(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		s := GenerateRandomECSPassword()
+
+		if len(s) < 8 || len(s) > 30 {
+			t.Errorf("Generated ECS password [%v]: bad len", s)
+		}
+
+		hasDigit := false
+		hasLower := false
+		hasUpper := false
+
+		for j := range s {
+
+			switch {
+			case '0' <= s[j] && s[j] <= '9':
+				hasDigit = true
+			case 'a' <= s[j] && s[j] <= 'z':
+				hasLower = true
+			case 'A' <= s[j] && s[j] <= 'Z':
+				hasUpper = true
+			}
+		}
+
+		if !hasDigit {
+			t.Errorf("Generated ECS password [%v]: no digit", s)
+		}
+
+		if !hasLower {
+			t.Errorf("Generated ECS password [%v]: no lower letter ", s)
+		}
+
+		if !hasUpper {
+			t.Errorf("Generated ECS password [%v]: no upper letter", s)
+		}
+	}
+}