github.com/vmware/govmomi@v0.37.2/object/virtual_machine.go (about)

     1  /*
     2  Copyright (c) 2015-2023 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package object
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"path"
    25  	"strings"
    26  
    27  	"github.com/vmware/govmomi/nfc"
    28  	"github.com/vmware/govmomi/property"
    29  	"github.com/vmware/govmomi/vim25"
    30  	"github.com/vmware/govmomi/vim25/methods"
    31  	"github.com/vmware/govmomi/vim25/mo"
    32  	"github.com/vmware/govmomi/vim25/types"
    33  )
    34  
    35  const (
    36  	PropRuntimePowerState = "summary.runtime.powerState"
    37  	PropConfigTemplate    = "summary.config.template"
    38  )
    39  
    40  type VirtualMachine struct {
    41  	Common
    42  }
    43  
    44  // extractDiskLayoutFiles is a helper function used to extract file keys for
    45  // all disk files attached to the virtual machine at the current point of
    46  // running.
    47  func extractDiskLayoutFiles(diskLayoutList []types.VirtualMachineFileLayoutExDiskLayout) []int {
    48  	var result []int
    49  
    50  	for _, layoutExDisk := range diskLayoutList {
    51  		for _, link := range layoutExDisk.Chain {
    52  			for i := range link.FileKey { // diskDescriptor, diskExtent pairs
    53  				result = append(result, int(link.FileKey[i]))
    54  			}
    55  		}
    56  	}
    57  
    58  	return result
    59  }
    60  
    61  // removeKey is a helper function for removing a specific file key from a list
    62  // of keys associated with disks attached to a virtual machine.
    63  func removeKey(l *[]int, key int) {
    64  	for i, k := range *l {
    65  		if k == key {
    66  			*l = append((*l)[:i], (*l)[i+1:]...)
    67  			break
    68  		}
    69  	}
    70  }
    71  
    72  func NewVirtualMachine(c *vim25.Client, ref types.ManagedObjectReference) *VirtualMachine {
    73  	return &VirtualMachine{
    74  		Common: NewCommon(c, ref),
    75  	}
    76  }
    77  
    78  func (v VirtualMachine) PowerState(ctx context.Context) (types.VirtualMachinePowerState, error) {
    79  	var o mo.VirtualMachine
    80  
    81  	err := v.Properties(ctx, v.Reference(), []string{PropRuntimePowerState}, &o)
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  
    86  	return o.Summary.Runtime.PowerState, nil
    87  }
    88  
    89  func (v VirtualMachine) IsTemplate(ctx context.Context) (bool, error) {
    90  	var o mo.VirtualMachine
    91  
    92  	err := v.Properties(ctx, v.Reference(), []string{PropConfigTemplate}, &o)
    93  	if err != nil {
    94  		return false, err
    95  	}
    96  
    97  	return o.Summary.Config.Template, nil
    98  }
    99  
   100  func (v VirtualMachine) PowerOn(ctx context.Context) (*Task, error) {
   101  	req := types.PowerOnVM_Task{
   102  		This: v.Reference(),
   103  	}
   104  
   105  	res, err := methods.PowerOnVM_Task(ctx, v.c, &req)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	return NewTask(v.c, res.Returnval), nil
   111  }
   112  
   113  func (v VirtualMachine) PowerOff(ctx context.Context) (*Task, error) {
   114  	req := types.PowerOffVM_Task{
   115  		This: v.Reference(),
   116  	}
   117  
   118  	res, err := methods.PowerOffVM_Task(ctx, v.c, &req)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	return NewTask(v.c, res.Returnval), nil
   124  }
   125  
   126  func (v VirtualMachine) PutUsbScanCodes(ctx context.Context, spec types.UsbScanCodeSpec) (int32, error) {
   127  	req := types.PutUsbScanCodes{
   128  		This: v.Reference(),
   129  		Spec: spec,
   130  	}
   131  
   132  	res, err := methods.PutUsbScanCodes(ctx, v.c, &req)
   133  	if err != nil {
   134  		return 0, err
   135  	}
   136  
   137  	return res.Returnval, nil
   138  }
   139  
   140  func (v VirtualMachine) Reset(ctx context.Context) (*Task, error) {
   141  	req := types.ResetVM_Task{
   142  		This: v.Reference(),
   143  	}
   144  
   145  	res, err := methods.ResetVM_Task(ctx, v.c, &req)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	return NewTask(v.c, res.Returnval), nil
   151  }
   152  
   153  func (v VirtualMachine) Suspend(ctx context.Context) (*Task, error) {
   154  	req := types.SuspendVM_Task{
   155  		This: v.Reference(),
   156  	}
   157  
   158  	res, err := methods.SuspendVM_Task(ctx, v.c, &req)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	return NewTask(v.c, res.Returnval), nil
   164  }
   165  
   166  func (v VirtualMachine) ShutdownGuest(ctx context.Context) error {
   167  	req := types.ShutdownGuest{
   168  		This: v.Reference(),
   169  	}
   170  
   171  	_, err := methods.ShutdownGuest(ctx, v.c, &req)
   172  	return err
   173  }
   174  
   175  func (v VirtualMachine) StandbyGuest(ctx context.Context) error {
   176  	req := types.StandbyGuest{
   177  		This: v.Reference(),
   178  	}
   179  
   180  	_, err := methods.StandbyGuest(ctx, v.c, &req)
   181  	return err
   182  }
   183  
   184  func (v VirtualMachine) RebootGuest(ctx context.Context) error {
   185  	req := types.RebootGuest{
   186  		This: v.Reference(),
   187  	}
   188  
   189  	_, err := methods.RebootGuest(ctx, v.c, &req)
   190  	return err
   191  }
   192  
   193  func (v VirtualMachine) Destroy(ctx context.Context) (*Task, error) {
   194  	req := types.Destroy_Task{
   195  		This: v.Reference(),
   196  	}
   197  
   198  	res, err := methods.Destroy_Task(ctx, v.c, &req)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	return NewTask(v.c, res.Returnval), nil
   204  }
   205  
   206  func (v VirtualMachine) Clone(ctx context.Context, folder *Folder, name string, config types.VirtualMachineCloneSpec) (*Task, error) {
   207  	req := types.CloneVM_Task{
   208  		This:   v.Reference(),
   209  		Folder: folder.Reference(),
   210  		Name:   name,
   211  		Spec:   config,
   212  	}
   213  
   214  	res, err := methods.CloneVM_Task(ctx, v.c, &req)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	return NewTask(v.c, res.Returnval), nil
   220  }
   221  
   222  func (v VirtualMachine) InstantClone(ctx context.Context, config types.VirtualMachineInstantCloneSpec) (*Task, error) {
   223  	req := types.InstantClone_Task{
   224  		This: v.Reference(),
   225  		Spec: config,
   226  	}
   227  
   228  	res, err := methods.InstantClone_Task(ctx, v.c, &req)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	return NewTask(v.c, res.Returnval), nil
   234  }
   235  
   236  func (v VirtualMachine) Customize(ctx context.Context, spec types.CustomizationSpec) (*Task, error) {
   237  	req := types.CustomizeVM_Task{
   238  		This: v.Reference(),
   239  		Spec: spec,
   240  	}
   241  
   242  	res, err := methods.CustomizeVM_Task(ctx, v.c, &req)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	return NewTask(v.c, res.Returnval), nil
   248  }
   249  
   250  func (v VirtualMachine) Relocate(ctx context.Context, config types.VirtualMachineRelocateSpec, priority types.VirtualMachineMovePriority) (*Task, error) {
   251  	req := types.RelocateVM_Task{
   252  		This:     v.Reference(),
   253  		Spec:     config,
   254  		Priority: priority,
   255  	}
   256  
   257  	res, err := methods.RelocateVM_Task(ctx, v.c, &req)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	return NewTask(v.c, res.Returnval), nil
   263  }
   264  
   265  func (v VirtualMachine) Reconfigure(ctx context.Context, config types.VirtualMachineConfigSpec) (*Task, error) {
   266  	req := types.ReconfigVM_Task{
   267  		This: v.Reference(),
   268  		Spec: config,
   269  	}
   270  
   271  	res, err := methods.ReconfigVM_Task(ctx, v.c, &req)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	return NewTask(v.c, res.Returnval), nil
   277  }
   278  
   279  func (v VirtualMachine) RefreshStorageInfo(ctx context.Context) error {
   280  	req := types.RefreshStorageInfo{
   281  		This: v.Reference(),
   282  	}
   283  
   284  	_, err := methods.RefreshStorageInfo(ctx, v.c, &req)
   285  	return err
   286  }
   287  
   288  // WaitForIP waits for the VM guest.ipAddress property to report an IP address.
   289  // Waits for an IPv4 address if the v4 param is true.
   290  func (v VirtualMachine) WaitForIP(ctx context.Context, v4 ...bool) (string, error) {
   291  	var ip string
   292  
   293  	p := property.DefaultCollector(v.c)
   294  	err := property.Wait(ctx, p, v.Reference(), []string{"guest.ipAddress"}, func(pc []types.PropertyChange) bool {
   295  		for _, c := range pc {
   296  			if c.Name != "guest.ipAddress" {
   297  				continue
   298  			}
   299  			if c.Op != types.PropertyChangeOpAssign {
   300  				continue
   301  			}
   302  			if c.Val == nil {
   303  				continue
   304  			}
   305  
   306  			ip = c.Val.(string)
   307  			if len(v4) == 1 && v4[0] {
   308  				if net.ParseIP(ip).To4() == nil {
   309  					return false
   310  				}
   311  			}
   312  			return true
   313  		}
   314  
   315  		return false
   316  	})
   317  
   318  	if err != nil {
   319  		return "", err
   320  	}
   321  
   322  	return ip, nil
   323  }
   324  
   325  // WaitForNetIP waits for the VM guest.net property to report an IP address for all VM NICs.
   326  // Only consider IPv4 addresses if the v4 param is true.
   327  // By default, wait for all NICs to get an IP address, unless 1 or more device is given.
   328  // A device can be specified by the MAC address or the device name, e.g. "ethernet-0".
   329  // Returns a map with MAC address as the key and IP address list as the value.
   330  func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool, device ...string) (map[string][]string, error) {
   331  	macs := make(map[string][]string)
   332  	eths := make(map[string]string)
   333  
   334  	p := property.DefaultCollector(v.c)
   335  
   336  	// Wait for all NICs to have a MacAddress, which may not be generated yet.
   337  	err := property.Wait(ctx, p, v.Reference(), []string{"config.hardware.device"}, func(pc []types.PropertyChange) bool {
   338  		for _, c := range pc {
   339  			if c.Op != types.PropertyChangeOpAssign {
   340  				continue
   341  			}
   342  
   343  			devices := VirtualDeviceList(c.Val.(types.ArrayOfVirtualDevice).VirtualDevice)
   344  			for _, d := range devices {
   345  				if nic, ok := d.(types.BaseVirtualEthernetCard); ok {
   346  					// Convert to lower so that e.g. 00:50:56:83:3A:5D is treated the
   347  					// same as 00:50:56:83:3a:5d
   348  					mac := strings.ToLower(nic.GetVirtualEthernetCard().MacAddress)
   349  					if mac == "" {
   350  						return false
   351  					}
   352  					macs[mac] = nil
   353  					eths[devices.Name(d)] = mac
   354  				}
   355  			}
   356  		}
   357  
   358  		return true
   359  	})
   360  
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	if len(device) != 0 {
   366  		// Only wait for specific NIC(s)
   367  		macs = make(map[string][]string)
   368  		for _, mac := range device {
   369  			if eth, ok := eths[mac]; ok {
   370  				mac = eth // device name, e.g. "ethernet-0"
   371  			}
   372  			macs[mac] = nil
   373  		}
   374  	}
   375  
   376  	err = property.Wait(ctx, p, v.Reference(), []string{"guest.net"}, func(pc []types.PropertyChange) bool {
   377  		for _, c := range pc {
   378  			if c.Op != types.PropertyChangeOpAssign {
   379  				continue
   380  			}
   381  
   382  			nics := c.Val.(types.ArrayOfGuestNicInfo).GuestNicInfo
   383  			for _, nic := range nics {
   384  				// Convert to lower so that e.g. 00:50:56:83:3A:5D is treated the
   385  				// same as 00:50:56:83:3a:5d
   386  				mac := strings.ToLower(nic.MacAddress)
   387  				if mac == "" || nic.IpConfig == nil {
   388  					continue
   389  				}
   390  
   391  				for _, ip := range nic.IpConfig.IpAddress {
   392  					if _, ok := macs[mac]; !ok {
   393  						continue // Ignore any that don't correspond to a VM device
   394  					}
   395  					if v4 && net.ParseIP(ip.IpAddress).To4() == nil {
   396  						continue // Ignore non IPv4 address
   397  					}
   398  					macs[mac] = append(macs[mac], ip.IpAddress)
   399  				}
   400  			}
   401  		}
   402  
   403  		for _, ips := range macs {
   404  			if len(ips) == 0 {
   405  				return false
   406  			}
   407  		}
   408  
   409  		return true
   410  	})
   411  
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	return macs, nil
   417  }
   418  
   419  // Device returns the VirtualMachine's config.hardware.device property.
   420  func (v VirtualMachine) Device(ctx context.Context) (VirtualDeviceList, error) {
   421  	var o mo.VirtualMachine
   422  
   423  	err := v.Properties(ctx, v.Reference(), []string{"config.hardware.device", "summary.runtime.connectionState"}, &o)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	// Quoting the SDK doc:
   429  	//   The virtual machine configuration is not guaranteed to be available.
   430  	//   For example, the configuration information would be unavailable if the server
   431  	//   is unable to access the virtual machine files on disk, and is often also unavailable
   432  	//   during the initial phases of virtual machine creation.
   433  	if o.Config == nil {
   434  		return nil, fmt.Errorf("%s Config is not available, connectionState=%s",
   435  			v.Reference(), o.Summary.Runtime.ConnectionState)
   436  	}
   437  
   438  	return VirtualDeviceList(o.Config.Hardware.Device), nil
   439  }
   440  
   441  func (v VirtualMachine) EnvironmentBrowser(ctx context.Context) (*EnvironmentBrowser, error) {
   442  	var vm mo.VirtualMachine
   443  
   444  	err := v.Properties(ctx, v.Reference(), []string{"environmentBrowser"}, &vm)
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  
   449  	return NewEnvironmentBrowser(v.c, vm.EnvironmentBrowser), nil
   450  }
   451  
   452  func (v VirtualMachine) HostSystem(ctx context.Context) (*HostSystem, error) {
   453  	var o mo.VirtualMachine
   454  
   455  	err := v.Properties(ctx, v.Reference(), []string{"summary.runtime.host"}, &o)
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  
   460  	host := o.Summary.Runtime.Host
   461  	if host == nil {
   462  		return nil, errors.New("VM doesn't have a HostSystem")
   463  	}
   464  
   465  	return NewHostSystem(v.c, *host), nil
   466  }
   467  
   468  func (v VirtualMachine) ResourcePool(ctx context.Context) (*ResourcePool, error) {
   469  	var o mo.VirtualMachine
   470  
   471  	err := v.Properties(ctx, v.Reference(), []string{"resourcePool"}, &o)
   472  	if err != nil {
   473  		return nil, err
   474  	}
   475  
   476  	rp := o.ResourcePool
   477  	if rp == nil {
   478  		return nil, errors.New("VM doesn't have a resourcePool")
   479  	}
   480  
   481  	return NewResourcePool(v.c, *rp), nil
   482  }
   483  
   484  func diskFileOperation(op types.VirtualDeviceConfigSpecOperation, fop types.VirtualDeviceConfigSpecFileOperation, device types.BaseVirtualDevice) types.VirtualDeviceConfigSpecFileOperation {
   485  	if disk, ok := device.(*types.VirtualDisk); ok {
   486  		// Special case to attach an existing disk
   487  		if op == types.VirtualDeviceConfigSpecOperationAdd && disk.CapacityInKB == 0 && disk.CapacityInBytes == 0 {
   488  			childDisk := false
   489  			if b, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
   490  				childDisk = b.Parent != nil
   491  			}
   492  
   493  			if !childDisk {
   494  				fop = "" // existing disk
   495  			}
   496  		}
   497  		return fop
   498  	}
   499  
   500  	return ""
   501  }
   502  
   503  func (v VirtualMachine) configureDevice(ctx context.Context, op types.VirtualDeviceConfigSpecOperation, fop types.VirtualDeviceConfigSpecFileOperation, devices ...types.BaseVirtualDevice) error {
   504  	spec := types.VirtualMachineConfigSpec{}
   505  
   506  	for _, device := range devices {
   507  		config := &types.VirtualDeviceConfigSpec{
   508  			Device:        device,
   509  			Operation:     op,
   510  			FileOperation: diskFileOperation(op, fop, device),
   511  		}
   512  
   513  		spec.DeviceChange = append(spec.DeviceChange, config)
   514  	}
   515  
   516  	task, err := v.Reconfigure(ctx, spec)
   517  	if err != nil {
   518  		return err
   519  	}
   520  
   521  	return task.Wait(ctx)
   522  }
   523  
   524  // AddDevice adds the given devices to the VirtualMachine
   525  func (v VirtualMachine) AddDevice(ctx context.Context, device ...types.BaseVirtualDevice) error {
   526  	return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationAdd, types.VirtualDeviceConfigSpecFileOperationCreate, device...)
   527  }
   528  
   529  // EditDevice edits the given (existing) devices on the VirtualMachine
   530  func (v VirtualMachine) EditDevice(ctx context.Context, device ...types.BaseVirtualDevice) error {
   531  	return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationEdit, types.VirtualDeviceConfigSpecFileOperationReplace, device...)
   532  }
   533  
   534  // RemoveDevice removes the given devices on the VirtualMachine
   535  func (v VirtualMachine) RemoveDevice(ctx context.Context, keepFiles bool, device ...types.BaseVirtualDevice) error {
   536  	fop := types.VirtualDeviceConfigSpecFileOperationDestroy
   537  	if keepFiles {
   538  		fop = ""
   539  	}
   540  	return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, fop, device...)
   541  }
   542  
   543  // AttachDisk attaches the given disk to the VirtualMachine
   544  func (v VirtualMachine) AttachDisk(ctx context.Context, id string, datastore *Datastore, controllerKey int32, unitNumber int32) error {
   545  	req := types.AttachDisk_Task{
   546  		This:          v.Reference(),
   547  		DiskId:        types.ID{Id: id},
   548  		Datastore:     datastore.Reference(),
   549  		ControllerKey: controllerKey,
   550  		UnitNumber:    &unitNumber,
   551  	}
   552  
   553  	res, err := methods.AttachDisk_Task(ctx, v.c, &req)
   554  	if err != nil {
   555  		return err
   556  	}
   557  
   558  	task := NewTask(v.c, res.Returnval)
   559  	return task.Wait(ctx)
   560  }
   561  
   562  // DetachDisk detaches the given disk from the VirtualMachine
   563  func (v VirtualMachine) DetachDisk(ctx context.Context, id string) error {
   564  	req := types.DetachDisk_Task{
   565  		This:   v.Reference(),
   566  		DiskId: types.ID{Id: id},
   567  	}
   568  
   569  	res, err := methods.DetachDisk_Task(ctx, v.c, &req)
   570  	if err != nil {
   571  		return err
   572  	}
   573  
   574  	task := NewTask(v.c, res.Returnval)
   575  	return task.Wait(ctx)
   576  }
   577  
   578  // BootOptions returns the VirtualMachine's config.bootOptions property.
   579  func (v VirtualMachine) BootOptions(ctx context.Context) (*types.VirtualMachineBootOptions, error) {
   580  	var o mo.VirtualMachine
   581  
   582  	err := v.Properties(ctx, v.Reference(), []string{"config.bootOptions"}, &o)
   583  	if err != nil {
   584  		return nil, err
   585  	}
   586  
   587  	return o.Config.BootOptions, nil
   588  }
   589  
   590  // SetBootOptions reconfigures the VirtualMachine with the given options.
   591  func (v VirtualMachine) SetBootOptions(ctx context.Context, options *types.VirtualMachineBootOptions) error {
   592  	spec := types.VirtualMachineConfigSpec{}
   593  
   594  	spec.BootOptions = options
   595  
   596  	task, err := v.Reconfigure(ctx, spec)
   597  	if err != nil {
   598  		return err
   599  	}
   600  
   601  	return task.Wait(ctx)
   602  }
   603  
   604  // Answer answers a pending question.
   605  func (v VirtualMachine) Answer(ctx context.Context, id, answer string) error {
   606  	req := types.AnswerVM{
   607  		This:         v.Reference(),
   608  		QuestionId:   id,
   609  		AnswerChoice: answer,
   610  	}
   611  
   612  	_, err := methods.AnswerVM(ctx, v.c, &req)
   613  	if err != nil {
   614  		return err
   615  	}
   616  
   617  	return nil
   618  }
   619  
   620  func (v VirtualMachine) AcquireTicket(ctx context.Context, kind string) (*types.VirtualMachineTicket, error) {
   621  	req := types.AcquireTicket{
   622  		This:       v.Reference(),
   623  		TicketType: kind,
   624  	}
   625  
   626  	res, err := methods.AcquireTicket(ctx, v.c, &req)
   627  	if err != nil {
   628  		return nil, err
   629  	}
   630  
   631  	return &res.Returnval, nil
   632  }
   633  
   634  // CreateSnapshot creates a new snapshot of a virtual machine.
   635  func (v VirtualMachine) CreateSnapshot(ctx context.Context, name string, description string, memory bool, quiesce bool) (*Task, error) {
   636  	req := types.CreateSnapshot_Task{
   637  		This:        v.Reference(),
   638  		Name:        name,
   639  		Description: description,
   640  		Memory:      memory,
   641  		Quiesce:     quiesce,
   642  	}
   643  
   644  	res, err := methods.CreateSnapshot_Task(ctx, v.c, &req)
   645  	if err != nil {
   646  		return nil, err
   647  	}
   648  
   649  	return NewTask(v.c, res.Returnval), nil
   650  }
   651  
   652  // RemoveAllSnapshot removes all snapshots of a virtual machine
   653  func (v VirtualMachine) RemoveAllSnapshot(ctx context.Context, consolidate *bool) (*Task, error) {
   654  	req := types.RemoveAllSnapshots_Task{
   655  		This:        v.Reference(),
   656  		Consolidate: consolidate,
   657  	}
   658  
   659  	res, err := methods.RemoveAllSnapshots_Task(ctx, v.c, &req)
   660  	if err != nil {
   661  		return nil, err
   662  	}
   663  
   664  	return NewTask(v.c, res.Returnval), nil
   665  }
   666  
   667  type snapshotMap map[string][]types.ManagedObjectReference
   668  
   669  func (m snapshotMap) add(parent string, tree []types.VirtualMachineSnapshotTree) {
   670  	for i, st := range tree {
   671  		sname := st.Name
   672  		names := []string{sname, st.Snapshot.Value}
   673  
   674  		if parent != "" {
   675  			sname = path.Join(parent, sname)
   676  			// Add full path as an option to resolve duplicate names
   677  			names = append(names, sname)
   678  		}
   679  
   680  		for _, name := range names {
   681  			m[name] = append(m[name], tree[i].Snapshot)
   682  		}
   683  
   684  		m.add(sname, st.ChildSnapshotList)
   685  	}
   686  }
   687  
   688  // SnapshotSize calculates the size of a given snapshot in bytes. If the
   689  // snapshot is current, disk files not associated with any parent snapshot are
   690  // included in size calculations. This allows for measuring and including the
   691  // growth from the last fixed snapshot to the present state.
   692  func SnapshotSize(info types.ManagedObjectReference, parent *types.ManagedObjectReference, vmlayout *types.VirtualMachineFileLayoutEx, isCurrent bool) int {
   693  	var fileKeyList []int
   694  	var parentFiles []int
   695  	var allSnapshotFiles []int
   696  
   697  	diskFiles := extractDiskLayoutFiles(vmlayout.Disk)
   698  
   699  	for _, layout := range vmlayout.Snapshot {
   700  		diskLayout := extractDiskLayoutFiles(layout.Disk)
   701  		allSnapshotFiles = append(allSnapshotFiles, diskLayout...)
   702  
   703  		if layout.Key.Value == info.Value {
   704  			fileKeyList = append(fileKeyList, int(layout.DataKey)) // The .vmsn file
   705  			fileKeyList = append(fileKeyList, diskLayout...)       // The .vmdk files
   706  		} else if parent != nil && layout.Key.Value == parent.Value {
   707  			parentFiles = append(parentFiles, diskLayout...)
   708  		}
   709  	}
   710  
   711  	for _, parentFile := range parentFiles {
   712  		removeKey(&fileKeyList, parentFile)
   713  	}
   714  
   715  	for _, file := range allSnapshotFiles {
   716  		removeKey(&diskFiles, file)
   717  	}
   718  
   719  	fileKeyMap := make(map[int]types.VirtualMachineFileLayoutExFileInfo)
   720  	for _, file := range vmlayout.File {
   721  		fileKeyMap[int(file.Key)] = file
   722  	}
   723  
   724  	size := 0
   725  
   726  	for _, fileKey := range fileKeyList {
   727  		file := fileKeyMap[fileKey]
   728  		if parent != nil ||
   729  			(file.Type != string(types.VirtualMachineFileLayoutExFileTypeDiskDescriptor) &&
   730  				file.Type != string(types.VirtualMachineFileLayoutExFileTypeDiskExtent)) {
   731  			size += int(file.Size)
   732  		}
   733  	}
   734  
   735  	if isCurrent {
   736  		for _, diskFile := range diskFiles {
   737  			file := fileKeyMap[diskFile]
   738  			size += int(file.Size)
   739  		}
   740  	}
   741  
   742  	return size
   743  }
   744  
   745  // FindSnapshot supports snapshot lookup by name, where name can be:
   746  // 1) snapshot ManagedObjectReference.Value (unique)
   747  // 2) snapshot name (may not be unique)
   748  // 3) snapshot tree path (may not be unique)
   749  func (v VirtualMachine) FindSnapshot(ctx context.Context, name string) (*types.ManagedObjectReference, error) {
   750  	var o mo.VirtualMachine
   751  
   752  	err := v.Properties(ctx, v.Reference(), []string{"snapshot"}, &o)
   753  	if err != nil {
   754  		return nil, err
   755  	}
   756  
   757  	if o.Snapshot == nil || len(o.Snapshot.RootSnapshotList) == 0 {
   758  		return nil, errors.New("no snapshots for this VM")
   759  	}
   760  
   761  	m := make(snapshotMap)
   762  	m.add("", o.Snapshot.RootSnapshotList)
   763  
   764  	s := m[name]
   765  	switch len(s) {
   766  	case 0:
   767  		return nil, fmt.Errorf("snapshot %q not found", name)
   768  	case 1:
   769  		return &s[0], nil
   770  	default:
   771  		return nil, fmt.Errorf("%q resolves to %d snapshots", name, len(s))
   772  	}
   773  }
   774  
   775  // RemoveSnapshot removes a named snapshot
   776  func (v VirtualMachine) RemoveSnapshot(ctx context.Context, name string, removeChildren bool, consolidate *bool) (*Task, error) {
   777  	snapshot, err := v.FindSnapshot(ctx, name)
   778  	if err != nil {
   779  		return nil, err
   780  	}
   781  
   782  	req := types.RemoveSnapshot_Task{
   783  		This:           snapshot.Reference(),
   784  		RemoveChildren: removeChildren,
   785  		Consolidate:    consolidate,
   786  	}
   787  
   788  	res, err := methods.RemoveSnapshot_Task(ctx, v.c, &req)
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  
   793  	return NewTask(v.c, res.Returnval), nil
   794  }
   795  
   796  // RevertToCurrentSnapshot reverts to the current snapshot
   797  func (v VirtualMachine) RevertToCurrentSnapshot(ctx context.Context, suppressPowerOn bool) (*Task, error) {
   798  	req := types.RevertToCurrentSnapshot_Task{
   799  		This:            v.Reference(),
   800  		SuppressPowerOn: types.NewBool(suppressPowerOn),
   801  	}
   802  
   803  	res, err := methods.RevertToCurrentSnapshot_Task(ctx, v.c, &req)
   804  	if err != nil {
   805  		return nil, err
   806  	}
   807  
   808  	return NewTask(v.c, res.Returnval), nil
   809  }
   810  
   811  // RevertToSnapshot reverts to a named snapshot
   812  func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppressPowerOn bool) (*Task, error) {
   813  	snapshot, err := v.FindSnapshot(ctx, name)
   814  	if err != nil {
   815  		return nil, err
   816  	}
   817  
   818  	req := types.RevertToSnapshot_Task{
   819  		This:            snapshot.Reference(),
   820  		SuppressPowerOn: types.NewBool(suppressPowerOn),
   821  	}
   822  
   823  	res, err := methods.RevertToSnapshot_Task(ctx, v.c, &req)
   824  	if err != nil {
   825  		return nil, err
   826  	}
   827  
   828  	return NewTask(v.c, res.Returnval), nil
   829  }
   830  
   831  // IsToolsRunning returns true if VMware Tools is currently running in the guest OS, and false otherwise.
   832  func (v VirtualMachine) IsToolsRunning(ctx context.Context) (bool, error) {
   833  	var o mo.VirtualMachine
   834  
   835  	err := v.Properties(ctx, v.Reference(), []string{"guest.toolsRunningStatus"}, &o)
   836  	if err != nil {
   837  		return false, err
   838  	}
   839  
   840  	return o.Guest.ToolsRunningStatus == string(types.VirtualMachineToolsRunningStatusGuestToolsRunning), nil
   841  }
   842  
   843  // Wait for the VirtualMachine to change to the desired power state.
   844  func (v VirtualMachine) WaitForPowerState(ctx context.Context, state types.VirtualMachinePowerState) error {
   845  	p := property.DefaultCollector(v.c)
   846  	err := property.Wait(ctx, p, v.Reference(), []string{PropRuntimePowerState}, func(pc []types.PropertyChange) bool {
   847  		for _, c := range pc {
   848  			if c.Name != PropRuntimePowerState {
   849  				continue
   850  			}
   851  			if c.Val == nil {
   852  				continue
   853  			}
   854  
   855  			ps := c.Val.(types.VirtualMachinePowerState)
   856  			if ps == state {
   857  				return true
   858  			}
   859  		}
   860  		return false
   861  	})
   862  
   863  	return err
   864  }
   865  
   866  func (v VirtualMachine) MarkAsTemplate(ctx context.Context) error {
   867  	req := types.MarkAsTemplate{
   868  		This: v.Reference(),
   869  	}
   870  
   871  	_, err := methods.MarkAsTemplate(ctx, v.c, &req)
   872  	if err != nil {
   873  		return err
   874  	}
   875  
   876  	return nil
   877  }
   878  
   879  func (v VirtualMachine) MarkAsVirtualMachine(ctx context.Context, pool ResourcePool, host *HostSystem) error {
   880  	req := types.MarkAsVirtualMachine{
   881  		This: v.Reference(),
   882  		Pool: pool.Reference(),
   883  	}
   884  
   885  	if host != nil {
   886  		ref := host.Reference()
   887  		req.Host = &ref
   888  	}
   889  
   890  	_, err := methods.MarkAsVirtualMachine(ctx, v.c, &req)
   891  	if err != nil {
   892  		return err
   893  	}
   894  
   895  	return nil
   896  }
   897  
   898  func (v VirtualMachine) Migrate(ctx context.Context, pool *ResourcePool, host *HostSystem, priority types.VirtualMachineMovePriority, state types.VirtualMachinePowerState) (*Task, error) {
   899  	req := types.MigrateVM_Task{
   900  		This:     v.Reference(),
   901  		Priority: priority,
   902  		State:    state,
   903  	}
   904  
   905  	if pool != nil {
   906  		ref := pool.Reference()
   907  		req.Pool = &ref
   908  	}
   909  
   910  	if host != nil {
   911  		ref := host.Reference()
   912  		req.Host = &ref
   913  	}
   914  
   915  	res, err := methods.MigrateVM_Task(ctx, v.c, &req)
   916  	if err != nil {
   917  		return nil, err
   918  	}
   919  
   920  	return NewTask(v.c, res.Returnval), nil
   921  }
   922  
   923  func (v VirtualMachine) Unregister(ctx context.Context) error {
   924  	req := types.UnregisterVM{
   925  		This: v.Reference(),
   926  	}
   927  
   928  	_, err := methods.UnregisterVM(ctx, v.Client(), &req)
   929  	return err
   930  }
   931  
   932  func (v VirtualMachine) MountToolsInstaller(ctx context.Context) error {
   933  	req := types.MountToolsInstaller{
   934  		This: v.Reference(),
   935  	}
   936  
   937  	_, err := methods.MountToolsInstaller(ctx, v.Client(), &req)
   938  	return err
   939  }
   940  
   941  func (v VirtualMachine) UnmountToolsInstaller(ctx context.Context) error {
   942  	req := types.UnmountToolsInstaller{
   943  		This: v.Reference(),
   944  	}
   945  
   946  	_, err := methods.UnmountToolsInstaller(ctx, v.Client(), &req)
   947  	return err
   948  }
   949  
   950  func (v VirtualMachine) UpgradeTools(ctx context.Context, options string) (*Task, error) {
   951  	req := types.UpgradeTools_Task{
   952  		This:             v.Reference(),
   953  		InstallerOptions: options,
   954  	}
   955  
   956  	res, err := methods.UpgradeTools_Task(ctx, v.Client(), &req)
   957  	if err != nil {
   958  		return nil, err
   959  	}
   960  
   961  	return NewTask(v.c, res.Returnval), nil
   962  }
   963  
   964  func (v VirtualMachine) Export(ctx context.Context) (*nfc.Lease, error) {
   965  	req := types.ExportVm{
   966  		This: v.Reference(),
   967  	}
   968  
   969  	res, err := methods.ExportVm(ctx, v.Client(), &req)
   970  	if err != nil {
   971  		return nil, err
   972  	}
   973  
   974  	return nfc.NewLease(v.c, res.Returnval), nil
   975  }
   976  
   977  func (v VirtualMachine) UpgradeVM(ctx context.Context, version string) (*Task, error) {
   978  	req := types.UpgradeVM_Task{
   979  		This:    v.Reference(),
   980  		Version: version,
   981  	}
   982  
   983  	res, err := methods.UpgradeVM_Task(ctx, v.Client(), &req)
   984  	if err != nil {
   985  		return nil, err
   986  	}
   987  
   988  	return NewTask(v.c, res.Returnval), nil
   989  }
   990  
   991  // UUID is a helper to get the UUID of the VirtualMachine managed object.
   992  // This method returns an empty string if an error occurs when retrieving UUID from the VirtualMachine object.
   993  func (v VirtualMachine) UUID(ctx context.Context) string {
   994  	var o mo.VirtualMachine
   995  
   996  	err := v.Properties(ctx, v.Reference(), []string{"config.uuid"}, &o)
   997  	if err != nil {
   998  		return ""
   999  	}
  1000  	if o.Config != nil {
  1001  		return o.Config.Uuid
  1002  	}
  1003  	return ""
  1004  }
  1005  
  1006  func (v VirtualMachine) QueryChangedDiskAreas(ctx context.Context, baseSnapshot, curSnapshot *types.ManagedObjectReference, disk *types.VirtualDisk, offset int64) (types.DiskChangeInfo, error) {
  1007  	var noChange types.DiskChangeInfo
  1008  	var err error
  1009  
  1010  	if offset > disk.CapacityInBytes {
  1011  		return noChange, fmt.Errorf("offset is greater than the disk size (%#x and %#x)", offset, disk.CapacityInBytes)
  1012  	} else if offset == disk.CapacityInBytes {
  1013  		return types.DiskChangeInfo{StartOffset: offset, Length: 0}, nil
  1014  	}
  1015  
  1016  	var b mo.VirtualMachineSnapshot
  1017  	err = v.Properties(ctx, baseSnapshot.Reference(), []string{"config.hardware"}, &b)
  1018  	if err != nil {
  1019  		return noChange, fmt.Errorf("failed to fetch config.hardware of snapshot %s: %s", baseSnapshot, err)
  1020  	}
  1021  
  1022  	var changeId *string
  1023  	for _, vd := range b.Config.Hardware.Device {
  1024  		d := vd.GetVirtualDevice()
  1025  		if d.Key != disk.Key {
  1026  			continue
  1027  		}
  1028  
  1029  		// As per VDDK programming guide, these are the four types of disks
  1030  		// that support CBT, see "Gathering Changed Block Information".
  1031  		if b, ok := d.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
  1032  			changeId = &b.ChangeId
  1033  			break
  1034  		}
  1035  		if b, ok := d.Backing.(*types.VirtualDiskSparseVer2BackingInfo); ok {
  1036  			changeId = &b.ChangeId
  1037  			break
  1038  		}
  1039  		if b, ok := d.Backing.(*types.VirtualDiskRawDiskMappingVer1BackingInfo); ok {
  1040  			changeId = &b.ChangeId
  1041  			break
  1042  		}
  1043  		if b, ok := d.Backing.(*types.VirtualDiskRawDiskVer2BackingInfo); ok {
  1044  			changeId = &b.ChangeId
  1045  			break
  1046  		}
  1047  
  1048  		return noChange, fmt.Errorf("disk %d has backing info without .ChangeId: %t", disk.Key, d.Backing)
  1049  	}
  1050  	if changeId == nil || *changeId == "" {
  1051  		return noChange, fmt.Errorf("CBT is not enabled on disk %d", disk.Key)
  1052  	}
  1053  
  1054  	req := types.QueryChangedDiskAreas{
  1055  		This:        v.Reference(),
  1056  		Snapshot:    curSnapshot,
  1057  		DeviceKey:   disk.Key,
  1058  		StartOffset: offset,
  1059  		ChangeId:    *changeId,
  1060  	}
  1061  
  1062  	res, err := methods.QueryChangedDiskAreas(ctx, v.Client(), &req)
  1063  	if err != nil {
  1064  		return noChange, err
  1065  	}
  1066  
  1067  	return res.Returnval, nil
  1068  }
  1069  
  1070  // ExportSnapshot exports all VMDK-files up to (but not including) a specified snapshot. This
  1071  // is useful when exporting a running VM.
  1072  func (v *VirtualMachine) ExportSnapshot(ctx context.Context, snapshot *types.ManagedObjectReference) (*nfc.Lease, error) {
  1073  	req := types.ExportSnapshot{
  1074  		This: *snapshot,
  1075  	}
  1076  	resp, err := methods.ExportSnapshot(ctx, v.Client(), &req)
  1077  	if err != nil {
  1078  		return nil, err
  1079  	}
  1080  	return nfc.NewLease(v.c, resp.Returnval), nil
  1081  }