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