github.com/vmware/govmomi@v0.37.2/simulator/virtual_machine_test.go (about)

     1  /*
     2  Copyright (c) 2017-2024 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 simulator
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math/rand"
    23  	"os"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/vmware/govmomi"
    29  	"github.com/vmware/govmomi/find"
    30  	"github.com/vmware/govmomi/object"
    31  	"github.com/vmware/govmomi/property"
    32  	"github.com/vmware/govmomi/simulator/esx"
    33  	"github.com/vmware/govmomi/task"
    34  	"github.com/vmware/govmomi/vim25"
    35  	"github.com/vmware/govmomi/vim25/mo"
    36  	"github.com/vmware/govmomi/vim25/types"
    37  )
    38  
    39  func TestCreateVm(t *testing.T) {
    40  	ctx := context.Background()
    41  
    42  	for _, model := range []*Model{ESX(), VPX()} {
    43  		defer model.Remove()
    44  		err := model.Create()
    45  		if err != nil {
    46  			t.Fatal(err)
    47  		}
    48  
    49  		s := model.Service.NewServer()
    50  		defer s.Close()
    51  
    52  		c, err := govmomi.NewClient(ctx, s.URL, true)
    53  		if err != nil {
    54  			t.Fatal(err)
    55  		}
    56  
    57  		p := property.DefaultCollector(c.Client)
    58  
    59  		finder := find.NewFinder(c.Client, false)
    60  
    61  		dc, err := finder.DefaultDatacenter(ctx)
    62  		if err != nil {
    63  			t.Fatal(err)
    64  		}
    65  
    66  		finder.SetDatacenter(dc)
    67  
    68  		folders, err := dc.Folders(ctx)
    69  		if err != nil {
    70  			t.Fatal(err)
    71  		}
    72  
    73  		ds, err := finder.DefaultDatastore(ctx)
    74  		if err != nil {
    75  			t.Fatal(err)
    76  		}
    77  
    78  		hosts, err := finder.HostSystemList(ctx, "*/*")
    79  		if err != nil {
    80  			t.Fatal(err)
    81  		}
    82  
    83  		nhosts := len(hosts)
    84  		host := hosts[rand.Intn(nhosts)]
    85  		pool, err := host.ResourcePool(ctx)
    86  		if err != nil {
    87  			t.Fatal(err)
    88  		}
    89  
    90  		if nhosts == 1 {
    91  			// test the default path against the ESX model
    92  			host = nil
    93  		}
    94  
    95  		vmFolder := folders.VmFolder
    96  
    97  		var vmx string
    98  
    99  		spec := types.VirtualMachineConfigSpec{
   100  			// Note: real ESX allows the VM to be created without a GuestId,
   101  			// but will power on will fail.
   102  			GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
   103  		}
   104  
   105  		steps := []func(){
   106  			func() {
   107  				spec.Name = "test"
   108  				vmx = fmt.Sprintf("%s/%s.vmx", spec.Name, spec.Name)
   109  			},
   110  			func() {
   111  				spec.Files = &types.VirtualMachineFileInfo{
   112  					VmPathName: fmt.Sprintf("[%s] %s", ds.Name(), vmx),
   113  				}
   114  			},
   115  		}
   116  
   117  		// expecting CreateVM to fail until all steps are taken
   118  		for _, step := range steps {
   119  			task, cerr := vmFolder.CreateVM(ctx, spec, pool, host)
   120  			if cerr != nil {
   121  				t.Fatal(err)
   122  			}
   123  
   124  			cerr = task.Wait(ctx)
   125  			if cerr == nil {
   126  				t.Error("expected error")
   127  			}
   128  
   129  			step()
   130  		}
   131  
   132  		task, err := vmFolder.CreateVM(ctx, spec, pool, host)
   133  		if err != nil {
   134  			t.Fatal(err)
   135  		}
   136  
   137  		info, err := task.WaitForResult(ctx, nil)
   138  		if err != nil {
   139  			t.Fatal(err)
   140  		}
   141  
   142  		// Test that datastore files were created
   143  		_, err = ds.Stat(ctx, vmx)
   144  		if err != nil {
   145  			t.Fatal(err)
   146  		}
   147  
   148  		vm := object.NewVirtualMachine(c.Client, info.Result.(types.ManagedObjectReference))
   149  
   150  		name, err := vm.ObjectName(ctx)
   151  		if err != nil {
   152  			t.Fatal(err)
   153  		}
   154  
   155  		if name != spec.Name {
   156  			t.Errorf("name=%s", name)
   157  		}
   158  
   159  		_, err = vm.Device(ctx)
   160  		if err != nil {
   161  			t.Fatal(err)
   162  		}
   163  
   164  		recreate := func(context.Context) (*object.Task, error) {
   165  			return vmFolder.CreateVM(ctx, spec, pool, nil)
   166  		}
   167  
   168  		ops := []struct {
   169  			method func(context.Context) (*object.Task, error)
   170  			state  types.VirtualMachinePowerState
   171  			fail   bool
   172  		}{
   173  			// Powered off by default
   174  			{nil, types.VirtualMachinePowerStatePoweredOff, false},
   175  			// Create with same .vmx path should fail
   176  			{recreate, "", true},
   177  			// Off -> On  == ok
   178  			{vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, false},
   179  			// On  -> On  == fail
   180  			{vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, true},
   181  			// On  -> Off == ok
   182  			{vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, false},
   183  			// Off -> Off == fail
   184  			{vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, true},
   185  			// Off -> On  == ok
   186  			{vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, false},
   187  			// Destroy == fail (power is On)
   188  			{vm.Destroy, types.VirtualMachinePowerStatePoweredOn, true},
   189  			// On  -> Off == ok
   190  			{vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, false},
   191  			// Off -> Reset == fail
   192  			{vm.Reset, types.VirtualMachinePowerStatePoweredOff, true},
   193  			// Off -> On  == ok
   194  			{vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, false},
   195  			// On -> Reset == ok
   196  			{vm.Reset, types.VirtualMachinePowerStatePoweredOn, false},
   197  			// On -> Suspend == ok
   198  			{vm.Suspend, types.VirtualMachinePowerStateSuspended, false},
   199  			// On  -> Off == ok
   200  			{vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, false},
   201  			// Destroy == ok (power is Off)
   202  			{vm.Destroy, "", false},
   203  		}
   204  
   205  		for i, op := range ops {
   206  			if op.method != nil {
   207  				task, err = op.method(ctx)
   208  				if err != nil {
   209  					t.Fatal(err)
   210  				}
   211  
   212  				err = task.Wait(ctx)
   213  				if op.fail {
   214  					if err == nil {
   215  						t.Errorf("%d: expected error", i)
   216  					}
   217  				} else {
   218  					if err != nil {
   219  						t.Errorf("%d: %s", i, err)
   220  					}
   221  				}
   222  			}
   223  
   224  			if len(op.state) != 0 {
   225  				state, err := vm.PowerState(ctx)
   226  				if err != nil {
   227  					t.Fatal(err)
   228  				}
   229  
   230  				if state != op.state {
   231  					t.Errorf("state=%s", state)
   232  				}
   233  
   234  				err = property.Wait(ctx, p, vm.Reference(), []string{object.PropRuntimePowerState}, func(pc []types.PropertyChange) bool {
   235  					for _, c := range pc {
   236  						switch v := c.Val.(type) {
   237  						case types.VirtualMachinePowerState:
   238  							if v != op.state {
   239  								t.Errorf("state=%s", v)
   240  							}
   241  						default:
   242  							t.Errorf("unexpected type %T", v)
   243  						}
   244  
   245  					}
   246  					return true
   247  				})
   248  				if err != nil {
   249  					t.Error(err)
   250  				}
   251  
   252  				running, err := vm.IsToolsRunning(ctx)
   253  				if err != nil {
   254  					t.Error(err)
   255  				}
   256  				if running {
   257  					t.Error("tools running")
   258  				}
   259  			}
   260  		}
   261  
   262  		// Test that datastore files were removed
   263  		_, err = ds.Stat(ctx, vmx)
   264  		if err == nil {
   265  			t.Error("expected error")
   266  		}
   267  	}
   268  }
   269  
   270  func TestCreateVmWithSpecialCharaters(t *testing.T) {
   271  	tests := []struct {
   272  		name     string
   273  		expected string
   274  	}{
   275  		{`/`, `%2f`},
   276  		{`\`, `%5c`},
   277  		{`%`, `%25`},
   278  		// multiple special characters
   279  		{`%%`, `%25%25`},
   280  		// slash-separated name
   281  		{`foo/bar`, `foo%2fbar`},
   282  	}
   283  
   284  	for _, test := range tests {
   285  		m := ESX()
   286  
   287  		Test(func(ctx context.Context, c *vim25.Client) {
   288  			finder := find.NewFinder(c, false)
   289  
   290  			dc, err := finder.DefaultDatacenter(ctx)
   291  			if err != nil {
   292  				t.Fatal(err)
   293  			}
   294  
   295  			finder.SetDatacenter(dc)
   296  			folders, err := dc.Folders(ctx)
   297  			if err != nil {
   298  				t.Fatal(err)
   299  			}
   300  			vmFolder := folders.VmFolder
   301  
   302  			ds, err := finder.DefaultDatastore(ctx)
   303  			if err != nil {
   304  				t.Fatal(err)
   305  			}
   306  
   307  			spec := types.VirtualMachineConfigSpec{
   308  				Name: test.name,
   309  				Files: &types.VirtualMachineFileInfo{
   310  					VmPathName: fmt.Sprintf("[%s]", ds.Name()),
   311  				},
   312  			}
   313  
   314  			pool := object.NewResourcePool(c, esx.ResourcePool.Self)
   315  
   316  			task, err := vmFolder.CreateVM(ctx, spec, pool, nil)
   317  			if err != nil {
   318  				t.Fatal(err)
   319  			}
   320  
   321  			info, err := task.WaitForResult(ctx, nil)
   322  			if err != nil {
   323  				t.Fatal(err)
   324  			}
   325  
   326  			vm := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference))
   327  			name, err := vm.ObjectName(ctx)
   328  			if err != nil {
   329  				t.Fatal(err)
   330  			}
   331  			if name != test.expected {
   332  				t.Errorf("expected %s, got %s", test.expected, name)
   333  			}
   334  		}, m)
   335  	}
   336  }
   337  
   338  func TestCloneVm(t *testing.T) {
   339  	tests := []struct {
   340  		name   string
   341  		vmName string
   342  		config types.VirtualMachineCloneSpec
   343  		fail   bool
   344  	}{
   345  		{
   346  			"clone a vm",
   347  			"cloned-vm",
   348  			types.VirtualMachineCloneSpec{
   349  				Template: false,
   350  				PowerOn:  false,
   351  			},
   352  			false,
   353  		},
   354  		{
   355  			"vm name is duplicated",
   356  			"DC0_H0_VM0",
   357  			types.VirtualMachineCloneSpec{
   358  				Template: false,
   359  				PowerOn:  false,
   360  			},
   361  			true,
   362  		},
   363  	}
   364  
   365  	for _, test := range tests {
   366  		test := test // assign to local var since loop var is reused
   367  
   368  		t.Run(test.name, func(t *testing.T) {
   369  			m := VPX()
   370  			defer m.Remove()
   371  
   372  			Test(func(ctx context.Context, c *vim25.Client) {
   373  				finder := find.NewFinder(c, false)
   374  				dc, err := finder.DefaultDatacenter(ctx)
   375  				if err != nil {
   376  					t.Fatal(err)
   377  				}
   378  
   379  				folders, err := dc.Folders(ctx)
   380  				if err != nil {
   381  					t.Fatal(err)
   382  				}
   383  
   384  				vmFolder := folders.VmFolder
   385  
   386  				vmm := Map.Any("VirtualMachine").(*VirtualMachine)
   387  				vm := object.NewVirtualMachine(c, vmm.Reference())
   388  
   389  				task, err := vm.Clone(ctx, vmFolder, test.vmName, test.config)
   390  				if err != nil {
   391  					t.Fatal(err)
   392  				}
   393  
   394  				err = task.Wait(ctx)
   395  				if test.fail {
   396  					if err == nil {
   397  						t.Errorf("%s: expected error", test.name)
   398  					}
   399  				} else {
   400  					if err != nil {
   401  						t.Errorf("%s: %s", test.name, err)
   402  					}
   403  				}
   404  			}, m)
   405  		})
   406  	}
   407  }
   408  
   409  func TestReconfigVmDevice(t *testing.T) {
   410  	ctx := context.Background()
   411  
   412  	m := ESX()
   413  	defer m.Remove()
   414  	err := m.Create()
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  
   419  	s := m.Service.NewServer()
   420  	defer s.Close()
   421  
   422  	c, err := govmomi.NewClient(ctx, s.URL, true)
   423  	if err != nil {
   424  		t.Fatal(err)
   425  	}
   426  
   427  	finder := find.NewFinder(c.Client, false)
   428  	finder.SetDatacenter(object.NewDatacenter(c.Client, esx.Datacenter.Reference()))
   429  
   430  	vms, err := finder.VirtualMachineList(ctx, "*")
   431  	if err != nil {
   432  		t.Fatal(err)
   433  	}
   434  
   435  	vm := vms[0]
   436  	device, err := vm.Device(ctx)
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  
   441  	// verify default device list
   442  	_, err = device.FindIDEController("")
   443  	if err != nil {
   444  		t.Fatal(err)
   445  	}
   446  
   447  	// default list of devices + 1 NIC + 1 SCSI controller + 1 CDROM + 1 disk created by the Model
   448  	mdevices := len(esx.VirtualDevice) + 4
   449  
   450  	if len(device) != mdevices {
   451  		t.Errorf("expected %d devices, got %d", mdevices, len(device))
   452  	}
   453  
   454  	d := device.FindByKey(esx.EthernetCard.Key)
   455  
   456  	err = vm.AddDevice(ctx, d)
   457  	if _, ok := err.(task.Error).Fault().(*types.InvalidDeviceSpec); !ok {
   458  		t.Fatalf("err=%v", err)
   459  	}
   460  
   461  	err = vm.RemoveDevice(ctx, false, d)
   462  	if err != nil {
   463  		t.Fatal(err)
   464  	}
   465  
   466  	device, err = vm.Device(ctx)
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  
   471  	if len(device) != mdevices-1 {
   472  		t.Error("device list mismatch")
   473  	}
   474  
   475  	// cover the path where the simulator assigns a UnitNumber
   476  	d.GetVirtualDevice().UnitNumber = nil
   477  	// cover the path where the simulator assigns a Key
   478  	d.GetVirtualDevice().Key = -1
   479  
   480  	err = vm.AddDevice(ctx, d)
   481  	if err != nil {
   482  		t.Fatal(err)
   483  	}
   484  
   485  	device, err = vm.Device(ctx)
   486  	if err != nil {
   487  		t.Fatal(err)
   488  	}
   489  
   490  	if len(device) != mdevices {
   491  		t.Error("device list mismatch")
   492  	}
   493  
   494  	disks := device.SelectByType((*types.VirtualDisk)(nil))
   495  
   496  	for _, d := range disks {
   497  		disk := d.(*types.VirtualDisk)
   498  		info := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
   499  
   500  		if info.Datastore.Type == "" || info.Datastore.Value == "" {
   501  			t.Errorf("invalid datastore for %s", device.Name(d))
   502  		}
   503  
   504  		// RemoveDevice and keep the file backing
   505  		if err = vm.RemoveDevice(ctx, true, d); err != nil {
   506  			t.Error(err)
   507  		}
   508  
   509  		if err = vm.AddDevice(ctx, d); err == nil {
   510  			t.Error("expected FileExists error")
   511  		}
   512  
   513  		// Need FileOperation=="" to add an existing disk, see object.VirtualMachine.configureDevice
   514  		disk.CapacityInKB = 0
   515  		disk.CapacityInBytes = 0
   516  		if err = vm.AddDevice(ctx, d); err != nil {
   517  			t.Error(err)
   518  		}
   519  
   520  		d.GetVirtualDevice().DeviceInfo = nil
   521  		if err = vm.EditDevice(ctx, d); err != nil {
   522  			t.Error(err)
   523  		}
   524  
   525  		// RemoveDevice and delete the file backing
   526  		if err = vm.RemoveDevice(ctx, false, d); err != nil {
   527  			t.Error(err)
   528  		}
   529  
   530  		if err = vm.AddDevice(ctx, d); err == nil {
   531  			t.Error("expected FileNotFound error")
   532  		}
   533  	}
   534  }
   535  
   536  func TestConnectVmDevice(t *testing.T) {
   537  	ctx := context.Background()
   538  
   539  	m := ESX()
   540  	defer m.Remove()
   541  	err := m.Create()
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  
   546  	s := m.Service.NewServer()
   547  	defer s.Close()
   548  
   549  	c, err := govmomi.NewClient(ctx, s.URL, true)
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  
   554  	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
   555  	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
   556  
   557  	l := object.VirtualDeviceList{} // used only for Connect/Disconnect function
   558  
   559  	tests := []struct {
   560  		description            string
   561  		changePower            func(context.Context) (*object.Task, error)
   562  		changeConnectivity     func(types.BaseVirtualDevice) error
   563  		expectedConnected      bool
   564  		expectedStartConnected bool
   565  	}{
   566  		{"disconnect when vm is on", nil, l.Disconnect, false, false},
   567  		{"connect when vm is on", nil, l.Connect, true, true},
   568  		{"power off vm", vm.PowerOff, nil, false, true},
   569  		{"disconnect when vm is off", nil, l.Disconnect, false, false},
   570  		{"connect when vm is off", nil, l.Connect, false, true},
   571  		{"power on vm when StartConnected is true", vm.PowerOn, nil, true, true},
   572  		{"power off vm and disconnect again", vm.PowerOff, l.Disconnect, false, false},
   573  		{"power on vm when StartConnected is false", vm.PowerOn, nil, false, false},
   574  	}
   575  
   576  	for _, testCase := range tests {
   577  		testCase := testCase // assign to local var since loop var is reused
   578  
   579  		t.Run(testCase.description, func(t *testing.T) {
   580  			if testCase.changePower != nil {
   581  				task, err := testCase.changePower(ctx)
   582  				if err != nil {
   583  					t.Fatal(err)
   584  				}
   585  
   586  				err = task.Wait(ctx)
   587  				if err != nil {
   588  					t.Fatal(err)
   589  				}
   590  			}
   591  
   592  			if testCase.changeConnectivity != nil {
   593  				list, err := vm.Device(ctx)
   594  				if err != nil {
   595  					t.Fatal(err)
   596  				}
   597  				device := list.FindByKey(esx.EthernetCard.Key)
   598  				if device == nil {
   599  					t.Fatal("cloud not find EthernetCard")
   600  				}
   601  
   602  				err = testCase.changeConnectivity(device)
   603  				if err != nil {
   604  					t.Fatal(err)
   605  				}
   606  				err = vm.EditDevice(ctx, device)
   607  				if err != nil {
   608  					t.Fatal(err)
   609  				}
   610  			}
   611  
   612  			updatedList, err := vm.Device(ctx)
   613  			if err != nil {
   614  				t.Fatal(err)
   615  			}
   616  			updatedDevice := updatedList.FindByKey(esx.EthernetCard.Key)
   617  			if updatedDevice == nil {
   618  				t.Fatal("cloud not find EthernetCard")
   619  			}
   620  			conn := updatedDevice.GetVirtualDevice().Connectable
   621  
   622  			if conn.Connected != testCase.expectedConnected {
   623  				t.Errorf("unexpected Connected property. expected: %t, actual: %t",
   624  					testCase.expectedConnected, conn.Connected)
   625  			}
   626  			if conn.StartConnected != testCase.expectedStartConnected {
   627  				t.Errorf("unexpected StartConnected property. expected: %t, actual: %t",
   628  					testCase.expectedStartConnected, conn.StartConnected)
   629  			}
   630  		})
   631  	}
   632  }
   633  
   634  func TestVAppConfigAdd(t *testing.T) {
   635  	ctx := context.Background()
   636  
   637  	m := ESX()
   638  	defer m.Remove()
   639  	err := m.Create()
   640  	if err != nil {
   641  		t.Fatal(err)
   642  	}
   643  
   644  	s := m.Service.NewServer()
   645  	defer s.Close()
   646  
   647  	c, err := govmomi.NewClient(ctx, s.URL, true)
   648  	if err != nil {
   649  		t.Fatal(err)
   650  	}
   651  
   652  	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
   653  	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
   654  
   655  	tests := []struct {
   656  		description      string
   657  		expectedErr      types.BaseMethodFault
   658  		spec             types.VirtualMachineConfigSpec
   659  		existingVMConfig *types.VirtualMachineConfigSpec
   660  		expectedProps    []types.VAppPropertyInfo
   661  	}{
   662  
   663  		{
   664  			description: "successfully add a new property",
   665  			spec: types.VirtualMachineConfigSpec{
   666  				VAppConfig: &types.VmConfigSpec{
   667  					Property: []types.VAppPropertySpec{
   668  						{
   669  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   670  								Operation: types.ArrayUpdateOperationAdd,
   671  							},
   672  							Info: &types.VAppPropertyInfo{
   673  								Key:   int32(1),
   674  								Id:    "foo-id",
   675  								Value: "foo-value",
   676  							},
   677  						},
   678  					},
   679  				},
   680  			},
   681  			expectedProps: []types.VAppPropertyInfo{
   682  				{
   683  					Key:   int32(1),
   684  					Id:    "foo-id",
   685  					Value: "foo-value",
   686  				},
   687  			},
   688  		},
   689  		{
   690  			description: "return error when a property that exists is added",
   691  			expectedErr: new(types.InvalidArgument),
   692  			existingVMConfig: &types.VirtualMachineConfigSpec{
   693  				VAppConfig: &types.VmConfigSpec{
   694  					Property: []types.VAppPropertySpec{
   695  						{
   696  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   697  								Operation: types.ArrayUpdateOperationAdd,
   698  							},
   699  							Info: &types.VAppPropertyInfo{
   700  								Key:   int32(2),
   701  								Id:    "foo-id",
   702  								Value: "foo-value",
   703  							},
   704  						},
   705  					},
   706  				},
   707  			},
   708  			spec: types.VirtualMachineConfigSpec{
   709  				VAppConfig: &types.VmConfigSpec{
   710  					Property: []types.VAppPropertySpec{
   711  						{
   712  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   713  								Operation: types.ArrayUpdateOperationAdd,
   714  							},
   715  							Info: &types.VAppPropertyInfo{
   716  								Key: int32(2),
   717  							},
   718  						},
   719  					},
   720  				},
   721  			},
   722  		},
   723  	}
   724  
   725  	for _, testCase := range tests {
   726  		t.Run(testCase.description, func(t *testing.T) {
   727  			if testCase.existingVMConfig != nil {
   728  				rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig)
   729  				if err := rtask.Wait(ctx); err != nil {
   730  					t.Errorf("Reconfigure failed during test setup. err: %v", err)
   731  				}
   732  			}
   733  
   734  			err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec())
   735  			if !reflect.DeepEqual(err, testCase.expectedErr) {
   736  				t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err)
   737  			}
   738  
   739  			if testCase.expectedErr == nil {
   740  				props := vmm.Config.VAppConfig.GetVmConfigInfo().Property
   741  				// the testcase only has one VApp property, so ordering of the elements does not matter.
   742  				if !reflect.DeepEqual(props, testCase.expectedProps) {
   743  					t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props)
   744  				}
   745  			}
   746  		})
   747  	}
   748  }
   749  
   750  func TestVAppConfigEdit(t *testing.T) {
   751  	ctx := context.Background()
   752  
   753  	m := ESX()
   754  	defer m.Remove()
   755  	err := m.Create()
   756  	if err != nil {
   757  		t.Fatal(err)
   758  	}
   759  
   760  	s := m.Service.NewServer()
   761  	defer s.Close()
   762  
   763  	c, err := govmomi.NewClient(ctx, s.URL, true)
   764  	if err != nil {
   765  		t.Fatal(err)
   766  	}
   767  
   768  	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
   769  	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
   770  
   771  	tests := []struct {
   772  		description      string
   773  		expectedErr      types.BaseMethodFault
   774  		spec             types.VirtualMachineConfigSpec
   775  		existingVMConfig *types.VirtualMachineConfigSpec
   776  		expectedProps    []types.VAppPropertyInfo
   777  	}{
   778  
   779  		{
   780  			description: "successfully update a property that exists",
   781  			existingVMConfig: &types.VirtualMachineConfigSpec{
   782  				VAppConfig: &types.VmConfigSpec{
   783  					Property: []types.VAppPropertySpec{
   784  						{
   785  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   786  								Operation: types.ArrayUpdateOperationAdd,
   787  							},
   788  							Info: &types.VAppPropertyInfo{
   789  								Key:   int32(1),
   790  								Id:    "foo-id",
   791  								Value: "foo-value",
   792  							},
   793  						},
   794  					},
   795  				},
   796  			},
   797  			spec: types.VirtualMachineConfigSpec{
   798  				VAppConfig: &types.VmConfigSpec{
   799  					Property: []types.VAppPropertySpec{
   800  						{
   801  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   802  								Operation: types.ArrayUpdateOperationEdit,
   803  							},
   804  							Info: &types.VAppPropertyInfo{
   805  								Key:   int32(1),
   806  								Id:    "foo-id-updated",
   807  								Value: "foo-value-updated",
   808  							},
   809  						},
   810  					},
   811  				},
   812  			},
   813  			expectedProps: []types.VAppPropertyInfo{
   814  				{
   815  					Key:   int32(1),
   816  					Id:    "foo-id-updated",
   817  					Value: "foo-value-updated",
   818  				},
   819  			},
   820  		},
   821  		{
   822  			description: "return error when a property that doesn't exist is updated",
   823  			expectedErr: new(types.InvalidArgument),
   824  			spec: types.VirtualMachineConfigSpec{
   825  				VAppConfig: &types.VmConfigSpec{
   826  					Property: []types.VAppPropertySpec{
   827  						{
   828  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   829  								Operation: types.ArrayUpdateOperationEdit,
   830  							},
   831  							Info: &types.VAppPropertyInfo{
   832  								Key: int32(2),
   833  							},
   834  						},
   835  					},
   836  				},
   837  			},
   838  		},
   839  	}
   840  
   841  	for _, testCase := range tests {
   842  		t.Run(testCase.description, func(t *testing.T) {
   843  			if testCase.existingVMConfig != nil {
   844  				rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig)
   845  				if err := rtask.Wait(ctx); err != nil {
   846  					t.Errorf("Reconfigure failed during test setup. err: %v", err)
   847  				}
   848  			}
   849  
   850  			err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec())
   851  			if !reflect.DeepEqual(err, testCase.expectedErr) {
   852  				t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err)
   853  			}
   854  
   855  			if testCase.expectedErr == nil {
   856  				props := vmm.Config.VAppConfig.GetVmConfigInfo().Property
   857  				// the testcase only has one VApp property, so ordering of the elements does not matter.
   858  				if !reflect.DeepEqual(props, testCase.expectedProps) {
   859  					t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props)
   860  				}
   861  			}
   862  		})
   863  	}
   864  }
   865  
   866  func TestVAppConfigRemove(t *testing.T) {
   867  	ctx := context.Background()
   868  
   869  	m := ESX()
   870  	defer m.Remove()
   871  	err := m.Create()
   872  	if err != nil {
   873  		t.Fatal(err)
   874  	}
   875  
   876  	s := m.Service.NewServer()
   877  	defer s.Close()
   878  
   879  	c, err := govmomi.NewClient(ctx, s.URL, true)
   880  	if err != nil {
   881  		t.Fatal(err)
   882  	}
   883  
   884  	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
   885  	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
   886  
   887  	tests := []struct {
   888  		description      string
   889  		expectedErr      types.BaseMethodFault
   890  		spec             types.VirtualMachineConfigSpec
   891  		existingVMConfig *types.VirtualMachineConfigSpec
   892  		expectedProps    []types.VAppPropertyInfo
   893  	}{
   894  		{
   895  			description: "returns success when a property that exists is removed",
   896  			existingVMConfig: &types.VirtualMachineConfigSpec{
   897  				VAppConfig: &types.VmConfigSpec{
   898  					Property: []types.VAppPropertySpec{
   899  						{
   900  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   901  								Operation: types.ArrayUpdateOperationAdd,
   902  							},
   903  							Info: &types.VAppPropertyInfo{
   904  								Key: int32(1),
   905  							},
   906  						},
   907  					},
   908  				},
   909  			},
   910  			spec: types.VirtualMachineConfigSpec{
   911  				VAppConfig: &types.VmConfigSpec{
   912  					Property: []types.VAppPropertySpec{
   913  						{
   914  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   915  								Operation: types.ArrayUpdateOperationRemove,
   916  							},
   917  							Info: &types.VAppPropertyInfo{
   918  								Key: int32(1),
   919  							},
   920  						},
   921  					},
   922  				},
   923  			},
   924  			expectedProps: []types.VAppPropertyInfo{},
   925  		},
   926  		{
   927  			description: "return error when a property that doesn't exist is removed",
   928  			expectedErr: new(types.InvalidArgument),
   929  			spec: types.VirtualMachineConfigSpec{
   930  				VAppConfig: &types.VmConfigSpec{
   931  					Property: []types.VAppPropertySpec{
   932  						{
   933  							ArrayUpdateSpec: types.ArrayUpdateSpec{
   934  								Operation: types.ArrayUpdateOperationRemove,
   935  							},
   936  							Info: &types.VAppPropertyInfo{
   937  								Key: int32(2),
   938  							},
   939  						},
   940  					},
   941  				},
   942  			},
   943  		},
   944  	}
   945  
   946  	for _, testCase := range tests {
   947  		t.Run(testCase.description, func(t *testing.T) {
   948  			if testCase.existingVMConfig != nil {
   949  				rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig)
   950  				if err := rtask.Wait(ctx); err != nil {
   951  					t.Errorf("Reconfigure failed during test setup. err: %v", err)
   952  				}
   953  			}
   954  
   955  			err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec())
   956  			if !reflect.DeepEqual(err, testCase.expectedErr) {
   957  				t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err)
   958  			}
   959  
   960  			if testCase.expectedErr == nil {
   961  				props := vmm.Config.VAppConfig.GetVmConfigInfo().Property
   962  				// the testcase only has one VApp property, so ordering of the elements does not matter.
   963  				if !reflect.DeepEqual(props, testCase.expectedProps) {
   964  					t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props)
   965  				}
   966  			}
   967  		})
   968  	}
   969  }
   970  
   971  func TestReconfigVm(t *testing.T) {
   972  	ctx := context.Background()
   973  
   974  	m := ESX()
   975  	defer m.Remove()
   976  	err := m.Create()
   977  	if err != nil {
   978  		t.Fatal(err)
   979  	}
   980  
   981  	s := m.Service.NewServer()
   982  	defer s.Close()
   983  
   984  	c, err := govmomi.NewClient(ctx, s.URL, true)
   985  	if err != nil {
   986  		t.Fatal(err)
   987  	}
   988  
   989  	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
   990  	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
   991  
   992  	tests := []struct {
   993  		fail bool
   994  		spec types.VirtualMachineConfigSpec
   995  	}{
   996  		{
   997  			true, types.VirtualMachineConfigSpec{
   998  				CpuAllocation: &types.ResourceAllocationInfo{Reservation: types.NewInt64(-1)},
   999  			},
  1000  		},
  1001  		{
  1002  			false, types.VirtualMachineConfigSpec{
  1003  				CpuAllocation: &types.ResourceAllocationInfo{Reservation: types.NewInt64(100)},
  1004  			},
  1005  		},
  1006  		{
  1007  			true, types.VirtualMachineConfigSpec{
  1008  				GuestId: "enoent",
  1009  			},
  1010  		},
  1011  		{
  1012  			false, types.VirtualMachineConfigSpec{
  1013  				GuestId: string(GuestID[0]),
  1014  			},
  1015  		},
  1016  		{
  1017  			false, types.VirtualMachineConfigSpec{
  1018  				NestedHVEnabled: types.NewBool(true),
  1019  			},
  1020  		},
  1021  		{
  1022  			false, types.VirtualMachineConfigSpec{
  1023  				CpuHotAddEnabled: types.NewBool(true),
  1024  			},
  1025  		},
  1026  		{
  1027  			false, types.VirtualMachineConfigSpec{
  1028  				CpuHotRemoveEnabled: types.NewBool(true),
  1029  			},
  1030  		},
  1031  		{
  1032  			false, types.VirtualMachineConfigSpec{
  1033  				GuestAutoLockEnabled: types.NewBool(true),
  1034  			},
  1035  		},
  1036  		{
  1037  			false, types.VirtualMachineConfigSpec{
  1038  				MemoryHotAddEnabled: types.NewBool(true),
  1039  			},
  1040  		},
  1041  		{
  1042  			false, types.VirtualMachineConfigSpec{
  1043  				MemoryReservationLockedToMax: types.NewBool(true),
  1044  			},
  1045  		},
  1046  		{
  1047  			false, types.VirtualMachineConfigSpec{
  1048  				MessageBusTunnelEnabled: types.NewBool(true),
  1049  			},
  1050  		},
  1051  		{
  1052  			false, types.VirtualMachineConfigSpec{
  1053  				NpivTemporaryDisabled: types.NewBool(true),
  1054  			},
  1055  		},
  1056  		{
  1057  			false, types.VirtualMachineConfigSpec{
  1058  				NpivOnNonRdmDisks: types.NewBool(true),
  1059  			},
  1060  		},
  1061  		{
  1062  			false, types.VirtualMachineConfigSpec{
  1063  				ConsolePreferences: &types.VirtualMachineConsolePreferences{
  1064  					PowerOnWhenOpened: types.NewBool(true),
  1065  				},
  1066  			},
  1067  		},
  1068  		{
  1069  			false, types.VirtualMachineConfigSpec{
  1070  				CpuAffinity: &types.VirtualMachineAffinityInfo{
  1071  					AffinitySet: []int32{1},
  1072  				},
  1073  			},
  1074  		},
  1075  		{
  1076  			false, types.VirtualMachineConfigSpec{
  1077  				CpuAllocation: &types.ResourceAllocationInfo{
  1078  					Reservation: types.NewInt64(100),
  1079  				},
  1080  			},
  1081  		},
  1082  		{
  1083  			false, types.VirtualMachineConfigSpec{
  1084  				MemoryAffinity: &types.VirtualMachineAffinityInfo{
  1085  					AffinitySet: []int32{1},
  1086  				},
  1087  			},
  1088  		},
  1089  		{
  1090  			false, types.VirtualMachineConfigSpec{
  1091  				MemoryAllocation: &types.ResourceAllocationInfo{
  1092  					Reservation: types.NewInt64(100),
  1093  				},
  1094  			},
  1095  		},
  1096  		{
  1097  			false, types.VirtualMachineConfigSpec{
  1098  				LatencySensitivity: &types.LatencySensitivity{
  1099  					Sensitivity: 1,
  1100  				},
  1101  			},
  1102  		},
  1103  	}
  1104  
  1105  	for i, test := range tests {
  1106  		rtask, _ := vm.Reconfigure(ctx, test.spec)
  1107  
  1108  		err := rtask.Wait(ctx)
  1109  		if test.fail {
  1110  			if err == nil {
  1111  				t.Errorf("%d: expected failure", i)
  1112  			}
  1113  		} else {
  1114  			if err != nil {
  1115  				t.Errorf("unexpected failure: %s", err)
  1116  			}
  1117  		}
  1118  	}
  1119  
  1120  	// Verify ReConfig actually works
  1121  	if *vmm.Config.NestedHVEnabled != true {
  1122  		t.Errorf("vm.Config.NestedHVEnabled expected true; got false")
  1123  	}
  1124  	if *vmm.Config.CpuHotAddEnabled != true {
  1125  		t.Errorf("vm.Config.CpuHotAddEnabled expected true; got false")
  1126  	}
  1127  	if *vmm.Config.CpuHotRemoveEnabled != true {
  1128  		t.Errorf("vm.Config.CpuHotRemoveEnabled expected true; got false")
  1129  	}
  1130  	if *vmm.Config.GuestAutoLockEnabled != true {
  1131  		t.Errorf("vm.Config.GuestAutoLockEnabled expected true; got false")
  1132  	}
  1133  	if *vmm.Config.MemoryHotAddEnabled != true {
  1134  		t.Errorf("vm.Config.MemoryHotAddEnabled expected true; got false")
  1135  	}
  1136  	if *vmm.Config.MemoryReservationLockedToMax != true {
  1137  		t.Errorf("vm.Config.MemoryReservationLockedToMax expected true; got false")
  1138  	}
  1139  	if *vmm.Config.MessageBusTunnelEnabled != true {
  1140  		t.Errorf("vm.Config.MessageBusTunnelEnabled expected true; got false")
  1141  	}
  1142  	if *vmm.Config.NpivTemporaryDisabled != true {
  1143  		t.Errorf("vm.Config.NpivTemporaryDisabled expected true; got false")
  1144  	}
  1145  	if *vmm.Config.NpivOnNonRdmDisks != true {
  1146  		t.Errorf("vm.Config.NpivOnNonRdmDisks expected true; got false")
  1147  	}
  1148  	if *vmm.Config.ConsolePreferences.PowerOnWhenOpened != true {
  1149  		t.Errorf("vm.Config.ConsolePreferences.PowerOnWhenOpened expected true; got false")
  1150  	}
  1151  	if vmm.Config.CpuAffinity.AffinitySet[0] != int32(1) {
  1152  		t.Errorf("vm.Config.CpuAffinity.AffinitySet[0] expected %d; got %d",
  1153  			1, vmm.Config.CpuAffinity.AffinitySet[0])
  1154  	}
  1155  	if vmm.Config.MemoryAffinity.AffinitySet[0] != int32(1) {
  1156  		t.Errorf("vm.Config.CpuAffinity.AffinitySet[0] expected %d; got %d",
  1157  			1, vmm.Config.CpuAffinity.AffinitySet[0])
  1158  	}
  1159  	if *vmm.Config.CpuAllocation.Reservation != 100 {
  1160  		t.Errorf("vm.Config.CpuAllocation.Reservation expected %d; got %d",
  1161  			100, *vmm.Config.CpuAllocation.Reservation)
  1162  	}
  1163  	if *vmm.Config.MemoryAllocation.Reservation != 100 {
  1164  		t.Errorf("vm.Config.MemoryAllocation.Reservation expected %d; got %d",
  1165  			100, *vmm.Config.MemoryAllocation.Reservation)
  1166  	}
  1167  	if vmm.Config.LatencySensitivity.Sensitivity != int32(1) {
  1168  		t.Errorf("vmm.Config.LatencySensitivity.Sensitivity expected %d; got %d",
  1169  			1, vmm.Config.LatencySensitivity.Sensitivity)
  1170  	}
  1171  }
  1172  
  1173  func TestCreateVmWithDevices(t *testing.T) {
  1174  	ctx := context.Background()
  1175  
  1176  	m := ESX()
  1177  	m.Datastore = 2
  1178  	defer m.Remove()
  1179  
  1180  	err := m.Create()
  1181  	if err != nil {
  1182  		t.Fatal(err)
  1183  	}
  1184  
  1185  	s := m.Service.NewServer()
  1186  	defer s.Close()
  1187  
  1188  	c := m.Service.client
  1189  
  1190  	folder := object.NewFolder(c, esx.Datacenter.VmFolder)
  1191  	pool := object.NewResourcePool(c, esx.ResourcePool.Self)
  1192  
  1193  	// different set of devices from Model.Create's
  1194  	var devices object.VirtualDeviceList
  1195  	ide, _ := devices.CreateIDEController()
  1196  	cdrom, _ := devices.CreateCdrom(ide.(*types.VirtualIDEController))
  1197  	scsi, _ := devices.CreateSCSIController("scsi")
  1198  	disk := &types.VirtualDisk{
  1199  		CapacityInKB: 1024,
  1200  		VirtualDevice: types.VirtualDevice{
  1201  			Backing: new(types.VirtualDiskFlatVer2BackingInfo), // Leave fields empty to test defaults
  1202  		},
  1203  	}
  1204  	disk2 := &types.VirtualDisk{
  1205  		CapacityInKB: 1024,
  1206  		VirtualDevice: types.VirtualDevice{
  1207  			Backing: &types.VirtualDiskFlatVer2BackingInfo{
  1208  				VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
  1209  					FileName: "[LocalDS_0]",
  1210  				},
  1211  			},
  1212  		},
  1213  	}
  1214  	devices = append(devices, ide, cdrom, scsi)
  1215  	devices.AssignController(disk, scsi.(*types.VirtualLsiLogicController))
  1216  	devices = append(devices, disk)
  1217  	devices.AssignController(disk2, scsi.(*types.VirtualLsiLogicController))
  1218  	devices = append(devices, disk2)
  1219  	create, _ := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
  1220  
  1221  	spec := types.VirtualMachineConfigSpec{
  1222  		Name:         "foo",
  1223  		GuestId:      string(types.VirtualMachineGuestOsIdentifierOtherGuest),
  1224  		DeviceChange: create,
  1225  		Files: &types.VirtualMachineFileInfo{
  1226  			VmPathName: "[LocalDS_0]",
  1227  		},
  1228  	}
  1229  
  1230  	ctask, _ := folder.CreateVM(ctx, spec, pool, nil)
  1231  	info, err := ctask.WaitForResult(ctx, nil)
  1232  	if err != nil {
  1233  		t.Fatal(err)
  1234  	}
  1235  
  1236  	vm := Map.Get(info.Result.(types.ManagedObjectReference)).(*VirtualMachine)
  1237  
  1238  	expect := len(esx.VirtualDevice) + len(devices)
  1239  	ndevice := len(vm.Config.Hardware.Device)
  1240  
  1241  	if expect != ndevice {
  1242  		t.Errorf("expected %d, got %d", expect, ndevice)
  1243  	}
  1244  
  1245  	// check number of disk and disk summary
  1246  	ndisk := 0
  1247  	for _, device := range vm.Config.Hardware.Device {
  1248  		disk, ok := device.(*types.VirtualDisk)
  1249  		if ok {
  1250  			ndisk++
  1251  			summary := disk.DeviceInfo.GetDescription().Summary
  1252  			if summary != "1,024 KB" {
  1253  				t.Errorf("expected '1,1024 KB', got %s", summary)
  1254  			}
  1255  		}
  1256  	}
  1257  	if ndisk != 2 {
  1258  		t.Errorf("expected 1 disk, got %d", ndisk)
  1259  	}
  1260  
  1261  	// Add disk on another datastore with empty path (issue 1854)
  1262  	ovm := object.NewVirtualMachine(c, vm.Self)
  1263  	disk = &types.VirtualDisk{
  1264  		CapacityInKB: 1024,
  1265  		VirtualDevice: types.VirtualDevice{
  1266  			Backing: &types.VirtualDiskFlatVer2BackingInfo{
  1267  				VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
  1268  					FileName: "[LocalDS_1]",
  1269  				},
  1270  			},
  1271  		},
  1272  	}
  1273  	devices.AssignController(disk, scsi.(*types.VirtualLsiLogicController))
  1274  	devices = nil
  1275  	devices = append(devices, disk)
  1276  	create, _ = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
  1277  	spec = types.VirtualMachineConfigSpec{DeviceChange: create}
  1278  	rtask, _ := ovm.Reconfigure(ctx, spec)
  1279  	_, err = rtask.WaitForResult(ctx, nil)
  1280  	if err != nil {
  1281  		t.Fatal(err)
  1282  	}
  1283  }
  1284  
  1285  func TestAddedDiskCapacity(t *testing.T) {
  1286  	tests := []struct {
  1287  		name                    string
  1288  		capacityInBytes         int64
  1289  		capacityInKB            int64
  1290  		expectedCapacityInBytes int64
  1291  		expectedCapacityInKB    int64
  1292  	}{
  1293  		{
  1294  			"specify capacityInBytes",
  1295  			512 * 1024,
  1296  			0,
  1297  			512 * 1024,
  1298  			512,
  1299  		},
  1300  		{
  1301  			"specify capacityInKB",
  1302  			0,
  1303  			512,
  1304  			512 * 1024,
  1305  			512,
  1306  		},
  1307  		{
  1308  			"specify both",
  1309  			512 * 1024,
  1310  			512,
  1311  			512 * 1024,
  1312  			512,
  1313  		},
  1314  		{
  1315  			"capacityInbytes takes precedence if two fields represents different capacity",
  1316  			512 * 1024,
  1317  			1024,
  1318  			512 * 1024,
  1319  			512,
  1320  		},
  1321  	}
  1322  
  1323  	for _, test := range tests {
  1324  		test := test // assign to local var since loop var is reused
  1325  		t.Run(test.name, func(t *testing.T) {
  1326  			m := ESX()
  1327  
  1328  			Test(func(ctx context.Context, c *vim25.Client) {
  1329  				vmm := Map.Any("VirtualMachine").(*VirtualMachine)
  1330  				vm := object.NewVirtualMachine(c, vmm.Reference())
  1331  
  1332  				ds := Map.Any("Datastore").(*Datastore)
  1333  
  1334  				devices, err := vm.Device(ctx)
  1335  				if err != nil {
  1336  					t.Fatal(err)
  1337  				}
  1338  
  1339  				controller, err := devices.FindDiskController("")
  1340  				if err != nil {
  1341  					t.Fatal(err)
  1342  				}
  1343  
  1344  				disk := devices.CreateDisk(controller, ds.Reference(), "")
  1345  				disk.CapacityInBytes = test.capacityInBytes
  1346  				disk.CapacityInKB = test.capacityInKB
  1347  
  1348  				err = vm.AddDevice(ctx, disk)
  1349  				if err != nil {
  1350  					t.Fatal(err)
  1351  				}
  1352  
  1353  				newDevices, err := vm.Device(ctx)
  1354  				if err != nil {
  1355  					t.Fatal(err)
  1356  				}
  1357  				disks := newDevices.SelectByType((*types.VirtualDisk)(nil))
  1358  				if len(disks) == 0 {
  1359  					t.Fatalf("len(disks)=%d", len(disks))
  1360  				}
  1361  
  1362  				newDisk := disks[len(disks)-1].(*types.VirtualDisk)
  1363  
  1364  				if newDisk.CapacityInBytes != test.expectedCapacityInBytes {
  1365  					t.Errorf("CapacityInBytes expected %d, got %d",
  1366  						test.expectedCapacityInBytes, newDisk.CapacityInBytes)
  1367  				}
  1368  				if newDisk.CapacityInKB != test.expectedCapacityInKB {
  1369  					t.Errorf("CapacityInKB expected %d, got %d",
  1370  						test.expectedCapacityInKB, newDisk.CapacityInKB)
  1371  				}
  1372  
  1373  			}, m)
  1374  		})
  1375  	}
  1376  }
  1377  
  1378  func TestEditedDiskCapacity(t *testing.T) {
  1379  	tests := []struct {
  1380  		name                    string
  1381  		capacityInBytes         int64
  1382  		capacityInKB            int64
  1383  		expectedCapacityInBytes int64
  1384  		expectedCapacityInKB    int64
  1385  		expectedErr             types.BaseMethodFault
  1386  	}{
  1387  		{
  1388  			"specify same capacities as before",
  1389  			10 * 1024 * 1024 * 1024, // 10GB
  1390  			10 * 1024 * 1024,        // 10GB
  1391  			10 * 1024 * 1024 * 1024, // 10GB
  1392  			10 * 1024 * 1024,        // 10GB
  1393  			nil,
  1394  		},
  1395  		{
  1396  			"increase only capacityInBytes",
  1397  			20 * 1024 * 1024 * 1024, // 20GB
  1398  			10 * 1024 * 1024,        // 10GB
  1399  			20 * 1024 * 1024 * 1024, // 20GB
  1400  			20 * 1024 * 1024,        // 20GB
  1401  			nil,
  1402  		},
  1403  		{
  1404  			"increase only capacityInKB",
  1405  			10 * 1024 * 1024 * 1024, // 10GB
  1406  			20 * 1024 * 1024,        // 20GB
  1407  			20 * 1024 * 1024 * 1024, // 20GB
  1408  			20 * 1024 * 1024,        // 20GB
  1409  			nil,
  1410  		},
  1411  		{
  1412  			"increase both capacityInBytes and capacityInKB",
  1413  			20 * 1024 * 1024 * 1024, // 20GB
  1414  			20 * 1024 * 1024,        // 20GB
  1415  			20 * 1024 * 1024 * 1024, // 20GB
  1416  			20 * 1024 * 1024,        // 20GB
  1417  			nil,
  1418  		},
  1419  		{
  1420  			"increase both capacityInBytes and capacityInKB but value is different",
  1421  			20 * 1024 * 1024 * 1024, // 20GB
  1422  			30 * 1024 * 1024,        // 30GB
  1423  			0,
  1424  			0,
  1425  			new(types.InvalidDeviceOperation),
  1426  		},
  1427  		{
  1428  			"decrease capacity",
  1429  			1 * 1024 * 1024 * 1024, // 1GB
  1430  			1 * 1024 * 1024,        // 1GB
  1431  			0,
  1432  			0,
  1433  			new(types.InvalidDeviceOperation),
  1434  		},
  1435  	}
  1436  
  1437  	for _, test := range tests {
  1438  		test := test // assign to local var since loop var is reused
  1439  		t.Run(test.name, func(t *testing.T) {
  1440  			m := ESX()
  1441  
  1442  			Test(func(ctx context.Context, c *vim25.Client) {
  1443  				vmm := Map.Any("VirtualMachine").(*VirtualMachine)
  1444  				vm := object.NewVirtualMachine(c, vmm.Reference())
  1445  				ds := Map.Any("Datastore").(*Datastore)
  1446  
  1447  				// create a new 10GB disk
  1448  				devices, err := vm.Device(ctx)
  1449  				if err != nil {
  1450  					t.Fatal(err)
  1451  				}
  1452  				controller, err := devices.FindDiskController("")
  1453  				if err != nil {
  1454  					t.Fatal(err)
  1455  				}
  1456  				disk := devices.CreateDisk(controller, ds.Reference(), "")
  1457  				disk.CapacityInBytes = 10 * 1024 * 1024 * 1024 // 10GB
  1458  				err = vm.AddDevice(ctx, disk)
  1459  				if err != nil {
  1460  					t.Fatal(err)
  1461  				}
  1462  
  1463  				// edit its capacity
  1464  				addedDevices, err := vm.Device(ctx)
  1465  				if err != nil {
  1466  					t.Fatal(err)
  1467  				}
  1468  				addedDisks := addedDevices.SelectByType((*types.VirtualDisk)(nil))
  1469  				if len(addedDisks) == 0 {
  1470  					t.Fatal("disk not found")
  1471  				}
  1472  				addedDisk := addedDisks[0].(*types.VirtualDisk)
  1473  				addedDisk.CapacityInBytes = test.capacityInBytes
  1474  				addedDisk.CapacityInKB = test.capacityInKB
  1475  				err = vm.EditDevice(ctx, addedDisk)
  1476  
  1477  				if test.expectedErr != nil {
  1478  					terr, ok := err.(task.Error)
  1479  					if !ok {
  1480  						t.Fatalf("error should be task.Error. actual: %T", err)
  1481  					}
  1482  
  1483  					if !reflect.DeepEqual(terr.Fault(), test.expectedErr) {
  1484  						t.Errorf("expectedErr: %v, actualErr: %v", test.expectedErr, terr.Fault())
  1485  					}
  1486  				} else {
  1487  					// obtain the disk again
  1488  					editedDevices, err := vm.Device(ctx)
  1489  					if err != nil {
  1490  						t.Fatal(err)
  1491  					}
  1492  					editedDisks := editedDevices.SelectByType((*types.VirtualDisk)(nil))
  1493  					if len(editedDevices) == 0 {
  1494  						t.Fatal("disk not found")
  1495  					}
  1496  					editedDisk := editedDisks[len(editedDisks)-1].(*types.VirtualDisk)
  1497  
  1498  					if editedDisk.CapacityInBytes != test.expectedCapacityInBytes {
  1499  						t.Errorf("CapacityInBytes expected %d, got %d",
  1500  							test.expectedCapacityInBytes, editedDisk.CapacityInBytes)
  1501  					}
  1502  					if editedDisk.CapacityInKB != test.expectedCapacityInKB {
  1503  						t.Errorf("CapacityInKB expected %d, got %d",
  1504  							test.expectedCapacityInKB, editedDisk.CapacityInKB)
  1505  					}
  1506  				}
  1507  			}, m)
  1508  		})
  1509  	}
  1510  }
  1511  
  1512  func TestReconfigureDevicesDatastoreFreespace(t *testing.T) {
  1513  	tests := []struct {
  1514  		name          string
  1515  		reconfigure   func(context.Context, *object.VirtualMachine, *Datastore, object.VirtualDeviceList) error
  1516  		freespaceDiff int64
  1517  	}{
  1518  		{
  1519  			"create a new disk",
  1520  			func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error {
  1521  				controller, err := l.FindDiskController("")
  1522  				if err != nil {
  1523  					return err
  1524  				}
  1525  
  1526  				disk := l.CreateDisk(controller, ds.Reference(), "")
  1527  				disk.CapacityInBytes = 10 * 1024 * 1024 * 1024 // 10GB
  1528  
  1529  				if err := vm.AddDevice(ctx, disk); err != nil {
  1530  					return err
  1531  				}
  1532  				return nil
  1533  			},
  1534  			-10 * 1024 * 1024 * 1024, // -10GB
  1535  		},
  1536  		{
  1537  			"edit disk size",
  1538  			func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error {
  1539  				disks := l.SelectByType((*types.VirtualDisk)(nil))
  1540  				if len(disks) == 0 {
  1541  					return fmt.Errorf("disk not found")
  1542  				}
  1543  				disk := disks[len(disks)-1].(*types.VirtualDisk)
  1544  
  1545  				// specify same disk capacity
  1546  				if err := vm.EditDevice(ctx, disk); err != nil {
  1547  					return err
  1548  				}
  1549  				return nil
  1550  			},
  1551  			0,
  1552  		},
  1553  		{
  1554  			"remove a disk and its files",
  1555  			func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error {
  1556  				disks := l.SelectByType((*types.VirtualDisk)(nil))
  1557  				if len(disks) == 0 {
  1558  					return fmt.Errorf("disk not found")
  1559  				}
  1560  				disk := disks[len(disks)-1].(*types.VirtualDisk)
  1561  
  1562  				if err := vm.RemoveDevice(ctx, false, disk); err != nil {
  1563  					return err
  1564  				}
  1565  				return nil
  1566  			},
  1567  			10 * 1024 * 1024 * 1024, // 10GB
  1568  		},
  1569  		{
  1570  			"remove a disk but keep its files",
  1571  			func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error {
  1572  				disks := l.SelectByType((*types.VirtualDisk)(nil))
  1573  				if len(disks) == 0 {
  1574  					return fmt.Errorf("disk not found")
  1575  				}
  1576  				disk := disks[len(disks)-1].(*types.VirtualDisk)
  1577  
  1578  				if err := vm.RemoveDevice(ctx, true, disk); err != nil {
  1579  					return err
  1580  				}
  1581  				return nil
  1582  			},
  1583  			0,
  1584  		},
  1585  	}
  1586  
  1587  	for _, test := range tests {
  1588  		test := test // assign to local var since loop var is reused
  1589  		t.Run(test.name, func(t *testing.T) {
  1590  			m := ESX()
  1591  
  1592  			Test(func(ctx context.Context, c *vim25.Client) {
  1593  				vmm := Map.Any("VirtualMachine").(*VirtualMachine)
  1594  				vm := object.NewVirtualMachine(c, vmm.Reference())
  1595  
  1596  				ds := Map.Any("Datastore").(*Datastore)
  1597  				freespaceBefore := ds.Datastore.Summary.FreeSpace
  1598  
  1599  				devices, err := vm.Device(ctx)
  1600  				if err != nil {
  1601  					t.Fatal(err)
  1602  				}
  1603  
  1604  				err = test.reconfigure(ctx, vm, ds, devices)
  1605  				if err != nil {
  1606  					t.Fatal(err)
  1607  				}
  1608  
  1609  				freespaceAfter := ds.Datastore.Summary.FreeSpace
  1610  
  1611  				if freespaceAfter-freespaceBefore != test.freespaceDiff {
  1612  					t.Errorf("difference of freespace expected %d, got %d",
  1613  						test.freespaceDiff, freespaceAfter-freespaceBefore)
  1614  				}
  1615  			}, m)
  1616  		})
  1617  	}
  1618  }
  1619  
  1620  func TestShutdownGuest(t *testing.T) {
  1621  	Test(func(ctx context.Context, c *vim25.Client) {
  1622  		vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference())
  1623  
  1624  		for _, timeout := range []bool{false, true} {
  1625  			if timeout {
  1626  				// ShutdownGuest will return right away, but powerState
  1627  				// is not updated until the internal task completes
  1628  				TaskDelay.MethodDelay = map[string]int{
  1629  					"ShutdownGuest": 500, // delay 500ms
  1630  					"LockHandoff":   0,   // don't lock vm during the delay
  1631  				}
  1632  			}
  1633  
  1634  			err := vm.ShutdownGuest(ctx)
  1635  			if err != nil {
  1636  				t.Fatal(err)
  1637  			}
  1638  
  1639  			wait := ctx
  1640  			var cancel context.CancelFunc
  1641  			if timeout {
  1642  				state, err := vm.PowerState(ctx)
  1643  				if err != nil {
  1644  					t.Fatal(err)
  1645  				}
  1646  
  1647  				// with the task delay, should still be on at this point
  1648  				if state != types.VirtualMachinePowerStatePoweredOn {
  1649  					t.Errorf("state=%s", state)
  1650  				}
  1651  
  1652  				wait, cancel = context.WithTimeout(ctx, time.Millisecond*250) // wait < task delay
  1653  				defer cancel()
  1654  			}
  1655  
  1656  			err = vm.WaitForPowerState(wait, types.VirtualMachinePowerStatePoweredOff)
  1657  			if timeout {
  1658  				if err == nil {
  1659  					t.Error("expected timeout")
  1660  				}
  1661  				// wait for power state to change, else next test may fail
  1662  				err = vm.WaitForPowerState(ctx, types.VirtualMachinePowerStatePoweredOff)
  1663  				if err != nil {
  1664  					t.Fatal(err)
  1665  				}
  1666  
  1667  			} else {
  1668  				if err != nil {
  1669  					t.Fatal(err)
  1670  				}
  1671  			}
  1672  
  1673  			// shutdown a poweroff vm should fail
  1674  			err = vm.ShutdownGuest(ctx)
  1675  			if err == nil {
  1676  				t.Error("expected error: InvalidPowerState")
  1677  			}
  1678  
  1679  			task, err := vm.PowerOn(ctx)
  1680  			if err != nil {
  1681  				t.Fatal(err)
  1682  			}
  1683  			err = task.Wait(ctx)
  1684  			if err != nil {
  1685  				t.Fatal(err)
  1686  			}
  1687  		}
  1688  	})
  1689  }
  1690  
  1691  func TestVmSnapshot(t *testing.T) {
  1692  	ctx := context.Background()
  1693  
  1694  	m := ESX()
  1695  	defer m.Remove()
  1696  	err := m.Create()
  1697  	if err != nil {
  1698  		t.Fatal(err)
  1699  	}
  1700  
  1701  	s := m.Service.NewServer()
  1702  	defer s.Close()
  1703  
  1704  	c, err := govmomi.NewClient(ctx, s.URL, true)
  1705  	if err != nil {
  1706  		t.Fatal(err)
  1707  	}
  1708  
  1709  	simVm := Map.Any("VirtualMachine")
  1710  	vm := object.NewVirtualMachine(c.Client, simVm.Reference())
  1711  
  1712  	_, err = fieldValue(reflect.ValueOf(simVm), "snapshot")
  1713  	if err != errEmptyField {
  1714  		t.Fatal("snapshot property should be 'nil' if there are no snapshots")
  1715  	}
  1716  
  1717  	task, err := vm.CreateSnapshot(ctx, "root", "description", true, true)
  1718  	if err != nil {
  1719  		t.Fatal(err)
  1720  	}
  1721  
  1722  	info, err := task.WaitForResult(ctx)
  1723  	if err != nil {
  1724  		t.Fatal(err)
  1725  	}
  1726  
  1727  	snapRef, ok := info.Result.(types.ManagedObjectReference)
  1728  	if !ok {
  1729  		t.Fatal("expected ManagedObjectRefrence result for CreateSnapshot")
  1730  	}
  1731  
  1732  	_, err = vm.FindSnapshot(ctx, snapRef.Value)
  1733  	if err != nil {
  1734  		t.Fatal(err, "snapshot should be found by result reference")
  1735  	}
  1736  
  1737  	_, err = fieldValue(reflect.ValueOf(simVm), "snapshot")
  1738  	if err == errEmptyField {
  1739  		t.Fatal("snapshot property should not be 'nil' if there are snapshots")
  1740  	}
  1741  	// NOTE: fieldValue cannot be used for nil check
  1742  	if len(simVm.(*VirtualMachine).RootSnapshot) == 0 {
  1743  		t.Fatal("rootSnapshot property should have elements if there are snapshots")
  1744  	}
  1745  
  1746  	task, err = vm.CreateSnapshot(ctx, "child", "description", true, true)
  1747  	if err != nil {
  1748  		t.Fatal(err)
  1749  	}
  1750  
  1751  	err = task.Wait(ctx)
  1752  	if err != nil {
  1753  		t.Fatal(err)
  1754  	}
  1755  
  1756  	_, err = vm.FindSnapshot(ctx, "child")
  1757  	if err != nil {
  1758  		t.Fatal(err)
  1759  	}
  1760  
  1761  	task, err = vm.RevertToCurrentSnapshot(ctx, true)
  1762  	if err != nil {
  1763  		t.Fatal(err)
  1764  	}
  1765  
  1766  	err = task.Wait(ctx)
  1767  	if err != nil {
  1768  		t.Fatal(err)
  1769  	}
  1770  
  1771  	task, err = vm.RevertToSnapshot(ctx, "root", true)
  1772  	if err != nil {
  1773  		t.Fatal(err)
  1774  	}
  1775  
  1776  	err = task.Wait(ctx)
  1777  	if err != nil {
  1778  		t.Fatal(err)
  1779  	}
  1780  
  1781  	task, err = vm.RemoveSnapshot(ctx, "child", false, nil)
  1782  	if err != nil {
  1783  		t.Fatal(err)
  1784  	}
  1785  
  1786  	err = task.Wait(ctx)
  1787  	if err != nil {
  1788  		t.Fatal(err)
  1789  	}
  1790  
  1791  	_, err = fieldValue(reflect.ValueOf(simVm), "snapshot")
  1792  	if err == errEmptyField {
  1793  		t.Fatal("snapshot property should not be 'nil' if there are snapshots")
  1794  	}
  1795  	// NOTE: fieldValue cannot be used for nil check
  1796  	if len(simVm.(*VirtualMachine).RootSnapshot) == 0 {
  1797  		t.Fatal("rootSnapshot property should have elements if there are snapshots")
  1798  	}
  1799  
  1800  	_, err = vm.FindSnapshot(ctx, "child")
  1801  	if err == nil {
  1802  		t.Fatal("child should be removed")
  1803  	}
  1804  
  1805  	task, err = vm.RemoveAllSnapshot(ctx, nil)
  1806  	if err != nil {
  1807  		t.Fatal(err)
  1808  	}
  1809  
  1810  	err = task.Wait(ctx)
  1811  	if err != nil {
  1812  		t.Fatal(err)
  1813  	}
  1814  
  1815  	_, err = fieldValue(reflect.ValueOf(simVm), "snapshot")
  1816  	if err != errEmptyField {
  1817  		t.Fatal("snapshot property should be 'nil' if there are no snapshots")
  1818  	}
  1819  	// NOTE: fieldValue cannot be used for nil check
  1820  	if len(simVm.(*VirtualMachine).RootSnapshot) != 0 {
  1821  		t.Fatal("rootSnapshot property should not have elements if there are no snapshots")
  1822  	}
  1823  
  1824  	_, err = vm.FindSnapshot(ctx, "root")
  1825  	if err == nil {
  1826  		t.Fatal("all snapshots should be removed")
  1827  	}
  1828  }
  1829  
  1830  func TestVmMarkAsTemplate(t *testing.T) {
  1831  	ctx := context.Background()
  1832  
  1833  	m := VPX()
  1834  	defer m.Remove()
  1835  	err := m.Create()
  1836  	if err != nil {
  1837  		t.Fatal(err)
  1838  	}
  1839  
  1840  	s := m.Service.NewServer()
  1841  	defer s.Close()
  1842  
  1843  	c, err := govmomi.NewClient(ctx, s.URL, true)
  1844  	if err != nil {
  1845  		t.Fatal(err)
  1846  	}
  1847  
  1848  	vm := object.NewVirtualMachine(c.Client, Map.Any("VirtualMachine").Reference())
  1849  
  1850  	err = vm.MarkAsTemplate(ctx)
  1851  	if err == nil {
  1852  		t.Fatal("cannot create template for a powered on vm")
  1853  	}
  1854  
  1855  	task, err := vm.PowerOff(ctx)
  1856  	if err != nil {
  1857  		t.Fatal(err)
  1858  	}
  1859  
  1860  	task.Wait(ctx)
  1861  
  1862  	err = vm.MarkAsTemplate(ctx)
  1863  	if err != nil {
  1864  		t.Fatal(err)
  1865  	}
  1866  
  1867  	_, err = vm.PowerOn(ctx)
  1868  	if err == nil {
  1869  		t.Fatal("cannot PowerOn a template")
  1870  	}
  1871  }
  1872  
  1873  func TestVmRefreshStorageInfo(t *testing.T) {
  1874  	ctx := context.Background()
  1875  
  1876  	m := ESX()
  1877  	defer m.Remove()
  1878  	err := m.Create()
  1879  	if err != nil {
  1880  		t.Fatal(err)
  1881  	}
  1882  
  1883  	s := m.Service.NewServer()
  1884  	defer s.Close()
  1885  
  1886  	c, err := govmomi.NewClient(ctx, s.URL, true)
  1887  	if err != nil {
  1888  		t.Fatal(err)
  1889  	}
  1890  
  1891  	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
  1892  	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
  1893  
  1894  	// take snapshot
  1895  	task, err := vm.CreateSnapshot(ctx, "root", "description", true, true)
  1896  	if err != nil {
  1897  		t.Fatal(err)
  1898  	}
  1899  
  1900  	err = task.Wait(ctx)
  1901  	if err != nil {
  1902  		t.Fatal(err)
  1903  	}
  1904  
  1905  	snapshot, err := vm.FindSnapshot(ctx, "root")
  1906  	if err != nil {
  1907  		t.Fatal(err)
  1908  	}
  1909  
  1910  	// check vm.Layout.Snapshot
  1911  	found := false
  1912  	for _, snapLayout := range vmm.Layout.Snapshot {
  1913  		if snapLayout.Key == *snapshot {
  1914  			found = true
  1915  		}
  1916  	}
  1917  
  1918  	if found == false {
  1919  		t.Fatal("could not find new snapshot in vm.Layout.Snapshot")
  1920  	}
  1921  
  1922  	// check vm.LayoutEx.Snapshot
  1923  	found = false
  1924  	for _, snapLayoutEx := range vmm.LayoutEx.Snapshot {
  1925  		if snapLayoutEx.Key == *snapshot {
  1926  			found = true
  1927  		}
  1928  	}
  1929  
  1930  	if found == false {
  1931  		t.Fatal("could not find new snapshot in vm.LayoutEx.Snapshot")
  1932  	}
  1933  
  1934  	// remove snapshot
  1935  	task, err = vm.RemoveAllSnapshot(ctx, nil)
  1936  	if err != nil {
  1937  		t.Fatal(err)
  1938  	}
  1939  
  1940  	err = task.Wait(ctx)
  1941  	if err != nil {
  1942  		t.Fatal(err)
  1943  	}
  1944  
  1945  	if len(vmm.Layout.Snapshot) != 0 {
  1946  		t.Fatal("expected vm.Layout.Snapshot to be empty")
  1947  	}
  1948  
  1949  	if len(vmm.LayoutEx.Snapshot) != 0 {
  1950  		t.Fatal("expected vm.LayoutEx.Snapshot to be empty")
  1951  	}
  1952  
  1953  	device, err := vm.Device(ctx)
  1954  	if err != nil {
  1955  		t.Fatal(err)
  1956  	}
  1957  
  1958  	disks := device.SelectByType((*types.VirtualDisk)(nil))
  1959  	if len(disks) < 1 {
  1960  		t.Fatal("expected VM to have at least 1 disk")
  1961  	}
  1962  
  1963  	findDiskFile := func(vmdkName string) *types.VirtualMachineFileLayoutExFileInfo {
  1964  		for _, dFile := range vmm.LayoutEx.File {
  1965  			if dFile.Name == vmdkName {
  1966  				return &dFile
  1967  			}
  1968  		}
  1969  
  1970  		return nil
  1971  	}
  1972  
  1973  	findDsStorage := func(dsName string) *types.VirtualMachineUsageOnDatastore {
  1974  		host := Map.Get(*vmm.Runtime.Host).(*HostSystem)
  1975  		ds := Map.FindByName(dsName, host.Datastore).(*Datastore)
  1976  
  1977  		for _, dsUsage := range vmm.Storage.PerDatastoreUsage {
  1978  			if dsUsage.Datastore == ds.Self {
  1979  				return &dsUsage
  1980  			}
  1981  		}
  1982  
  1983  		return nil
  1984  	}
  1985  
  1986  	for _, d := range disks {
  1987  		disk := d.(*types.VirtualDisk)
  1988  		info := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
  1989  		diskLayoutCount := len(vmm.Layout.Disk)
  1990  		summaryStorageNew := vmm.Summary.Storage
  1991  
  1992  		p, fault := parseDatastorePath(info.FileName)
  1993  		if fault != nil {
  1994  			t.Fatalf("could not parse datastore path for disk file: %s", info.FileName)
  1995  		}
  1996  
  1997  		storageNew := findDsStorage(p.Datastore)
  1998  		if storageNew == nil {
  1999  			t.Fatalf("could not find vm usage on datastore: %s", p.Datastore)
  2000  		}
  2001  
  2002  		diskFile := findDiskFile(info.FileName)
  2003  		if diskFile == nil {
  2004  			t.Fatal("could not find disk file in vm.LayoutEx.File")
  2005  		}
  2006  
  2007  		// remove disk
  2008  		if err = vm.RemoveDevice(ctx, false, d); err != nil {
  2009  			t.Error(err)
  2010  		}
  2011  
  2012  		summaryStorageOld := summaryStorageNew
  2013  		summaryStorageNew = vmm.Summary.Storage
  2014  
  2015  		storageOld := storageNew
  2016  		storageNew = findDsStorage(p.Datastore)
  2017  		if storageNew == nil {
  2018  			t.Fatalf("could not find vm usage on datastore: %s", p.Datastore)
  2019  		}
  2020  
  2021  		tests := []struct {
  2022  			got      int64
  2023  			expected int64
  2024  		}{
  2025  			{int64(len(vmm.Layout.Disk)), int64(diskLayoutCount - 1)},
  2026  			{summaryStorageNew.Committed, summaryStorageOld.Committed - diskFile.Size},
  2027  			{summaryStorageNew.Unshared, summaryStorageOld.Unshared - diskFile.Size},
  2028  			{summaryStorageNew.Uncommitted, summaryStorageOld.Uncommitted - disk.CapacityInBytes + diskFile.Size},
  2029  			{storageNew.Committed, storageOld.Committed - diskFile.Size},
  2030  			{storageNew.Unshared, storageOld.Unshared - diskFile.Size},
  2031  			{storageNew.Uncommitted, storageOld.Uncommitted - disk.CapacityInBytes + diskFile.Size},
  2032  		}
  2033  
  2034  		for _, test := range tests {
  2035  			if test.got != test.expected {
  2036  				t.Errorf("expected %d, got %d", test.expected, test.got)
  2037  			}
  2038  		}
  2039  
  2040  		// add disk
  2041  		disk.CapacityInBytes = 1000000000
  2042  		if err = vm.AddDevice(ctx, d); err != nil {
  2043  			t.Error(err)
  2044  		}
  2045  
  2046  		summaryStorageOld = summaryStorageNew
  2047  		summaryStorageNew = vmm.Summary.Storage
  2048  
  2049  		storageOld = storageNew
  2050  		storageNew = findDsStorage(p.Datastore)
  2051  		if storageNew == nil {
  2052  			t.Fatalf("could not find vm usage on datastore: %s", p.Datastore)
  2053  		}
  2054  
  2055  		diskFile = findDiskFile(info.FileName)
  2056  		if diskFile == nil {
  2057  			t.Fatal("could not find disk file in vm.LayoutEx.File")
  2058  		}
  2059  
  2060  		tests = []struct {
  2061  			got      int64
  2062  			expected int64
  2063  		}{
  2064  			{int64(len(vmm.Layout.Disk)), int64(diskLayoutCount)},
  2065  			{summaryStorageNew.Committed, summaryStorageOld.Committed + diskFile.Size},
  2066  			{summaryStorageNew.Unshared, summaryStorageOld.Unshared + diskFile.Size},
  2067  			{summaryStorageNew.Uncommitted, summaryStorageOld.Uncommitted + disk.CapacityInBytes - diskFile.Size},
  2068  			{storageNew.Committed, storageOld.Committed + diskFile.Size},
  2069  			{storageNew.Unshared, storageOld.Unshared + diskFile.Size},
  2070  			{storageNew.Uncommitted, storageOld.Uncommitted + disk.CapacityInBytes - diskFile.Size},
  2071  		}
  2072  
  2073  		for _, test := range tests {
  2074  			if test.got != test.expected {
  2075  				t.Errorf("expected %d, got %d", test.expected, test.got)
  2076  			}
  2077  		}
  2078  	}
  2079  
  2080  	// manually create log file
  2081  	fileLayoutExCount := len(vmm.LayoutEx.File)
  2082  
  2083  	p, fault := parseDatastorePath(vmm.Config.Files.LogDirectory)
  2084  	if fault != nil {
  2085  		t.Fatalf("could not parse datastore path: %s", vmm.Config.Files.LogDirectory)
  2086  	}
  2087  
  2088  	f, fault := vmm.createFile(p.String(), "test.log", false)
  2089  	if fault != nil {
  2090  		t.Fatal("could not create log file")
  2091  	}
  2092  
  2093  	if len(vmm.LayoutEx.File) != fileLayoutExCount {
  2094  		t.Errorf("expected %d, got %d", fileLayoutExCount, len(vmm.LayoutEx.File))
  2095  	}
  2096  
  2097  	if err = vm.RefreshStorageInfo(ctx); err != nil {
  2098  		t.Error(err)
  2099  	}
  2100  
  2101  	if len(vmm.LayoutEx.File) != fileLayoutExCount+1 {
  2102  		t.Errorf("expected %d, got %d", fileLayoutExCount+1, len(vmm.LayoutEx.File))
  2103  	}
  2104  
  2105  	err = f.Close()
  2106  	if err != nil {
  2107  		t.Fatalf("f.Close failure: %v", err)
  2108  	}
  2109  	err = os.Remove(f.Name())
  2110  	if err != nil {
  2111  		t.Fatalf("os.Remove(%s) failure: %v", f.Name(), err)
  2112  	}
  2113  
  2114  	if err = vm.RefreshStorageInfo(ctx); err != nil {
  2115  		t.Error(err)
  2116  	}
  2117  
  2118  	if len(vmm.LayoutEx.File) != fileLayoutExCount {
  2119  		t.Errorf("expected %d, got %d", fileLayoutExCount, len(vmm.LayoutEx.File))
  2120  	}
  2121  }
  2122  
  2123  func TestApplyExtraConfig(t *testing.T) {
  2124  
  2125  	applyAndAssertExtraConfigValue := func(
  2126  		ctx context.Context,
  2127  		vm *object.VirtualMachine,
  2128  		val string,
  2129  		assertDoesNotExist bool) {
  2130  
  2131  		task, err := vm.Reconfigure(ctx, types.VirtualMachineConfigSpec{
  2132  			ExtraConfig: []types.BaseOptionValue{
  2133  				&types.OptionValue{
  2134  					Key:   "hello",
  2135  					Value: val,
  2136  				},
  2137  			},
  2138  		})
  2139  		if err != nil {
  2140  			t.Fatal(err)
  2141  		}
  2142  		if err := task.Wait(ctx); err != nil {
  2143  			t.Fatal(err)
  2144  		}
  2145  
  2146  		var moVM mo.VirtualMachine
  2147  		if err := vm.Properties(
  2148  			ctx,
  2149  			vm.Reference(),
  2150  			[]string{"config.extraConfig"},
  2151  			&moVM); err != nil {
  2152  			t.Fatal(err)
  2153  		}
  2154  		if moVM.Config == nil {
  2155  			t.Fatal("nil config")
  2156  		}
  2157  		var found bool
  2158  		for i := range moVM.Config.ExtraConfig {
  2159  			bov := moVM.Config.ExtraConfig[i]
  2160  			if bov == nil {
  2161  				continue
  2162  			}
  2163  			ov := bov.GetOptionValue()
  2164  			if ov == nil {
  2165  				continue
  2166  			}
  2167  			if ov.Key == "hello" {
  2168  				if ov.Value != val {
  2169  					t.Fatalf("invalid ExtraConfig value: expected=%s, actual=%v", val, ov.Value)
  2170  				}
  2171  				found = true
  2172  			}
  2173  		}
  2174  		if !assertDoesNotExist && !found {
  2175  			t.Fatal("failed to apply ExtraConfig")
  2176  		}
  2177  	}
  2178  
  2179  	Test(func(ctx context.Context, c *vim25.Client) {
  2180  		vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference())
  2181  		applyAndAssertExtraConfigValue(ctx, vm, "world", false)
  2182  		applyAndAssertExtraConfigValue(ctx, vm, "there", false)
  2183  		applyAndAssertExtraConfigValue(ctx, vm, "", true)
  2184  	})
  2185  }
  2186  
  2187  func TestLastModifiedAndChangeVersionAreUpdated(t *testing.T) {
  2188  	Test(func(ctx context.Context, c *vim25.Client) {
  2189  		vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference())
  2190  		var vmMo mo.VirtualMachine
  2191  		if err := vm.Properties(
  2192  			ctx,
  2193  			vm.Reference(),
  2194  			[]string{"config.modified", "config.changeVersion"},
  2195  			&vmMo); err != nil {
  2196  
  2197  			t.Fatalf("failed to fetch initial vm props: %v", err)
  2198  		}
  2199  
  2200  		oldModified := vmMo.Config.Modified
  2201  		oldChangeVersion := vmMo.Config.ChangeVersion
  2202  
  2203  		tsk, err := vm.Reconfigure(ctx, types.VirtualMachineConfigSpec{
  2204  			ExtraConfig: []types.BaseOptionValue{
  2205  				&types.OptionValue{
  2206  					Key:   "hello",
  2207  					Value: "world",
  2208  				},
  2209  			},
  2210  		})
  2211  		if err != nil {
  2212  			t.Fatalf("failed to call reconfigure api: %v", err)
  2213  		}
  2214  		if err := tsk.WaitEx(ctx); err != nil {
  2215  			t.Fatalf("failed to reconfigure: %v", err)
  2216  		}
  2217  
  2218  		if err := vm.Properties(
  2219  			ctx,
  2220  			vm.Reference(),
  2221  			[]string{"config.modified", "config.changeVersion"},
  2222  			&vmMo); err != nil {
  2223  
  2224  			t.Fatalf("failed to fetch vm props after reconfigure: %v", err)
  2225  		}
  2226  
  2227  		newModified := vmMo.Config.Modified
  2228  		newChangeVersion := vmMo.Config.ChangeVersion
  2229  
  2230  		if a, e := newModified, oldModified; a == e {
  2231  			t.Errorf("config.modified was not updated: %v", a)
  2232  		}
  2233  
  2234  		if a, e := newChangeVersion, oldChangeVersion; a == e {
  2235  			t.Errorf("config.changeVersion was not updated: %v", a)
  2236  		}
  2237  	})
  2238  }
  2239  
  2240  func TestUpgradeVm(t *testing.T) {
  2241  
  2242  	const (
  2243  		vmx1  = "vmx-1"
  2244  		vmx2  = "vmx-2"
  2245  		vmx15 = "vmx-15"
  2246  		vmx17 = "vmx-17"
  2247  		vmx19 = "vmx-19"
  2248  		vmx20 = "vmx-20"
  2249  		vmx21 = "vmx-21"
  2250  		vmx22 = "vmx-22"
  2251  	)
  2252  
  2253  	model := VPX()
  2254  	model.Autostart = false
  2255  	model.Cluster = 1
  2256  	model.ClusterHost = 1
  2257  	model.Host = 1
  2258  
  2259  	Test(func(ctx context.Context, c *vim25.Client) {
  2260  		props := []string{"config.version", "summary.config.hwVersion"}
  2261  
  2262  		vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference())
  2263  		vm2 := Map.Get(vm.Reference()).(*VirtualMachine)
  2264  		Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2265  			vm2.Config.Version = vmx15
  2266  		})
  2267  
  2268  		host, err := vm.HostSystem(ctx)
  2269  		if err != nil {
  2270  			t.Fatalf("failed to get vm's host: %v", err)
  2271  		}
  2272  		host2 := Map.Get(host.Reference()).(*HostSystem)
  2273  
  2274  		var eb *EnvironmentBrowser
  2275  		{
  2276  			ref := Map.Get(host.Reference()).(*HostSystem).Parent
  2277  			switch ref.Type {
  2278  			case "ClusterComputeResource":
  2279  				obj := Map.Get(*ref).(*ClusterComputeResource)
  2280  				eb = Map.Get(*obj.EnvironmentBrowser).(*EnvironmentBrowser)
  2281  			case "ComputeResource":
  2282  				obj := Map.Get(*ref).(*mo.ComputeResource)
  2283  				eb = Map.Get(*obj.EnvironmentBrowser).(*EnvironmentBrowser)
  2284  			}
  2285  		}
  2286  
  2287  		baseline := func() {
  2288  			Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2289  				vm2.Config.Version = vmx15
  2290  				vm2.Config.Template = false
  2291  				vm2.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff
  2292  			})
  2293  			Map.WithLock(SpoofContext(), host2.Reference(), func() {
  2294  				host2.Runtime.InMaintenanceMode = false
  2295  			})
  2296  			Map.WithLock(SpoofContext(), eb.Reference(), func() {
  2297  				for i := range eb.QueryConfigOptionDescriptorResponse.Returnval {
  2298  					cod := &eb.QueryConfigOptionDescriptorResponse.Returnval[i]
  2299  					hostFound := false
  2300  					for j := range cod.Host {
  2301  						if cod.Host[j].Value == host2.Reference().Value {
  2302  							hostFound = true
  2303  							break
  2304  						}
  2305  					}
  2306  					if !hostFound {
  2307  						cod.Host = append(cod.Host, host2.Reference())
  2308  					}
  2309  				}
  2310  			})
  2311  		}
  2312  
  2313  		t.Run("InvalidPowerState", func(t *testing.T) {
  2314  			baseline()
  2315  			Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2316  				vm2.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOn
  2317  			})
  2318  
  2319  			tsk, err := vm.UpgradeVM(ctx, vmx15)
  2320  			if err != nil {
  2321  				t.Fatalf("failed to call upgradeVm api: %v", err)
  2322  			}
  2323  			if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2324  				t.Fatal("expected error did not occur")
  2325  			} else if err2, ok := err.(task.Error); !ok {
  2326  				t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2327  			} else if f := err2.Fault(); f == nil {
  2328  				t.Fatal("fault is nil")
  2329  			} else if f2, ok := f.(*types.InvalidPowerStateFault); !ok {
  2330  				t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2331  			} else {
  2332  				if f2.ExistingState != types.VirtualMachinePowerStatePoweredOn {
  2333  					t.Errorf("unexpected existing state: %v", f2.ExistingState)
  2334  				}
  2335  				if f2.RequestedState != types.VirtualMachinePowerStatePoweredOff {
  2336  					t.Errorf("unexpected requested state: %v", f2.RequestedState)
  2337  				}
  2338  			}
  2339  		})
  2340  
  2341  		t.Run("InvalidState", func(t *testing.T) {
  2342  			t.Run("MaintenanceMode", func(t *testing.T) {
  2343  				baseline()
  2344  				Map.WithLock(SpoofContext(), host2.Reference(), func() {
  2345  					host2.Runtime.InMaintenanceMode = true
  2346  				})
  2347  
  2348  				if tsk, err := vm.UpgradeVM(ctx, vmx15); err != nil {
  2349  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2350  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2351  					t.Fatal("expected error did not occur")
  2352  				} else if err, ok := err.(task.Error); !ok {
  2353  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2354  				} else if f := err.Fault(); f == nil {
  2355  					t.Fatal("fault is nil")
  2356  				} else if f2, ok := f.(*types.InvalidState); !ok {
  2357  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2358  				} else if fc := f2.FaultCause; fc == nil {
  2359  					t.Fatal("fault cause is nil")
  2360  				} else if fc.LocalizedMessage != fmt.Sprintf("%s in maintenance mode", host.Reference().Value) {
  2361  					t.Fatalf("unexpected error message: %s", fc.LocalizedMessage)
  2362  				}
  2363  			})
  2364  
  2365  			t.Run("Template", func(t *testing.T) {
  2366  				baseline()
  2367  				Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2368  					vm2.Config.Template = true
  2369  				})
  2370  
  2371  				if tsk, err := vm.UpgradeVM(ctx, vmx15); err != nil {
  2372  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2373  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2374  					t.Fatal("expected error did not occur")
  2375  				} else if err, ok := err.(task.Error); !ok {
  2376  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2377  				} else if f := err.Fault(); f == nil {
  2378  					t.Fatal("fault is nil")
  2379  				} else if f2, ok := f.(*types.InvalidState); !ok {
  2380  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2381  				} else if fc := f2.FaultCause; fc == nil {
  2382  					t.Fatal("fault cause is nil")
  2383  				} else if fc.LocalizedMessage != fmt.Sprintf("%s is template", vm.Reference().Value) {
  2384  					t.Fatalf("unexpected error message: %s", fc.LocalizedMessage)
  2385  				}
  2386  			})
  2387  
  2388  			t.Run("LatestHardwareVersion", func(t *testing.T) {
  2389  				baseline()
  2390  				Map.WithLock(SpoofContext(), vm.Reference(), func() {
  2391  					vm2.Config.Version = vmx21
  2392  				})
  2393  
  2394  				if tsk, err := vm.UpgradeVM(ctx, vmx21); err != nil {
  2395  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2396  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2397  					t.Fatal("expected error did not occur")
  2398  				} else if err, ok := err.(task.Error); !ok {
  2399  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2400  				} else if f := err.Fault(); f == nil {
  2401  					t.Fatal("fault is nil")
  2402  				} else if f2, ok := f.(*types.InvalidState); !ok {
  2403  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2404  				} else if fc := f2.FaultCause; fc == nil {
  2405  					t.Fatal("fault cause is nil")
  2406  				} else if fc.LocalizedMessage != fmt.Sprintf("%s is latest version", vm.Reference().Value) {
  2407  					t.Fatalf("unexpected error message: %s", fc.LocalizedMessage)
  2408  				}
  2409  			})
  2410  		})
  2411  
  2412  		t.Run("NotSupported", func(t *testing.T) {
  2413  			t.Run("AtAll", func(t *testing.T) {
  2414  				baseline()
  2415  
  2416  				if tsk, err := vm.UpgradeVM(ctx, vmx22); err != nil {
  2417  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2418  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2419  					t.Fatal("expected error did not occur")
  2420  				} else if err, ok := err.(task.Error); !ok {
  2421  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2422  				} else if f := err.Fault(); f == nil {
  2423  					t.Fatal("fault is nil")
  2424  				} else if f2, ok := f.(*types.NotSupported); !ok {
  2425  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2426  				} else if fc := f2.FaultCause; fc == nil {
  2427  					t.Fatal("fault cause is nil")
  2428  				} else if fc.LocalizedMessage != "vmx-22 not supported" {
  2429  					t.Fatalf("unexpected error message: %s", fc.LocalizedMessage)
  2430  				}
  2431  			})
  2432  			t.Run("OnVmHost", func(t *testing.T) {
  2433  				baseline()
  2434  				Map.WithLock(SpoofContext(), eb.Reference(), func() {
  2435  					for i := range eb.QueryConfigOptionDescriptorResponse.Returnval {
  2436  						cod := &eb.QueryConfigOptionDescriptorResponse.Returnval[i]
  2437  						if cod.Key == vmx17 {
  2438  							cod.Host = nil
  2439  						}
  2440  					}
  2441  				})
  2442  
  2443  				if tsk, err := vm.UpgradeVM(ctx, vmx17); err != nil {
  2444  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2445  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2446  					t.Fatal("expected error did not occur")
  2447  				} else if err, ok := err.(task.Error); !ok {
  2448  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2449  				} else if f := err.Fault(); f == nil {
  2450  					t.Fatal("fault is nil")
  2451  				} else if f2, ok := f.(*types.NotSupported); !ok {
  2452  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2453  				} else if fc := f2.FaultCause; fc == nil {
  2454  					t.Fatal("fault cause is nil")
  2455  				} else if fc.LocalizedMessage != "vmx-17 not supported" {
  2456  					t.Fatalf("unexpected error message: %s", fc.LocalizedMessage)
  2457  				}
  2458  			})
  2459  		})
  2460  
  2461  		t.Run("AlreadyUpgraded", func(t *testing.T) {
  2462  			t.Run("EqualToTargetVersion", func(t *testing.T) {
  2463  				baseline()
  2464  				if tsk, err := vm.UpgradeVM(ctx, vmx15); err != nil {
  2465  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2466  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2467  					t.Fatal("expected error did not occur")
  2468  				} else if err, ok := err.(task.Error); !ok {
  2469  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2470  				} else if f := err.Fault(); f == nil {
  2471  					t.Fatal("fault is nil")
  2472  				} else if _, ok := f.(*types.AlreadyUpgradedFault); !ok {
  2473  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2474  				}
  2475  			})
  2476  
  2477  			t.Run("GreaterThanTargetVersion", func(t *testing.T) {
  2478  				baseline()
  2479  				Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2480  					vm2.Config.Version = vmx20
  2481  				})
  2482  				if tsk, err := vm.UpgradeVM(ctx, vmx17); err != nil {
  2483  					t.Fatalf("failed to call upgradeVm api: %v", err)
  2484  				} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2485  					t.Fatal("expected error did not occur")
  2486  				} else if err, ok := err.(task.Error); !ok {
  2487  					t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2488  				} else if f := err.Fault(); f == nil {
  2489  					t.Fatal("fault is nil")
  2490  				} else if _, ok := f.(*types.AlreadyUpgradedFault); !ok {
  2491  					t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2492  				}
  2493  			})
  2494  		})
  2495  
  2496  		t.Run("InvalidArgument", func(t *testing.T) {
  2497  			baseline()
  2498  			Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2499  				vm2.Config.Version = vmx1
  2500  			})
  2501  			Map.WithLock(SpoofContext(), eb.Reference(), func() {
  2502  				eb.QueryConfigOptionDescriptorResponse.Returnval = append(
  2503  					eb.QueryConfigOptionDescriptorResponse.Returnval,
  2504  					types.VirtualMachineConfigOptionDescriptor{
  2505  						Key:  vmx2,
  2506  						Host: []types.ManagedObjectReference{host.Reference()},
  2507  					})
  2508  			})
  2509  
  2510  			if tsk, err := vm.UpgradeVM(ctx, vmx2); err != nil {
  2511  				t.Fatalf("failed to call upgradeVm api: %v", err)
  2512  			} else if _, err := tsk.WaitForResultEx(ctx); err == nil {
  2513  				t.Fatal("expected error did not occur")
  2514  			} else if err, ok := err.(task.Error); !ok {
  2515  				t.Fatalf("unexpected error: %[1]T %+[1]v", err)
  2516  			} else if f := err.Fault(); f == nil {
  2517  				t.Fatal("fault is nil")
  2518  			} else if _, ok := f.(*types.InvalidArgument); !ok {
  2519  				t.Fatalf("unexpected fault: %[1]T %+[1]v", f)
  2520  			}
  2521  		})
  2522  
  2523  		t.Run("UpgradeToLatest", func(t *testing.T) {
  2524  			baseline()
  2525  
  2526  			if tsk, err := vm.UpgradeVM(ctx, ""); err != nil {
  2527  				t.Fatalf("failed to call upgradeVm api: %v", err)
  2528  			} else if _, err := tsk.WaitForResultEx(ctx); err != nil {
  2529  				t.Fatalf("failed to upgrade vm: %v", err)
  2530  			}
  2531  			var vmMo mo.VirtualMachine
  2532  			if err := vm.Properties(
  2533  				ctx,
  2534  				vm.Reference(),
  2535  				props,
  2536  				&vmMo); err != nil {
  2537  
  2538  				t.Fatalf("failed to fetch vm props after upgrade: %v", err)
  2539  			}
  2540  			if v := vmMo.Config.Version; v != vmx21 {
  2541  				t.Fatalf("unexpected config.version %v", v)
  2542  			}
  2543  			if v := vmMo.Summary.Config.HwVersion; v != vmx21 {
  2544  				t.Fatalf("unexpected summary.config.hwVersion %v", v)
  2545  			}
  2546  		})
  2547  
  2548  		t.Run("UpgradeFrom15To17", func(t *testing.T) {
  2549  			const targetVersion = vmx17
  2550  			baseline()
  2551  
  2552  			if tsk, err := vm.UpgradeVM(ctx, targetVersion); err != nil {
  2553  				t.Fatalf("failed to call upgradeVm api: %v", err)
  2554  			} else if _, err := tsk.WaitForResultEx(ctx); err != nil {
  2555  				t.Fatalf("failed to upgrade vm: %v", err)
  2556  			}
  2557  			var vmMo mo.VirtualMachine
  2558  			if err := vm.Properties(
  2559  				ctx,
  2560  				vm.Reference(),
  2561  				props,
  2562  				&vmMo); err != nil {
  2563  
  2564  				t.Fatalf("failed to fetch vm props after upgrade: %v", err)
  2565  			}
  2566  			if v := vmMo.Config.Version; v != targetVersion {
  2567  				t.Fatalf("unexpected config.version %v", v)
  2568  			}
  2569  			if v := vmMo.Summary.Config.HwVersion; v != targetVersion {
  2570  				t.Fatalf("unexpected summary.config.hwVersion %v", v)
  2571  			}
  2572  		})
  2573  
  2574  		t.Run("UpgradeFrom17To20", func(t *testing.T) {
  2575  			const targetVersion = vmx20
  2576  			baseline()
  2577  			Map.WithLock(SpoofContext(), vm2.Reference(), func() {
  2578  				vm2.Config.Version = vmx17
  2579  			})
  2580  
  2581  			if tsk, err := vm.UpgradeVM(ctx, targetVersion); err != nil {
  2582  				t.Fatalf("failed to call upgradeVm api: %v", err)
  2583  			} else if _, err := tsk.WaitForResultEx(ctx); err != nil {
  2584  				t.Fatalf("failed to upgrade vm: %v", err)
  2585  			}
  2586  			var vmMo mo.VirtualMachine
  2587  			if err := vm.Properties(
  2588  				ctx,
  2589  				vm.Reference(),
  2590  				props,
  2591  				&vmMo); err != nil {
  2592  
  2593  				t.Fatalf("failed to fetch vm props after upgrade: %v", err)
  2594  			}
  2595  			if v := vmMo.Config.Version; v != targetVersion {
  2596  				t.Fatalf("unexpected config.version %v", v)
  2597  			}
  2598  			if v := vmMo.Summary.Config.HwVersion; v != targetVersion {
  2599  				t.Fatalf("unexpected summary.config.hwVersion %v", v)
  2600  			}
  2601  		})
  2602  
  2603  		t.Run("UpgradeFrom15To17To20", func(t *testing.T) {
  2604  			const (
  2605  				targetVersion1 = vmx17
  2606  				targetVersion2 = vmx20
  2607  			)
  2608  			baseline()
  2609  
  2610  			if tsk, err := vm.UpgradeVM(ctx, targetVersion1); err != nil {
  2611  				t.Fatalf("failed to call upgradeVm api first time: %v", err)
  2612  			} else if _, err := tsk.WaitForResultEx(ctx); err != nil {
  2613  				t.Fatalf("failed to upgrade vm first time: %v", err)
  2614  			}
  2615  			var vmMo mo.VirtualMachine
  2616  			if err := vm.Properties(
  2617  				ctx,
  2618  				vm.Reference(),
  2619  				props,
  2620  				&vmMo); err != nil {
  2621  
  2622  				t.Fatalf("failed to fetch vm props after first upgrade: %v", err)
  2623  			}
  2624  			if v := vmMo.Config.Version; v != targetVersion1 {
  2625  				t.Fatalf("unexpected config.version after first upgrade %v", v)
  2626  			}
  2627  			if v := vmMo.Summary.Config.HwVersion; v != targetVersion1 {
  2628  				t.Fatalf("unexpected summary.config.hwVersion after first upgrade %v", v)
  2629  			}
  2630  
  2631  			if tsk, err := vm.UpgradeVM(ctx, targetVersion2); err != nil {
  2632  				t.Fatalf("failed to call upgradeVm api second time: %v", err)
  2633  			} else if _, err := tsk.WaitForResultEx(ctx); err != nil {
  2634  				t.Fatalf("failed to upgrade vm second time: %v", err)
  2635  			}
  2636  			if err := vm.Properties(
  2637  				ctx,
  2638  				vm.Reference(),
  2639  				props,
  2640  				&vmMo); err != nil {
  2641  
  2642  				t.Fatalf("failed to fetch vm props after second upgrade: %v", err)
  2643  			}
  2644  			if v := vmMo.Config.Version; v != targetVersion2 {
  2645  				t.Fatalf("unexpected config.version after second upgrade %v", v)
  2646  			}
  2647  			if v := vmMo.Summary.Config.HwVersion; v != targetVersion2 {
  2648  				t.Fatalf("unexpected summary.config.hwVersion after second upgrade %v", v)
  2649  			}
  2650  		})
  2651  
  2652  	}, model)
  2653  }