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