yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aliyun/instance.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aliyun
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    27  	"yunion.io/x/pkg/util/osprofile"
    28  	"yunion.io/x/pkg/util/seclib"
    29  	"yunion.io/x/pkg/utils"
    30  
    31  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    32  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    33  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    34  	"yunion.io/x/cloudmux/pkg/multicloud"
    35  	"yunion.io/x/onecloud/pkg/util/billing"
    36  	"yunion.io/x/onecloud/pkg/util/imagetools"
    37  )
    38  
    39  const (
    40  	// Running:运行中
    41  	//Starting:启动中
    42  	//Stopping:停止中
    43  	//Stopped:已停止
    44  
    45  	InstanceStatusStopped  = "Stopped"
    46  	InstanceStatusRunning  = "Running"
    47  	InstanceStatusStopping = "Stopping"
    48  	InstanceStatusStarting = "Starting"
    49  )
    50  
    51  type SDedicatedHostAttribute struct {
    52  	DedicatedHostId   string
    53  	DedicatedHostName string
    54  }
    55  
    56  type SIpAddress struct {
    57  	IpAddress []string
    58  }
    59  
    60  type SNetworkInterfaces struct {
    61  	NetworkInterface []SNetworkInterface
    62  }
    63  
    64  type SOperationLocks struct {
    65  	LockReason []string
    66  }
    67  
    68  type SSecurityGroupIds struct {
    69  	SecurityGroupId []string
    70  }
    71  
    72  // {"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"}
    73  
    74  type SVpcAttributes struct {
    75  	NatIpAddress     string
    76  	PrivateIpAddress SIpAddress
    77  	VSwitchId        string
    78  	VpcId            string
    79  }
    80  
    81  type SInstance struct {
    82  	multicloud.SInstanceBase
    83  	AliyunTags
    84  
    85  	host *SHost
    86  
    87  	osInfo *imagetools.ImageInfo
    88  
    89  	// idisks []cloudprovider.ICloudDisk
    90  
    91  	AutoReleaseTime         string
    92  	ClusterId               string
    93  	Cpu                     int
    94  	CreationTime            time.Time
    95  	DedicatedHostAttribute  SDedicatedHostAttribute
    96  	Description             string
    97  	DeviceAvailable         bool
    98  	EipAddress              SEipAddress
    99  	ExpiredTime             time.Time
   100  	GPUAmount               int
   101  	GPUSpec                 string
   102  	HostName                string
   103  	ImageId                 string
   104  	InnerIpAddress          SIpAddress
   105  	InstanceChargeType      TChargeType
   106  	InstanceId              string
   107  	InstanceName            string
   108  	InstanceNetworkType     string
   109  	InstanceType            string
   110  	InstanceTypeFamily      string
   111  	InternetChargeType      TInternetChargeType
   112  	InternetMaxBandwidthIn  int
   113  	InternetMaxBandwidthOut int
   114  	IoOptimized             bool
   115  	KeyPairName             string
   116  	Memory                  int
   117  	NetworkInterfaces       SNetworkInterfaces
   118  	OSName                  string
   119  	OSType                  string
   120  	OperationLocks          SOperationLocks
   121  	PublicIpAddress         SIpAddress
   122  	Recyclable              bool
   123  	RegionId                string
   124  	ResourceGroupId         string
   125  	SaleCycle               string
   126  	SecurityGroupIds        SSecurityGroupIds
   127  	SerialNumber            string
   128  	SpotPriceLimit          string
   129  	SpotStrategy            string
   130  	StartTime               time.Time
   131  	Status                  string
   132  	StoppedMode             string
   133  	VlanId                  string
   134  	VpcAttributes           SVpcAttributes
   135  	ZoneId                  string
   136  	Throughput              int
   137  }
   138  
   139  // {"AutoReleaseTime":"","ClusterId":"","Cpu":1,"CreationTime":"2018-05-23T07:58Z","DedicatedHostAttribute":{"DedicatedHostId":"","DedicatedHostName":""},"Description":"","DeviceAvailable":true,"EipAddress":{"AllocationId":"","InternetChargeType":"","IpAddress":""},"ExpiredTime":"2018-05-30T16:00Z","GPUAmount":0,"GPUSpec":"","HostName":"iZ2ze57isp1ali72tzkjowZ","ImageId":"centos_7_04_64_20G_alibase_201701015.vhd","InnerIpAddress":{"IpAddress":[]},"InstanceChargeType":"PrePaid","InstanceId":"i-2ze57isp1ali72tzkjow","InstanceName":"gaoxianqi-test-7days","InstanceNetworkType":"vpc","InstanceType":"ecs.t5-lc2m1.nano","InstanceTypeFamily":"ecs.t5","InternetChargeType":"PayByBandwidth","InternetMaxBandwidthIn":-1,"InternetMaxBandwidthOut":0,"IoOptimized":true,"Memory":512,"NetworkInterfaces":{"NetworkInterface":[{"MacAddress":"00:16:3e:10:f0:c9","NetworkInterfaceId":"eni-2zecqsagtpztl6x5hu2r","PrimaryIpAddress":"192.168.220.214"}]},"OSName":"CentOS  7.4 64位","OSType":"linux","OperationLocks":{"LockReason":[]},"PublicIpAddress":{"IpAddress":[]},"Recyclable":false,"RegionId":"cn-beijing","ResourceGroupId":"","SaleCycle":"Week","SecurityGroupIds":{"SecurityGroupId":["sg-2zecqsagtpztl6x9zynl"]},"SerialNumber":"df05d9b4-df3d-4400-88d1-5f843f0dd088","SpotPriceLimit":0.000000,"SpotStrategy":"NoSpot","StartTime":"2018-05-23T07:58Z","Status":"Running","StoppedMode":"Not-applicable","VlanId":"","VpcAttributes":{"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"},"ZoneId":"cn-beijing-f"}
   140  
   141  func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) {
   142  	if limit > 50 || limit <= 0 {
   143  		limit = 50
   144  	}
   145  	params := make(map[string]string)
   146  	params["RegionId"] = self.RegionId
   147  	params["PageSize"] = fmt.Sprintf("%d", limit)
   148  	params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1)
   149  
   150  	if len(zoneId) > 0 {
   151  		params["ZoneId"] = zoneId
   152  	}
   153  
   154  	if ids != nil && len(ids) > 0 {
   155  		params["InstanceIds"] = jsonutils.Marshal(ids).String()
   156  	}
   157  
   158  	body, err := self.ecsRequest("DescribeInstances", params)
   159  	if err != nil {
   160  		log.Errorf("GetInstances fail %s", err)
   161  		return nil, 0, err
   162  	}
   163  
   164  	instances := make([]SInstance, 0)
   165  	err = body.Unmarshal(&instances, "Instances", "Instance")
   166  	if err != nil {
   167  		log.Errorf("Unmarshal security group details fail %s", err)
   168  		return nil, 0, err
   169  	}
   170  	total, _ := body.Int("TotalCount")
   171  	return instances, int(total), nil
   172  }
   173  
   174  func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
   175  	return self.SecurityGroupIds.SecurityGroupId, nil
   176  }
   177  
   178  func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
   179  	return self.host
   180  }
   181  
   182  func (self *SInstance) GetId() string {
   183  	return self.InstanceId
   184  }
   185  
   186  func (self *SInstance) GetName() string {
   187  	if len(self.InstanceName) > 0 {
   188  		return self.InstanceName
   189  	}
   190  	return self.InstanceId
   191  }
   192  
   193  func (self *SInstance) GetHostname() string {
   194  	return self.HostName
   195  }
   196  
   197  func (self *SInstance) GetGlobalId() string {
   198  	return self.InstanceId
   199  }
   200  
   201  func (self *SInstance) IsEmulated() bool {
   202  	return false
   203  }
   204  
   205  func (self *SInstance) GetInstanceType() string {
   206  	return self.InstanceType
   207  }
   208  
   209  func (self *SInstance) getVpc() (*SVpc, error) {
   210  	return self.host.zone.region.getVpc(self.VpcAttributes.VpcId)
   211  }
   212  
   213  type byAttachedTime []SDisk
   214  
   215  func (a byAttachedTime) Len() int      { return len(a) }
   216  func (a byAttachedTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   217  func (a byAttachedTime) Less(i, j int) bool {
   218  	switch a[i].GetDiskType() {
   219  	case api.DISK_TYPE_SYS:
   220  		return true
   221  	case api.DISK_TYPE_SWAP:
   222  		switch a[j].GetDiskType() {
   223  		case api.DISK_TYPE_SYS:
   224  			return false
   225  		case api.DISK_TYPE_DATA:
   226  			return true
   227  		}
   228  	case api.DISK_TYPE_DATA:
   229  		if a[j].GetDiskType() != api.DISK_TYPE_DATA {
   230  			return false
   231  		}
   232  	}
   233  	return a[i].AttachedTime.Before(a[j].AttachedTime)
   234  }
   235  
   236  func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   237  	disks := []SDisk{}
   238  	for {
   239  		part, total, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, len(disks), 50)
   240  		if err != nil {
   241  			return nil, errors.Wrapf(err, "GetDisks for %s", self.InstanceId)
   242  		}
   243  		disks = append(disks, part...)
   244  		if len(disks) >= total {
   245  			break
   246  		}
   247  	}
   248  
   249  	sort.Sort(byAttachedTime(disks))
   250  
   251  	log.Debugf("%s", jsonutils.Marshal(&disks))
   252  
   253  	idisks := make([]cloudprovider.ICloudDisk, len(disks))
   254  	for i := 0; i < len(disks); i += 1 {
   255  		store, err := self.host.zone.getStorageByCategory(disks[i].Category)
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  		disks[i].storage = store
   260  		idisks[i] = &disks[i]
   261  	}
   262  	return idisks, nil
   263  }
   264  
   265  func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   266  	var (
   267  		networkInterfaces = self.NetworkInterfaces.NetworkInterface
   268  		nics              []cloudprovider.ICloudNic
   269  	)
   270  	for _, networkInterface := range networkInterfaces {
   271  		nic := SInstanceNic{
   272  			instance: self,
   273  			id:       networkInterface.NetworkInterfaceId,
   274  			ipAddr:   networkInterface.PrimaryIpAddress,
   275  			macAddr:  networkInterface.MacAddress,
   276  		}
   277  		nics = append(nics, &nic)
   278  	}
   279  	for _, classicIp := range self.InnerIpAddress.IpAddress {
   280  		nic := SInstanceNic{
   281  			instance: self,
   282  			id:       fmt.Sprintf("%s-%s", self.InstanceId, classicIp),
   283  			ipAddr:   classicIp,
   284  			classic:  true,
   285  		}
   286  		nics = append(nics, &nic)
   287  	}
   288  	return nics, nil
   289  }
   290  
   291  func (self *SInstance) GetVcpuCount() int {
   292  	return self.Cpu
   293  }
   294  
   295  func (self *SInstance) GetVmemSizeMB() int {
   296  	return self.Memory
   297  }
   298  
   299  func (self *SInstance) GetBootOrder() string {
   300  	return "dcn"
   301  }
   302  
   303  func (self *SInstance) GetVga() string {
   304  	return "std"
   305  }
   306  
   307  func (self *SInstance) GetVdi() string {
   308  	return "vnc"
   309  }
   310  
   311  func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
   312  	if ins.osInfo == nil {
   313  		osInfo := imagetools.NormalizeImageInfo(ins.OSName, "", ins.OSType, "", "")
   314  		ins.osInfo = &osInfo
   315  	}
   316  	return ins.osInfo
   317  }
   318  
   319  func (self *SInstance) GetOsType() cloudprovider.TOsType {
   320  	return cloudprovider.TOsType(osprofile.NormalizeOSType(self.OSType))
   321  }
   322  
   323  func (self *SInstance) GetFullOsName() string {
   324  	return self.OSName
   325  }
   326  
   327  func (ins *SInstance) GetBios() cloudprovider.TBiosType {
   328  	return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios)
   329  }
   330  
   331  func (ins *SInstance) GetOsArch() string {
   332  	return ins.getNormalizedOsInfo().OsArch
   333  }
   334  
   335  func (ins *SInstance) GetOsDist() string {
   336  	return ins.getNormalizedOsInfo().OsDistro
   337  }
   338  
   339  func (ins *SInstance) GetOsVersion() string {
   340  	return ins.getNormalizedOsInfo().OsVersion
   341  }
   342  
   343  func (ins *SInstance) GetOsLang() string {
   344  	return ins.getNormalizedOsInfo().OsLang
   345  }
   346  
   347  func (self *SInstance) GetMachine() string {
   348  	return "pc"
   349  }
   350  
   351  func (self *SInstance) GetStatus() string {
   352  	// Running:运行中
   353  	//Starting:启动中
   354  	//Stopping:停止中
   355  	//Stopped:已停止
   356  	switch self.Status {
   357  	case InstanceStatusRunning:
   358  		return api.VM_RUNNING
   359  	case InstanceStatusStarting:
   360  		return api.VM_STARTING
   361  	case InstanceStatusStopping:
   362  		return api.VM_STOPPING
   363  	case InstanceStatusStopped:
   364  		return api.VM_READY
   365  	default:
   366  		return api.VM_UNKNOWN
   367  	}
   368  }
   369  
   370  func (self *SInstance) Refresh() error {
   371  	ins, err := self.host.zone.region.GetInstance(self.InstanceId)
   372  	if err != nil {
   373  		return err
   374  	}
   375  	return jsonutils.Update(self, ins)
   376  }
   377  
   378  /*
   379  func (self *SInstance) GetRemoteStatus() string {
   380  	// Running:运行中
   381  	//Starting:启动中
   382  	//Stopping:停止中
   383  	//Stopped:已停止
   384  	switch self.Status {
   385  	case InstanceStatusRunning:
   386  		return cloudprovider.CloudVMStatusRunning
   387  	case InstanceStatusStarting:
   388  		return cloudprovider.CloudVMStatusStopped
   389  	case InstanceStatusStopping:
   390  		return cloudprovider.CloudVMStatusRunning
   391  	case InstanceStatusStopped:
   392  		return cloudprovider.CloudVMStatusStopped
   393  	default:
   394  		return cloudprovider.CloudVMStatusOther
   395  	}
   396  }
   397  */
   398  
   399  func (self *SInstance) GetHypervisor() string {
   400  	return api.HYPERVISOR_ALIYUN
   401  }
   402  
   403  func (self *SInstance) StartVM(ctx context.Context) error {
   404  	timeout := 300 * time.Second
   405  	interval := 15 * time.Second
   406  
   407  	startTime := time.Now()
   408  	for time.Now().Sub(startTime) < timeout {
   409  		err := self.Refresh()
   410  		if err != nil {
   411  			return err
   412  		}
   413  		log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING)
   414  		if self.GetStatus() == api.VM_RUNNING {
   415  			return nil
   416  		} else if self.GetStatus() == api.VM_READY {
   417  			err := self.host.zone.region.StartVM(self.InstanceId)
   418  			if err != nil {
   419  				return err
   420  			}
   421  		}
   422  		time.Sleep(interval)
   423  	}
   424  	return cloudprovider.ErrTimeout
   425  }
   426  
   427  func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   428  	err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce, opts.StopCharging)
   429  	if err != nil {
   430  		return err
   431  	}
   432  	return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues
   433  }
   434  
   435  func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   436  	url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId)
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	passwd := seclib.RandomPassword(6)
   441  	err = self.host.zone.region.ModifyInstanceVNCUrlPassword(self.InstanceId, passwd)
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  	ret := &cloudprovider.ServerVncOutput{
   446  		Url:        url,
   447  		Password:   passwd,
   448  		Protocol:   "aliyun",
   449  		InstanceId: self.InstanceId,
   450  		Hypervisor: api.HYPERVISOR_ALIYUN,
   451  	}
   452  	return ret, nil
   453  }
   454  
   455  func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
   456  	return self.host.zone.region.UpdateVM(self.InstanceId, name, self.OSType)
   457  }
   458  
   459  func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   460  	var keypairName string
   461  	if len(publicKey) > 0 {
   462  		var err error
   463  		keypairName, err = self.host.zone.region.syncKeypair(publicKey)
   464  		if err != nil {
   465  			return err
   466  		}
   467  	}
   468  
   469  	return self.host.zone.region.DeployVM(self.InstanceId, name, password, keypairName, deleteKeypair, description)
   470  }
   471  
   472  func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   473  	keypair := ""
   474  	if len(desc.PublicKey) > 0 {
   475  		var err error
   476  		keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey)
   477  		if err != nil {
   478  			return "", err
   479  		}
   480  	}
   481  	diskId, err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB)
   482  	if err != nil {
   483  		return "", err
   484  	}
   485  
   486  	return diskId, nil
   487  }
   488  
   489  func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   490  	isDowngrade, isPrepaid := false, self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID
   491  	if (self.GetVcpuCount() > config.Cpu && config.Cpu > 0) || (self.GetVmemSizeMB() > config.MemoryMB && config.MemoryMB > 0) {
   492  		isDowngrade = true
   493  	}
   494  
   495  	instanceTypes := []string{}
   496  
   497  	if len(config.InstanceType) > 0 {
   498  		instanceTypes = []string{config.InstanceType}
   499  	} else {
   500  		specs, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, 0, self.ZoneId)
   501  		if err != nil {
   502  			return errors.Wrapf(err, "GetMatchInstanceTypes")
   503  		}
   504  		for _, spec := range specs {
   505  			instanceTypes = append(instanceTypes, spec.InstanceTypeId)
   506  		}
   507  	}
   508  
   509  	var err error
   510  	for _, instanceType := range instanceTypes {
   511  		if isPrepaid {
   512  			err = self.host.zone.region.ChangePrepaidVMConfig(self.InstanceId, instanceType, isDowngrade)
   513  			if err != nil {
   514  				log.Errorf("ChangePrepaidVMConfig %s error: %v", instanceType, err)
   515  				continue
   516  			}
   517  		} else {
   518  			err = self.host.zone.region.ChangeVMConfig(self.InstanceId, instanceType)
   519  			if err != nil {
   520  				log.Errorf("ChangeVMConfig %s error: %v", instanceType, err)
   521  				continue
   522  			}
   523  		}
   524  		return nil
   525  	}
   526  	if err != nil {
   527  		return errors.Wrapf(err, "ChangeVMConfig")
   528  	}
   529  
   530  	return fmt.Errorf("Failed to change vm config, specification not supported")
   531  }
   532  
   533  func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   534  	return self.host.zone.region.AttachDisk(self.InstanceId, diskId)
   535  }
   536  
   537  func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   538  	return cloudprovider.RetryOnError(
   539  		func() error {
   540  			return self.host.zone.region.DetachDisk(self.InstanceId, diskId)
   541  		},
   542  		[]string{
   543  			`"Code":"InvalidOperation.Conflict"`,
   544  		},
   545  		4)
   546  }
   547  
   548  func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
   549  	instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	if len(instances) == 0 {
   554  		return nil, cloudprovider.ErrNotFound
   555  	}
   556  	return &instances[0], nil
   557  }
   558  
   559  func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupId string,
   560  	zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string,
   561  	keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string,
   562  	tags map[string]string, publicIp cloudprovider.SPublicIpInfo,
   563  ) (string, error) {
   564  	params := make(map[string]string)
   565  	params["RegionId"] = self.RegionId
   566  	params["ImageId"] = imageId
   567  	params["InstanceType"] = instanceType
   568  	params["SecurityGroupId"] = securityGroupId
   569  	params["ZoneId"] = zoneId
   570  	params["InstanceName"] = name
   571  	if len(hostname) > 0 {
   572  		params["HostName"] = hostname
   573  	}
   574  	params["Description"] = desc
   575  	params["InternetChargeType"] = "PayByTraffic"
   576  	params["InternetMaxBandwidthIn"] = "200"
   577  	params["InternetMaxBandwidthOut"] = "100"
   578  	if publicIp.PublicIpBw > 0 {
   579  		params["InternetMaxBandwidthOut"] = fmt.Sprintf("%d", publicIp.PublicIpBw)
   580  	}
   581  	if publicIp.PublicIpChargeType == cloudprovider.ElasticipChargeTypeByBandwidth {
   582  		params["InternetChargeType"] = "PayByBandwidth"
   583  	}
   584  	if len(passwd) > 0 {
   585  		params["Password"] = passwd
   586  	} else {
   587  		params["PasswordInherit"] = "True"
   588  	}
   589  
   590  	if len(projectId) > 0 {
   591  		params["ResourceGroupId"] = projectId
   592  	}
   593  	//{"Code":"InvalidSystemDiskCategory.ValueNotSupported","HostId":"ecs.aliyuncs.com","Message":"The specified parameter 'SystemDisk.Category' is not support IoOptimized Instance. Valid Values: cloud_efficiency;cloud_ssd. ","RequestId":"9C9A4E99-5196-42A2-80B6-4762F8F75C90"}
   594  	params["IoOptimized"] = "optimized"
   595  	for i, d := range disks {
   596  		if i == 0 {
   597  			params["SystemDisk.Category"] = d.Category
   598  			if d.Category == api.STORAGE_CLOUD_ESSD_PL2 {
   599  				params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
   600  				params["SystemDisk.PerformanceLevel"] = "PL2"
   601  			}
   602  			if d.Category == api.STORAGE_CLOUD_ESSD_PL3 {
   603  				params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
   604  				params["SystemDisk.PerformanceLevel"] = "PL3"
   605  			}
   606  			params["SystemDisk.Size"] = fmt.Sprintf("%d", d.Size)
   607  			params["SystemDisk.DiskName"] = d.GetName()
   608  			params["SystemDisk.Description"] = d.Description
   609  		} else {
   610  			params[fmt.Sprintf("DataDisk.%d.Size", i)] = fmt.Sprintf("%d", d.Size)
   611  			params[fmt.Sprintf("DataDisk.%d.Category", i)] = d.Category
   612  			if d.Category == api.STORAGE_CLOUD_ESSD_PL2 {
   613  				params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
   614  				params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL2"
   615  			}
   616  			if d.Category == api.STORAGE_CLOUD_ESSD_PL3 {
   617  				params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
   618  				params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL3"
   619  			}
   620  			params[fmt.Sprintf("DataDisk.%d.DiskName", i)] = d.GetName()
   621  			params[fmt.Sprintf("DataDisk.%d.Description", i)] = d.Description
   622  			params[fmt.Sprintf("DataDisk.%d.Encrypted", i)] = "false"
   623  		}
   624  	}
   625  	params["VSwitchId"] = vSwitchId
   626  	params["PrivateIpAddress"] = ipAddr
   627  
   628  	if len(keypair) > 0 {
   629  		params["KeyPairName"] = keypair
   630  	}
   631  
   632  	if len(userData) > 0 {
   633  		params["UserData"] = userData
   634  	}
   635  
   636  	if len(tags) > 0 {
   637  		tagIdx := 1
   638  		for k, v := range tags {
   639  			params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k
   640  			params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v
   641  			tagIdx += 1
   642  		}
   643  	}
   644  
   645  	if bc != nil {
   646  		params["InstanceChargeType"] = "PrePaid"
   647  		err := billingCycle2Params(bc, params)
   648  		if err != nil {
   649  			return "", err
   650  		}
   651  		if bc.AutoRenew {
   652  			params["AutoRenew"] = "true"
   653  			params["AutoRenewPeriod"] = "1"
   654  		} else {
   655  			params["AutoRenew"] = "False"
   656  		}
   657  	} else {
   658  		params["InstanceChargeType"] = "PostPaid"
   659  		params["SpotStrategy"] = "NoSpot"
   660  	}
   661  
   662  	params["ClientToken"] = utils.GenRequestId(20)
   663  
   664  	body, err := self.ecsRequest("CreateInstance", params)
   665  	if err != nil {
   666  		log.Errorf("CreateInstance fail %s", err)
   667  		return "", err
   668  	}
   669  	instanceId, _ := body.GetString("InstanceId")
   670  	return instanceId, nil
   671  }
   672  
   673  func (self *SRegion) AllocatePublicIpAddress(instanceId string) (string, error) {
   674  	params := map[string]string{
   675  		"InstanceId": instanceId,
   676  	}
   677  	resp, err := self.ecsRequest("AllocatePublicIpAddress", params)
   678  	if err != nil {
   679  		return "", errors.Wrapf(err, "AllocatePublicIpAddress")
   680  	}
   681  	return resp.GetString("IpAddress")
   682  }
   683  
   684  func (self *SInstance) AllocatePublicIpAddress() (string, error) {
   685  	return self.host.zone.region.AllocatePublicIpAddress(self.InstanceId)
   686  }
   687  
   688  func (self *SRegion) doStartVM(instanceId string) error {
   689  	return self.instanceOperation(instanceId, "StartInstance", nil)
   690  }
   691  
   692  func (self *SRegion) doStopVM(instanceId string, isForce, stopCharging bool) error {
   693  	params := make(map[string]string)
   694  	if isForce {
   695  		params["ForceStop"] = "true"
   696  	} else {
   697  		params["ForceStop"] = "false"
   698  	}
   699  	params["StoppedMode"] = "KeepCharging"
   700  	if stopCharging {
   701  		params["StoppedMode"] = "StopCharging"
   702  	}
   703  	return self.instanceOperation(instanceId, "StopInstance", params)
   704  }
   705  
   706  func (self *SRegion) doDeleteVM(instanceId string) error {
   707  	params := make(map[string]string)
   708  	params["TerminateSubscription"] = "true" // terminate expired prepaid instance
   709  	params["Force"] = "true"
   710  	return self.instanceOperation(instanceId, "DeleteInstance", params)
   711  }
   712  
   713  /*func (self *SRegion) waitInstanceStatus(instanceId string, target string, interval time.Duration, timeout time.Duration) error {
   714  	startTime := time.Now()
   715  	for time.Now().Sub(startTime) < timeout {
   716  		status, err := self.GetInstanceStatus(instanceId)
   717  		if err != nil {
   718  			return err
   719  		}
   720  		if status == target {
   721  			return nil
   722  		}
   723  		time.Sleep(interval)
   724  	}
   725  	return cloudprovider.ErrTimeout
   726  }
   727  
   728  func (self *SInstance) waitStatus(target string, interval time.Duration, timeout time.Duration) error {
   729  	return self.host.zone.region.waitInstanceStatus(self.InstanceId, target, interval, timeout)
   730  }*/
   731  
   732  func (self *SRegion) StartVM(instanceId string) error {
   733  	status, err := self.GetInstanceStatus(instanceId)
   734  	if err != nil {
   735  		log.Errorf("Fail to get instance status on StartVM: %s", err)
   736  		return err
   737  	}
   738  	if status != InstanceStatusStopped {
   739  		log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped)
   740  		return cloudprovider.ErrInvalidStatus
   741  	}
   742  	return self.doStartVM(instanceId)
   743  	// if err != nil {
   744  	//	return err
   745  	// }
   746  	// return self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*5, time.Second*180) // 3 minutes to timeout
   747  }
   748  
   749  func (self *SRegion) StopVM(instanceId string, isForce, stopCharging bool) error {
   750  	status, err := self.GetInstanceStatus(instanceId)
   751  	if err != nil {
   752  		log.Errorf("Fail to get instance status on StopVM: %s", err)
   753  		return err
   754  	}
   755  	if status == InstanceStatusStopped {
   756  		return nil
   757  	}
   758  	if status != InstanceStatusRunning {
   759  		log.Errorf("StopVM: vm status is %s expect %s", status, InstanceStatusRunning)
   760  		return cloudprovider.ErrInvalidStatus
   761  	}
   762  	return self.doStopVM(instanceId, isForce, stopCharging)
   763  	// if err != nil {
   764  	//  return err
   765  	// }
   766  	// return self.waitInstanceStatus(instanceId, InstanceStatusStopped, time.Second*10, time.Second*300) // 5 minutes to timeout
   767  }
   768  
   769  func (self *SRegion) DeleteVM(instanceId string) error {
   770  	status, err := self.GetInstanceStatus(instanceId)
   771  	if err != nil {
   772  		log.Errorf("Fail to get instance status on DeleteVM: %s", err)
   773  		return err
   774  	}
   775  	log.Debugf("Instance status on delete is %s", status)
   776  	if status != InstanceStatusStopped {
   777  		log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped)
   778  	}
   779  	return self.doDeleteVM(instanceId)
   780  	// if err != nil {
   781  	// 	return err
   782  	// }
   783  	// err = self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*10, time.Second*300) // 5 minutes to timeout
   784  	// if err == cloudprovider.ErrNotFound {
   785  	// 	return nil
   786  	// } else if err == nil {
   787  	// 	return cloudprovider.ErrTimeout
   788  	// } else {
   789  	// 	return err
   790  	// }
   791  }
   792  
   793  func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error {
   794  	instance, err := self.GetInstance(instanceId)
   795  	if err != nil {
   796  		return err
   797  	}
   798  
   799  	// 修改密钥时直接返回
   800  	if deleteKeypair {
   801  		err = self.DetachKeyPair(instanceId, instance.KeyPairName)
   802  		if err != nil {
   803  			return err
   804  		}
   805  	}
   806  
   807  	if len(keypairName) > 0 {
   808  		err = self.AttachKeypair(instanceId, keypairName)
   809  		if err != nil {
   810  			return err
   811  		}
   812  	}
   813  
   814  	params := make(map[string]string)
   815  
   816  	// if resetPassword {
   817  	//	params["Password"] = seclib2.RandomPassword2(12)
   818  	// }
   819  	// 指定密码的情况下,使用指定的密码
   820  	if len(password) > 0 {
   821  		params["Password"] = password
   822  	}
   823  
   824  	if len(name) > 0 && instance.InstanceName != name {
   825  		params["InstanceName"] = name
   826  	}
   827  
   828  	if len(description) > 0 && instance.Description != description {
   829  		params["Description"] = description
   830  	}
   831  
   832  	if len(params) > 0 {
   833  		return self.modifyInstanceAttribute(instanceId, params)
   834  	} else {
   835  		return nil
   836  	}
   837  }
   838  
   839  func (self *SInstance) DeleteVM(ctx context.Context) error {
   840  	for {
   841  		err := self.host.zone.region.DeleteVM(self.InstanceId)
   842  		if err != nil {
   843  			if isError(err, "IncorrectInstanceStatus.Initializing") {
   844  				log.Infof("The instance is initializing, try later ...")
   845  				time.Sleep(10 * time.Second)
   846  			} else {
   847  				log.Errorf("DeleteVM fail: %s", err)
   848  				return err
   849  			}
   850  		} else {
   851  			break
   852  		}
   853  	}
   854  	return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
   855  }
   856  
   857  func (self *SRegion) UpdateVM(instanceId string, name, osType string) error {
   858  	/*
   859  			api: ModifyInstanceAttribute
   860  		    https://help.aliyun.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW
   861  	*/
   862  	params := make(map[string]string)
   863  	params["InstanceName"] = name
   864  	return self.modifyInstanceAttribute(instanceId, params)
   865  }
   866  
   867  func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error {
   868  	return self.instanceOperation(instanceId, "ModifyInstanceAttribute", params)
   869  }
   870  
   871  func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) (string, error) {
   872  	params := make(map[string]string)
   873  	params["RegionId"] = self.RegionId
   874  	params["InstanceId"] = instanceId
   875  	params["ImageId"] = imageId
   876  	if len(passwd) > 0 {
   877  		params["Password"] = passwd
   878  	} else {
   879  		params["PasswordInherit"] = "True"
   880  	}
   881  	if len(keypairName) > 0 {
   882  		params["KeyPairName"] = keypairName
   883  	}
   884  	if sysDiskSizeGB > 0 {
   885  		params["SystemDisk.Size"] = fmt.Sprintf("%d", sysDiskSizeGB)
   886  	}
   887  	body, err := self.ecsRequest("ReplaceSystemDisk", params)
   888  	if err != nil {
   889  		return "", err
   890  	}
   891  	// log.Debugf("%s", body.String())
   892  	return body.GetString("DiskId")
   893  }
   894  
   895  func (self *SRegion) ChangePrepaidVMConfig(instanceId string, instanceType string, isDowngrade bool) error {
   896  	// todo: support change disk config?
   897  	params := make(map[string]string)
   898  	params["InstanceType"] = instanceType
   899  	params["ClientToken"] = utils.GenRequestId(20)
   900  	if isDowngrade {
   901  		params["OperatorType"] = "downgrade"
   902  	}
   903  	err := self.instanceOperation(instanceId, "ModifyPrepayInstanceSpec", params)
   904  	if err != nil {
   905  		return errors.Wrapf(err, "ModifyPrepayInstanceSpec %s", instanceType)
   906  	}
   907  	return nil
   908  }
   909  
   910  func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
   911  	// todo: support change disk config?
   912  	params := make(map[string]string)
   913  	params["InstanceType"] = instanceType
   914  	params["ClientToken"] = utils.GenRequestId(20)
   915  	err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params)
   916  	if err != nil {
   917  		return errors.Wrapf(err, "ModifyInstanceSpec %s", instanceType)
   918  	}
   919  	return nil
   920  }
   921  
   922  func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
   923  	params := make(map[string]string)
   924  	params["InstanceId"] = instanceId
   925  	params["DiskId"] = diskId
   926  	log.Infof("Detach instance %s disk %s", instanceId, diskId)
   927  	_, err := self.ecsRequest("DetachDisk", params)
   928  	if err != nil {
   929  		if strings.Contains(err.Error(), "The specified disk has not been attached on the specified instance") {
   930  			return nil
   931  		}
   932  		return errors.Wrap(err, "DetachDisk")
   933  	}
   934  
   935  	return nil
   936  }
   937  
   938  func (self *SRegion) AttachDisk(instanceId string, diskId string) error {
   939  	params := make(map[string]string)
   940  	params["InstanceId"] = instanceId
   941  	params["DiskId"] = diskId
   942  	_, err := self.ecsRequest("AttachDisk", params)
   943  	if err != nil {
   944  		log.Errorf("AttachDisk %s to %s fail %s", diskId, instanceId, err)
   945  		return err
   946  	}
   947  
   948  	return nil
   949  }
   950  
   951  func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   952  	if len(self.EipAddress.IpAddress) > 0 {
   953  		return self.host.zone.region.GetEip(self.EipAddress.AllocationId)
   954  	}
   955  	if len(self.PublicIpAddress.IpAddress) > 0 {
   956  		eip := SEipAddress{}
   957  		eip.region = self.host.zone.region
   958  		eip.IpAddress = self.PublicIpAddress.IpAddress[0]
   959  		eip.InstanceId = self.InstanceId
   960  		eip.InstanceType = EIP_INSTANCE_TYPE_ECS
   961  		eip.Status = EIP_STATUS_INUSE
   962  		eip.AllocationId = self.InstanceId // fixed
   963  		eip.AllocationTime = self.CreationTime
   964  		eip.Bandwidth = self.InternetMaxBandwidthOut
   965  		eip.ResourceGroupId = self.ResourceGroupId
   966  		eip.InternetChargeType = self.InternetChargeType
   967  		return &eip, nil
   968  	}
   969  	return nil, nil
   970  }
   971  
   972  func (self *SInstance) AssignSecurityGroup(secgroupId string) error {
   973  	return self.host.zone.region.AssignSecurityGroup(secgroupId, self.InstanceId)
   974  }
   975  
   976  func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
   977  	return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId)
   978  }
   979  
   980  func (self *SInstance) GetBillingType() string {
   981  	return convertChargeType(self.InstanceChargeType)
   982  }
   983  
   984  func (self *SInstance) GetCreatedAt() time.Time {
   985  	return self.CreationTime
   986  }
   987  
   988  func (self *SInstance) GetExpiredAt() time.Time {
   989  	return convertExpiredAt(self.ExpiredTime)
   990  }
   991  
   992  func (self *SInstance) UpdateUserData(userData string) error {
   993  	return self.host.zone.region.updateInstance(self.InstanceId, "", "", "", "", userData)
   994  }
   995  
   996  func (self *SInstance) Renew(bc billing.SBillingCycle) error {
   997  	return self.host.zone.region.RenewInstance(self.InstanceId, bc)
   998  }
   999  
  1000  func (self *SInstance) GetThroughput() int {
  1001  	return self.Throughput
  1002  }
  1003  
  1004  func (self *SInstance) GetInternetMaxBandwidthOut() int {
  1005  	return self.InternetMaxBandwidthOut
  1006  }
  1007  
  1008  func billingCycle2Params(bc *billing.SBillingCycle, params map[string]string) error {
  1009  	if bc.GetMonths() > 0 {
  1010  		params["PeriodUnit"] = "Month"
  1011  		params["Period"] = fmt.Sprintf("%d", bc.GetMonths())
  1012  	} else if bc.GetWeeks() > 0 {
  1013  		params["PeriodUnit"] = "Week"
  1014  		params["Period"] = fmt.Sprintf("%d", bc.GetWeeks())
  1015  	} else {
  1016  		return fmt.Errorf("invalid renew time period %s", bc.String())
  1017  	}
  1018  	return nil
  1019  }
  1020  
  1021  func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error {
  1022  	params := make(map[string]string)
  1023  	params["InstanceId"] = instanceId
  1024  	err := billingCycle2Params(&bc, params)
  1025  	if err != nil {
  1026  		return err
  1027  	}
  1028  	params["ClientToken"] = utils.GenRequestId(20)
  1029  	_, err = region.ecsRequest("RenewInstance", params)
  1030  	if err != nil {
  1031  		log.Errorf("RenewInstance fail %s", err)
  1032  		return err
  1033  	}
  1034  	return nil
  1035  }
  1036  
  1037  func (self *SInstance) GetProjectId() string {
  1038  	return self.ResourceGroupId
  1039  }
  1040  
  1041  func (self *SInstance) GetError() error {
  1042  	return nil
  1043  }
  1044  
  1045  func (region *SRegion) ConvertPublicIpToEip(instanceId string) error {
  1046  	params := make(map[string]string)
  1047  	params["InstanceId"] = instanceId
  1048  	params["RegionId"] = region.RegionId
  1049  	_, err := region.ecsRequest("ConvertNatPublicIpToEip", params)
  1050  	return err
  1051  }
  1052  
  1053  func (self *SInstance) ConvertPublicIpToEip() error {
  1054  	err := self.host.zone.region.ConvertPublicIpToEip(self.InstanceId)
  1055  	if err != nil {
  1056  		return errors.Wrapf(err, "ConvertPublicIpToEip")
  1057  	}
  1058  	return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
  1059  		self.Refresh()
  1060  		iEip, err := self.GetIEIP()
  1061  		if err != nil {
  1062  			return false, errors.Wrapf(err, "GetIEIP")
  1063  		}
  1064  		if iEip == nil {
  1065  			return false, nil
  1066  		}
  1067  		if iEip.GetMode() == api.EIP_MODE_STANDALONE_EIP {
  1068  			return true, self.host.zone.region.VpcMoveResourceGroup("eip", self.ResourceGroupId, iEip.GetId())
  1069  		}
  1070  		return false, nil
  1071  	})
  1072  }
  1073  
  1074  func (self *SRegion) VpcMoveResourceGroup(resType, groupId, resId string) error {
  1075  	params := map[string]string{
  1076  		"RegionId":           self.RegionId,
  1077  		"ResourceType":       resType,
  1078  		"NewResourceGroupId": groupId,
  1079  		"ResourceId":         resId,
  1080  	}
  1081  	_, err := self.vpcRequest("MoveResourceGroup", params)
  1082  	return errors.Wrapf(err, "MoveResourceGroup")
  1083  }
  1084  
  1085  // https://help.aliyun.com/document_detail/52843.html
  1086  func (region *SRegion) SetInstanceAutoRenew(instanceId string, bc billing.SBillingCycle) error {
  1087  	params := make(map[string]string)
  1088  	params["InstanceId"] = instanceId
  1089  	params["RegionId"] = region.RegionId
  1090  	if bc.AutoRenew {
  1091  		params["RenewalStatus"] = "AutoRenewal"
  1092  		switch bc.Unit {
  1093  		case billing.BillingCycleYear:
  1094  			params["Duration"] = fmt.Sprintf("%d", bc.GetYears())
  1095  			params["PeriodUnit"] = "Year"
  1096  		case billing.BillingCycleMonth:
  1097  			params["Duration"] = fmt.Sprintf("%d", bc.GetMonths())
  1098  			params["PeriodUnit"] = "Month"
  1099  		case billing.BillingCycleWeek:
  1100  			params["Duration"] = fmt.Sprintf("%d", bc.GetWeeks())
  1101  			params["PeriodUnit"] = "Week"
  1102  		}
  1103  	} else {
  1104  		params["RenewalStatus"] = "Normal"
  1105  	}
  1106  	_, err := region.ecsRequest("ModifyInstanceAutoRenewAttribute", params)
  1107  	return err
  1108  }
  1109  
  1110  type SAutoRenewAttr struct {
  1111  	Duration         int
  1112  	AutoRenewEnabled bool
  1113  	RenewalStatus    string
  1114  	PeriodUnit       string
  1115  }
  1116  
  1117  func (region *SRegion) GetInstanceAutoRenewAttribute(instanceId string) (*SAutoRenewAttr, error) {
  1118  	params := make(map[string]string)
  1119  	params["InstanceId"] = instanceId
  1120  	params["RegionId"] = region.RegionId
  1121  	resp, err := region.ecsRequest("DescribeInstanceAutoRenewAttribute", params)
  1122  	if err != nil {
  1123  		return nil, errors.Wrap(err, "DescribeInstanceAutoRenewAttribute")
  1124  	}
  1125  	attr := []SAutoRenewAttr{}
  1126  	err = resp.Unmarshal(&attr, "InstanceRenewAttributes", "InstanceRenewAttribute")
  1127  	if err != nil {
  1128  		return nil, errors.Wrap(err, "resp.Unmarshal")
  1129  	}
  1130  	if len(attr) == 1 {
  1131  		return &attr[0], nil
  1132  	}
  1133  	return nil, fmt.Errorf("get %d auto renew info", len(attr))
  1134  }
  1135  
  1136  func (self *SInstance) IsAutoRenew() bool {
  1137  	attr, err := self.host.zone.region.GetInstanceAutoRenewAttribute(self.InstanceId)
  1138  	if err != nil {
  1139  		log.Errorf("failed to get instance %s auto renew info", self.InstanceId)
  1140  		return false
  1141  	}
  1142  	return attr.AutoRenewEnabled
  1143  }
  1144  
  1145  func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error {
  1146  	return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc)
  1147  }
  1148  
  1149  func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  1150  	return self.host.zone.region.SetResourceTags(ALIYUN_SERVICE_ECS, "instance", self.InstanceId, tags, replace)
  1151  }
  1152  
  1153  func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  1154  	params := map[string]string{
  1155  		"InstanceId":  instanceId,
  1156  		"ImageName":   opts.Name,
  1157  		"Description": opts.Notes,
  1158  		"ClientToken": utils.GenRequestId(20),
  1159  	}
  1160  	resp, err := self.ecsRequest("CreateImage", params)
  1161  	if err != nil {
  1162  		return nil, errors.Wrapf(err, "CreateImage")
  1163  	}
  1164  	ret := struct{ ImageId string }{}
  1165  	err = resp.Unmarshal(&ret)
  1166  	if err != nil {
  1167  		return nil, errors.Wrapf(err, "resp.Unmarshal")
  1168  	}
  1169  	image, err := self.GetImage(ret.ImageId)
  1170  	if err != nil {
  1171  		return nil, errors.Wrapf(err, "GetImage(%s)", ret.ImageId)
  1172  	}
  1173  	image.storageCache = self.getStoragecache()
  1174  	return image, nil
  1175  }
  1176  
  1177  func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1178  	image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
  1179  	if err != nil {
  1180  		return nil, errors.Wrapf(err, "SaveImage(%s)", opts.Name)
  1181  	}
  1182  	return image, nil
  1183  }