/* Copyright (c) 2017 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package simulator import ( "context" "log" "reflect" "sync" "testing" "time" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/simulator/esx" "github.com/vmware/govmomi/simulator/vpx" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" ) func TestRetrieveProperties(t *testing.T) { configs := []struct { folder mo.Folder content types.ServiceContent dc *types.ManagedObjectReference }{ {esx.RootFolder, esx.ServiceContent, &esx.Datacenter.Self}, {vpx.RootFolder, vpx.ServiceContent, nil}, } for _, config := range configs { s := New(NewServiceInstance(SpoofContext(), config.content, config.folder)) ts := s.NewServer() defer ts.Close() ctx := context.Background() client, err := govmomi.NewClient(ctx, ts.URL, true) if err != nil { t.Fatal(err) } if config.dc == nil { dc, cerr := object.NewRootFolder(client.Client).CreateDatacenter(ctx, "dc1") if cerr != nil { t.Fatal(cerr) } ref := dc.Reference() config.dc = &ref } // Retrieve a specific property f := mo.Folder{} err = client.RetrieveOne(ctx, config.content.RootFolder, []string{"name"}, &f) if err != nil { t.Fatal(err) } if f.Name != config.folder.Name { t.Fail() } // Retrieve all properties f = mo.Folder{} err = client.RetrieveOne(ctx, config.content.RootFolder, nil, &f) if err != nil { t.Fatal(err) } if f.Name != config.folder.Name { t.Fatalf("'%s' vs '%s'", f.Name, config.folder.Name) } // Retrieve an ArrayOf property f = mo.Folder{} err = client.RetrieveOne(ctx, config.content.RootFolder, []string{"childEntity"}, &f) if err != nil { t.Fatal(err) } if len(f.ChildEntity) != 1 { t.Fail() } es, err := mo.Ancestors(ctx, client.Client, config.content.PropertyCollector, config.content.RootFolder) if err != nil { t.Fatal(err) } if len(es) != 1 { t.Fail() } finder := find.NewFinder(client.Client, false) dc, err := finder.DatacenterOrDefault(ctx, "") if err != nil { t.Fatal(err) } if dc.Reference() != *config.dc { t.Fail() } finder.SetDatacenter(dc) es, err = mo.Ancestors(ctx, client.Client, config.content.PropertyCollector, dc.Reference()) if err != nil { t.Fatal(err) } expect := map[string]types.ManagedObjectReference{ "Folder": config.folder.Reference(), "Datacenter": dc.Reference(), } if len(es) != len(expect) { t.Fail() } for _, e := range es { ref := e.Reference() if r, ok := expect[ref.Type]; ok { if r != ref { t.Errorf("%#v vs %#v", r, ref) } } else { t.Errorf("unexpected object %#v", e.Reference()) } } // finder tests ls, err := finder.ManagedObjectListChildren(ctx, ".") if err != nil { t.Error(err) } folders, err := dc.Folders(ctx) if err != nil { t.Fatal(err) } // Validated name properties are recursively retrieved for the datacenter and its folder children ipaths := []string{ folders.VmFolder.InventoryPath, folders.HostFolder.InventoryPath, folders.DatastoreFolder.InventoryPath, folders.NetworkFolder.InventoryPath, } var lpaths []string for _, p := range ls { lpaths = append(lpaths, p.Path) } if !reflect.DeepEqual(ipaths, lpaths) { t.Errorf("%#v != %#v\n", ipaths, lpaths) } // We have no VMs, expect NotFoundError _, err = finder.VirtualMachineList(ctx, "*") if err == nil { t.Error("expected error") } else { if _, ok := err.(*find.NotFoundError); !ok { t.Error(err) } } // Retrieve a missing property mdc := mo.Datacenter{} err = client.RetrieveOne(ctx, dc.Reference(), []string{"enoent"}, &mdc) if err == nil { t.Error("expected error") } else { switch fault := soap.ToVimFault(err).(type) { case *types.InvalidProperty: // ok default: t.Errorf("unexpected fault: %#v", fault) } } // Retrieve a nested property Map.Get(dc.Reference()).(*Datacenter).Configuration.DefaultHardwareVersionKey = "foo" mdc = mo.Datacenter{} err = client.RetrieveOne(ctx, dc.Reference(), []string{"configuration.defaultHardwareVersionKey"}, &mdc) if err != nil { t.Fatal(err) } if mdc.Configuration.DefaultHardwareVersionKey != "foo" { t.Fail() } // Retrieve a missing nested property mdc = mo.Datacenter{} err = client.RetrieveOne(ctx, dc.Reference(), []string{"configuration.enoent"}, &mdc) if err == nil { t.Error("expected error") } else { switch fault := soap.ToVimFault(err).(type) { case *types.InvalidProperty: // ok default: t.Errorf("unexpected fault: %#v", fault) } } // Retrieve an empty property err = client.RetrieveOne(ctx, dc.Reference(), []string{""}, &mdc) if err != nil { t.Error(err) } // Expect ManagedObjectNotFoundError Map.Remove(SpoofContext(), dc.Reference()) err = client.RetrieveOne(ctx, dc.Reference(), []string{"name"}, &mdc) if err == nil { t.Fatal("expected error") } } } func TestWaitForUpdates(t *testing.T) { folder := esx.RootFolder s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, folder)) ts := s.NewServer() defer ts.Close() ctx := context.Background() c, err := govmomi.NewClient(ctx, ts.URL, true) if err != nil { t.Fatal(err) } updates := make(chan bool) cb := func(once bool) func([]types.PropertyChange) bool { return func(pc []types.PropertyChange) bool { if len(pc) != 1 { t.Fail() } c := pc[0] if c.Op != types.PropertyChangeOpAssign { t.Fail() } if c.Name != "name" { t.Fail() } if c.Val.(string) != folder.Name { t.Fail() } if once == false { updates <- true } return once } } pc := property.DefaultCollector(c.Client) props := []string{"name"} err = property.Wait(ctx, pc, folder.Reference(), props, cb(true)) if err != nil { t.Error(err) } wctx, cancel := context.WithCancel(ctx) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() _ = property.Wait(wctx, pc, folder.Reference(), props, cb(false)) }() <-updates cancel() wg.Wait() // test object not found Map.Remove(SpoofContext(), folder.Reference()) err = property.Wait(ctx, pc, folder.Reference(), props, cb(true)) if err == nil { t.Error("expected error") } // test CancelWaitForUpdates p, err := pc.Create(ctx) if err != nil { t.Fatal(err) } // test the deprecated WaitForUpdates methods _, err = methods.WaitForUpdates(ctx, c.Client, &types.WaitForUpdates{This: p.Reference()}) if err != nil { t.Fatal(err) } err = p.CancelWaitForUpdates(ctx) if err != nil { t.Fatal(err) } } func TestIncrementalWaitForUpdates(t *testing.T) { ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() c, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } pc := property.DefaultCollector(c.Client) obj := Map.Any("VirtualMachine").(*VirtualMachine) ref := obj.Reference() vm := object.NewVirtualMachine(c.Client, ref) tests := []struct { name string props []string }{ {"1 field", []string{"runtime.powerState"}}, {"2 fields", []string{"summary.runtime.powerState", "summary.runtime.bootTime"}}, {"3 fields", []string{"runtime.powerState", "summary.runtime.powerState", "summary.runtime.bootTime"}}, {"parent field", []string{"runtime"}}, {"nested parent field", []string{"summary.runtime"}}, {"all", nil}, } // toggle power state to generate updates state := map[types.VirtualMachinePowerState]func(context.Context) (*object.Task, error){ types.VirtualMachinePowerStatePoweredOff: vm.PowerOn, types.VirtualMachinePowerStatePoweredOn: vm.PowerOff, } for i, test := range tests { var props []string matches := false wait := make(chan bool) host := obj.Summary.Runtime.Host // add host to filter just to have a different type in the filter filter := new(property.WaitFilter).Add(*host, host.Type, nil).Add(ref, ref.Type, test.props) go func() { perr := property.WaitForUpdates(ctx, pc, filter, func(updates []types.ObjectUpdate) bool { if updates[0].Kind == types.ObjectUpdateKindEnter { wait <- true return false } for _, update := range updates { for _, change := range update.ChangeSet { props = append(props, change.Name) } } if test.props == nil { // special case to test All flag matches = isTrue(filter.Spec.PropSet[0].All) && len(props) > 1 return matches } if len(props) > len(test.props) { return true } matches = reflect.DeepEqual(props, test.props) return matches }) if perr != nil { t.Error(perr) } wait <- matches }() <-wait // wait for enter _, _ = state[obj.Runtime.PowerState](ctx) if !<-wait { // wait for modify t.Errorf("%d: updates=%s, expected=%s", i, props, test.props) } } // Test ContainerView + Delete v, err := view.NewManager(c.Client).CreateContainerView(ctx, c.Client.ServiceContent.RootFolder, []string{ref.Type}, true) if err != nil { log.Fatal(err) } var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() filter := new(property.WaitFilter).Add(v.Reference(), ref.Type, tests[0].props, v.TraversalSpec()) perr := property.WaitForUpdates(ctx, pc, filter, func(updates []types.ObjectUpdate) bool { for _, update := range updates { switch update.Kind { case types.ObjectUpdateKindEnter: wg.Done() return false case types.ObjectUpdateKindModify: case types.ObjectUpdateKindLeave: return update.Obj == vm.Reference() } } return false }) if perr != nil { t.Error(perr) } }() wg.Wait() // wait for 1st enter wg.Add(1) task, _ := vm.PowerOff(ctx) _ = task.Wait(ctx) task, _ = vm.Destroy(ctx) wg.Wait() // wait for Delete to be reported task.Wait(ctx) } func TestWaitForUpdatesOneUpdateCalculation(t *testing.T) { /* * In this test, we use WaitForUpdatesEx in non-blocking way * by setting the MaxWaitSeconds to 0. * We filter on 'runtime.powerState' * Once we get the first 'enter' update, we change the * power state of the VM to generate a 'modify' update. * Once we get the modify, we can stop. */ ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() c, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } wait := make(chan bool) pc := property.DefaultCollector(c.Client) obj := Map.Any("VirtualMachine").(*VirtualMachine) ref := obj.Reference() vm := object.NewVirtualMachine(c.Client, ref) filter := new(property.WaitFilter).Add(ref, ref.Type, []string{"runtime.powerState"}) // WaitOptions.maxWaitSeconds: // A value of 0 causes WaitForUpdatesEx to do one update calculation and return any results. filter.Options = &types.WaitOptions{ MaxWaitSeconds: types.NewInt32(0), } // toggle power state to generate updates state := map[types.VirtualMachinePowerState]func(context.Context) (*object.Task, error){ types.VirtualMachinePowerStatePoweredOff: vm.PowerOn, types.VirtualMachinePowerStatePoweredOn: vm.PowerOff, } err = pc.CreateFilter(ctx, filter.CreateFilter) if err != nil { t.Fatal(err) } req := types.WaitForUpdatesEx{ This: pc.Reference(), Options: filter.Options, } go func() { for { res, err := methods.WaitForUpdatesEx(ctx, c.Client, &req) if err != nil { if ctx.Err() == context.Canceled { werr := pc.CancelWaitForUpdates(context.Background()) t.Error(werr) return } t.Error(err) return } set := res.Returnval if set == nil { // Retry if the result came back empty // That's a normal case when MaxWaitSeconds is set to 0. // It means we have no updates for now time.Sleep(500 * time.Millisecond) continue } req.Version = set.Version for _, fs := range set.FilterSet { // We expect the enter of VM first if fs.ObjectSet[0].Kind == types.ObjectUpdateKindEnter { wait <- true // Keep going continue } // We also expect a modify due to the power state change if fs.ObjectSet[0].Kind == types.ObjectUpdateKindModify { wait <- true // Now we can return to stop the routine return } } } }() // wait for the enter update. <-wait // Now change the VM power state, to generate a modify update _, err = state[obj.Runtime.PowerState](ctx) if err != nil { t.Error(err) } // wait for the modify update. <-wait } func TestPropertyCollectorWithUnsetValues(t *testing.T) { ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() client, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } pc := property.DefaultCollector(client.Client) vm := Map.Any("VirtualMachine") vmRef := vm.Reference() propSets := [][]string{ {"parentVApp"}, // unset string by default (not returned by RetrievePropertiesEx) {"rootSnapshot"}, // unset VirtualMachineSnapshot[] by default (returned by RetrievePropertiesEx) {"config.networkShaper.enabled"}, // unset at config.networkShaper level by default (not returned by RetrievePropertiesEx) {"parentVApp", "rootSnapshot", "config.networkShaper.enabled"}, // (not returned by RetrievePropertiesEx) {"name", "parentVApp", "rootSnapshot", "config.networkShaper.enabled"}, // (only name returned by RetrievePropertiesEx) {"name", "config.guestFullName"}, // both set (and returned by RetrievePropertiesEx)) } for _, propSet := range propSets { // RetrievePropertiesEx var objectContents []types.ObjectContent err = pc.RetrieveOne(ctx, vmRef, propSet, &objectContents) if err != nil { t.Error(err) } if len(objectContents) != 1 { t.Fatalf("len(objectContents) %#v != 1", len(objectContents)) } objectContent := objectContents[0] if objectContent.Obj != vmRef { t.Fatalf("objectContent.Obj %#v != vmRef %#v", objectContent.Obj, vmRef) } inRetrieveResponseCount := 0 for _, prop := range propSet { _, err := fieldValue(reflect.ValueOf(vm), prop) switch err { case nil: inRetrieveResponseCount++ found := false for _, objProp := range objectContent.PropSet { if prop == objProp.Name { found = true break } } if !found { t.Fatalf("prop %#v was not found", prop) } case errEmptyField: continue default: t.Error(err) } } if len(objectContent.PropSet) != inRetrieveResponseCount { t.Fatalf("len(objectContent.PropSet) %#v != inRetrieveResponseCount %#v", len(objectContent.PropSet), inRetrieveResponseCount) } if len(objectContent.MissingSet) != 0 { t.Fatalf("len(objectContent.MissingSet) %#v != 0", len(objectContent.MissingSet)) } // WaitForUpdatesEx f := func(once bool) func([]types.PropertyChange) bool { return func(pc []types.PropertyChange) bool { if len(propSet) != len(pc) { t.Fatalf("len(propSet) %#v != len(pc) %#v", len(propSet), len(pc)) } for _, prop := range propSet { found := false for _, objProp := range pc { switch err { case nil, errEmptyField: if prop == objProp.Name { found = true } default: t.Error(err) } if found { break } } if !found { t.Fatalf("prop %#v was not found", prop) } } return once } } err = property.Wait(ctx, pc, vmRef, propSet, f(true)) if err != nil { t.Error(err) } } } func TestCollectInterfaceType(t *testing.T) { // test that we properly collect an interface type (types.BaseVirtualDevice in this case) var config types.VirtualMachineConfigInfo config.Hardware.Device = append(config.Hardware.Device, new(types.VirtualFloppy)) _, err := fieldValue(reflect.ValueOf(&config), "hardware.device") if err != nil { t.Fatal(err) } } func TestExtractEmbeddedField(t *testing.T) { type YourResourcePool struct { mo.ResourcePool } type MyResourcePool struct { YourResourcePool } x := new(MyResourcePool) Map.Put(x) obj, ok := getObject(SpoofContext(), x.Reference()) if !ok { t.Error("expected obj") } if obj.Type() != reflect.ValueOf(new(mo.ResourcePool)).Elem().Type() { t.Errorf("unexpected type=%s", obj.Type().Name()) } } func TestPropertyCollectorFold(t *testing.T) { ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() client, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } cluster := Map.Any("ClusterComputeResource") compute := Map.Any("ComputeResource") // Test that we fold duplicate properties (rbvmomi depends on this) var content []types.ObjectContent err = client.Retrieve(ctx, []types.ManagedObjectReference{cluster.Reference()}, []string{"name", "name"}, &content) if err != nil { t.Fatal(err) } if len(content) != 1 { t.Fatalf("len(content)=%d", len(content)) } if len(content[0].PropSet) != 1 { t.Fatalf("len(PropSet)=%d", len(content[0].PropSet)) } // Test that we fold embedded type properties (rbvmomi depends on this) req := types.RetrieveProperties{ SpecSet: []types.PropertyFilterSpec{ { PropSet: []types.PropertySpec{ { DynamicData: types.DynamicData{}, Type: "ComputeResource", PathSet: []string{"name"}, }, { DynamicData: types.DynamicData{}, Type: "ClusterComputeResource", PathSet: []string{"name"}, }, }, ObjectSet: []types.ObjectSpec{ { Obj: compute.Reference(), }, { Obj: cluster.Reference(), }, }, }, }, } pc := client.PropertyCollector() res, err := pc.RetrieveProperties(ctx, req) if err != nil { t.Fatal(err) } content = res.Returnval if len(content) != 2 { t.Fatalf("len(content)=%d", len(content)) } for _, oc := range content { if len(oc.PropSet) != 1 { t.Errorf("%s len(PropSet)=%d", oc.Obj, len(oc.PropSet)) } } } func TestPropertyCollectorInvalidSpecName(t *testing.T) { obj := Map.Put(new(Folder)) folderPutChild(SpoofContext(), &obj.(*Folder).Folder, new(Folder)) pc := &PropertyCollector{} req := types.RetrievePropertiesEx{ SpecSet: []types.PropertyFilterSpec{ { PropSet: []types.PropertySpec{ { Type: obj.Reference().Type, PathSet: []string{"name"}, }, }, ObjectSet: []types.ObjectSpec{ { Obj: obj.Reference(), SelectSet: []types.BaseSelectionSpec{ &types.TraversalSpec{ Type: "Folder", Path: "childEntity", SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "enoent", }, }, }, }, }, }, }, }, } _, err := pc.collect(SpoofContext(), &req) if err == nil { t.Fatal("expected error") } if _, ok := err.(*types.InvalidArgument); !ok { t.Errorf("unexpected fault: %#v", err) } } func TestPropertyCollectorRecursiveSelectSet(t *testing.T) { ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() client, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } // Capture of PowerCLI's Get-VM spec req := types.RetrieveProperties{ SpecSet: []types.PropertyFilterSpec{ { PropSet: []types.PropertySpec{ { Type: "VirtualMachine", PathSet: []string{ "runtime.host", "parent", "resourcePool", "resourcePool", "datastore", "config.swapPlacement", "config.version", "config.instanceUuid", "config.guestId", "config.annotation", "summary.storage.committed", "summary.storage.uncommitted", "summary.storage.committed", "config.template", }, }, }, ObjectSet: []types.ObjectSpec{ { Obj: client.ServiceContent.RootFolder, Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "traverseFolders", }, Type: "Folder", Path: "childEntity", Skip: types.NewBool(true), SelectSet: []types.BaseSelectionSpec{ &types.TraversalSpec{ Type: "HostSystem", Path: "vm", Skip: types.NewBool(false), }, &types.TraversalSpec{ Type: "ComputeResource", Path: "host", Skip: types.NewBool(true), SelectSet: []types.BaseSelectionSpec{ &types.TraversalSpec{ Type: "HostSystem", Path: "vm", Skip: types.NewBool(false), }, }, }, &types.TraversalSpec{ Type: "Datacenter", Path: "hostFolder", Skip: types.NewBool(true), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "traverseFolders", }, }, }, &types.SelectionSpec{ Name: "traverseFolders", }, }, }, }, }, }, }, }, } pc := client.PropertyCollector() res, err := pc.RetrieveProperties(ctx, req) if err != nil { t.Fatal(err) } content := res.Returnval count := m.Count() if len(content) != count.Machine { t.Fatalf("len(content)=%d", len(content)) } } func TestPropertyCollectorSelectionSpec(t *testing.T) { ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() client, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } // Capture of PowerCLI's Start-VM spec // Differs from the norm in that: // 1) Named SelectionSpec before TraversalSpec is defined // 2) Skip=false for all mo types, but PropSet only has Type VirtualMachine req := types.RetrieveProperties{ SpecSet: []types.PropertyFilterSpec{ { PropSet: []types.PropertySpec{ { Type: "VirtualMachine", All: types.NewBool(false), PathSet: []string{"name", "parent", "runtime.host", "config.template"}, }, }, ObjectSet: []types.ObjectSpec{ { Obj: client.ServiceContent.RootFolder, Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "folderTraversalSpec", }, Type: "Folder", Path: "childEntity", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "computeResourceRpTraversalSpec", }, &types.SelectionSpec{ Name: "computeResourceHostTraversalSpec", }, &types.SelectionSpec{ Name: "folderTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterHostTraversalSpec", }, &types.SelectionSpec{ Name: "hostVmTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, &types.SelectionSpec{ Name: "hostRpTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterVmTraversalSpec", }, &types.SelectionSpec{ Name: "datastoreVmTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterDatastoreTraversalSpec", }, &types.SelectionSpec{ Name: "vappTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterNetworkTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "computeResourceHostTraversalSpec", }, Type: "ComputeResource", Path: "host", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "hostVmTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "computeResourceRpTraversalSpec", }, Type: "ComputeResource", Path: "resourcePool", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, &types.SelectionSpec{ Name: "vappTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterHostTraversalSpec", }, Type: "Datacenter", Path: "hostFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, Type: "ResourcePool", Path: "resourcePool", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "hostVmTraversalSpec", }, Type: "HostSystem", Path: "vm", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterVmTraversalSpec", }, Type: "Datacenter", Path: "vmFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, Type: "ResourcePool", Path: "vm", Skip: types.NewBool(false), }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datastoreVmTraversalSpec", }, Type: "Datastore", Path: "vm", Skip: types.NewBool(false), }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterDatastoreTraversalSpec", }, Type: "Datacenter", Path: "datastoreFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "vappTraversalSpec", }, Type: "VirtualApp", Path: "resourcePool", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "vappTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "vappVmTraversalSpec", }, Type: "VirtualApp", Path: "vm", Skip: types.NewBool(false), }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "distributedSwitchHostTraversalSpec", }, Type: "DistributedVirtualSwitch", Path: "summary.hostMember", Skip: types.NewBool(false), }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "distributedSwitchVmTraversalSpec", }, Type: "DistributedVirtualSwitch", Path: "summary.vm", Skip: types.NewBool(false), }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterNetworkTraversalSpec", }, Type: "Datacenter", Path: "networkFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "hostRpTraversalSpec", }, Type: "HostSystem", Path: "parent", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "computeResourceRpTraversalSpec", }, }, }, }, }, }, }, }, } pc := client.PropertyCollector() res, err := pc.RetrieveProperties(ctx, req) if err != nil { t.Fatal(err) } content := res.Returnval count := m.Count() if len(content) != count.Machine { t.Fatalf("len(content)=%d", len(content)) } } func TestIssue945(t *testing.T) { // pyvsphere request xml := ` propertyCollector VirtualMachine name group-d1 false visitFolders Folder childEntity false visitFolders dcToHf dcToVmf crToH crToRp dcToDs hToVm rpToVm dcToVmf Datacenter vmFolder false visitFolders dcToDs Datacenter datastore false visitFolders dcToHf Datacenter hostFolder false visitFolders crToH ComputeResource host false crToRp ComputeResource resourcePool false rpToRp rpToVm rpToRp ResourcePool resourcePool false rpToRp rpToVm hToVm HostSystem vm false visitFolders rpToVm ResourcePool vm false ` method, err := UnmarshalBody(defaultMapType, []byte(xml)) if err != nil { t.Fatal(err) } req := method.Body.(*types.RetrievePropertiesEx) ctx := context.Background() m := VPX() defer m.Remove() err = m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() client, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } res, err := methods.RetrievePropertiesEx(ctx, client, req) if err != nil { t.Fatal(err) } content := res.Returnval.Objects count := m.Count() if len(content) != count.Machine { t.Fatalf("len(content)=%d", len(content)) } } func TestPropertyCollectorSession(t *testing.T) { // aka issue-923 ctx := context.Background() m := VPX() defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() u := s.URL.User s.URL.User = nil // skip Login() c, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } for i := 0; i < 2; i++ { if err = c.Login(ctx, u); err != nil { t.Fatal(err) } if err = c.Login(ctx, u); err == nil { t.Error("expected Login failure") // Login fails if we already have a session } pc := property.DefaultCollector(c.Client) filter := new(property.WaitFilter).Add(c.ServiceContent.RootFolder, "Folder", []string{"name"}) if err = pc.CreateFilter(ctx, filter.CreateFilter); err != nil { t.Fatal(err) } res, err := pc.WaitForUpdates(ctx, "") if err != nil { t.Error(err) } if len(res.FilterSet) != 1 { t.Errorf("len FilterSet=%d", len(res.FilterSet)) } if err = c.Logout(ctx); err != nil { t.Fatal(err) } } } func TestPropertyCollectorNoPathSet(t *testing.T) { ctx := context.Background() m := VPX() m.Datacenter = 3 m.Folder = 2 defer m.Remove() err := m.Create() if err != nil { t.Fatal(err) } s := m.Service.NewServer() defer s.Close() client, err := govmomi.NewClient(ctx, s.URL, true) if err != nil { t.Fatal(err) } // request from https://github.com/vmware/govmomi/issues/1199 req := &types.RetrieveProperties{ This: client.ServiceContent.PropertyCollector, SpecSet: []types.PropertyFilterSpec{ { PropSet: []types.PropertySpec{ { Type: "Datacenter", All: types.NewBool(false), PathSet: nil, }, }, ObjectSet: []types.ObjectSpec{ { Obj: client.ServiceContent.RootFolder, Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, Type: "ResourcePool", Path: "resourcePool", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, Type: "ResourcePool", Path: "vm", Skip: types.NewBool(false), SelectSet: nil, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "computeResourceRpTraversalSpec", }, Type: "ComputeResource", Path: "resourcePool", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "resourcePoolTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "computeResourceHostTraversalSpec", }, Type: "ComputeResource", Path: "host", Skip: types.NewBool(false), SelectSet: nil, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterVmTraversalSpec", }, Type: "Datacenter", Path: "vmFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterHostTraversalSpec", }, Type: "Datacenter", Path: "hostFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "hostVmTraversalSpec", }, Type: "HostSystem", Path: "vm", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "datacenterDatastoreTraversalSpec", }, Type: "Datacenter", Path: "datastoreFolder", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, }, }, &types.TraversalSpec{ SelectionSpec: types.SelectionSpec{ Name: "folderTraversalSpec", }, Type: "Folder", Path: "childEntity", Skip: types.NewBool(false), SelectSet: []types.BaseSelectionSpec{ &types.SelectionSpec{ Name: "folderTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterHostTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterVmTraversalSpec", }, &types.SelectionSpec{ Name: "computeResourceRpTraversalSpec", }, &types.SelectionSpec{ Name: "computeResourceHostTraversalSpec", }, &types.SelectionSpec{ Name: "hostVmTraversalSpec", }, &types.SelectionSpec{ Name: "resourcePoolVmTraversalSpec", }, &types.SelectionSpec{ Name: "datacenterDatastoreTraversalSpec", }, }, }, }, }, }, ReportMissingObjectsInResults: (*bool)(nil), }, }, } res, err := methods.RetrieveProperties(ctx, client, req) if err != nil { t.Fatal(err) } content := res.Returnval count := m.Count() if len(content) != count.Datacenter { t.Fatalf("len(content)=%d", len(content)) } }