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