yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/zstack/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 zstack 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 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 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/cloudmux/pkg/multicloud" 32 "yunion.io/x/onecloud/pkg/util/billing" 33 "yunion.io/x/onecloud/pkg/util/imagetools" 34 "yunion.io/x/onecloud/pkg/util/version" 35 ) 36 37 type SInstanceCdrome struct { 38 } 39 40 type SInstance struct { 41 multicloud.SInstanceBase 42 ZStackTags 43 host *SHost 44 45 osInfo *imagetools.ImageInfo 46 47 ZStackBasic 48 ZoneUUID string `json:"zoneUuid"` 49 ClusterUUID string `json:"clusterUuid"` 50 HostUUID string `json:"hostUuid"` 51 LastHostUUID string `json:"lastHostUuid"` 52 RootVolumeUUID string `json:"rootVolumeUuid"` 53 Platform string `json:"platform"` 54 InstanceOfferingUUID string `json:"instanceOfferingUuid"` 55 56 DefaultL3NetworkUUID string `json:"defaultL3NetworkUuid"` 57 Type string `json:"type"` 58 HypervisorType string `json:"hypervisorType"` 59 MemorySize int `json:"memorySize"` 60 CPUNum int `json:"cpuNum"` 61 CPUSpeed int `json:"cpuSpeed"` 62 State string `json:"state"` 63 InternalID string `json:"internalId"` 64 VMNics []SInstanceNic `json:"vmNics"` 65 AllVolumes []SDisk `json:"allVolumes"` 66 VMCdRoms []SInstanceCdrome `json:"vmCdRoms"` 67 ZStackTime 68 } 69 70 func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) { 71 instance := &SInstance{} 72 err := region.client.getResource("vm-instances", instanceId, instance) 73 if err != nil { 74 return nil, err 75 } 76 if instance.State == "Destroyed" { 77 return nil, cloudprovider.ErrNotFound 78 } 79 return instance, nil 80 } 81 82 func (region *SRegion) GetInstances(hostId string, instanceId string, nicId string) ([]SInstance, error) { 83 instance := []SInstance{} 84 params := url.Values{} 85 params.Add("q", "type=UserVm") 86 params.Add("q", "state!=Destroyed") 87 if len(hostId) > 0 { 88 params.Add("q", "lastHostUuid="+hostId) 89 } 90 if len(instanceId) > 0 { 91 params.Add("q", "uuid="+instanceId) 92 } 93 if len(nicId) > 0 { 94 params.Add("q", "vmNics.uuid="+nicId) 95 } 96 if SkipEsxi { 97 params.Add("q", "hypervisorType!=ESX") 98 } 99 return instance, region.client.listAll("vm-instances", params, &instance) 100 } 101 102 func (instance *SInstance) GetSecurityGroupIds() ([]string, error) { 103 ids := []string{} 104 secgroups, err := instance.host.zone.region.GetSecurityGroups("", instance.UUID, "") 105 if err != nil { 106 return nil, err 107 } 108 for _, secgroup := range secgroups { 109 ids = append(ids, secgroup.UUID) 110 } 111 return ids, nil 112 } 113 114 func (instance *SInstance) GetIHost() cloudprovider.ICloudHost { 115 return instance.host 116 } 117 118 func (instance *SInstance) GetIHostId() string { 119 if len(instance.LastHostUUID) > 0 { 120 return instance.LastHostUUID 121 } 122 return instance.HostUUID 123 } 124 125 func (instance *SInstance) GetId() string { 126 return instance.UUID 127 } 128 129 func (instance *SInstance) GetName() string { 130 return instance.Name 131 } 132 133 func (instance *SInstance) GetHostname() string { 134 return instance.Name 135 } 136 137 func (instance *SInstance) GetGlobalId() string { 138 return instance.GetId() 139 } 140 141 func (instance *SInstance) IsEmulated() bool { 142 return false 143 } 144 145 func (instance *SInstance) GetInstanceType() string { 146 if len(instance.InstanceOfferingUUID) > 0 { 147 offer, err := instance.host.zone.region.GetInstanceOffering(instance.InstanceOfferingUUID) 148 if err == nil { 149 return offer.Name 150 } 151 } 152 return instance.Type 153 } 154 155 func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 156 idisks := []cloudprovider.ICloudDisk{} 157 rootDisk, err := instance.host.zone.region.GetDiskWithStorage(instance.RootVolumeUUID) 158 if err != nil { 159 return nil, err 160 } 161 idisks = append(idisks, rootDisk) 162 for i := 0; i < len(instance.AllVolumes); i++ { 163 if instance.AllVolumes[i].UUID != instance.RootVolumeUUID { 164 dataDisk, err := instance.host.zone.region.GetDiskWithStorage(instance.AllVolumes[i].UUID) 165 if err != nil { 166 return nil, err 167 } 168 idisks = append(idisks, dataDisk) 169 } 170 } 171 return idisks, nil 172 } 173 174 func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 175 iNics := []cloudprovider.ICloudNic{} 176 for i := 0; i < len(instance.VMNics); i++ { 177 instance.VMNics[i].instance = instance 178 iNics = append(iNics, &instance.VMNics[i]) 179 } 180 return iNics, nil 181 } 182 183 func (instance *SInstance) GetVcpuCount() int { 184 return instance.CPUNum 185 } 186 187 func (instance *SInstance) GetVmemSizeMB() int { 188 return instance.MemorySize / 1024 / 1024 189 } 190 191 func (instance *SInstance) GetBootOrder() string { 192 return instance.host.zone.region.GetBootOrder(instance.UUID) 193 } 194 195 func (region *SRegion) GetBootOrder(instanceId string) string { 196 resp, err := region.client.get("vm-instances", instanceId, "boot-orders") 197 if err != nil { 198 return "dcn" 199 } 200 orders := []string{} 201 err = resp.Unmarshal(&orders, "orders") 202 if err != nil { 203 return "dcn" 204 } 205 order := "" 206 for _, _order := range orders { 207 switch _order { 208 case "CdRom": 209 order += "c" 210 case "HardDisk": 211 order += "d" 212 default: 213 log.Errorf("Unknown BootOrder %s for instance %s", _order, instanceId) 214 } 215 } 216 return order 217 } 218 219 func (instance *SInstance) GetVga() string { 220 return "std" 221 } 222 223 func (instance *SInstance) GetVdi() string { 224 return "vnc" 225 } 226 227 func (instance *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo { 228 if instance.osInfo == nil { 229 osInfo := imagetools.NormalizeImageInfo(instance.Platform, "", "", "", "") 230 instance.osInfo = &osInfo 231 } 232 return instance.osInfo 233 } 234 235 func (instance *SInstance) GetOsType() cloudprovider.TOsType { 236 return cloudprovider.TOsType(instance.getNormalizedOsInfo().OsType) 237 } 238 239 func (instance *SInstance) GetFullOsName() string { 240 return instance.Platform 241 } 242 243 func (instance *SInstance) GetBios() cloudprovider.TBiosType { 244 return cloudprovider.ToBiosType(instance.getNormalizedOsInfo().OsBios) 245 } 246 247 func (instance *SInstance) GetOsDist() string { 248 return instance.getNormalizedOsInfo().OsDistro 249 } 250 251 func (instance *SInstance) GetOsVersion() string { 252 return instance.getNormalizedOsInfo().OsVersion 253 } 254 255 func (instance *SInstance) GetOsLang() string { 256 return instance.getNormalizedOsInfo().OsLang 257 } 258 259 func (instance *SInstance) GetOsArch() string { 260 return instance.getNormalizedOsInfo().OsArch 261 } 262 263 func (instance *SInstance) GetMachine() string { 264 return "pc" 265 } 266 267 func (instance *SInstance) GetStatus() string { 268 switch instance.State { 269 case "Stopped": 270 return api.VM_READY 271 case "Running": 272 return api.VM_RUNNING 273 case "Destroyed": 274 return api.VM_DEALLOCATED 275 default: 276 log.Errorf("Unknown instance %s status %s", instance.Name, instance.State) 277 return api.VM_UNKNOWN 278 } 279 } 280 281 func (instance *SInstance) Refresh() error { 282 new, err := instance.host.zone.region.GetInstance(instance.UUID) 283 if err != nil { 284 return err 285 } 286 return jsonutils.Update(instance, new) 287 } 288 289 func (instance *SInstance) GetHypervisor() string { 290 return api.HYPERVISOR_ZSTACK 291 } 292 293 func (instance *SInstance) StartVM(ctx context.Context) error { 294 err := instance.host.zone.region.StartVM(instance.UUID) 295 if err != nil { 296 return err 297 } 298 return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 5*time.Second, 5*time.Minute) 299 } 300 301 func (region *SRegion) StartVM(instanceId string) error { 302 params := map[string]interface{}{ 303 "startVmInstance": jsonutils.NewDict(), 304 } 305 _, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params)) 306 return err 307 } 308 309 func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 310 err := instance.host.zone.region.StopVM(instance.UUID, opts.IsForce) 311 if err != nil { 312 return err 313 } 314 return cloudprovider.WaitStatus(instance, api.VM_READY, 5*time.Second, 5*time.Minute) 315 } 316 317 func (region *SRegion) StopVM(instanceId string, isForce bool) error { 318 option := "grace" 319 if isForce { 320 option = "cold" 321 } 322 params := map[string]interface{}{ 323 "stopVmInstance": map[string]string{ 324 "type": option, 325 "stopHA": "true", 326 }, 327 } 328 _, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params)) 329 return err 330 } 331 332 func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 333 info, err := instance.host.zone.region.GetInstanceConsoleInfo(instance.UUID) 334 if err != nil { 335 return nil, err 336 } 337 authURL, _ := url.Parse(instance.host.zone.region.client.authURL) 338 url := fmt.Sprintf("%s://%s:5000/thirdparty/vnc_auto.html?host=%s&port=%d&token=%s&title=%s", info.Scheme, authURL.Hostname(), info.Hostname, info.Port, info.Token, instance.Name) 339 if ver, _ := instance.host.zone.region.client.GetVersion(); ver != nil { 340 if version.GE(ver.Version, "4.0.0") { 341 url = fmt.Sprintf("%s://%s:5000/novnc/index.html?host=%s&port=%d&token=%s&title=%s&language=zh-CN&lowVersion=false", info.Scheme, authURL.Hostname(), info.Hostname, info.Port, info.Token, instance.Name) 342 } 343 } 344 password, _ := instance.host.zone.region.GetInstanceConsolePassword(instance.UUID) 345 if len(password) > 0 { 346 url = url + fmt.Sprintf("&password=%s", password) 347 } 348 ret := &cloudprovider.ServerVncOutput{ 349 Url: url, 350 Protocol: "zstack", 351 InstanceId: instance.UUID, 352 Hypervisor: api.HYPERVISOR_ZSTACK, 353 } 354 return ret, nil 355 } 356 357 func (instance *SInstance) UpdateVM(ctx context.Context, name string) error { 358 params := map[string]interface{}{ 359 "updateVmInstance": map[string]string{ 360 "name": name, 361 }, 362 } 363 return instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params)) 364 } 365 366 func (region *SRegion) UpdateVM(instanceId string, params jsonutils.JSONObject) error { 367 _, err := region.client.put("vm-instances", instanceId, params) 368 return err 369 } 370 371 func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 372 if instance.Name != name || instance.Description != description { 373 params := map[string]interface{}{ 374 "updateVmInstance": map[string]string{ 375 "name": name, 376 "description": description, 377 }, 378 } 379 err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params)) 380 if err != nil { 381 return err 382 } 383 } 384 if len(password) > 0 { 385 params := map[string]interface{}{ 386 "changeVmPassword": map[string]string{ 387 "account": username, 388 "password": password, 389 }, 390 } 391 err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params)) 392 if err != nil { 393 return err 394 } 395 } 396 if len(publicKey) > 0 { 397 params := map[string]interface{}{ 398 "setVmSshKey": map[string]string{ 399 "SshKey": publicKey, 400 }, 401 } 402 err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params)) 403 if err != nil { 404 return err 405 } 406 } 407 if deleteKeypair { 408 err := instance.host.zone.region.client.delete("vm-instances", fmt.Sprintf("%s/ssh-keys", instance.UUID), "") 409 if err != nil { 410 return err 411 } 412 } 413 414 return nil 415 } 416 417 func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 418 return instance.host.zone.region.RebuildRoot(instance.UUID, desc.ImageId, desc.SysSizeGB) 419 } 420 421 func (region *SRegion) RebuildRoot(instanceId, imageId string, sysSizeGB int) (string, error) { 422 params := map[string]interface{}{ 423 "changeVmImage": map[string]string{ 424 "imageUuid": imageId, 425 }, 426 } 427 instance := &SInstance{} 428 resp, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params)) 429 if err != nil { 430 return "", err 431 } 432 err = resp.Unmarshal(instance, "inventory") 433 if err != nil { 434 return "", err 435 } 436 disk, err := region.GetDisk(instance.RootVolumeUUID) 437 if err != nil { 438 return "", err 439 } 440 if sysSizeGB > disk.GetDiskSizeMB()*1024 { 441 return instance.RootVolumeUUID, region.ResizeDisk(disk.UUID, int64(sysSizeGB)*1024) 442 } 443 return instance.RootVolumeUUID, nil 444 } 445 446 func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 447 offerings, err := instance.host.zone.region.GetInstanceOfferings("", "", config.Cpu, config.MemoryMB) 448 if err != nil { 449 return err 450 } 451 if len(config.InstanceType) > 0 { 452 for _, offering := range offerings { 453 if offering.Name == config.InstanceType { 454 return instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID) 455 } 456 } 457 offering, err := instance.host.zone.region.CreateInstanceOffering(config.InstanceType, config.Cpu, config.MemoryMB, "UserVm") 458 if err != nil { 459 return err 460 } 461 return instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID) 462 } 463 for _, offering := range offerings { 464 log.Debugf("try instance offering %s(%s) ...", offering.Name, offering.UUID) 465 err := instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID) 466 if err != nil { 467 log.Errorf("failed to change config for instance %s(%s) error: %v", instance.Name, instance.UUID, err) 468 } else { 469 return nil 470 } 471 } 472 return fmt.Errorf("Failed to change vm config, specification not supported") 473 } 474 475 func (region *SRegion) ChangeConfig(instanceId, offeringId string) error { 476 params := map[string]interface{}{ 477 "changeInstanceOffering": map[string]string{ 478 "instanceOfferingUuid": offeringId, 479 }, 480 } 481 return region.UpdateVM(instanceId, jsonutils.Marshal(params)) 482 } 483 484 func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error { 485 return instance.host.zone.region.AttachDisk(instance.UUID, diskId) 486 } 487 488 func (region *SRegion) AttachDisk(instanceId string, diskId string) error { 489 _, err := region.client.post(fmt.Sprintf("volumes/%s/vm-instances/%s", diskId, instanceId), jsonutils.NewDict()) 490 return err 491 } 492 493 func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error { 494 return instance.host.zone.region.DetachDisk(instance.UUID, diskId) 495 } 496 497 func (region *SRegion) DetachDisk(instanceId, diskId string) error { 498 url := fmt.Sprintf("volumes/%s/vm-instances?vmUuid=%s", diskId, instanceId) 499 err := region.client.delete(url, "", "") 500 if err != nil && strings.Contains(err.Error(), "is not attached to any vm") { 501 return nil 502 } 503 return err 504 } 505 506 func (instance *SInstance) DeleteVM(ctx context.Context) error { 507 disks, err := instance.GetIDisks() 508 if err != nil { 509 return errors.Wrapf(err, "GetIDisks") 510 } 511 err = instance.host.zone.region.DeleteVM(instance.UUID) 512 if err != nil { 513 return errors.Wrapf(err, "DeleteVM") 514 } 515 for i := range disks { 516 if disks[i].GetDiskType() != api.DISK_TYPE_SYS && disks[i].GetIsAutoDelete() { 517 err = disks[i].Delete(ctx) 518 if err != nil { 519 log.Warningf("delete disk %s failed %s", disks[i].GetId(), err) 520 } 521 } 522 } 523 return nil 524 } 525 526 func (region *SRegion) DeleteVM(instanceId string) error { 527 err := region.client.delete("vm-instances", instanceId, "Enforcing") 528 if err != nil { 529 return err 530 } 531 params := map[string]interface{}{ 532 "expungeVmInstance": jsonutils.NewDict(), 533 } 534 _, err = region.client.put("vm-instances", instanceId, jsonutils.Marshal(params)) 535 if err != nil { 536 return errors.Wrapf(err, "expungeVmInstance") 537 } 538 return nil 539 } 540 541 func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 542 eips, err := instance.host.zone.region.GetEips("", instance.UUID) 543 if err != nil { 544 return nil, err 545 } 546 if len(eips) == 0 { 547 return nil, cloudprovider.ErrNotFound 548 } 549 if len(eips) == 1 { 550 return &eips[0], nil 551 } 552 return nil, cloudprovider.ErrDuplicateId 553 } 554 555 func (instance *SInstance) AssignSecurityGroup(secgroupId string) error { 556 return instance.host.zone.region.AssignSecurityGroup(instance.UUID, secgroupId) 557 } 558 559 func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error { 560 currentIds, err := instance.GetSecurityGroupIds() 561 if err != nil { 562 return err 563 } 564 for _, id := range currentIds { 565 if !utils.IsInStringArray(id, secgroupIds) { 566 err := instance.host.zone.region.RevokeSecurityGroup(instance.UUID, id) 567 if err != nil { 568 return err 569 } 570 } 571 } 572 for _, id := range secgroupIds { 573 if !utils.IsInStringArray(id, currentIds) { 574 err := instance.host.zone.region.AssignSecurityGroup(instance.UUID, id) 575 if err != nil { 576 return err 577 } 578 } 579 } 580 return nil 581 } 582 583 func (region *SRegion) AssignSecurityGroup(instanceId, secgroupId string) error { 584 instance, err := region.GetInstance(instanceId) 585 if err != nil { 586 return err 587 } 588 secgroup, err := region.GetSecurityGroup(secgroupId) 589 if err != nil { 590 return err 591 } 592 if len(instance.VMNics) > 0 { 593 if !utils.IsInStringArray(instance.VMNics[0].L3NetworkUUID, secgroup.AttachedL3NetworkUUIDs) { 594 resource := fmt.Sprintf("security-groups/%s/l3-networks/%s", secgroupId, instance.VMNics[0].L3NetworkUUID) 595 _, err := region.client.post(resource, jsonutils.NewDict()) 596 if err != nil { 597 return err 598 } 599 } 600 params := map[string]interface{}{ 601 "params": map[string]interface{}{ 602 "vmNicUuids": []string{instance.VMNics[0].UUID}, 603 }, 604 } 605 resource := fmt.Sprintf("security-groups/%s/vm-instances/nics", secgroupId) 606 _, err = region.client.post(resource, jsonutils.Marshal(params)) 607 return err 608 } 609 return nil 610 } 611 612 func (region *SRegion) RevokeSecurityGroup(instanceId, secgroupId string) error { 613 instance, err := region.GetInstance(instanceId) 614 if err != nil { 615 return err 616 } 617 for _, nic := range instance.VMNics { 618 resource := fmt.Sprintf("security-groups/%s/vm-instances/nics?vmNicUuids=%s", secgroupId, nic.UUID) 619 err := region.client.delete(resource, "", "") 620 if err != nil { 621 return err 622 } 623 } 624 return nil 625 } 626 627 func (instance *SInstance) GetBillingType() string { 628 return "" 629 } 630 631 func (instance *SInstance) GetCreatedAt() time.Time { 632 return instance.CreateDate 633 } 634 635 func (instance *SInstance) GetExpiredAt() time.Time { 636 return time.Time{} 637 } 638 639 func (instance *SInstance) UpdateUserData(userData string) error { 640 return cloudprovider.ErrNotSupported 641 } 642 643 func (instance *SInstance) Renew(bc billing.SBillingCycle) error { 644 return cloudprovider.ErrNotSupported 645 } 646 647 func (instance *SInstance) GetProjectId() string { 648 return "" 649 } 650 651 func (instance *SInstance) GetError() error { 652 return nil 653 } 654 655 type SConsoleInfo struct { 656 Scheme string `json:"scheme"` 657 Hostname string `json:"hostname"` 658 Port int `json:"port"` 659 Token string `json:"token"` 660 } 661 662 func (region *SRegion) GetInstanceConsoleInfo(instnaceId string) (*SConsoleInfo, error) { 663 params := map[string]interface{}{ 664 "params": map[string]string{ 665 "vmInstanceUuid": instnaceId, 666 }, 667 } 668 resp, err := region.client.post("consoles", jsonutils.Marshal(params)) 669 if err != nil { 670 return nil, err 671 } 672 info := &SConsoleInfo{} 673 err = resp.Unmarshal(info, "inventory") 674 if err != nil { 675 return nil, err 676 } 677 return info, nil 678 } 679 680 func (region *SRegion) GetInstanceConsolePassword(instnaceId string) (string, error) { 681 resp, err := region.client.get("vm-instances", instnaceId, "console-passwords") 682 if err != nil { 683 return "", err 684 } 685 if resp.Contains("consolePassword") { 686 return resp.GetString("consolePassword") 687 } 688 return "", nil 689 }