github.com/vmware/govmomi@v0.51.0/simulator/property_collector_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  	"fmt"
    10  	"log"
    11  	"reflect"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/vmware/govmomi"
    17  	"github.com/vmware/govmomi/find"
    18  	"github.com/vmware/govmomi/object"
    19  	"github.com/vmware/govmomi/property"
    20  	"github.com/vmware/govmomi/simulator/esx"
    21  	"github.com/vmware/govmomi/simulator/internal"
    22  	"github.com/vmware/govmomi/simulator/vpx"
    23  	"github.com/vmware/govmomi/view"
    24  	"github.com/vmware/govmomi/vim25"
    25  	"github.com/vmware/govmomi/vim25/methods"
    26  	"github.com/vmware/govmomi/vim25/mo"
    27  	"github.com/vmware/govmomi/vim25/soap"
    28  	"github.com/vmware/govmomi/vim25/types"
    29  )
    30  
    31  func TestRetrieveProperties(t *testing.T) {
    32  	configs := []struct {
    33  		folder  mo.Folder
    34  		content types.ServiceContent
    35  		dc      *types.ManagedObjectReference
    36  	}{
    37  		{esx.RootFolder, esx.ServiceContent, &esx.Datacenter.Self},
    38  		{vpx.RootFolder, vpx.ServiceContent, nil},
    39  	}
    40  
    41  	for _, config := range configs {
    42  		ctx := NewContext()
    43  		s := New(NewServiceInstance(ctx, config.content, config.folder))
    44  
    45  		ts := s.NewServer()
    46  		defer ts.Close()
    47  
    48  		client, err := govmomi.NewClient(ctx, ts.URL, true)
    49  		if err != nil {
    50  			t.Fatal(err)
    51  		}
    52  
    53  		if config.dc == nil {
    54  			dc, cerr := object.NewRootFolder(client.Client).CreateDatacenter(ctx, "dc1")
    55  			if cerr != nil {
    56  				t.Fatal(cerr)
    57  			}
    58  			ref := dc.Reference()
    59  			config.dc = &ref
    60  		}
    61  
    62  		// Retrieve a specific property
    63  		f := mo.Folder{}
    64  		err = client.RetrieveOne(ctx, config.content.RootFolder, []string{"name"}, &f)
    65  		if err != nil {
    66  			t.Fatal(err)
    67  		}
    68  
    69  		if f.Name != config.folder.Name {
    70  			t.Fail()
    71  		}
    72  
    73  		// Retrieve all properties
    74  		f = mo.Folder{}
    75  		err = client.RetrieveOne(ctx, config.content.RootFolder, nil, &f)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  
    80  		if f.Name != config.folder.Name {
    81  			t.Fatalf("'%s' vs '%s'", f.Name, config.folder.Name)
    82  		}
    83  
    84  		// Retrieve an ArrayOf property
    85  		f = mo.Folder{}
    86  		err = client.RetrieveOne(ctx, config.content.RootFolder, []string{"childEntity"}, &f)
    87  		if err != nil {
    88  			t.Fatal(err)
    89  		}
    90  
    91  		if len(f.ChildEntity) != 1 {
    92  			t.Fail()
    93  		}
    94  
    95  		es, err := mo.Ancestors(ctx, client.Client, config.content.PropertyCollector, config.content.RootFolder)
    96  		if err != nil {
    97  			t.Fatal(err)
    98  		}
    99  
   100  		if len(es) != 1 {
   101  			t.Fail()
   102  		}
   103  
   104  		finder := find.NewFinder(client.Client, false)
   105  		dc, err := finder.DatacenterOrDefault(ctx, "")
   106  		if err != nil {
   107  			t.Fatal(err)
   108  		}
   109  
   110  		if dc.Reference() != *config.dc {
   111  			t.Fail()
   112  		}
   113  
   114  		finder.SetDatacenter(dc)
   115  
   116  		es, err = mo.Ancestors(ctx, client.Client, config.content.PropertyCollector, dc.Reference())
   117  		if err != nil {
   118  			t.Fatal(err)
   119  		}
   120  
   121  		expect := map[string]types.ManagedObjectReference{
   122  			"Folder":     config.folder.Reference(),
   123  			"Datacenter": dc.Reference(),
   124  		}
   125  
   126  		if len(es) != len(expect) {
   127  			t.Fail()
   128  		}
   129  
   130  		for _, e := range es {
   131  			ref := e.Reference()
   132  			if r, ok := expect[ref.Type]; ok {
   133  				if r != ref {
   134  					t.Errorf("%#v vs %#v", r, ref)
   135  				}
   136  			} else {
   137  				t.Errorf("unexpected object %#v", e.Reference())
   138  			}
   139  		}
   140  
   141  		// finder tests
   142  		ls, err := finder.ManagedObjectListChildren(ctx, ".")
   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  		// Validated name properties are recursively retrieved for the datacenter and its folder children
   153  		ipaths := []string{
   154  			folders.VmFolder.InventoryPath,
   155  			folders.HostFolder.InventoryPath,
   156  			folders.DatastoreFolder.InventoryPath,
   157  			folders.NetworkFolder.InventoryPath,
   158  		}
   159  
   160  		var lpaths []string
   161  		for _, p := range ls {
   162  			lpaths = append(lpaths, p.Path)
   163  		}
   164  
   165  		if !reflect.DeepEqual(ipaths, lpaths) {
   166  			t.Errorf("%#v != %#v\n", ipaths, lpaths)
   167  		}
   168  
   169  		// We have no VMs, expect NotFoundError
   170  		_, err = finder.VirtualMachineList(ctx, "*")
   171  		if err == nil {
   172  			t.Error("expected error")
   173  		} else {
   174  			if _, ok := err.(*find.NotFoundError); !ok {
   175  				t.Error(err)
   176  			}
   177  		}
   178  
   179  		// Retrieve a missing property
   180  		mdc := mo.Datacenter{}
   181  		err = client.RetrieveOne(ctx, dc.Reference(), []string{"enoent"}, &mdc)
   182  		if err == nil {
   183  			t.Error("expected error")
   184  		} else {
   185  			switch fault := soap.ToVimFault(err).(type) {
   186  			case *types.InvalidProperty:
   187  				// ok
   188  			default:
   189  				t.Errorf("unexpected fault: %#v", fault)
   190  			}
   191  		}
   192  
   193  		// Retrieve a nested property
   194  		ctx.Map.Get(dc.Reference()).(*Datacenter).Configuration.DefaultHardwareVersionKey = "foo"
   195  		mdc = mo.Datacenter{}
   196  		err = client.RetrieveOne(ctx, dc.Reference(), []string{"configuration.defaultHardwareVersionKey"}, &mdc)
   197  		if err != nil {
   198  			t.Fatal(err)
   199  		}
   200  		if mdc.Configuration.DefaultHardwareVersionKey != "foo" {
   201  			t.Fail()
   202  		}
   203  
   204  		// Retrieve a missing nested property
   205  		mdc = mo.Datacenter{}
   206  		err = client.RetrieveOne(ctx, dc.Reference(), []string{"configuration.enoent"}, &mdc)
   207  		if err == nil {
   208  			t.Error("expected error")
   209  		} else {
   210  			switch fault := soap.ToVimFault(err).(type) {
   211  			case *types.InvalidProperty:
   212  				// ok
   213  			default:
   214  				t.Errorf("unexpected fault: %#v", fault)
   215  			}
   216  		}
   217  
   218  		// Retrieve an empty property
   219  		err = client.RetrieveOne(ctx, dc.Reference(), []string{""}, &mdc)
   220  		if err != nil {
   221  			t.Error(err)
   222  		}
   223  
   224  		// Expect ManagedObjectNotFoundError
   225  		ctx.Map.Remove(ctx, dc.Reference())
   226  		err = client.RetrieveOne(ctx, dc.Reference(), []string{"name"}, &mdc)
   227  		if err == nil {
   228  			t.Fatal("expected error")
   229  		}
   230  	}
   231  }
   232  
   233  func TestWaitForUpdates(t *testing.T) {
   234  	folder := esx.RootFolder
   235  	ctx := NewContext()
   236  	s := New(NewServiceInstance(ctx, esx.ServiceContent, folder))
   237  
   238  	ts := s.NewServer()
   239  	defer ts.Close()
   240  
   241  	c, err := govmomi.NewClient(ctx, ts.URL, true)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  
   246  	updates := make(chan bool)
   247  	cb := func(once bool) func([]types.PropertyChange) bool {
   248  		return func(pc []types.PropertyChange) bool {
   249  			if len(pc) != 1 {
   250  				t.Fail()
   251  			}
   252  
   253  			c := pc[0]
   254  			if c.Op != types.PropertyChangeOpAssign {
   255  				t.Fail()
   256  			}
   257  			if c.Name != "name" {
   258  				t.Fail()
   259  			}
   260  			if c.Val.(string) != folder.Name {
   261  				t.Fail()
   262  			}
   263  
   264  			if once == false {
   265  				updates <- true
   266  			}
   267  			return once
   268  		}
   269  	}
   270  
   271  	pc := property.DefaultCollector(c.Client)
   272  	props := []string{"name"}
   273  
   274  	err = property.Wait(ctx, pc, folder.Reference(), props, cb(true))
   275  	if err != nil {
   276  		t.Error(err)
   277  	}
   278  
   279  	wctx, cancel := context.WithCancel(ctx)
   280  	var wg sync.WaitGroup
   281  	wg.Add(1)
   282  	go func() {
   283  		defer wg.Done()
   284  		_ = property.Wait(wctx, pc, folder.Reference(), props, cb(false))
   285  	}()
   286  	<-updates
   287  	cancel()
   288  	wg.Wait()
   289  
   290  	// test object not found
   291  	ctx.Map.Remove(NewContext(), folder.Reference())
   292  
   293  	err = property.Wait(ctx, pc, folder.Reference(), props, cb(true))
   294  	if err == nil {
   295  		t.Error("expected error")
   296  	}
   297  
   298  	// test CancelWaitForUpdates
   299  	p, err := pc.Create(ctx)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	// test the deprecated WaitForUpdates methods
   305  	_, err = methods.WaitForUpdates(ctx, c.Client, &types.WaitForUpdates{This: p.Reference()})
   306  	if err != nil {
   307  		t.Fatal(err)
   308  	}
   309  
   310  	err = p.CancelWaitForUpdates(ctx)
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  }
   315  
   316  func TestIncrementalWaitForUpdates(t *testing.T) {
   317  	ctx := context.Background()
   318  
   319  	m := VPX()
   320  
   321  	defer m.Remove()
   322  
   323  	err := m.Create()
   324  	if err != nil {
   325  		t.Fatal(err)
   326  	}
   327  
   328  	s := m.Service.NewServer()
   329  	defer s.Close()
   330  
   331  	c, err := govmomi.NewClient(ctx, s.URL, true)
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  
   336  	pc := property.DefaultCollector(c.Client)
   337  	obj := m.Map().Any("VirtualMachine").(*VirtualMachine)
   338  	ref := obj.Reference()
   339  	vm := object.NewVirtualMachine(c.Client, ref)
   340  
   341  	tests := []struct {
   342  		name  string
   343  		props []string
   344  	}{
   345  		{"1 field", []string{"runtime.powerState"}},
   346  		{"2 fields", []string{"summary.runtime.powerState", "summary.runtime.bootTime"}},
   347  		{"3 fields", []string{"runtime.powerState", "summary.runtime.powerState", "summary.runtime.bootTime"}},
   348  		{"parent field", []string{"runtime"}},
   349  		{"nested parent field", []string{"summary.runtime"}},
   350  		{"all", nil},
   351  	}
   352  
   353  	// toggle power state to generate updates
   354  	state := map[types.VirtualMachinePowerState]func(context.Context) (*object.Task, error){
   355  		types.VirtualMachinePowerStatePoweredOff: vm.PowerOn,
   356  		types.VirtualMachinePowerStatePoweredOn:  vm.PowerOff,
   357  	}
   358  
   359  	for i, test := range tests {
   360  		var props []string
   361  		matches := false
   362  		wait := make(chan bool)
   363  		host := obj.Summary.Runtime.Host // add host to filter just to have a different type in the filter
   364  		filter := new(property.WaitFilter).Add(*host, host.Type, nil).Add(ref, ref.Type, test.props)
   365  
   366  		go func() {
   367  			perr := property.WaitForUpdates(ctx, pc, filter, func(updates []types.ObjectUpdate) bool {
   368  				if updates[0].Kind == types.ObjectUpdateKindEnter {
   369  					wait <- true
   370  					return false
   371  				}
   372  				for _, update := range updates {
   373  					for _, change := range update.ChangeSet {
   374  						props = append(props, change.Name)
   375  					}
   376  				}
   377  
   378  				if test.props == nil {
   379  					// special case to test All flag
   380  					matches = isTrue(filter.Spec.PropSet[0].All) && len(props) > 1
   381  
   382  					return matches
   383  				}
   384  
   385  				if len(props) > len(test.props) {
   386  					return true
   387  				}
   388  
   389  				matches = reflect.DeepEqual(props, test.props)
   390  				return matches
   391  			})
   392  
   393  			if perr != nil {
   394  				t.Error(perr)
   395  			}
   396  			wait <- matches
   397  		}()
   398  
   399  		<-wait // wait for enter
   400  		_, _ = state[obj.Runtime.PowerState](ctx)
   401  		if !<-wait { // wait for modify
   402  			t.Errorf("%d: updates=%s, expected=%s", i, props, test.props)
   403  		}
   404  	}
   405  
   406  	// Test ContainerView + Delete
   407  	v, err := view.NewManager(c.Client).CreateContainerView(ctx, c.Client.ServiceContent.RootFolder, []string{ref.Type}, true)
   408  	if err != nil {
   409  		log.Fatal(err)
   410  	}
   411  
   412  	var wg sync.WaitGroup
   413  	wg.Add(1)
   414  	go func() {
   415  		defer wg.Done()
   416  
   417  		filter := new(property.WaitFilter).Add(v.Reference(), ref.Type, tests[0].props, v.TraversalSpec())
   418  		perr := property.WaitForUpdates(ctx, pc, filter, func(updates []types.ObjectUpdate) bool {
   419  			for _, update := range updates {
   420  				switch update.Kind {
   421  				case types.ObjectUpdateKindEnter:
   422  					wg.Done()
   423  					return false
   424  				case types.ObjectUpdateKindModify:
   425  				case types.ObjectUpdateKindLeave:
   426  					return update.Obj == vm.Reference()
   427  				}
   428  			}
   429  			return false
   430  		})
   431  		if perr != nil {
   432  			t.Error(perr)
   433  		}
   434  	}()
   435  
   436  	wg.Wait() // wait for 1st enter
   437  	wg.Add(1)
   438  	task, _ := vm.PowerOff(ctx)
   439  	_ = task.Wait(ctx)
   440  	task, _ = vm.Destroy(ctx)
   441  	wg.Wait() // wait for Delete to be reported
   442  	task.Wait(ctx)
   443  }
   444  
   445  func TestWaitForUpdatesOneUpdateCalculation(t *testing.T) {
   446  	/*
   447  	 * In this test, we use WaitForUpdatesEx in non-blocking way
   448  	 * by setting the MaxWaitSeconds to 0.
   449  	 * We filter on 'runtime.powerState'
   450  	 * Once we get the first 'enter' update, we change the
   451  	 * power state of the VM to generate a 'modify' update.
   452  	 * Once we get the modify, we can stop.
   453  	 */
   454  	ctx := context.Background()
   455  
   456  	m := VPX()
   457  	defer m.Remove()
   458  
   459  	err := m.Create()
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  
   464  	s := m.Service.NewServer()
   465  	defer s.Close()
   466  
   467  	c, err := govmomi.NewClient(ctx, s.URL, true)
   468  	if err != nil {
   469  		t.Fatal(err)
   470  	}
   471  
   472  	wait := make(chan bool)
   473  	pc := property.DefaultCollector(c.Client)
   474  	obj := m.Map().Any("VirtualMachine").(*VirtualMachine)
   475  	ref := obj.Reference()
   476  	vm := object.NewVirtualMachine(c.Client, ref)
   477  	filter := new(property.WaitFilter).Add(ref, ref.Type, []string{"runtime.powerState"})
   478  	// WaitOptions.maxWaitSeconds:
   479  	// A value of 0 causes WaitForUpdatesEx to do one update calculation and return any results.
   480  	filter.Options = &types.WaitOptions{
   481  		MaxWaitSeconds: types.NewInt32(0),
   482  	}
   483  	// toggle power state to generate updates
   484  	state := map[types.VirtualMachinePowerState]func(context.Context) (*object.Task, error){
   485  		types.VirtualMachinePowerStatePoweredOff: vm.PowerOn,
   486  		types.VirtualMachinePowerStatePoweredOn:  vm.PowerOff,
   487  	}
   488  
   489  	_, err = pc.CreateFilter(ctx, filter.CreateFilter)
   490  	if err != nil {
   491  		t.Fatal(err)
   492  	}
   493  
   494  	req := types.WaitForUpdatesEx{
   495  		This:    pc.Reference(),
   496  		Options: filter.Options,
   497  	}
   498  
   499  	go func() {
   500  		for {
   501  			res, err := methods.WaitForUpdatesEx(ctx, c.Client, &req)
   502  			if err != nil {
   503  				if ctx.Err() == context.Canceled {
   504  					werr := pc.CancelWaitForUpdates(context.Background())
   505  					t.Error(werr)
   506  					return
   507  				}
   508  				t.Error(err)
   509  				return
   510  			}
   511  
   512  			set := res.Returnval
   513  			if set == nil {
   514  				// Retry if the result came back empty
   515  				// That's a normal case when MaxWaitSeconds is set to 0.
   516  				// It means we have no updates for now
   517  				time.Sleep(500 * time.Millisecond)
   518  				continue
   519  			}
   520  
   521  			req.Version = set.Version
   522  
   523  			for _, fs := range set.FilterSet {
   524  				// We expect the enter of VM first
   525  				if fs.ObjectSet[0].Kind == types.ObjectUpdateKindEnter {
   526  					wait <- true
   527  					// Keep going
   528  					continue
   529  				}
   530  
   531  				// We also expect a modify due to the power state change
   532  				if fs.ObjectSet[0].Kind == types.ObjectUpdateKindModify {
   533  					wait <- true
   534  					// Now we can return to stop the routine
   535  					return
   536  				}
   537  			}
   538  		}
   539  	}()
   540  
   541  	// wait for the enter update.
   542  	<-wait
   543  
   544  	// Now change the VM power state, to generate a modify update
   545  	_, err = state[obj.Runtime.PowerState](ctx)
   546  	if err != nil {
   547  		t.Error(err)
   548  	}
   549  
   550  	// wait for the modify update.
   551  	<-wait
   552  }
   553  
   554  func TestPropertyCollectorWithUnsetValues(t *testing.T) {
   555  	ctx := context.Background()
   556  
   557  	m := VPX()
   558  
   559  	defer m.Remove()
   560  
   561  	err := m.Create()
   562  	if err != nil {
   563  		t.Fatal(err)
   564  	}
   565  
   566  	s := m.Service.NewServer()
   567  	defer s.Close()
   568  
   569  	client, err := govmomi.NewClient(ctx, s.URL, true)
   570  	if err != nil {
   571  		t.Fatal(err)
   572  	}
   573  
   574  	pc := property.DefaultCollector(client.Client)
   575  
   576  	vm := m.Map().Any("VirtualMachine")
   577  	vmRef := vm.Reference()
   578  
   579  	propSets := [][]string{
   580  		{"parentVApp"},                   // unset string by default (not returned by RetrievePropertiesEx)
   581  		{"rootSnapshot"},                 // unset VirtualMachineSnapshot[] by default (returned by RetrievePropertiesEx)
   582  		{"config.networkShaper.enabled"}, // unset at config.networkShaper level by default (not returned by RetrievePropertiesEx)
   583  		{"parentVApp", "rootSnapshot", "config.networkShaper.enabled"},         // (not returned by RetrievePropertiesEx)
   584  		{"name", "parentVApp", "rootSnapshot", "config.networkShaper.enabled"}, // (only name returned by RetrievePropertiesEx)
   585  		{"name", "config.guestFullName"},                                       // both set (and returned by RetrievePropertiesEx))
   586  	}
   587  
   588  	for _, propSet := range propSets {
   589  		// RetrievePropertiesEx
   590  		var objectContents []types.ObjectContent
   591  		err = pc.RetrieveOne(ctx, vmRef, propSet, &objectContents)
   592  		if err != nil {
   593  			t.Error(err)
   594  		}
   595  
   596  		if len(objectContents) != 1 {
   597  			t.Fatalf("len(objectContents) %#v != 1", len(objectContents))
   598  		}
   599  		objectContent := objectContents[0]
   600  
   601  		if objectContent.Obj != vmRef {
   602  			t.Fatalf("objectContent.Obj %#v != vmRef %#v", objectContent.Obj, vmRef)
   603  		}
   604  
   605  		inRetrieveResponseCount := 0
   606  		for _, prop := range propSet {
   607  			_, err := fieldValue(reflect.ValueOf(vm), prop)
   608  
   609  			switch err {
   610  			case nil:
   611  				inRetrieveResponseCount++
   612  				found := false
   613  				for _, objProp := range objectContent.PropSet {
   614  					if prop == objProp.Name {
   615  						found = true
   616  						break
   617  					}
   618  				}
   619  				if !found {
   620  					t.Fatalf("prop %#v was not found", prop)
   621  				}
   622  			case errEmptyField:
   623  				continue
   624  			default:
   625  				t.Error(err)
   626  			}
   627  		}
   628  
   629  		if len(objectContent.PropSet) != inRetrieveResponseCount {
   630  			t.Fatalf("len(objectContent.PropSet) %#v != inRetrieveResponseCount %#v", len(objectContent.PropSet), inRetrieveResponseCount)
   631  		}
   632  
   633  		if len(objectContent.MissingSet) != 0 {
   634  			t.Fatalf("len(objectContent.MissingSet) %#v != 0", len(objectContent.MissingSet))
   635  		}
   636  
   637  		// WaitForUpdatesEx
   638  		f := func(once bool) func([]types.PropertyChange) bool {
   639  			return func(pc []types.PropertyChange) bool {
   640  				if len(propSet) != len(pc) {
   641  					t.Fatalf("len(propSet) %#v != len(pc) %#v", len(propSet), len(pc))
   642  				}
   643  
   644  				for _, prop := range propSet {
   645  					found := false
   646  					for _, objProp := range pc {
   647  						switch err {
   648  						case nil, errEmptyField:
   649  							if prop == objProp.Name {
   650  								found = true
   651  							}
   652  						default:
   653  							t.Error(err)
   654  						}
   655  						if found {
   656  							break
   657  						}
   658  					}
   659  					if !found {
   660  						t.Fatalf("prop %#v was not found", prop)
   661  					}
   662  				}
   663  
   664  				return once
   665  			}
   666  		}
   667  
   668  		err = property.Wait(ctx, pc, vmRef, propSet, f(true))
   669  		if err != nil {
   670  			t.Error(err)
   671  		}
   672  
   673  		// internal Fetch() method used by ovftool and pyvmomi
   674  		for _, prop := range propSet {
   675  			body := &internal.FetchBody{
   676  				Req: &internal.Fetch{
   677  					This: vm.Reference(),
   678  					Prop: prop,
   679  				},
   680  			}
   681  
   682  			if err := client.RoundTrip(ctx, body, body); err != nil {
   683  				t.Error(err)
   684  			}
   685  		}
   686  	}
   687  }
   688  
   689  func TestCollectInterfaceType(t *testing.T) {
   690  	// test that we properly collect an interface type (types.BaseVirtualDevice in this case)
   691  	var config types.VirtualMachineConfigInfo
   692  	config.Hardware.Device = append(config.Hardware.Device, new(types.VirtualFloppy))
   693  
   694  	_, err := fieldValue(reflect.ValueOf(&config), "hardware.device")
   695  	if err != nil {
   696  		t.Fatal(err)
   697  	}
   698  }
   699  
   700  func TestExtractEmbeddedField(t *testing.T) {
   701  	type YourResourcePool struct {
   702  		mo.ResourcePool
   703  	}
   704  
   705  	type MyResourcePool struct {
   706  		YourResourcePool
   707  	}
   708  
   709  	x := new(MyResourcePool)
   710  
   711  	ctx := NewContext()
   712  	ctx.Map.Put(x)
   713  
   714  	obj, ok := getObject(ctx, x.Reference())
   715  	if !ok {
   716  		t.Error("expected obj")
   717  	}
   718  
   719  	if obj.Type() != reflect.ValueOf(new(mo.ResourcePool)).Elem().Type() {
   720  		t.Errorf("unexpected type=%s", obj.Type().Name())
   721  	}
   722  }
   723  
   724  func TestPropertyCollectorFold(t *testing.T) {
   725  	m := VPX()
   726  
   727  	defer m.Remove()
   728  
   729  	err := m.Create()
   730  	if err != nil {
   731  		t.Fatal(err)
   732  	}
   733  
   734  	s := m.Service.NewServer()
   735  	defer s.Close()
   736  
   737  	ctx := m.Service.Context
   738  
   739  	client, err := govmomi.NewClient(ctx, s.URL, true)
   740  	if err != nil {
   741  		t.Fatal(err)
   742  	}
   743  
   744  	cluster := ctx.Map.Any("ClusterComputeResource")
   745  	compute := ctx.Map.Any("ComputeResource")
   746  
   747  	// Test that we fold duplicate properties (rbvmomi depends on this)
   748  	var content []types.ObjectContent
   749  	err = client.Retrieve(ctx, []types.ManagedObjectReference{cluster.Reference()}, []string{"name", "name"}, &content)
   750  	if err != nil {
   751  		t.Fatal(err)
   752  	}
   753  	if len(content) != 1 {
   754  		t.Fatalf("len(content)=%d", len(content))
   755  	}
   756  	if len(content[0].PropSet) != 1 {
   757  		t.Fatalf("len(PropSet)=%d", len(content[0].PropSet))
   758  	}
   759  
   760  	// Test that we fold embedded type properties (rbvmomi depends on this)
   761  	req := types.RetrieveProperties{
   762  		SpecSet: []types.PropertyFilterSpec{
   763  			{
   764  				PropSet: []types.PropertySpec{
   765  					{
   766  						DynamicData: types.DynamicData{},
   767  						Type:        "ComputeResource",
   768  						PathSet:     []string{"name"},
   769  					},
   770  					{
   771  						DynamicData: types.DynamicData{},
   772  						Type:        "ClusterComputeResource",
   773  						PathSet:     []string{"name"},
   774  					},
   775  				},
   776  				ObjectSet: []types.ObjectSpec{
   777  					{
   778  						Obj: compute.Reference(),
   779  					},
   780  					{
   781  						Obj: cluster.Reference(),
   782  					},
   783  				},
   784  			},
   785  		},
   786  	}
   787  
   788  	pc := client.PropertyCollector()
   789  
   790  	res, err := pc.RetrieveProperties(ctx, req)
   791  	if err != nil {
   792  		t.Fatal(err)
   793  	}
   794  
   795  	content = res.Returnval
   796  
   797  	if len(content) != 2 {
   798  		t.Fatalf("len(content)=%d", len(content))
   799  	}
   800  
   801  	for _, oc := range content {
   802  		if len(oc.PropSet) != 1 {
   803  			t.Errorf("%s len(PropSet)=%d", oc.Obj, len(oc.PropSet))
   804  		}
   805  	}
   806  }
   807  
   808  func TestPropertyCollectorInvalidSpecName(t *testing.T) {
   809  	ctx := NewContext()
   810  	obj := ctx.Map.Put(new(Folder))
   811  	folderPutChild(ctx, &obj.(*Folder).Folder, new(Folder))
   812  
   813  	req := types.RetrievePropertiesEx{
   814  		SpecSet: []types.PropertyFilterSpec{
   815  			{
   816  				PropSet: []types.PropertySpec{
   817  					{
   818  						Type:    obj.Reference().Type,
   819  						PathSet: []string{"name"},
   820  					},
   821  				},
   822  				ObjectSet: []types.ObjectSpec{
   823  					{
   824  						Obj: obj.Reference(),
   825  						SelectSet: []types.BaseSelectionSpec{
   826  							&types.TraversalSpec{
   827  								Type: "Folder",
   828  								Path: "childEntity",
   829  								SelectSet: []types.BaseSelectionSpec{
   830  									&types.SelectionSpec{
   831  										Name: "enoent",
   832  									},
   833  								},
   834  							},
   835  						},
   836  					},
   837  				},
   838  			},
   839  		},
   840  	}
   841  
   842  	_, err := collect(ctx, &req)
   843  	if err == nil {
   844  		t.Fatal("expected error")
   845  	}
   846  
   847  	if _, ok := err.(*types.InvalidArgument); !ok {
   848  		t.Errorf("unexpected fault: %#v", err)
   849  	}
   850  }
   851  
   852  func TestPropertyCollectorInvalidProperty(t *testing.T) {
   853  	tests := []struct {
   854  		name          string
   855  		path          string
   856  		expectedFault types.BaseMethodFault
   857  	}{
   858  		{
   859  			"specify property of array property",
   860  			"config.hardware.device.key",
   861  			new(types.InvalidProperty),
   862  		},
   863  	}
   864  
   865  	for _, test := range tests {
   866  		test := test // assign to local var since loop var is reused
   867  
   868  		t.Run(test.name, func(t *testing.T) {
   869  			m := ESX()
   870  
   871  			Test(func(ctx context.Context, c *vim25.Client) {
   872  				pc := property.DefaultCollector(c)
   873  
   874  				vm := Map(ctx).Any("VirtualMachine")
   875  				vmRef := vm.Reference()
   876  
   877  				var vmMo mo.VirtualMachine
   878  				err := pc.RetrieveOne(ctx, vmRef, []string{test.path}, &vmMo)
   879  				if err == nil {
   880  					t.Fatal("expected error")
   881  				}
   882  
   883  				// NOTE: since soap.vimFaultError is not exported, use interface literal for assertion instead
   884  				fault, ok := err.(interface {
   885  					Fault() types.BaseMethodFault
   886  				})
   887  				if !ok {
   888  					t.Fatalf("err does not have fault: type: %T", err)
   889  				}
   890  
   891  				if reflect.TypeOf(fault.Fault()) != reflect.TypeOf(test.expectedFault) {
   892  					t.Errorf("expected fault: %T, actual fault: %T", test.expectedFault, fault.Fault())
   893  				}
   894  			}, m)
   895  		})
   896  	}
   897  }
   898  
   899  func TestPropertyCollectorRecursiveSelectSet(t *testing.T) {
   900  	ctx := context.Background()
   901  
   902  	m := VPX()
   903  
   904  	defer m.Remove()
   905  
   906  	err := m.Create()
   907  	if err != nil {
   908  		t.Fatal(err)
   909  	}
   910  
   911  	s := m.Service.NewServer()
   912  	defer s.Close()
   913  
   914  	client, err := govmomi.NewClient(ctx, s.URL, true)
   915  	if err != nil {
   916  		t.Fatal(err)
   917  	}
   918  
   919  	// Capture of PowerCLI's Get-VM spec
   920  	req := types.RetrieveProperties{
   921  		SpecSet: []types.PropertyFilterSpec{
   922  			{
   923  				PropSet: []types.PropertySpec{
   924  					{
   925  						Type: "VirtualMachine",
   926  						PathSet: []string{
   927  							"runtime.host",
   928  							"parent",
   929  							"resourcePool",
   930  							"resourcePool",
   931  							"datastore",
   932  							"config.swapPlacement",
   933  							"config.version",
   934  							"config.instanceUuid",
   935  							"config.guestId",
   936  							"config.annotation",
   937  							"summary.storage.committed",
   938  							"summary.storage.uncommitted",
   939  							"summary.storage.committed",
   940  							"config.template",
   941  						},
   942  					},
   943  				},
   944  				ObjectSet: []types.ObjectSpec{
   945  					{
   946  						Obj:  client.ServiceContent.RootFolder,
   947  						Skip: types.NewBool(false),
   948  						SelectSet: []types.BaseSelectionSpec{
   949  							&types.TraversalSpec{
   950  								SelectionSpec: types.SelectionSpec{
   951  									Name: "traverseFolders",
   952  								},
   953  								Type: "Folder",
   954  								Path: "childEntity",
   955  								Skip: types.NewBool(true),
   956  								SelectSet: []types.BaseSelectionSpec{
   957  									&types.TraversalSpec{
   958  										Type: "HostSystem",
   959  										Path: "vm",
   960  										Skip: types.NewBool(false),
   961  									},
   962  									&types.TraversalSpec{
   963  										Type: "ComputeResource",
   964  										Path: "host",
   965  										Skip: types.NewBool(true),
   966  										SelectSet: []types.BaseSelectionSpec{
   967  											&types.TraversalSpec{
   968  												Type: "HostSystem",
   969  												Path: "vm",
   970  												Skip: types.NewBool(false),
   971  											},
   972  										},
   973  									},
   974  									&types.TraversalSpec{
   975  										Type: "Datacenter",
   976  										Path: "hostFolder",
   977  										Skip: types.NewBool(true),
   978  										SelectSet: []types.BaseSelectionSpec{
   979  											&types.SelectionSpec{
   980  												Name: "traverseFolders",
   981  											},
   982  										},
   983  									},
   984  									&types.SelectionSpec{
   985  										Name: "traverseFolders",
   986  									},
   987  								},
   988  							},
   989  						},
   990  					},
   991  				},
   992  			},
   993  		},
   994  	}
   995  
   996  	pc := client.PropertyCollector()
   997  
   998  	res, err := pc.RetrieveProperties(ctx, req)
   999  	if err != nil {
  1000  		t.Fatal(err)
  1001  	}
  1002  
  1003  	content := res.Returnval
  1004  
  1005  	count := m.Count()
  1006  
  1007  	if len(content) != count.Machine {
  1008  		t.Fatalf("len(content)=%d", len(content))
  1009  	}
  1010  }
  1011  
  1012  func TestPropertyCollectorSelectionSpec(t *testing.T) {
  1013  	ctx := context.Background()
  1014  
  1015  	m := VPX()
  1016  
  1017  	defer m.Remove()
  1018  
  1019  	err := m.Create()
  1020  	if err != nil {
  1021  		t.Fatal(err)
  1022  	}
  1023  
  1024  	s := m.Service.NewServer()
  1025  	defer s.Close()
  1026  
  1027  	client, err := govmomi.NewClient(ctx, s.URL, true)
  1028  	if err != nil {
  1029  		t.Fatal(err)
  1030  	}
  1031  
  1032  	// Capture of PowerCLI's Start-VM spec
  1033  	// Differs from the norm in that:
  1034  	// 1) Named SelectionSpec before TraversalSpec is defined
  1035  	// 2) Skip=false for all mo types, but PropSet only has Type VirtualMachine
  1036  	req := types.RetrieveProperties{
  1037  		SpecSet: []types.PropertyFilterSpec{
  1038  			{
  1039  				PropSet: []types.PropertySpec{
  1040  					{
  1041  						Type:    "VirtualMachine",
  1042  						All:     types.NewBool(false),
  1043  						PathSet: []string{"name", "parent", "runtime.host", "config.template"},
  1044  					},
  1045  				},
  1046  				ObjectSet: []types.ObjectSpec{
  1047  					{
  1048  						Obj:  client.ServiceContent.RootFolder,
  1049  						Skip: types.NewBool(false),
  1050  						SelectSet: []types.BaseSelectionSpec{
  1051  							&types.TraversalSpec{
  1052  								SelectionSpec: types.SelectionSpec{
  1053  									Name: "folderTraversalSpec",
  1054  								},
  1055  								Type: "Folder",
  1056  								Path: "childEntity",
  1057  								Skip: types.NewBool(false),
  1058  								SelectSet: []types.BaseSelectionSpec{
  1059  									&types.SelectionSpec{
  1060  										Name: "computeResourceRpTraversalSpec",
  1061  									},
  1062  									&types.SelectionSpec{
  1063  										Name: "computeResourceHostTraversalSpec",
  1064  									},
  1065  									&types.SelectionSpec{
  1066  										Name: "folderTraversalSpec",
  1067  									},
  1068  									&types.SelectionSpec{
  1069  										Name: "datacenterHostTraversalSpec",
  1070  									},
  1071  									&types.SelectionSpec{
  1072  										Name: "hostVmTraversalSpec",
  1073  									},
  1074  									&types.SelectionSpec{
  1075  										Name: "resourcePoolVmTraversalSpec",
  1076  									},
  1077  									&types.SelectionSpec{
  1078  										Name: "hostRpTraversalSpec",
  1079  									},
  1080  									&types.SelectionSpec{
  1081  										Name: "datacenterVmTraversalSpec",
  1082  									},
  1083  									&types.SelectionSpec{
  1084  										Name: "datastoreVmTraversalSpec",
  1085  									},
  1086  									&types.SelectionSpec{
  1087  										Name: "datacenterDatastoreTraversalSpec",
  1088  									},
  1089  									&types.SelectionSpec{
  1090  										Name: "vappTraversalSpec",
  1091  									},
  1092  									&types.SelectionSpec{
  1093  										Name: "datacenterNetworkTraversalSpec",
  1094  									},
  1095  								},
  1096  							},
  1097  							&types.TraversalSpec{
  1098  								SelectionSpec: types.SelectionSpec{
  1099  									Name: "computeResourceHostTraversalSpec",
  1100  								},
  1101  								Type: "ComputeResource",
  1102  								Path: "host",
  1103  								Skip: types.NewBool(false),
  1104  								SelectSet: []types.BaseSelectionSpec{
  1105  									&types.SelectionSpec{
  1106  										Name: "hostVmTraversalSpec",
  1107  									},
  1108  								},
  1109  							},
  1110  							&types.TraversalSpec{
  1111  								SelectionSpec: types.SelectionSpec{
  1112  									Name: "computeResourceRpTraversalSpec",
  1113  								},
  1114  								Type: "ComputeResource",
  1115  								Path: "resourcePool",
  1116  								Skip: types.NewBool(false),
  1117  								SelectSet: []types.BaseSelectionSpec{
  1118  									&types.SelectionSpec{
  1119  										Name: "resourcePoolTraversalSpec",
  1120  									},
  1121  									&types.SelectionSpec{
  1122  										Name: "vappTraversalSpec",
  1123  									},
  1124  								},
  1125  							},
  1126  							&types.TraversalSpec{
  1127  								SelectionSpec: types.SelectionSpec{
  1128  									Name: "datacenterHostTraversalSpec",
  1129  								},
  1130  								Type: "Datacenter",
  1131  								Path: "hostFolder",
  1132  								Skip: types.NewBool(false),
  1133  								SelectSet: []types.BaseSelectionSpec{
  1134  									&types.SelectionSpec{
  1135  										Name: "folderTraversalSpec",
  1136  									},
  1137  								},
  1138  							},
  1139  							&types.TraversalSpec{
  1140  								SelectionSpec: types.SelectionSpec{
  1141  									Name: "resourcePoolTraversalSpec",
  1142  								},
  1143  								Type: "ResourcePool",
  1144  								Path: "resourcePool",
  1145  								Skip: types.NewBool(false),
  1146  								SelectSet: []types.BaseSelectionSpec{
  1147  									&types.SelectionSpec{
  1148  										Name: "resourcePoolTraversalSpec",
  1149  									},
  1150  									&types.SelectionSpec{
  1151  										Name: "resourcePoolVmTraversalSpec",
  1152  									},
  1153  								},
  1154  							},
  1155  							&types.TraversalSpec{
  1156  								SelectionSpec: types.SelectionSpec{
  1157  									Name: "hostVmTraversalSpec",
  1158  								},
  1159  								Type: "HostSystem",
  1160  								Path: "vm",
  1161  								Skip: types.NewBool(false),
  1162  								SelectSet: []types.BaseSelectionSpec{
  1163  									&types.SelectionSpec{
  1164  										Name: "folderTraversalSpec",
  1165  									},
  1166  								},
  1167  							},
  1168  							&types.TraversalSpec{
  1169  								SelectionSpec: types.SelectionSpec{
  1170  									Name: "datacenterVmTraversalSpec",
  1171  								},
  1172  								Type: "Datacenter",
  1173  								Path: "vmFolder",
  1174  								Skip: types.NewBool(false),
  1175  								SelectSet: []types.BaseSelectionSpec{
  1176  									&types.SelectionSpec{
  1177  										Name: "folderTraversalSpec",
  1178  									},
  1179  								},
  1180  							},
  1181  							&types.TraversalSpec{
  1182  								SelectionSpec: types.SelectionSpec{
  1183  									Name: "resourcePoolVmTraversalSpec",
  1184  								},
  1185  								Type: "ResourcePool",
  1186  								Path: "vm",
  1187  								Skip: types.NewBool(false),
  1188  							},
  1189  							&types.TraversalSpec{
  1190  								SelectionSpec: types.SelectionSpec{
  1191  									Name: "datastoreVmTraversalSpec",
  1192  								},
  1193  								Type: "Datastore",
  1194  								Path: "vm",
  1195  								Skip: types.NewBool(false),
  1196  							},
  1197  							&types.TraversalSpec{
  1198  								SelectionSpec: types.SelectionSpec{
  1199  									Name: "datacenterDatastoreTraversalSpec",
  1200  								},
  1201  								Type: "Datacenter",
  1202  								Path: "datastoreFolder",
  1203  								Skip: types.NewBool(false),
  1204  								SelectSet: []types.BaseSelectionSpec{
  1205  									&types.SelectionSpec{
  1206  										Name: "folderTraversalSpec",
  1207  									},
  1208  								},
  1209  							},
  1210  							&types.TraversalSpec{
  1211  								SelectionSpec: types.SelectionSpec{
  1212  									Name: "vappTraversalSpec",
  1213  								},
  1214  								Type: "VirtualApp",
  1215  								Path: "resourcePool",
  1216  								Skip: types.NewBool(false),
  1217  								SelectSet: []types.BaseSelectionSpec{
  1218  									&types.SelectionSpec{
  1219  										Name: "vappTraversalSpec",
  1220  									},
  1221  									&types.SelectionSpec{
  1222  										Name: "resourcePoolTraversalSpec",
  1223  									},
  1224  									&types.SelectionSpec{
  1225  										Name: "resourcePoolVmTraversalSpec",
  1226  									},
  1227  								},
  1228  							},
  1229  							&types.TraversalSpec{
  1230  								SelectionSpec: types.SelectionSpec{
  1231  									Name: "vappVmTraversalSpec",
  1232  								},
  1233  								Type: "VirtualApp",
  1234  								Path: "vm",
  1235  								Skip: types.NewBool(false),
  1236  							},
  1237  							&types.TraversalSpec{
  1238  								SelectionSpec: types.SelectionSpec{
  1239  									Name: "distributedSwitchHostTraversalSpec",
  1240  								},
  1241  								Type: "DistributedVirtualSwitch",
  1242  								Path: "summary.hostMember",
  1243  								Skip: types.NewBool(false),
  1244  							},
  1245  							&types.TraversalSpec{
  1246  								SelectionSpec: types.SelectionSpec{
  1247  									Name: "distributedSwitchVmTraversalSpec",
  1248  								},
  1249  								Type: "DistributedVirtualSwitch",
  1250  								Path: "summary.vm",
  1251  								Skip: types.NewBool(false),
  1252  							},
  1253  							&types.TraversalSpec{
  1254  								SelectionSpec: types.SelectionSpec{
  1255  									Name: "datacenterNetworkTraversalSpec",
  1256  								},
  1257  								Type: "Datacenter",
  1258  								Path: "networkFolder",
  1259  								Skip: types.NewBool(false),
  1260  								SelectSet: []types.BaseSelectionSpec{
  1261  									&types.SelectionSpec{
  1262  										Name: "folderTraversalSpec",
  1263  									},
  1264  								},
  1265  							},
  1266  							&types.TraversalSpec{
  1267  								SelectionSpec: types.SelectionSpec{
  1268  									Name: "hostRpTraversalSpec",
  1269  								},
  1270  								Type: "HostSystem",
  1271  								Path: "parent",
  1272  								Skip: types.NewBool(false),
  1273  								SelectSet: []types.BaseSelectionSpec{
  1274  									&types.SelectionSpec{
  1275  										Name: "computeResourceRpTraversalSpec",
  1276  									},
  1277  								},
  1278  							},
  1279  						},
  1280  					},
  1281  				},
  1282  			},
  1283  		},
  1284  	}
  1285  
  1286  	pc := client.PropertyCollector()
  1287  
  1288  	res, err := pc.RetrieveProperties(ctx, req)
  1289  	if err != nil {
  1290  		t.Fatal(err)
  1291  	}
  1292  
  1293  	content := res.Returnval
  1294  	count := m.Count()
  1295  
  1296  	if len(content) != count.Machine {
  1297  		t.Fatalf("len(content)=%d", len(content))
  1298  	}
  1299  }
  1300  
  1301  func TestIssue945(t *testing.T) {
  1302  	// pyvsphere request
  1303  	xml := `<?xml version="1.0" encoding="UTF-8"?>
  1304  <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ZSI="http://www.zolera.com/schemas/ZSI/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  1305     <SOAP-ENV:Header />
  1306     <SOAP-ENV:Body xmlns:ns1="urn:vim25">
  1307        <ns1:RetrievePropertiesEx xsi:type="ns1:RetrievePropertiesExRequestType">
  1308           <ns1:_this type="PropertyCollector">propertyCollector</ns1:_this>
  1309           <ns1:specSet>
  1310              <ns1:propSet>
  1311                 <ns1:type>VirtualMachine</ns1:type>
  1312                 <ns1:pathSet>name</ns1:pathSet>
  1313              </ns1:propSet>
  1314              <ns1:objectSet>
  1315                 <ns1:obj type="Folder">group-d1</ns1:obj>
  1316                 <ns1:skip>false</ns1:skip>
  1317                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1318                    <ns1:name>visitFolders</ns1:name>
  1319                    <ns1:type>Folder</ns1:type>
  1320                    <ns1:path>childEntity</ns1:path>
  1321                    <ns1:skip>false</ns1:skip>
  1322                    <ns1:selectSet>
  1323                       <ns1:name>visitFolders</ns1:name>
  1324                    </ns1:selectSet>
  1325                    <ns1:selectSet>
  1326                       <ns1:name>dcToHf</ns1:name>
  1327                    </ns1:selectSet>
  1328                    <ns1:selectSet>
  1329                       <ns1:name>dcToVmf</ns1:name>
  1330                    </ns1:selectSet>
  1331                    <ns1:selectSet>
  1332                       <ns1:name>crToH</ns1:name>
  1333                    </ns1:selectSet>
  1334                    <ns1:selectSet>
  1335                       <ns1:name>crToRp</ns1:name>
  1336                    </ns1:selectSet>
  1337                    <ns1:selectSet>
  1338                       <ns1:name>dcToDs</ns1:name>
  1339                    </ns1:selectSet>
  1340                    <ns1:selectSet>
  1341                       <ns1:name>hToVm</ns1:name>
  1342                    </ns1:selectSet>
  1343                    <ns1:selectSet>
  1344                       <ns1:name>rpToVm</ns1:name>
  1345                    </ns1:selectSet>
  1346                 </ns1:selectSet>
  1347                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1348                    <ns1:name>dcToVmf</ns1:name>
  1349                    <ns1:type>Datacenter</ns1:type>
  1350                    <ns1:path>vmFolder</ns1:path>
  1351                    <ns1:skip>false</ns1:skip>
  1352                    <ns1:selectSet>
  1353                       <ns1:name>visitFolders</ns1:name>
  1354                    </ns1:selectSet>
  1355                 </ns1:selectSet>
  1356                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1357                    <ns1:name>dcToDs</ns1:name>
  1358                    <ns1:type>Datacenter</ns1:type>
  1359                    <ns1:path>datastore</ns1:path>
  1360                    <ns1:skip>false</ns1:skip>
  1361                    <ns1:selectSet>
  1362                       <ns1:name>visitFolders</ns1:name>
  1363                    </ns1:selectSet>
  1364                 </ns1:selectSet>
  1365                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1366                    <ns1:name>dcToHf</ns1:name>
  1367                    <ns1:type>Datacenter</ns1:type>
  1368                    <ns1:path>hostFolder</ns1:path>
  1369                    <ns1:skip>false</ns1:skip>
  1370                    <ns1:selectSet>
  1371                       <ns1:name>visitFolders</ns1:name>
  1372                    </ns1:selectSet>
  1373                 </ns1:selectSet>
  1374                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1375                    <ns1:name>crToH</ns1:name>
  1376                    <ns1:type>ComputeResource</ns1:type>
  1377                    <ns1:path>host</ns1:path>
  1378                    <ns1:skip>false</ns1:skip>
  1379                 </ns1:selectSet>
  1380                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1381                    <ns1:name>crToRp</ns1:name>
  1382                    <ns1:type>ComputeResource</ns1:type>
  1383                    <ns1:path>resourcePool</ns1:path>
  1384                    <ns1:skip>false</ns1:skip>
  1385                    <ns1:selectSet>
  1386                       <ns1:name>rpToRp</ns1:name>
  1387                    </ns1:selectSet>
  1388                    <ns1:selectSet>
  1389                       <ns1:name>rpToVm</ns1:name>
  1390                    </ns1:selectSet>
  1391                 </ns1:selectSet>
  1392                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1393                    <ns1:name>rpToRp</ns1:name>
  1394                    <ns1:type>ResourcePool</ns1:type>
  1395                    <ns1:path>resourcePool</ns1:path>
  1396                    <ns1:skip>false</ns1:skip>
  1397                    <ns1:selectSet>
  1398                       <ns1:name>rpToRp</ns1:name>
  1399                    </ns1:selectSet>
  1400                    <ns1:selectSet>
  1401                       <ns1:name>rpToVm</ns1:name>
  1402                    </ns1:selectSet>
  1403                 </ns1:selectSet>
  1404                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1405                    <ns1:name>hToVm</ns1:name>
  1406                    <ns1:type>HostSystem</ns1:type>
  1407                    <ns1:path>vm</ns1:path>
  1408                    <ns1:skip>false</ns1:skip>
  1409                    <ns1:selectSet>
  1410                       <ns1:name>visitFolders</ns1:name>
  1411                    </ns1:selectSet>
  1412                 </ns1:selectSet>
  1413                 <ns1:selectSet xsi:type="ns1:TraversalSpec">
  1414                    <ns1:name>rpToVm</ns1:name>
  1415                    <ns1:type>ResourcePool</ns1:type>
  1416                    <ns1:path>vm</ns1:path>
  1417                    <ns1:skip>false</ns1:skip>
  1418                 </ns1:selectSet>
  1419              </ns1:objectSet>
  1420           </ns1:specSet>
  1421           <ns1:options />
  1422        </ns1:RetrievePropertiesEx>
  1423     </SOAP-ENV:Body>
  1424  </SOAP-ENV:Envelope>`
  1425  
  1426  	method, err := UnmarshalBody(defaultMapType, []byte(xml))
  1427  	if err != nil {
  1428  		t.Fatal(err)
  1429  	}
  1430  
  1431  	req := method.Body.(*types.RetrievePropertiesEx)
  1432  
  1433  	ctx := context.Background()
  1434  
  1435  	m := VPX()
  1436  
  1437  	defer m.Remove()
  1438  
  1439  	err = m.Create()
  1440  	if err != nil {
  1441  		t.Fatal(err)
  1442  	}
  1443  
  1444  	s := m.Service.NewServer()
  1445  	defer s.Close()
  1446  
  1447  	client, err := govmomi.NewClient(ctx, s.URL, true)
  1448  	if err != nil {
  1449  		t.Fatal(err)
  1450  	}
  1451  
  1452  	res, err := methods.RetrievePropertiesEx(ctx, client, req)
  1453  	if err != nil {
  1454  		t.Fatal(err)
  1455  	}
  1456  
  1457  	content := res.Returnval.Objects
  1458  	count := m.Count()
  1459  
  1460  	if len(content) != count.Machine {
  1461  		t.Fatalf("len(content)=%d", len(content))
  1462  	}
  1463  }
  1464  
  1465  func TestPropertyCollectorSession(t *testing.T) { // aka issue-923
  1466  	ctx := context.Background()
  1467  
  1468  	m := VPX()
  1469  
  1470  	defer m.Remove()
  1471  
  1472  	err := m.Create()
  1473  	if err != nil {
  1474  		t.Fatal(err)
  1475  	}
  1476  
  1477  	s := m.Service.NewServer()
  1478  	defer s.Close()
  1479  
  1480  	u := s.URL.User
  1481  	s.URL.User = nil // skip Login()
  1482  
  1483  	c, err := govmomi.NewClient(ctx, s.URL, true)
  1484  	if err != nil {
  1485  		t.Fatal(err)
  1486  	}
  1487  
  1488  	for i := 0; i < 2; i++ {
  1489  		if err = c.Login(ctx, u); err != nil {
  1490  			t.Fatal(err)
  1491  		}
  1492  
  1493  		if err = c.Login(ctx, u); err == nil {
  1494  			t.Error("expected Login failure") // Login fails if we already have a session
  1495  		}
  1496  
  1497  		pc := property.DefaultCollector(c.Client)
  1498  		filter := new(property.WaitFilter).Add(c.ServiceContent.RootFolder, "Folder", []string{"name"})
  1499  
  1500  		if _, err = pc.CreateFilter(ctx, filter.CreateFilter); err != nil {
  1501  			t.Fatal(err)
  1502  		}
  1503  
  1504  		res, err := pc.WaitForUpdates(ctx, "")
  1505  		if err != nil {
  1506  			t.Error(err)
  1507  		}
  1508  
  1509  		if len(res.FilterSet) != 1 {
  1510  			t.Errorf("len FilterSet=%d", len(res.FilterSet))
  1511  		}
  1512  
  1513  		if err = c.Logout(ctx); err != nil {
  1514  			t.Fatal(err)
  1515  		}
  1516  	}
  1517  }
  1518  
  1519  func TestPropertyCollectorNoPathSet(t *testing.T) {
  1520  	ctx := context.Background()
  1521  
  1522  	m := VPX()
  1523  	m.Datacenter = 3
  1524  	m.Folder = 2
  1525  
  1526  	defer m.Remove()
  1527  
  1528  	err := m.Create()
  1529  	if err != nil {
  1530  		t.Fatal(err)
  1531  	}
  1532  
  1533  	s := m.Service.NewServer()
  1534  	defer s.Close()
  1535  
  1536  	client, err := govmomi.NewClient(ctx, s.URL, true)
  1537  	if err != nil {
  1538  		t.Fatal(err)
  1539  	}
  1540  
  1541  	// request from https://github.com/vmware/govmomi/issues/1199
  1542  	req := &types.RetrieveProperties{
  1543  		This: client.ServiceContent.PropertyCollector,
  1544  		SpecSet: []types.PropertyFilterSpec{
  1545  			{
  1546  				PropSet: []types.PropertySpec{
  1547  					{
  1548  						Type:    "Datacenter",
  1549  						All:     types.NewBool(false),
  1550  						PathSet: nil,
  1551  					},
  1552  				},
  1553  				ObjectSet: []types.ObjectSpec{
  1554  					{
  1555  						Obj:  client.ServiceContent.RootFolder,
  1556  						Skip: types.NewBool(false),
  1557  						SelectSet: []types.BaseSelectionSpec{
  1558  							&types.TraversalSpec{
  1559  								SelectionSpec: types.SelectionSpec{
  1560  									Name: "resourcePoolTraversalSpec",
  1561  								},
  1562  								Type: "ResourcePool",
  1563  								Path: "resourcePool",
  1564  								Skip: types.NewBool(false),
  1565  								SelectSet: []types.BaseSelectionSpec{
  1566  									&types.SelectionSpec{
  1567  										Name: "resourcePoolTraversalSpec",
  1568  									},
  1569  									&types.SelectionSpec{
  1570  										Name: "resourcePoolVmTraversalSpec",
  1571  									},
  1572  								},
  1573  							},
  1574  							&types.TraversalSpec{
  1575  								SelectionSpec: types.SelectionSpec{
  1576  									Name: "resourcePoolVmTraversalSpec",
  1577  								},
  1578  								Type:      "ResourcePool",
  1579  								Path:      "vm",
  1580  								Skip:      types.NewBool(false),
  1581  								SelectSet: nil,
  1582  							},
  1583  							&types.TraversalSpec{
  1584  								SelectionSpec: types.SelectionSpec{
  1585  									Name: "computeResourceRpTraversalSpec",
  1586  								},
  1587  								Type: "ComputeResource",
  1588  								Path: "resourcePool",
  1589  								Skip: types.NewBool(false),
  1590  								SelectSet: []types.BaseSelectionSpec{
  1591  									&types.SelectionSpec{
  1592  										Name: "resourcePoolTraversalSpec",
  1593  									},
  1594  									&types.SelectionSpec{
  1595  										Name: "resourcePoolVmTraversalSpec",
  1596  									},
  1597  								},
  1598  							},
  1599  							&types.TraversalSpec{
  1600  								SelectionSpec: types.SelectionSpec{
  1601  									Name: "computeResourceHostTraversalSpec",
  1602  								},
  1603  								Type:      "ComputeResource",
  1604  								Path:      "host",
  1605  								Skip:      types.NewBool(false),
  1606  								SelectSet: nil,
  1607  							},
  1608  							&types.TraversalSpec{
  1609  								SelectionSpec: types.SelectionSpec{
  1610  									Name: "datacenterVmTraversalSpec",
  1611  								},
  1612  								Type: "Datacenter",
  1613  								Path: "vmFolder",
  1614  								Skip: types.NewBool(false),
  1615  								SelectSet: []types.BaseSelectionSpec{
  1616  									&types.SelectionSpec{
  1617  										Name: "folderTraversalSpec",
  1618  									},
  1619  								},
  1620  							},
  1621  							&types.TraversalSpec{
  1622  								SelectionSpec: types.SelectionSpec{
  1623  									Name: "datacenterHostTraversalSpec",
  1624  								},
  1625  								Type: "Datacenter",
  1626  								Path: "hostFolder",
  1627  								Skip: types.NewBool(false),
  1628  								SelectSet: []types.BaseSelectionSpec{
  1629  									&types.SelectionSpec{
  1630  										Name: "folderTraversalSpec",
  1631  									},
  1632  								},
  1633  							},
  1634  							&types.TraversalSpec{
  1635  								SelectionSpec: types.SelectionSpec{
  1636  									Name: "hostVmTraversalSpec",
  1637  								},
  1638  								Type: "HostSystem",
  1639  								Path: "vm",
  1640  								Skip: types.NewBool(false),
  1641  								SelectSet: []types.BaseSelectionSpec{
  1642  									&types.SelectionSpec{
  1643  										Name: "folderTraversalSpec",
  1644  									},
  1645  								},
  1646  							},
  1647  							&types.TraversalSpec{
  1648  								SelectionSpec: types.SelectionSpec{
  1649  									Name: "datacenterDatastoreTraversalSpec",
  1650  								},
  1651  								Type: "Datacenter",
  1652  								Path: "datastoreFolder",
  1653  								Skip: types.NewBool(false),
  1654  								SelectSet: []types.BaseSelectionSpec{
  1655  									&types.SelectionSpec{
  1656  										Name: "folderTraversalSpec",
  1657  									},
  1658  								},
  1659  							},
  1660  							&types.TraversalSpec{
  1661  								SelectionSpec: types.SelectionSpec{
  1662  									Name: "folderTraversalSpec",
  1663  								},
  1664  								Type: "Folder",
  1665  								Path: "childEntity",
  1666  								Skip: types.NewBool(false),
  1667  								SelectSet: []types.BaseSelectionSpec{
  1668  									&types.SelectionSpec{
  1669  										Name: "folderTraversalSpec",
  1670  									},
  1671  									&types.SelectionSpec{
  1672  										Name: "datacenterHostTraversalSpec",
  1673  									},
  1674  									&types.SelectionSpec{
  1675  										Name: "datacenterVmTraversalSpec",
  1676  									},
  1677  									&types.SelectionSpec{
  1678  										Name: "computeResourceRpTraversalSpec",
  1679  									},
  1680  									&types.SelectionSpec{
  1681  										Name: "computeResourceHostTraversalSpec",
  1682  									},
  1683  									&types.SelectionSpec{
  1684  										Name: "hostVmTraversalSpec",
  1685  									},
  1686  									&types.SelectionSpec{
  1687  										Name: "resourcePoolVmTraversalSpec",
  1688  									},
  1689  									&types.SelectionSpec{
  1690  										Name: "datacenterDatastoreTraversalSpec",
  1691  									},
  1692  								},
  1693  							},
  1694  						},
  1695  					},
  1696  				},
  1697  				ReportMissingObjectsInResults: (*bool)(nil),
  1698  			},
  1699  		},
  1700  	}
  1701  
  1702  	res, err := methods.RetrieveProperties(ctx, client, req)
  1703  	if err != nil {
  1704  		t.Fatal(err)
  1705  	}
  1706  
  1707  	content := res.Returnval
  1708  	count := m.Count()
  1709  
  1710  	if len(content) != count.Datacenter {
  1711  		t.Fatalf("len(content)=%d", len(content))
  1712  	}
  1713  }
  1714  
  1715  func TestLcFirst(t *testing.T) {
  1716  	tests := []struct {
  1717  		input    string
  1718  		expected string
  1719  	}{
  1720  		{input: "ABC", expected: "aBC"},
  1721  		{input: "abc", expected: "abc"},
  1722  		{input: "", expected: ""},
  1723  	}
  1724  
  1725  	for _, tt := range tests {
  1726  		t.Run(tt.input, func(t *testing.T) {
  1727  			actual := lcFirst(tt.input)
  1728  
  1729  			if actual != tt.expected {
  1730  				t.Errorf("%q != %q", actual, tt.expected)
  1731  			}
  1732  		})
  1733  	}
  1734  }
  1735  
  1736  func TestUcFirst(t *testing.T) {
  1737  	tests := []struct {
  1738  		input    string
  1739  		expected string
  1740  	}{
  1741  		{input: "ABC", expected: "ABC"},
  1742  		{input: "abc", expected: "Abc"},
  1743  		{input: "", expected: ""},
  1744  	}
  1745  
  1746  	for _, tt := range tests {
  1747  		t.Run(tt.input, func(t *testing.T) {
  1748  			actual := ucFirst(tt.input)
  1749  
  1750  			if actual != tt.expected {
  1751  				t.Errorf("%q != %q", actual, tt.expected)
  1752  			}
  1753  		})
  1754  	}
  1755  }
  1756  
  1757  func TestPageUpdateSet(t *testing.T) {
  1758  	max := defaultMaxObjectUpdates
  1759  
  1760  	sum := func(u []int) int {
  1761  		total := 0
  1762  		for i := range u {
  1763  			total += u[i]
  1764  		}
  1765  		return total
  1766  	}
  1767  
  1768  	sumUpdateSet := func(u *types.UpdateSet) int {
  1769  		total := 0
  1770  		for _, f := range u.FilterSet {
  1771  			total += len(f.ObjectSet)
  1772  		}
  1773  		return total
  1774  	}
  1775  
  1776  	tests := []struct {
  1777  		filters int
  1778  		objects []int
  1779  	}{
  1780  		{0, nil},
  1781  		{1, []int{10}},
  1782  		{1, []int{104}},
  1783  		{3, []int{10, 0, 25}},
  1784  		{1, []int{234, 21, 4}},
  1785  		{2, []int{95, 32}},
  1786  	}
  1787  
  1788  	for _, test := range tests {
  1789  		name := fmt.Sprintf("%d filter %d objs", test.filters, sum(test.objects))
  1790  		t.Run(name, func(t *testing.T) {
  1791  			update := &types.UpdateSet{}
  1792  
  1793  			for i := 0; i < test.filters; i++ {
  1794  				f := types.PropertyFilterUpdate{}
  1795  				for j := 0; j < 156; j++ {
  1796  					f.ObjectSet = append(f.ObjectSet, types.ObjectUpdate{})
  1797  				}
  1798  				update.FilterSet = append(update.FilterSet, f)
  1799  			}
  1800  
  1801  			total := sumUpdateSet(update)
  1802  			sum := 0
  1803  
  1804  			for {
  1805  				pending := pageUpdateSet(update, max)
  1806  				n := sumUpdateSet(update)
  1807  				if n > max {
  1808  					t.Fatalf("%d > %d", n, max)
  1809  				}
  1810  				sum += n
  1811  				if pending == nil {
  1812  					break
  1813  				}
  1814  				update = pending
  1815  			}
  1816  
  1817  			if sum != total {
  1818  				t.Fatalf("%d != %d", sum, total)
  1819  			}
  1820  		})
  1821  	}
  1822  }