github.com/vmware/govmomi@v0.51.0/simulator/folder_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 "reflect" 10 "strings" 11 "testing" 12 13 "github.com/vmware/govmomi" 14 "github.com/vmware/govmomi/fault" 15 "github.com/vmware/govmomi/find" 16 "github.com/vmware/govmomi/object" 17 "github.com/vmware/govmomi/simulator/esx" 18 "github.com/vmware/govmomi/simulator/vpx" 19 "github.com/vmware/govmomi/vim25" 20 "github.com/vmware/govmomi/vim25/methods" 21 "github.com/vmware/govmomi/vim25/mo" 22 "github.com/vmware/govmomi/vim25/soap" 23 "github.com/vmware/govmomi/vim25/types" 24 ) 25 26 func addStandaloneHostTask(folder *object.Folder, spec types.HostConnectSpec) (*object.Task, error) { 27 // TODO: add govmomi wrapper 28 req := types.AddStandaloneHost_Task{ 29 This: folder.Reference(), 30 Spec: spec, 31 AddConnected: true, 32 } 33 34 res, err := methods.AddStandaloneHost_Task(context.TODO(), folder.Client(), &req) 35 if err != nil { 36 return nil, err 37 } 38 39 task := object.NewTask(folder.Client(), res.Returnval) 40 return task, nil 41 } 42 43 func TestFolderESX(t *testing.T) { 44 content := esx.ServiceContent 45 s := New(NewServiceInstance(NewContext(), content, esx.RootFolder)) 46 47 ts := s.NewServer() 48 defer ts.Close() 49 50 ctx := context.Background() 51 c, err := govmomi.NewClient(ctx, ts.URL, true) 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 f := object.NewRootFolder(c.Client) 57 58 _, err = f.CreateFolder(ctx, "foo") 59 if err == nil { 60 t.Error("expected error") 61 } 62 63 _, err = f.CreateDatacenter(ctx, "foo") 64 if err == nil { 65 t.Fatal("expected error") 66 } 67 68 finder := find.NewFinder(c.Client, false) 69 dc, err := finder.DatacenterOrDefault(ctx, "") 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 folders, err := dc.Folders(ctx) 75 if err != nil { 76 t.Fatal(err) 77 } 78 79 spec := types.HostConnectSpec{} 80 _, err = addStandaloneHostTask(folders.HostFolder, spec) 81 if err == nil { 82 t.Fatal("expected error") 83 } 84 85 _, err = folders.DatastoreFolder.CreateStoragePod(ctx, "pod") 86 if err == nil { 87 t.Fatal("expected error") 88 } 89 } 90 91 func TestFolderVC(t *testing.T) { 92 content := vpx.ServiceContent 93 ctx := NewContext() 94 s := New(NewServiceInstance(ctx, content, vpx.RootFolder)) 95 96 ts := s.NewServer() 97 defer ts.Close() 98 99 c, err := govmomi.NewClient(ctx, ts.URL, true) 100 if err != nil { 101 t.Fatal(err) 102 } 103 104 f := object.NewRootFolder(c.Client) 105 106 ff, err := f.CreateFolder(ctx, "foo") 107 if err != nil { 108 t.Error(err) 109 } 110 111 _, err = f.CreateFolder(ctx, "foo") 112 if err == nil { 113 t.Error("expected error") 114 } 115 116 var dup *types.DuplicateName 117 _, ok := fault.As(err, &dup) 118 if !ok { 119 t.Fatal("expected DuplicateName type") 120 } 121 if dup.Object != ff.Reference() { 122 t.Fatal("Duplicate object not matched") 123 } 124 125 dc, err := f.CreateDatacenter(ctx, "bar") 126 if err != nil { 127 t.Error(err) 128 } 129 130 for _, ref := range []object.Reference{ff, dc} { 131 o := ctx.Map.Get(ref.Reference()) 132 if o == nil { 133 t.Fatalf("failed to find %#v", ref) 134 } 135 136 e := o.(mo.Entity).Entity() 137 if *e.Parent != f.Reference() { 138 t.Fail() 139 } 140 } 141 142 dc, err = ff.CreateDatacenter(ctx, "biz") 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 _, err = folders.VmFolder.CreateStoragePod(ctx, "pod") 153 if err == nil { 154 t.Error("expected error") 155 } 156 157 pod, err := folders.DatastoreFolder.CreateStoragePod(ctx, "pod") 158 if err != nil { 159 t.Error(err) 160 } 161 162 _, err = folders.DatastoreFolder.CreateStoragePod(ctx, "pod") 163 if err == nil { 164 t.Error("expected error") 165 } 166 _, ok = fault.As(err, &dup) 167 if !ok { 168 t.Fatal("expected DuplicateName type") 169 } 170 if dup.Object != pod.Reference() { 171 t.Fatal("Duplicate object not matched") 172 } 173 174 tests := []struct { 175 name string 176 state types.TaskInfoState 177 }{ 178 {"", types.TaskInfoStateError}, 179 {"foo.local", types.TaskInfoStateSuccess}, 180 } 181 182 for _, test := range tests { 183 spec := types.HostConnectSpec{ 184 HostName: test.name, 185 } 186 187 task, err := addStandaloneHostTask(folders.HostFolder, spec) 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 res, err := task.WaitForResult(ctx, nil) 193 if test.state == types.TaskInfoStateError { 194 if err == nil { 195 t.Error("expected error") 196 } 197 198 if res.Result != nil { 199 t.Error("expected nil") 200 } 201 } else { 202 if err != nil { 203 t.Fatal(err) 204 } 205 206 ref, ok := res.Result.(types.ManagedObjectReference) 207 if !ok { 208 t.Errorf("expected moref, got type=%T", res.Result) 209 } 210 host := ctx.Map.Get(ref).(*HostSystem) 211 if host.Name != test.name { 212 t.Fail() 213 } 214 215 if ref == esx.HostSystem.Self { 216 t.Error("expected new host Self reference") 217 } 218 if *host.Summary.Host == esx.HostSystem.Self { 219 t.Error("expected new host summary Self reference") 220 } 221 222 pool := ctx.Map.Get(*host.Parent).(*mo.ComputeResource).ResourcePool 223 if *pool == esx.ResourcePool.Self { 224 t.Error("expected new pool Self reference") 225 } 226 } 227 228 if res.State != test.state { 229 t.Fatalf("%s", res.State) 230 } 231 } 232 } 233 234 func TestFolderSpecialCharaters(t *testing.T) { 235 content := vpx.ServiceContent 236 ctx := NewContext() 237 s := New(NewServiceInstance(ctx, content, vpx.RootFolder)) 238 239 ts := s.NewServer() 240 defer ts.Close() 241 242 c, err := govmomi.NewClient(ctx, ts.URL, true) 243 if err != nil { 244 t.Fatal(err) 245 } 246 247 f := object.NewRootFolder(c.Client) 248 249 tests := []struct { 250 name string 251 expected string 252 }{ 253 {`/`, `%2f`}, 254 {`\`, `%5c`}, 255 {`%`, `%25`}, 256 // multiple special characters 257 {`%%`, `%25%25`}, 258 } 259 260 for _, test := range tests { 261 ff, err := f.CreateFolder(ctx, test.name) 262 if err != nil { 263 t.Fatal(err) 264 } 265 266 o := ctx.Map.Get(ff.Reference()) 267 if o == nil { 268 t.Fatalf("failed to find %#v", ff) 269 } 270 271 e := o.(mo.Entity).Entity() 272 if e.Name != test.expected { 273 t.Errorf("expected %s, got %s", test.expected, e.Name) 274 } 275 } 276 } 277 278 func TestFolderFaults(t *testing.T) { 279 f := Folder{} 280 f.ChildType = []string{"VirtualMachine"} 281 282 if f.CreateFolder(nil, nil).Fault() == nil { 283 t.Error("expected fault") 284 } 285 286 if f.CreateDatacenter(nil, nil).Fault() == nil { 287 t.Error("expected fault") 288 } 289 } 290 291 func TestRegisterVm(t *testing.T) { 292 for i, model := range []*Model{ESX(), VPX()} { 293 match := "*" 294 if i == 1 { 295 model.App = 1 296 match = "*APP*" 297 } 298 defer model.Remove() 299 err := model.Create() 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 s := model.Service.NewServer() 305 defer s.Close() 306 307 ctx := model.Service.Context 308 309 c, err := govmomi.NewClient(ctx, s.URL, true) 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 finder := find.NewFinder(c.Client, false) 315 dc, err := finder.DefaultDatacenter(ctx) 316 if err != nil { 317 t.Fatal(err) 318 } 319 320 finder.SetDatacenter(dc) 321 322 folders, err := dc.Folders(ctx) 323 if err != nil { 324 t.Fatal(err) 325 } 326 327 vmFolder := folders.VmFolder 328 329 vms, err := finder.VirtualMachineList(ctx, match) 330 if err != nil { 331 t.Fatal(err) 332 } 333 334 vm := ctx.Map.Get(vms[0].Reference()).(*VirtualMachine) 335 336 req := types.RegisterVM_Task{ 337 This: vmFolder.Reference(), 338 AsTemplate: true, 339 } 340 341 steps := []struct { 342 e any 343 f func() 344 }{ 345 { 346 new(types.InvalidArgument), func() { req.AsTemplate = false }, 347 }, 348 { 349 new(types.InvalidArgument), func() { req.Pool = vm.ResourcePool }, 350 }, 351 { 352 new(types.InvalidArgument), func() { req.Path = "enoent" }, 353 }, 354 { 355 new(types.InvalidDatastorePath), func() { req.Path = vm.Config.Files.VmPathName + "-enoent" }, 356 }, 357 { 358 new(types.NotFound), func() { req.Path = vm.Config.Files.VmPathName }, 359 }, 360 { 361 new(types.AlreadyExists), func() { ctx.Map.Remove(ctx, vm.Reference()) }, 362 }, 363 { 364 nil, func() {}, 365 }, 366 } 367 368 for _, step := range steps { 369 res, err := methods.RegisterVM_Task(ctx, c.Client, &req) 370 if err != nil { 371 t.Fatal(err) 372 } 373 374 ct := object.NewTask(c.Client, res.Returnval) 375 _ = ct.Wait(ctx) 376 377 rt := ctx.Map.Get(res.Returnval).(*Task) 378 379 if step.e != nil { 380 fault := rt.Info.Error.Fault 381 if reflect.TypeOf(fault) != reflect.TypeOf(step.e) { 382 t.Errorf("%T != %T", fault, step.e) 383 } 384 } else { 385 if rt.Info.Error != nil { 386 t.Errorf("unexpected error: %#v", rt.Info.Error) 387 } 388 } 389 390 step.f() 391 } 392 393 nvm, err := finder.VirtualMachine(ctx, vm.Name) 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 if nvm.Reference() == vm.Reference() { 399 t.Error("expected new moref") 400 } 401 402 onTask, _ := nvm.PowerOn(ctx) 403 _ = onTask.Wait(ctx) 404 405 steps = []struct { 406 e any 407 f func() 408 }{ 409 { 410 types.InvalidPowerState{}, func() { offTask, _ := nvm.PowerOff(ctx); _ = offTask.Wait(ctx) }, 411 }, 412 { 413 nil, func() {}, 414 }, 415 { 416 types.ManagedObjectNotFound{}, func() {}, 417 }, 418 } 419 420 for _, step := range steps { 421 err = nvm.Unregister(ctx) 422 423 if step.e != nil { 424 fault := soap.ToSoapFault(err).VimFault() 425 if reflect.TypeOf(fault) != reflect.TypeOf(step.e) { 426 t.Errorf("%T != %T", fault, step.e) 427 } 428 } else { 429 if err != nil { 430 t.Errorf("unexpected error: %#v", err) 431 } 432 } 433 434 step.f() 435 } 436 } 437 } 438 439 func TestFolderMoveInto(t *testing.T) { 440 ctx := context.Background() 441 model := VPX() 442 defer model.Remove() 443 err := model.Create() 444 if err != nil { 445 t.Fatal(err) 446 } 447 448 s := model.Service.NewServer() 449 defer s.Close() 450 451 c, err := govmomi.NewClient(ctx, s.URL, true) 452 if err != nil { 453 t.Fatal(err) 454 } 455 456 finder := find.NewFinder(c.Client, false) 457 458 dc, err := finder.DefaultDatacenter(ctx) 459 if err != nil { 460 t.Fatal(err) 461 } 462 463 finder.SetDatacenter(dc) 464 465 folders, err := dc.Folders(ctx) 466 if err != nil { 467 t.Fatal(err) 468 } 469 470 ds, err := finder.DefaultDatastore(ctx) 471 if err != nil { 472 t.Fatal(err) 473 } 474 475 // Move Datastore into a vm folder should fail 476 task, err := folders.VmFolder.MoveInto(ctx, []types.ManagedObjectReference{ds.Reference()}) 477 if err != nil { 478 t.Fatal(err) 479 } 480 481 err = task.Wait(ctx) 482 if err == nil { 483 t.Errorf("expected error") 484 } 485 486 // Move Datacenter into a sub folder should pass 487 f, err := object.NewRootFolder(c.Client).CreateFolder(ctx, "foo") 488 if err != nil { 489 t.Error(err) 490 } 491 492 task, _ = f.MoveInto(ctx, []types.ManagedObjectReference{dc.Reference()}) 493 err = task.Wait(ctx) 494 if err != nil { 495 t.Error(err) 496 } 497 498 pod, err := folders.DatastoreFolder.CreateStoragePod(ctx, "pod") 499 if err != nil { 500 t.Error(err) 501 } 502 503 // Moving any type other than Datastore into a StoragePod should fail 504 task, _ = pod.MoveInto(ctx, []types.ManagedObjectReference{dc.Reference()}) 505 err = task.Wait(ctx) 506 if err == nil { 507 t.Error("expected error") 508 } 509 510 // Move DS into a StoragePod 511 task, _ = pod.MoveInto(ctx, []types.ManagedObjectReference{ds.Reference()}) 512 err = task.Wait(ctx) 513 if err != nil { 514 t.Error(err) 515 } 516 } 517 518 func TestFolderCreateDVS(t *testing.T) { 519 ctx := context.Background() 520 model := VPX() 521 defer model.Remove() 522 err := model.Create() 523 if err != nil { 524 t.Fatal(err) 525 } 526 527 s := model.Service.NewServer() 528 defer s.Close() 529 530 c, err := govmomi.NewClient(ctx, s.URL, true) 531 if err != nil { 532 t.Fatal(err) 533 } 534 535 finder := find.NewFinder(c.Client, false) 536 537 dc, err := finder.DefaultDatacenter(ctx) 538 if err != nil { 539 t.Fatal(err) 540 } 541 542 finder.SetDatacenter(dc) 543 544 folders, err := dc.Folders(ctx) 545 if err != nil { 546 t.Fatal(err) 547 } 548 549 var spec types.DVSCreateSpec 550 spec.ConfigSpec = &types.VMwareDVSConfigSpec{} 551 spec.ConfigSpec.GetDVSConfigSpec().Name = "foo" 552 553 task, err := folders.NetworkFolder.CreateDVS(ctx, spec) 554 if err != nil { 555 t.Fatal(err) 556 } 557 558 err = task.Wait(ctx) 559 if err != nil { 560 t.Error(err) 561 } 562 563 net, err := finder.Network(ctx, "foo") 564 if err != nil { 565 t.Error(err) 566 } 567 568 dvs, ok := net.(*object.DistributedVirtualSwitch) 569 if !ok { 570 t.Fatalf("%T is not of type %T", net, dvs) 571 } 572 573 task, err = folders.NetworkFolder.CreateDVS(ctx, spec) 574 if err != nil { 575 t.Fatal(err) 576 } 577 578 err = task.Wait(ctx) 579 if err == nil { 580 t.Error("expected error") 581 } 582 583 pspec := types.DVPortgroupConfigSpec{Name: "xnet"} 584 task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{pspec}) 585 if err != nil { 586 t.Fatal(err) 587 } 588 589 err = task.Wait(ctx) 590 if err != nil { 591 t.Error(err) 592 } 593 594 net, err = finder.Network(ctx, "xnet") 595 if err != nil { 596 t.Error(err) 597 } 598 599 pg, ok := net.(*object.DistributedVirtualPortgroup) 600 if !ok { 601 t.Fatalf("%T is not of type %T", net, pg) 602 } 603 604 backing, err := net.EthernetCardBackingInfo(ctx) 605 if err != nil { 606 t.Fatal(err) 607 } 608 609 info, ok := backing.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo) 610 if ok { 611 if info.Port.SwitchUuid == "" || info.Port.PortgroupKey == "" { 612 t.Errorf("invalid port: %#v", info.Port) 613 } 614 } else { 615 t.Fatalf("%T is not of type %T", net, info) 616 } 617 618 task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{pspec}) 619 if err != nil { 620 t.Fatal(err) 621 } 622 623 err = task.Wait(ctx) 624 if err == nil { 625 t.Error("expected error") 626 } 627 } 628 629 func TestPlaceVmsXClusterCreateAndPowerOn(t *testing.T) { 630 vpx := VPX() 631 vpx.Cluster = 3 632 633 Test(func(ctx context.Context, c *vim25.Client) { 634 finder := find.NewFinder(c, false) 635 636 spec := types.PlaceVmsXClusterSpec{} 637 638 pools, err := finder.ResourcePoolList(ctx, "/DC0/host/DC0_C*/*") 639 if err != nil { 640 t.Fatal(err) 641 } 642 643 for _, pool := range pools { 644 spec.ResourcePools = append(spec.ResourcePools, pool.Reference()) 645 } 646 647 spec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{ 648 ConfigSpec: types.VirtualMachineConfigSpec{ 649 Name: "test-vm", 650 }, 651 }} 652 653 folder := object.NewRootFolder(c) 654 res, err := folder.PlaceVmsXCluster(ctx, spec) 655 if err != nil { 656 t.Fatal(err) 657 } 658 659 if len(res.PlacementInfos) != len(spec.VmPlacementSpecs) { 660 t.Errorf("%d PlacementInfos vs %d VmPlacementSpecs", len(res.PlacementInfos), len(spec.VmPlacementSpecs)) 661 } 662 }, vpx) 663 } 664 665 func TestPlaceVmsXClusterRelocate(t *testing.T) { 666 vpx := VPX() 667 vpx.Cluster = 3 668 669 Test(func(ctx context.Context, c *vim25.Client) { 670 finder := find.NewFinder(c, true) 671 datacenter, err := finder.DefaultDatacenter(ctx) 672 if err != nil { 673 t.Fatalf("failed to get default datacenter: %v", err) 674 } 675 finder.SetDatacenter(datacenter) 676 677 pools, err := finder.ResourcePoolList(ctx, "/DC0/host/DC0_C*/*") 678 if err != nil { 679 t.Fatal(err) 680 } 681 682 var poolMoRefs []types.ManagedObjectReference 683 for _, pool := range pools { 684 poolMoRefs = append(poolMoRefs, pool.Reference()) 685 } 686 687 vmMoRef := Map(ctx).Any("VirtualMachine").(*VirtualMachine).Reference() 688 689 cfgSpec := types.VirtualMachineConfigSpec{} 690 691 tests := []struct { 692 name string 693 poolMoRefs []types.ManagedObjectReference 694 configSpec types.VirtualMachineConfigSpec 695 relocateSpec *types.VirtualMachineRelocateSpec 696 vmMoRef *types.ManagedObjectReference 697 expectedErr string 698 }{ 699 { 700 "relocate without any resource pools", 701 nil, 702 cfgSpec, 703 &types.VirtualMachineRelocateSpec{}, 704 &vmMoRef, 705 "InvalidArgument", 706 }, 707 { 708 "relocate without a relocate spec", 709 poolMoRefs, 710 cfgSpec, 711 nil, 712 &vmMoRef, 713 "InvalidArgument", 714 }, 715 { 716 "relocate without a vm in the placement spec", 717 poolMoRefs, 718 cfgSpec, 719 &types.VirtualMachineRelocateSpec{}, 720 nil, 721 "InvalidArgument", 722 }, 723 { 724 "relocate with a non-existing vm in the placement spec", 725 poolMoRefs, 726 cfgSpec, 727 &types.VirtualMachineRelocateSpec{}, 728 &types.ManagedObjectReference{ 729 Type: "VirtualMachine", 730 Value: "fake-vm-999", 731 }, 732 "InvalidArgument", 733 }, 734 { 735 "relocate with an empty relocate spec", 736 poolMoRefs, 737 cfgSpec, 738 &types.VirtualMachineRelocateSpec{}, 739 &vmMoRef, 740 "", 741 }, 742 } 743 744 for testNo, test := range tests { 745 test := test // assign to local var since loop var is reused 746 747 truebool := true 748 749 placeVmsXClusterSpec := types.PlaceVmsXClusterSpec{ 750 ResourcePools: test.poolMoRefs, 751 PlacementType: string(types.PlaceVmsXClusterSpecPlacementTypeRelocate), 752 HostRecommRequired: &truebool, 753 DatastoreRecommRequired: &truebool, 754 } 755 756 placeVmsXClusterSpec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{ 757 ConfigSpec: test.configSpec, 758 Vm: test.vmMoRef, 759 RelocateSpec: test.relocateSpec, 760 }} 761 762 folder := object.NewRootFolder(c) 763 res, err := folder.PlaceVmsXCluster(ctx, placeVmsXClusterSpec) 764 765 if err == nil && test.expectedErr != "" { 766 t.Fatalf("Test %v: expected error %q, received nil", testNo, test.expectedErr) 767 } else if err != nil && 768 (test.expectedErr == "" || !strings.Contains(err.Error(), test.expectedErr)) { 769 t.Fatalf("Test %v: expected error %q, received %v", testNo, test.expectedErr, err) 770 } 771 772 if err == nil { 773 if len(res.PlacementInfos) != len(placeVmsXClusterSpec.VmPlacementSpecs) { 774 t.Errorf("Test %v: %d PlacementInfos vs %d VmPlacementSpecs", testNo, len(res.PlacementInfos), len(placeVmsXClusterSpec.VmPlacementSpecs)) 775 } 776 777 for _, pinfo := range res.PlacementInfos { 778 for _, action := range pinfo.Recommendation.Action { 779 if relocateAction, ok := action.(*types.ClusterClusterRelocatePlacementAction); ok { 780 if relocateAction.TargetHost == nil { 781 t.Errorf("Test %v: received nil host recommendation", testNo) 782 } 783 } else { 784 t.Errorf("Test %v: received wrong action type in recommendation", testNo) 785 } 786 } 787 } 788 } 789 } 790 }, vpx) 791 } 792 793 func TestPlaceVmsXClusterReconfigure(t *testing.T) { 794 vpx := VPX() 795 // All hosts are cluster hosts 796 vpx.Host = 0 797 798 Test(func(ctx context.Context, c *vim25.Client) { 799 finder := find.NewFinder(c, true) 800 datacenter, err := finder.DefaultDatacenter(ctx) 801 if err != nil { 802 t.Fatalf("failed to get default datacenter: %v", err) 803 } 804 finder.SetDatacenter(datacenter) 805 806 vm := Map(ctx).Any("VirtualMachine").(*VirtualMachine) 807 host := Map(ctx).Get(vm.Runtime.Host.Reference()).(*HostSystem) 808 cluster := Map(ctx).Get(*host.Parent).(*ClusterComputeResource) 809 pool := Map(ctx).Get(*cluster.ResourcePool).(*ResourcePool) 810 811 var poolMoRefs []types.ManagedObjectReference 812 poolMoRefs = append(poolMoRefs, pool.Reference()) 813 814 cfgSpec := types.VirtualMachineConfigSpec{} 815 816 tests := []struct { 817 name string 818 poolMoRefs []types.ManagedObjectReference 819 configSpec types.VirtualMachineConfigSpec 820 relocateSpec *types.VirtualMachineRelocateSpec 821 vmMoRef *types.ManagedObjectReference 822 expectedErr string 823 }{ 824 { 825 "reconfigure without any resource pools", 826 nil, 827 cfgSpec, 828 nil, 829 &vm.Self, 830 "InvalidArgument", 831 }, 832 { 833 "reconfigure with a relocate spec", 834 poolMoRefs, 835 cfgSpec, 836 &types.VirtualMachineRelocateSpec{}, 837 &vm.Self, 838 "InvalidArgument", 839 }, 840 { 841 "reconfigure without a vm in the placement spec", 842 poolMoRefs, 843 cfgSpec, 844 nil, 845 nil, 846 "InvalidArgument", 847 }, 848 { 849 "reconfigure with a non-existing vm in the placement spec", 850 poolMoRefs, 851 cfgSpec, 852 nil, 853 &types.ManagedObjectReference{ 854 Type: "VirtualMachine", 855 Value: "fake-vm-999", 856 }, 857 "InvalidArgument", 858 }, 859 { 860 "reconfigure with an empty config spec", 861 poolMoRefs, 862 cfgSpec, 863 nil, 864 &vm.Self, 865 "", 866 }, 867 } 868 869 for testNo, test := range tests { 870 test := test // assign to local var since loop var is reused 871 872 placeVmsXClusterSpec := types.PlaceVmsXClusterSpec{ 873 ResourcePools: test.poolMoRefs, 874 PlacementType: string(types.PlaceVmsXClusterSpecPlacementTypeReconfigure), 875 HostRecommRequired: types.NewBool(true), 876 DatastoreRecommRequired: types.NewBool(true), 877 } 878 879 placeVmsXClusterSpec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{ 880 ConfigSpec: test.configSpec, 881 Vm: test.vmMoRef, 882 RelocateSpec: test.relocateSpec, 883 }} 884 885 folder := object.NewRootFolder(c) 886 res, err := folder.PlaceVmsXCluster(ctx, placeVmsXClusterSpec) 887 888 if err == nil && test.expectedErr != "" { 889 t.Fatalf("Test %v: expected error %q, received nil", testNo, test.expectedErr) 890 } else if err != nil && 891 (test.expectedErr == "" || !strings.Contains(err.Error(), test.expectedErr)) { 892 t.Fatalf("Test %v: expected error %q, received %v", testNo, test.expectedErr, err) 893 } 894 895 if err == nil { 896 if len(res.PlacementInfos) != len(placeVmsXClusterSpec.VmPlacementSpecs) { 897 t.Errorf("%d PlacementInfos vs %d VmPlacementSpecs", len(res.PlacementInfos), len(placeVmsXClusterSpec.VmPlacementSpecs)) 898 } 899 900 for _, pinfo := range res.PlacementInfos { 901 for _, action := range pinfo.Recommendation.Action { 902 if reconfigureAction, ok := action.(*types.ClusterClusterReconfigurePlacementAction); ok { 903 if reconfigureAction.TargetHost == nil { 904 t.Errorf("Test %v: received nil host recommendation", testNo) 905 } 906 } else { 907 t.Errorf("Test %v: received wrong action type in recommendation", testNo) 908 } 909 } 910 } 911 } 912 } 913 }, vpx) 914 }