yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ctyun/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 ctyun 16 17 import ( 18 "context" 19 "fmt" 20 "strconv" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 "yunion.io/x/pkg/utils" 28 29 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 30 api "yunion.io/x/cloudmux/pkg/apis/compute" 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 "yunion.io/x/onecloud/pkg/util/billing" 34 ) 35 36 type SInstance struct { 37 multicloud.SInstanceBase 38 CtyunTags 39 multicloud.SBillingBase 40 41 host *SHost 42 image *SImage 43 44 vmDetails *InstanceDetails 45 46 HostID string `json:"hostId"` 47 ID string `json:"id"` 48 Name string `json:"name"` 49 Status string `json:"status"` 50 TenantID string `json:"tenant_id"` 51 Metadata Metadata `json:"metadata"` 52 Image Image `json:"image"` 53 Flavor FlavorObj `json:"flavor"` 54 Addresses map[string][]Address `json:"addresses"` 55 UserID string `json:"user_id"` 56 Created time.Time `json:"created"` 57 DueDate *time.Time `json:"dueDate"` 58 SecurityGroups []SecurityGroup `json:"security_groups"` 59 OSEXTAZAvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` 60 OSExtendedVolumesVolumesAttached []Volume `json:"os-extended-volumes:volumes_attached"` 61 MasterOrderId string `json:"masterOrderId"` 62 } 63 64 type InstanceDetails struct { 65 HostID string `json:"hostId"` 66 Name string `json:"name"` 67 Status string `json:"status"` 68 PrivateIPS []PrivateIP `json:"privateIps"` 69 PublicIPS []PublicIP `json:"publicIps"` 70 Volumes []Volume `json:"volumes"` 71 Created string `json:"created"` 72 FlavorObj FlavorObj `json:"flavorObj"` 73 } 74 75 type Address struct { 76 Addr string `json:"addr"` 77 OSEXTIPSType string `json:"OS-EXT-IPS:type"` 78 Version int64 `json:"version"` 79 OSEXTIPSMACMACAddr string `json:"OS-EXT-IPS-MAC:mac_addr"` 80 } 81 82 type Image struct { 83 Id string `json:"id"` 84 } 85 86 type SecurityGroup struct { 87 Name string `json:"name"` 88 } 89 90 type FlavorObj struct { 91 Name string `json:"name"` 92 CPUNum int `json:"cpuNum"` 93 MemSize int `json:"memSize"` 94 ID string `json:"id"` 95 } 96 97 type PrivateIP struct { 98 ID string `json:"id"` 99 Address string `json:"address"` 100 } 101 102 type PublicP struct { 103 ID string `json:"id"` 104 Address string `json:"address"` 105 Bandwidth string `json:"bandwidth"` 106 } 107 108 func (self *SInstance) GetBillingType() string { 109 if self.DueDate != nil { 110 return billing_api.BILLING_TYPE_PREPAID 111 } else { 112 return billing_api.BILLING_TYPE_POSTPAID 113 } 114 } 115 116 func (self *SInstance) GetCreatedAt() time.Time { 117 return self.Created 118 } 119 120 func (self *SInstance) GetExpiredAt() time.Time { 121 if self.DueDate == nil { 122 return time.Time{} 123 } 124 125 return *self.DueDate 126 } 127 128 func (self *SInstance) GetId() string { 129 return self.ID 130 } 131 132 func (self *SInstance) GetName() string { 133 return self.Name 134 } 135 136 func (self *SInstance) GetHostname() string { 137 return self.Name 138 } 139 140 func (self *SInstance) GetGlobalId() string { 141 return self.GetId() 142 } 143 144 func (self *SInstance) GetStatus() string { 145 switch self.Status { 146 case "RUNNING", "ACTIVE": 147 return api.VM_RUNNING 148 case "RESTARTING", "BUILD", "RESIZE", "VERIFY_RESIZE": 149 return api.VM_STARTING 150 case "STOPPING", "HARD_REBOOT": 151 return api.VM_STOPPING 152 case "STOPPED", "SHUTOFF": 153 return api.VM_READY 154 default: 155 return api.VM_UNKNOWN 156 } 157 } 158 159 func (self *SInstance) Refresh() error { 160 new, err := self.host.zone.region.GetVMById(self.GetId()) 161 if err != nil { 162 return err 163 } 164 165 new.host = self.host 166 if err != nil { 167 return err 168 } 169 170 if new.Status == "DELETED" { 171 log.Debugf("Instance already terminated.") 172 return cloudprovider.ErrNotFound 173 } 174 175 // update details 176 detail, err := self.host.zone.region.GetVMDetails(self.GetId()) 177 if err != nil { 178 return errors.Wrap(err, "SInstance.Refresh.GetDetails") 179 } 180 181 self.vmDetails = detail 182 return jsonutils.Update(self, new) 183 } 184 185 func (self *SInstance) IsEmulated() bool { 186 return false 187 } 188 189 func (self *SInstance) GetProjectId() string { 190 return "" 191 } 192 193 func (self *SInstance) GetIHost() cloudprovider.ICloudHost { 194 return self.host 195 } 196 197 // GET http://ctyun-api-url/apiproxy/v3/queryDataDiskByVMId 198 func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 199 details, err := self.GetDetails() 200 if err != nil { 201 return nil, errors.Wrap(err, "SInstance.GetIDisks.GetDetails") 202 } 203 204 disks := []SDisk{} 205 for i := range details.Volumes { 206 volume := details.Volumes[i] 207 disk, err := self.host.zone.region.GetDisk(volume.ID) 208 if err != nil { 209 return nil, errors.Wrap(err, "SInstance.GetIDisks.GetDisk") 210 } 211 212 disks = append(disks, *disk) 213 } 214 215 for i := 0; i < len(disks); i += 1 { 216 // 将系统盘放到第0个位置 217 if disks[i].Bootable == "true" { 218 _temp := disks[0] 219 disks[0] = disks[i] 220 disks[i] = _temp 221 } 222 } 223 224 idisks := make([]cloudprovider.ICloudDisk, len(disks)) 225 for i := range disks { 226 disk := disks[i] 227 idisks[i] = &disk 228 } 229 230 return idisks, nil 231 } 232 233 func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 234 nics, err := self.host.zone.region.GetNics(self.GetId()) 235 if err != nil { 236 return nil, errors.Wrap(err, "SInstance.GetINics") 237 } 238 239 inics := make([]cloudprovider.ICloudNic, len(nics)) 240 for i := range nics { 241 inics[i] = &nics[i] 242 } 243 244 return inics, nil 245 } 246 247 // GET http://ctyun-api-urlapiproxy/v3/queryNetworkByVMId 248 func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 249 detail, err := self.GetDetails() 250 if err != nil { 251 return nil, errors.Wrap(err, "SInstance.GetIEIP.GetDetails") 252 } 253 254 if len(detail.PublicIPS) > 0 { 255 return self.host.zone.region.GetEip(detail.PublicIPS[0].ID) 256 } 257 258 return nil, nil 259 } 260 261 func (self *SInstance) GetDetails() (*InstanceDetails, error) { 262 if self.vmDetails != nil { 263 return self.vmDetails, nil 264 } 265 266 detail, err := self.host.zone.region.GetVMDetails(self.GetId()) 267 if err != nil { 268 return nil, errors.Wrap(err, "SInstance.GetDetails") 269 } 270 271 self.vmDetails = detail 272 return self.vmDetails, nil 273 } 274 275 func (self *SInstance) GetVcpuCount() int { 276 details, err := self.GetDetails() 277 if err == nil { 278 return details.FlavorObj.CPUNum 279 } 280 281 return self.Flavor.CPUNum 282 } 283 284 func (self *SInstance) GetVmemSizeMB() int { 285 details, err := self.GetDetails() 286 if err == nil { 287 return details.FlavorObj.MemSize * 1024 288 } 289 290 return self.Flavor.MemSize * 1024 291 } 292 293 func (self *SInstance) GetBootOrder() string { 294 return "dcn" 295 } 296 297 func (self *SInstance) GetVga() string { 298 return "std" 299 } 300 301 func (self *SInstance) GetVdi() string { 302 return "vnc" 303 } 304 305 func (self *SInstance) GetImage() (*SImage, error) { 306 if self.image != nil { 307 return self.image, nil 308 } 309 310 image, err := self.host.zone.region.GetImage(self.Image.Id) 311 if err != nil { 312 return nil, errors.Wrap(err, "SInstance.GetImage") 313 } 314 315 self.image = image 316 return self.image, nil 317 } 318 319 func (self *SInstance) GetOsType() cloudprovider.TOsType { 320 image, err := self.GetImage() 321 if err != nil { 322 log.Errorf("SInstance.Image %s", err) 323 return cloudprovider.OsTypeLinux 324 } 325 return image.GetOsType() 326 } 327 328 func (self *SInstance) GetFullOsName() string { 329 image, err := self.GetImage() 330 if err != nil { 331 log.Errorf("SInstance.Image %s", err) 332 return "" 333 } 334 return image.GetFullOsName() 335 } 336 337 func (self *SInstance) GetBios() cloudprovider.TBiosType { 338 image, err := self.GetImage() 339 if err != nil { 340 log.Errorf("SInstance.Image %s", err) 341 return cloudprovider.BIOS 342 } 343 return image.GetBios() 344 } 345 346 func (self *SInstance) GetOsArch() string { 347 image, err := self.GetImage() 348 if err != nil { 349 log.Errorf("SInstance.Image %s", err) 350 return "" 351 } 352 return image.GetOsArch() 353 } 354 355 func (self *SInstance) GetOsDist() string { 356 image, err := self.GetImage() 357 if err != nil { 358 log.Errorf("SInstance.Image %s", err) 359 return "" 360 } 361 return image.GetOsDist() 362 } 363 364 func (self *SInstance) GetOsVersion() string { 365 image, err := self.GetImage() 366 if err != nil { 367 log.Errorf("SInstance.Image %s", err) 368 return "" 369 } 370 return image.GetOsVersion() 371 } 372 373 func (self *SInstance) GetOsLang() string { 374 image, err := self.GetImage() 375 if err != nil { 376 log.Errorf("SInstance.Image %s", err) 377 return "" 378 } 379 return image.GetOsLang() 380 } 381 382 func (self *SInstance) GetMachine() string { 383 return "pc" 384 } 385 386 func (self *SInstance) GetInstanceType() string { 387 return self.Flavor.ID 388 } 389 390 func (self *SInstance) GetSecurityGroupIds() ([]string, error) { 391 if len(self.SecurityGroups) == 0 { 392 return []string{}, nil 393 } 394 395 if len(self.MasterOrderId) > 0 { 396 return self.getSecurityGroupIdsByMasterOrderId(self.MasterOrderId) 397 } 398 399 secgroups, err := self.host.zone.region.GetSecurityGroups("") 400 if err != nil { 401 return nil, errors.Wrap(err, "SInstance.GetSecurityGroupIds.GetSecurityGroups") 402 } 403 404 names := []string{} 405 for i := range self.SecurityGroups { 406 names = append(names, self.SecurityGroups[i].Name) 407 } 408 409 ids := []string{} 410 for i := range secgroups { 411 // todo: bugfix 如果安全组重名比较尴尬 412 if utils.IsInStringArray(secgroups[i].Name, names) { 413 ids = append(ids, secgroups[i].ResSecurityGroupID) 414 } 415 } 416 417 return ids, nil 418 } 419 420 func (self *SInstance) getSecurityGroupIdsByMasterOrderId(orderId string) ([]string, error) { 421 orders, err := self.host.zone.region.GetOrder(self.MasterOrderId) 422 if err != nil { 423 return nil, errors.Wrap(err, "SInstance.GetSecurityGroupIds.GetOrder") 424 } 425 426 if len(orders) == 0 { 427 return nil, nil 428 } 429 430 for i := range orders { 431 secgroups := orders[i].ResourceConfigMap.SecurityGroups 432 if len(secgroups) > 0 { 433 ids := []string{} 434 for j := range secgroups { 435 ids = append(ids, secgroups[j].ID) 436 } 437 438 return ids, nil 439 } 440 } 441 442 return nil, nil 443 } 444 445 func (self *SInstance) AssignSecurityGroup(secgroupId string) error { 446 return self.host.zone.region.AssignSecurityGroup(self.GetId(), secgroupId) 447 } 448 449 func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { 450 currentIds, err := self.GetSecurityGroupIds() 451 if err != nil { 452 return errors.Wrap(err, "GetSecurityGroupIds") 453 } 454 455 adds := []string{} 456 for i := range secgroupIds { 457 if !utils.IsInStringArray(secgroupIds[i], currentIds) { 458 adds = append(adds, secgroupIds[i]) 459 } 460 } 461 462 for i := range adds { 463 err := self.host.zone.region.AssignSecurityGroup(self.GetId(), adds[i]) 464 if err != nil { 465 return errors.Wrap(err, "Instance.SetSecurityGroups") 466 } 467 } 468 469 removes := []string{} 470 for i := range currentIds { 471 if !utils.IsInStringArray(currentIds[i], secgroupIds) { 472 removes = append(removes, currentIds[i]) 473 } 474 } 475 476 for i := range removes { 477 err := self.host.zone.region.UnsignSecurityGroup(self.GetId(), removes[i]) 478 if err != nil { 479 return errors.Wrap(err, "Instance.SetSecurityGroups") 480 } 481 } 482 483 return nil 484 } 485 486 func (self *SInstance) GetHypervisor() string { 487 return api.HYPERVISOR_CTYUN 488 } 489 490 func (self *SInstance) StartVM(ctx context.Context) error { 491 err := self.host.zone.region.StartVM(self.GetId()) 492 if err != nil { 493 return errors.Wrap(err, "Instance.StartVM") 494 } 495 496 err = cloudprovider.WaitStatus(self, api.VM_RUNNING, 5*time.Second, 300*time.Second) 497 if err != nil { 498 return errors.Wrap(err, "Instance.StartVM.WaitStatus") 499 } 500 return nil 501 } 502 503 func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 504 err := self.host.zone.region.StopVM(self.GetId()) 505 if err != nil { 506 return errors.Wrap(err, "Instance.StopVM") 507 } 508 509 err = cloudprovider.WaitStatus(self, api.VM_READY, 5*time.Second, 300*time.Second) 510 if err != nil { 511 return errors.Wrap(err, "Instance.StopVM.WaitStatus") 512 } 513 return nil 514 } 515 516 func (self *SInstance) DeleteVM(ctx context.Context) error { 517 err := self.host.zone.region.DeleteVM(self.GetId()) 518 if err != nil { 519 return errors.Wrap(err, "SInstance.DeleteVM") 520 } 521 522 err = cloudprovider.WaitDeleted(self, 10*time.Second, 180*time.Second) 523 if err != nil { 524 return errors.Wrap(err, "Instance.DeleteVM.WaitDeleted") 525 } 526 return nil 527 } 528 529 func (self *SInstance) UpdateVM(ctx context.Context, name string) error { 530 return cloudprovider.ErrNotSupported 531 } 532 533 func (self *SInstance) UpdateUserData(userData string) error { 534 return cloudprovider.ErrNotSupported 535 } 536 537 func (self *SInstance) RebuildRoot(ctx context.Context, config *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 538 currentImage, err := self.GetImage() 539 if err != nil { 540 return "", errors.Wrap(err, "Instance.RebuildRoot") 541 } 542 543 publicKeyName := "" 544 if len(config.PublicKey) > 0 { 545 publicKeyName, err = self.host.zone.region.syncKeypair(config.PublicKey) 546 if err != nil { 547 return "", errors.Wrap(err, "Instance.RebuildRoot.syncKeypair") 548 } 549 } 550 551 jobId := "" 552 if currentImage.GetId() != config.ImageId { 553 jobId, err = self.host.zone.region.SwitchVMOs(self.GetId(), config.Password, publicKeyName, config.ImageId) 554 if err != nil { 555 return "", errors.Wrap(err, "SInstance.RebuildRoot.SwitchVMOs") 556 } 557 } else { 558 jobId, err = self.host.zone.region.RebuildVM(self.GetId(), config.Password, publicKeyName) 559 if err != nil { 560 return "", errors.Wrap(err, "SInstance.RebuildRoot.RebuildVM") 561 } 562 } 563 564 err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { 565 statusJson, err := self.host.zone.region.GetJob(jobId) 566 if err != nil { 567 if strings.Contains(err.Error(), "job fail") { 568 return false, err 569 } 570 571 return false, nil 572 } 573 574 if status, _ := statusJson.GetString("status"); status == "SUCCESS" { 575 return true, nil 576 } else if status == "FAILED" { 577 return false, fmt.Errorf("RebuildRoot job %s failed", jobId) 578 } else { 579 return false, nil 580 } 581 }) 582 if err != nil { 583 return "", errors.Wrap(err, "Instance.RebuildRoot.Wait") 584 } 585 586 err = self.Refresh() 587 if err != nil { 588 return "", err 589 } 590 591 idisks, err := self.GetIDisks() 592 if err != nil { 593 return "", err 594 } 595 596 if len(idisks) == 0 { 597 return "", fmt.Errorf("server %s has no volume attached.", self.GetId()) 598 } 599 600 return idisks[0].GetId(), nil 601 } 602 603 func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 604 if len(password) == 0 { 605 return cloudprovider.ErrNotSupported 606 } 607 608 // 只支持重置密码 609 return self.host.zone.region.ResetVMPassword(self.GetId(), password) 610 } 611 612 func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 613 jobId, err := self.host.zone.region.ChangeVMConfig(self.GetId(), config.InstanceType) 614 if err != nil { 615 return errors.Wrap(err, "Instance.ChangeConfig") 616 } 617 618 err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { 619 statusJson, err := self.host.zone.region.GetJob(jobId) 620 if err != nil { 621 if strings.Contains(err.Error(), "job fail") { 622 return false, err 623 } 624 625 return false, nil 626 } 627 628 if status, _ := statusJson.GetString("status"); status == "SUCCESS" { 629 return true, nil 630 } else if status == "FAILED" { 631 return false, fmt.Errorf("ChangeConfig job %s failed", jobId) 632 } else { 633 return false, nil 634 } 635 }) 636 if err != nil { 637 return errors.Wrap(err, "Instance.ChangeConfig.Wait") 638 } 639 640 return nil 641 } 642 643 // http://ctyun-api-url/apiproxy/v3/queryVncUrl 644 func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 645 url, err := self.host.zone.region.GetInstanceVNCUrl(self.GetId()) 646 if err != nil { 647 return nil, err 648 } 649 ret := &cloudprovider.ServerVncOutput{ 650 Url: url, 651 Protocol: "ctyun", 652 InstanceId: self.GetId(), 653 Hypervisor: api.HYPERVISOR_CTYUN, 654 } 655 return ret, nil 656 } 657 658 func (self *SInstance) NextDeviceName() (string, error) { 659 details, err := self.GetDetails() 660 if err != nil { 661 return "", errors.Wrap(err, "SInstance.NextDeviceName.GetDetails") 662 } 663 664 disks := []*SDisk{} 665 for i := range details.Volumes { 666 disk, err := self.host.zone.region.GetDisk(details.Volumes[i].ID) 667 if err != nil { 668 return "", errors.Wrap(err, "SInstance.NextDeviceName.GetDisk") 669 } 670 671 disks = append(disks, disk) 672 } 673 674 prefix := "s" 675 if len(disks) > 0 && strings.Contains(disks[0].GetMountpoint(), "/vd") { 676 prefix = "v" 677 } 678 679 currents := []string{} 680 for _, disk := range disks { 681 currents = append(currents, strings.ToLower(disk.GetMountpoint())) 682 } 683 684 for i := 0; i < 25; i++ { 685 device := fmt.Sprintf("/dev/%sd%s", prefix, string([]byte{byte(98 + i)})) 686 if ok, _ := utils.InStringArray(device, currents); !ok { 687 return device, nil 688 } 689 } 690 691 return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents) 692 } 693 694 func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { 695 device, err := self.NextDeviceName() 696 if err != nil { 697 return errors.Wrap(err, "Instance.AttachDisk.NextDeviceName") 698 } 699 700 _, err = self.host.zone.region.AttachDisk(self.GetId(), diskId, device) 701 if err != nil { 702 return errors.Wrap(err, "Instance.AttachDisk") 703 } 704 705 disk, err := self.host.zone.region.GetDisk(diskId) 706 if err != nil { 707 return errors.Wrap(err, "AttachDisk.GetDisk") 708 } 709 710 err = cloudprovider.WaitStatusWithDelay(disk, api.DISK_READY, 10*time.Second, 5*time.Second, 180*time.Second) 711 if err != nil { 712 return errors.Wrap(err, "Instance.DetachDisk.WaitStatusWithDelay") 713 } 714 715 if disk.Status != "in-use" { 716 return errors.Wrap(fmt.Errorf("disk status %s", disk.Status), "Instance.DetachDisk.Status") 717 } 718 719 return nil 720 } 721 722 func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { 723 disk, err := self.host.zone.region.GetDisk(diskId) 724 if err != nil { 725 return errors.Wrap(err, "DetachDisk.Wait") 726 } 727 728 if len(disk.Attachments) == 0 { 729 return errors.Wrap(err, "Instance.DetachDisk") 730 } 731 732 _, err = self.host.zone.region.DetachDisk(self.GetId(), diskId, disk.Attachments[0].Device) 733 if err != nil { 734 return errors.Wrap(err, "Instance.DetachDisk") 735 } 736 737 err = cloudprovider.WaitStatusWithDelay(disk, api.DISK_READY, 10*time.Second, 5*time.Second, 180*time.Second) 738 if err != nil { 739 return errors.Wrap(err, "Instance.DetachDisk.WaitStatusWithDelay") 740 } 741 742 if disk.Status != "available" { 743 return errors.Wrap(fmt.Errorf("disk status %s", disk.Status), "Instance.DetachDisk.Status") 744 } 745 746 return nil 747 } 748 749 func (self *SInstance) Renew(bc billing.SBillingCycle) error { 750 _, err := self.host.zone.region.RenewVM(self.GetId(), &bc) 751 if err != nil { 752 return errors.Wrap(err, "Instance.Renew.RenewVM") 753 } 754 755 return nil 756 } 757 758 func (self *SInstance) GetError() error { 759 return nil 760 } 761 762 type SDiskDetails struct { 763 HostID string `json:"hostId"` 764 Name string `json:"name"` 765 Status string `json:"status"` 766 PrivateIPS []PrivateIP `json:"privateIps"` 767 PublicIPS []PublicIP `json:"publicIps"` 768 Volumes []Volume `json:"volumes"` 769 Created string `json:"created"` 770 FlavorObj FlavorObj `json:"flavorObj"` 771 } 772 773 type PublicIP struct { 774 ID string `json:"id"` 775 Address string `json:"address"` 776 Bandwidth string `json:"bandwidth"` 777 } 778 779 type Volume struct { 780 ID string `json:"id"` 781 Status string `json:"status"` 782 Type string `json:"type"` 783 Size string `json:"size"` 784 Name string `json:"name"` 785 Bootable bool `json:"bootable"` 786 } 787 788 func (self *SRegion) GetVMDetails(vmId string) (*InstanceDetails, error) { 789 params := map[string]string{ 790 "regionId": self.GetId(), 791 "vmId": vmId, 792 } 793 794 resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVMDetail", params) 795 if err != nil { 796 return nil, errors.Wrap(err, "SRegion.GetVMDetails.DoGet") 797 } 798 799 details := &InstanceDetails{} 800 err = resp.Unmarshal(details, "returnObj") 801 if err != nil { 802 return nil, errors.Wrap(err, "SRegion.GetVMDetails.Unmarshal") 803 } 804 805 return details, nil 806 } 807 808 type SVncInfo struct { 809 Type string `json:"type"` 810 URL string `json:"url"` 811 } 812 813 func (self *SRegion) GetInstanceVNCUrl(vmId string) (string, error) { 814 params := map[string]string{ 815 "regionId": self.GetId(), 816 "vmId": vmId, 817 } 818 819 resp, err := self.client.DoGet("/apiproxy/v3/queryVncUrl", params) 820 if err != nil { 821 return "", errors.Wrap(err, "") 822 } 823 824 ret := SVncInfo{} 825 err = resp.Unmarshal(&ret, "returnObj", "console") 826 if err != nil { 827 return "", errors.Wrap(err, "") 828 } 829 830 return ret.URL, nil 831 } 832 833 /* 834 创建主机接口目前没有绑定密钥的参数选项,不支持绑定密码。 835 但是重装系统接口支持绑定密钥 836 */ 837 func (self *SRegion) CreateInstance(zoneId, name, imageId, osType, flavorRef, vpcid, subnetId, secGroupId, adminPass, volumetype string, volumeSize int, dataDisks []cloudprovider.SDiskInfo) (string, error) { 838 rootParams := jsonutils.NewDict() 839 rootParams.Set("volumetype", jsonutils.NewString(volumetype)) 840 if volumeSize > 0 { 841 rootParams.Set("size", jsonutils.NewInt(int64(volumeSize))) 842 } 843 844 nicParams := jsonutils.NewArray() 845 nicParam := jsonutils.NewDict() 846 nicParam.Set("subnet_id", jsonutils.NewString(subnetId)) 847 nicParams.Add(nicParam) 848 849 secgroupParams := jsonutils.NewArray() 850 secgroupParam := jsonutils.NewDict() 851 secgroupParam.Set("id", jsonutils.NewString(secGroupId)) 852 secgroupParams.Add(secgroupParam) 853 854 extParams := jsonutils.NewDict() 855 extParams.Set("regionID", jsonutils.NewString(self.GetId())) 856 857 serverParams := jsonutils.NewDict() 858 serverParams.Set("availability_zone", jsonutils.NewString(zoneId)) 859 serverParams.Set("name", jsonutils.NewString(name)) 860 serverParams.Set("imageRef", jsonutils.NewString(imageId)) 861 serverParams.Set("root_volume", rootParams) 862 serverParams.Set("flavorRef", jsonutils.NewString(flavorRef)) 863 serverParams.Set("osType", jsonutils.NewString(osType)) 864 serverParams.Set("vpcid", jsonutils.NewString(vpcid)) 865 serverParams.Set("security_groups", secgroupParams) 866 serverParams.Set("nics", nicParams) 867 serverParams.Set("adminPass", jsonutils.NewString(adminPass)) 868 serverParams.Set("count", jsonutils.NewString("1")) 869 serverParams.Set("extendparam", extParams) 870 871 if dataDisks != nil && len(dataDisks) > 0 { 872 dataDisksParams := jsonutils.NewArray() 873 for i := range dataDisks { 874 dataDiskParams := jsonutils.NewDict() 875 dataDiskParams.Set("volumetype", jsonutils.NewString(dataDisks[i].StorageType)) 876 dataDiskParams.Set("size", jsonutils.NewInt(int64(dataDisks[i].SizeGB))) 877 dataDisksParams.Add(dataDiskParams) 878 } 879 880 serverParams.Set("data_volumes", dataDisksParams) 881 } 882 883 vmParams := jsonutils.NewDict() 884 vmParams.Set("server", serverParams) 885 886 params := map[string]jsonutils.JSONObject{ 887 "createVMInfo": vmParams, 888 } 889 890 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVM", params) 891 if err != nil { 892 return "", errors.Wrap(err, "SRegion.CreateInstance.DoPost") 893 } 894 895 var ok bool 896 err = resp.Unmarshal(&ok, "returnObj", "status") 897 if !ok { 898 msg, _ := resp.GetString("returnObj", "message") 899 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.CreateInstance.JobFailed") 900 } 901 902 var jobId string 903 err = resp.Unmarshal(&jobId, "returnObj", "data") 904 if err != nil { 905 return "", errors.Wrap(err, "SRegion.CreateInstance.Unmarshal") 906 } 907 908 return jobId, nil 909 } 910 911 // vm & nic job 912 func (self *SRegion) GetJob(jobId string) (jsonutils.JSONObject, error) { 913 params := map[string]string{ 914 "regionId": self.GetId(), 915 "jobId": jobId, 916 } 917 918 resp, err := self.client.DoGet("/apiproxy/v3/queryJobStatus", params) 919 if err != nil { 920 return nil, errors.Wrap(err, "SRegion.GetJob.DoGet") 921 } 922 923 ret := jsonutils.NewDict() 924 err = resp.Unmarshal(&ret, "returnObj") 925 if err != nil { 926 return nil, errors.Wrap(err, "SRegion.GetJob.Unmarshal") 927 } 928 929 return ret, nil 930 } 931 932 // 查询云硬盘备份JOB状态信息 933 func (self *SRegion) GetVbsJob(jobId string) (jsonutils.JSONObject, error) { 934 params := map[string]string{ 935 "regionId": self.GetId(), 936 "jobId": jobId, 937 } 938 939 resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVbsJob", params) 940 if err != nil { 941 return nil, errors.Wrap(err, "SRegion.GetVbsJob.DoGet") 942 } 943 944 ret := jsonutils.NewDict() 945 err = resp.Unmarshal(&ret, "returnObj") 946 if err != nil { 947 return nil, errors.Wrap(err, "SRegion.GetVbsJob.Unmarshal") 948 } 949 950 return ret, nil 951 } 952 953 // 查询云硬盘JOB状态信息 954 func (self *SRegion) GetVolumeJob(jobId string) (jsonutils.JSONObject, error) { 955 params := map[string]string{ 956 "regionId": self.GetId(), 957 "jobId": jobId, 958 } 959 960 resp, err := self.client.DoGet("/apiproxy/v3/queryVolumeJob", params) 961 if err != nil { 962 return nil, errors.Wrap(err, "SRegion.GetVolumeJob.DoGet") 963 } 964 965 ret := jsonutils.NewDict() 966 err = resp.Unmarshal(&ret, "returnObj") 967 if err != nil { 968 return nil, errors.Wrap(err, "SRegion.GetVolumeJob.Unmarshal") 969 } 970 971 return ret, nil 972 } 973 974 // POST http://ctyun-api-url/apiproxy/v3/addSecurityGroup 绑定安全组 975 func (self *SRegion) AssignSecurityGroup(vmId, securityGroupRuleId string) error { 976 securityParams := jsonutils.NewDict() 977 securityParams.Set("regionId", jsonutils.NewString(self.GetId())) 978 securityParams.Set("vmId", jsonutils.NewString(vmId)) 979 securityParams.Set("securityGroupRuleId", jsonutils.NewString(securityGroupRuleId)) 980 981 params := map[string]jsonutils.JSONObject{ 982 "securityGroup": securityParams, 983 } 984 985 _, err := self.client.DoPost("/apiproxy/v3/addSecurityGroup", params) 986 if err != nil { 987 return errors.Wrap(err, "SRegion.AssignSecurityGroup.DoPost") 988 } 989 990 return nil 991 } 992 993 // POST http://ctyun-api-url/apiproxy/v3/removeSecurityGroup 解绑安全组 994 func (self *SRegion) UnsignSecurityGroup(vmId, securityGroupRuleId string) error { 995 securityParams := jsonutils.NewDict() 996 securityParams.Set("regionId", jsonutils.NewString(self.GetId())) 997 securityParams.Set("vmId", jsonutils.NewString(vmId)) 998 securityParams.Set("securityGroupRuleId", jsonutils.NewString(securityGroupRuleId)) 999 1000 params := map[string]jsonutils.JSONObject{ 1001 "securityGroup": securityParams, 1002 } 1003 1004 _, err := self.client.DoPost("/apiproxy/v3/removeSecurityGroup", params) 1005 if err != nil { 1006 return errors.Wrap(err, "SRegion.UnsignSecurityGroup.DoPost") 1007 } 1008 1009 return nil 1010 } 1011 1012 func (self *SRegion) StartVM(vmId string) error { 1013 params := map[string]jsonutils.JSONObject{ 1014 "regionId": jsonutils.NewString(self.GetId()), 1015 "vmId": jsonutils.NewString(vmId), 1016 } 1017 1018 _, err := self.client.DoPost("/apiproxy/v3/ondemand/startVM", params) 1019 if err != nil { 1020 return errors.Wrap(err, "SRegion.StartVm.DoPost") 1021 } 1022 1023 return nil 1024 } 1025 1026 func (self *SRegion) StopVM(vmId string) error { 1027 params := map[string]jsonutils.JSONObject{ 1028 "regionId": jsonutils.NewString(self.GetId()), 1029 "vmId": jsonutils.NewString(vmId), 1030 } 1031 1032 _, err := self.client.DoPost("/apiproxy/v3/ondemand/stopVM", params) 1033 if err != nil { 1034 return errors.Wrap(err, "SRegion.StopVM.DoPost") 1035 } 1036 1037 return nil 1038 } 1039 1040 func (self *SRegion) DeleteVM(vmId string) error { 1041 params := map[string]jsonutils.JSONObject{ 1042 "regionId": jsonutils.NewString(self.GetId()), 1043 "vmId": jsonutils.NewString(vmId), 1044 } 1045 1046 _, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVM", params) 1047 if err != nil { 1048 return errors.Wrap(err, "SRegion.DeleteVM.DoPost") 1049 } 1050 1051 return nil 1052 } 1053 1054 func (self *SRegion) RestartVM(vmId string) error { 1055 params := map[string]jsonutils.JSONObject{ 1056 "regionId": jsonutils.NewString(self.GetId()), 1057 "vmId": jsonutils.NewString(vmId), 1058 "type": jsonutils.NewString("SOFT"), 1059 } 1060 1061 _, err := self.client.DoPost("/apiproxy/v3/ondemand/restartVM", params) 1062 if err != nil { 1063 return errors.Wrap(err, "SRegion.RestartVM.DoPost") 1064 } 1065 1066 return nil 1067 } 1068 1069 func (self *SRegion) SwitchVMOs(vmId, adminPass, keyName, imageRef string) (string, error) { 1070 params := map[string]jsonutils.JSONObject{ 1071 "regionId": jsonutils.NewString(self.GetId()), 1072 "vmId": jsonutils.NewString(vmId), 1073 "imageRef": jsonutils.NewString(imageRef), 1074 } 1075 1076 if len(keyName) > 0 { 1077 params["keyName"] = jsonutils.NewString(keyName) 1078 } else if len(adminPass) > 0 { 1079 params["adminPass"] = jsonutils.NewString(adminPass) 1080 } else { 1081 return "", errors.Wrap(fmt.Errorf("require public key or password"), "SRegion.SwitchVMOs") 1082 } 1083 1084 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/switchSys", params) 1085 if err != nil { 1086 return "", errors.Wrap(err, "SRegion.SwitchVMOs.DoPost") 1087 } 1088 1089 var ok bool 1090 err = resp.Unmarshal(&ok, "returnObj", "status") 1091 if !ok { 1092 msg, _ := resp.GetString("returnObj", "message") 1093 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.SwitchVMOs.JobFailed") 1094 } 1095 1096 var jobId string 1097 err = resp.Unmarshal(&jobId, "returnObj", "data") 1098 if err != nil { 1099 return "", errors.Wrap(err, "SRegion.SwitchVMOs.Unmarshal") 1100 } 1101 1102 return jobId, nil 1103 } 1104 1105 func (self *SRegion) RebuildVM(vmId, adminPass, keyName string) (string, error) { 1106 params := map[string]jsonutils.JSONObject{ 1107 "regionId": jsonutils.NewString(self.GetId()), 1108 "vmId": jsonutils.NewString(vmId), 1109 } 1110 1111 if len(keyName) > 0 { 1112 params["keyName"] = jsonutils.NewString(keyName) 1113 } else if len(adminPass) > 0 { 1114 params["adminPass"] = jsonutils.NewString(adminPass) 1115 } else { 1116 return "", errors.Wrap(fmt.Errorf("require public key or password"), "SRegion.RebuildVM") 1117 } 1118 1119 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/reInstallSys", params) 1120 if err != nil { 1121 return "", errors.Wrap(err, "SRegion.RebuildVM.DoPost") 1122 } 1123 1124 var ok bool 1125 err = resp.Unmarshal(&ok, "returnObj", "status") 1126 if !ok { 1127 msg, _ := resp.GetString("returnObj", "message") 1128 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RebuildVM.JobFailed") 1129 } 1130 1131 var jobId string 1132 err = resp.Unmarshal(&jobId, "returnObj", "data") 1133 if err != nil { 1134 return "", errors.Wrap(err, "SRegion.RebuildVM.Unmarshal") 1135 } 1136 1137 return jobId, nil 1138 } 1139 1140 func (self *SRegion) AttachDisk(vmId, volumeId, device string) (string, error) { 1141 params := map[string]jsonutils.JSONObject{ 1142 "regionId": jsonutils.NewString(self.GetId()), 1143 "volumeId": jsonutils.NewString(volumeId), 1144 "vmId": jsonutils.NewString(vmId), 1145 "device": jsonutils.NewString(device), 1146 } 1147 1148 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/attachVolume", params) 1149 if err != nil { 1150 return "", errors.Wrap(err, "SRegion.AttachDisk.DoPost") 1151 } 1152 1153 var ok bool 1154 err = resp.Unmarshal(&ok, "returnObj", "status") 1155 if !ok { 1156 msg, _ := resp.GetString("returnObj", "message") 1157 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.AttachDisk.JobFailed") 1158 } 1159 1160 var jobId string 1161 err = resp.Unmarshal(&jobId, "returnObj", "data") 1162 if err != nil { 1163 return "", errors.Wrap(err, "SRegion.AttachDisk.Unmarshal") 1164 } 1165 1166 return jobId, nil 1167 } 1168 1169 func (self *SRegion) DetachDisk(vmId, volumeId, device string) (string, error) { 1170 params := map[string]jsonutils.JSONObject{ 1171 "regionId": jsonutils.NewString(self.GetId()), 1172 "volumeId": jsonutils.NewString(volumeId), 1173 "vmId": jsonutils.NewString(vmId), 1174 "device": jsonutils.NewString(device), 1175 } 1176 1177 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/uninstallVolume", params) 1178 if err != nil { 1179 return "", errors.Wrap(err, "SRegion.DetachDisk.DoPost") 1180 } 1181 1182 var ok bool 1183 err = resp.Unmarshal(&ok, "returnObj", "status") 1184 if !ok { 1185 msg, _ := resp.GetString("returnObj", "message") 1186 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DetachDisk.JobFailed") 1187 } 1188 1189 var jobId string 1190 err = resp.Unmarshal(&jobId, "returnObj", "data") 1191 if err != nil { 1192 return "", errors.Wrap(err, "SRegion.DetachDisk.Unmarshal") 1193 } 1194 1195 return jobId, nil 1196 } 1197 1198 func (self *SRegion) ChangeVMConfig(vmId, flavorId string) (string, error) { 1199 params := map[string]jsonutils.JSONObject{ 1200 "regionId": jsonutils.NewString(self.GetId()), 1201 "vmId": jsonutils.NewString(vmId), 1202 "flavorId": jsonutils.NewString(flavorId), 1203 } 1204 1205 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/upgradeVM", params) 1206 if err != nil { 1207 return "", errors.Wrap(err, "SRegion.ChangeVMConfig.DoPost") 1208 } 1209 1210 var ok bool 1211 err = resp.Unmarshal(&ok, "returnObj", "status") 1212 if !ok { 1213 msg, _ := resp.GetString("returnObj", "message") 1214 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ChangeVMConfig.JobFailed") 1215 } 1216 1217 var jobId string 1218 err = resp.Unmarshal(&jobId, "returnObj", "data") 1219 if err != nil { 1220 return "", errors.Wrap(err, "SRegion.ChangeVMConfig.Unmarshal") 1221 } 1222 1223 return jobId, nil 1224 } 1225 1226 // POST http://ctyun-api-url/apiproxy/v3/order/placeRenewOrder 续订 1227 func (self *SRegion) RenewVM(vmId string, bc *billing.SBillingCycle) ([]string, error) { 1228 if bc == nil { 1229 return nil, errors.Wrap(fmt.Errorf("SBillingCycle is nil"), "Region.RenewVM") 1230 } 1231 1232 resourcePackage := jsonutils.NewDict() 1233 month := bc.GetMonths() 1234 switch { 1235 case month <= 11: 1236 resourcePackage.Set("cycleCount", jsonutils.NewString(strconv.Itoa(month))) 1237 resourcePackage.Set("cycleType", jsonutils.NewString("3")) 1238 case month == 12: 1239 resourcePackage.Set("cycleCount", jsonutils.NewString("1")) 1240 resourcePackage.Set("cycleType", jsonutils.NewString("5")) 1241 case month == 24: 1242 resourcePackage.Set("cycleCount", jsonutils.NewString("1")) 1243 resourcePackage.Set("cycleType", jsonutils.NewString("6")) 1244 case month == 36: 1245 resourcePackage.Set("cycleCount", jsonutils.NewString("1")) 1246 resourcePackage.Set("cycleType", jsonutils.NewString("7")) 1247 default: 1248 return nil, errors.Wrap(fmt.Errorf("unsupported month duration %d. expected 1~11, 12, 24, 36", month), "Region.RenewVM") 1249 } 1250 1251 vmIds := jsonutils.NewArray() 1252 vmIds.Add(jsonutils.NewString(vmId)) 1253 resourcePackage.Set("resourceIds", vmIds) 1254 1255 params := map[string]jsonutils.JSONObject{ 1256 "resourceDetailJson": resourcePackage, 1257 } 1258 1259 resp, err := self.client.DoPost("/apiproxy/v3/order/placeRenewOrder", params) 1260 if err != nil { 1261 return nil, errors.Wrap(err, "SRegion.RenewVM.DoPost") 1262 } 1263 1264 var ok bool 1265 err = resp.Unmarshal(&ok, "returnObj", "submitted") 1266 if !ok { 1267 msg, _ := resp.GetString("returnObj", "message") 1268 return nil, errors.Wrap(fmt.Errorf(msg), "SRegion.RenewVM.JobFailed") 1269 } 1270 1271 type OrderPlacedEventsElement struct { 1272 ErrorMessage string `json:"errorMessage"` 1273 Submitted bool `json:"submitted"` 1274 NewOrderID string `json:"newOrderId"` 1275 NewOrderNo string `json:"newOrderNo"` 1276 TotalPrice int64 `json:"totalPrice"` 1277 } 1278 1279 orders := []OrderPlacedEventsElement{} 1280 err = resp.Unmarshal(&orders, "returnObj", "orderPlacedEvents") 1281 if err != nil { 1282 return nil, errors.Wrap(err, "SRegion.RenewVM.Unmarshal") 1283 } 1284 1285 orderIds := []string{} 1286 for i := range orders { 1287 orderIds = append(orderIds, orders[i].NewOrderID) 1288 } 1289 1290 return orderIds, nil 1291 } 1292 1293 func (self *SRegion) ResetVMPassword(vmId, password string) error { 1294 params := map[string]jsonutils.JSONObject{ 1295 "regionId": jsonutils.NewString(self.GetId()), 1296 "vmId": jsonutils.NewString(vmId), 1297 "password": jsonutils.NewString(password), 1298 } 1299 1300 _, err := self.client.DoPost("/apiproxy/v3/resetVmPassword", params) 1301 if err != nil { 1302 return errors.Wrap(err, "SRegion.ResetVMPassword.DoPost") 1303 } 1304 1305 return nil 1306 }