yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/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 hcso
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"fmt"
    21  	"sort"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"yunion.io/x/jsonutils"
    27  	"yunion.io/x/log"
    28  	"yunion.io/x/pkg/errors"
    29  	"yunion.io/x/pkg/util/osprofile"
    30  	"yunion.io/x/pkg/utils"
    31  
    32  	"yunion.io/x/cloudmux/pkg/apis"
    33  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    34  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    35  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    36  	"yunion.io/x/cloudmux/pkg/multicloud"
    37  	"yunion.io/x/cloudmux/pkg/multicloud/hcso/client/modules"
    38  	"yunion.io/x/cloudmux/pkg/multicloud/huawei"
    39  	"yunion.io/x/onecloud/pkg/util/billing"
    40  	"yunion.io/x/onecloud/pkg/util/cloudinit"
    41  	"yunion.io/x/onecloud/pkg/util/imagetools"
    42  )
    43  
    44  const (
    45  	InstanceStatusRunning    = "ACTIVE"
    46  	InstanceStatusTerminated = "DELETED"
    47  	InstanceStatusStopped    = "SHUTOFF"
    48  )
    49  
    50  type IpAddress struct {
    51  	Version            string `json:"version"`
    52  	Addr               string `json:"addr"`
    53  	OSEXTIPSMACMACAddr string `json:"OS-EXT-IPS-MAC:mac_addr"`
    54  	OSEXTIPSPortID     string `json:"OS-EXT-IPS:port_id"`
    55  	OSEXTIPSType       string `json:"OS-EXT-IPS:type"`
    56  }
    57  
    58  type Flavor struct {
    59  	Disk  string `json:"disk"`
    60  	Vcpus string `json:"vcpus"`
    61  	RAM   string `json:"ram"`
    62  	ID    string `json:"id"`
    63  	Name  string `json:"name"`
    64  }
    65  
    66  type Image struct {
    67  	ID string `json:"id"`
    68  }
    69  
    70  type VMMetadata struct {
    71  	MeteringImageID           string `json:"metering.image_id"`
    72  	MeteringImagetype         string `json:"metering.imagetype"`
    73  	MeteringResourcespeccode  string `json:"metering.resourcespeccode"`
    74  	ImageName                 string `json:"image_name"`
    75  	OSBit                     string `json:"os_bit"`
    76  	VpcID                     string `json:"vpc_id"`
    77  	MeteringResourcetype      string `json:"metering.resourcetype"`
    78  	CascadedInstanceExtrainfo string `json:"cascaded.instance_extrainfo"`
    79  	OSType                    string `json:"os_type"`
    80  	ChargingMode              string `json:"charging_mode"`
    81  }
    82  
    83  type OSExtendedVolumesVolumesAttached struct {
    84  	Device              string `json:"device"`
    85  	BootIndex           string `json:"bootIndex"`
    86  	ID                  string `json:"id"`
    87  	DeleteOnTermination string `json:"delete_on_termination"`
    88  }
    89  
    90  type OSSchedulerHints struct {
    91  }
    92  
    93  type SecurityGroup struct {
    94  	Name string `json:"name"`
    95  }
    96  
    97  type SysTag struct {
    98  	Key   string `json:"key"`
    99  	Value string `json:"value"`
   100  }
   101  
   102  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
   103  // https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0100166287.html v1.1 支持创建包年/包月的弹性云服务器
   104  type SInstance struct {
   105  	multicloud.SInstanceBase
   106  	huawei.HuaweiTags
   107  
   108  	host *SHost
   109  
   110  	osInfo *imagetools.ImageInfo
   111  
   112  	ID          string                 `json:"id"`
   113  	Name        string                 `json:"name"`
   114  	Addresses   map[string][]IpAddress `json:"addresses"`
   115  	Flavor      Flavor                 `json:"flavor"`
   116  	AccessIPv4  string                 `json:"accessIPv4"`
   117  	AccessIPv6  string                 `json:"accessIPv6"`
   118  	Status      string                 `json:"status"`
   119  	Progress    string                 `json:"progress"`
   120  	HostID      string                 `json:"hostId"`
   121  	Updated     string                 `json:"updated"`
   122  	Created     time.Time              `json:"created"`
   123  	Metadata    VMMetadata             `json:"metadata"`
   124  	Description string                 `json:"description"`
   125  	Locked      bool                   `json:"locked"`
   126  	ConfigDrive string                 `json:"config_drive"`
   127  	TenantID    string                 `json:"tenant_id"`
   128  	UserID      string                 `json:"user_id"`
   129  	KeyName     string                 `json:"key_name"`
   130  
   131  	OSExtendedVolumesVolumesAttached []OSExtendedVolumesVolumesAttached `json:"os-extended-volumes:volumes_attached"`
   132  	OSEXTSTSTaskState                string                             `json:"OS-EXT-STS:task_state"`
   133  	OSEXTSTSPowerState               int64                              `json:"OS-EXT-STS:power_state"`
   134  	OSEXTSTSVMState                  string                             `json:"OS-EXT-STS:vm_state"`
   135  	OSEXTSRVATTRHost                 string                             `json:"OS-EXT-SRV-ATTR:host"`
   136  	OSEXTSRVATTRInstanceName         string                             `json:"OS-EXT-SRV-ATTR:instance_name"`
   137  	OSEXTSRVATTRHypervisorHostname   string                             `json:"OS-EXT-SRV-ATTR:hypervisor_hostname"`
   138  	OSDCFDiskConfig                  string                             `json:"OS-DCF:diskConfig"`
   139  	OSEXTAZAvailabilityZone          string                             `json:"OS-EXT-AZ:availability_zone"`
   140  	OSSchedulerHints                 OSSchedulerHints                   `json:"os:scheduler_hints"`
   141  	OSEXTSRVATTRRootDeviceName       string                             `json:"OS-EXT-SRV-ATTR:root_device_name"`
   142  	OSEXTSRVATTRRamdiskID            string                             `json:"OS-EXT-SRV-ATTR:ramdisk_id"`
   143  	EnterpriseProjectID              string                             `json:"enterprise_project_id"`
   144  	OSEXTSRVATTRUserData             string                             `json:"OS-EXT-SRV-ATTR:user_data"`
   145  	OSSRVUSGLaunchedAt               time.Time                          `json:"OS-SRV-USG:launched_at"`
   146  	OSEXTSRVATTRKernelID             string                             `json:"OS-EXT-SRV-ATTR:kernel_id"`
   147  	OSEXTSRVATTRLaunchIndex          int64                              `json:"OS-EXT-SRV-ATTR:launch_index"`
   148  	HostStatus                       string                             `json:"host_status"`
   149  	OSEXTSRVATTRReservationID        string                             `json:"OS-EXT-SRV-ATTR:reservation_id"`
   150  	OSEXTSRVATTRHostname             string                             `json:"OS-EXT-SRV-ATTR:hostname"`
   151  	OSSRVUSGTerminatedAt             time.Time                          `json:"OS-SRV-USG:terminated_at"`
   152  	SysTags                          []SysTag                           `json:"sys_tags"`
   153  	SecurityGroups                   []SecurityGroup                    `json:"security_groups"`
   154  	EnterpriseProjectId              string
   155  }
   156  
   157  func compareSet(currentSet []string, newSet []string) (add []string, remove []string, keep []string) {
   158  	sort.Strings(currentSet)
   159  	sort.Strings(newSet)
   160  
   161  	i, j := 0, 0
   162  	for i < len(currentSet) || j < len(newSet) {
   163  		if i < len(currentSet) && j < len(newSet) {
   164  			if currentSet[i] == newSet[j] {
   165  				keep = append(keep, currentSet[i])
   166  				i += 1
   167  				j += 1
   168  			} else if currentSet[i] < newSet[j] {
   169  				remove = append(remove, currentSet[i])
   170  				i += 1
   171  			} else {
   172  				add = append(add, newSet[j])
   173  				j += 1
   174  			}
   175  		} else if i >= len(currentSet) {
   176  			add = append(add, newSet[j])
   177  			j += 1
   178  		} else if j >= len(newSet) {
   179  			remove = append(remove, currentSet[i])
   180  			i += 1
   181  		}
   182  	}
   183  
   184  	return add, remove, keep
   185  }
   186  
   187  // 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
   188  func isBootDisk(server *SInstance, disk *SDisk) bool {
   189  	if disk.GetDiskType() != api.DISK_TYPE_SYS {
   190  		return false
   191  	}
   192  
   193  	for _, attachment := range disk.Attachments {
   194  		if attachment.ServerID == server.GetId() && attachment.Device == server.OSEXTSRVATTRRootDeviceName {
   195  			return true
   196  		}
   197  	}
   198  
   199  	return false
   200  }
   201  
   202  func (self *SInstance) GetId() string {
   203  	return self.ID
   204  }
   205  
   206  func (self *SInstance) GetName() string {
   207  	return self.Name
   208  }
   209  
   210  func (self *SInstance) GetHostname() string {
   211  	return self.OSEXTSRVATTRHostname
   212  }
   213  
   214  func (self *SInstance) GetGlobalId() string {
   215  	return self.ID
   216  }
   217  
   218  func (self *SInstance) GetStatus() string {
   219  	switch self.Status {
   220  	case "ACTIVE":
   221  		return api.VM_RUNNING
   222  	case "MIGRATING", "REBUILD", "BUILD", "RESIZE", "VERIFY_RESIZE": // todo: pending ?
   223  		return api.VM_STARTING
   224  	case "REBOOT", "HARD_REBOOT":
   225  		return api.VM_STOPPING
   226  	case "SHUTOFF":
   227  		return api.VM_READY
   228  	default:
   229  		return api.VM_UNKNOWN
   230  	}
   231  }
   232  
   233  func (self *SInstance) Refresh() error {
   234  	new, err := self.host.zone.region.GetInstanceByID(self.GetId())
   235  	new.host = self.host
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	if new.Status == InstanceStatusTerminated {
   241  		log.Debugf("Instance already terminated.")
   242  		return cloudprovider.ErrNotFound
   243  	}
   244  
   245  	err = jsonutils.Update(self, new)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	return nil
   250  }
   251  
   252  func (self *SInstance) IsEmulated() bool {
   253  	return false
   254  }
   255  
   256  func (self *SInstance) GetInstanceType() string {
   257  	return self.Flavor.ID
   258  }
   259  
   260  func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
   261  	return self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId())
   262  }
   263  
   264  // https://support.huaweicloud.com/api-ecs/ecs_02_1002.html
   265  // key 相同时value不会替换
   266  func (self *SRegion) CreateServerTags(instanceId string, tags map[string]string) error {
   267  	params := map[string]interface{}{
   268  		"action": "create",
   269  	}
   270  
   271  	tagsObj := []map[string]string{}
   272  	for k, v := range tags {
   273  		tagsObj = append(tagsObj, map[string]string{"key": k, "value": v})
   274  	}
   275  	params["tags"] = tagsObj
   276  
   277  	_, err := self.ecsClient.Servers.PerformAction2("tags/action", instanceId, jsonutils.Marshal(params), "")
   278  	return err
   279  }
   280  
   281  // https://support.huaweicloud.com/api-ecs/ecs_02_1003.html
   282  func (self *SRegion) DeleteServerTags(instanceId string, tagsKey []string) error {
   283  	params := map[string]interface{}{
   284  		"action": "delete",
   285  	}
   286  	tagsObj := []map[string]string{}
   287  	for _, k := range tagsKey {
   288  		tagsObj = append(tagsObj, map[string]string{"key": k})
   289  	}
   290  	params["tags"] = tagsObj
   291  
   292  	_, err := self.ecsClient.Servers.PerformAction2("tags/action", instanceId, jsonutils.Marshal(params), "")
   293  	return err
   294  }
   295  
   296  func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
   297  	existedTags, err := self.GetTags()
   298  	if err != nil {
   299  		return errors.Wrap(err, "self.GetTags()")
   300  	}
   301  	deleteTagsKey := []string{}
   302  	for k := range existedTags {
   303  		if replace {
   304  			deleteTagsKey = append(deleteTagsKey, k)
   305  		} else {
   306  			if _, ok := tags[k]; ok {
   307  				deleteTagsKey = append(deleteTagsKey, k)
   308  			}
   309  		}
   310  	}
   311  	if len(deleteTagsKey) > 0 {
   312  		err := self.host.zone.region.DeleteServerTags(self.GetId(), deleteTagsKey)
   313  		if err != nil {
   314  			return errors.Wrapf(err, "self.host.zone.region.DeleteServerTags(%s,%s)", self.GetId(), deleteTagsKey)
   315  		}
   316  	}
   317  	if len(tags) > 0 {
   318  		err := self.host.zone.region.CreateServerTags(self.GetId(), tags)
   319  		if err != nil {
   320  			return errors.Wrapf(err, "self.host.zone.region.CreateServerTags(%s,%s)", self.GetId(), jsonutils.Marshal(tags).String())
   321  		}
   322  	}
   323  	return nil
   324  }
   325  
   326  func (self *SInstance) GetBillingType() string {
   327  	// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
   328  	// charging_mode “0”:按需计费    “1”:按包年包月计费
   329  	if self.Metadata.ChargingMode == "1" {
   330  		return billing_api.BILLING_TYPE_PREPAID
   331  	} else {
   332  		return billing_api.BILLING_TYPE_POSTPAID
   333  	}
   334  }
   335  
   336  func (self *SInstance) GetCreatedAt() time.Time {
   337  	return self.Created
   338  }
   339  
   340  // charging_mode “0”:按需计费  “1”:按包年包月计费
   341  func (self *SInstance) GetExpiredAt() time.Time {
   342  	var expiredTime time.Time
   343  	return expiredTime
   344  }
   345  
   346  func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
   347  	return self.host
   348  }
   349  
   350  func (self *SInstance) GetIHostId() string {
   351  	return self.host.GetGlobalId()
   352  }
   353  
   354  func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   355  	err := self.Refresh()
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	attached := self.OSExtendedVolumesVolumesAttached
   361  	disks := make([]SDisk, 0)
   362  	for _, vol := range attached {
   363  		disk, err := self.host.zone.region.GetDisk(vol.ID)
   364  		if err != nil {
   365  			return nil, err
   366  		}
   367  
   368  		disks = append(disks, *disk)
   369  	}
   370  
   371  	idisks := make([]cloudprovider.ICloudDisk, len(disks))
   372  	for i := 0; i < len(disks); i += 1 {
   373  		storage, err := self.host.zone.getStorageByCategory(disks[i].VolumeType)
   374  		if err != nil {
   375  			return nil, err
   376  		}
   377  		disks[i].storage = storage
   378  		idisks[i] = &disks[i]
   379  		// 将系统盘放到第0个位置
   380  		if isBootDisk(self, &disks[i]) {
   381  			_temp := idisks[0]
   382  			idisks[0] = &disks[i]
   383  			idisks[i] = _temp
   384  		}
   385  	}
   386  	return idisks, nil
   387  }
   388  
   389  func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   390  	nics := make([]cloudprovider.ICloudNic, 0)
   391  
   392  	// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
   393  	// OS-EXT-IPS.type
   394  	// todo: 这里没有区分是IPv4 还是 IPv6。统一当IPv4处理了.可能会引发错误
   395  	for _, ipAddresses := range self.Addresses {
   396  		for _, ipAddress := range ipAddresses {
   397  			if ipAddress.OSEXTIPSType == "fixed" {
   398  				nic := SInstanceNic{
   399  					instance: self,
   400  					ipAddr:   ipAddress.Addr,
   401  					macAddr:  ipAddress.OSEXTIPSMACMACAddr,
   402  				}
   403  				nics = append(nics, &nic)
   404  			}
   405  		}
   406  	}
   407  	return nics, nil
   408  }
   409  
   410  func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   411  	ips := make([]string, 0)
   412  	for _, addresses := range self.Addresses {
   413  		for _, address := range addresses {
   414  			if address.OSEXTIPSType != "fixed" && !strings.HasPrefix(address.Addr, "100.") {
   415  				ips = append(ips, address.Addr)
   416  			}
   417  		}
   418  	}
   419  
   420  	if len(ips) == 0 {
   421  		return nil, nil
   422  	}
   423  
   424  	eips, err := self.host.zone.region.GetEips()
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  
   429  	for _, eip := range eips {
   430  		if eip.PublicIPAddress == ips[0] {
   431  			return &eip, nil
   432  		}
   433  	}
   434  
   435  	return nil, nil
   436  }
   437  
   438  func (self *SInstance) GetVcpuCount() int {
   439  	cpu, _ := strconv.Atoi(self.Flavor.Vcpus)
   440  	return cpu
   441  }
   442  
   443  func (self *SInstance) GetVmemSizeMB() int {
   444  	mem, _ := strconv.Atoi(self.Flavor.RAM)
   445  	return int(mem)
   446  }
   447  
   448  func (self *SInstance) GetBootOrder() string {
   449  	return "dcn"
   450  }
   451  
   452  func (self *SInstance) GetVga() string {
   453  	return "std"
   454  }
   455  
   456  func (self *SInstance) GetVdi() string {
   457  	return "vnc"
   458  }
   459  
   460  func (self *SInstance) GetOsArch() string {
   461  	if flavor, err := self.host.zone.region.GetICloudSku(self.Flavor.ID); err == nil {
   462  		return flavor.GetCpuArch()
   463  	} else {
   464  		log.Debugf("GetOSArch.GetICloudSku %s: %s", self.Flavor.ID, err)
   465  	}
   466  
   467  	t := self.GetInstanceType()
   468  	if len(t) > 0 {
   469  		if strings.HasPrefix(t, "k") {
   470  			return apis.OS_ARCH_AARCH64
   471  		}
   472  	}
   473  
   474  	return apis.OS_ARCH_X86
   475  }
   476  
   477  func (self *SInstance) GetOsType() cloudprovider.TOsType {
   478  	return cloudprovider.TOsType(osprofile.NormalizeOSType(self.Metadata.OSType))
   479  }
   480  
   481  func (self *SInstance) GetFullOsName() string {
   482  	return self.Metadata.ImageName
   483  }
   484  
   485  func (i *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
   486  	if i.osInfo == nil {
   487  		osInfo := imagetools.NormalizeImageInfo(i.Metadata.ImageName, "", i.Metadata.OSType, "", "")
   488  		i.osInfo = &osInfo
   489  	}
   490  	return i.osInfo
   491  }
   492  
   493  func (i *SInstance) GetBios() cloudprovider.TBiosType {
   494  	return cloudprovider.ToBiosType(i.getNormalizedOsInfo().OsBios)
   495  }
   496  
   497  func (i *SInstance) GetOsDist() string {
   498  	return i.getNormalizedOsInfo().OsDistro
   499  }
   500  
   501  func (i *SInstance) GetOsVersion() string {
   502  	return i.getNormalizedOsInfo().OsVersion
   503  }
   504  
   505  func (i *SInstance) GetOsLang() string {
   506  	return i.getNormalizedOsInfo().OsLang
   507  }
   508  
   509  func (self *SInstance) GetMachine() string {
   510  	return "pc"
   511  }
   512  
   513  func (self *SInstance) AssignSecurityGroup(secgroupId string) error {
   514  	return self.SetSecurityGroups([]string{secgroupId})
   515  }
   516  
   517  func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
   518  	currentSecgroups, err := self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId())
   519  	if err != nil {
   520  		return err
   521  	}
   522  
   523  	add, remove, _ := compareSet(currentSecgroups, secgroupIds)
   524  	err = self.host.zone.region.assignSecurityGroups(add, self.GetId())
   525  	if err != nil {
   526  		return err
   527  	}
   528  
   529  	return self.host.zone.region.unassignSecurityGroups(remove, self.GetId())
   530  }
   531  
   532  func (self *SInstance) GetHypervisor() string {
   533  	return api.HYPERVISOR_HCSO
   534  }
   535  
   536  func (self *SInstance) StartVM(ctx context.Context) error {
   537  	if self.Status == InstanceStatusRunning {
   538  		return nil
   539  	}
   540  
   541  	timeout := 300 * time.Second
   542  	interval := 15 * time.Second
   543  
   544  	startTime := time.Now()
   545  	for time.Now().Sub(startTime) < timeout {
   546  		err := self.Refresh()
   547  		if err != nil {
   548  			return err
   549  		}
   550  
   551  		if self.GetStatus() == api.VM_RUNNING {
   552  			return nil
   553  		} else if self.GetStatus() == api.VM_READY {
   554  			err := self.host.zone.region.StartVM(self.GetId())
   555  			if err != nil {
   556  				return err
   557  			}
   558  		}
   559  		time.Sleep(interval)
   560  	}
   561  	return cloudprovider.ErrTimeout
   562  }
   563  
   564  func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   565  	if self.Status == InstanceStatusStopped {
   566  		return nil
   567  	}
   568  
   569  	if self.Status == InstanceStatusTerminated {
   570  		log.Debugf("Instance already terminated.")
   571  		return nil
   572  	}
   573  
   574  	err := self.host.zone.region.StopVM(self.GetId(), opts.IsForce)
   575  	if err != nil {
   576  		return err
   577  	}
   578  	return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues
   579  }
   580  
   581  func (self *SInstance) DeleteVM(ctx context.Context) error {
   582  	if self.Status == InstanceStatusTerminated {
   583  		return nil
   584  	}
   585  
   586  	for {
   587  		err := self.host.zone.region.DeleteVM(self.GetId())
   588  		if err != nil && self.Status != InstanceStatusTerminated {
   589  			log.Errorf("DeleteVM fail: %s", err)
   590  			return err
   591  		} else {
   592  			break
   593  		}
   594  	}
   595  
   596  	return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
   597  }
   598  
   599  func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
   600  	return self.host.zone.region.UpdateVM(self.GetId(), name)
   601  }
   602  
   603  // https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0032380449.html
   604  // 创建云服务器过程中注入用户数据。支持注入文本、文本文件或gzip文件。
   605  // 注入内容,需要进行base64格式编码。注入内容(编码之前的内容)最大长度32KB。
   606  // 对于Linux弹性云服务器,adminPass参数传入时,user_data参数不生效。
   607  func (self *SInstance) UpdateUserData(userData string) error {
   608  	return cloudprovider.ErrNotSupported
   609  }
   610  
   611  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html 使用原镜像重装
   612  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html 更换系统盘操作系统
   613  // 不支持调整系统盘大小
   614  func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   615  	var err error
   616  	var jobId string
   617  
   618  	publicKeyName := ""
   619  	if len(desc.PublicKey) > 0 {
   620  		publicKeyName, err = self.host.zone.region.syncKeypair(desc.PublicKey)
   621  		if err != nil {
   622  			return "", err
   623  		}
   624  	}
   625  
   626  	image, err := self.host.zone.region.GetImage(desc.ImageId)
   627  	if err != nil {
   628  		return "", errors.Wrap(err, "SInstance.RebuildRoot.GetImage")
   629  	}
   630  
   631  	// Password存在的情况下,windows 系统直接使用密码
   632  	if strings.ToLower(image.Platform) == strings.ToLower(osprofile.OS_TYPE_WINDOWS) && len(desc.Password) > 0 {
   633  		publicKeyName = ""
   634  	}
   635  
   636  	userData, err := updateUserData(self.OSEXTSRVATTRUserData, image.OSVersion, desc.Account, desc.Password, desc.PublicKey)
   637  	if err != nil {
   638  		return "", errors.Wrap(err, "SInstance.RebuildRoot.updateUserData")
   639  	}
   640  
   641  	if self.Metadata.MeteringImageID == desc.ImageId {
   642  		jobId, err = self.host.zone.region.RebuildRoot(ctx, self.UserID, self.GetId(), desc.Password, publicKeyName, userData)
   643  		if err != nil {
   644  			return "", err
   645  		}
   646  	} else {
   647  		jobId, err = self.host.zone.region.ChangeRoot(ctx, self.UserID, self.GetId(), desc.ImageId, desc.Password, publicKeyName, userData)
   648  		if err != nil {
   649  			return "", err
   650  		}
   651  	}
   652  
   653  	err = self.host.zone.region.waitTaskStatus(self.host.zone.region.ecsClient.Servers.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 900*time.Second)
   654  	if err != nil {
   655  		log.Errorf("RebuildRoot task error %s", err)
   656  		return "", err
   657  	}
   658  
   659  	err = self.Refresh()
   660  	if err != nil {
   661  		return "", err
   662  	}
   663  
   664  	idisks, err := self.GetIDisks()
   665  	if err != nil {
   666  		return "", err
   667  	}
   668  
   669  	if len(idisks) == 0 {
   670  		return "", fmt.Errorf("server %s has no volume attached.", self.GetId())
   671  	}
   672  
   673  	return idisks[0].GetId(), nil
   674  }
   675  
   676  func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   677  	return self.host.zone.region.DeployVM(self.GetId(), name, password, publicKey, deleteKeypair, description)
   678  }
   679  
   680  func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   681  	instanceTypes := []string{}
   682  	if len(config.InstanceType) > 0 {
   683  		instanceTypes = []string{config.InstanceType}
   684  	} else {
   685  		flavors, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, self.OSEXTAZAvailabilityZone)
   686  		if err != nil {
   687  			return errors.Wrapf(err, "GetMatchInstanceTypes")
   688  		}
   689  		for _, flavor := range flavors {
   690  			instanceTypes = append(instanceTypes, flavor.ID)
   691  		}
   692  	}
   693  	var err error
   694  	for _, instanceType := range instanceTypes {
   695  		err = self.host.zone.region.ChangeVMConfig(self.GetId(), instanceType)
   696  		if err != nil {
   697  			log.Warningf("ChangeVMConfig %s for %s error: %v", self.GetId(), instanceType, err)
   698  		} else {
   699  			return cloudprovider.WaitStatusWithDelay(self, api.VM_READY, 15*time.Second, 15*time.Second, 180*time.Second)
   700  		}
   701  	}
   702  	if err != nil {
   703  		return errors.Wrapf(err, "ChangeVMConfig")
   704  	}
   705  	return fmt.Errorf("Failed to change vm config, specification not supported")
   706  }
   707  
   708  // todo:// 返回jsonobject感觉很诡异。不能直接知道内部细节
   709  func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   710  	return self.host.zone.region.GetInstanceVNCUrl(self.GetId())
   711  }
   712  
   713  func (self *SInstance) NextDeviceName() (string, error) {
   714  	prefix := "s"
   715  	if strings.Contains(self.OSEXTSRVATTRRootDeviceName, "/vd") {
   716  		prefix = "v"
   717  	}
   718  
   719  	currents := []string{}
   720  	for _, item := range self.OSExtendedVolumesVolumesAttached {
   721  		currents = append(currents, strings.ToLower(item.Device))
   722  	}
   723  
   724  	for i := 0; i < 25; i++ {
   725  		device := fmt.Sprintf("/dev/%sd%s", prefix, string([]byte{byte(98 + i)}))
   726  		if ok, _ := utils.InStringArray(device, currents); !ok {
   727  			return device, nil
   728  		}
   729  	}
   730  
   731  	return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents)
   732  }
   733  
   734  func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   735  	device, err := self.NextDeviceName()
   736  	if err != nil {
   737  		return errors.Wrap(err, "Instance.AttachDisk.NextDeviceName")
   738  	}
   739  
   740  	err = self.host.zone.region.AttachDisk(self.GetId(), diskId, device)
   741  	if err != nil {
   742  		return errors.Wrap(err, "Instance.AttachDisk.AttachDisk")
   743  	}
   744  
   745  	return cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) {
   746  		disk, err := self.host.zone.region.GetDisk(diskId)
   747  		if err != nil {
   748  			log.Debugf("Instance.AttachDisk.GetDisk %s", err)
   749  			return false, nil
   750  		}
   751  
   752  		if disk.Status == "in-use" {
   753  			return true, nil
   754  		}
   755  
   756  		return false, nil
   757  	})
   758  }
   759  
   760  func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   761  	err := self.host.zone.region.DetachDisk(self.GetId(), diskId)
   762  	if err != nil {
   763  		return errors.Wrap(err, "Instance.DetachDisk")
   764  	}
   765  
   766  	return cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) {
   767  		disk, err := self.host.zone.region.GetDisk(diskId)
   768  		if err != nil {
   769  			log.Debugf("Instance.DetachDisk.GetDisk %s", err)
   770  			return false, nil
   771  		}
   772  
   773  		if disk.Status == "available" {
   774  			return true, nil
   775  		}
   776  
   777  		return false, nil
   778  	})
   779  }
   780  
   781  func (self *SInstance) Renew(bc billing.SBillingCycle) error {
   782  	return cloudprovider.ErrNotSupported
   783  }
   784  
   785  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148850.html
   786  func (self *SRegion) GetInstances() ([]SInstance, error) {
   787  	queries := make(map[string]string)
   788  
   789  	if len(self.client.projectId) > 0 {
   790  		queries["project_id"] = self.client.projectId
   791  	}
   792  
   793  	instances := make([]SInstance, 0)
   794  	err := doListAllWithPagerOffset(self.ecsClient.Servers.List, queries, &instances)
   795  	return instances, err
   796  }
   797  
   798  func (self *SRegion) GetInstanceByID(instanceId string) (SInstance, error) {
   799  	instance := SInstance{}
   800  	err := DoGet(self.ecsClient.Servers.Get, instanceId, nil, &instance)
   801  	if instance.Status == "DELETED" {
   802  		return instance, errors.Wrap(cloudprovider.ErrNotFound, "GetInstanceByID")
   803  	}
   804  	return instance, err
   805  }
   806  
   807  func (self *SRegion) GetInstanceByIds(ids []string) ([]SInstance, int, error) {
   808  	instances := make([]SInstance, 0)
   809  	for _, instanceId := range ids {
   810  		instance, err := self.GetInstanceByID(instanceId)
   811  		if err != nil {
   812  			return nil, 0, err
   813  		}
   814  		instances = append(instances, instance)
   815  	}
   816  
   817  	return instances, len(instances), nil
   818  }
   819  
   820  /*
   821  系统盘大小取值范围:1-1024 GB,且必须不小于镜像min_disk.
   822  */
   823  type SServerCreate struct {
   824  	AvailabilityZone string            `json:"availability_zone"`
   825  	Name             string            `json:"name"`
   826  	ImageRef         string            `json:"imageRef"`
   827  	RootVolume       RootVolume        `json:"root_volume"`
   828  	DataVolumes      []DataVolume      `json:"data_volumes"`
   829  	FlavorRef        string            `json:"flavorRef"`
   830  	UserData         string            `json:"user_data"`
   831  	Vpcid            string            `json:"vpcid"`
   832  	SecurityGroups   []SecGroup        `json:"security_groups"`
   833  	Nics             []NIC             `json:"nics"`
   834  	KeyName          string            `json:"key_name"`
   835  	AdminPass        string            `json:"adminPass"`
   836  	Count            int64             `json:"count"`
   837  	Extendparam      ServerExtendparam `json:"extendparam"`
   838  	ServerTags       []ServerTag       `json:"server_tags"`
   839  	Description      string            `json:"description"`
   840  }
   841  
   842  type DataVolume struct {
   843  	Volumetype    string                 `json:"volumetype"`
   844  	SizeGB        int                    `json:"size"`
   845  	Extendparam   *DataVolumeExtendparam `json:"extendparam,omitempty"`
   846  	Multiattach   *bool                  `json:"multiattach,omitempty"`
   847  	HwPassthrough *string                `json:"hw:passthrough,omitempty"`
   848  }
   849  
   850  type DataVolumeExtendparam struct {
   851  	SnapshotID string `json:"snapshotId"`
   852  }
   853  
   854  type ServerExtendparam struct {
   855  	ChargingMode        string `json:"chargingMode"` // 计费模式 prePaid|postPaid
   856  	PeriodType          string `json:"periodType"`   // 周期类型:month|year
   857  	PeriodNum           string `json:"periodNum"`    // 订购周期数:periodType=month(周期类型为月)时,取值为[1,9]。periodType=year(周期类型为年)时,取值为1。
   858  	IsAutoRenew         string `json:"isAutoRenew"`  // 是否自动续订  true|false
   859  	IsAutoPay           string `json:"isAutoPay"`    // 是否自动从客户的账户中支付 true|false
   860  	RegionID            string `json:"regionID"`
   861  	EnterpriseProjectId string `json:"enterprise_project_id,omitempty"`
   862  }
   863  
   864  type NIC struct {
   865  	SubnetID  string `json:"subnet_id"` // 网络ID. 与 SNetwork里的ID对应。统一使用这个ID
   866  	IpAddress string `json:"ip_address"`
   867  }
   868  
   869  type RootVolume struct {
   870  	Volumetype string `json:"volumetype"`
   871  	SizeGB     int    `json:"size"`
   872  }
   873  
   874  type SecGroup struct {
   875  	ID string `json:"id"`
   876  }
   877  
   878  type ServerTag struct {
   879  	Key   string `json:"key"`
   880  	Value string `json:"value"`
   881  }
   882  
   883  /*
   884  包月机器退订规则: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0083138805.html
   885  5天无理由全额退订:新购资源(不包含续费资源)在开通的五天内且退订次数不超过10次(每账号每年10次)的符合5天无理由全额退订。
   886  非5天无理由退订:不符合5天无理由全额退订条件的退订,都属于非5天无理由退订。非5天无理由退订,不限制退订次数,但需要收取退订手续费。
   887  
   888  退订资源的方法: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0072297197.html
   889  */
   890  func (self *SRegion) CreateInstance(name string, imageId string, instanceType string, SubnetId string,
   891  	securityGroupId string, vpcId string, zoneId string, desc string, disks []SDisk, ipAddr string,
   892  	keypair string, publicKey string, passwd string, userData string, bc *billing.SBillingCycle, projectId string, tags map[string]string) (string, error) {
   893  	params := SServerCreate{}
   894  	params.AvailabilityZone = zoneId
   895  	params.Name = name
   896  	params.FlavorRef = instanceType
   897  	params.ImageRef = imageId
   898  	params.Description = desc
   899  	params.Count = 1
   900  	params.Nics = []NIC{{SubnetID: SubnetId, IpAddress: ipAddr}}
   901  	params.SecurityGroups = []SecGroup{{ID: securityGroupId}}
   902  	params.Vpcid = vpcId
   903  
   904  	for i, disk := range disks {
   905  		if i == 0 {
   906  			params.RootVolume.Volumetype = disk.VolumeType
   907  			params.RootVolume.SizeGB = disk.SizeGB
   908  		} else {
   909  			dataVolume := DataVolume{}
   910  			dataVolume.Volumetype = disk.VolumeType
   911  			dataVolume.SizeGB = disk.SizeGB
   912  			params.DataVolumes = append(params.DataVolumes, dataVolume)
   913  		}
   914  	}
   915  
   916  	if len(projectId) > 0 {
   917  		params.Extendparam.EnterpriseProjectId = projectId
   918  	}
   919  
   920  	// billing type
   921  	if bc != nil {
   922  		params.Extendparam.ChargingMode = PRE_PAID
   923  		if bc.GetMonths() <= 9 {
   924  			params.Extendparam.PeriodNum = strconv.Itoa(bc.GetMonths())
   925  			params.Extendparam.PeriodType = "month"
   926  		} else {
   927  			params.Extendparam.PeriodNum = strconv.Itoa(bc.GetYears())
   928  			params.Extendparam.PeriodType = "year"
   929  		}
   930  
   931  		params.Extendparam.RegionID = self.GetId()
   932  		if bc.AutoRenew {
   933  			params.Extendparam.IsAutoRenew = "true"
   934  		} else {
   935  			params.Extendparam.IsAutoRenew = "false"
   936  		}
   937  		params.Extendparam.IsAutoPay = "true"
   938  	} else {
   939  		params.Extendparam.ChargingMode = POST_PAID
   940  	}
   941  
   942  	// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html#ZH-CN_TOPIC_0020212668__table761103195216
   943  	if len(keypair) > 0 {
   944  		params.KeyName = keypair
   945  	} else {
   946  		params.AdminPass = passwd
   947  	}
   948  
   949  	if len(userData) > 0 {
   950  		params.UserData = userData
   951  	}
   952  
   953  	if len(tags) > 0 {
   954  		serverTags := []ServerTag{}
   955  		for k, v := range tags {
   956  			serverTags = append(serverTags, ServerTag{Key: k, Value: v})
   957  		}
   958  		params.ServerTags = serverTags
   959  	}
   960  
   961  	serverObj := jsonutils.Marshal(params)
   962  	createParams := jsonutils.NewDict()
   963  	createParams.Add(serverObj, "server")
   964  	_id, err := self.ecsClient.Servers.AsyncCreate(createParams)
   965  	if err != nil {
   966  		return "", err
   967  	}
   968  
   969  	var ids []string
   970  	if params.Extendparam.ChargingMode == POST_PAID {
   971  		// 按需计费
   972  		ids, err = self.GetAllSubTaskEntityIDs(self.ecsClient.Servers.ServiceType(), _id, "server_id")
   973  	} else {
   974  		// 包年包月
   975  		return "", errors.Wrap(cloudprovider.ErrNotSupported, "CreateInstance")
   976  	}
   977  
   978  	if err != nil {
   979  		return "", err
   980  	} else if len(ids) == 0 {
   981  		return "", fmt.Errorf("CreateInstance job %s result is emtpy", _id)
   982  	} else if len(ids) == 1 {
   983  		return ids[0], nil
   984  	} else {
   985  		return "", fmt.Errorf("CreateInstance job %s mutliple instance id returned. %s", _id, ids)
   986  	}
   987  }
   988  
   989  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067161469.html
   990  // 添加多个安全组时,建议最多为弹性云服务器添加5个安全组。
   991  // todo: 确认是否需要先删除,再进行添加操作
   992  func (self *SRegion) assignSecurityGroups(secgroupIds []string, instanceId string) error {
   993  	_, err := self.GetInstanceByID(instanceId)
   994  	if err != nil {
   995  		return err
   996  	}
   997  
   998  	for i := range secgroupIds {
   999  		secId := secgroupIds[i]
  1000  		params := jsonutils.NewDict()
  1001  		secgroupObj := jsonutils.NewDict()
  1002  		secgroupObj.Add(jsonutils.NewString(secId), "name")
  1003  		params.Add(secgroupObj, "addSecurityGroup")
  1004  
  1005  		_, err := self.ecsClient.NovaServers.PerformAction("action", instanceId, params)
  1006  		if err != nil {
  1007  			return err
  1008  		}
  1009  	}
  1010  	return nil
  1011  }
  1012  
  1013  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067161717.html
  1014  func (self *SRegion) unassignSecurityGroups(secgroupIds []string, instanceId string) error {
  1015  	for i := range secgroupIds {
  1016  		secId := secgroupIds[i]
  1017  		params := jsonutils.NewDict()
  1018  		secgroupObj := jsonutils.NewDict()
  1019  		secgroupObj.Add(jsonutils.NewString(secId), "name")
  1020  		params.Add(secgroupObj, "removeSecurityGroup")
  1021  
  1022  		_, err := self.ecsClient.NovaServers.PerformAction("action", instanceId, params)
  1023  		if err != nil {
  1024  			return err
  1025  		}
  1026  	}
  1027  	return nil
  1028  }
  1029  
  1030  func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) {
  1031  	instance, err := self.GetInstanceByID(instanceId)
  1032  	if err != nil {
  1033  		return "", err
  1034  	}
  1035  	return instance.Status, nil
  1036  }
  1037  
  1038  func (self *SRegion) instanceStatusChecking(instanceId, status string) error {
  1039  	remoteStatus, err := self.GetInstanceStatus(instanceId)
  1040  	if err != nil {
  1041  		log.Errorf("Fail to get instance status: %s", err)
  1042  		return err
  1043  	}
  1044  	if status != remoteStatus {
  1045  		log.Errorf("instanceStatusChecking: vm status is %s expect %s", remoteStatus, status)
  1046  		return cloudprovider.ErrInvalidStatus
  1047  	}
  1048  
  1049  	return nil
  1050  }
  1051  
  1052  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212207.html
  1053  func (self *SRegion) StartVM(instanceId string) error {
  1054  	rstatus, err := self.GetInstanceStatus(instanceId)
  1055  	if err != nil {
  1056  		return err
  1057  	}
  1058  
  1059  	if rstatus == InstanceStatusRunning {
  1060  		return nil
  1061  	}
  1062  
  1063  	if rstatus != InstanceStatusStopped {
  1064  		log.Errorf("instanceStatusChecking: vm status is %s expect %s", rstatus, InstanceStatusStopped)
  1065  		return cloudprovider.ErrInvalidStatus
  1066  	}
  1067  
  1068  	params := jsonutils.NewDict()
  1069  	startObj := jsonutils.NewDict()
  1070  	serversObj := jsonutils.NewArray()
  1071  	serverObj := jsonutils.NewDict()
  1072  	serverObj.Add(jsonutils.NewString(instanceId), "id")
  1073  	serversObj.Add(serverObj)
  1074  	startObj.Add(serversObj, "servers")
  1075  	params.Add(startObj, "os-start")
  1076  	_, err = self.ecsClient.Servers.PerformAction2("action", "", params, "")
  1077  	return err
  1078  }
  1079  
  1080  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212651.html
  1081  func (self *SRegion) StopVM(instanceId string, isForce bool) error {
  1082  	rstatus, err := self.GetInstanceStatus(instanceId)
  1083  	if err != nil {
  1084  		return err
  1085  	}
  1086  
  1087  	if rstatus == InstanceStatusStopped {
  1088  		return nil
  1089  	}
  1090  
  1091  	if rstatus != InstanceStatusRunning {
  1092  		log.Errorf("instanceStatusChecking: vm status is %s expect %s", rstatus, InstanceStatusRunning)
  1093  		return cloudprovider.ErrInvalidStatus
  1094  	}
  1095  
  1096  	params := jsonutils.NewDict()
  1097  	stopObj := jsonutils.NewDict()
  1098  	serversObj := jsonutils.NewArray()
  1099  	serverObj := jsonutils.NewDict()
  1100  	serverObj.Add(jsonutils.NewString(instanceId), "id")
  1101  	serversObj.Add(serverObj)
  1102  	stopObj.Add(serversObj, "servers")
  1103  	if isForce {
  1104  		stopObj.Add(jsonutils.NewString("HARD"), "type")
  1105  	} else {
  1106  		stopObj.Add(jsonutils.NewString("SOFT"), "type")
  1107  	}
  1108  	params.Add(stopObj, "os-stop")
  1109  	_, err = self.ecsClient.Servers.PerformAction2("action", "", params, "")
  1110  	return err
  1111  }
  1112  
  1113  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212679.html
  1114  // 只删除主机。弹性IP和数据盘需要单独删除
  1115  func (self *SRegion) DeleteVM(instanceId string) error {
  1116  	remoteStatus, err := self.GetInstanceStatus(instanceId)
  1117  	if err != nil {
  1118  		return err
  1119  	}
  1120  
  1121  	if remoteStatus != InstanceStatusStopped {
  1122  		log.Errorf("DeleteVM vm status is %s expect %s", remoteStatus, InstanceStatusStopped)
  1123  		return cloudprovider.ErrInvalidStatus
  1124  	}
  1125  
  1126  	params := jsonutils.NewDict()
  1127  	serversObj := jsonutils.NewArray()
  1128  	serverObj := jsonutils.NewDict()
  1129  	serverObj.Add(jsonutils.NewString(instanceId), "id")
  1130  	serversObj.Add(serverObj)
  1131  	params.Add(serversObj, "servers")
  1132  	params.Add(jsonutils.NewBool(false), "delete_publicip")
  1133  	params.Add(jsonutils.NewBool(false), "delete_volume")
  1134  
  1135  	_, err = self.ecsClient.Servers.PerformAction2("delete", "", params, "")
  1136  	return err
  1137  }
  1138  
  1139  func (self *SRegion) UpdateVM(instanceId, name string) error {
  1140  	params := jsonutils.NewDict()
  1141  	serverObj := jsonutils.NewDict()
  1142  	serverObj.Add(jsonutils.NewString(name), "name")
  1143  	params.Add(serverObj, "server")
  1144  
  1145  	_, err := self.ecsClient.Servers.Update(instanceId, params)
  1146  	return err
  1147  }
  1148  
  1149  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html
  1150  // 返回job id
  1151  func (self *SRegion) RebuildRoot(ctx context.Context, userId, instanceId, passwd, publicKeyName, userData string) (string, error) {
  1152  	params := jsonutils.NewDict()
  1153  	reinstallObj := jsonutils.NewDict()
  1154  
  1155  	if len(publicKeyName) > 0 {
  1156  		reinstallObj.Add(jsonutils.NewString(publicKeyName), "keyname")
  1157  	} else if len(passwd) > 0 {
  1158  		reinstallObj.Add(jsonutils.NewString(passwd), "adminpass")
  1159  	} else {
  1160  		return "", fmt.Errorf("both password and publicKey are empty.")
  1161  	}
  1162  
  1163  	if len(userData) > 0 {
  1164  		meta := jsonutils.NewDict()
  1165  		meta.Add(jsonutils.NewString(userData), "user_data")
  1166  		reinstallObj.Add(meta, "metadata")
  1167  	}
  1168  
  1169  	if len(userId) > 0 {
  1170  		reinstallObj.Add(jsonutils.NewString(userId), "userid")
  1171  	}
  1172  
  1173  	params.Add(reinstallObj, "os-reinstall")
  1174  	ret, err := self.ecsClient.ServersV2.PerformAction2("reinstallos", instanceId, params, "")
  1175  	if err != nil {
  1176  		return "", err
  1177  	}
  1178  
  1179  	return ret.GetString("job_id")
  1180  }
  1181  
  1182  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html
  1183  // 返回job id
  1184  func (self *SRegion) ChangeRoot(ctx context.Context, userId, instanceId, imageId, passwd, publicKeyName, userData string) (string, error) {
  1185  	params := jsonutils.NewDict()
  1186  	changeOsObj := jsonutils.NewDict()
  1187  
  1188  	if len(publicKeyName) > 0 {
  1189  		changeOsObj.Add(jsonutils.NewString(publicKeyName), "keyname")
  1190  	} else if len(passwd) > 0 {
  1191  		changeOsObj.Add(jsonutils.NewString(passwd), "adminpass")
  1192  	} else {
  1193  		return "", fmt.Errorf("both password and publicKey are empty.")
  1194  	}
  1195  
  1196  	if len(userData) > 0 {
  1197  		meta := jsonutils.NewDict()
  1198  		meta.Add(jsonutils.NewString(userData), "user_data")
  1199  		changeOsObj.Add(meta, "metadata")
  1200  	}
  1201  
  1202  	if len(userId) > 0 {
  1203  		changeOsObj.Add(jsonutils.NewString(userId), "userid")
  1204  	}
  1205  
  1206  	changeOsObj.Add(jsonutils.NewString(imageId), "imageid")
  1207  	params.Add(changeOsObj, "os-change")
  1208  
  1209  	ret, err := self.ecsClient.ServersV2.PerformAction2("changeos", instanceId, params, "")
  1210  	if err != nil {
  1211  		return "", err
  1212  	}
  1213  
  1214  	return ret.GetString("job_id")
  1215  }
  1216  
  1217  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212692.html
  1218  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0110109377.html
  1219  // 一键式重置密码 需要安装安装一键式重置密码插件 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0068095385.html
  1220  // 目前不支持直接重置密钥
  1221  func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error {
  1222  	serverObj := jsonutils.NewDict()
  1223  	if len(name) > 0 {
  1224  		serverObj.Add(jsonutils.NewString(name), "name")
  1225  	}
  1226  
  1227  	// if len(description) > 0 {
  1228  	// 	serverObj.Add(jsonutils.NewString(description), "description")
  1229  	// }
  1230  
  1231  	if serverObj.Size() > 0 {
  1232  		params := jsonutils.NewDict()
  1233  		params.Add(serverObj, "server")
  1234  		// 这里华为返回的image字段是字符串。和SInstance的定义的image是字典结构不一致。
  1235  		err := DoUpdate(self.ecsClient.NovaServers.Update, instanceId, params, nil)
  1236  		if err != nil {
  1237  			return err
  1238  		}
  1239  	}
  1240  
  1241  	if len(password) > 0 {
  1242  		params := jsonutils.NewDict()
  1243  		passwdObj := jsonutils.NewDict()
  1244  		passwdObj.Add(jsonutils.NewString(password), "new_password")
  1245  		params.Add(passwdObj, "reset-password")
  1246  
  1247  		err := DoUpdateWithSpec(self.ecsClient.NovaServers.UpdateInContextWithSpec, instanceId, "os-reset-password", params)
  1248  		if err != nil {
  1249  			return err
  1250  		}
  1251  	}
  1252  
  1253  	return nil
  1254  }
  1255  
  1256  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212653.html
  1257  func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
  1258  	self.ecsClient.Servers.SetVersion("v1.1")
  1259  	defer self.ecsClient.Servers.SetVersion("v1")
  1260  
  1261  	params := jsonutils.NewDict()
  1262  	resizeObj := jsonutils.NewDict()
  1263  	resizeObj.Add(jsonutils.NewString(instanceType), "flavorRef")
  1264  	params.Add(resizeObj, "resize")
  1265  	_, err := self.ecsClient.Servers.PerformAction2("resize", instanceId, params, "")
  1266  	return errors.Wrapf(err, "PerformAction2(resize)")
  1267  }
  1268  
  1269  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0142763126.html 微版本2.6及以上?
  1270  // https://support.huaweicloud.com/api-ecs/ecs_02_0208.html
  1271  func (self *SRegion) GetInstanceVNCUrl(instanceId string) (*cloudprovider.ServerVncOutput, error) {
  1272  	params := jsonutils.NewDict()
  1273  	vncObj := jsonutils.NewDict()
  1274  	vncObj.Add(jsonutils.NewString("novnc"), "type")
  1275  	vncObj.Add(jsonutils.NewString("vnc"), "protocol")
  1276  	params.Add(vncObj, "remote_console")
  1277  
  1278  	resp, err := self.ecsClient.Servers.PerformAction2("remote_console", instanceId, params, "remote_console")
  1279  	if err != nil {
  1280  		return nil, err
  1281  	}
  1282  
  1283  	ret := &cloudprovider.ServerVncOutput{
  1284  		Hypervisor: api.HYPERVISOR_HCSO,
  1285  	}
  1286  	resp.Unmarshal(ret)
  1287  	ret.Protocol = "huawei"
  1288  	return ret, nil
  1289  }
  1290  
  1291  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472987.html
  1292  // XEN平台虚拟机device为必选参数。
  1293  func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error {
  1294  	params := jsonutils.NewDict()
  1295  	volumeObj := jsonutils.NewDict()
  1296  	volumeObj.Add(jsonutils.NewString(diskId), "volumeId")
  1297  	if len(device) > 0 {
  1298  		volumeObj.Add(jsonutils.NewString(device), "device")
  1299  	}
  1300  
  1301  	params.Add(volumeObj, "volumeAttachment")
  1302  
  1303  	_, err := self.ecsClient.Servers.PerformAction2("attachvolume", instanceId, params, "")
  1304  	return err
  1305  }
  1306  
  1307  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472988.html
  1308  // 默认非强制卸载。delete_flag=0
  1309  func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
  1310  	path := fmt.Sprintf("detachvolume/%s", diskId)
  1311  	err := DoDeleteWithSpec(self.ecsClient.Servers.DeleteInContextWithSpec, nil, instanceId, path, nil, nil)
  1312  	//volume a2091934-2669-4fca-8eb4-a950c1836b3c is not in server 49b053d2-f798-432f-af55-76eb6ef2c769 attach volume list => 磁盘已经被卸载了
  1313  	if err != nil && strings.Contains(err.Error(), fmt.Sprintf("is not in server")) && strings.Contains(err.Error(), fmt.Sprintf("attach volume list")) {
  1314  		return nil
  1315  	}
  1316  	return err
  1317  }
  1318  
  1319  // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0065817702.html
  1320  func (self *SRegion) GetInstanceSecrityGroupIds(instanceId string) ([]string, error) {
  1321  	if len(instanceId) == 0 {
  1322  		return nil, fmt.Errorf("GetInstanceSecrityGroups instanceId is empty")
  1323  	}
  1324  
  1325  	securitygroups := make([]SSecurityGroup, 0)
  1326  	ctx := &modules.SManagerContext{InstanceManager: self.ecsClient.NovaServers, InstanceId: instanceId}
  1327  	err := DoListInContext(self.ecsClient.NovaSecurityGroups.ListInContext, ctx, nil, &securitygroups)
  1328  	if err != nil {
  1329  		return nil, err
  1330  	}
  1331  
  1332  	securitygroupIds := []string{}
  1333  	for _, secgroup := range securitygroups {
  1334  		securitygroupIds = append(securitygroupIds, secgroup.GetId())
  1335  	}
  1336  
  1337  	return securitygroupIds, nil
  1338  }
  1339  
  1340  func (self *SInstance) GetProjectId() string {
  1341  	return self.EnterpriseProjectId
  1342  }
  1343  
  1344  func (self *SInstance) GetError() error {
  1345  	return nil
  1346  }
  1347  
  1348  func updateUserData(userData, osVersion, username, password, publicKey string) (string, error) {
  1349  	winOS := strings.ToLower(osprofile.OS_TYPE_WINDOWS)
  1350  	osVersion = strings.ToLower(osVersion)
  1351  	config := &cloudinit.SCloudConfig{}
  1352  	if strings.Contains(osVersion, winOS) {
  1353  		if _config, err := cloudinit.ParseUserDataBase64(userData); err == nil {
  1354  			config = _config
  1355  		} else {
  1356  			log.Debugf("updateWindowsUserData invalid userdata %s", userData)
  1357  		}
  1358  	} else {
  1359  		if _config, err := cloudinit.ParseUserDataBase64(userData); err == nil {
  1360  			config = _config
  1361  		} else {
  1362  			return "", fmt.Errorf("updateLinuxUserData invalid userdata %s", userData)
  1363  		}
  1364  	}
  1365  
  1366  	user := cloudinit.NewUser(username)
  1367  	config.RemoveUser(user)
  1368  	config.DisableRoot = 0
  1369  	if len(password) > 0 {
  1370  		config.SshPwauth = cloudinit.SSH_PASSWORD_AUTH_ON
  1371  		user.Password(password)
  1372  		config.MergeUser(user)
  1373  	}
  1374  
  1375  	if len(publicKey) > 0 {
  1376  		user.SshKey(publicKey)
  1377  		config.MergeUser(user)
  1378  	}
  1379  
  1380  	if strings.Contains(osVersion, winOS) {
  1381  		userData, err := updateWindowsUserData(config.UserDataPowerShell(), osVersion, username, password)
  1382  		if err != nil {
  1383  			return "", errors.Wrap(err, "updateUserData.updateWindowsUserData")
  1384  		}
  1385  		return userData, nil
  1386  	} else {
  1387  		return config.UserDataBase64(), nil
  1388  	}
  1389  }
  1390  
  1391  func updateWindowsUserData(userData string, osVersion string, username, password string) (string, error) {
  1392  	// Windows Server 2003, Windows Vista, Windows Server 2008, Windows Server 2003 R2, Windows Server 2000, Windows Server 2012, Windows Server 2003 with SP1, Windows 8
  1393  	oldVersions := []string{"2000", "2003", "2008", "2012", "Vista"}
  1394  	isOldVersion := false
  1395  	for i := range oldVersions {
  1396  		if strings.Contains(osVersion, oldVersions[i]) {
  1397  			isOldVersion = true
  1398  		}
  1399  	}
  1400  
  1401  	shells := ""
  1402  	if isOldVersion {
  1403  		shells += fmt.Sprintf("rem cmd\n")
  1404  		if username == "Administrator" {
  1405  			shells += fmt.Sprintf("net user %s %s\n", username, password)
  1406  		} else {
  1407  			shells += fmt.Sprintf("net user %s %s  /add\n", username, password)
  1408  			shells += fmt.Sprintf("net localgroup administrators %s  /add\n", username)
  1409  		}
  1410  
  1411  		shells += fmt.Sprintf("net user %s /active:yes", username)
  1412  	} else {
  1413  		if !strings.HasPrefix(userData, "#ps1") {
  1414  			shells = fmt.Sprintf("#ps1\n%s", userData)
  1415  		}
  1416  	}
  1417  
  1418  	return base64.StdEncoding.EncodeToString([]byte(shells)), nil
  1419  }
  1420  
  1421  func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  1422  	params := map[string]string{
  1423  		"name":        opts.Name,
  1424  		"instance_id": instanceId,
  1425  	}
  1426  	if len(opts.Notes) > 0 {
  1427  		params["description"] = func() string {
  1428  			opts.Notes = strings.ReplaceAll(opts.Notes, "<", "")
  1429  			opts.Notes = strings.ReplaceAll(opts.Notes, ">", "")
  1430  			opts.Notes = strings.ReplaceAll(opts.Notes, "\n", "")
  1431  			if len(opts.Notes) > 1024 {
  1432  				opts.Notes = opts.Notes[:1024]
  1433  			}
  1434  			return opts.Notes
  1435  		}()
  1436  	}
  1437  	resp, err := self.ecsClient.Images.CreateInContextWithSpec(nil, "action", jsonutils.Marshal(params), "")
  1438  	if err != nil {
  1439  		return nil, errors.Wrapf(err, "Images.Create")
  1440  	}
  1441  	jobId, err := resp.GetString("job_id")
  1442  	if err != nil {
  1443  		return nil, errors.Wrapf(err, "resp.GetString(job_id)")
  1444  	}
  1445  	err = self.waitTaskStatus(self.ecsClient.Images.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 10*time.Minute)
  1446  	if err != nil {
  1447  		return nil, errors.Wrapf(err, "waitTaskStatus")
  1448  	}
  1449  	imageId, err := self.GetTaskEntityID(self.ecsClient.Images.ServiceType(), jobId, "image_id")
  1450  	if err != nil {
  1451  		return nil, errors.Wrapf(err, "GetTaskEntityID")
  1452  	}
  1453  	image, err := self.GetImage(imageId)
  1454  	if err != nil {
  1455  		return nil, errors.Wrapf(err, "GetImage(%s)", imageId)
  1456  	}
  1457  	image.storageCache = self.getStoragecache()
  1458  	return image, nil
  1459  }
  1460  
  1461  func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1462  	image, err := self.host.zone.region.SaveImage(self.ID, opts)
  1463  	if err != nil {
  1464  		return nil, errors.Wrapf(err, "SaveImage")
  1465  	}
  1466  	return image, nil
  1467  }