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

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