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] 和 [( ) ~ ! @ # $ % ^ & * - + = | { } [ ] : ; ' , . ? / ]中的特殊符号。<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 }