github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/vm.go (about) 1 /* 2 * Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "fmt" 9 "net" 10 "net/http" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/vmware/go-vcloud-director/v2/types/v56" 16 "github.com/vmware/go-vcloud-director/v2/util" 17 ) 18 19 type VM struct { 20 VM *types.Vm 21 client *Client 22 } 23 24 type VMRecord struct { 25 VM *types.QueryResultVMRecordType 26 client *Client 27 } 28 29 func NewVM(cli *Client) *VM { 30 return &VM{ 31 VM: new(types.Vm), 32 client: cli, 33 } 34 } 35 36 // NewVMRecord creates an instance with reference to types.QueryResultVMRecordType 37 func NewVMRecord(cli *Client) *VMRecord { 38 return &VMRecord{ 39 VM: new(types.QueryResultVMRecordType), 40 client: cli, 41 } 42 } 43 44 func (vm *VM) GetStatus() (string, error) { 45 err := vm.Refresh() 46 if err != nil { 47 return "", fmt.Errorf("error refreshing VM: %s", err) 48 } 49 return types.VAppStatuses[vm.VM.Status], nil 50 } 51 52 // IsDeployed checks if the VM is deployed or not 53 func (vm *VM) IsDeployed() (bool, error) { 54 err := vm.Refresh() 55 if err != nil { 56 return false, fmt.Errorf("error refreshing VM: %s", err) 57 } 58 return vm.VM.Deployed, nil 59 } 60 61 func (vm *VM) Refresh() error { 62 63 if vm.VM.HREF == "" { 64 return fmt.Errorf("cannot refresh VM, Object is empty") 65 } 66 67 refreshUrl := vm.VM.HREF 68 69 // Empty struct before a new unmarshal, otherwise we end up with duplicate 70 // elements in slices. 71 vm.VM = &types.Vm{} 72 73 // 37.1 Introduced BootOptions and Firmware parameters of a VM 74 _, err := vm.client.ExecuteRequestWithApiVersion(refreshUrl, http.MethodGet, "", "error refreshing VM: %s", nil, vm.VM, vm.client.GetSpecificApiVersionOnCondition(">=37.1", "37.1")) 75 76 // The request was successful 77 return err 78 } 79 80 // GetVirtualHardwareSection returns the virtual hardware items attached to a VM 81 func (vm *VM) GetVirtualHardwareSection() (*types.VirtualHardwareSection, error) { 82 83 virtualHardwareSection := &types.VirtualHardwareSection{} 84 85 if vm.VM.HREF == "" { 86 return nil, fmt.Errorf("cannot refresh, invalid reference url") 87 } 88 89 _, err := vm.client.ExecuteRequest(vm.VM.HREF+"/virtualHardwareSection/", http.MethodGet, 90 types.MimeVirtualHardwareSection, "error retrieving virtual hardware: %s", nil, virtualHardwareSection) 91 92 // The request was successful 93 return virtualHardwareSection, err 94 } 95 96 // GetNetworkConnectionSection returns current networks attached to VM 97 // 98 // The slice of NICs is not necessarily ordered by NIC index 99 func (vm *VM) GetNetworkConnectionSection() (*types.NetworkConnectionSection, error) { 100 101 networkConnectionSection := &types.NetworkConnectionSection{} 102 103 if vm.VM.HREF == "" { 104 return networkConnectionSection, fmt.Errorf("cannot retrieve network when VM HREF is unset") 105 } 106 107 _, err := vm.client.ExecuteRequest(vm.VM.HREF+"/networkConnectionSection/", http.MethodGet, 108 types.MimeNetworkConnectionSection, "error retrieving network connection: %s", nil, networkConnectionSection) 109 110 // The request was successful 111 return networkConnectionSection, err 112 } 113 114 // UpdateNetworkConnectionSection applies network configuration of types.NetworkConnectionSection for the VM 115 // Runs synchronously, VM is ready for another operation after this function returns. 116 func (vm *VM) UpdateNetworkConnectionSection(networks *types.NetworkConnectionSection) error { 117 if vm.VM.HREF == "" { 118 return fmt.Errorf("cannot update network connection when VM HREF is unset") 119 } 120 121 // Retrieve current network configuration so that we are not altering any other internal fields 122 updateNetwork, err := vm.GetNetworkConnectionSection() 123 if err != nil { 124 return fmt.Errorf("cannot read network section for update: %s", err) 125 } 126 updateNetwork.PrimaryNetworkConnectionIndex = networks.PrimaryNetworkConnectionIndex 127 updateNetwork.NetworkConnection = networks.NetworkConnection 128 updateNetwork.Ovf = types.XMLNamespaceOVF 129 130 task, err := vm.client.ExecuteTaskRequest(vm.VM.HREF+"/networkConnectionSection/", http.MethodPut, 131 types.MimeNetworkConnectionSection, "error updating network connection: %s", updateNetwork) 132 if err != nil { 133 return err 134 } 135 err = task.WaitTaskCompletion() 136 if err != nil { 137 return fmt.Errorf("error waiting for task completion after network update for vm %s: %s", vm.VM.Name, err) 138 } 139 140 return nil 141 } 142 143 // Deprecated: use client.GetVMByHref instead 144 func (client *Client) FindVMByHREF(vmHREF string) (VM, error) { 145 146 newVm := NewVM(client) 147 148 _, err := client.ExecuteRequest(vmHREF, http.MethodGet, 149 "", "error retrieving VM: %s", nil, newVm.VM) 150 151 return *newVm, err 152 153 } 154 155 func (vm *VM) PowerOn() (Task, error) { 156 157 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 158 apiEndpoint.Path += "/power/action/powerOn" 159 160 // Return the task 161 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 162 "", "error powering on VM: %s", nil) 163 164 } 165 166 // PowerOnAndForceCustomization is a synchronous function which is equivalent to the functionality 167 // one has in UI. It triggers customization which may be useful in some cases (like altering NICs) 168 // 169 // The VM _must_ be un-deployed for this action to actually work. 170 func (vm *VM) PowerOnAndForceCustomization() error { 171 // PowerOnAndForceCustomization only works if the VM was previously un-deployed 172 vmIsDeployed, err := vm.IsDeployed() 173 if err != nil { 174 return fmt.Errorf("unable to check if VM %s is un-deployed forcing customization: %s", 175 vm.VM.Name, err) 176 } 177 178 if vmIsDeployed { 179 return fmt.Errorf("VM %s must be undeployed before forcing customization", vm.VM.Name) 180 } 181 182 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 183 apiEndpoint.Path += "/action/deploy" 184 185 powerOnAndCustomize := &types.DeployVAppParams{ 186 Xmlns: types.XMLNamespaceVCloud, 187 PowerOn: true, 188 ForceCustomization: true, 189 } 190 191 task, err := vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 192 "", "error powering on VM with customization: %s", powerOnAndCustomize) 193 194 if err != nil { 195 return err 196 } 197 198 err = task.WaitTaskCompletion() 199 if err != nil { 200 return fmt.Errorf("error waiting for task completion after power on with customization %s: %s", vm.VM.Name, err) 201 } 202 203 return nil 204 } 205 206 func (vm *VM) PowerOff() (Task, error) { 207 208 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 209 apiEndpoint.Path += "/power/action/powerOff" 210 211 // Return the task 212 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 213 "", "error powering off VM: %s", nil) 214 } 215 216 // ChangeCPUCount sets number of available virtual logical processors 217 // (i.e. CPUs x cores per socket) 218 // Cpu cores count is inherited from template. 219 // https://communities.vmware.com/thread/576209 220 // Deprecated: use vm.ChangeCPU instead 221 func (vm *VM) ChangeCPUCount(virtualCpuCount int) (Task, error) { 222 return vm.ChangeCPUCountWithCore(virtualCpuCount, nil) 223 } 224 225 // ChangeCPUCountWithCore sets number of available virtual logical processors 226 // (i.e. CPUs x cores per socket) and cores per socket. 227 // Socket count is a result of: virtual logical processors/cores per socket 228 // https://communities.vmware.com/thread/576209 229 // Deprecated: use vm.ChangeCPU instead 230 func (vm *VM) ChangeCPUCountWithCore(virtualCpuCount int, coresPerSocket *int) (Task, error) { 231 232 err := vm.Refresh() 233 if err != nil { 234 return Task{}, fmt.Errorf("error refreshing VM before running customization: %s", err) 235 } 236 237 newCpu := &types.OVFItem{ 238 XmlnsRasd: types.XMLNamespaceRASD, 239 XmlnsVCloud: types.XMLNamespaceVCloud, 240 XmlnsXsi: types.XMLNamespaceXSI, 241 XmlnsVmw: types.XMLNamespaceVMW, 242 VCloudHREF: vm.VM.HREF + "/virtualHardwareSection/cpu", 243 VCloudType: types.MimeRasdItem, 244 AllocationUnits: "hertz * 10^6", 245 Description: "Number of Virtual CPUs", 246 ElementName: strconv.Itoa(virtualCpuCount) + " virtual CPU(s)", 247 InstanceID: 4, 248 Reservation: 0, 249 ResourceType: types.ResourceTypeProcessor, 250 VirtualQuantity: int64(virtualCpuCount), 251 CoresPerSocket: coresPerSocket, 252 Link: &types.Link{ 253 HREF: vm.VM.HREF + "/virtualHardwareSection/cpu", 254 Rel: "edit", 255 Type: types.MimeRasdItem, 256 }, 257 } 258 259 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 260 apiEndpoint.Path += "/virtualHardwareSection/cpu" 261 262 // Return the task 263 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 264 types.MimeRasdItem, "error changing CPU count: %s", newCpu) 265 266 } 267 268 func (vm *VM) updateNicParameters(networks []map[string]interface{}, networkSection *types.NetworkConnectionSection) error { 269 for tfNicSlot, network := range networks { 270 for loopIndex := range networkSection.NetworkConnection { 271 // Change network config only if we have the same virtual slot number as in .tf config 272 if tfNicSlot == networkSection.NetworkConnection[loopIndex].NetworkConnectionIndex { 273 274 // Determine what type of address is requested for the vApp 275 var ipAllocationMode string 276 ipAddress := "Any" 277 278 var ipFieldString string 279 ipField, ipIsSet := network["ip"] 280 if ipIsSet { 281 ipFieldString = ipField.(string) 282 } 283 284 switch { 285 // TODO v3.0 remove from here when deprecated `ip` and `network_name` attributes are removed 286 case ipIsSet && ipFieldString == "dhcp": // Deprecated ip="dhcp" mode 287 ipAllocationMode = types.IPAllocationModeDHCP 288 case ipIsSet && ipFieldString == "allocated": // Deprecated ip="allocated" mode 289 ipAllocationMode = types.IPAllocationModePool 290 case ipIsSet && ipFieldString == "none": // Deprecated ip="none" mode 291 ipAllocationMode = types.IPAllocationModeNone 292 293 // Deprecated ip="valid_ip" mode (currently it is hit by ip_allocation_mode=MANUAL as well) 294 case ipIsSet && net.ParseIP(ipFieldString) != nil: 295 ipAllocationMode = types.IPAllocationModeManual 296 ipAddress = ipFieldString 297 case ipIsSet && ipFieldString != "": // Deprecated ip="something_invalid" we default to DHCP. This is odd but backwards compatible. 298 ipAllocationMode = types.IPAllocationModeDHCP 299 // TODO v3.0 remove until here when deprecated `ip` and `network_name` attributes are removed 300 301 // Removed for Coverity warning: dead code - We can reinstate after removing above code 302 //case ipIsSet && net.ParseIP(ipFieldString) != nil && (network["ip_allocation_mode"].(string) == types.IPAllocationModeManual): 303 // ipAllocationMode = types.IPAllocationModeManual 304 // ipAddress = ipFieldString 305 default: // New networks functionality. IP was not set and we're defaulting to provided ip_allocation_mode (only manual requires the IP) 306 ipAllocationMode = network["ip_allocation_mode"].(string) 307 } 308 309 networkSection.NetworkConnection[loopIndex].NeedsCustomization = true 310 networkSection.NetworkConnection[loopIndex].IsConnected = true 311 networkSection.NetworkConnection[loopIndex].IPAddress = ipAddress 312 networkSection.NetworkConnection[loopIndex].IPAddressAllocationMode = ipAllocationMode 313 314 // for IPAllocationModeNone we hardcode special network name used by vcd 'none' 315 if ipAllocationMode == types.IPAllocationModeNone { 316 networkSection.NetworkConnection[loopIndex].Network = types.NoneNetwork 317 } else { 318 if _, ok := network["network_name"]; !ok { 319 return fmt.Errorf("could not identify network name") 320 } 321 networkSection.NetworkConnection[loopIndex].Network = network["network_name"].(string) 322 } 323 324 // If we have one NIC only then it is primary by default, otherwise we check for "is_primary" key 325 if (len(networks) == 1) || (network["is_primary"] != nil && network["is_primary"].(bool)) { 326 networkSection.PrimaryNetworkConnectionIndex = tfNicSlot 327 } 328 } 329 } 330 } 331 return nil 332 } 333 334 // ChangeNetworkConfig allows to update existing VM NIC configuration.f 335 func (vm *VM) ChangeNetworkConfig(networks []map[string]interface{}) (Task, error) { 336 err := vm.Refresh() 337 if err != nil { 338 return Task{}, fmt.Errorf("error refreshing VM before running customization: %s", err) 339 } 340 341 networkSection, err := vm.GetNetworkConnectionSection() 342 if err != nil { 343 return Task{}, fmt.Errorf("could not retrieve network connection for VM: %s", err) 344 } 345 346 err = vm.updateNicParameters(networks, networkSection) 347 if err != nil { 348 return Task{}, fmt.Errorf("failed processing NIC parameters: %s", err) 349 } 350 351 networkSection.Xmlns = types.XMLNamespaceVCloud 352 networkSection.Ovf = types.XMLNamespaceOVF 353 networkSection.Info = "Specifies the available VM network connections" 354 355 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 356 apiEndpoint.Path += "/networkConnectionSection/" 357 358 // Return the task 359 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 360 types.MimeNetworkConnectionSection, "error changing network config: %s", networkSection) 361 } 362 363 // Deprecated: use vm.ChangeMemory instead 364 func (vm *VM) ChangeMemorySize(size int) (Task, error) { 365 366 err := vm.Refresh() 367 if err != nil { 368 return Task{}, fmt.Errorf("error refreshing VM before running customization: %s", err) 369 } 370 371 newMem := &types.OVFItem{ 372 XmlnsRasd: types.XMLNamespaceRASD, 373 XmlnsVCloud: types.XMLNamespaceVCloud, 374 XmlnsXsi: types.XMLNamespaceXSI, 375 VCloudHREF: vm.VM.HREF + "/virtualHardwareSection/memory", 376 VCloudType: types.MimeRasdItem, 377 AllocationUnits: "byte * 2^20", 378 Description: "Memory SizeMb", 379 ElementName: strconv.Itoa(size) + " MB of memory", 380 InstanceID: 5, 381 Reservation: 0, 382 ResourceType: types.ResourceTypeMemory, 383 VirtualQuantity: int64(size), 384 Weight: 0, 385 Link: &types.Link{ 386 HREF: vm.VM.HREF + "/virtualHardwareSection/memory", 387 Rel: "edit", 388 Type: types.MimeRasdItem, 389 }, 390 } 391 392 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 393 apiEndpoint.Path += "/virtualHardwareSection/memory" 394 395 // Return the task 396 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 397 types.MimeRasdItem, "error changing memory size: %s", newMem) 398 } 399 400 func (vm *VM) RunCustomizationScript(computerName, script string) (Task, error) { 401 return vm.Customize(computerName, script, false) 402 } 403 404 // GetGuestCustomizationStatus retrieves guest customization status. 405 // It can be one of "GC_PENDING", "REBOOT_PENDING", "GC_FAILED", "POST_GC_PENDING", "GC_COMPLETE" 406 func (vm *VM) GetGuestCustomizationStatus() (string, error) { 407 guestCustomizationStatus := &types.GuestCustomizationStatusSection{} 408 409 if vm.VM.HREF == "" { 410 return "", fmt.Errorf("cannot retrieve guest customization, VM HREF is empty") 411 } 412 413 _, err := vm.client.ExecuteRequest(vm.VM.HREF+"/guestcustomizationstatus", http.MethodGet, 414 types.MimeGuestCustomizationStatus, "error retrieving guest customization status: %s", nil, guestCustomizationStatus) 415 416 // The request was successful 417 return guestCustomizationStatus.GuestCustStatus, err 418 } 419 420 // BlockWhileGuestCustomizationStatus blocks until the customization status of VM exits unwantedStatus. 421 // It sleeps 3 seconds between iterations and times out after timeOutAfterSeconds of seconds. 422 // 423 // timeOutAfterSeconds must be more than 4 and less than 2 hours (60s*120) 424 func (vm *VM) BlockWhileGuestCustomizationStatus(unwantedStatus string, timeOutAfterSeconds int) error { 425 if timeOutAfterSeconds < 5 || timeOutAfterSeconds > 60*120 { 426 return fmt.Errorf("timeOutAfterSeconds must be in range 4<X<7200") 427 } 428 429 timeoutAfter := time.After(time.Duration(timeOutAfterSeconds) * time.Second) 430 tick := time.NewTicker(3 * time.Second) 431 432 for { 433 select { 434 case <-timeoutAfter: 435 return fmt.Errorf("timed out waiting for VM guest customization status to exit state %s after %d seconds", 436 unwantedStatus, timeOutAfterSeconds) 437 case <-tick.C: 438 currentStatus, err := vm.GetGuestCustomizationStatus() 439 if err != nil { 440 return fmt.Errorf("could not get VM customization status %s", err) 441 } 442 if currentStatus != unwantedStatus { 443 return nil 444 } 445 } 446 } 447 } 448 449 // Customize function allows to set ComputerName, apply customization script and enable or disable the changeSid option 450 // 451 // Deprecated: Use vm.SetGuestCustomizationSection() 452 func (vm *VM) Customize(computerName, script string, changeSid bool) (Task, error) { 453 err := vm.Refresh() 454 if err != nil { 455 return Task{}, fmt.Errorf("error refreshing VM before running customization: %s", err) 456 } 457 458 vu := &types.GuestCustomizationSection{ 459 Ovf: types.XMLNamespaceOVF, 460 Xsi: types.XMLNamespaceXSI, 461 Xmlns: types.XMLNamespaceVCloud, 462 463 HREF: vm.VM.HREF, 464 Type: types.MimeGuestCustomizationSection, 465 Info: "Specifies Guest OS Customization Settings", 466 Enabled: addrOf(true), 467 ComputerName: computerName, 468 CustomizationScript: script, 469 ChangeSid: &changeSid, 470 } 471 472 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 473 apiEndpoint.Path += "/guestCustomizationSection/" 474 475 // Return the task 476 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 477 types.MimeGuestCustomizationSection, "error customizing VM: %s", vu) 478 } 479 480 // Undeploy triggers a VM undeploy and power off action. "Power off" action in UI behaves this way. 481 func (vm *VM) Undeploy() (Task, error) { 482 483 vu := &types.UndeployVAppParams{ 484 Xmlns: types.XMLNamespaceVCloud, 485 UndeployPowerAction: "powerOff", 486 } 487 488 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 489 apiEndpoint.Path += "/action/undeploy" 490 491 // Return the task 492 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 493 types.MimeUndeployVappParams, "error undeploy VM: %s", vu) 494 } 495 496 // Shutdown triggers a VM undeploy and shutdown action. "Shut Down Guest OS" action in UI behaves 497 // this way. 498 // 499 // Note. Success of this operation depends on the VM having Guest Tools installed. 500 func (vm *VM) Shutdown() (Task, error) { 501 502 vu := &types.UndeployVAppParams{ 503 Xmlns: types.XMLNamespaceVCloud, 504 UndeployPowerAction: "shutdown", 505 } 506 507 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 508 apiEndpoint.Path += "/action/undeploy" 509 510 // Return the task 511 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 512 types.MimeUndeployVappParams, "error undeploy VM: %s", vu) 513 } 514 515 // Attach or detach an independent disk 516 // Use the disk/action/attach or disk/action/detach links in a VM to attach or detach an independent disk. 517 // Reference: vCloud API Programming Guide for Service Providers vCloud API 30.0 PDF Page 164 - 165, 518 // https://vdc-download.vmware.com/vmwb-repository/dcr-public/1b6cf07d-adb3-4dba-8c47-9c1c92b04857/ 519 // 241956dd-e128-4fcc-8131-bf66e1edd895/vcloud_sp_api_guide_30_0.pdf 520 func (vm *VM) attachOrDetachDisk(diskParams *types.DiskAttachOrDetachParams, rel string) (Task, error) { 521 util.Logger.Printf("[TRACE] Attach or detach disk, href: %s, rel: %s \n", diskParams.Disk.HREF, rel) 522 523 var attachOrDetachDiskLink *types.Link 524 for _, link := range vm.VM.Link { 525 if link.Rel == rel && link.Type == types.MimeDiskAttachOrDetachParams { 526 util.Logger.Printf("[TRACE] Attach or detach disk - found the proper link for request, HREF: %s, name: %s, type: %s, id: %s, rel: %s \n", 527 link.HREF, 528 link.Name, 529 link.Type, 530 link.ID, 531 link.Rel) 532 attachOrDetachDiskLink = link 533 } 534 } 535 536 if attachOrDetachDiskLink == nil { 537 return Task{}, fmt.Errorf("could not find request URL for attach or detach disk in disk Link") 538 } 539 540 diskParams.Xmlns = types.XMLNamespaceVCloud 541 542 // Return the task 543 return vm.client.ExecuteTaskRequest(attachOrDetachDiskLink.HREF, http.MethodPost, 544 attachOrDetachDiskLink.Type, "error attach or detach disk: %s", diskParams) 545 } 546 547 // AttachDisk attaches an independent disk 548 // Call attachOrDetachDisk with disk and types.RelDiskAttach to attach an independent disk. 549 // Please verify the independent disk is not connected to any VM before calling this function. 550 // If the independent disk is connected to a VM, the task will be failed. 551 // Reference: vCloud API Programming Guide for Service Providers vCloud API 30.0 PDF Page 164 - 165, 552 // https://vdc-download.vmware.com/vmwb-repository/dcr-public/1b6cf07d-adb3-4dba-8c47-9c1c92b04857/ 553 // 241956dd-e128-4fcc-8131-bf66e1edd895/vcloud_sp_api_guide_30_0.pdf 554 func (vm *VM) AttachDisk(diskParams *types.DiskAttachOrDetachParams) (Task, error) { 555 if diskParams == nil || diskParams.Disk == nil || diskParams.Disk.HREF == "" { 556 return Task{}, fmt.Errorf("could not find disk info for attach") 557 } 558 util.Logger.Printf("[TRACE] Attach disk, HREF: %s\n", diskParams.Disk.HREF) 559 560 return vm.attachOrDetachDisk(diskParams, types.RelDiskAttach) 561 } 562 563 // DetachDisk detaches an independent disk 564 // Call attachOrDetachDisk with disk and types.RelDiskDetach to detach an independent disk. 565 // Please verify the independent disk is connected the VM before calling this function. 566 // If the independent disk is not connected to the VM, the task will be failed. 567 // Reference: vCloud API Programming Guide for Service Providers vCloud API 30.0 PDF Page 164 - 165, 568 // https://vdc-download.vmware.com/vmwb-repository/dcr-public/1b6cf07d-adb3-4dba-8c47-9c1c92b04857/ 569 // 241956dd-e128-4fcc-8131-bf66e1edd895/vcloud_sp_api_guide_30_0.pdf 570 func (vm *VM) DetachDisk(diskParams *types.DiskAttachOrDetachParams) (Task, error) { 571 572 if diskParams == nil || diskParams.Disk == nil || diskParams.Disk.HREF == "" { 573 return Task{}, fmt.Errorf("could not find disk info for detach") 574 } 575 util.Logger.Printf("[TRACE] Detach disk, HREF: %s\n", diskParams.Disk.HREF) 576 577 return vm.attachOrDetachDisk(diskParams, types.RelDiskDetach) 578 } 579 580 // HandleInsertMedia helper function finds media and calls InsertMedia 581 func (vm *VM) HandleInsertMedia(org *Org, catalogName, mediaName string) (Task, error) { 582 583 catalog, err := org.GetCatalogByName(catalogName, false) 584 if err != nil { 585 return Task{}, err 586 } 587 588 media, err := catalog.GetMediaByName(mediaName, false) 589 if err != nil { 590 return Task{}, err 591 } 592 593 return vm.InsertMedia(&types.MediaInsertOrEjectParams{ 594 Media: &types.Reference{ 595 HREF: media.Media.HREF, 596 Name: media.Media.Name, 597 ID: media.Media.ID, 598 Type: media.Media.Type, 599 }, 600 }) 601 } 602 603 // HandleEjectMediaAndAnswer helper function which finds media, calls EjectMedia, waits for task to complete and answer question. 604 // Also waits until VM status refreshes - this added as 9.7-10.0 vCD versions has lag in status update. 605 // answerYes - handles question risen when VM is running. True value enforces ejection. 606 func (vm *VM) HandleEjectMediaAndAnswer(org *Org, catalogName, mediaName string, answerYes bool) (*VM, error) { 607 task, err := vm.HandleEjectMedia(org, catalogName, mediaName) 608 if err != nil { 609 return nil, fmt.Errorf("error: %s", err) 610 } 611 612 err = task.WaitTaskCompletion(answerYes) 613 if err != nil { 614 return nil, fmt.Errorf("error: %s", err) 615 } 616 617 for i := 0; i < 10; i++ { 618 err = vm.Refresh() 619 if err != nil { 620 return nil, fmt.Errorf("error: %s", err) 621 } 622 if !isMediaInjected(vm.VM.VirtualHardwareSection.Item) { 623 return vm, nil 624 } 625 time.Sleep(200 * time.Millisecond) 626 } 627 628 return nil, fmt.Errorf("eject media executed but waiting for state update failed") 629 } 630 631 // check resource subtype for specific value which means media is injected 632 func isMediaInjected(items []*types.VirtualHardwareItem) bool { 633 for _, hardwareItem := range items { 634 if hardwareItem.ResourceSubType == types.VMsCDResourceSubType { 635 return true 636 } 637 } 638 return false 639 } 640 641 // HandleEjectMedia is a helper function which finds media and calls EjectMedia 642 func (vm *VM) HandleEjectMedia(org *Org, catalogName, mediaName string) (EjectTask, error) { 643 catalog, err := org.GetCatalogByName(catalogName, false) 644 if err != nil { 645 return EjectTask{}, err 646 } 647 648 media, err := catalog.GetMediaByName(mediaName, false) 649 if err != nil { 650 return EjectTask{}, err 651 } 652 653 task, err := vm.EjectMedia(&types.MediaInsertOrEjectParams{ 654 Media: &types.Reference{ 655 HREF: media.Media.HREF, 656 }, 657 }) 658 659 return task, err 660 } 661 662 // InsertMedia insert media for a VM 663 // Call insertOrEjectMedia with media and types.RelMediaInsertMedia to insert media from VM. 664 func (vm *VM) InsertMedia(mediaParams *types.MediaInsertOrEjectParams) (Task, error) { 665 util.Logger.Printf("[TRACE] Insert media, HREF: %s\n", mediaParams.Media.HREF) 666 667 err := validateMediaParams(mediaParams) 668 if err != nil { 669 return Task{}, err 670 } 671 672 return vm.insertOrEjectMedia(mediaParams, types.RelMediaInsertMedia) 673 } 674 675 // EjectMedia ejects media from VM 676 // Call insertOrEjectMedia with media and types.RelMediaEjectMedia to eject media from VM. 677 // If media isn't inserted then task still will be successful. 678 func (vm *VM) EjectMedia(mediaParams *types.MediaInsertOrEjectParams) (EjectTask, error) { 679 util.Logger.Printf("[TRACE] Detach disk, HREF: %s\n", mediaParams.Media.HREF) 680 681 err := validateMediaParams(mediaParams) 682 if err != nil { 683 return EjectTask{}, err 684 } 685 686 task, err := vm.insertOrEjectMedia(mediaParams, types.RelMediaEjectMedia) 687 if err != nil { 688 return EjectTask{}, err 689 } 690 691 return *NewEjectTask(&task, vm), nil 692 } 693 694 // validates that media and media.href isn't empty 695 func validateMediaParams(mediaParams *types.MediaInsertOrEjectParams) error { 696 if mediaParams.Media == nil { 697 return fmt.Errorf("could not find media info for eject") 698 } 699 if mediaParams.Media.HREF == "" { 700 return fmt.Errorf("could not find media HREF which is required for insert") 701 } 702 return nil 703 } 704 705 // Insert or eject a media for VM 706 // Use the vm/action/insert or vm/action/eject links in a VM to insert or eject media. 707 // Reference: 708 // https://code.vmware.com/apis/287/vcloud#/doc/doc/operations/POST-InsertCdRom.html 709 // https://code.vmware.com/apis/287/vcloud#/doc/doc/operations/POST-EjectCdRom.html 710 func (vm *VM) insertOrEjectMedia(mediaParams *types.MediaInsertOrEjectParams, linkRel string) (Task, error) { 711 util.Logger.Printf("[TRACE] Insert or eject media, href: %s, name: %s, , linkRel: %s \n", mediaParams.Media.HREF, mediaParams.Media.Name, linkRel) 712 713 var insertOrEjectMediaLink *types.Link 714 for _, link := range vm.VM.Link { 715 if link.Rel == linkRel && link.Type == types.MimeMediaInsertOrEjectParams { 716 util.Logger.Printf("[TRACE] Insert or eject media - found the proper link for request, HREF: %s, "+ 717 "name: %s, type: %s, id: %s, rel: %s \n", link.HREF, link.Name, link.Type, link.ID, link.Rel) 718 insertOrEjectMediaLink = link 719 } 720 } 721 722 if insertOrEjectMediaLink == nil { 723 return Task{}, fmt.Errorf("could not find request URL for insert or eject media") 724 } 725 726 mediaParams.Xmlns = types.XMLNamespaceVCloud 727 728 // Return the task 729 return vm.client.ExecuteTaskRequest(insertOrEjectMediaLink.HREF, http.MethodPost, 730 insertOrEjectMediaLink.Type, "error insert or eject media: %s", mediaParams) 731 } 732 733 // GetQuestion uses the get existing VM question for operation which need additional response 734 // Reference: 735 // https://code.vmware.com/apis/287/vcloud#/doc/doc/operations/GET-VmPendingQuestion.html 736 func (vm *VM) GetQuestion() (types.VmPendingQuestion, error) { 737 738 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 739 apiEndpoint.Path += "/question" 740 741 req := vm.client.NewRequest(map[string]string{}, http.MethodGet, *apiEndpoint, nil) 742 743 resp, err := vm.client.Http.Do(req) 744 if err != nil { 745 return types.VmPendingQuestion{}, fmt.Errorf("error getting VM question: %s", err) 746 } 747 748 // vCD security feature - on no question return 403 access error 749 if http.StatusForbidden == resp.StatusCode { 750 util.Logger.Printf("No question found for VM: %s\n", vm.VM.ID) 751 return types.VmPendingQuestion{}, nil 752 } 753 754 if http.StatusOK != resp.StatusCode { 755 return types.VmPendingQuestion{}, fmt.Errorf("error getting question: %s", ParseErr(types.BodyTypeXML, resp, &types.Error{})) 756 } 757 758 question := &types.VmPendingQuestion{} 759 760 if err = decodeBody(types.BodyTypeXML, resp, question); err != nil { 761 return types.VmPendingQuestion{}, fmt.Errorf("error decoding question response: %s", err) 762 } 763 764 // The request was successful 765 return *question, nil 766 767 } 768 769 // AnswerQuestion uses the provided answer to existing VM question for operation which need additional response 770 // Reference: 771 // https://code.vmware.com/apis/287/vcloud#/doc/doc/operations/POST-AnswerVmPendingQuestion.html 772 func (vm *VM) AnswerQuestion(questionId string, choiceId int) error { 773 774 //validate input 775 if questionId == "" { 776 return fmt.Errorf("questionId can not be empty") 777 } 778 779 answer := &types.VmQuestionAnswer{ 780 Xmlns: types.XMLNamespaceVCloud, 781 QuestionId: questionId, 782 ChoiceId: choiceId, 783 } 784 785 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 786 apiEndpoint.Path += "/question/action/answer" 787 788 return vm.client.ExecuteRequestWithoutResponse(apiEndpoint.String(), http.MethodPost, 789 "", "error asnwering question: %s", answer) 790 } 791 792 // ToggleHardwareVirtualization allows to either enable or disable hardware assisted 793 // CPU virtualization for the VM. It can only be performed on a powered off VM and 794 // will return an error otherwise. This is mainly useful for hypervisor nesting. 795 func (vm *VM) ToggleHardwareVirtualization(isEnabled bool) (Task, error) { 796 vmStatus, err := vm.GetStatus() 797 if err != nil { 798 return Task{}, fmt.Errorf("unable to toggle hardware virtualization: %s", err) 799 } 800 if vmStatus != "POWERED_OFF" && vmStatus != "PARTIALLY_POWERED_OFF" { 801 return Task{}, fmt.Errorf("hardware virtualization can be changed from powered off state, status: %s", vmStatus) 802 } 803 804 apiEndpoint := urlParseRequestURI(vm.VM.HREF) 805 if isEnabled { 806 apiEndpoint.Path += "/action/enableNestedHypervisor" 807 } else { 808 apiEndpoint.Path += "/action/disableNestedHypervisor" 809 } 810 errMessage := fmt.Sprintf("error toggling hypervisor nesting feature to %t for VM: %%s", isEnabled) 811 return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 812 "", errMessage, nil) 813 } 814 815 // SetProductSectionList sets product section for a VM. It allows to change VM guest properties. 816 // 817 // The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered 818 // or returned as set before 819 func (vm *VM) SetProductSectionList(productSection *types.ProductSectionList) (*types.ProductSectionList, error) { 820 err := setProductSectionList(vm.client, vm.VM.HREF, productSection) 821 if err != nil { 822 return nil, fmt.Errorf("unable to set VM product section: %s", err) 823 } 824 825 return vm.GetProductSectionList() 826 } 827 828 // GetProductSectionList retrieves product section for a VM. It allows to read VM guest properties. 829 // 830 // The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered 831 // or returned as set before 832 func (vm *VM) GetProductSectionList() (*types.ProductSectionList, error) { 833 return getProductSectionList(vm.client, vm.VM.HREF) 834 } 835 836 // GetEnvironment returns the OVF Environment. It's only available for poweredOn VM 837 func (vm *VM) GetEnvironment() (*types.OvfEnvironment, error) { 838 vmStatus, err := vm.GetStatus() 839 if err != nil { 840 return nil, fmt.Errorf("unable to get OVF environment: %s", err) 841 } 842 843 if vmStatus != "POWERED_ON" { 844 return nil, fmt.Errorf("OVF environment is only available when VM is powered on") 845 } 846 847 return vm.VM.Environment, nil 848 } 849 850 // GetGuestCustomizationSection retrieves guest customization section for a VM. It allows to read VM guest customization properties. 851 func (vm *VM) GetGuestCustomizationSection() (*types.GuestCustomizationSection, error) { 852 if vm == nil || vm.VM.HREF == "" { 853 return nil, fmt.Errorf("vm or href cannot be empty to get guest customization section") 854 } 855 guestCustomizationSection := &types.GuestCustomizationSection{} 856 857 _, err := vm.client.ExecuteRequest(vm.VM.HREF+"/guestCustomizationSection", http.MethodGet, 858 types.MimeGuestCustomizationSection, "error retrieving guest customization section : %s", nil, guestCustomizationSection) 859 860 if err != nil { 861 return nil, fmt.Errorf("unable to retrieve guest customization section: %s", err) 862 } 863 864 return guestCustomizationSection, nil 865 } 866 867 // SetGuestCustomizationSection sets guest customization section for a VM. It allows to change VM guest customization properties. 868 func (vm *VM) SetGuestCustomizationSection(guestCustomizationSection *types.GuestCustomizationSection) (*types.GuestCustomizationSection, error) { 869 if vm == nil || vm.VM.HREF == "" { 870 return nil, fmt.Errorf("vm or href cannot be empty to get guest customization section") 871 } 872 873 guestCustomizationSection.Xmlns = types.XMLNamespaceVCloud 874 guestCustomizationSection.Ovf = types.XMLNamespaceOVF 875 876 task, err := vm.client.ExecuteTaskRequest(vm.VM.HREF+"/guestCustomizationSection", http.MethodPut, 877 types.MimeGuestCustomizationSection, "error setting product section: %s", guestCustomizationSection) 878 879 if err != nil { 880 return nil, fmt.Errorf("unable to set guest customization section: %s", err) 881 } 882 883 err = task.WaitTaskCompletion() 884 if err != nil { 885 return nil, fmt.Errorf("task for setting guest customization section failed: %s", err) 886 } 887 888 return vm.GetGuestCustomizationSection() 889 } 890 891 // GetParentVApp find parent vApp for VM by checking its "up" "link". 892 // 893 // Note. The VM has a parent vApp defined even if it was created as a standalone 894 func (vm *VM) GetParentVApp() (*VApp, error) { 895 if vm == nil || vm.VM == nil { 896 return nil, fmt.Errorf("vm object cannot be nil to get parent vApp") 897 } 898 899 for _, link := range vm.VM.Link { 900 if link.Type == types.MimeVApp && link.Rel == "up" { 901 vapp := NewVApp(vm.client) 902 vapp.VApp.HREF = link.HREF 903 904 err := vapp.Refresh() 905 906 if err != nil { 907 return nil, fmt.Errorf("could not refresh parent vApp for VM %s: %s", vm.VM.Name, err) 908 } 909 910 return vapp, nil 911 } 912 } 913 914 return nil, fmt.Errorf("could not find parent vApp link") 915 } 916 917 // GetParentVdc returns parent VDC for VM 918 func (vm *VM) GetParentVdc() (*Vdc, error) { 919 if vm == nil || vm.VM == nil { 920 return nil, fmt.Errorf("vm object cannot be nil to get parent vApp") 921 } 922 923 vapp, err := vm.GetParentVApp() 924 if err != nil { 925 return nil, fmt.Errorf("could not find parent vApp for VM %s: %s", vm.VM.Name, err) 926 } 927 928 vdc, err := vapp.GetParentVDC() 929 if err != nil { 930 return nil, fmt.Errorf("could not find parent vApp for VM %s: %s", vm.VM.Name, err) 931 } 932 933 return &vdc, nil 934 } 935 936 // getEdgeGatewaysForRoutedNics checks if any NICs are using routed networks and are attached to 937 // edge gateway 938 func (vm *VM) getEdgeGatewaysForRoutedNics(nicDhcpConfigs []nicDhcpConfig) ([]nicDhcpConfig, error) { 939 // Lookup parent vDC for VM 940 vdc, err := vm.GetParentVdc() 941 if err != nil { 942 return nil, fmt.Errorf("could not find parent vDC for VM %s: %s", vm.VM.Name, err) 943 } 944 945 for index, nic := range nicDhcpConfigs { 946 edgeGatewayName, err := vm.getEdgeGatewayNameForNic(nic.vmNicIndex) 947 if err != nil && !IsNotFound(err) { 948 return nil, fmt.Errorf("could not validate if NIC %d uses routed network attached to edge gateway: %s", 949 nic.vmNicIndex, err) 950 } 951 952 // This nicIndex is not attached to routed network, move further 953 if IsNotFound(err) { 954 util.Logger.Printf("[TRACE] [DHCP IP Lookup] VM '%s' NIC with index %d is not attached to edge gateway routed network\n", 955 vm.VM.Name, nic.vmNicIndex) 956 } else { 957 // Lookup edge gateway 958 edgeGateway, err := vdc.GetEdgeGatewayByName(edgeGatewayName, false) 959 if ContainsNotFound(err) { 960 util.Logger.Printf("[TRACE] [DHCP IP Lookup] edge gateway not found: %s. Ignoring.", edgeGatewayName) 961 continue 962 } 963 if err != nil { 964 return nil, fmt.Errorf("could not lookup edge gateway for routed network on NIC %d: %s", 965 nic.vmNicIndex, err) 966 } 967 968 util.Logger.Printf("[TRACE] [DHCP IP Lookup] VM '%s' NIC with index %d is attached to edge gateway routed network\n", 969 vm.VM.Name, nic.vmNicIndex) 970 nicDhcpConfigs[index].routedNetworkEdgeGateway = edgeGateway 971 } 972 } 973 974 return nicDhcpConfigs, nil 975 } 976 977 // nicDhcpConfig is used to group data for carrying between multiple functions and optimizing on API 978 // calls 979 type nicDhcpConfig struct { 980 vmNicIndex int 981 ip string 982 mac string 983 routedNetworkEdgeGateway *EdgeGateway 984 } 985 986 // nicDhcpConfigs is a slice of nicDhcpConfig 987 type nicDhcpConfigs []nicDhcpConfig 988 989 // getIpsFromNicDhcpConfigs extracts just IP addresses from nicDhcpConfigs 990 func getIpsFromNicDhcpConfigs(nicConfigs []nicDhcpConfig) []string { 991 result := make([]string, len(nicConfigs)) 992 for index, nicConfig := range nicConfigs { 993 result[index] = nicConfig.ip 994 } 995 return result 996 } 997 998 // allNicsHaveIps checks if all nicDhcpConfig in slice have not empty IP field 999 func allNicsHaveIps(nicConfigs []nicDhcpConfig) bool { 1000 allNicsHaveIps := true 1001 for _, nicConfig := range nicConfigs { 1002 if nicConfig.ip == "" { 1003 allNicsHaveIps = false 1004 } 1005 } 1006 return allNicsHaveIps 1007 } 1008 1009 // WaitForDhcpIpByNicIndexes accepts a slice of NIC indexes in VM, tries to get these IPs up to 1010 // maxWaitSeconds and then returns: 1011 // * a list of IPs 1012 // * whether the function hit timeout (some IP values may be available after timeout) 1013 // * error 1014 // 1015 // This function checks a slice of nicIndexes and reuses all possible API calls. It may return a 1016 // partial result for IP addresses when the timeout is hit. 1017 // 1018 // Getting a DHCP address is complicated because vCD (in UI and in types.NetworkConnectionSection) 1019 // reports IP addresses only when guest tools are present on a VM. This function also attempts to 1020 // check if VM NICs are attached to routed network on edge gateway - then there is a chance that 1021 // built-in DHCP pools are used and active DHCP leases can be found. 1022 // 1023 // For this function to work - at least one the following must be true: 1024 // * VM has guest tools (vCD UI shows IP address). (Takes longer time) 1025 // * VM DHCP interface is connected to routed Org network and is using NSX-V Edge Gateway DHCP. (Takes 1026 // less time, but is more constrained) 1027 func (vm *VM) WaitForDhcpIpByNicIndexes(nicIndexes []int, maxWaitSeconds int, useNsxvDhcpLeaseCheck bool) ([]string, bool, error) { 1028 util.Logger.Printf("[TRACE] [DHCP IP Lookup] VM '%s' attempting to lookup IP addresses for DHCP NICs %v\n", 1029 vm.VM.Name, nicIndexes) 1030 // validate NIC indexes 1031 if len(nicIndexes) == 0 { 1032 return []string{}, false, fmt.Errorf("at least one NIC index must be specified") 1033 } 1034 for index, nicIndex := range nicIndexes { 1035 if nicIndex < 0 { 1036 return []string{}, false, fmt.Errorf("NIC index %d cannot be negative", index) 1037 } 1038 } 1039 1040 // inject NIC indexes into structure []nicDhcpConfig as this allows to save API calls when 1041 // querying for multiple NICs 1042 // This slice is ordered the same as original slice of nicIndexes 1043 nicStates := make(nicDhcpConfigs, len(nicIndexes)) 1044 for index, nicIndex := range nicIndexes { 1045 nicStates[index].vmNicIndex = nicIndex 1046 } 1047 var err error 1048 if useNsxvDhcpLeaseCheck { // Edge gateways have to be looked up when DHCP lease checks are enabled 1049 // Lookup edge gateways for routed networks and store them 1050 nicStates, err = vm.getEdgeGatewaysForRoutedNics(nicStates) 1051 if err != nil { 1052 return []string{}, false, fmt.Errorf("unable to validate if NICs are attached to edge gateway: %s", err) 1053 } 1054 } 1055 1056 // Run a timer to wait for IPs being present until maxWaitSeconds 1057 timeoutAfter := time.After(time.Duration(maxWaitSeconds) * time.Second) 1058 tick := time.NewTicker(3 * time.Second) 1059 for { 1060 select { 1061 // If timeout occured - return as much as was found 1062 case <-timeoutAfter: 1063 ipSlice := getIpsFromNicDhcpConfigs(nicStates) 1064 util.Logger.Printf("[DEBUG] [DHCP IP Lookup] VM '%s' NICs with indexes %v did not all report IP "+ 1065 "addresses after %d seconds. Indexes: %v ,IPs: '%s'\n", vm.VM.Name, nicIndexes, 1066 maxWaitSeconds, nicIndexes, strings.Join(ipSlice, ", ")) 1067 return ipSlice, true, nil 1068 case <-tick.C: 1069 // Step 1 check if VMware tools reported IPs in NetworkConnectionSection (HTML5 UI reads it to show IPs as well). 1070 // Also populate MAC addresses into nicStates structure for later usage. 1071 nicStates, err = vm.getIpsMacsByNicIndexes(nicStates) 1072 if err != nil { 1073 return []string{}, false, fmt.Errorf("could not check IP addresses assigned to VM %s: %s", 1074 vm.VM.Name, err) 1075 } 1076 1077 // All IP addresses found - return 1078 if allNicsHaveIps(nicStates) { 1079 util.Logger.Printf("[TRACE] [DHCP IP Lookup] VM '%s' NICs with indexes %v all reported their IPs using guest tools\n", 1080 vm.VM.Name, nicIndexes) 1081 return getIpsFromNicDhcpConfigs(nicStates), false, nil 1082 } 1083 1084 util.Logger.Printf("[DEBUG] [DHCP IP Lookup] VM '%s' NICs with indexes %v did not all report their IPs using guest tools\n", 1085 vm.VM.Name, nicIndexes) 1086 1087 // Step 2 If enabled - check if DHCP leases in edge gateways can hint IP addresses 1088 if useNsxvDhcpLeaseCheck { 1089 nicStates, err = vm.getIpsByDhcpLeaseMacs(nicStates) 1090 if err != nil { 1091 return []string{}, false, fmt.Errorf("could not check MAC leases for VM '%s': %s", 1092 vm.VM.Name, err) 1093 } 1094 1095 // All IP addresses found - return 1096 if allNicsHaveIps(nicStates) { 1097 util.Logger.Printf("[DEBUG] [DHCP IP Lookup] VM '%s' NICs with indexes %v all reported their IPs after lease check\n", 1098 vm.VM.Name, nicIndexes) 1099 return getIpsFromNicDhcpConfigs(nicStates), false, nil 1100 } 1101 util.Logger.Printf("[DEBUG] [DHCP IP Lookup] VM '%s' NICs with indexes %v did not all report their IPs using DHCP leases\n", 1102 vm.VM.Name, nicIndexes) 1103 } 1104 } 1105 } 1106 } 1107 1108 // getEdgeGatewayNameForNic checks if a network card with specified nicIndex uses routed network and 1109 // is attached to particular edge gateway. Edge gateway name is returned if so. 1110 func (vm *VM) getEdgeGatewayNameForNic(nicIndex int) (string, error) { 1111 if nicIndex < 0 { 1112 return "", fmt.Errorf("NIC index cannot be negative") 1113 } 1114 1115 networkConnnectionSection, err := vm.GetNetworkConnectionSection() 1116 if err != nil { 1117 return "", fmt.Errorf("could not get IP address for NIC %d: %s", nicIndex, err) 1118 } 1119 1120 // Find NIC 1121 var networkConnection *types.NetworkConnection 1122 for _, nic := range networkConnnectionSection.NetworkConnection { 1123 if nic.NetworkConnectionIndex == nicIndex { 1124 networkConnection = nic 1125 } 1126 } 1127 1128 if networkConnection == nil { 1129 return "", fmt.Errorf("could not find NIC with index %d", nicIndex) 1130 } 1131 1132 // Validate if the VM is attached to routed org vdc network 1133 vdc, err := vm.GetParentVdc() 1134 if err != nil { 1135 return "", fmt.Errorf("could not find parent vDC for VM %s: %s", vm.VM.Name, err) 1136 } 1137 1138 edgeGatewayName, err := vdc.FindEdgeGatewayNameByNetwork(networkConnection.Network) 1139 if err != nil && !IsNotFound(err) { 1140 return "", fmt.Errorf("could not find edge gateway name for network %s: %s", 1141 networkConnection.Network, err) 1142 } 1143 1144 if edgeGatewayName == "" { 1145 return "", ErrorEntityNotFound 1146 } 1147 1148 return edgeGatewayName, nil 1149 } 1150 1151 // getIpsByDhcpLeaseMacs accepts a slice of nicDhcpConfig and tries to find an active DHCP lease for 1152 // all defined MAC addresses 1153 func (vm *VM) getIpsByDhcpLeaseMacs(nicConfigs []nicDhcpConfig) ([]nicDhcpConfig, error) { 1154 dhcpLeaseCache := make(map[string][]*types.EdgeDhcpLeaseInfo) 1155 1156 var err error 1157 1158 for index, nicConfig := range nicConfigs { 1159 // If the NIC does not have Edge Gateway defined - skip it 1160 if nicConfig.routedNetworkEdgeGateway == nil { 1161 util.Logger.Printf("[DEBUG] VM '%s' skipping check of DHCP lease for NIC index %d "+ 1162 "because it is not attached to edge gateway\n", vm.VM.Name, index) 1163 continue 1164 } 1165 1166 egw := nicConfig.routedNetworkEdgeGateway 1167 1168 if dhcpLeaseCache[egw.EdgeGateway.Name] == nil { 1169 dhcpLeaseCache[egw.EdgeGateway.Name], err = egw.GetAllNsxvDhcpLeases() 1170 if err != nil && !IsNotFound(err) { 1171 return nicConfigs, fmt.Errorf("unable to get DHCP leases for edge gateway %s: %s", 1172 egw.EdgeGateway.Name, err) 1173 } 1174 } 1175 1176 for _, lease := range dhcpLeaseCache[egw.EdgeGateway.Name] { 1177 util.Logger.Printf("[DEBUG] Checking DHCP lease: %#+v", lease) 1178 if lease.BindingState == "active" && lease.MacAddress == nicConfig.mac { 1179 nicConfigs[index].ip = lease.IpAddress 1180 } 1181 } 1182 1183 } 1184 1185 return nicConfigs, nil 1186 } 1187 1188 // getIpsMacsByNicIndexes searches for NICs attached to VM by vmNicIndex and populated 1189 // []nicDhcpConfig with IPs and MAC addresses 1190 func (vm *VM) getIpsMacsByNicIndexes(nicConfigs []nicDhcpConfig) ([]nicDhcpConfig, error) { 1191 networkConnnectionSection, err := vm.GetNetworkConnectionSection() 1192 if err != nil { 1193 return nil, fmt.Errorf("could not get IP configuration for VM %s : %s", vm.VM.Name, err) 1194 } 1195 1196 // Find NICs and populate their IPs and MACs 1197 for sliceIndex, nicConfig := range nicConfigs { 1198 for _, nic := range networkConnnectionSection.NetworkConnection { 1199 if nic.NetworkConnectionIndex == nicConfig.vmNicIndex { 1200 nicConfigs[sliceIndex].ip = nic.IPAddress 1201 nicConfigs[sliceIndex].mac = nic.MACAddress 1202 } 1203 } 1204 } 1205 1206 return nicConfigs, nil 1207 } 1208 1209 // AddInternalDisk creates disk type *types.DiskSettings to the VM. 1210 // Returns new disk ID and error. 1211 // Runs synchronously, VM is ready for another operation after this function returns. 1212 func (vm *VM) AddInternalDisk(diskData *types.DiskSettings) (string, error) { 1213 err := vm.Refresh() 1214 if err != nil { 1215 return "", fmt.Errorf("error refreshing VM: %s", err) 1216 } 1217 1218 err = vm.validateInternalDiskInput(diskData, vm.VM.Name, vm.VM.ID) 1219 if err != nil { 1220 return "", err 1221 } 1222 1223 var diskSettings []*types.DiskSettings 1224 if vm.VM.VmSpecSection != nil && vm.VM.VmSpecSection.DiskSection != nil && vm.VM.VmSpecSection.DiskSection.DiskSettings != nil { 1225 diskSettings = vm.VM.VmSpecSection.DiskSection.DiskSettings 1226 } 1227 1228 diskSettings = append(diskSettings, diskData) 1229 vmSpecSection := vm.VM.VmSpecSection 1230 vmSpecSection.DiskSection.DiskSettings = diskSettings 1231 1232 vmSpecSection, err = vm.UpdateInternalDisks(vmSpecSection) 1233 if err != nil { 1234 return "", err 1235 } 1236 1237 for _, diskSetting := range vmSpecSection.DiskSection.DiskSettings { 1238 if diskSetting.AdapterType == diskData.AdapterType && 1239 diskSetting.BusNumber == diskData.BusNumber && 1240 diskSetting.UnitNumber == diskData.UnitNumber { 1241 return diskSetting.DiskId, nil 1242 } 1243 } 1244 1245 return "", fmt.Errorf("created disk wasn't in list of returned VM internal disks") 1246 } 1247 1248 func (vm *VM) validateInternalDiskInput(diskData *types.DiskSettings, vmName, vmId string) error { 1249 if diskData.AdapterType == "" { 1250 return fmt.Errorf("[VM %s Id %s] disk settings missing required field: adapter type", vmName, vmId) 1251 } 1252 1253 if diskData.BusNumber < 0 { 1254 return fmt.Errorf("[VM %s Id %s] disk settings bus number has to be 0 or higher", vmName, vmId) 1255 } 1256 1257 if diskData.UnitNumber < 0 { 1258 return fmt.Errorf("[VM %s Id %s] disk settings unit number has to be 0 or higher", vmName, vmId) 1259 } 1260 1261 if diskData.SizeMb < int64(0) { 1262 return fmt.Errorf("[VM %s Id %s] disk settings size MB has to be 0 or higher", vmName, vmId) 1263 } 1264 1265 if diskData.IopsAllocation != nil && diskData.IopsAllocation.Reservation < int64(0) { 1266 return fmt.Errorf("[VM %s Id %s] disk settings iops reservation has to be 0 or higher", vmName, vmId) 1267 } 1268 1269 if diskData.ThinProvisioned == nil { 1270 return fmt.Errorf("[VM %s Id %s] disk settings missing required field: thin provisioned", vmName, vmId) 1271 } 1272 1273 if diskData.StorageProfile == nil { 1274 return fmt.Errorf("[VM %s Id %s]disk settings missing required field: storage profile", vmName, vmId) 1275 } 1276 1277 return nil 1278 } 1279 1280 // GetInternalDiskById returns a *types.DiskSettings if one exists. 1281 // If it doesn't, returns nil and ErrorEntityNotFound or other err. 1282 func (vm *VM) GetInternalDiskById(diskId string, refresh bool) (*types.DiskSettings, error) { 1283 if diskId == "" { 1284 return nil, fmt.Errorf("cannot get internal disk - provided disk Id is empty") 1285 } 1286 1287 if refresh { 1288 err := vm.Refresh() 1289 if err != nil { 1290 return nil, fmt.Errorf("error refreshing VM: %s", err) 1291 } 1292 } 1293 1294 if vm.VM.VmSpecSection.DiskSection == nil || vm.VM.VmSpecSection.DiskSection.DiskSettings == nil || 1295 len(vm.VM.VmSpecSection.DiskSection.DiskSettings) == 0 { 1296 return nil, fmt.Errorf("cannot get internal disk - VM doesn't have internal disks") 1297 } 1298 1299 for _, diskSetting := range vm.VM.VmSpecSection.DiskSection.DiskSettings { 1300 if diskSetting.DiskId == diskId { 1301 return diskSetting, nil 1302 } 1303 } 1304 1305 return nil, ErrorEntityNotFound 1306 } 1307 1308 // DeleteInternalDisk delete disk using provided disk ID. 1309 // Runs synchronously, VM is ready for another operation after this function returns. 1310 func (vm *VM) DeleteInternalDisk(diskId string) error { 1311 err := vm.Refresh() 1312 if err != nil { 1313 return fmt.Errorf("error refreshing VM: %s", err) 1314 } 1315 1316 diskSettings := vm.VM.VmSpecSection.DiskSection.DiskSettings 1317 if diskSettings == nil { 1318 diskSettings = []*types.DiskSettings{} 1319 } 1320 1321 diskPlacement := -1 1322 for i, diskSetting := range vm.VM.VmSpecSection.DiskSection.DiskSettings { 1323 if diskSetting.DiskId == diskId { 1324 diskPlacement = i 1325 } 1326 } 1327 1328 if diskPlacement == -1 { 1329 return ErrorEntityNotFound 1330 } 1331 1332 // remove disk from slice 1333 diskSettings = append(diskSettings[:diskPlacement], diskSettings[diskPlacement+1:]...) 1334 1335 vmSpecSection := vm.VM.VmSpecSection 1336 vmSpecSection.DiskSection.DiskSettings = diskSettings 1337 1338 _, err = vm.UpdateInternalDisks(vmSpecSection) 1339 if err != nil { 1340 return fmt.Errorf("error deleting VM %s internal disk %s: %s", vm.VM.Name, diskId, err) 1341 } 1342 1343 return nil 1344 } 1345 1346 // UpdateInternalDisks applies disks configuration for the VM. 1347 // types.VmSpecSection has to have all internal disk state. Disks which don't match provided ones in types.VmSpecSection 1348 // will be deleted. Matched internal disk will be updated. New internal disk description found 1349 // in types.VmSpecSection will be created. Returns updated types.VmSpecSection and error. 1350 // Runs synchronously, VM is ready for another operation after this function returns. 1351 func (vm *VM) UpdateInternalDisks(disksSettingToUpdate *types.VmSpecSection) (*types.VmSpecSection, error) { 1352 if vm.VM.HREF == "" { 1353 return nil, fmt.Errorf("cannot update internal disks - VM HREF is unset") 1354 } 1355 1356 description := vm.VM.Description 1357 vm, err := vm.UpdateVmSpecSection(disksSettingToUpdate, description) 1358 if err != nil { 1359 return nil, err 1360 } 1361 1362 return vm.VM.VmSpecSection, nil 1363 } 1364 1365 // UpdateInternalDisksAsync applies disks configuration for the VM. 1366 // types.VmSpecSection has to have all internal disk state. Disks which don't 1367 // match provided ones in types.VmSpecSection will be deleted. 1368 // Matched internal disk will be updated. New internal disk description found in types.VmSpecSection will be created. 1369 // Returns Task and error. 1370 // 1371 // Deprecated: use UpdateInternalDisks or UpdateVmSpecSectionAsync instead 1372 func (vm *VM) UpdateInternalDisksAsync(disksSettingToUpdate *types.VmSpecSection) (Task, error) { 1373 if vm.VM.HREF == "" { 1374 return Task{}, fmt.Errorf("cannot update disks, VM HREF is unset") 1375 } 1376 1377 vmSpecSectionModified := true 1378 disksSettingToUpdate.Modified = &vmSpecSectionModified 1379 1380 return vm.client.ExecuteTaskRequest(vm.VM.HREF+"/action/reconfigureVm", http.MethodPost, 1381 types.MimeVM, "error updating VM disks: %s", &types.VMDiskChange{ 1382 Xmlns: types.XMLNamespaceVCloud, 1383 Ovf: types.XMLNamespaceOVF, 1384 Name: vm.VM.Name, 1385 Description: vm.VM.Description, 1386 VmSpecSection: disksSettingToUpdate, 1387 }) 1388 } 1389 1390 // AddEmptyVm adds an empty VM (without template) to vApp and returns the new created VM or an error. 1391 func (vapp *VApp) AddEmptyVm(reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) (*VM, error) { 1392 apiVersion, err := vapp.client.MaxSupportedVersion() 1393 if err != nil { 1394 return nil, err 1395 } 1396 vmFunctions := getVmVersionedFuncsByVdcVersion("vm" + apiVersionToVcdVersion[apiVersion]) 1397 if vmFunctions.AddEmptyVm == nil { 1398 return nil, fmt.Errorf("function AddEmptyVm is not defined for %s", "vdc"+apiVersion) 1399 } 1400 1401 util.Logger.Printf("[DEBUG] AddEmptyVm call function for version %s", vmFunctions.SupportedVersion) 1402 return vmFunctions.AddEmptyVm(vapp, reComposeVAppParams) 1403 } 1404 1405 // AddEmptyVmAsync adds an empty VM (without template) to the vApp and returns a Task and an error. 1406 func (vapp *VApp) AddEmptyVmAsync(reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) (Task, error) { 1407 apiVersion, err := vapp.client.MaxSupportedVersion() 1408 if err != nil { 1409 return Task{}, err 1410 } 1411 vmFunctions := getVmVersionedFuncsByVdcVersion("vm" + apiVersionToVcdVersion[apiVersion]) 1412 if vmFunctions.AddEmptyVmAsync == nil { 1413 return Task{}, fmt.Errorf("function AddEmptyVmAsync is not defined for %s", "vdc"+apiVersion) 1414 } 1415 1416 util.Logger.Printf("[DEBUG] AddEmptyVmAsync call function for version %s", vmFunctions.SupportedVersion) 1417 1418 return vmFunctions.AddEmptyVmAsync(vapp, reComposeVAppParams) 1419 } 1420 1421 // validateEmptyVmParams checks if all required parameters are provided 1422 func validateEmptyVmParams(reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) error { 1423 if reComposeVAppParams.CreateItem == nil { 1424 return fmt.Errorf("[AddEmptyVmAsync] CreateItem can't be empty") 1425 } 1426 1427 if reComposeVAppParams.CreateItem.Name == "" { 1428 return fmt.Errorf("[AddEmptyVmAsync] CreateItem.Name can't be empty") 1429 } 1430 1431 if reComposeVAppParams.CreateItem.VmSpecSection == nil { 1432 return fmt.Errorf("[AddEmptyVmAsync] CreateItem.VmSpecSection can't be empty") 1433 } 1434 1435 if reComposeVAppParams.CreateItem.VmSpecSection.HardwareVersion == nil { 1436 return fmt.Errorf("[AddEmptyVmAsync] CreateItem.VmSpecSection.HardwareVersion can't be empty") 1437 } 1438 1439 if reComposeVAppParams.CreateItem.VmSpecSection.HardwareVersion.Value == "" { 1440 return fmt.Errorf("[AddEmptyVmAsync] CreateItem.VmSpecSection.HardwareVersion.Value can't be empty") 1441 } 1442 1443 if reComposeVAppParams.CreateItem.VmSpecSection.MemoryResourceMb == nil { 1444 return fmt.Errorf("[AddEmptyVmAsync] CreateItem.VmSpecSection.MemoryResourceMb can't be empty") 1445 } 1446 1447 if reComposeVAppParams.CreateItem.VmSpecSection.MemoryResourceMb.Configured <= int64(0) { 1448 return fmt.Errorf("[AddEmptyVmAsync] CreateItem.VmSpecSection.MemoryResourceMb.Configured can't be empty") 1449 } 1450 1451 return nil 1452 } 1453 1454 // UpdateVmSpecSection updates VM Spec section and returns refreshed VM or error. 1455 func (vm *VM) UpdateVmSpecSection(vmSettingsToUpdate *types.VmSpecSection, description string) (*VM, error) { 1456 task, err := vm.UpdateVmSpecSectionAsync(vmSettingsToUpdate, description) 1457 if err != nil { 1458 return nil, err 1459 } 1460 1461 err = task.WaitTaskCompletion() 1462 if err != nil { 1463 return nil, err 1464 } 1465 1466 err = vm.Refresh() 1467 if err != nil { 1468 return nil, err 1469 } 1470 1471 return vm, nil 1472 1473 } 1474 1475 // UpdateVmSpecSectionAsync updates VM Spec section and returns Task and error. 1476 func (vm *VM) UpdateVmSpecSectionAsync(vmSettingsToUpdate *types.VmSpecSection, description string) (Task, error) { 1477 if vm.VM.HREF == "" { 1478 return Task{}, fmt.Errorf("cannot update VM spec section, VM HREF is unset") 1479 } 1480 1481 // Firmware field is unavailable on <37.1 API Versions 1482 if vmSettingsToUpdate.Firmware != "" && vm.client.APIVCDMaxVersionIs("<37.1") { 1483 return Task{}, fmt.Errorf("VM Firmware can only be set on VCD 10.4.1+ (API 37.1+)") 1484 } 1485 1486 vmSpecSectionModified := true 1487 vmSettingsToUpdate.Modified = &vmSpecSectionModified 1488 1489 // `reconfigureVm` updates VM name, Description, and any or all of the following sections. 1490 // VirtualHardwareSection 1491 // OperatingSystemSection 1492 // NetworkConnectionSection 1493 // GuestCustomizationSection 1494 // Sections not included in the request body will not be updated. 1495 1496 vmPayload := &types.Vm{ 1497 Xmlns: types.XMLNamespaceVCloud, 1498 Ovf: types.XMLNamespaceOVF, 1499 Name: vm.VM.Name, 1500 Description: description, 1501 VmSpecSection: vmSettingsToUpdate, 1502 } 1503 1504 // Since 37.1 there is a Firmware field in VmSpecSection 1505 return vm.client.ExecuteTaskRequestWithApiVersion(vm.VM.HREF+"/action/reconfigureVm", 1506 http.MethodPost, types.MimeVM, "error updating VM spec section: %s", vmPayload, 1507 vm.client.GetSpecificApiVersionOnCondition(">=37.1", "37.1")) 1508 } 1509 1510 // UpdateComputePolicyV2 updates VM Compute policy with the given compute policies using v2.0.0 OpenAPI endpoint, 1511 // and returns an error if something went wrong, or the refreshed VM if all went OK. 1512 // Updating with an empty compute policy ID will remove it from the VM. Both policies can't be empty as the VM requires 1513 // at least one policy. 1514 func (vm *VM) UpdateComputePolicyV2(sizingPolicyId, placementPolicyId, vGpuPolicyId string) (*VM, error) { 1515 task, err := vm.UpdateComputePolicyV2Async(sizingPolicyId, placementPolicyId, vGpuPolicyId) 1516 if err != nil { 1517 return nil, err 1518 } 1519 1520 err = task.WaitTaskCompletion() 1521 if err != nil { 1522 return nil, err 1523 } 1524 1525 err = vm.Refresh() 1526 if err != nil { 1527 return nil, err 1528 } 1529 1530 return vm, nil 1531 1532 } 1533 1534 // UpdateComputePolicy updates VM compute policy and returns refreshed VM or error. 1535 // Deprecated: Use VM.UpdateComputePolicyV2 instead 1536 func (vm *VM) UpdateComputePolicy(computePolicy *types.VdcComputePolicy) (*VM, error) { 1537 task, err := vm.UpdateComputePolicyAsync(computePolicy) 1538 if err != nil { 1539 return nil, err 1540 } 1541 1542 err = task.WaitTaskCompletion() 1543 if err != nil { 1544 return nil, err 1545 } 1546 1547 err = vm.Refresh() 1548 if err != nil { 1549 return nil, err 1550 } 1551 1552 return vm, nil 1553 1554 } 1555 1556 // UpdateComputePolicyV2Async updates VM Compute policy with the given compute policies using v2.0.0 OpenAPI endpoint, 1557 // and returns a Task and an error. Updating with an empty compute policy ID will remove it from the VM. Both 1558 // policies can't be empty as the VM requires at least one policy. 1559 // WARNING: At the moment, vGPU Policies are not supported. Using one will return an error. 1560 func (vm *VM) UpdateComputePolicyV2Async(sizingPolicyId, placementPolicyId, vGpuPolicyId string) (Task, error) { 1561 if vm.VM.HREF == "" { 1562 return Task{}, fmt.Errorf("cannot update VM compute policy, VM HREF is unset") 1563 } 1564 1565 sizingIsEmpty := strings.TrimSpace(sizingPolicyId) == "" 1566 placementIsEmpty := strings.TrimSpace(placementPolicyId) == "" 1567 vGpuPolicyIsEmpty := strings.TrimSpace(vGpuPolicyId) == "" 1568 1569 if !vGpuPolicyIsEmpty { 1570 return Task{}, fmt.Errorf("vGPU policies are not supported, hence %s should be empty", vGpuPolicyId) 1571 } 1572 1573 if sizingIsEmpty && placementIsEmpty { 1574 return Task{}, fmt.Errorf("either sizing policy ID or placement policy ID is needed") 1575 } 1576 1577 // `reconfigureVm` updates VM name, Description, and any or all of the following sections. 1578 // VirtualHardwareSection 1579 // OperatingSystemSection 1580 // NetworkConnectionSection 1581 // GuestCustomizationSection 1582 // Sections not included in the request body will not be updated. 1583 1584 computePolicy := &types.ComputePolicy{} 1585 1586 if !sizingIsEmpty { 1587 vdcSizingPolicyHref, err := vm.client.OpenApiBuildEndpoint(types.OpenApiPathVersion2_0_0, types.OpenApiEndpointVdcComputePolicies, sizingPolicyId) 1588 if err != nil { 1589 return Task{}, fmt.Errorf("error constructing HREF for sizing policy") 1590 } 1591 computePolicy.VmSizingPolicy = &types.Reference{HREF: vdcSizingPolicyHref.String()} 1592 } 1593 1594 if !placementIsEmpty { 1595 vdcPlacementPolicyHref, err := vm.client.OpenApiBuildEndpoint(types.OpenApiPathVersion2_0_0, types.OpenApiEndpointVdcComputePolicies, placementPolicyId) 1596 if err != nil { 1597 return Task{}, fmt.Errorf("error constructing HREF for placement policy") 1598 } 1599 computePolicy.VmPlacementPolicy = &types.Reference{HREF: vdcPlacementPolicyHref.String()} 1600 } 1601 1602 return vm.client.ExecuteTaskRequest(vm.VM.HREF+"/action/reconfigureVm", http.MethodPost, 1603 types.MimeVM, "error updating VM spec section: %s", &types.Vm{ 1604 Xmlns: types.XMLNamespaceVCloud, 1605 Ovf: types.XMLNamespaceOVF, 1606 Name: vm.VM.Name, 1607 Description: vm.VM.Description, 1608 ComputePolicy: computePolicy, 1609 }) 1610 } 1611 1612 // UpdateComputePolicyAsync updates VM Compute policy and returns Task and error. 1613 // Deprecated: Use VM.UpdateComputePolicyV2Async instead 1614 func (vm *VM) UpdateComputePolicyAsync(computePolicy *types.VdcComputePolicy) (Task, error) { 1615 if vm.VM.HREF == "" { 1616 return Task{}, fmt.Errorf("cannot update VM compute policy, VM HREF is unset") 1617 } 1618 1619 // `reconfigureVm` updates VM name, Description, and any or all of the following sections. 1620 // VirtualHardwareSection 1621 // OperatingSystemSection 1622 // NetworkConnectionSection 1623 // GuestCustomizationSection 1624 // Sections not included in the request body will not be updated. 1625 1626 vcdComputePolicyHref, err := vm.client.OpenApiBuildEndpoint(types.OpenApiPathVersion1_0_0, types.OpenApiEndpointVdcComputePolicies, computePolicy.ID) 1627 if err != nil { 1628 return Task{}, fmt.Errorf("error constructing HREF for compute policy") 1629 } 1630 1631 return vm.client.ExecuteTaskRequest(vm.VM.HREF+"/action/reconfigureVm", http.MethodPost, 1632 types.MimeVM, "error updating VM spec section: %s", &types.Vm{ 1633 Xmlns: types.XMLNamespaceVCloud, 1634 Ovf: types.XMLNamespaceOVF, 1635 Name: vm.VM.Name, 1636 Description: vm.VM.Description, 1637 ComputePolicy: &types.ComputePolicy{VmSizingPolicy: &types.Reference{HREF: vcdComputePolicyHref.String()}}, 1638 }) 1639 } 1640 1641 // QueryVmList returns a list of all VMs in all the organizations available to the caller 1642 func (client *Client) QueryVmList(filter types.VmQueryFilter) ([]*types.QueryResultVMRecordType, error) { 1643 var vmList []*types.QueryResultVMRecordType 1644 queryType := client.GetQueryType(types.QtVm) 1645 params := map[string]string{ 1646 "type": queryType, 1647 "filterEncoded": "true", 1648 } 1649 if filter.String() != "" { 1650 params["filter"] = filter.String() 1651 } 1652 vmResult, err := client.cumulativeQuery(queryType, nil, params) 1653 if err != nil { 1654 return nil, fmt.Errorf("error getting VM list : %s", err) 1655 } 1656 vmList = vmResult.Results.VMRecord 1657 if client.IsSysAdmin { 1658 vmList = vmResult.Results.AdminVMRecord 1659 } 1660 return vmList, nil 1661 } 1662 1663 // QueryVmList returns a list of all VMs in a given Org 1664 func (org *Org) QueryVmList(filter types.VmQueryFilter) ([]*types.QueryResultVMRecordType, error) { 1665 if org.client.IsSysAdmin { 1666 return queryVmList(filter, org.client, "org", org.Org.HREF) 1667 } 1668 return queryVmList(filter, org.client, "", "") 1669 } 1670 1671 // QueryVmList returns a list of all VMs in a given VDC 1672 func (vdc *Vdc) QueryVmList(filter types.VmQueryFilter) ([]*types.QueryResultVMRecordType, error) { 1673 return queryVmList(filter, vdc.client, "vdc", vdc.Vdc.HREF) 1674 } 1675 1676 // queryVmList is extracted and used by org.QueryVmList and vdc.QueryVmList to adjust filtering scope 1677 func queryVmList(filter types.VmQueryFilter, client *Client, filterParent, filterParentHref string) ([]*types.QueryResultVMRecordType, error) { 1678 var vmList []*types.QueryResultVMRecordType 1679 queryType := client.GetQueryType(types.QtVm) 1680 params := map[string]string{ 1681 "type": queryType, 1682 "filterEncoded": "true", 1683 } 1684 filterText := "" 1685 if filter.String() != "" { 1686 filterText = filter.String() 1687 } 1688 if filterParent != "" { 1689 if filterText == "" { 1690 filterText = fmt.Sprintf("%s==%s", filterParent, filterParentHref) 1691 } else { 1692 filterText = fmt.Sprintf("%s;%s==%s", filterText, filterParent, filterParentHref) 1693 } 1694 params["filter"] = filterText 1695 } 1696 vmResult, err := client.cumulativeQuery(queryType, nil, params) 1697 if err != nil { 1698 return nil, fmt.Errorf("error getting VM list : %s", err) 1699 } 1700 vmList = vmResult.Results.VMRecord 1701 if client.IsSysAdmin { 1702 vmList = vmResult.Results.AdminVMRecord 1703 } 1704 return vmList, nil 1705 } 1706 1707 // QueryVmList retrieves a list of VMs across all VDC, using parameters defined in searchParams 1708 func QueryVmList(vmType types.VmQueryFilter, client *Client, searchParams map[string]string) ([]*types.QueryResultVMRecordType, error) { 1709 var vmList []*types.QueryResultVMRecordType 1710 queryType := client.GetQueryType(types.QtVm) 1711 params := map[string]string{ 1712 "type": queryType, 1713 "filterEncoded": "true", 1714 } 1715 filterText := "" 1716 if vmType.String() != "" { 1717 // The first filter will be the type of VM, i.e. deployed (inside a vApp) or not (inside a vApp template) 1718 filterText = vmType.String() 1719 } 1720 for k, v := range searchParams { 1721 filterText = fmt.Sprintf("%s;%s==%s", filterText, k, v) 1722 } 1723 1724 params["filter"] = filterText 1725 vmResult, err := client.cumulativeQuery(queryType, nil, params) 1726 if err != nil { 1727 return nil, fmt.Errorf("error getting VM list : %s", err) 1728 } 1729 vmList = vmResult.Results.VMRecord 1730 if client.IsSysAdmin { 1731 vmList = vmResult.Results.AdminVMRecord 1732 } 1733 return vmList, nil 1734 } 1735 1736 // UpdateVmCpuAndMemoryHotAdd updates VM Capabilities and returns refreshed VM or error. 1737 func (vm *VM) UpdateVmCpuAndMemoryHotAdd(cpuAdd, memoryAdd bool) (*VM, error) { 1738 task, err := vm.UpdateVmCpuAndMemoryHotAddAsync(cpuAdd, memoryAdd) 1739 if err != nil { 1740 return nil, err 1741 } 1742 1743 err = task.WaitTaskCompletion() 1744 if err != nil { 1745 return nil, err 1746 } 1747 1748 err = vm.Refresh() 1749 if err != nil { 1750 return nil, err 1751 } 1752 1753 return vm, nil 1754 1755 } 1756 1757 // UpdateVmCpuAndMemoryHotAddAsync updates VM Capabilities and returns Task and error. 1758 func (vm *VM) UpdateVmCpuAndMemoryHotAddAsync(cpuHot, memoryAdd bool) (Task, error) { 1759 if vm.VM.HREF == "" { 1760 return Task{}, fmt.Errorf("cannot update VM capabilities, VM HREF is unset") 1761 } 1762 1763 return vm.client.ExecuteTaskRequest(vm.VM.HREF+"/vmCapabilities", http.MethodPut, 1764 types.MimeVmCapabilities, "error updating VM capabilities section: %s", &types.VmCapabilities{ 1765 Xmlns: types.XMLNamespaceVCloud, 1766 CPUHotAddEnabled: cpuHot, 1767 MemoryHotAddEnabled: memoryAdd, 1768 }) 1769 } 1770 1771 // vmVersionedFuncs is a generic representation of VM CRUD operations across multiple versions 1772 type vmVersionedFuncs struct { 1773 SupportedVersion string 1774 GetVMByHref func(client *Client, vmHref string) (*VM, error) 1775 AddEmptyVm func(vapp *VApp, reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) (*VM, error) 1776 AddEmptyVmAsync func(vapp *VApp, reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) (Task, error) 1777 } 1778 1779 // VM function mapping for API version 33.0 (from vCD 10.0) 1780 var vmVersionedFuncsV10 = vmVersionedFuncs{ 1781 SupportedVersion: "33.0", 1782 GetVMByHref: getVMByHrefV10, 1783 AddEmptyVm: addEmptyVmV10, 1784 AddEmptyVmAsync: addEmptyVmAsyncV10, 1785 } 1786 1787 // vmVersionedFuncsByVcdVersion is a map of VDC functions by vCD version 1788 var vmVersionedFuncsByVcdVersion = map[string]vmVersionedFuncs{ 1789 "vm10.2": vmVersionedFuncsV10, 1790 "vm10.1": vmVersionedFuncsV10, 1791 "vm10.0": vmVersionedFuncsV10, 1792 // If we add a new function to this list, we also need to update the "default" entry 1793 // The "default" entry will hold the highest currently available function 1794 "default": vmVersionedFuncsV10, 1795 } 1796 1797 // getVmVersionedFuncsByVdcVersion is a wrapper function that retrieves the requested versioned VDC function 1798 // When the wanted version does not exist in the map, it returns the highest available one. 1799 func getVmVersionedFuncsByVdcVersion(version string) vmVersionedFuncs { 1800 f, ok := vmVersionedFuncsByVcdVersion[version] 1801 if ok { 1802 return f 1803 } else { 1804 return vmVersionedFuncsByVcdVersion["default"] 1805 } 1806 } 1807 1808 // addEmptyVmAsyncV10 adds an empty VM (without template) to the vApp and returns a Task and an error. 1809 func addEmptyVmAsyncV10(vapp *VApp, reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) (Task, error) { 1810 err := validateEmptyVmParams(reComposeVAppParams) 1811 if err != nil { 1812 return Task{}, err 1813 } 1814 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 1815 apiEndpoint.Path += "/action/recomposeVApp" 1816 1817 reComposeVAppParams.XmlnsVcloud = types.XMLNamespaceVCloud 1818 reComposeVAppParams.XmlnsOvf = types.XMLNamespaceOVF 1819 1820 // Return the task 1821 return vapp.client.ExecuteTaskRequestWithApiVersion(apiEndpoint.String(), http.MethodPost, 1822 types.MimeRecomposeVappParams, "error instantiating a new VM: %s", reComposeVAppParams, 1823 vapp.client.GetSpecificApiVersionOnCondition(">=37.1", "37.1")) 1824 } 1825 1826 // addEmptyVmV10 adds an empty VM (without template) to vApp and returns the new created VM or an error. 1827 func addEmptyVmV10(vapp *VApp, reComposeVAppParams *types.RecomposeVAppParamsForEmptyVm) (*VM, error) { 1828 task, err := addEmptyVmAsyncV10(vapp, reComposeVAppParams) 1829 if err != nil { 1830 return nil, err 1831 } 1832 1833 err = task.WaitTaskCompletion() 1834 if err != nil { 1835 return nil, err 1836 } 1837 1838 err = vapp.Refresh() 1839 if err != nil { 1840 return nil, fmt.Errorf("error refreshing vApp: %s", err) 1841 } 1842 1843 //vApp Might Not Have Any VMs 1844 if vapp.VApp.Children == nil { 1845 return nil, ErrorEntityNotFound 1846 } 1847 1848 util.Logger.Printf("[TRACE] Looking for VM: %s", reComposeVAppParams.CreateItem.Name) 1849 for _, child := range vapp.VApp.Children.VM { 1850 1851 util.Logger.Printf("[TRACE] Looking at: %s", child.Name) 1852 if child.Name == reComposeVAppParams.CreateItem.Name { 1853 return getVMByHrefV10(vapp.client, child.HREF) 1854 } 1855 1856 } 1857 util.Logger.Printf("[TRACE] Couldn't find VM: %s", reComposeVAppParams.CreateItem.Name) 1858 return nil, ErrorEntityNotFound 1859 } 1860 1861 // getVMByHrefV10 returns a VM reference by running a vCD API call 1862 // If no valid VM is found, it returns a nil VM reference and an error 1863 // Note that the pointer receiver here is a Client instead of a VApp, because 1864 // there are cases where we know the VM HREF but not which VApp it belongs to. 1865 // V10 of function overrides API version to allow to access compute policy in VM. 1866 func getVMByHrefV10(client *Client, vmHref string) (*VM, error) { 1867 1868 newVm := NewVM(client) 1869 1870 _, err := client.ExecuteRequestWithApiVersion(vmHref, http.MethodGet, 1871 "", "error retrieving vm: %s", nil, newVm.VM, client.GetSpecificApiVersionOnCondition(">=37.1", "37.1")) 1872 1873 if err != nil { 1874 1875 return nil, err 1876 } 1877 1878 return newVm, nil 1879 } 1880 1881 // GetVMByHref returns a VM reference by running a vCD API call 1882 // If no valid VM is found, it returns a nil VM reference and an error 1883 // Note that the pointer receiver here is a Client instead of a VApp, because 1884 // there are cases where we know the VM HREF but not which VApp it belongs to. 1885 func (client *Client) GetVMByHref(vmHref string) (*VM, error) { 1886 1887 apiVersion, err := client.MaxSupportedVersion() 1888 if err != nil { 1889 return nil, err 1890 } 1891 vmFunctions := getVmVersionedFuncsByVdcVersion("vm" + apiVersionToVcdVersion[apiVersion]) 1892 if vmFunctions.GetVMByHref == nil { 1893 return nil, fmt.Errorf("function GetVMByHref is not defined for %s", "vdc"+apiVersion) 1894 } 1895 1896 util.Logger.Printf("[DEBUG] GetVMByHref call function for version %s", vmFunctions.SupportedVersion) 1897 1898 return vmFunctions.GetVMByHref(client, vmHref) 1899 } 1900 1901 // UpdateStorageProfile updates VM storage profile and returns refreshed VM or error. 1902 func (vm *VM) UpdateStorageProfile(storageProfileHref string) (*VM, error) { 1903 task, err := vm.UpdateStorageProfileAsync(storageProfileHref) 1904 if err != nil { 1905 return nil, err 1906 } 1907 1908 err = task.WaitTaskCompletion() 1909 if err != nil { 1910 return nil, err 1911 } 1912 1913 err = vm.Refresh() 1914 if err != nil { 1915 return nil, err 1916 } 1917 1918 return vm, nil 1919 } 1920 1921 // UpdateStorageProfileAsync updates VM storage profile and returns Task and error. 1922 func (vm *VM) UpdateStorageProfileAsync(storageProfileHref string) (Task, error) { 1923 if vm.VM.HREF == "" { 1924 return Task{}, fmt.Errorf("cannot update VM storage profile, VM HREF is unset") 1925 } 1926 if storageProfileHref == "" { 1927 return Task{}, fmt.Errorf("cannot update VM storage profile, storage profile HREF is unset") 1928 } 1929 1930 // `reconfigureVm` updates VM name, Description, and any or all of the following sections. 1931 // VirtualHardwareSection 1932 // OperatingSystemSection 1933 // NetworkConnectionSection 1934 // GuestCustomizationSection 1935 // Sections not included in the request body will not be updated. 1936 return vm.client.ExecuteTaskRequest(vm.VM.HREF+"/action/reconfigureVm", http.MethodPost, 1937 types.MimeVM, "error updating VM storage profile: %s", &types.Vm{ 1938 Xmlns: types.XMLNamespaceVCloud, 1939 Ovf: types.XMLNamespaceOVF, 1940 Name: vm.VM.Name, 1941 Description: vm.VM.Description, 1942 StorageProfile: &types.Reference{HREF: storageProfileHref}, 1943 }) 1944 } 1945 1946 // UpdateBootOptions updates the Boot Options of a VM and returns the updated instance of the VM 1947 func (vm *VM) UpdateBootOptions(bootOptions *types.BootOptions) (*VM, error) { 1948 task, err := vm.UpdateBootOptionsAsync(bootOptions) 1949 if err != nil { 1950 return nil, err 1951 } 1952 1953 err = task.WaitTaskCompletion() 1954 if err != nil { 1955 return nil, err 1956 } 1957 1958 err = vm.Refresh() 1959 if err != nil { 1960 return nil, err 1961 } 1962 1963 return vm, nil 1964 } 1965 1966 // UpdateBootOptionsAsync updates the boot options of a VM 1967 func (vm *VM) UpdateBootOptionsAsync(bootOptions *types.BootOptions) (Task, error) { 1968 if vm.VM.HREF == "" { 1969 return Task{}, fmt.Errorf("cannot update VM boot options, VM HREF is unset") 1970 } 1971 1972 if vm.client.APIVCDMaxVersionIs("<37.1") { 1973 1974 if bootOptions.BootRetryEnabled != nil || bootOptions.BootRetryDelay != nil || 1975 bootOptions.EfiSecureBootEnabled != nil || bootOptions.NetworkBootProtocol != "" { 1976 return Task{}, fmt.Errorf("error: Boot retry, EFI Secure Boot and Boot Network Protocol options were introduced in VCD 10.4.1") 1977 } 1978 } 1979 1980 if bootOptions == nil { 1981 return Task{}, fmt.Errorf("cannot update VM boot options, none given") 1982 } 1983 1984 return vm.client.ExecuteTaskRequestWithApiVersion(vm.VM.HREF+"/action/reconfigureVm", http.MethodPost, 1985 types.MimeVM, "error updating VM boot options: %s", &types.Vm{ 1986 Xmlns: types.XMLNamespaceVCloud, 1987 Ovf: types.XMLNamespaceOVF, 1988 Name: vm.VM.Name, 1989 Description: vm.VM.Description, 1990 // We need to add ComputePolicy in the Request Body or settings will 1991 // be set to default sizing policy set in the VDC if the VM is Not 1992 // compliant with the current sizing policy 1993 ComputePolicy: vm.VM.ComputePolicy, 1994 BootOptions: bootOptions, 1995 }, vm.client.GetSpecificApiVersionOnCondition(">=37.1", "37.1")) 1996 } 1997 1998 // DeleteAsync starts a standalone VM deletion, returning a task 1999 func (vm *VM) DeleteAsync() (Task, error) { 2000 if vm.VM.HREF == "" { 2001 return Task{}, fmt.Errorf("no HREF found for this VM") 2002 } 2003 2004 task, err := vm.Undeploy() 2005 if err == nil { 2006 err = task.WaitTaskCompletion() 2007 if err != nil { 2008 return Task{}, err 2009 } 2010 } 2011 return vm.client.ExecuteTaskRequest(vm.VM.HREF, http.MethodDelete, 2012 "", "error deleting VM: %s", nil) 2013 } 2014 2015 // Delete deletes a standalone VM 2016 func (vm *VM) Delete() error { 2017 task, err := vm.DeleteAsync() 2018 if err != nil { 2019 return err 2020 } 2021 return task.WaitTaskCompletion() 2022 } 2023 2024 func (vm *VM) getTenantContext() (*TenantContext, error) { 2025 parentVdc, err := vm.GetParentVdc() 2026 if err != nil { 2027 return nil, err 2028 } 2029 return parentVdc.getTenantContext() 2030 } 2031 2032 // ChangeMemory sets memory value. Size is MB 2033 func (vm *VM) ChangeMemory(sizeInMb int64) error { 2034 vmSpecSection := vm.VM.VmSpecSection 2035 description := vm.VM.Description 2036 // update treats same values as changes and fails, with no values provided - no changes are made for that section 2037 vmSpecSection.DiskSection = nil 2038 2039 vmSpecSection.MemoryResourceMb.Configured = sizeInMb 2040 2041 _, err := vm.UpdateVmSpecSection(vmSpecSection, description) 2042 if err != nil { 2043 return fmt.Errorf("error changing memory size: %s", err) 2044 } 2045 return nil 2046 } 2047 2048 // ChangeCPUCount sets number of available virtual logical processors 2049 // (i.e. CPUs x cores per socket) 2050 // Cpu cores count is inherited from template. 2051 // https://communities.vmware.com/thread/576209 2052 // Deprecated: use ChangeCPUAndCoreCount 2053 func (vm *VM) ChangeCPU(cpus, cpuCores int) error { 2054 vmSpecSection := vm.VM.VmSpecSection 2055 description := vm.VM.Description 2056 // update treats same values as changes and fails, with no values provided - no changes are made for that section 2057 vmSpecSection.DiskSection = nil 2058 2059 vmSpecSection.NumCpus = &cpus 2060 // has to come together 2061 vmSpecSection.NumCoresPerSocket = &cpuCores 2062 2063 _, err := vm.UpdateVmSpecSection(vmSpecSection, description) 2064 if err != nil { 2065 return fmt.Errorf("error changing cpu size: %s", err) 2066 } 2067 return nil 2068 } 2069 2070 // ChangeCPUAndCoreCount sets CPU and CPU core counts 2071 // Accepts values or `nil` for both parameters. 2072 func (vm *VM) ChangeCPUAndCoreCount(cpus, cpuCores *int) error { 2073 vmSpecSection := vm.VM.VmSpecSection 2074 description := vm.VM.Description 2075 // update treats same values as changes and fails, with no values provided - no changes are made for that section 2076 vmSpecSection.DiskSection = nil 2077 2078 vmSpecSection.NumCpus = cpus 2079 // has to come together 2080 vmSpecSection.NumCoresPerSocket = cpuCores 2081 2082 _, err := vm.UpdateVmSpecSection(vmSpecSection, description) 2083 if err != nil { 2084 return fmt.Errorf("error changing CPU size: %s", err) 2085 } 2086 return nil 2087 } 2088 2089 // ConsolidateDisksAsync triggers VM disk consolidation task 2090 func (vm *VM) ConsolidateDisksAsync() (Task, error) { 2091 if vm.VM.HREF == "" { 2092 return Task{}, fmt.Errorf("cannot consolidate disks, VM HREF is unset") 2093 } 2094 2095 return vm.client.ExecuteTaskRequest(vm.VM.HREF+"/action/consolidate", http.MethodPost, 2096 types.AnyXMLMime, "error consolidating VM disks: %s", nil) 2097 } 2098 2099 // ConsolidateDisks triggers VM disk consolidation task and waits until it is completed 2100 func (vm *VM) ConsolidateDisks() error { 2101 task, err := vm.ConsolidateDisksAsync() 2102 if err != nil { 2103 return err 2104 } 2105 return task.WaitTaskCompletion() 2106 }