yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/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 qcloud
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
    25  
    26  	"yunion.io/x/jsonutils"
    27  	"yunion.io/x/log"
    28  	"yunion.io/x/pkg/errors"
    29  	"yunion.io/x/pkg/utils"
    30  
    31  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    32  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    33  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    34  	"yunion.io/x/cloudmux/pkg/multicloud"
    35  	"yunion.io/x/onecloud/pkg/util/billing"
    36  	"yunion.io/x/onecloud/pkg/util/imagetools"
    37  )
    38  
    39  const (
    40  	// PENDING:表示创建中
    41  	// LAUNCH_FAILED:表示创建失败
    42  	// RUNNING:表示运行中
    43  	// STOPPED:表示关机
    44  	// STARTING:表示开机中
    45  	// STOPPING:表示关机中
    46  	// REBOOTING:表示重启中
    47  	// SHUTDOWN:表示停止待销毁
    48  	// TERMINATING:表示销毁中。
    49  
    50  	InstanceStatusStopped  = "STOPPED"
    51  	InstanceStatusRunning  = "RUNNING"
    52  	InstanceStatusStopping = "STOPPING"
    53  	InstanceStatusStarting = "STARTING"
    54  )
    55  
    56  const (
    57  	InternetChargeTypeBandwidthPrepaid        = "BANDWIDTH_PREPAID"
    58  	InternetChargeTypeTrafficPostpaidByHour   = "TRAFFIC_POSTPAID_BY_HOUR"
    59  	InternetChargeTypeBandwidthPostpaidByHour = "BANDWIDTH_POSTPAID_BY_HOUR"
    60  	InternetChargeTypeBandwidthPackage        = "BANDWIDTH_PACKAGE"
    61  )
    62  
    63  type SystemDisk struct {
    64  	DiskType string  //系统盘类型。系统盘类型限制详见CVM实例配置。取值范围:LOCAL_BASIC:本地硬盘 LOCAL_SSD:本地SSD硬盘 CLOUD_BASIC:普通云硬盘 CLOUD_SSD:SSD云硬盘 CLOUD_PREMIUM:高性能云硬盘 默认取值:CLOUD_BASIC。
    65  	DiskId   string  //	系统盘ID。LOCAL_BASIC 和 LOCAL_SSD 类型没有ID。暂时不支持该参数。
    66  	DiskSize float32 //系统盘大小,单位:GB。默认值为 50
    67  }
    68  
    69  type DataDisk struct {
    70  	DiskSize           float32 //	数据盘大小,单位:GB。最小调整步长为10G,不同数据盘类型取值范围不同,具体限制详见:CVM实例配置。默认值为0,表示不购买数据盘。更多限制详见产品文档。
    71  	DiskType           string  //	数据盘类型。数据盘类型限制详见CVM实例配置。取值范围:LOCAL_BASIC:本地硬盘 LOCAL_SSD:本地SSD硬盘 CLOUD_BASIC:普通云硬盘 CLOUD_PREMIUM:高性能云硬盘 CLOUD_SSD:SSD云硬盘 默认取值:LOCAL_BASIC。 该参数对ResizeInstanceDisk接口无效。
    72  	DiskId             string  //	数据盘ID。LOCAL_BASIC 和 LOCAL_SSD 类型没有ID。暂时不支持该参数。
    73  	DeleteWithInstance bool    //	数据盘是否随子机销毁。取值范围:TRUE:子机销毁时,销毁数据盘 FALSE:子机销毁时,保留数据盘 默认取值:TRUE 该参数目前仅用于 RunInstances 接口。
    74  }
    75  
    76  type InternetAccessible struct {
    77  	InternetChargeType      string //网络计费类型。取值范围:BANDWIDTH_PREPAID:预付费按带宽结算 TRAFFIC_POSTPAID_BY_HOUR:流量按小时后付费 BANDWIDTH_POSTPAID_BY_HOUR:带宽按小时后付费 BANDWIDTH_PACKAGE:带宽包用户 默认取值:非带宽包用户默认与子机付费类型保持一致。
    78  	InternetMaxBandwidthOut int    //	公网出带宽上限,单位:Mbps。默认值:0Mbps。不同机型带宽上限范围不一致,具体限制详见购买网络带宽。
    79  	PublicIpAssigned        bool   //	是否分配公网IP。取值范围: TRUE:表示分配公网IP FALSE:表示不分配公网IP 当公网带宽大于0Mbps时,可自由选择开通与否,默认开通公网IP;当公网带宽为0,则不允许分配公网IP。
    80  }
    81  
    82  type VirtualPrivateCloud struct {
    83  	VpcId              string   //	私有网络ID,形如vpc-xxx。有效的VpcId可通过登录控制台查询;也可以调用接口 DescribeVpcEx ,从接口返回中的unVpcId字段获取。
    84  	SubnetId           string   //	私有网络子网ID,形如subnet-xxx。有效的私有网络子网ID可通过登录控制台查询;也可以调用接口 DescribeSubnets ,从接口返回中的unSubnetId字段获取。
    85  	AsVpcGateway       bool     //	是否用作公网网关。公网网关只有在实例拥有公网IP以及处于私有网络下时才能正常使用。取值范围:TRUE:表示用作公网网关 FALSE:表示不用作公网网关 默认取值:FALSE。
    86  	PrivateIpAddresses []string //	私有网络子网 IP 数组,在创建实例、修改实例vpc属性操作中可使用此参数。当前仅批量创建多台实例时支持传入相同子网的多个 IP。
    87  }
    88  
    89  type LoginSettings struct {
    90  	Password       string   //实例登录密码。不同操作系统类型密码复杂度限制不一样,具体如下:Linux实例密码必须8到16位,至少包括两项[a-z,A-Z]、[0-9] 和 [( ) ~ ! @ # $ % ^ & * - + = &#124; { } [ ] : ; ' , . ? / ]中的特殊符号。<br><li>Windows实例密码必须12到16位,至少包括三项[a-z],[A-Z],[0-9] 和 [( ) ~ ! @ # $ % ^ & * - + = { } [ ] : ; ' , . ? /]中的特殊符号。 若不指定该参数,则由系统随机生成密码,并通过站内信方式通知到用户。
    91  	KeyIds         []string //	密钥ID列表。关联密钥后,就可以通过对应的私钥来访问实例;KeyId可通过接口DescribeKeyPairs获取,密钥与密码不能同时指定,同时Windows操作系统不支持指定密钥。当前仅支持购买的时候指定一个密钥。
    92  	KeepImageLogin string   //	保持镜像的原始设置。该参数与Password或KeyIds.N不能同时指定。只有使用自定义镜像、共享镜像或外部导入镜像创建实例时才能指定该参数为TRUE。取值范围: TRUE:表示保持镜像的登录设置 FALSE:表示不保持镜像的登录设置 默认取值:FALSE。
    93  }
    94  
    95  type Tag struct {
    96  	Key   string
    97  	Value string
    98  }
    99  
   100  type SInstance struct {
   101  	multicloud.SInstanceBase
   102  	QcloudTags
   103  
   104  	host *SHost
   105  
   106  	// normalized image info
   107  	osInfo *imagetools.ImageInfo
   108  
   109  	image  *SImage
   110  	idisks []cloudprovider.ICloudDisk
   111  
   112  	Placement           Placement
   113  	InstanceId          string
   114  	InstanceType        string
   115  	CPU                 int
   116  	Memory              int
   117  	RestrictState       string //NORMAL EXPIRED PROTECTIVELY_ISOLATED
   118  	InstanceName        string
   119  	InstanceChargeType  InstanceChargeType  //PREPAID:表示预付费,即包年包月 POSTPAID_BY_HOUR:表示后付费,即按量计费 CDHPAID:CDH付费,即只对CDH计费,不对CDH上的实例计费。
   120  	SystemDisk          SystemDisk          //实例系统盘信息。
   121  	DataDisks           []DataDisk          //实例数据盘信息。只包含随实例购买的数据盘。
   122  	PrivateIpAddresses  []string            //实例主网卡的内网IP列表。
   123  	PublicIpAddresses   []string            //实例主网卡的公网IP列表。
   124  	InternetAccessible  InternetAccessible  //实例带宽信息。
   125  	VirtualPrivateCloud VirtualPrivateCloud //实例所属虚拟私有网络信息。
   126  	ImageId             string              //	生产实例所使用的镜像ID。
   127  	RenewFlag           string              //	自动续费标识。取值范围:NOTIFY_AND_MANUAL_RENEW:表示通知即将过期,但不自动续费 NOTIFY_AND_AUTO_RENEW:表示通知即将过期,而且自动续费 DISABLE_NOTIFY_AND_MANUAL_RENEW:表示不通知即将过期,也不自动续费。
   128  	CreatedTime         time.Time           //	创建时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。
   129  	ExpiredTime         time.Time           //	到期时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。
   130  	OsName              string              //	操作系统名称。
   131  	SecurityGroupIds    []string            //	实例所属安全组。该参数可以通过调用 DescribeSecurityGroups 的返回值中的sgId字段来获取。
   132  	LoginSettings       LoginSettings       //实例登录设置。目前只返回实例所关联的密钥。
   133  	InstanceState       string              //	实例状态。取值范围:PENDING:表示创建中 LAUNCH_FAILED:表示创建失败 RUNNING:表示运行中 STOPPED:表示关机 STARTING:表示开机中 STOPPING:表示关机中 REBOOTING:表示重启中 SHUTDOWN:表示停止待销毁 TERMINATING:表示销毁中。
   134  	Tags                []Tag
   135  }
   136  
   137  func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) {
   138  	params := make(map[string]string)
   139  	if limit < 1 || limit > 50 {
   140  		limit = 50
   141  	}
   142  
   143  	params["Limit"] = fmt.Sprintf("%d", limit)
   144  	params["Offset"] = fmt.Sprintf("%d", offset)
   145  	instances := make([]SInstance, 0)
   146  	if ids != nil && len(ids) > 0 {
   147  		for index, id := range ids {
   148  			params[fmt.Sprintf("InstanceIds.%d", index)] = id
   149  		}
   150  	} else {
   151  		if len(zoneId) > 0 {
   152  			params["Filters.0.Name"] = "zone"
   153  			params["Filters.0.Values.0"] = zoneId
   154  		}
   155  	}
   156  	body, err := self.cvmRequest("DescribeInstances", params, true)
   157  	if err != nil {
   158  		return nil, 0, err
   159  	}
   160  	err = body.Unmarshal(&instances, "InstanceSet")
   161  	if err != nil {
   162  		return nil, 0, err
   163  	}
   164  	total, _ := body.Float("TotalCount")
   165  	return instances, int(total), nil
   166  }
   167  
   168  func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
   169  	return self.SecurityGroupIds, nil
   170  }
   171  
   172  func (self *SInstance) getCloudMetadata() (map[string]string, error) {
   173  	mtags, err := self.host.zone.region.FetchResourceTags("cvm", "instance", []string{self.InstanceId})
   174  	if err != nil {
   175  		return nil, errors.Wrap(err, "FetchResourceTags")
   176  	}
   177  	if tags, ok := mtags[self.InstanceId]; ok {
   178  		return *tags, nil
   179  	} else {
   180  		return nil, nil
   181  	}
   182  }
   183  
   184  func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
   185  	return self.host
   186  }
   187  
   188  func (self *SInstance) GetId() string {
   189  	return self.InstanceId
   190  }
   191  
   192  func (self *SInstance) GetName() string {
   193  	if len(self.InstanceName) > 0 && self.InstanceName != "未命名" {
   194  		return self.InstanceName
   195  	}
   196  	return self.InstanceId
   197  }
   198  
   199  func (self *SInstance) GetHostname() string {
   200  	return self.GetName()
   201  }
   202  
   203  func (self *SInstance) GetGlobalId() string {
   204  	return self.InstanceId
   205  }
   206  
   207  func (self *SInstance) IsEmulated() bool {
   208  	return false
   209  }
   210  
   211  func (self *SInstance) GetInstanceType() string {
   212  	return self.InstanceType
   213  }
   214  
   215  func (self *SInstance) getVpc() (*SVpc, error) {
   216  	return self.host.zone.region.getVpc(self.VirtualPrivateCloud.VpcId)
   217  }
   218  
   219  func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   220  	idisks := make([]cloudprovider.ICloudDisk, 0)
   221  
   222  	if utils.IsInStringArray(strings.ToUpper(self.SystemDisk.DiskType), QCLOUD_LOCAL_STORAGE_TYPES) {
   223  		storage := SLocalStorage{zone: self.host.zone, storageType: self.SystemDisk.DiskType}
   224  		disk := SLocalDisk{
   225  			storage:   &storage,
   226  			DiskId:    self.SystemDisk.DiskId,
   227  			DiskSize:  self.SystemDisk.DiskSize,
   228  			DisktType: self.SystemDisk.DiskType,
   229  			DiskUsage: "SYSTEM_DISK",
   230  			imageId:   self.ImageId,
   231  		}
   232  		idisks = append(idisks, &disk)
   233  	}
   234  
   235  	for i := 0; i < len(self.DataDisks); i++ {
   236  		if utils.IsInStringArray(strings.ToUpper(self.DataDisks[i].DiskType), QCLOUD_LOCAL_STORAGE_TYPES) {
   237  			storage := SLocalStorage{zone: self.host.zone, storageType: self.DataDisks[i].DiskType}
   238  			disk := SLocalDisk{
   239  				storage:   &storage,
   240  				DiskId:    self.DataDisks[i].DiskId,
   241  				DiskSize:  self.DataDisks[i].DiskSize,
   242  				DisktType: self.DataDisks[i].DiskType,
   243  				DiskUsage: "DATA_DISK",
   244  			}
   245  			idisks = append(idisks, &disk)
   246  		}
   247  	}
   248  
   249  	disks := make([]SDisk, 0)
   250  	totalDisk := -1
   251  	for totalDisk < 0 || len(disks) < totalDisk {
   252  		parts, total, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, len(disks), 50)
   253  		if err != nil {
   254  			log.Errorf("fetchDisks fail %s", err)
   255  			return nil, err
   256  		}
   257  		if len(parts) > 0 {
   258  			disks = append(disks, parts...)
   259  		}
   260  		totalDisk = total
   261  	}
   262  
   263  	for i := 0; i < len(disks); i += 1 {
   264  		store, err := self.host.zone.getStorageByCategory(disks[i].DiskType)
   265  		if err != nil {
   266  			return nil, err
   267  		}
   268  		disks[i].storage = store
   269  		idisks = append(idisks, &disks[i])
   270  	}
   271  
   272  	return idisks, nil
   273  }
   274  
   275  func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   276  	classic := self.VirtualPrivateCloud.VpcId == ""
   277  	region := self.host.zone.region
   278  	ret := []cloudprovider.ICloudNic{}
   279  	if classic {
   280  		for _, ipAddr := range self.PrivateIpAddresses {
   281  			nic := SInstanceNic{
   282  				instance: self,
   283  				ipAddr:   ipAddr,
   284  				classic:  true,
   285  			}
   286  			ret = append(ret, &nic)
   287  		}
   288  	}
   289  	nics, _, err := region.GetNetworkInterfaces(nil, self.InstanceId, "", 0, 10)
   290  	if err != nil {
   291  		return nil, errors.Wrapf(err, "GetNetworkInterfaces")
   292  	}
   293  	for _, networkInterface := range nics {
   294  		nic := &SInstanceNic{
   295  			instance: self,
   296  			id:       String(&networkInterface.NetworkInterfaceId),
   297  			macAddr:  strings.ToLower(networkInterface.MacAddress),
   298  			classic:  classic,
   299  		}
   300  		for _, addr := range networkInterface.PrivateIpAddressSet {
   301  			if addr.Primary {
   302  				nic.ipAddr = addr.PrivateIpAddress
   303  			}
   304  		}
   305  		ret = append(ret, nic)
   306  	}
   307  	return ret, nil
   308  }
   309  
   310  func (self *SInstance) GetVcpuCount() int {
   311  	return self.CPU
   312  }
   313  
   314  func (self *SInstance) GetVmemSizeMB() int {
   315  	return self.Memory * 1024
   316  }
   317  
   318  func (self *SInstance) GetBootOrder() string {
   319  	return "dcn"
   320  }
   321  
   322  func (self *SInstance) GetVga() string {
   323  	return "std"
   324  }
   325  
   326  func (self *SInstance) GetVdi() string {
   327  	return "vnc"
   328  }
   329  
   330  func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
   331  	if ins.osInfo == nil {
   332  		osInfo := imagetools.NormalizeImageInfo(ins.OsName, "", "", "", "")
   333  		ins.osInfo = &osInfo
   334  	}
   335  	return ins.osInfo
   336  }
   337  
   338  func (ins *SInstance) GetOsType() cloudprovider.TOsType {
   339  	return cloudprovider.TOsType(ins.getNormalizedOsInfo().OsType)
   340  }
   341  
   342  func (ins *SInstance) GetBios() cloudprovider.TBiosType {
   343  	return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios)
   344  }
   345  
   346  func (ins *SInstance) GetFullOsName() string {
   347  	return ins.OsName
   348  }
   349  
   350  func (ins *SInstance) GetOsLang() string {
   351  	return ins.getNormalizedOsInfo().OsLang
   352  }
   353  
   354  func (ins *SInstance) GetOsArch() string {
   355  	return ins.getNormalizedOsInfo().OsArch
   356  }
   357  
   358  func (ins *SInstance) GetOsDist() string {
   359  	return ins.getNormalizedOsInfo().OsDistro
   360  }
   361  
   362  func (ins *SInstance) GetOsVersion() string {
   363  	return ins.getNormalizedOsInfo().OsVersion
   364  }
   365  
   366  func (self *SInstance) GetMachine() string {
   367  	return "pc"
   368  }
   369  
   370  func (self *SInstance) GetStatus() string {
   371  	switch self.InstanceState {
   372  	case "PENDING":
   373  		return api.VM_DEPLOYING
   374  	case "LAUNCH_FAILED":
   375  		return api.VM_DEPLOY_FAILED
   376  	case "RUNNING":
   377  		return api.VM_RUNNING
   378  	case "STOPPED":
   379  		return api.VM_READY
   380  	case "STARTING", "REBOOTING":
   381  		return api.VM_STARTING
   382  	case "STOPPING":
   383  		return api.VM_STOPPING
   384  	case "SHUTDOWN":
   385  		return api.VM_DEALLOCATED
   386  	case "TERMINATING":
   387  		return api.VM_DELETING
   388  	default:
   389  		return api.VM_UNKNOWN
   390  	}
   391  }
   392  
   393  func (self *SInstance) Refresh() error {
   394  	new, err := self.host.zone.region.GetInstance(self.InstanceId)
   395  	if err != nil {
   396  		return err
   397  	}
   398  	return jsonutils.Update(self, new)
   399  }
   400  
   401  func (self *SInstance) GetHypervisor() string {
   402  	return api.HYPERVISOR_QCLOUD
   403  }
   404  
   405  func (self *SInstance) StartVM(ctx context.Context) error {
   406  	err := cloudprovider.Wait(time.Second*15, time.Minute*5, func() (bool, error) {
   407  		err := self.Refresh()
   408  		if err != nil {
   409  			return true, errors.Wrapf(err, "Refresh")
   410  		}
   411  		log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING)
   412  		if self.GetStatus() == api.VM_RUNNING {
   413  			return true, nil
   414  		}
   415  		if self.GetStatus() == api.VM_STARTING {
   416  			return false, nil
   417  		}
   418  		if self.GetStatus() == api.VM_READY {
   419  			err := self.host.zone.region.StartVM(self.InstanceId)
   420  			if err != nil {
   421  				if e, ok := errors.Cause(err).(*sdkerrors.TencentCloudSDKError); ok {
   422  					if e.Code == "UnsupportedOperation.InstanceStateRunning" {
   423  						return true, nil
   424  					}
   425  				}
   426  				return true, err
   427  			}
   428  		}
   429  		return false, nil
   430  	})
   431  	return errors.Wrapf(err, "Wait")
   432  }
   433  
   434  func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   435  	err := self.host.zone.region.StopVM(self.InstanceId, opts)
   436  	if err != nil {
   437  		return err
   438  	}
   439  	return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 8*time.Minute) // 8 mintues, 腾讯云有时关机比较慢
   440  }
   441  
   442  func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   443  	url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	ret := &cloudprovider.ServerVncOutput{
   448  		Url:        url,
   449  		Protocol:   "qcloud",
   450  		InstanceId: self.InstanceId,
   451  		Hypervisor: api.HYPERVISOR_QCLOUD,
   452  	}
   453  	return ret, nil
   454  }
   455  
   456  func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
   457  	return self.host.zone.region.UpdateVM(self.InstanceId, name)
   458  }
   459  
   460  func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   461  	var keypairName string
   462  	if len(publicKey) > 0 {
   463  		var err error
   464  		keypairName, err = self.host.zone.region.syncKeypair(publicKey)
   465  		if err != nil {
   466  			return err
   467  		}
   468  	}
   469  
   470  	return self.host.zone.region.DeployVM(self.InstanceId, name, password, keypairName, deleteKeypair, description)
   471  }
   472  
   473  func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   474  	keypair := ""
   475  	if len(desc.PublicKey) > 0 {
   476  		var err error
   477  		keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey)
   478  		if err != nil {
   479  			return "", err
   480  		}
   481  	}
   482  	err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB)
   483  	if err != nil {
   484  		return "", err
   485  	}
   486  	opts := &cloudprovider.ServerStopOptions{
   487  		IsForce: true,
   488  	}
   489  	self.StopVM(ctx, opts)
   490  	instance, err := self.host.zone.region.GetInstance(self.InstanceId)
   491  	if err != nil {
   492  		return "", err
   493  	}
   494  	return instance.SystemDisk.DiskId, nil
   495  }
   496  
   497  func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   498  	instanceTypes := []string{}
   499  	if len(config.InstanceType) > 0 {
   500  		instanceTypes = []string{config.InstanceType}
   501  	} else {
   502  		specs, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, 0, self.Placement.Zone)
   503  		if err != nil {
   504  			return errors.Wrapf(err, "GetMatchInstanceTypes")
   505  		}
   506  		for _, spec := range specs {
   507  			instanceTypes = append(instanceTypes, spec.InstanceType)
   508  		}
   509  	}
   510  
   511  	var err error
   512  	for _, instanceType := range instanceTypes {
   513  		err = self.host.zone.region.ChangeVMConfig(self.InstanceId, instanceType)
   514  		if err != nil {
   515  			log.Errorf("ChangeConfig for %s with %s error: %v", self.InstanceId, instanceType, err)
   516  			continue
   517  		}
   518  		return nil
   519  	}
   520  	if err != nil {
   521  		return err
   522  	}
   523  
   524  	return fmt.Errorf("Failed to change vm config, specification not supported")
   525  }
   526  
   527  func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   528  	return self.host.zone.region.AttachDisk(self.InstanceId, diskId)
   529  }
   530  
   531  func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   532  	return self.host.zone.region.DetachDisk(self.InstanceId, diskId)
   533  }
   534  
   535  func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
   536  	instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1)
   537  	if err != nil {
   538  		return nil, err
   539  	}
   540  	if len(instances) == 0 {
   541  		return nil, cloudprovider.ErrNotFound
   542  	}
   543  	if len(instances) > 1 {
   544  		return nil, cloudprovider.ErrDuplicateId
   545  	}
   546  	if instances[0].InstanceState == "LAUNCH_FAILED" {
   547  		return nil, cloudprovider.ErrNotFound
   548  	}
   549  	return &instances[0], nil
   550  }
   551  
   552  func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupId string,
   553  	zoneId string, desc string, passwd string, disks []SDisk, networkId string, ipAddr string,
   554  	keypair string, userData string, bc *billing.SBillingCycle, projectId string,
   555  	publicIpBw int, publicIpChargeType cloudprovider.TElasticipChargeType,
   556  	tags map[string]string, osType string,
   557  ) (string, error) {
   558  	params := make(map[string]string)
   559  	params["Region"] = self.Region
   560  	params["ImageId"] = imageId
   561  	params["InstanceType"] = instanceType
   562  	params["SecurityGroupIds.0"] = securityGroupId
   563  	params["Placement.Zone"] = zoneId
   564  	if len(projectId) > 0 {
   565  		params["Placement.ProjectId"] = projectId
   566  	}
   567  	params["InstanceName"] = name
   568  	if len(hostname) > 0 {
   569  		params["HostName"] = hostname
   570  	}
   571  
   572  	bandwidth := publicIpBw
   573  	if publicIpBw == 0 {
   574  		bandwidth = 100
   575  		if bc != nil {
   576  			bandwidth = 200
   577  		}
   578  	}
   579  
   580  	internetChargeType := "TRAFFIC_POSTPAID_BY_HOUR"
   581  	if publicIpChargeType == cloudprovider.ElasticipChargeTypeByBandwidth {
   582  		internetChargeType = "BANDWIDTH_POSTPAID_BY_HOUR"
   583  	}
   584  	pkgs, _, err := self.GetBandwidthPackages([]string{}, 0, 50)
   585  	if err != nil {
   586  		return "", errors.Wrapf(err, "GetBandwidthPackages")
   587  	}
   588  	if len(pkgs) > 0 {
   589  		bandwidth = 65535   // unlimited bandwidth
   590  		if publicIpBw > 0 { // 若用户指定带宽则限制带宽大小
   591  			bandwidth = publicIpBw
   592  		}
   593  		internetChargeType = "BANDWIDTH_PACKAGE"
   594  		pkgId := pkgs[0].BandwidthPackageId
   595  		for _, pkg := range pkgs {
   596  			if len(pkg.ResourceSet) < 100 {
   597  				pkgId = pkg.BandwidthPackageId
   598  				break
   599  			}
   600  		}
   601  		params["InternetAccessible.BandwidthPackageId"] = pkgId
   602  	}
   603  
   604  	params["InternetAccessible.InternetChargeType"] = internetChargeType
   605  	params["InternetAccessible.InternetMaxBandwidthOut"] = fmt.Sprintf("%d", bandwidth)
   606  	params["InternetAccessible.PublicIpAssigned"] = "TRUE"
   607  	if publicIpBw == 0 {
   608  		params["InternetAccessible.PublicIpAssigned"] = "FALSE"
   609  	}
   610  	if len(keypair) > 0 {
   611  		params["LoginSettings.KeyIds.0"] = keypair
   612  	} else if len(passwd) > 0 {
   613  		params["LoginSettings.Password"] = passwd
   614  	} else {
   615  		params["LoginSettings.KeepImageLogin"] = "TRUE"
   616  	}
   617  	if len(userData) > 0 {
   618  		params["UserData"] = userData
   619  	}
   620  
   621  	if bc != nil {
   622  		params["InstanceChargeType"] = "PREPAID"
   623  		params["InstanceChargePrepaid.Period"] = fmt.Sprintf("%d", bc.GetMonths())
   624  		if bc.AutoRenew {
   625  			params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_AUTO_RENEW"
   626  		} else {
   627  			params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_MANUAL_RENEW"
   628  		}
   629  	} else {
   630  		params["InstanceChargeType"] = "POSTPAID_BY_HOUR"
   631  	}
   632  
   633  	// tags
   634  	if len(tags) > 0 {
   635  		params["TagSpecification.0.ResourceType"] = "instance"
   636  		tagIdx := 0
   637  		for k, v := range tags {
   638  			params[fmt.Sprintf("TagSpecification.0.Tags.%d.Key", tagIdx)] = k
   639  			params[fmt.Sprintf("TagSpecification.0.Tags.%d.Value", tagIdx)] = v
   640  			tagIdx += 1
   641  		}
   642  	}
   643  
   644  	//params["IoOptimized"] = "optimized"
   645  	for i, d := range disks {
   646  		if i == 0 {
   647  			params["SystemDisk.DiskType"] = d.DiskType
   648  			params["SystemDisk.DiskSize"] = fmt.Sprintf("%d", d.DiskSize)
   649  		} else {
   650  			params[fmt.Sprintf("DataDisks.%d.DiskSize", i-1)] = fmt.Sprintf("%d", d.DiskSize)
   651  			params[fmt.Sprintf("DataDisks.%d.DiskType", i-1)] = d.DiskType
   652  		}
   653  	}
   654  	network, err := self.GetNetwork(networkId)
   655  	if err != nil {
   656  		return "", errors.Wrapf(err, "GetNetwork(%s)", networkId)
   657  	}
   658  	params["VirtualPrivateCloud.SubnetId"] = networkId
   659  	params["VirtualPrivateCloud.VpcId"] = network.VpcId
   660  	if len(ipAddr) > 0 {
   661  		params["VirtualPrivateCloud.PrivateIpAddresses.0"] = ipAddr
   662  	}
   663  
   664  	var body jsonutils.JSONObject
   665  	instanceIdSet := []string{}
   666  	err = cloudprovider.Wait(time.Second*10, time.Minute, func() (bool, error) {
   667  		params["ClientToken"] = utils.GenRequestId(20)
   668  		body, err = self.cvmRequest("RunInstances", params, true)
   669  		if err != nil {
   670  			if strings.Contains(err.Error(), "Code=InvalidPermission") { // 带宽上移用户未指定公网ip时不能设置带宽
   671  				delete(params, "InternetAccessible.InternetChargeType")
   672  				delete(params, "InternetAccessible.InternetMaxBandwidthOut")
   673  				return false, nil
   674  			}
   675  			if strings.Contains(err.Error(), "UnsupportedOperation.BandwidthPackageIdNotSupported") ||
   676  				(strings.Contains(err.Error(), "Code=InvalidParameterCombination") && strings.Contains(err.Error(), "InternetAccessible.BandwidthPackageId")) {
   677  				delete(params, "InternetAccessible.BandwidthPackageId")
   678  				return false, nil
   679  			}
   680  			return false, errors.Wrapf(err, "RunInstances")
   681  		}
   682  		return true, nil
   683  	})
   684  	if err != nil {
   685  		return "", errors.Wrap(err, "RunInstances")
   686  	}
   687  	err = body.Unmarshal(&instanceIdSet, "InstanceIdSet")
   688  	if err == nil && len(instanceIdSet) > 0 {
   689  		return instanceIdSet[0], nil
   690  	}
   691  	return "", fmt.Errorf("Failed to create instance")
   692  }
   693  
   694  func (self *SRegion) doStartVM(instanceId string) error {
   695  	return self.instanceOperation(instanceId, "StartInstances", nil, true)
   696  }
   697  
   698  func (self *SRegion) doStopVM(instanceId string, opts *cloudprovider.ServerStopOptions) error {
   699  	params := make(map[string]string)
   700  	if opts.IsForce {
   701  		// params["ForceStop"] = "FALSE"
   702  		params["StopType"] = "HARD"
   703  	} else {
   704  		// params["ForceStop"] = "FALSE"
   705  		params["StopType"] = "SOFT"
   706  	}
   707  	if opts.StopCharging {
   708  		params["StoppedMode"] = "STOP_CHARGING"
   709  	}
   710  	return self.instanceOperation(instanceId, "StopInstances", params, true)
   711  }
   712  
   713  func (self *SRegion) doDeleteVM(instanceId string) error {
   714  	params := make(map[string]string)
   715  	err := self.instanceOperation(instanceId, "TerminateInstances", params, true)
   716  	if err != nil && cloudprovider.IsError(err, []string{"InvalidInstanceId.NotFound"}) {
   717  		return nil
   718  	}
   719  	return err
   720  }
   721  
   722  func (self *SRegion) StartVM(instanceId string) error {
   723  	status, err := self.GetInstanceStatus(instanceId)
   724  	if err != nil {
   725  		log.Errorf("Fail to get instance status on StartVM: %s", err)
   726  		return err
   727  	}
   728  	if status != InstanceStatusStopped {
   729  		log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped)
   730  		return cloudprovider.ErrInvalidStatus
   731  	}
   732  	return self.doStartVM(instanceId)
   733  }
   734  
   735  func (self *SRegion) StopVM(instanceId string, opts *cloudprovider.ServerStopOptions) error {
   736  	status, err := self.GetInstanceStatus(instanceId)
   737  	if err != nil {
   738  		log.Errorf("Fail to get instance status on StopVM: %s", err)
   739  		return err
   740  	}
   741  	if status == InstanceStatusStopped {
   742  		return nil
   743  	}
   744  	return self.doStopVM(instanceId, opts)
   745  }
   746  
   747  func (self *SRegion) DeleteVM(instanceId string) error {
   748  	status, err := self.GetInstanceStatus(instanceId)
   749  	if err != nil {
   750  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   751  			return nil
   752  		}
   753  		return errors.Wrapf(err, "self.GetInstanceStatus")
   754  	}
   755  	log.Debugf("Instance status on delete is %s", status)
   756  	if status != InstanceStatusStopped {
   757  		log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped)
   758  	}
   759  	return self.doDeleteVM(instanceId)
   760  }
   761  
   762  func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error {
   763  	instance, err := self.GetInstance(instanceId)
   764  	if err != nil {
   765  		return err
   766  	}
   767  
   768  	// 修改密钥时直接返回
   769  	if deleteKeypair {
   770  		for i := 0; i < len(instance.LoginSettings.KeyIds); i++ {
   771  			err = self.DetachKeyPair(instanceId, instance.LoginSettings.KeyIds[i])
   772  			if err != nil {
   773  				return err
   774  			}
   775  		}
   776  	}
   777  
   778  	if len(keypairName) > 0 {
   779  		err = self.AttachKeypair(instanceId, keypairName)
   780  		if err != nil {
   781  			return err
   782  		}
   783  	}
   784  
   785  	params := make(map[string]string)
   786  
   787  	if len(name) > 0 && instance.InstanceName != name {
   788  		params["InstanceName"] = name
   789  	}
   790  
   791  	if len(params) > 0 {
   792  		err := self.modifyInstanceAttribute(instanceId, params)
   793  		if err != nil {
   794  			return err
   795  		}
   796  	}
   797  	if len(password) > 0 {
   798  		return self.instanceOperation(instanceId, "ResetInstancesPassword", map[string]string{"Password": password}, true)
   799  	}
   800  	return nil
   801  }
   802  
   803  func (self *SInstance) DeleteVM(ctx context.Context) error {
   804  	err := self.host.zone.region.DeleteVM(self.InstanceId)
   805  	if err != nil {
   806  		return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId)
   807  	}
   808  	if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID { // 预付费的需要删除两次
   809  		cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) {
   810  			err = self.Refresh()
   811  			if err != nil {
   812  				log.Warningf("refresh instance %s(%s) error: %v", self.InstanceName, self.InstanceId, err)
   813  				return false, nil
   814  			}
   815  			// 需要等待第一次删除后,状态变为SHUTDOWN,才可以进行第二次删除,否则会报:
   816  			// Code=OperationDenied.InstanceOperationInProgress, Message=该实例`['ins-mxqturgj']`
   817  			if self.InstanceState == "SHUTDOWN" {
   818  				return true, nil
   819  			}
   820  			log.Debugf("wait %s(%s) status be SHUTDOWN, current is %s", self.InstanceName, self.InstanceId, self.InstanceState)
   821  			return false, nil
   822  		})
   823  		err := self.host.zone.region.DeleteVM(self.InstanceId)
   824  		if err != nil {
   825  			return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId)
   826  		}
   827  	}
   828  	return cloudprovider.WaitDeleted(self, 10*time.Second, 5*time.Minute) // 5minutes
   829  }
   830  
   831  func (self *SRegion) UpdateVM(instanceId string, name string) error {
   832  	params := make(map[string]string)
   833  	params["InstanceName"] = name
   834  	return self.modifyInstanceAttribute(instanceId, params)
   835  }
   836  
   837  func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error {
   838  	return self.instanceOperation(instanceId, "ModifyInstancesAttribute", params, true)
   839  }
   840  
   841  func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) error {
   842  	params := make(map[string]string)
   843  	params["InstanceId"] = instanceId
   844  	params["ImageId"] = imageId
   845  	params["EnhancedService.SecurityService.Enabled"] = "TRUE"
   846  	params["EnhancedService.MonitorService.Enabled"] = "TRUE"
   847  
   848  	// 秘钥和密码及保留镜像设置只能选其一
   849  	if len(keypairName) > 0 {
   850  		params["LoginSettings.KeyIds.0"] = keypairName
   851  	} else if len(passwd) > 0 {
   852  		params["LoginSettings.Password"] = passwd
   853  	} else {
   854  		params["LoginSettings.KeepImageLogin"] = "TRUE"
   855  	}
   856  
   857  	if sysDiskSizeGB > 0 {
   858  		params["SystemDisk.DiskSize"] = fmt.Sprintf("%d", sysDiskSizeGB)
   859  	}
   860  	_, err := self.cvmRequest("ResetInstance", params, true)
   861  	return err
   862  }
   863  
   864  func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
   865  	params := make(map[string]string)
   866  	params["InstanceType"] = instanceType
   867  
   868  	err := self.instanceOperation(instanceId, "ResetInstancesType", params, true)
   869  	return errors.Wrapf(err, "ResetInstancesType %s", instanceType)
   870  }
   871  
   872  func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
   873  	params := make(map[string]string)
   874  	params["DiskIds.0"] = diskId
   875  	log.Infof("Detach instance %s disk %s", instanceId, diskId)
   876  	_, err := self.cbsRequest("DetachDisks", params)
   877  	if err != nil {
   878  		// 可重复卸载,无报错,若磁盘被删除会有以下错误
   879  		//[TencentCloudSDKError] Code=InvalidDisk.NotSupported, Message=disk(disk-4g5s7zhl) deleted (39a711ce2d17), RequestId=508d7fe3-e64e-4bb8-8ad7-39a711ce2d17
   880  		if strings.Contains(err.Error(), fmt.Sprintf("disk(%s) deleted", diskId)) {
   881  			return nil
   882  		}
   883  		return errors.Wrap(err, "DetachDisks")
   884  	}
   885  
   886  	return nil
   887  }
   888  
   889  func (self *SRegion) AttachDisk(instanceId string, diskId string) error {
   890  	params := make(map[string]string)
   891  	params["InstanceId"] = instanceId
   892  	params["DiskIds.0"] = diskId
   893  	_, err := self.cbsRequest("AttachDisks", params)
   894  	if err != nil {
   895  		log.Errorf("AttachDisks %s to %s fail %s", diskId, instanceId, err)
   896  		return err
   897  	}
   898  	return nil
   899  }
   900  
   901  func (self *SInstance) AssignSecurityGroup(secgroupId string) error {
   902  	params := map[string]string{"SecurityGroups.0": secgroupId}
   903  	return self.host.zone.region.instanceOperation(self.InstanceId, "ModifyInstancesAttribute", params, true)
   904  }
   905  
   906  func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
   907  	params := map[string]string{}
   908  	for i := 0; i < len(secgroupIds); i++ {
   909  		params[fmt.Sprintf("SecurityGroups.%d", i)] = secgroupIds[i]
   910  	}
   911  	return self.host.zone.region.instanceOperation(self.InstanceId, "ModifyInstancesAttribute", params, true)
   912  }
   913  
   914  func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   915  	eip, total, err := self.host.zone.region.GetEips("", self.InstanceId, 0, 1)
   916  	if err != nil {
   917  		return nil, err
   918  	}
   919  	if total == 1 {
   920  		return &eip[0], nil
   921  	}
   922  	self.Refresh()
   923  	for _, address := range self.PublicIpAddresses {
   924  		eip := SEipAddress{region: self.host.zone.region}
   925  		eip.AddressIp = address
   926  		eip.InstanceId = self.InstanceId
   927  		eip.AddressId = self.InstanceId
   928  		eip.AddressName = address
   929  		eip.AddressType = EIP_TYPE_WANIP
   930  		eip.AddressStatus = EIP_STATUS_BIND
   931  		eip.Bandwidth = self.InternetAccessible.InternetMaxBandwidthOut
   932  		return &eip, nil
   933  	}
   934  	return nil, nil
   935  }
   936  
   937  func (self *SInstance) GetBillingType() string {
   938  	switch self.InstanceChargeType {
   939  	case PrePaidInstanceChargeType:
   940  		return billing_api.BILLING_TYPE_PREPAID
   941  	case PostPaidInstanceChargeType:
   942  		return billing_api.BILLING_TYPE_POSTPAID
   943  	default:
   944  		return billing_api.BILLING_TYPE_PREPAID
   945  	}
   946  }
   947  
   948  func (self *SInstance) GetCreatedAt() time.Time {
   949  	return self.CreatedTime
   950  }
   951  
   952  func (self *SInstance) GetExpiredAt() time.Time {
   953  	return self.ExpiredTime
   954  }
   955  
   956  func (self *SInstance) UpdateUserData(userData string) error {
   957  	return cloudprovider.ErrNotSupported
   958  }
   959  
   960  func (self *SInstance) Renew(bc billing.SBillingCycle) error {
   961  	return self.host.zone.region.RenewInstances([]string{self.InstanceId}, bc)
   962  }
   963  
   964  func (region *SRegion) RenewInstances(instanceId []string, bc billing.SBillingCycle) error {
   965  	params := make(map[string]string)
   966  	for i := 0; i < len(instanceId); i += 1 {
   967  		params[fmt.Sprintf("InstanceIds.%d", i)] = instanceId[i]
   968  	}
   969  	params["InstanceChargePrepaid.Period"] = fmt.Sprintf("%d", bc.GetMonths())
   970  	params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_MANUAL_RENEW"
   971  	params["RenewPortableDataDisk"] = "TRUE"
   972  	_, err := region.cvmRequest("RenewInstances", params, true)
   973  	if err != nil {
   974  		log.Errorf("RenewInstance fail %s", err)
   975  		return err
   976  	}
   977  	return nil
   978  }
   979  
   980  func (self *SInstance) GetProjectId() string {
   981  	return strconv.Itoa(self.Placement.ProjectId)
   982  }
   983  
   984  func (self *SInstance) GetError() error {
   985  	return nil
   986  }
   987  
   988  func (region *SRegion) ConvertPublicIpToEip(instanceId string) error {
   989  	params := map[string]string{
   990  		"InstanceId": instanceId,
   991  		"Region":     region.Region,
   992  	}
   993  	_, err := region.vpcRequest("TransformAddress", params)
   994  	if err != nil {
   995  		log.Errorf("TransformAddress fail %s", err)
   996  		return err
   997  	}
   998  	return nil
   999  }
  1000  
  1001  func (self *SInstance) ConvertPublicIpToEip() error {
  1002  	return self.host.zone.region.ConvertPublicIpToEip(self.InstanceId)
  1003  }
  1004  
  1005  func (self *SInstance) IsAutoRenew() bool {
  1006  	return self.RenewFlag == "NOTIFY_AND_AUTO_RENEW"
  1007  }
  1008  
  1009  // https://cloud.tencent.com/document/api/213/15752
  1010  func (region *SRegion) SetInstanceAutoRenew(instanceId string, autoRenew bool) error {
  1011  	params := map[string]string{
  1012  		"InstanceIds.0": instanceId,
  1013  		"Region":        region.Region,
  1014  		"RenewFlag":     "NOTIFY_AND_MANUAL_RENEW",
  1015  	}
  1016  	if autoRenew {
  1017  		params["RenewFlag"] = "NOTIFY_AND_AUTO_RENEW"
  1018  	}
  1019  	_, err := region.cvmRequest("ModifyInstancesRenewFlag", params, true)
  1020  	return err
  1021  }
  1022  
  1023  func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error {
  1024  	return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc.AutoRenew)
  1025  }
  1026  
  1027  func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  1028  	return self.host.zone.region.SetResourceTags("cvm", "instance", []string{self.InstanceId}, tags, replace)
  1029  }
  1030  
  1031  func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  1032  	params := map[string]string{
  1033  		"ImageName":  opts.Name,
  1034  		"InstanceId": instanceId,
  1035  	}
  1036  	if len(opts.Notes) > 0 {
  1037  		params["ImageDescription"] = opts.Notes
  1038  	}
  1039  	resp, err := self.cvmRequest("CreateImage", params, true)
  1040  	if err != nil {
  1041  		return nil, errors.Wrapf(err, "CreateImage")
  1042  	}
  1043  	ret := struct{ ImageId string }{}
  1044  	err = resp.Unmarshal(&ret)
  1045  	if err != nil {
  1046  		return nil, errors.Wrapf(err, "resp.Unmarshal")
  1047  	}
  1048  	imageIds := []string{}
  1049  	if len(ret.ImageId) > 0 {
  1050  		imageIds = append(imageIds, ret.ImageId)
  1051  	}
  1052  
  1053  	images, _, err := self.GetImages("", "PRIVATE_IMAGE", imageIds, opts.Name, 0, 1)
  1054  	if err != nil {
  1055  		return nil, errors.Wrapf(err, "GetImage(%s,%s)", opts.Name, ret.ImageId)
  1056  	}
  1057  	for i := range images {
  1058  		if images[i].ImageId == ret.ImageId || images[i].ImageName == opts.Name {
  1059  			images[i].storageCache = self.getStoragecache()
  1060  			return &images[i], nil
  1061  		}
  1062  	}
  1063  	return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after save image %s", opts.Name)
  1064  }
  1065  
  1066  func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1067  	image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
  1068  	if err != nil {
  1069  		return nil, errors.Wrapf(err, "SaveImage")
  1070  	}
  1071  	return image, nil
  1072  }