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