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  }