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