github.com/vmware/govmomi@v0.51.0/simulator/folder_test.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 simulator
     6  
     7  import (
     8  	"context"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/vmware/govmomi"
    14  	"github.com/vmware/govmomi/fault"
    15  	"github.com/vmware/govmomi/find"
    16  	"github.com/vmware/govmomi/object"
    17  	"github.com/vmware/govmomi/simulator/esx"
    18  	"github.com/vmware/govmomi/simulator/vpx"
    19  	"github.com/vmware/govmomi/vim25"
    20  	"github.com/vmware/govmomi/vim25/methods"
    21  	"github.com/vmware/govmomi/vim25/mo"
    22  	"github.com/vmware/govmomi/vim25/soap"
    23  	"github.com/vmware/govmomi/vim25/types"
    24  )
    25  
    26  func addStandaloneHostTask(folder *object.Folder, spec types.HostConnectSpec) (*object.Task, error) {
    27  	// TODO: add govmomi wrapper
    28  	req := types.AddStandaloneHost_Task{
    29  		This:         folder.Reference(),
    30  		Spec:         spec,
    31  		AddConnected: true,
    32  	}
    33  
    34  	res, err := methods.AddStandaloneHost_Task(context.TODO(), folder.Client(), &req)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	task := object.NewTask(folder.Client(), res.Returnval)
    40  	return task, nil
    41  }
    42  
    43  func TestFolderESX(t *testing.T) {
    44  	content := esx.ServiceContent
    45  	s := New(NewServiceInstance(NewContext(), content, esx.RootFolder))
    46  
    47  	ts := s.NewServer()
    48  	defer ts.Close()
    49  
    50  	ctx := context.Background()
    51  	c, err := govmomi.NewClient(ctx, ts.URL, true)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	f := object.NewRootFolder(c.Client)
    57  
    58  	_, err = f.CreateFolder(ctx, "foo")
    59  	if err == nil {
    60  		t.Error("expected error")
    61  	}
    62  
    63  	_, err = f.CreateDatacenter(ctx, "foo")
    64  	if err == nil {
    65  		t.Fatal("expected error")
    66  	}
    67  
    68  	finder := find.NewFinder(c.Client, false)
    69  	dc, err := finder.DatacenterOrDefault(ctx, "")
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  
    74  	folders, err := dc.Folders(ctx)
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	spec := types.HostConnectSpec{}
    80  	_, err = addStandaloneHostTask(folders.HostFolder, spec)
    81  	if err == nil {
    82  		t.Fatal("expected error")
    83  	}
    84  
    85  	_, err = folders.DatastoreFolder.CreateStoragePod(ctx, "pod")
    86  	if err == nil {
    87  		t.Fatal("expected error")
    88  	}
    89  }
    90  
    91  func TestFolderVC(t *testing.T) {
    92  	content := vpx.ServiceContent
    93  	ctx := NewContext()
    94  	s := New(NewServiceInstance(ctx, content, vpx.RootFolder))
    95  
    96  	ts := s.NewServer()
    97  	defer ts.Close()
    98  
    99  	c, err := govmomi.NewClient(ctx, ts.URL, true)
   100  	if err != nil {
   101  		t.Fatal(err)
   102  	}
   103  
   104  	f := object.NewRootFolder(c.Client)
   105  
   106  	ff, err := f.CreateFolder(ctx, "foo")
   107  	if err != nil {
   108  		t.Error(err)
   109  	}
   110  
   111  	_, err = f.CreateFolder(ctx, "foo")
   112  	if err == nil {
   113  		t.Error("expected error")
   114  	}
   115  
   116  	var dup *types.DuplicateName
   117  	_, ok := fault.As(err, &dup)
   118  	if !ok {
   119  		t.Fatal("expected DuplicateName type")
   120  	}
   121  	if dup.Object != ff.Reference() {
   122  		t.Fatal("Duplicate object not matched")
   123  	}
   124  
   125  	dc, err := f.CreateDatacenter(ctx, "bar")
   126  	if err != nil {
   127  		t.Error(err)
   128  	}
   129  
   130  	for _, ref := range []object.Reference{ff, dc} {
   131  		o := ctx.Map.Get(ref.Reference())
   132  		if o == nil {
   133  			t.Fatalf("failed to find %#v", ref)
   134  		}
   135  
   136  		e := o.(mo.Entity).Entity()
   137  		if *e.Parent != f.Reference() {
   138  			t.Fail()
   139  		}
   140  	}
   141  
   142  	dc, err = ff.CreateDatacenter(ctx, "biz")
   143  	if err != nil {
   144  		t.Error(err)
   145  	}
   146  
   147  	folders, err := dc.Folders(ctx)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  
   152  	_, err = folders.VmFolder.CreateStoragePod(ctx, "pod")
   153  	if err == nil {
   154  		t.Error("expected error")
   155  	}
   156  
   157  	pod, err := folders.DatastoreFolder.CreateStoragePod(ctx, "pod")
   158  	if err != nil {
   159  		t.Error(err)
   160  	}
   161  
   162  	_, err = folders.DatastoreFolder.CreateStoragePod(ctx, "pod")
   163  	if err == nil {
   164  		t.Error("expected error")
   165  	}
   166  	_, ok = fault.As(err, &dup)
   167  	if !ok {
   168  		t.Fatal("expected DuplicateName type")
   169  	}
   170  	if dup.Object != pod.Reference() {
   171  		t.Fatal("Duplicate object not matched")
   172  	}
   173  
   174  	tests := []struct {
   175  		name  string
   176  		state types.TaskInfoState
   177  	}{
   178  		{"", types.TaskInfoStateError},
   179  		{"foo.local", types.TaskInfoStateSuccess},
   180  	}
   181  
   182  	for _, test := range tests {
   183  		spec := types.HostConnectSpec{
   184  			HostName: test.name,
   185  		}
   186  
   187  		task, err := addStandaloneHostTask(folders.HostFolder, spec)
   188  		if err != nil {
   189  			t.Fatal(err)
   190  		}
   191  
   192  		res, err := task.WaitForResult(ctx, nil)
   193  		if test.state == types.TaskInfoStateError {
   194  			if err == nil {
   195  				t.Error("expected error")
   196  			}
   197  
   198  			if res.Result != nil {
   199  				t.Error("expected nil")
   200  			}
   201  		} else {
   202  			if err != nil {
   203  				t.Fatal(err)
   204  			}
   205  
   206  			ref, ok := res.Result.(types.ManagedObjectReference)
   207  			if !ok {
   208  				t.Errorf("expected moref, got type=%T", res.Result)
   209  			}
   210  			host := ctx.Map.Get(ref).(*HostSystem)
   211  			if host.Name != test.name {
   212  				t.Fail()
   213  			}
   214  
   215  			if ref == esx.HostSystem.Self {
   216  				t.Error("expected new host Self reference")
   217  			}
   218  			if *host.Summary.Host == esx.HostSystem.Self {
   219  				t.Error("expected new host summary Self reference")
   220  			}
   221  
   222  			pool := ctx.Map.Get(*host.Parent).(*mo.ComputeResource).ResourcePool
   223  			if *pool == esx.ResourcePool.Self {
   224  				t.Error("expected new pool Self reference")
   225  			}
   226  		}
   227  
   228  		if res.State != test.state {
   229  			t.Fatalf("%s", res.State)
   230  		}
   231  	}
   232  }
   233  
   234  func TestFolderSpecialCharaters(t *testing.T) {
   235  	content := vpx.ServiceContent
   236  	ctx := NewContext()
   237  	s := New(NewServiceInstance(ctx, content, vpx.RootFolder))
   238  
   239  	ts := s.NewServer()
   240  	defer ts.Close()
   241  
   242  	c, err := govmomi.NewClient(ctx, ts.URL, true)
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  
   247  	f := object.NewRootFolder(c.Client)
   248  
   249  	tests := []struct {
   250  		name     string
   251  		expected string
   252  	}{
   253  		{`/`, `%2f`},
   254  		{`\`, `%5c`},
   255  		{`%`, `%25`},
   256  		// multiple special characters
   257  		{`%%`, `%25%25`},
   258  	}
   259  
   260  	for _, test := range tests {
   261  		ff, err := f.CreateFolder(ctx, test.name)
   262  		if err != nil {
   263  			t.Fatal(err)
   264  		}
   265  
   266  		o := ctx.Map.Get(ff.Reference())
   267  		if o == nil {
   268  			t.Fatalf("failed to find %#v", ff)
   269  		}
   270  
   271  		e := o.(mo.Entity).Entity()
   272  		if e.Name != test.expected {
   273  			t.Errorf("expected %s, got %s", test.expected, e.Name)
   274  		}
   275  	}
   276  }
   277  
   278  func TestFolderFaults(t *testing.T) {
   279  	f := Folder{}
   280  	f.ChildType = []string{"VirtualMachine"}
   281  
   282  	if f.CreateFolder(nil, nil).Fault() == nil {
   283  		t.Error("expected fault")
   284  	}
   285  
   286  	if f.CreateDatacenter(nil, nil).Fault() == nil {
   287  		t.Error("expected fault")
   288  	}
   289  }
   290  
   291  func TestRegisterVm(t *testing.T) {
   292  	for i, model := range []*Model{ESX(), VPX()} {
   293  		match := "*"
   294  		if i == 1 {
   295  			model.App = 1
   296  			match = "*APP*"
   297  		}
   298  		defer model.Remove()
   299  		err := model.Create()
   300  		if err != nil {
   301  			t.Fatal(err)
   302  		}
   303  
   304  		s := model.Service.NewServer()
   305  		defer s.Close()
   306  
   307  		ctx := model.Service.Context
   308  
   309  		c, err := govmomi.NewClient(ctx, s.URL, true)
   310  		if err != nil {
   311  			t.Fatal(err)
   312  		}
   313  
   314  		finder := find.NewFinder(c.Client, false)
   315  		dc, err := finder.DefaultDatacenter(ctx)
   316  		if err != nil {
   317  			t.Fatal(err)
   318  		}
   319  
   320  		finder.SetDatacenter(dc)
   321  
   322  		folders, err := dc.Folders(ctx)
   323  		if err != nil {
   324  			t.Fatal(err)
   325  		}
   326  
   327  		vmFolder := folders.VmFolder
   328  
   329  		vms, err := finder.VirtualMachineList(ctx, match)
   330  		if err != nil {
   331  			t.Fatal(err)
   332  		}
   333  
   334  		vm := ctx.Map.Get(vms[0].Reference()).(*VirtualMachine)
   335  
   336  		req := types.RegisterVM_Task{
   337  			This:       vmFolder.Reference(),
   338  			AsTemplate: true,
   339  		}
   340  
   341  		steps := []struct {
   342  			e any
   343  			f func()
   344  		}{
   345  			{
   346  				new(types.InvalidArgument), func() { req.AsTemplate = false },
   347  			},
   348  			{
   349  				new(types.InvalidArgument), func() { req.Pool = vm.ResourcePool },
   350  			},
   351  			{
   352  				new(types.InvalidArgument), func() { req.Path = "enoent" },
   353  			},
   354  			{
   355  				new(types.InvalidDatastorePath), func() { req.Path = vm.Config.Files.VmPathName + "-enoent" },
   356  			},
   357  			{
   358  				new(types.NotFound), func() { req.Path = vm.Config.Files.VmPathName },
   359  			},
   360  			{
   361  				new(types.AlreadyExists), func() { ctx.Map.Remove(ctx, vm.Reference()) },
   362  			},
   363  			{
   364  				nil, func() {},
   365  			},
   366  		}
   367  
   368  		for _, step := range steps {
   369  			res, err := methods.RegisterVM_Task(ctx, c.Client, &req)
   370  			if err != nil {
   371  				t.Fatal(err)
   372  			}
   373  
   374  			ct := object.NewTask(c.Client, res.Returnval)
   375  			_ = ct.Wait(ctx)
   376  
   377  			rt := ctx.Map.Get(res.Returnval).(*Task)
   378  
   379  			if step.e != nil {
   380  				fault := rt.Info.Error.Fault
   381  				if reflect.TypeOf(fault) != reflect.TypeOf(step.e) {
   382  					t.Errorf("%T != %T", fault, step.e)
   383  				}
   384  			} else {
   385  				if rt.Info.Error != nil {
   386  					t.Errorf("unexpected error: %#v", rt.Info.Error)
   387  				}
   388  			}
   389  
   390  			step.f()
   391  		}
   392  
   393  		nvm, err := finder.VirtualMachine(ctx, vm.Name)
   394  		if err != nil {
   395  			t.Fatal(err)
   396  		}
   397  
   398  		if nvm.Reference() == vm.Reference() {
   399  			t.Error("expected new moref")
   400  		}
   401  
   402  		onTask, _ := nvm.PowerOn(ctx)
   403  		_ = onTask.Wait(ctx)
   404  
   405  		steps = []struct {
   406  			e any
   407  			f func()
   408  		}{
   409  			{
   410  				types.InvalidPowerState{}, func() { offTask, _ := nvm.PowerOff(ctx); _ = offTask.Wait(ctx) },
   411  			},
   412  			{
   413  				nil, func() {},
   414  			},
   415  			{
   416  				types.ManagedObjectNotFound{}, func() {},
   417  			},
   418  		}
   419  
   420  		for _, step := range steps {
   421  			err = nvm.Unregister(ctx)
   422  
   423  			if step.e != nil {
   424  				fault := soap.ToSoapFault(err).VimFault()
   425  				if reflect.TypeOf(fault) != reflect.TypeOf(step.e) {
   426  					t.Errorf("%T != %T", fault, step.e)
   427  				}
   428  			} else {
   429  				if err != nil {
   430  					t.Errorf("unexpected error: %#v", err)
   431  				}
   432  			}
   433  
   434  			step.f()
   435  		}
   436  	}
   437  }
   438  
   439  func TestFolderMoveInto(t *testing.T) {
   440  	ctx := context.Background()
   441  	model := VPX()
   442  	defer model.Remove()
   443  	err := model.Create()
   444  	if err != nil {
   445  		t.Fatal(err)
   446  	}
   447  
   448  	s := model.Service.NewServer()
   449  	defer s.Close()
   450  
   451  	c, err := govmomi.NewClient(ctx, s.URL, true)
   452  	if err != nil {
   453  		t.Fatal(err)
   454  	}
   455  
   456  	finder := find.NewFinder(c.Client, false)
   457  
   458  	dc, err := finder.DefaultDatacenter(ctx)
   459  	if err != nil {
   460  		t.Fatal(err)
   461  	}
   462  
   463  	finder.SetDatacenter(dc)
   464  
   465  	folders, err := dc.Folders(ctx)
   466  	if err != nil {
   467  		t.Fatal(err)
   468  	}
   469  
   470  	ds, err := finder.DefaultDatastore(ctx)
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  
   475  	// Move Datastore into a vm folder should fail
   476  	task, err := folders.VmFolder.MoveInto(ctx, []types.ManagedObjectReference{ds.Reference()})
   477  	if err != nil {
   478  		t.Fatal(err)
   479  	}
   480  
   481  	err = task.Wait(ctx)
   482  	if err == nil {
   483  		t.Errorf("expected error")
   484  	}
   485  
   486  	// Move Datacenter into a sub folder should pass
   487  	f, err := object.NewRootFolder(c.Client).CreateFolder(ctx, "foo")
   488  	if err != nil {
   489  		t.Error(err)
   490  	}
   491  
   492  	task, _ = f.MoveInto(ctx, []types.ManagedObjectReference{dc.Reference()})
   493  	err = task.Wait(ctx)
   494  	if err != nil {
   495  		t.Error(err)
   496  	}
   497  
   498  	pod, err := folders.DatastoreFolder.CreateStoragePod(ctx, "pod")
   499  	if err != nil {
   500  		t.Error(err)
   501  	}
   502  
   503  	// Moving any type other than Datastore into a StoragePod should fail
   504  	task, _ = pod.MoveInto(ctx, []types.ManagedObjectReference{dc.Reference()})
   505  	err = task.Wait(ctx)
   506  	if err == nil {
   507  		t.Error("expected error")
   508  	}
   509  
   510  	// Move DS into a StoragePod
   511  	task, _ = pod.MoveInto(ctx, []types.ManagedObjectReference{ds.Reference()})
   512  	err = task.Wait(ctx)
   513  	if err != nil {
   514  		t.Error(err)
   515  	}
   516  }
   517  
   518  func TestFolderCreateDVS(t *testing.T) {
   519  	ctx := context.Background()
   520  	model := VPX()
   521  	defer model.Remove()
   522  	err := model.Create()
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  
   527  	s := model.Service.NewServer()
   528  	defer s.Close()
   529  
   530  	c, err := govmomi.NewClient(ctx, s.URL, true)
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  
   535  	finder := find.NewFinder(c.Client, false)
   536  
   537  	dc, err := finder.DefaultDatacenter(ctx)
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  
   542  	finder.SetDatacenter(dc)
   543  
   544  	folders, err := dc.Folders(ctx)
   545  	if err != nil {
   546  		t.Fatal(err)
   547  	}
   548  
   549  	var spec types.DVSCreateSpec
   550  	spec.ConfigSpec = &types.VMwareDVSConfigSpec{}
   551  	spec.ConfigSpec.GetDVSConfigSpec().Name = "foo"
   552  
   553  	task, err := folders.NetworkFolder.CreateDVS(ctx, spec)
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	err = task.Wait(ctx)
   559  	if err != nil {
   560  		t.Error(err)
   561  	}
   562  
   563  	net, err := finder.Network(ctx, "foo")
   564  	if err != nil {
   565  		t.Error(err)
   566  	}
   567  
   568  	dvs, ok := net.(*object.DistributedVirtualSwitch)
   569  	if !ok {
   570  		t.Fatalf("%T is not of type %T", net, dvs)
   571  	}
   572  
   573  	task, err = folders.NetworkFolder.CreateDVS(ctx, spec)
   574  	if err != nil {
   575  		t.Fatal(err)
   576  	}
   577  
   578  	err = task.Wait(ctx)
   579  	if err == nil {
   580  		t.Error("expected error")
   581  	}
   582  
   583  	pspec := types.DVPortgroupConfigSpec{Name: "xnet"}
   584  	task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{pspec})
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  
   589  	err = task.Wait(ctx)
   590  	if err != nil {
   591  		t.Error(err)
   592  	}
   593  
   594  	net, err = finder.Network(ctx, "xnet")
   595  	if err != nil {
   596  		t.Error(err)
   597  	}
   598  
   599  	pg, ok := net.(*object.DistributedVirtualPortgroup)
   600  	if !ok {
   601  		t.Fatalf("%T is not of type %T", net, pg)
   602  	}
   603  
   604  	backing, err := net.EthernetCardBackingInfo(ctx)
   605  	if err != nil {
   606  		t.Fatal(err)
   607  	}
   608  
   609  	info, ok := backing.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo)
   610  	if ok {
   611  		if info.Port.SwitchUuid == "" || info.Port.PortgroupKey == "" {
   612  			t.Errorf("invalid port: %#v", info.Port)
   613  		}
   614  	} else {
   615  		t.Fatalf("%T is not of type %T", net, info)
   616  	}
   617  
   618  	task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{pspec})
   619  	if err != nil {
   620  		t.Fatal(err)
   621  	}
   622  
   623  	err = task.Wait(ctx)
   624  	if err == nil {
   625  		t.Error("expected error")
   626  	}
   627  }
   628  
   629  func TestPlaceVmsXClusterCreateAndPowerOn(t *testing.T) {
   630  	vpx := VPX()
   631  	vpx.Cluster = 3
   632  
   633  	Test(func(ctx context.Context, c *vim25.Client) {
   634  		finder := find.NewFinder(c, false)
   635  
   636  		spec := types.PlaceVmsXClusterSpec{}
   637  
   638  		pools, err := finder.ResourcePoolList(ctx, "/DC0/host/DC0_C*/*")
   639  		if err != nil {
   640  			t.Fatal(err)
   641  		}
   642  
   643  		for _, pool := range pools {
   644  			spec.ResourcePools = append(spec.ResourcePools, pool.Reference())
   645  		}
   646  
   647  		spec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{
   648  			ConfigSpec: types.VirtualMachineConfigSpec{
   649  				Name: "test-vm",
   650  			},
   651  		}}
   652  
   653  		folder := object.NewRootFolder(c)
   654  		res, err := folder.PlaceVmsXCluster(ctx, spec)
   655  		if err != nil {
   656  			t.Fatal(err)
   657  		}
   658  
   659  		if len(res.PlacementInfos) != len(spec.VmPlacementSpecs) {
   660  			t.Errorf("%d PlacementInfos vs %d VmPlacementSpecs", len(res.PlacementInfos), len(spec.VmPlacementSpecs))
   661  		}
   662  	}, vpx)
   663  }
   664  
   665  func TestPlaceVmsXClusterRelocate(t *testing.T) {
   666  	vpx := VPX()
   667  	vpx.Cluster = 3
   668  
   669  	Test(func(ctx context.Context, c *vim25.Client) {
   670  		finder := find.NewFinder(c, true)
   671  		datacenter, err := finder.DefaultDatacenter(ctx)
   672  		if err != nil {
   673  			t.Fatalf("failed to get default datacenter: %v", err)
   674  		}
   675  		finder.SetDatacenter(datacenter)
   676  
   677  		pools, err := finder.ResourcePoolList(ctx, "/DC0/host/DC0_C*/*")
   678  		if err != nil {
   679  			t.Fatal(err)
   680  		}
   681  
   682  		var poolMoRefs []types.ManagedObjectReference
   683  		for _, pool := range pools {
   684  			poolMoRefs = append(poolMoRefs, pool.Reference())
   685  		}
   686  
   687  		vmMoRef := Map(ctx).Any("VirtualMachine").(*VirtualMachine).Reference()
   688  
   689  		cfgSpec := types.VirtualMachineConfigSpec{}
   690  
   691  		tests := []struct {
   692  			name         string
   693  			poolMoRefs   []types.ManagedObjectReference
   694  			configSpec   types.VirtualMachineConfigSpec
   695  			relocateSpec *types.VirtualMachineRelocateSpec
   696  			vmMoRef      *types.ManagedObjectReference
   697  			expectedErr  string
   698  		}{
   699  			{
   700  				"relocate without any resource pools",
   701  				nil,
   702  				cfgSpec,
   703  				&types.VirtualMachineRelocateSpec{},
   704  				&vmMoRef,
   705  				"InvalidArgument",
   706  			},
   707  			{
   708  				"relocate without a relocate spec",
   709  				poolMoRefs,
   710  				cfgSpec,
   711  				nil,
   712  				&vmMoRef,
   713  				"InvalidArgument",
   714  			},
   715  			{
   716  				"relocate without a vm in the placement spec",
   717  				poolMoRefs,
   718  				cfgSpec,
   719  				&types.VirtualMachineRelocateSpec{},
   720  				nil,
   721  				"InvalidArgument",
   722  			},
   723  			{
   724  				"relocate with a non-existing vm in the placement spec",
   725  				poolMoRefs,
   726  				cfgSpec,
   727  				&types.VirtualMachineRelocateSpec{},
   728  				&types.ManagedObjectReference{
   729  					Type:  "VirtualMachine",
   730  					Value: "fake-vm-999",
   731  				},
   732  				"InvalidArgument",
   733  			},
   734  			{
   735  				"relocate with an empty relocate spec",
   736  				poolMoRefs,
   737  				cfgSpec,
   738  				&types.VirtualMachineRelocateSpec{},
   739  				&vmMoRef,
   740  				"",
   741  			},
   742  		}
   743  
   744  		for testNo, test := range tests {
   745  			test := test // assign to local var since loop var is reused
   746  
   747  			truebool := true
   748  
   749  			placeVmsXClusterSpec := types.PlaceVmsXClusterSpec{
   750  				ResourcePools:           test.poolMoRefs,
   751  				PlacementType:           string(types.PlaceVmsXClusterSpecPlacementTypeRelocate),
   752  				HostRecommRequired:      &truebool,
   753  				DatastoreRecommRequired: &truebool,
   754  			}
   755  
   756  			placeVmsXClusterSpec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{
   757  				ConfigSpec:   test.configSpec,
   758  				Vm:           test.vmMoRef,
   759  				RelocateSpec: test.relocateSpec,
   760  			}}
   761  
   762  			folder := object.NewRootFolder(c)
   763  			res, err := folder.PlaceVmsXCluster(ctx, placeVmsXClusterSpec)
   764  
   765  			if err == nil && test.expectedErr != "" {
   766  				t.Fatalf("Test %v: expected error %q, received nil", testNo, test.expectedErr)
   767  			} else if err != nil &&
   768  				(test.expectedErr == "" || !strings.Contains(err.Error(), test.expectedErr)) {
   769  				t.Fatalf("Test %v: expected error %q, received %v", testNo, test.expectedErr, err)
   770  			}
   771  
   772  			if err == nil {
   773  				if len(res.PlacementInfos) != len(placeVmsXClusterSpec.VmPlacementSpecs) {
   774  					t.Errorf("Test %v: %d PlacementInfos vs %d VmPlacementSpecs", testNo, len(res.PlacementInfos), len(placeVmsXClusterSpec.VmPlacementSpecs))
   775  				}
   776  
   777  				for _, pinfo := range res.PlacementInfos {
   778  					for _, action := range pinfo.Recommendation.Action {
   779  						if relocateAction, ok := action.(*types.ClusterClusterRelocatePlacementAction); ok {
   780  							if relocateAction.TargetHost == nil {
   781  								t.Errorf("Test %v: received nil host recommendation", testNo)
   782  							}
   783  						} else {
   784  							t.Errorf("Test %v: received wrong action type in recommendation", testNo)
   785  						}
   786  					}
   787  				}
   788  			}
   789  		}
   790  	}, vpx)
   791  }
   792  
   793  func TestPlaceVmsXClusterReconfigure(t *testing.T) {
   794  	vpx := VPX()
   795  	// All hosts are cluster hosts
   796  	vpx.Host = 0
   797  
   798  	Test(func(ctx context.Context, c *vim25.Client) {
   799  		finder := find.NewFinder(c, true)
   800  		datacenter, err := finder.DefaultDatacenter(ctx)
   801  		if err != nil {
   802  			t.Fatalf("failed to get default datacenter: %v", err)
   803  		}
   804  		finder.SetDatacenter(datacenter)
   805  
   806  		vm := Map(ctx).Any("VirtualMachine").(*VirtualMachine)
   807  		host := Map(ctx).Get(vm.Runtime.Host.Reference()).(*HostSystem)
   808  		cluster := Map(ctx).Get(*host.Parent).(*ClusterComputeResource)
   809  		pool := Map(ctx).Get(*cluster.ResourcePool).(*ResourcePool)
   810  
   811  		var poolMoRefs []types.ManagedObjectReference
   812  		poolMoRefs = append(poolMoRefs, pool.Reference())
   813  
   814  		cfgSpec := types.VirtualMachineConfigSpec{}
   815  
   816  		tests := []struct {
   817  			name         string
   818  			poolMoRefs   []types.ManagedObjectReference
   819  			configSpec   types.VirtualMachineConfigSpec
   820  			relocateSpec *types.VirtualMachineRelocateSpec
   821  			vmMoRef      *types.ManagedObjectReference
   822  			expectedErr  string
   823  		}{
   824  			{
   825  				"reconfigure without any resource pools",
   826  				nil,
   827  				cfgSpec,
   828  				nil,
   829  				&vm.Self,
   830  				"InvalidArgument",
   831  			},
   832  			{
   833  				"reconfigure with a relocate spec",
   834  				poolMoRefs,
   835  				cfgSpec,
   836  				&types.VirtualMachineRelocateSpec{},
   837  				&vm.Self,
   838  				"InvalidArgument",
   839  			},
   840  			{
   841  				"reconfigure without a vm in the placement spec",
   842  				poolMoRefs,
   843  				cfgSpec,
   844  				nil,
   845  				nil,
   846  				"InvalidArgument",
   847  			},
   848  			{
   849  				"reconfigure with a non-existing vm in the placement spec",
   850  				poolMoRefs,
   851  				cfgSpec,
   852  				nil,
   853  				&types.ManagedObjectReference{
   854  					Type:  "VirtualMachine",
   855  					Value: "fake-vm-999",
   856  				},
   857  				"InvalidArgument",
   858  			},
   859  			{
   860  				"reconfigure with an empty config spec",
   861  				poolMoRefs,
   862  				cfgSpec,
   863  				nil,
   864  				&vm.Self,
   865  				"",
   866  			},
   867  		}
   868  
   869  		for testNo, test := range tests {
   870  			test := test // assign to local var since loop var is reused
   871  
   872  			placeVmsXClusterSpec := types.PlaceVmsXClusterSpec{
   873  				ResourcePools:           test.poolMoRefs,
   874  				PlacementType:           string(types.PlaceVmsXClusterSpecPlacementTypeReconfigure),
   875  				HostRecommRequired:      types.NewBool(true),
   876  				DatastoreRecommRequired: types.NewBool(true),
   877  			}
   878  
   879  			placeVmsXClusterSpec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{
   880  				ConfigSpec:   test.configSpec,
   881  				Vm:           test.vmMoRef,
   882  				RelocateSpec: test.relocateSpec,
   883  			}}
   884  
   885  			folder := object.NewRootFolder(c)
   886  			res, err := folder.PlaceVmsXCluster(ctx, placeVmsXClusterSpec)
   887  
   888  			if err == nil && test.expectedErr != "" {
   889  				t.Fatalf("Test %v: expected error %q, received nil", testNo, test.expectedErr)
   890  			} else if err != nil &&
   891  				(test.expectedErr == "" || !strings.Contains(err.Error(), test.expectedErr)) {
   892  				t.Fatalf("Test %v: expected error %q, received %v", testNo, test.expectedErr, err)
   893  			}
   894  
   895  			if err == nil {
   896  				if len(res.PlacementInfos) != len(placeVmsXClusterSpec.VmPlacementSpecs) {
   897  					t.Errorf("%d PlacementInfos vs %d VmPlacementSpecs", len(res.PlacementInfos), len(placeVmsXClusterSpec.VmPlacementSpecs))
   898  				}
   899  
   900  				for _, pinfo := range res.PlacementInfos {
   901  					for _, action := range pinfo.Recommendation.Action {
   902  						if reconfigureAction, ok := action.(*types.ClusterClusterReconfigurePlacementAction); ok {
   903  							if reconfigureAction.TargetHost == nil {
   904  								t.Errorf("Test %v: received nil host recommendation", testNo)
   905  							}
   906  						} else {
   907  							t.Errorf("Test %v: received wrong action type in recommendation", testNo)
   908  						}
   909  					}
   910  				}
   911  			}
   912  		}
   913  	}, vpx)
   914  }