yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/openstack/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 openstack 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "time" 22 23 "gopkg.in/fatih/set.v0" 24 25 "yunion.io/x/jsonutils" 26 "yunion.io/x/log" 27 "yunion.io/x/pkg/errors" 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 const ( 37 INSTANCE_STATUS_ACTIVE = "ACTIVE" //The server is active. 38 INSTANCE_STATUS_BUILD = "BUILD" //The server has not finished the original build process. 39 INSTANCE_STATUS_DELETED = "DELETED" //The server is permanently deleted. 40 INSTANCE_STATUS_ERROR = "ERROR" //The server is in error. 41 INSTANCE_STATUS_HARD_REBOOT = "HARD_REBOOT" //The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it. 42 INSTANCE_STATUS_MIGRATING = "MIGRATING" //The server is being migrated to a new host. 43 INSTANCE_STATUS_PASSWORD = "PASSWORD" //The password is being reset on the server. 44 INSTANCE_STATUS_PAUSED = "PAUSED" //In a paused state, the state of the server is stored in RAM.A paused server continues to run in frozen state. 45 INSTANCE_STATUS_REBOOT = "REBOOT" //The server is in a soft reboot state. A reboot command was passed to the operating system. 46 INSTANCE_STATUS_REBUILD = "REBUILD" //The server is currently being rebuilt from an image. 47 INSTANCE_STATUS_RESCUE = "RESCUE" //The server is in rescue mode. A rescue image is running with the original server image attached. 48 INSTANCE_STATUS_RESIZE = "RESIZE" //Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage. 49 INSTANCE_STATUS_REVERT_RESIZE = "REVERT_RESIZE" //The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting. 50 INSTANCE_STATUS_SHELVED = "SHELVED" // The server is in shelved state. Depending on the shelve offload time, the server will be automatically shelved offloaded. 51 INSTANCE_STATUS_SHELVED_OFFLOADED = "SHELVED_OFFLOADED" // The shelved server is offloaded (removed from the compute host) and it needs unshelved action to be used again. 52 INSTANCE_STATUS_SHUTOFF = "SHUTOFF" //The server is powered off and the disk image still persists. 53 INSTANCE_STATUS_SOFT_DELETED = "SOFT_DELETED" //The server is marked as deleted but the disk images are still available to restore. 54 INSTANCE_STATUS_SUSPENDED = "SUSPENDED" //The server is suspended, either by request or necessity. This status appears for only the XenServer/XCP, KVM, and ESXi hypervisors. Administrative users can suspend an instance if it is infrequently used or to perform system maintenance. When you suspend an instance, its VM state is stored on disk, all memory is written to disk, and the virtual machine is stopped. Suspending an instance is similar to placing a device in hibernation; memory and vCPUs become available to create other instances. 55 INSTANCE_STATUS_UNKNOWN = "UNKNOWN" //The state of the server is unknown. Contact your cloud provider. 56 INSTANCE_STATUS_VERIFY_RESIZE = "VERIFY_RESIZE" //System is awaiting confirmation that the server is operational after a move or resize. 57 ) 58 59 type SPrivate struct { 60 MacAddr string `json:"OS-EXT-IPS-MAC:mac_addr,omitempty"` 61 Addr string `json:"OS-EXT-IPS:type,omitempty"` 62 Version int 63 } 64 65 type SecurityGroup struct { 66 Id string 67 Name string 68 Description string 69 } 70 71 type ExtraSpecs struct { 72 CpuPolicy string `json:"hw:cpu_policy,omitempty"` 73 MemPageSize string `json:"hw:mem_page_size,omitempty"` 74 } 75 76 type Resource struct { 77 Id string 78 Links []Link 79 } 80 81 type VolumesAttached struct { 82 Id string 83 DeleteOnTermination bool 84 } 85 86 type SFault struct { 87 Message string 88 Code int 89 Details string 90 } 91 92 type SInstance struct { 93 multicloud.SInstanceBase 94 OpenStackTags 95 host *SHypervisor 96 97 imageObj *SImage 98 99 DiskConfig string `json:"OS-DCF:diskConfig,omitempty"` 100 AvailabilityZone string `json:"OS-EXT-AZ:availability_zone,omitempty"` 101 Host string `json:"OS-EXT-SRV-ATTR:host,omitempty"` 102 Hostname string `json:"OS-EXT-SRV-ATTR:hostname,omitempty"` 103 HypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname,omitempty"` 104 InstanceName string `json:"OS-EXT-SRV-ATTR:instance_name,omitempty"` 105 KernelId string `json:"OS-EXT-SRV-ATTR:kernel_id,omitempty"` 106 LaunchIndex int `json:"OS-EXT-SRV-ATTR:launch_index,omitempty"` 107 RamdiskId string `json:"OS-EXT-SRV-ATTR:ramdisk_id,omitempty"` 108 ReservationId string `json:"OS-EXT-SRV-ATTR:reservation_id,omitempty"` 109 RootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name,omitempty"` 110 UserData string `json:"OS-EXT-SRV-ATTR:user_data,omitempty"` 111 PowerState int `json:"OS-EXT-STS:power_state,omitempty"` 112 TaskState string `json:"OS-EXT-STS:task_state,omitempty"` 113 VmState string `json:"OS-EXT-STS:vm_state,omitempty"` 114 LaunchedAt time.Time `json:"OS-SRV-USG:launched_at,omitempty"` 115 TerminatedAt string `json:"OS-SRV-USG:terminated_at,omitempty"` 116 117 AccessIPv4 string 118 AccessIPv6 string 119 Addresses map[string][]SInstanceNic 120 ConfigDrive string 121 Created time.Time 122 Description string 123 Flavor SFlavor 124 HostId string 125 HostStatus string 126 Id string 127 Image jsonutils.JSONObject `json:"image"` //有可能是字符串 128 KeyName string 129 Links []Link 130 Locked bool 131 Name string 132 VolumesAttached []VolumesAttached `json:"os-extended-volumes:volumes_attached,omitempty"` 133 Progress int 134 SecurityGroups []SecurityGroup 135 Status string 136 Tags []string 137 TenantId string 138 TrustedImageCertificates []string 139 Updated time.Time 140 UserId string 141 Fault SFault 142 } 143 144 func (region *SRegion) GetSecurityGroupsByInstance(instanceId string) ([]SecurityGroup, error) { 145 resource := fmt.Sprintf("/servers/%s/os-security-groups", instanceId) 146 resp, err := region.ecsGet(resource) 147 if err != nil { 148 return nil, errors.Wrap(err, "ecsGet") 149 } 150 secgroups := []SecurityGroup{} 151 err = resp.Unmarshal(&secgroups, "security_groups") 152 if err != nil { 153 return nil, errors.Wrap(err, "resp.Unmarshal") 154 } 155 return secgroups, nil 156 } 157 158 func (region *SRegion) GetInstances(host string) ([]SInstance, error) { 159 instances := []SInstance{} 160 resource := "/servers/detail" 161 query := url.Values{} 162 query.Set("all_tenants", "True") 163 for { 164 resp, err := region.ecsList(resource, query) 165 if err != nil { 166 return nil, errors.Wrap(err, "ecsList") 167 } 168 part := struct { 169 Servers []SInstance 170 ServersLinks SNextLinks 171 }{} 172 err = resp.Unmarshal(&part) 173 if err != nil { 174 return nil, errors.Wrap(err, "resp.Unmarshal") 175 } 176 for i := range part.Servers { 177 if len(host) == 0 || part.Servers[i].Host == host || part.Servers[i].HypervisorHostname == host { 178 instances = append(instances, part.Servers[i]) 179 } 180 } 181 marker := part.ServersLinks.GetNextMark() 182 if len(marker) == 0 { 183 break 184 } 185 query.Set("marker", marker) 186 } 187 return instances, nil 188 } 189 190 func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) { 191 resource := "/servers/" + instanceId 192 resp, err := region.ecsGet(resource) 193 if err != nil { 194 return nil, errors.Wrap(err, "ecsGet") 195 } 196 instance := &SInstance{} 197 err = resp.Unmarshal(instance, "server") 198 if err != nil { 199 return nil, errors.Wrap(err, "resp.Unmarsha") 200 } 201 return instance, nil 202 } 203 204 func (instance *SInstance) GetSecurityGroupIds() ([]string, error) { 205 secgroupIds := []string{} 206 secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.Id) 207 if err != nil { 208 return nil, err 209 } 210 for _, secgroup := range secgroups { 211 secgroupIds = append(secgroupIds, secgroup.Id) 212 } 213 return secgroupIds, nil 214 } 215 216 func (instance *SInstance) GetIHost() cloudprovider.ICloudHost { 217 return instance.host 218 } 219 220 func (instance *SInstance) GetId() string { 221 return instance.Id 222 } 223 224 func (instance *SInstance) GetName() string { 225 return instance.Name 226 } 227 228 func (instance *SInstance) GetHostname() string { 229 return instance.Hostname 230 } 231 232 func (instance *SInstance) GetGlobalId() string { 233 return instance.Id 234 } 235 236 func (instance *SInstance) IsEmulated() bool { 237 return false 238 } 239 240 func (instance *SInstance) fetchFlavor() error { 241 if len(instance.Flavor.Id) > 0 && instance.Flavor.Vcpus == 0 { 242 flavor, err := instance.host.zone.region.GetFlavor(instance.Flavor.Id) 243 if err != nil { 244 return err 245 } 246 instance.Flavor = *flavor 247 } 248 return nil 249 } 250 251 func (instance *SInstance) GetInstanceType() string { 252 err := instance.fetchFlavor() 253 if err != nil { 254 return "" 255 } 256 return instance.Flavor.GetName() 257 } 258 259 func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 260 disks := []SDisk{} 261 hasSysDisk := false 262 for i := 0; i < len(instance.VolumesAttached); i++ { 263 disk, err := instance.host.zone.region.GetDisk(instance.VolumesAttached[i].Id) 264 if err != nil { 265 return nil, errors.Wrapf(err, "GetDisk(%s)", instance.VolumesAttached[i].Id) 266 } 267 disks = append(disks, *disk) 268 if disk.GetDiskType() == api.DISK_TYPE_SYS { 269 hasSysDisk = true 270 } 271 } 272 idisks := []cloudprovider.ICloudDisk{} 273 for i := 0; i < len(disks); i++ { 274 store, err := instance.host.zone.getStorageByCategory(disks[i].VolumeType, disks[i].Host) 275 if err != nil { 276 return nil, errors.Wrapf(err, "getStorageByCategory(%s.%s)", disks[i].Id, disks[i].VolumeType) 277 } 278 disks[i].storage = store 279 idisks = append(idisks, &disks[i]) 280 } 281 282 if !hasSysDisk { 283 store := &SNovaStorage{zone: instance.host.zone, host: instance.host} 284 disk := &SNovaDisk{storage: store, instanceId: instance.Id, region: instance.host.zone.region} 285 idisks = append([]cloudprovider.ICloudDisk{disk}, idisks...) 286 } 287 288 return idisks, nil 289 } 290 291 func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 292 nics, err := instance.host.zone.region.GetInstancePorts(instance.Id) 293 if err != nil { 294 return nil, errors.Wrap(err, "GetInstancePorts") 295 } 296 inics := []cloudprovider.ICloudNic{} 297 for i := range nics { 298 nics[i].region = instance.host.zone.region 299 inics = append(inics, &nics[i]) 300 } 301 return inics, nil 302 } 303 304 func (instance *SInstance) GetVcpuCount() int { 305 err := instance.fetchFlavor() 306 if err != nil { 307 return 0 308 } 309 return instance.Flavor.Vcpus 310 } 311 312 func (instance *SInstance) GetVmemSizeMB() int { 313 err := instance.fetchFlavor() 314 if err != nil { 315 return 0 316 } 317 return instance.Flavor.RAM 318 } 319 320 func (instance *SInstance) GetBootOrder() string { 321 return "dcn" 322 } 323 324 func (instance *SInstance) GetVga() string { 325 return "std" 326 } 327 328 func (instance *SInstance) GetVdi() string { 329 return "vnc" 330 } 331 332 func (instance *SInstance) getImage() *SImage { 333 if instance.imageObj == nil && instance.Image != nil { 334 imageId, _ := instance.Image.GetString("id") 335 if len(imageId) == 0 { 336 imageId, _ = instance.Image.GetString() 337 } 338 if len(imageId) > 0 { 339 image, _ := instance.host.zone.region.GetImage(imageId) 340 if image != nil { 341 instance.imageObj = image 342 } 343 } 344 } 345 return instance.imageObj 346 } 347 348 func (instance *SInstance) GetOsType() cloudprovider.TOsType { 349 img := instance.getImage() 350 if img != nil { 351 return img.GetOsType() 352 } 353 return cloudprovider.OsTypeLinux 354 } 355 356 func (instance *SInstance) GetFullOsName() string { 357 img := instance.getImage() 358 if img != nil { 359 return img.GetFullOsName() 360 } 361 return "" 362 } 363 364 func (instance *SInstance) GetBios() cloudprovider.TBiosType { 365 img := instance.getImage() 366 if img != nil { 367 return img.GetBios() 368 } 369 return "BIOS" 370 } 371 372 func (instance *SInstance) GetOsDist() string { 373 img := instance.getImage() 374 if img != nil { 375 return img.GetOsDist() 376 } 377 return "" 378 } 379 380 func (instance *SInstance) GetOsVersion() string { 381 img := instance.getImage() 382 if img != nil { 383 return img.GetOsVersion() 384 } 385 return "" 386 } 387 388 func (instance *SInstance) GetOsLang() string { 389 img := instance.getImage() 390 if img != nil { 391 return img.GetOsLang() 392 } 393 return "" 394 } 395 396 func (instance *SInstance) GetOsArch() string { 397 img := instance.getImage() 398 if img != nil { 399 return img.GetOsArch() 400 } 401 return "" 402 } 403 404 func (instance *SInstance) GetMachine() string { 405 return "pc" 406 } 407 408 func (instance *SInstance) GetStatus() string { 409 switch instance.Status { 410 case INSTANCE_STATUS_ACTIVE, INSTANCE_STATUS_RESCUE: 411 return api.VM_RUNNING 412 case INSTANCE_STATUS_BUILD, INSTANCE_STATUS_PASSWORD: 413 return api.VM_DEPLOYING 414 case INSTANCE_STATUS_DELETED: 415 return api.VM_DELETING 416 case INSTANCE_STATUS_HARD_REBOOT, INSTANCE_STATUS_REBOOT: 417 return api.VM_STARTING 418 case INSTANCE_STATUS_MIGRATING: 419 return api.VM_MIGRATING 420 case INSTANCE_STATUS_PAUSED, INSTANCE_STATUS_SUSPENDED: 421 return api.VM_SUSPEND 422 case INSTANCE_STATUS_RESIZE: 423 return api.VM_CHANGE_FLAVOR 424 case INSTANCE_STATUS_VERIFY_RESIZE: 425 // API请求更改配置后,状态先回变更到 INSTANCE_STATUS_RESIZE 等待一会变成此状态 426 // 到达此状态后需要再次发送确认请求,变更才会生效 427 // 此状态不能和INSTANCE_STATUS_RESIZE返回一样,避免在INSTANCE_STATUS_RESIZE状态下发送确认请求,导致更改配置失败 428 return api.VM_SYNC_CONFIG 429 case INSTANCE_STATUS_SHELVED, INSTANCE_STATUS_SHELVED_OFFLOADED, INSTANCE_STATUS_SHUTOFF, INSTANCE_STATUS_SOFT_DELETED: 430 return api.VM_READY 431 default: 432 return api.VM_UNKNOWN 433 } 434 } 435 436 func (instance *SInstance) Refresh() error { 437 _instance, err := instance.host.zone.region.GetInstance(instance.Id) 438 if err != nil { 439 return err 440 } 441 return jsonutils.Update(instance, _instance) 442 } 443 444 func (instance *SInstance) UpdateVM(ctx context.Context, name string) error { 445 if instance.Name != name { 446 params := map[string]map[string]string{ 447 "server": { 448 "name": name, 449 }, 450 } 451 resource := "/servers/" + instance.Id 452 _, err := instance.host.zone.region.ecsUpdate(resource, params) 453 return err 454 } 455 return nil 456 } 457 458 func (instance *SInstance) GetHypervisor() string { 459 return api.HYPERVISOR_OPENSTACK 460 } 461 462 func (instance *SInstance) StartVM(ctx context.Context) error { 463 err := instance.host.zone.region.StartVM(instance.Id) 464 if err != nil { 465 return errors.Wrapf(err, "StartVM(%s)", instance.Id) 466 } 467 return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 10*time.Second, 8*time.Minute) 468 } 469 470 func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 471 err := instance.host.zone.region.StopVM(instance.Id, opts.IsForce) 472 if err != nil { 473 return errors.Wrapf(err, "StopVM(%s)", instance.Id) 474 } 475 return cloudprovider.WaitStatus(instance, api.VM_READY, 10*time.Second, 8*time.Minute) 476 } 477 478 func (region *SRegion) GetInstanceVNCUrl(instanceId string, origin bool) (*cloudprovider.ServerVncOutput, error) { 479 params := map[string]map[string]string{ 480 "remote_console": { 481 "protocol": "vnc", 482 "type": "novnc", 483 }, 484 } 485 resource := fmt.Sprintf("/servers/%s/remote-consoles", instanceId) 486 resp, err := region.ecsPost(resource, params) 487 if err != nil { 488 return nil, errors.Wrap(err, "ecsPost") 489 } 490 ret := &cloudprovider.ServerVncOutput{ 491 Protocol: "openstack", 492 InstanceId: instanceId, 493 Hypervisor: api.HYPERVISOR_OPENSTACK, 494 } 495 496 ret.Url, err = resp.GetString("remote_console", "url") 497 if err != nil { 498 return nil, errors.Wrapf(err, "remote_console") 499 } 500 501 if origin { 502 return ret, nil 503 } 504 505 token := string([]byte(ret.Url)[len(ret.Url)-36:]) 506 vncUrl, _ := url.Parse(ret.Url) 507 ret.Url = fmt.Sprintf("ws://%s?token=%s", vncUrl.Host, token) 508 ret.Protocol = "vnc" 509 return ret, nil 510 } 511 512 func (region *SRegion) GetInstanceVNC(instanceId string, origin bool) (*cloudprovider.ServerVncOutput, error) { 513 params := map[string]map[string]string{ 514 "os-getVNCConsole": { 515 "type": "novnc", 516 }, 517 } 518 resource := fmt.Sprintf("/servers/%s/action", instanceId) 519 resp, err := region.ecsPost(resource, params) 520 if err != nil { 521 return nil, errors.Wrap(err, "ecsPost") 522 } 523 ret := &cloudprovider.ServerVncOutput{ 524 Protocol: "openstack", 525 InstanceId: instanceId, 526 Hypervisor: api.HYPERVISOR_OPENSTACK, 527 } 528 529 ret.Url, err = resp.GetString("console", "url") 530 if err != nil { 531 return nil, errors.Wrapf(err, "remote_console") 532 } 533 534 if origin { 535 return ret, nil 536 } 537 538 token := string([]byte(ret.Url)[len(ret.Url)-36:]) 539 vncUrl, _ := url.Parse(ret.Url) 540 ret.Url = fmt.Sprintf("ws://%s?token=%s", vncUrl.Host, token) 541 ret.Protocol = "vnc" 542 return ret, nil 543 } 544 545 func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 546 origin := false 547 if input != nil { 548 origin = input.Origin 549 } 550 ret, err := instance.host.zone.region.GetInstanceVNCUrl(instance.Id, origin) 551 if err == nil { 552 return ret, nil 553 } 554 return instance.host.zone.region.GetInstanceVNC(instance.Id, origin) 555 } 556 557 func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 558 return instance.host.zone.region.DeployVM(instance.Id, name, password, publicKey, deleteKeypair, description) 559 } 560 561 func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 562 return instance.Id, instance.host.zone.region.ReplaceSystemDisk(instance.Id, desc.ImageId, desc.Password, desc.PublicKey, desc.SysSizeGB) 563 } 564 565 func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 566 if (len(config.InstanceType) > 0 && instance.GetInstanceType() != config.InstanceType) || instance.GetVcpuCount() != config.Cpu || instance.GetVmemSizeMB() != config.MemoryMB { 567 flavor, err := instance.host.zone.region.syncFlavor(config.InstanceType, config.Cpu, config.MemoryMB, 40) 568 if err != nil { 569 return errors.Wrapf(err, "syncFlavor(%s)", config.InstanceType) 570 } 571 // When resizing, instances must change flavor! 572 if flavor.Name == instance.Flavor.OriginalName { 573 return nil 574 } 575 return instance.host.zone.region.ChangeConfig(instance, flavor.Id) 576 } 577 return nil 578 } 579 580 func (region *SRegion) ChangeConfig(instance *SInstance, flavorId string) error { 581 params := map[string]map[string]string{ 582 "resize": { 583 "flavorRef": flavorId, 584 }, 585 } 586 resource := fmt.Sprintf("/servers/%s/action", instance.Id) 587 _, err := region.ecsPost(resource, params) 588 if err != nil { 589 return errors.Wrap(err, "ecsPost") 590 } 591 err = cloudprovider.WaitStatus(instance, api.VM_SYNC_CONFIG, time.Second*3, time.Minute*4) 592 if err != nil { 593 return errors.Wrap(err, "WaitStatsAfterChangeConfig") 594 } 595 return region.instanceOperation(instance.Id, "confirmResize") 596 } 597 598 func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error { 599 return instance.host.zone.region.AttachDisk(instance.Id, diskId) 600 } 601 602 func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error { 603 return instance.host.zone.region.DetachDisk(instance.Id, diskId) 604 } 605 606 func (region *SRegion) instanceOperation(instanceId, operate string) error { 607 params := map[string]string{operate: ""} 608 resource := fmt.Sprintf("/servers/%s/action", instanceId) 609 _, err := region.ecsPost(resource, params) 610 return err 611 } 612 613 func (region *SRegion) doStopVM(instanceId string, isForce bool) error { 614 return region.instanceOperation(instanceId, "os-stop") 615 } 616 617 func (region *SRegion) doDeleteVM(instanceId string) error { 618 return region.instanceOperation(instanceId, "forceDelete") 619 } 620 621 func (region *SRegion) StartVM(instanceId string) error { 622 return region.instanceOperation(instanceId, "os-start") 623 } 624 625 func (region *SRegion) StopVM(instanceId string, isForce bool) error { 626 return region.doStopVM(instanceId, isForce) 627 } 628 629 func (region *SRegion) DeleteVM(instanceId string) error { 630 instance, err := region.GetInstance(instanceId) 631 if err != nil { 632 if errors.Cause(err) == cloudprovider.ErrNotFound { 633 return nil 634 } 635 return errors.Wrapf(err, "GetInstance(%s)", instanceId) 636 } 637 status := instance.GetStatus() 638 log.Debugf("Instance status on delete is %s", status) 639 if status != api.VM_READY { 640 log.Warningf("DeleteVM: vm status is %s expect %s", status, api.VM_READY) 641 } 642 return region.doDeleteVM(instanceId) 643 } 644 645 func (region *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error { 646 if len(password) > 0 { 647 params := map[string]map[string]string{ 648 "changePassword": { 649 "adminPass": password, 650 }, 651 } 652 resource := fmt.Sprintf("/servers/%s/action", instanceId) 653 _, err := region.ecsPost(resource, params) 654 return err 655 } 656 return nil 657 } 658 659 func (instance *SInstance) DeleteVM(ctx context.Context) error { 660 err := instance.host.zone.region.DeleteVM(instance.Id) 661 if err != nil { 662 return errors.Wrapf(err, "instance.host.zone.region.DeleteVM(%s)", instance.Id) 663 } 664 return cloudprovider.WaitDeleted(instance, time.Second*5, time.Minute*10) 665 } 666 667 func (region *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, publicKey string, sysDiskSizeGB int) error { 668 params := map[string]map[string]string{ 669 "rebuild": { 670 "imageRef": imageId, 671 }, 672 } 673 674 if len(publicKey) > 0 { 675 keypairName, err := region.syncKeypair(instanceId, publicKey) 676 if err != nil { 677 return err 678 } 679 params["rebuild"]["key_name"] = keypairName 680 } 681 682 if len(passwd) > 0 { 683 params["rebuild"]["adminPass"] = passwd 684 } 685 resource := fmt.Sprintf("/servers/%s/action", instanceId) 686 _, err := region.ecsPost(resource, params) 687 return err 688 } 689 690 func (region *SRegion) DetachDisk(instanceId string, diskId string) error { 691 resource := fmt.Sprintf("/servers/%s/os-volume_attachments/%s", instanceId, diskId) 692 _, err := region.ecsDelete(resource) 693 if err != nil { 694 return errors.Wrap(err, "ecsDelete") 695 } 696 status := "" 697 startTime := time.Now() 698 for time.Now().Sub(startTime) < time.Minute*10 { 699 disk, err := region.GetDisk(diskId) 700 if err != nil { 701 return errors.Wrapf(err, "GetDisk(%s)", diskId) 702 } 703 status = disk.Status 704 log.Debugf("status %s expect %s", status, DISK_STATUS_AVAILABLE) 705 if status == DISK_STATUS_AVAILABLE { 706 return nil 707 } 708 time.Sleep(time.Second * 15) 709 } 710 return fmt.Errorf("timeout for waitting detach disk, current status: %s", status) 711 } 712 713 func (region *SRegion) AttachDisk(instanceId string, diskId string) error { 714 params := map[string]map[string]string{ 715 "volumeAttachment": { 716 "volumeId": diskId, 717 }, 718 } 719 resource := fmt.Sprintf("/servers/%s/os-volume_attachments", instanceId) 720 _, err := region.ecsPost(resource, params) 721 if err != nil { 722 return errors.Wrap(err, "ecsPost") 723 } 724 status := "" 725 startTime := time.Now() 726 for time.Now().Sub(startTime) < time.Minute*10 { 727 disk, err := region.GetDisk(diskId) 728 if err != nil { 729 return errors.Wrapf(err, "GetDisk(%s)", diskId) 730 } 731 status = disk.Status 732 log.Debugf("status %s expect %s", status, DISK_STATUS_IN_USE) 733 if status == DISK_STATUS_IN_USE { 734 return nil 735 } 736 time.Sleep(time.Second * 15) 737 } 738 return fmt.Errorf("timeout for waitting attach disk, current status: %s", status) 739 } 740 741 func (region *SRegion) MigrateVM(instanceId string, hostName string) error { 742 params := jsonutils.NewDict() 743 migrate := jsonutils.NewDict() 744 migrate.Add(jsonutils.JSONNull, "host") 745 if hostName != "" { 746 migrate.Add(jsonutils.NewString(hostName), "host") 747 } 748 params.Add(migrate, "migrate") 749 resource := fmt.Sprintf("/servers/%s/action", instanceId) 750 _, err := region.ecsPost(resource, params) 751 if err != nil { 752 return errors.Wrapf(err, "On Requst Migrate instance:%s", instanceId) 753 } 754 return nil 755 } 756 757 func (region *SRegion) LiveMigrateVM(instanceId string, hostName string) error { 758 params := jsonutils.NewDict() 759 osMigrateLive := jsonutils.NewDict() 760 osMigrateLive.Add(jsonutils.NewString("auto"), "block_migration") 761 osMigrateLive.Add(jsonutils.JSONNull, "host") 762 if hostName != "" { 763 osMigrateLive.Add(jsonutils.NewString(hostName), "host") 764 } 765 params.Add(osMigrateLive, "os-migrateLive") 766 resource := fmt.Sprintf("/servers/%s/action", instanceId) 767 _, err := region.ecsPost(resource, params) 768 if err != nil { 769 return errors.Wrapf(err, "On Requst LiveMigrate instance:%s", instanceId) 770 } 771 return nil 772 } 773 774 //仅live-migration 775 func (region *SRegion) ListServerMigration(instanceId string) error { 776 resource := fmt.Sprintf("/servers/%s/migrations", instanceId) 777 _, err := region.ecsGet(resource) 778 if err != nil { 779 return errors.Wrapf(err, "ListServerMigration") 780 } 781 return nil 782 } 783 784 //仅live-migration 785 func (region *SRegion) DeleteMigration(instanceId string, migrationId string) error { 786 resource := fmt.Sprintf("/servers/%s/migrations/%s", instanceId, migrationId) 787 _, err := region.ecsDelete(resource) 788 if err != nil { 789 return errors.Wrapf(err, "On Requst delete LiveMigrate:%s", migrationId) 790 } 791 return nil 792 } 793 794 //仅live-migration 795 func (region *SRegion) ForceCompleteMigration(instanceId string, migrationId string) error { 796 params := jsonutils.NewDict() 797 params.Add(jsonutils.JSONNull, "force_complete") 798 resource := fmt.Sprintf("/servers/%s/migrations/%s/action", instanceId, migrationId) 799 _, err := region.ecsPost(resource, params) 800 if err != nil { 801 return errors.Wrapf(err, "On Requst delete LiveMigrate:%s", migrationId) 802 } 803 return nil 804 } 805 806 func (region *SRegion) GetMigrations(instanceId string, migrationType string) (jsonutils.JSONObject, error) { 807 query := url.Values{} 808 query.Set("instance_uuid", instanceId) 809 query.Set("migration_type", migrationType) 810 resource := "/os-migrations" 811 migrations, err := region.ecsList(resource, query) 812 if err != nil { 813 return nil, errors.Wrapf(err, "On Get instance :%s Migration,migration_type:%s", instanceId, migrationType) 814 } 815 return migrations, nil 816 } 817 818 func (instance *SInstance) AssignSecurityGroup(secgroupId string) error { 819 if secgroupId == SECGROUP_NOT_SUPPORT { 820 return fmt.Errorf("Security groups are not supported. Security group components are not installed") 821 } 822 secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId) 823 if err != nil { 824 return errors.Wrapf(err, "GetSecurityGroup(%s)", secgroupId) 825 } 826 params := map[string]map[string]string{ 827 "addSecurityGroup": { 828 "name": secgroup.Name, 829 }, 830 } 831 resource := fmt.Sprintf("/servers/%s/action", instance.Id) 832 _, err = instance.host.zone.region.ecsDo(instance.GetProjectId(), resource, params) 833 return err 834 } 835 836 func (instance *SInstance) RevokeSecurityGroup(secgroupId string) error { 837 // 若OpenStack不支持安全组,则忽略解绑安全组 838 if secgroupId == SECGROUP_NOT_SUPPORT { 839 return nil 840 } 841 secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId) 842 if err != nil { 843 return errors.Wrapf(err, "GetSecurityGroup(%s)", secgroupId) 844 } 845 params := map[string]map[string]string{ 846 "removeSecurityGroup": { 847 "name": secgroup.Name, 848 }, 849 } 850 resource := fmt.Sprintf("/servers/%s/action", instance.Id) 851 _, err = instance.host.zone.region.ecsDo(instance.GetProjectId(), resource, params) 852 return err 853 } 854 855 func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error { 856 secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.Id) 857 if err != nil { 858 return errors.Wrapf(err, "GetSecurityGroupsByInstance(%s)", instance.Id) 859 } 860 local := set.New(set.ThreadSafe) 861 for _, secgroup := range secgroups { 862 local.Add(secgroup.Id) 863 } 864 newG := set.New(set.ThreadSafe) 865 for _, secgroupId := range secgroupIds { 866 newG.Add(secgroupId) 867 } 868 for _, del := range set.Difference(local, newG).List() { 869 secgroupId := del.(string) 870 err := instance.RevokeSecurityGroup(secgroupId) 871 if err != nil { 872 return errors.Wrapf(err, "RevokeSecurityGroup(%s)", secgroupId) 873 } 874 } 875 for _, add := range set.Difference(newG, local).List() { 876 secgroupId := add.(string) 877 err := instance.AssignSecurityGroup(secgroupId) 878 if err != nil { 879 return errors.Wrapf(err, "AssignSecurityGroup(%s)", secgroupId) 880 } 881 } 882 return nil 883 } 884 885 func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 886 for networkName, address := range instance.Addresses { 887 for i := 0; i < len(address); i++ { 888 if instance.Addresses[networkName][i].Type == "floating" { 889 return instance.host.zone.region.GetEipByIp(instance.Addresses[networkName][i].Addr) 890 } 891 } 892 } 893 return nil, nil 894 } 895 896 func (instance *SInstance) GetBillingType() string { 897 return billing_api.BILLING_TYPE_POSTPAID 898 } 899 900 func (instance *SInstance) GetCreatedAt() time.Time { 901 return instance.Created 902 } 903 904 func (instance *SInstance) GetExpiredAt() time.Time { 905 return time.Time{} 906 } 907 908 func (instance *SInstance) UpdateUserData(userData string) error { 909 return cloudprovider.ErrNotSupported 910 } 911 912 func (instance *SInstance) Renew(bc billing.SBillingCycle) error { 913 return cloudprovider.ErrNotSupported 914 } 915 916 func (region *SRegion) RenewInstances(instanceId []string, bc billing.SBillingCycle) error { 917 return cloudprovider.ErrNotSupported 918 } 919 920 func (instance *SInstance) GetProjectId() string { 921 return instance.TenantId 922 } 923 924 func (self *SInstance) GetError() error { 925 if self.Status == INSTANCE_STATUS_ERROR && len(self.Fault.Message) > 0 { 926 return errors.Error(self.Fault.Message) 927 } 928 return nil 929 } 930 931 func (instance *SInstance) MigrateVM(hostId string) error { 932 hostName := "" 933 if hostId != "" { 934 iHost, err := instance.host.zone.region.GetIHostById(hostId) 935 if err != nil { 936 return errors.Wrapf(err, "GetIHostById(%s)", hostId) 937 } 938 hostName = iHost.GetName() 939 } 940 941 previousHostName := instance.Host 942 err := instance.host.zone.region.MigrateVM(instance.Id, hostName) 943 if err != nil { 944 return errors.Wrap(err, "MigrateVm") 945 } 946 err = cloudprovider.WaitMultiStatus(instance, []string{api.VM_SYNC_CONFIG, api.VM_READY, api.VM_UNKNOWN}, time.Second*10, time.Hour*3) 947 if err != nil { 948 return errors.Wrap(err, "WaitMultiStatus") 949 } 950 if instance.GetStatus() == api.VM_UNKNOWN { 951 return errors.Wrap(errors.ErrInvalidStatus, "GetStatus") 952 } 953 if instance.GetStatus() == api.VM_READY { 954 if instance.Host == previousHostName { 955 return errors.Wrap(fmt.Errorf("instance not migrated"), "Check host after migration") 956 } 957 return nil 958 } 959 return instance.host.zone.region.instanceOperation(instance.Id, "confirmResize") 960 } 961 962 func (instance *SInstance) LiveMigrateVM(hostId string) error { 963 hostName := "" 964 if hostId != "" { 965 iHost, err := instance.host.zone.region.GetIHostById(hostId) 966 if err != nil { 967 return errors.Wrapf(err, "GetIHostById(%s)", hostId) 968 } 969 hostName = iHost.GetName() 970 } 971 previousHostName := instance.Host 972 err := instance.host.zone.region.LiveMigrateVM(instance.Id, hostName) 973 if err != nil { 974 return errors.Wrap(err, "LiveMIgrateVm") 975 } 976 err = cloudprovider.WaitMultiStatus(instance, []string{api.VM_SYNC_CONFIG, api.VM_RUNNING, api.VM_UNKNOWN}, time.Second*10, time.Hour*3) 977 if err != nil { 978 return errors.Wrap(err, "WaitMultiStatus") 979 } 980 if instance.GetStatus() == api.VM_UNKNOWN { 981 return errors.Wrap(errors.ErrInvalidStatus, "GetStatus") 982 } 983 if instance.GetStatus() == api.VM_RUNNING { 984 if instance.Host == previousHostName { 985 return errors.Wrap(fmt.Errorf("instance not migrated"), "Check host after migration") 986 } 987 return nil 988 } 989 return instance.host.zone.region.instanceOperation(instance.Id, "confirmResize") 990 } 991 func (instance *SInstance) GetIHostId() string { 992 err := instance.host.zone.fetchHosts() 993 if err != nil { 994 return "" 995 } 996 for _, host := range instance.host.zone.hosts { 997 if instance.HypervisorHostname == host.HypervisorHostname { 998 return host.GetGlobalId() 999 } 1000 } 1001 return "" 1002 } 1003 1004 func (region *SRegion) GetInstanceMetadata(instanceId string) (map[string]string, error) { 1005 resource := fmt.Sprintf("/servers/%s/metadata", instanceId) 1006 resp, err := region.ecsList(resource, nil) 1007 if err != nil { 1008 return nil, errors.Wrap(err, "ecsList") 1009 } 1010 result := struct { 1011 Metadata map[string]string 1012 }{} 1013 err = resp.Unmarshal(&result) 1014 if err != nil { 1015 return nil, errors.Wrap(err, "resp.Unmarshal") 1016 } 1017 return result.Metadata, nil 1018 } 1019 1020 func (zone *SZone) CreateVM(hypervisor string, opts *cloudprovider.SManagedVMCreateConfig) (*SInstance, error) { 1021 region := zone.region 1022 network, err := region.GetNetwork(opts.ExternalNetworkId) 1023 if err != nil { 1024 return nil, err 1025 } 1026 1027 secgroups := []map[string]string{} 1028 for _, secgroupId := range opts.ExternalSecgroupIds { 1029 if secgroupId != SECGROUP_NOT_SUPPORT { 1030 secgroups = append(secgroups, map[string]string{"name": secgroupId}) 1031 } 1032 } 1033 1034 image, err := region.GetImage(opts.ExternalImageId) 1035 if err != nil { 1036 return nil, errors.Wrapf(err, "GetImage(%s)", opts.ExternalImageId) 1037 } 1038 1039 sysDiskSizeGB := image.Size / 1024 / 1024 / 1024 1040 if opts.SysDisk.SizeGB < sysDiskSizeGB { 1041 opts.SysDisk.SizeGB = sysDiskSizeGB 1042 } 1043 1044 if opts.SysDisk.SizeGB < image.GetMinOsDiskSizeGb() { 1045 opts.SysDisk.SizeGB = image.GetMinOsDiskSizeGb() 1046 } 1047 1048 BlockDeviceMappingV2 := []map[string]interface{}{} 1049 1050 diskIds := []string{} 1051 1052 defer func() { 1053 for _, diskId := range diskIds { 1054 err = region.DeleteDisk(diskId) 1055 if err != nil { 1056 log.Errorf("clean disk %s error: %v", diskId, err) 1057 } 1058 } 1059 }() 1060 1061 if opts.SysDisk.StorageType != api.STORAGE_OPENSTACK_NOVA { //新建volume 1062 istorage, err := zone.GetIStorageById(opts.SysDisk.StorageExternalId) 1063 if err != nil { 1064 return nil, errors.Wrapf(err, "GetIStorageById(%s)", opts.SysDisk.StorageExternalId) 1065 } 1066 1067 _sysDisk, err := region.CreateDisk(opts.ExternalImageId, istorage.GetName(), "", opts.SysDisk.SizeGB, opts.SysDisk.Name, opts.ProjectId) 1068 if err != nil { 1069 return nil, errors.Wrapf(err, "CreateDisk %s", opts.SysDisk.Name) 1070 } 1071 1072 diskIds = append(diskIds, _sysDisk.GetGlobalId()) 1073 1074 BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{ 1075 "boot_index": 0, 1076 "uuid": _sysDisk.GetGlobalId(), 1077 "source_type": "volume", 1078 "destination_type": "volume", 1079 "delete_on_termination": true, 1080 }) 1081 } else { 1082 BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{ 1083 "boot_index": 0, 1084 "uuid": image.Id, 1085 "source_type": "image", 1086 "destination_type": "local", 1087 "delete_on_termination": true, 1088 }) 1089 } 1090 1091 var _disk *SDisk 1092 for index, disk := range opts.DataDisks { 1093 istorage, err := zone.GetIStorageById(disk.StorageExternalId) 1094 if err != nil { 1095 return nil, errors.Wrapf(err, "GetIStorageById(%s)", disk.StorageExternalId) 1096 } 1097 _disk, err = region.CreateDisk("", istorage.GetName(), "", disk.SizeGB, disk.Name, opts.ProjectId) 1098 if err != nil { 1099 return nil, errors.Wrapf(err, "CreateDisk %s", disk.Name) 1100 } 1101 diskIds = append(diskIds, _disk.Id) 1102 1103 mapping := map[string]interface{}{ 1104 "source_type": "volume", 1105 "destination_type": "volume", 1106 "delete_on_termination": true, 1107 "boot_index": index + 1, 1108 "uuid": _disk.Id, 1109 } 1110 1111 BlockDeviceMappingV2 = append(BlockDeviceMappingV2, mapping) 1112 } 1113 1114 az := zone.ZoneName 1115 if len(hypervisor) > 0 { 1116 az = fmt.Sprintf("%s:%s", zone.ZoneName, hypervisor) 1117 } 1118 1119 net := map[string]string{ 1120 "uuid": network.NetworkId, 1121 } 1122 if len(opts.IpAddr) > 0 { 1123 net["fixed_ip"] = opts.IpAddr 1124 } 1125 1126 params := map[string]map[string]interface{}{ 1127 "server": { 1128 "name": opts.Name, 1129 "adminPass": opts.Password, 1130 "availability_zone": az, 1131 "networks": []map[string]string{net}, 1132 "security_groups": secgroups, 1133 "user_data": opts.UserData, 1134 "imageRef": opts.ExternalImageId, 1135 "block_device_mapping_v2": BlockDeviceMappingV2, 1136 }, 1137 } 1138 if len(opts.IpAddr) > 0 { 1139 params["server"]["accessIPv4"] = opts.IpAddr 1140 } 1141 1142 flavor, err := region.syncFlavor(opts.InstanceType, opts.Cpu, opts.MemoryMB, opts.SysDisk.SizeGB) 1143 if err != nil { 1144 return nil, err 1145 } 1146 params["server"]["flavorRef"] = flavor.Id 1147 1148 if len(opts.PublicKey) > 0 { 1149 keypairName, err := region.syncKeypair(opts.Name, opts.PublicKey) 1150 if err != nil { 1151 return nil, err 1152 } 1153 params["server"]["key_name"] = keypairName 1154 } 1155 1156 resp, err := region.ecsCreate(opts.ProjectId, "/servers", params) 1157 if err != nil { 1158 return nil, errors.Wrap(err, "ecsCreate") 1159 } 1160 diskIds = []string{} 1161 instance := &SInstance{} 1162 err = resp.Unmarshal(instance, "server") 1163 if err != nil { 1164 return nil, errors.Wrap(err, "resp.Unmarshal") 1165 } 1166 return instance, nil 1167 }