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