github.com/vmware/govmomi@v0.43.0/simulator/property_collector_test.go (about)

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