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