github.com/vmware/govmomi@v0.37.1/simulator/virtual_machine_test.go (about) 1 /* 2 Copyright (c) 2017-2024 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 "math/rand" 23 "os" 24 "reflect" 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/task" 34 "github.com/vmware/govmomi/vim25" 35 "github.com/vmware/govmomi/vim25/mo" 36 "github.com/vmware/govmomi/vim25/types" 37 ) 38 39 func TestCreateVm(t *testing.T) { 40 ctx := context.Background() 41 42 for _, model := range []*Model{ESX(), VPX()} { 43 defer model.Remove() 44 err := model.Create() 45 if err != nil { 46 t.Fatal(err) 47 } 48 49 s := model.Service.NewServer() 50 defer s.Close() 51 52 c, err := govmomi.NewClient(ctx, s.URL, true) 53 if err != nil { 54 t.Fatal(err) 55 } 56 57 p := property.DefaultCollector(c.Client) 58 59 finder := find.NewFinder(c.Client, false) 60 61 dc, err := finder.DefaultDatacenter(ctx) 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 finder.SetDatacenter(dc) 67 68 folders, err := dc.Folders(ctx) 69 if err != nil { 70 t.Fatal(err) 71 } 72 73 ds, err := finder.DefaultDatastore(ctx) 74 if err != nil { 75 t.Fatal(err) 76 } 77 78 hosts, err := finder.HostSystemList(ctx, "*/*") 79 if err != nil { 80 t.Fatal(err) 81 } 82 83 nhosts := len(hosts) 84 host := hosts[rand.Intn(nhosts)] 85 pool, err := host.ResourcePool(ctx) 86 if err != nil { 87 t.Fatal(err) 88 } 89 90 if nhosts == 1 { 91 // test the default path against the ESX model 92 host = nil 93 } 94 95 vmFolder := folders.VmFolder 96 97 var vmx string 98 99 spec := types.VirtualMachineConfigSpec{ 100 // Note: real ESX allows the VM to be created without a GuestId, 101 // but will power on will fail. 102 GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest), 103 } 104 105 steps := []func(){ 106 func() { 107 spec.Name = "test" 108 vmx = fmt.Sprintf("%s/%s.vmx", spec.Name, spec.Name) 109 }, 110 func() { 111 spec.Files = &types.VirtualMachineFileInfo{ 112 VmPathName: fmt.Sprintf("[%s] %s", ds.Name(), vmx), 113 } 114 }, 115 } 116 117 // expecting CreateVM to fail until all steps are taken 118 for _, step := range steps { 119 task, cerr := vmFolder.CreateVM(ctx, spec, pool, host) 120 if cerr != nil { 121 t.Fatal(err) 122 } 123 124 cerr = task.Wait(ctx) 125 if cerr == nil { 126 t.Error("expected error") 127 } 128 129 step() 130 } 131 132 task, err := vmFolder.CreateVM(ctx, spec, pool, host) 133 if err != nil { 134 t.Fatal(err) 135 } 136 137 info, err := task.WaitForResult(ctx, nil) 138 if err != nil { 139 t.Fatal(err) 140 } 141 142 // Test that datastore files were created 143 _, err = ds.Stat(ctx, vmx) 144 if err != nil { 145 t.Fatal(err) 146 } 147 148 vm := object.NewVirtualMachine(c.Client, info.Result.(types.ManagedObjectReference)) 149 150 name, err := vm.ObjectName(ctx) 151 if err != nil { 152 t.Fatal(err) 153 } 154 155 if name != spec.Name { 156 t.Errorf("name=%s", name) 157 } 158 159 _, err = vm.Device(ctx) 160 if err != nil { 161 t.Fatal(err) 162 } 163 164 recreate := func(context.Context) (*object.Task, error) { 165 return vmFolder.CreateVM(ctx, spec, pool, nil) 166 } 167 168 ops := []struct { 169 method func(context.Context) (*object.Task, error) 170 state types.VirtualMachinePowerState 171 fail bool 172 }{ 173 // Powered off by default 174 {nil, types.VirtualMachinePowerStatePoweredOff, false}, 175 // Create with same .vmx path should fail 176 {recreate, "", true}, 177 // Off -> On == ok 178 {vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, false}, 179 // On -> On == fail 180 {vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, true}, 181 // On -> Off == ok 182 {vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, false}, 183 // Off -> Off == fail 184 {vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, true}, 185 // Off -> On == ok 186 {vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, false}, 187 // Destroy == fail (power is On) 188 {vm.Destroy, types.VirtualMachinePowerStatePoweredOn, true}, 189 // On -> Off == ok 190 {vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, false}, 191 // Off -> Reset == fail 192 {vm.Reset, types.VirtualMachinePowerStatePoweredOff, true}, 193 // Off -> On == ok 194 {vm.PowerOn, types.VirtualMachinePowerStatePoweredOn, false}, 195 // On -> Reset == ok 196 {vm.Reset, types.VirtualMachinePowerStatePoweredOn, false}, 197 // On -> Suspend == ok 198 {vm.Suspend, types.VirtualMachinePowerStateSuspended, false}, 199 // On -> Off == ok 200 {vm.PowerOff, types.VirtualMachinePowerStatePoweredOff, false}, 201 // Destroy == ok (power is Off) 202 {vm.Destroy, "", false}, 203 } 204 205 for i, op := range ops { 206 if op.method != nil { 207 task, err = op.method(ctx) 208 if err != nil { 209 t.Fatal(err) 210 } 211 212 err = task.Wait(ctx) 213 if op.fail { 214 if err == nil { 215 t.Errorf("%d: expected error", i) 216 } 217 } else { 218 if err != nil { 219 t.Errorf("%d: %s", i, err) 220 } 221 } 222 } 223 224 if len(op.state) != 0 { 225 state, err := vm.PowerState(ctx) 226 if err != nil { 227 t.Fatal(err) 228 } 229 230 if state != op.state { 231 t.Errorf("state=%s", state) 232 } 233 234 err = property.Wait(ctx, p, vm.Reference(), []string{object.PropRuntimePowerState}, func(pc []types.PropertyChange) bool { 235 for _, c := range pc { 236 switch v := c.Val.(type) { 237 case types.VirtualMachinePowerState: 238 if v != op.state { 239 t.Errorf("state=%s", v) 240 } 241 default: 242 t.Errorf("unexpected type %T", v) 243 } 244 245 } 246 return true 247 }) 248 if err != nil { 249 t.Error(err) 250 } 251 252 running, err := vm.IsToolsRunning(ctx) 253 if err != nil { 254 t.Error(err) 255 } 256 if running { 257 t.Error("tools running") 258 } 259 } 260 } 261 262 // Test that datastore files were removed 263 _, err = ds.Stat(ctx, vmx) 264 if err == nil { 265 t.Error("expected error") 266 } 267 } 268 } 269 270 func TestCreateVmWithSpecialCharaters(t *testing.T) { 271 tests := []struct { 272 name string 273 expected string 274 }{ 275 {`/`, `%2f`}, 276 {`\`, `%5c`}, 277 {`%`, `%25`}, 278 // multiple special characters 279 {`%%`, `%25%25`}, 280 // slash-separated name 281 {`foo/bar`, `foo%2fbar`}, 282 } 283 284 for _, test := range tests { 285 m := ESX() 286 287 Test(func(ctx context.Context, c *vim25.Client) { 288 finder := find.NewFinder(c, false) 289 290 dc, err := finder.DefaultDatacenter(ctx) 291 if err != nil { 292 t.Fatal(err) 293 } 294 295 finder.SetDatacenter(dc) 296 folders, err := dc.Folders(ctx) 297 if err != nil { 298 t.Fatal(err) 299 } 300 vmFolder := folders.VmFolder 301 302 ds, err := finder.DefaultDatastore(ctx) 303 if err != nil { 304 t.Fatal(err) 305 } 306 307 spec := types.VirtualMachineConfigSpec{ 308 Name: test.name, 309 Files: &types.VirtualMachineFileInfo{ 310 VmPathName: fmt.Sprintf("[%s]", ds.Name()), 311 }, 312 } 313 314 pool := object.NewResourcePool(c, esx.ResourcePool.Self) 315 316 task, err := vmFolder.CreateVM(ctx, spec, pool, nil) 317 if err != nil { 318 t.Fatal(err) 319 } 320 321 info, err := task.WaitForResult(ctx, nil) 322 if err != nil { 323 t.Fatal(err) 324 } 325 326 vm := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference)) 327 name, err := vm.ObjectName(ctx) 328 if err != nil { 329 t.Fatal(err) 330 } 331 if name != test.expected { 332 t.Errorf("expected %s, got %s", test.expected, name) 333 } 334 }, m) 335 } 336 } 337 338 func TestCloneVm(t *testing.T) { 339 tests := []struct { 340 name string 341 vmName string 342 config types.VirtualMachineCloneSpec 343 fail bool 344 }{ 345 { 346 "clone a vm", 347 "cloned-vm", 348 types.VirtualMachineCloneSpec{ 349 Template: false, 350 PowerOn: false, 351 }, 352 false, 353 }, 354 { 355 "vm name is duplicated", 356 "DC0_H0_VM0", 357 types.VirtualMachineCloneSpec{ 358 Template: false, 359 PowerOn: false, 360 }, 361 true, 362 }, 363 } 364 365 for _, test := range tests { 366 test := test // assign to local var since loop var is reused 367 368 t.Run(test.name, func(t *testing.T) { 369 m := VPX() 370 defer m.Remove() 371 372 Test(func(ctx context.Context, c *vim25.Client) { 373 finder := find.NewFinder(c, false) 374 dc, err := finder.DefaultDatacenter(ctx) 375 if err != nil { 376 t.Fatal(err) 377 } 378 379 folders, err := dc.Folders(ctx) 380 if err != nil { 381 t.Fatal(err) 382 } 383 384 vmFolder := folders.VmFolder 385 386 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 387 vm := object.NewVirtualMachine(c, vmm.Reference()) 388 389 task, err := vm.Clone(ctx, vmFolder, test.vmName, test.config) 390 if err != nil { 391 t.Fatal(err) 392 } 393 394 err = task.Wait(ctx) 395 if test.fail { 396 if err == nil { 397 t.Errorf("%s: expected error", test.name) 398 } 399 } else { 400 if err != nil { 401 t.Errorf("%s: %s", test.name, err) 402 } 403 } 404 }, m) 405 }) 406 } 407 } 408 409 func TestReconfigVmDevice(t *testing.T) { 410 ctx := context.Background() 411 412 m := ESX() 413 defer m.Remove() 414 err := m.Create() 415 if err != nil { 416 t.Fatal(err) 417 } 418 419 s := m.Service.NewServer() 420 defer s.Close() 421 422 c, err := govmomi.NewClient(ctx, s.URL, true) 423 if err != nil { 424 t.Fatal(err) 425 } 426 427 finder := find.NewFinder(c.Client, false) 428 finder.SetDatacenter(object.NewDatacenter(c.Client, esx.Datacenter.Reference())) 429 430 vms, err := finder.VirtualMachineList(ctx, "*") 431 if err != nil { 432 t.Fatal(err) 433 } 434 435 vm := vms[0] 436 device, err := vm.Device(ctx) 437 if err != nil { 438 t.Fatal(err) 439 } 440 441 // verify default device list 442 _, err = device.FindIDEController("") 443 if err != nil { 444 t.Fatal(err) 445 } 446 447 // default list of devices + 1 NIC + 1 SCSI controller + 1 CDROM + 1 disk created by the Model 448 mdevices := len(esx.VirtualDevice) + 4 449 450 if len(device) != mdevices { 451 t.Errorf("expected %d devices, got %d", mdevices, len(device)) 452 } 453 454 d := device.FindByKey(esx.EthernetCard.Key) 455 456 err = vm.AddDevice(ctx, d) 457 if _, ok := err.(task.Error).Fault().(*types.InvalidDeviceSpec); !ok { 458 t.Fatalf("err=%v", err) 459 } 460 461 err = vm.RemoveDevice(ctx, false, d) 462 if err != nil { 463 t.Fatal(err) 464 } 465 466 device, err = vm.Device(ctx) 467 if err != nil { 468 t.Fatal(err) 469 } 470 471 if len(device) != mdevices-1 { 472 t.Error("device list mismatch") 473 } 474 475 // cover the path where the simulator assigns a UnitNumber 476 d.GetVirtualDevice().UnitNumber = nil 477 // cover the path where the simulator assigns a Key 478 d.GetVirtualDevice().Key = -1 479 480 err = vm.AddDevice(ctx, d) 481 if err != nil { 482 t.Fatal(err) 483 } 484 485 device, err = vm.Device(ctx) 486 if err != nil { 487 t.Fatal(err) 488 } 489 490 if len(device) != mdevices { 491 t.Error("device list mismatch") 492 } 493 494 disks := device.SelectByType((*types.VirtualDisk)(nil)) 495 496 for _, d := range disks { 497 disk := d.(*types.VirtualDisk) 498 info := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 499 500 if info.Datastore.Type == "" || info.Datastore.Value == "" { 501 t.Errorf("invalid datastore for %s", device.Name(d)) 502 } 503 504 // RemoveDevice and keep the file backing 505 if err = vm.RemoveDevice(ctx, true, d); err != nil { 506 t.Error(err) 507 } 508 509 if err = vm.AddDevice(ctx, d); err == nil { 510 t.Error("expected FileExists error") 511 } 512 513 // Need FileOperation=="" to add an existing disk, see object.VirtualMachine.configureDevice 514 disk.CapacityInKB = 0 515 disk.CapacityInBytes = 0 516 if err = vm.AddDevice(ctx, d); err != nil { 517 t.Error(err) 518 } 519 520 d.GetVirtualDevice().DeviceInfo = nil 521 if err = vm.EditDevice(ctx, d); err != nil { 522 t.Error(err) 523 } 524 525 // RemoveDevice and delete the file backing 526 if err = vm.RemoveDevice(ctx, false, d); err != nil { 527 t.Error(err) 528 } 529 530 if err = vm.AddDevice(ctx, d); err == nil { 531 t.Error("expected FileNotFound error") 532 } 533 } 534 } 535 536 func TestConnectVmDevice(t *testing.T) { 537 ctx := context.Background() 538 539 m := ESX() 540 defer m.Remove() 541 err := m.Create() 542 if err != nil { 543 t.Fatal(err) 544 } 545 546 s := m.Service.NewServer() 547 defer s.Close() 548 549 c, err := govmomi.NewClient(ctx, s.URL, true) 550 if err != nil { 551 t.Fatal(err) 552 } 553 554 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 555 vm := object.NewVirtualMachine(c.Client, vmm.Reference()) 556 557 l := object.VirtualDeviceList{} // used only for Connect/Disconnect function 558 559 tests := []struct { 560 description string 561 changePower func(context.Context) (*object.Task, error) 562 changeConnectivity func(types.BaseVirtualDevice) error 563 expectedConnected bool 564 expectedStartConnected bool 565 }{ 566 {"disconnect when vm is on", nil, l.Disconnect, false, false}, 567 {"connect when vm is on", nil, l.Connect, true, true}, 568 {"power off vm", vm.PowerOff, nil, false, true}, 569 {"disconnect when vm is off", nil, l.Disconnect, false, false}, 570 {"connect when vm is off", nil, l.Connect, false, true}, 571 {"power on vm when StartConnected is true", vm.PowerOn, nil, true, true}, 572 {"power off vm and disconnect again", vm.PowerOff, l.Disconnect, false, false}, 573 {"power on vm when StartConnected is false", vm.PowerOn, nil, false, false}, 574 } 575 576 for _, testCase := range tests { 577 testCase := testCase // assign to local var since loop var is reused 578 579 t.Run(testCase.description, func(t *testing.T) { 580 if testCase.changePower != nil { 581 task, err := testCase.changePower(ctx) 582 if err != nil { 583 t.Fatal(err) 584 } 585 586 err = task.Wait(ctx) 587 if err != nil { 588 t.Fatal(err) 589 } 590 } 591 592 if testCase.changeConnectivity != nil { 593 list, err := vm.Device(ctx) 594 if err != nil { 595 t.Fatal(err) 596 } 597 device := list.FindByKey(esx.EthernetCard.Key) 598 if device == nil { 599 t.Fatal("cloud not find EthernetCard") 600 } 601 602 err = testCase.changeConnectivity(device) 603 if err != nil { 604 t.Fatal(err) 605 } 606 err = vm.EditDevice(ctx, device) 607 if err != nil { 608 t.Fatal(err) 609 } 610 } 611 612 updatedList, err := vm.Device(ctx) 613 if err != nil { 614 t.Fatal(err) 615 } 616 updatedDevice := updatedList.FindByKey(esx.EthernetCard.Key) 617 if updatedDevice == nil { 618 t.Fatal("cloud not find EthernetCard") 619 } 620 conn := updatedDevice.GetVirtualDevice().Connectable 621 622 if conn.Connected != testCase.expectedConnected { 623 t.Errorf("unexpected Connected property. expected: %t, actual: %t", 624 testCase.expectedConnected, conn.Connected) 625 } 626 if conn.StartConnected != testCase.expectedStartConnected { 627 t.Errorf("unexpected StartConnected property. expected: %t, actual: %t", 628 testCase.expectedStartConnected, conn.StartConnected) 629 } 630 }) 631 } 632 } 633 634 func TestVAppConfigAdd(t *testing.T) { 635 ctx := context.Background() 636 637 m := ESX() 638 defer m.Remove() 639 err := m.Create() 640 if err != nil { 641 t.Fatal(err) 642 } 643 644 s := m.Service.NewServer() 645 defer s.Close() 646 647 c, err := govmomi.NewClient(ctx, s.URL, true) 648 if err != nil { 649 t.Fatal(err) 650 } 651 652 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 653 vm := object.NewVirtualMachine(c.Client, vmm.Reference()) 654 655 tests := []struct { 656 description string 657 expectedErr types.BaseMethodFault 658 spec types.VirtualMachineConfigSpec 659 existingVMConfig *types.VirtualMachineConfigSpec 660 expectedProps []types.VAppPropertyInfo 661 }{ 662 663 { 664 description: "successfully add a new property", 665 spec: types.VirtualMachineConfigSpec{ 666 VAppConfig: &types.VmConfigSpec{ 667 Property: []types.VAppPropertySpec{ 668 { 669 ArrayUpdateSpec: types.ArrayUpdateSpec{ 670 Operation: types.ArrayUpdateOperationAdd, 671 }, 672 Info: &types.VAppPropertyInfo{ 673 Key: int32(1), 674 Id: "foo-id", 675 Value: "foo-value", 676 }, 677 }, 678 }, 679 }, 680 }, 681 expectedProps: []types.VAppPropertyInfo{ 682 { 683 Key: int32(1), 684 Id: "foo-id", 685 Value: "foo-value", 686 }, 687 }, 688 }, 689 { 690 description: "return error when a property that exists is added", 691 expectedErr: new(types.InvalidArgument), 692 existingVMConfig: &types.VirtualMachineConfigSpec{ 693 VAppConfig: &types.VmConfigSpec{ 694 Property: []types.VAppPropertySpec{ 695 { 696 ArrayUpdateSpec: types.ArrayUpdateSpec{ 697 Operation: types.ArrayUpdateOperationAdd, 698 }, 699 Info: &types.VAppPropertyInfo{ 700 Key: int32(2), 701 Id: "foo-id", 702 Value: "foo-value", 703 }, 704 }, 705 }, 706 }, 707 }, 708 spec: types.VirtualMachineConfigSpec{ 709 VAppConfig: &types.VmConfigSpec{ 710 Property: []types.VAppPropertySpec{ 711 { 712 ArrayUpdateSpec: types.ArrayUpdateSpec{ 713 Operation: types.ArrayUpdateOperationAdd, 714 }, 715 Info: &types.VAppPropertyInfo{ 716 Key: int32(2), 717 }, 718 }, 719 }, 720 }, 721 }, 722 }, 723 } 724 725 for _, testCase := range tests { 726 t.Run(testCase.description, func(t *testing.T) { 727 if testCase.existingVMConfig != nil { 728 rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig) 729 if err := rtask.Wait(ctx); err != nil { 730 t.Errorf("Reconfigure failed during test setup. err: %v", err) 731 } 732 } 733 734 err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec()) 735 if !reflect.DeepEqual(err, testCase.expectedErr) { 736 t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err) 737 } 738 739 if testCase.expectedErr == nil { 740 props := vmm.Config.VAppConfig.GetVmConfigInfo().Property 741 // the testcase only has one VApp property, so ordering of the elements does not matter. 742 if !reflect.DeepEqual(props, testCase.expectedProps) { 743 t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props) 744 } 745 } 746 }) 747 } 748 } 749 750 func TestVAppConfigEdit(t *testing.T) { 751 ctx := context.Background() 752 753 m := ESX() 754 defer m.Remove() 755 err := m.Create() 756 if err != nil { 757 t.Fatal(err) 758 } 759 760 s := m.Service.NewServer() 761 defer s.Close() 762 763 c, err := govmomi.NewClient(ctx, s.URL, true) 764 if err != nil { 765 t.Fatal(err) 766 } 767 768 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 769 vm := object.NewVirtualMachine(c.Client, vmm.Reference()) 770 771 tests := []struct { 772 description string 773 expectedErr types.BaseMethodFault 774 spec types.VirtualMachineConfigSpec 775 existingVMConfig *types.VirtualMachineConfigSpec 776 expectedProps []types.VAppPropertyInfo 777 }{ 778 779 { 780 description: "successfully update a property that exists", 781 existingVMConfig: &types.VirtualMachineConfigSpec{ 782 VAppConfig: &types.VmConfigSpec{ 783 Property: []types.VAppPropertySpec{ 784 { 785 ArrayUpdateSpec: types.ArrayUpdateSpec{ 786 Operation: types.ArrayUpdateOperationAdd, 787 }, 788 Info: &types.VAppPropertyInfo{ 789 Key: int32(1), 790 Id: "foo-id", 791 Value: "foo-value", 792 }, 793 }, 794 }, 795 }, 796 }, 797 spec: types.VirtualMachineConfigSpec{ 798 VAppConfig: &types.VmConfigSpec{ 799 Property: []types.VAppPropertySpec{ 800 { 801 ArrayUpdateSpec: types.ArrayUpdateSpec{ 802 Operation: types.ArrayUpdateOperationEdit, 803 }, 804 Info: &types.VAppPropertyInfo{ 805 Key: int32(1), 806 Id: "foo-id-updated", 807 Value: "foo-value-updated", 808 }, 809 }, 810 }, 811 }, 812 }, 813 expectedProps: []types.VAppPropertyInfo{ 814 { 815 Key: int32(1), 816 Id: "foo-id-updated", 817 Value: "foo-value-updated", 818 }, 819 }, 820 }, 821 { 822 description: "return error when a property that doesn't exist is updated", 823 expectedErr: new(types.InvalidArgument), 824 spec: types.VirtualMachineConfigSpec{ 825 VAppConfig: &types.VmConfigSpec{ 826 Property: []types.VAppPropertySpec{ 827 { 828 ArrayUpdateSpec: types.ArrayUpdateSpec{ 829 Operation: types.ArrayUpdateOperationEdit, 830 }, 831 Info: &types.VAppPropertyInfo{ 832 Key: int32(2), 833 }, 834 }, 835 }, 836 }, 837 }, 838 }, 839 } 840 841 for _, testCase := range tests { 842 t.Run(testCase.description, func(t *testing.T) { 843 if testCase.existingVMConfig != nil { 844 rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig) 845 if err := rtask.Wait(ctx); err != nil { 846 t.Errorf("Reconfigure failed during test setup. err: %v", err) 847 } 848 } 849 850 err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec()) 851 if !reflect.DeepEqual(err, testCase.expectedErr) { 852 t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err) 853 } 854 855 if testCase.expectedErr == nil { 856 props := vmm.Config.VAppConfig.GetVmConfigInfo().Property 857 // the testcase only has one VApp property, so ordering of the elements does not matter. 858 if !reflect.DeepEqual(props, testCase.expectedProps) { 859 t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props) 860 } 861 } 862 }) 863 } 864 } 865 866 func TestVAppConfigRemove(t *testing.T) { 867 ctx := context.Background() 868 869 m := ESX() 870 defer m.Remove() 871 err := m.Create() 872 if err != nil { 873 t.Fatal(err) 874 } 875 876 s := m.Service.NewServer() 877 defer s.Close() 878 879 c, err := govmomi.NewClient(ctx, s.URL, true) 880 if err != nil { 881 t.Fatal(err) 882 } 883 884 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 885 vm := object.NewVirtualMachine(c.Client, vmm.Reference()) 886 887 tests := []struct { 888 description string 889 expectedErr types.BaseMethodFault 890 spec types.VirtualMachineConfigSpec 891 existingVMConfig *types.VirtualMachineConfigSpec 892 expectedProps []types.VAppPropertyInfo 893 }{ 894 { 895 description: "returns success when a property that exists is removed", 896 existingVMConfig: &types.VirtualMachineConfigSpec{ 897 VAppConfig: &types.VmConfigSpec{ 898 Property: []types.VAppPropertySpec{ 899 { 900 ArrayUpdateSpec: types.ArrayUpdateSpec{ 901 Operation: types.ArrayUpdateOperationAdd, 902 }, 903 Info: &types.VAppPropertyInfo{ 904 Key: int32(1), 905 }, 906 }, 907 }, 908 }, 909 }, 910 spec: types.VirtualMachineConfigSpec{ 911 VAppConfig: &types.VmConfigSpec{ 912 Property: []types.VAppPropertySpec{ 913 { 914 ArrayUpdateSpec: types.ArrayUpdateSpec{ 915 Operation: types.ArrayUpdateOperationRemove, 916 }, 917 Info: &types.VAppPropertyInfo{ 918 Key: int32(1), 919 }, 920 }, 921 }, 922 }, 923 }, 924 expectedProps: []types.VAppPropertyInfo{}, 925 }, 926 { 927 description: "return error when a property that doesn't exist is removed", 928 expectedErr: new(types.InvalidArgument), 929 spec: types.VirtualMachineConfigSpec{ 930 VAppConfig: &types.VmConfigSpec{ 931 Property: []types.VAppPropertySpec{ 932 { 933 ArrayUpdateSpec: types.ArrayUpdateSpec{ 934 Operation: types.ArrayUpdateOperationRemove, 935 }, 936 Info: &types.VAppPropertyInfo{ 937 Key: int32(2), 938 }, 939 }, 940 }, 941 }, 942 }, 943 }, 944 } 945 946 for _, testCase := range tests { 947 t.Run(testCase.description, func(t *testing.T) { 948 if testCase.existingVMConfig != nil { 949 rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig) 950 if err := rtask.Wait(ctx); err != nil { 951 t.Errorf("Reconfigure failed during test setup. err: %v", err) 952 } 953 } 954 955 err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec()) 956 if !reflect.DeepEqual(err, testCase.expectedErr) { 957 t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err) 958 } 959 960 if testCase.expectedErr == nil { 961 props := vmm.Config.VAppConfig.GetVmConfigInfo().Property 962 // the testcase only has one VApp property, so ordering of the elements does not matter. 963 if !reflect.DeepEqual(props, testCase.expectedProps) { 964 t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props) 965 } 966 } 967 }) 968 } 969 } 970 971 func TestReconfigVm(t *testing.T) { 972 ctx := context.Background() 973 974 m := ESX() 975 defer m.Remove() 976 err := m.Create() 977 if err != nil { 978 t.Fatal(err) 979 } 980 981 s := m.Service.NewServer() 982 defer s.Close() 983 984 c, err := govmomi.NewClient(ctx, s.URL, true) 985 if err != nil { 986 t.Fatal(err) 987 } 988 989 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 990 vm := object.NewVirtualMachine(c.Client, vmm.Reference()) 991 992 tests := []struct { 993 fail bool 994 spec types.VirtualMachineConfigSpec 995 }{ 996 { 997 true, types.VirtualMachineConfigSpec{ 998 CpuAllocation: &types.ResourceAllocationInfo{Reservation: types.NewInt64(-1)}, 999 }, 1000 }, 1001 { 1002 false, types.VirtualMachineConfigSpec{ 1003 CpuAllocation: &types.ResourceAllocationInfo{Reservation: types.NewInt64(100)}, 1004 }, 1005 }, 1006 { 1007 true, types.VirtualMachineConfigSpec{ 1008 GuestId: "enoent", 1009 }, 1010 }, 1011 { 1012 false, types.VirtualMachineConfigSpec{ 1013 GuestId: string(GuestID[0]), 1014 }, 1015 }, 1016 { 1017 false, types.VirtualMachineConfigSpec{ 1018 NestedHVEnabled: types.NewBool(true), 1019 }, 1020 }, 1021 { 1022 false, types.VirtualMachineConfigSpec{ 1023 CpuHotAddEnabled: types.NewBool(true), 1024 }, 1025 }, 1026 { 1027 false, types.VirtualMachineConfigSpec{ 1028 CpuHotRemoveEnabled: types.NewBool(true), 1029 }, 1030 }, 1031 { 1032 false, types.VirtualMachineConfigSpec{ 1033 GuestAutoLockEnabled: types.NewBool(true), 1034 }, 1035 }, 1036 { 1037 false, types.VirtualMachineConfigSpec{ 1038 MemoryHotAddEnabled: types.NewBool(true), 1039 }, 1040 }, 1041 { 1042 false, types.VirtualMachineConfigSpec{ 1043 MemoryReservationLockedToMax: types.NewBool(true), 1044 }, 1045 }, 1046 { 1047 false, types.VirtualMachineConfigSpec{ 1048 MessageBusTunnelEnabled: types.NewBool(true), 1049 }, 1050 }, 1051 { 1052 false, types.VirtualMachineConfigSpec{ 1053 NpivTemporaryDisabled: types.NewBool(true), 1054 }, 1055 }, 1056 { 1057 false, types.VirtualMachineConfigSpec{ 1058 NpivOnNonRdmDisks: types.NewBool(true), 1059 }, 1060 }, 1061 { 1062 false, types.VirtualMachineConfigSpec{ 1063 ConsolePreferences: &types.VirtualMachineConsolePreferences{ 1064 PowerOnWhenOpened: types.NewBool(true), 1065 }, 1066 }, 1067 }, 1068 { 1069 false, types.VirtualMachineConfigSpec{ 1070 CpuAffinity: &types.VirtualMachineAffinityInfo{ 1071 AffinitySet: []int32{1}, 1072 }, 1073 }, 1074 }, 1075 { 1076 false, types.VirtualMachineConfigSpec{ 1077 CpuAllocation: &types.ResourceAllocationInfo{ 1078 Reservation: types.NewInt64(100), 1079 }, 1080 }, 1081 }, 1082 { 1083 false, types.VirtualMachineConfigSpec{ 1084 MemoryAffinity: &types.VirtualMachineAffinityInfo{ 1085 AffinitySet: []int32{1}, 1086 }, 1087 }, 1088 }, 1089 { 1090 false, types.VirtualMachineConfigSpec{ 1091 MemoryAllocation: &types.ResourceAllocationInfo{ 1092 Reservation: types.NewInt64(100), 1093 }, 1094 }, 1095 }, 1096 { 1097 false, types.VirtualMachineConfigSpec{ 1098 LatencySensitivity: &types.LatencySensitivity{ 1099 Sensitivity: 1, 1100 }, 1101 }, 1102 }, 1103 } 1104 1105 for i, test := range tests { 1106 rtask, _ := vm.Reconfigure(ctx, test.spec) 1107 1108 err := rtask.Wait(ctx) 1109 if test.fail { 1110 if err == nil { 1111 t.Errorf("%d: expected failure", i) 1112 } 1113 } else { 1114 if err != nil { 1115 t.Errorf("unexpected failure: %s", err) 1116 } 1117 } 1118 } 1119 1120 // Verify ReConfig actually works 1121 if *vmm.Config.NestedHVEnabled != true { 1122 t.Errorf("vm.Config.NestedHVEnabled expected true; got false") 1123 } 1124 if *vmm.Config.CpuHotAddEnabled != true { 1125 t.Errorf("vm.Config.CpuHotAddEnabled expected true; got false") 1126 } 1127 if *vmm.Config.CpuHotRemoveEnabled != true { 1128 t.Errorf("vm.Config.CpuHotRemoveEnabled expected true; got false") 1129 } 1130 if *vmm.Config.GuestAutoLockEnabled != true { 1131 t.Errorf("vm.Config.GuestAutoLockEnabled expected true; got false") 1132 } 1133 if *vmm.Config.MemoryHotAddEnabled != true { 1134 t.Errorf("vm.Config.MemoryHotAddEnabled expected true; got false") 1135 } 1136 if *vmm.Config.MemoryReservationLockedToMax != true { 1137 t.Errorf("vm.Config.MemoryReservationLockedToMax expected true; got false") 1138 } 1139 if *vmm.Config.MessageBusTunnelEnabled != true { 1140 t.Errorf("vm.Config.MessageBusTunnelEnabled expected true; got false") 1141 } 1142 if *vmm.Config.NpivTemporaryDisabled != true { 1143 t.Errorf("vm.Config.NpivTemporaryDisabled expected true; got false") 1144 } 1145 if *vmm.Config.NpivOnNonRdmDisks != true { 1146 t.Errorf("vm.Config.NpivOnNonRdmDisks expected true; got false") 1147 } 1148 if *vmm.Config.ConsolePreferences.PowerOnWhenOpened != true { 1149 t.Errorf("vm.Config.ConsolePreferences.PowerOnWhenOpened expected true; got false") 1150 } 1151 if vmm.Config.CpuAffinity.AffinitySet[0] != int32(1) { 1152 t.Errorf("vm.Config.CpuAffinity.AffinitySet[0] expected %d; got %d", 1153 1, vmm.Config.CpuAffinity.AffinitySet[0]) 1154 } 1155 if vmm.Config.MemoryAffinity.AffinitySet[0] != int32(1) { 1156 t.Errorf("vm.Config.CpuAffinity.AffinitySet[0] expected %d; got %d", 1157 1, vmm.Config.CpuAffinity.AffinitySet[0]) 1158 } 1159 if *vmm.Config.CpuAllocation.Reservation != 100 { 1160 t.Errorf("vm.Config.CpuAllocation.Reservation expected %d; got %d", 1161 100, *vmm.Config.CpuAllocation.Reservation) 1162 } 1163 if *vmm.Config.MemoryAllocation.Reservation != 100 { 1164 t.Errorf("vm.Config.MemoryAllocation.Reservation expected %d; got %d", 1165 100, *vmm.Config.MemoryAllocation.Reservation) 1166 } 1167 if vmm.Config.LatencySensitivity.Sensitivity != int32(1) { 1168 t.Errorf("vmm.Config.LatencySensitivity.Sensitivity expected %d; got %d", 1169 1, vmm.Config.LatencySensitivity.Sensitivity) 1170 } 1171 } 1172 1173 func TestCreateVmWithDevices(t *testing.T) { 1174 ctx := context.Background() 1175 1176 m := ESX() 1177 m.Datastore = 2 1178 defer m.Remove() 1179 1180 err := m.Create() 1181 if err != nil { 1182 t.Fatal(err) 1183 } 1184 1185 s := m.Service.NewServer() 1186 defer s.Close() 1187 1188 c := m.Service.client 1189 1190 folder := object.NewFolder(c, esx.Datacenter.VmFolder) 1191 pool := object.NewResourcePool(c, esx.ResourcePool.Self) 1192 1193 // different set of devices from Model.Create's 1194 var devices object.VirtualDeviceList 1195 ide, _ := devices.CreateIDEController() 1196 cdrom, _ := devices.CreateCdrom(ide.(*types.VirtualIDEController)) 1197 scsi, _ := devices.CreateSCSIController("scsi") 1198 disk := &types.VirtualDisk{ 1199 CapacityInKB: 1024, 1200 VirtualDevice: types.VirtualDevice{ 1201 Backing: new(types.VirtualDiskFlatVer2BackingInfo), // Leave fields empty to test defaults 1202 }, 1203 } 1204 disk2 := &types.VirtualDisk{ 1205 CapacityInKB: 1024, 1206 VirtualDevice: types.VirtualDevice{ 1207 Backing: &types.VirtualDiskFlatVer2BackingInfo{ 1208 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 1209 FileName: "[LocalDS_0]", 1210 }, 1211 }, 1212 }, 1213 } 1214 devices = append(devices, ide, cdrom, scsi) 1215 devices.AssignController(disk, scsi.(*types.VirtualLsiLogicController)) 1216 devices = append(devices, disk) 1217 devices.AssignController(disk2, scsi.(*types.VirtualLsiLogicController)) 1218 devices = append(devices, disk2) 1219 create, _ := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) 1220 1221 spec := types.VirtualMachineConfigSpec{ 1222 Name: "foo", 1223 GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest), 1224 DeviceChange: create, 1225 Files: &types.VirtualMachineFileInfo{ 1226 VmPathName: "[LocalDS_0]", 1227 }, 1228 } 1229 1230 ctask, _ := folder.CreateVM(ctx, spec, pool, nil) 1231 info, err := ctask.WaitForResult(ctx, nil) 1232 if err != nil { 1233 t.Fatal(err) 1234 } 1235 1236 vm := Map.Get(info.Result.(types.ManagedObjectReference)).(*VirtualMachine) 1237 1238 expect := len(esx.VirtualDevice) + len(devices) 1239 ndevice := len(vm.Config.Hardware.Device) 1240 1241 if expect != ndevice { 1242 t.Errorf("expected %d, got %d", expect, ndevice) 1243 } 1244 1245 // check number of disk and disk summary 1246 ndisk := 0 1247 for _, device := range vm.Config.Hardware.Device { 1248 disk, ok := device.(*types.VirtualDisk) 1249 if ok { 1250 ndisk++ 1251 summary := disk.DeviceInfo.GetDescription().Summary 1252 if summary != "1,024 KB" { 1253 t.Errorf("expected '1,1024 KB', got %s", summary) 1254 } 1255 } 1256 } 1257 if ndisk != 2 { 1258 t.Errorf("expected 1 disk, got %d", ndisk) 1259 } 1260 1261 // Add disk on another datastore with empty path (issue 1854) 1262 ovm := object.NewVirtualMachine(c, vm.Self) 1263 disk = &types.VirtualDisk{ 1264 CapacityInKB: 1024, 1265 VirtualDevice: types.VirtualDevice{ 1266 Backing: &types.VirtualDiskFlatVer2BackingInfo{ 1267 VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ 1268 FileName: "[LocalDS_1]", 1269 }, 1270 }, 1271 }, 1272 } 1273 devices.AssignController(disk, scsi.(*types.VirtualLsiLogicController)) 1274 devices = nil 1275 devices = append(devices, disk) 1276 create, _ = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) 1277 spec = types.VirtualMachineConfigSpec{DeviceChange: create} 1278 rtask, _ := ovm.Reconfigure(ctx, spec) 1279 _, err = rtask.WaitForResult(ctx, nil) 1280 if err != nil { 1281 t.Fatal(err) 1282 } 1283 } 1284 1285 func TestAddedDiskCapacity(t *testing.T) { 1286 tests := []struct { 1287 name string 1288 capacityInBytes int64 1289 capacityInKB int64 1290 expectedCapacityInBytes int64 1291 expectedCapacityInKB int64 1292 }{ 1293 { 1294 "specify capacityInBytes", 1295 512 * 1024, 1296 0, 1297 512 * 1024, 1298 512, 1299 }, 1300 { 1301 "specify capacityInKB", 1302 0, 1303 512, 1304 512 * 1024, 1305 512, 1306 }, 1307 { 1308 "specify both", 1309 512 * 1024, 1310 512, 1311 512 * 1024, 1312 512, 1313 }, 1314 { 1315 "capacityInbytes takes precedence if two fields represents different capacity", 1316 512 * 1024, 1317 1024, 1318 512 * 1024, 1319 512, 1320 }, 1321 } 1322 1323 for _, test := range tests { 1324 test := test // assign to local var since loop var is reused 1325 t.Run(test.name, func(t *testing.T) { 1326 m := ESX() 1327 1328 Test(func(ctx context.Context, c *vim25.Client) { 1329 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 1330 vm := object.NewVirtualMachine(c, vmm.Reference()) 1331 1332 ds := Map.Any("Datastore").(*Datastore) 1333 1334 devices, err := vm.Device(ctx) 1335 if err != nil { 1336 t.Fatal(err) 1337 } 1338 1339 controller, err := devices.FindDiskController("") 1340 if err != nil { 1341 t.Fatal(err) 1342 } 1343 1344 disk := devices.CreateDisk(controller, ds.Reference(), "") 1345 disk.CapacityInBytes = test.capacityInBytes 1346 disk.CapacityInKB = test.capacityInKB 1347 1348 err = vm.AddDevice(ctx, disk) 1349 if err != nil { 1350 t.Fatal(err) 1351 } 1352 1353 newDevices, err := vm.Device(ctx) 1354 if err != nil { 1355 t.Fatal(err) 1356 } 1357 disks := newDevices.SelectByType((*types.VirtualDisk)(nil)) 1358 if len(disks) == 0 { 1359 t.Fatalf("len(disks)=%d", len(disks)) 1360 } 1361 1362 newDisk := disks[len(disks)-1].(*types.VirtualDisk) 1363 1364 if newDisk.CapacityInBytes != test.expectedCapacityInBytes { 1365 t.Errorf("CapacityInBytes expected %d, got %d", 1366 test.expectedCapacityInBytes, newDisk.CapacityInBytes) 1367 } 1368 if newDisk.CapacityInKB != test.expectedCapacityInKB { 1369 t.Errorf("CapacityInKB expected %d, got %d", 1370 test.expectedCapacityInKB, newDisk.CapacityInKB) 1371 } 1372 1373 }, m) 1374 }) 1375 } 1376 } 1377 1378 func TestEditedDiskCapacity(t *testing.T) { 1379 tests := []struct { 1380 name string 1381 capacityInBytes int64 1382 capacityInKB int64 1383 expectedCapacityInBytes int64 1384 expectedCapacityInKB int64 1385 expectedErr types.BaseMethodFault 1386 }{ 1387 { 1388 "specify same capacities as before", 1389 10 * 1024 * 1024 * 1024, // 10GB 1390 10 * 1024 * 1024, // 10GB 1391 10 * 1024 * 1024 * 1024, // 10GB 1392 10 * 1024 * 1024, // 10GB 1393 nil, 1394 }, 1395 { 1396 "increase only capacityInBytes", 1397 20 * 1024 * 1024 * 1024, // 20GB 1398 10 * 1024 * 1024, // 10GB 1399 20 * 1024 * 1024 * 1024, // 20GB 1400 20 * 1024 * 1024, // 20GB 1401 nil, 1402 }, 1403 { 1404 "increase only capacityInKB", 1405 10 * 1024 * 1024 * 1024, // 10GB 1406 20 * 1024 * 1024, // 20GB 1407 20 * 1024 * 1024 * 1024, // 20GB 1408 20 * 1024 * 1024, // 20GB 1409 nil, 1410 }, 1411 { 1412 "increase both capacityInBytes and capacityInKB", 1413 20 * 1024 * 1024 * 1024, // 20GB 1414 20 * 1024 * 1024, // 20GB 1415 20 * 1024 * 1024 * 1024, // 20GB 1416 20 * 1024 * 1024, // 20GB 1417 nil, 1418 }, 1419 { 1420 "increase both capacityInBytes and capacityInKB but value is different", 1421 20 * 1024 * 1024 * 1024, // 20GB 1422 30 * 1024 * 1024, // 30GB 1423 0, 1424 0, 1425 new(types.InvalidDeviceOperation), 1426 }, 1427 { 1428 "decrease capacity", 1429 1 * 1024 * 1024 * 1024, // 1GB 1430 1 * 1024 * 1024, // 1GB 1431 0, 1432 0, 1433 new(types.InvalidDeviceOperation), 1434 }, 1435 } 1436 1437 for _, test := range tests { 1438 test := test // assign to local var since loop var is reused 1439 t.Run(test.name, func(t *testing.T) { 1440 m := ESX() 1441 1442 Test(func(ctx context.Context, c *vim25.Client) { 1443 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 1444 vm := object.NewVirtualMachine(c, vmm.Reference()) 1445 ds := Map.Any("Datastore").(*Datastore) 1446 1447 // create a new 10GB disk 1448 devices, err := vm.Device(ctx) 1449 if err != nil { 1450 t.Fatal(err) 1451 } 1452 controller, err := devices.FindDiskController("") 1453 if err != nil { 1454 t.Fatal(err) 1455 } 1456 disk := devices.CreateDisk(controller, ds.Reference(), "") 1457 disk.CapacityInBytes = 10 * 1024 * 1024 * 1024 // 10GB 1458 err = vm.AddDevice(ctx, disk) 1459 if err != nil { 1460 t.Fatal(err) 1461 } 1462 1463 // edit its capacity 1464 addedDevices, err := vm.Device(ctx) 1465 if err != nil { 1466 t.Fatal(err) 1467 } 1468 addedDisks := addedDevices.SelectByType((*types.VirtualDisk)(nil)) 1469 if len(addedDisks) == 0 { 1470 t.Fatal("disk not found") 1471 } 1472 addedDisk := addedDisks[0].(*types.VirtualDisk) 1473 addedDisk.CapacityInBytes = test.capacityInBytes 1474 addedDisk.CapacityInKB = test.capacityInKB 1475 err = vm.EditDevice(ctx, addedDisk) 1476 1477 if test.expectedErr != nil { 1478 terr, ok := err.(task.Error) 1479 if !ok { 1480 t.Fatalf("error should be task.Error. actual: %T", err) 1481 } 1482 1483 if !reflect.DeepEqual(terr.Fault(), test.expectedErr) { 1484 t.Errorf("expectedErr: %v, actualErr: %v", test.expectedErr, terr.Fault()) 1485 } 1486 } else { 1487 // obtain the disk again 1488 editedDevices, err := vm.Device(ctx) 1489 if err != nil { 1490 t.Fatal(err) 1491 } 1492 editedDisks := editedDevices.SelectByType((*types.VirtualDisk)(nil)) 1493 if len(editedDevices) == 0 { 1494 t.Fatal("disk not found") 1495 } 1496 editedDisk := editedDisks[len(editedDisks)-1].(*types.VirtualDisk) 1497 1498 if editedDisk.CapacityInBytes != test.expectedCapacityInBytes { 1499 t.Errorf("CapacityInBytes expected %d, got %d", 1500 test.expectedCapacityInBytes, editedDisk.CapacityInBytes) 1501 } 1502 if editedDisk.CapacityInKB != test.expectedCapacityInKB { 1503 t.Errorf("CapacityInKB expected %d, got %d", 1504 test.expectedCapacityInKB, editedDisk.CapacityInKB) 1505 } 1506 } 1507 }, m) 1508 }) 1509 } 1510 } 1511 1512 func TestReconfigureDevicesDatastoreFreespace(t *testing.T) { 1513 tests := []struct { 1514 name string 1515 reconfigure func(context.Context, *object.VirtualMachine, *Datastore, object.VirtualDeviceList) error 1516 freespaceDiff int64 1517 }{ 1518 { 1519 "create a new disk", 1520 func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error { 1521 controller, err := l.FindDiskController("") 1522 if err != nil { 1523 return err 1524 } 1525 1526 disk := l.CreateDisk(controller, ds.Reference(), "") 1527 disk.CapacityInBytes = 10 * 1024 * 1024 * 1024 // 10GB 1528 1529 if err := vm.AddDevice(ctx, disk); err != nil { 1530 return err 1531 } 1532 return nil 1533 }, 1534 -10 * 1024 * 1024 * 1024, // -10GB 1535 }, 1536 { 1537 "edit disk size", 1538 func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error { 1539 disks := l.SelectByType((*types.VirtualDisk)(nil)) 1540 if len(disks) == 0 { 1541 return fmt.Errorf("disk not found") 1542 } 1543 disk := disks[len(disks)-1].(*types.VirtualDisk) 1544 1545 // specify same disk capacity 1546 if err := vm.EditDevice(ctx, disk); err != nil { 1547 return err 1548 } 1549 return nil 1550 }, 1551 0, 1552 }, 1553 { 1554 "remove a disk and its files", 1555 func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error { 1556 disks := l.SelectByType((*types.VirtualDisk)(nil)) 1557 if len(disks) == 0 { 1558 return fmt.Errorf("disk not found") 1559 } 1560 disk := disks[len(disks)-1].(*types.VirtualDisk) 1561 1562 if err := vm.RemoveDevice(ctx, false, disk); err != nil { 1563 return err 1564 } 1565 return nil 1566 }, 1567 10 * 1024 * 1024 * 1024, // 10GB 1568 }, 1569 { 1570 "remove a disk but keep its files", 1571 func(ctx context.Context, vm *object.VirtualMachine, ds *Datastore, l object.VirtualDeviceList) error { 1572 disks := l.SelectByType((*types.VirtualDisk)(nil)) 1573 if len(disks) == 0 { 1574 return fmt.Errorf("disk not found") 1575 } 1576 disk := disks[len(disks)-1].(*types.VirtualDisk) 1577 1578 if err := vm.RemoveDevice(ctx, true, disk); err != nil { 1579 return err 1580 } 1581 return nil 1582 }, 1583 0, 1584 }, 1585 } 1586 1587 for _, test := range tests { 1588 test := test // assign to local var since loop var is reused 1589 t.Run(test.name, func(t *testing.T) { 1590 m := ESX() 1591 1592 Test(func(ctx context.Context, c *vim25.Client) { 1593 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 1594 vm := object.NewVirtualMachine(c, vmm.Reference()) 1595 1596 ds := Map.Any("Datastore").(*Datastore) 1597 freespaceBefore := ds.Datastore.Summary.FreeSpace 1598 1599 devices, err := vm.Device(ctx) 1600 if err != nil { 1601 t.Fatal(err) 1602 } 1603 1604 err = test.reconfigure(ctx, vm, ds, devices) 1605 if err != nil { 1606 t.Fatal(err) 1607 } 1608 1609 freespaceAfter := ds.Datastore.Summary.FreeSpace 1610 1611 if freespaceAfter-freespaceBefore != test.freespaceDiff { 1612 t.Errorf("difference of freespace expected %d, got %d", 1613 test.freespaceDiff, freespaceAfter-freespaceBefore) 1614 } 1615 }, m) 1616 }) 1617 } 1618 } 1619 1620 func TestShutdownGuest(t *testing.T) { 1621 Test(func(ctx context.Context, c *vim25.Client) { 1622 vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference()) 1623 1624 for _, timeout := range []bool{false, true} { 1625 if timeout { 1626 // ShutdownGuest will return right away, but powerState 1627 // is not updated until the internal task completes 1628 TaskDelay.MethodDelay = map[string]int{ 1629 "ShutdownGuest": 500, // delay 500ms 1630 "LockHandoff": 0, // don't lock vm during the delay 1631 } 1632 } 1633 1634 err := vm.ShutdownGuest(ctx) 1635 if err != nil { 1636 t.Fatal(err) 1637 } 1638 1639 wait := ctx 1640 var cancel context.CancelFunc 1641 if timeout { 1642 state, err := vm.PowerState(ctx) 1643 if err != nil { 1644 t.Fatal(err) 1645 } 1646 1647 // with the task delay, should still be on at this point 1648 if state != types.VirtualMachinePowerStatePoweredOn { 1649 t.Errorf("state=%s", state) 1650 } 1651 1652 wait, cancel = context.WithTimeout(ctx, time.Millisecond*250) // wait < task delay 1653 defer cancel() 1654 } 1655 1656 err = vm.WaitForPowerState(wait, types.VirtualMachinePowerStatePoweredOff) 1657 if timeout { 1658 if err == nil { 1659 t.Error("expected timeout") 1660 } 1661 // wait for power state to change, else next test may fail 1662 err = vm.WaitForPowerState(ctx, types.VirtualMachinePowerStatePoweredOff) 1663 if err != nil { 1664 t.Fatal(err) 1665 } 1666 1667 } else { 1668 if err != nil { 1669 t.Fatal(err) 1670 } 1671 } 1672 1673 // shutdown a poweroff vm should fail 1674 err = vm.ShutdownGuest(ctx) 1675 if err == nil { 1676 t.Error("expected error: InvalidPowerState") 1677 } 1678 1679 task, err := vm.PowerOn(ctx) 1680 if err != nil { 1681 t.Fatal(err) 1682 } 1683 err = task.Wait(ctx) 1684 if err != nil { 1685 t.Fatal(err) 1686 } 1687 } 1688 }) 1689 } 1690 1691 func TestVmSnapshot(t *testing.T) { 1692 ctx := context.Background() 1693 1694 m := ESX() 1695 defer m.Remove() 1696 err := m.Create() 1697 if err != nil { 1698 t.Fatal(err) 1699 } 1700 1701 s := m.Service.NewServer() 1702 defer s.Close() 1703 1704 c, err := govmomi.NewClient(ctx, s.URL, true) 1705 if err != nil { 1706 t.Fatal(err) 1707 } 1708 1709 simVm := Map.Any("VirtualMachine") 1710 vm := object.NewVirtualMachine(c.Client, simVm.Reference()) 1711 1712 _, err = fieldValue(reflect.ValueOf(simVm), "snapshot") 1713 if err != errEmptyField { 1714 t.Fatal("snapshot property should be 'nil' if there are no snapshots") 1715 } 1716 1717 task, err := vm.CreateSnapshot(ctx, "root", "description", true, true) 1718 if err != nil { 1719 t.Fatal(err) 1720 } 1721 1722 info, err := task.WaitForResult(ctx) 1723 if err != nil { 1724 t.Fatal(err) 1725 } 1726 1727 snapRef, ok := info.Result.(types.ManagedObjectReference) 1728 if !ok { 1729 t.Fatal("expected ManagedObjectRefrence result for CreateSnapshot") 1730 } 1731 1732 _, err = vm.FindSnapshot(ctx, snapRef.Value) 1733 if err != nil { 1734 t.Fatal(err, "snapshot should be found by result reference") 1735 } 1736 1737 _, err = fieldValue(reflect.ValueOf(simVm), "snapshot") 1738 if err == errEmptyField { 1739 t.Fatal("snapshot property should not be 'nil' if there are snapshots") 1740 } 1741 // NOTE: fieldValue cannot be used for nil check 1742 if len(simVm.(*VirtualMachine).RootSnapshot) == 0 { 1743 t.Fatal("rootSnapshot property should have elements if there are snapshots") 1744 } 1745 1746 task, err = vm.CreateSnapshot(ctx, "child", "description", true, true) 1747 if err != nil { 1748 t.Fatal(err) 1749 } 1750 1751 err = task.Wait(ctx) 1752 if err != nil { 1753 t.Fatal(err) 1754 } 1755 1756 _, err = vm.FindSnapshot(ctx, "child") 1757 if err != nil { 1758 t.Fatal(err) 1759 } 1760 1761 task, err = vm.RevertToCurrentSnapshot(ctx, true) 1762 if err != nil { 1763 t.Fatal(err) 1764 } 1765 1766 err = task.Wait(ctx) 1767 if err != nil { 1768 t.Fatal(err) 1769 } 1770 1771 task, err = vm.RevertToSnapshot(ctx, "root", true) 1772 if err != nil { 1773 t.Fatal(err) 1774 } 1775 1776 err = task.Wait(ctx) 1777 if err != nil { 1778 t.Fatal(err) 1779 } 1780 1781 task, err = vm.RemoveSnapshot(ctx, "child", false, nil) 1782 if err != nil { 1783 t.Fatal(err) 1784 } 1785 1786 err = task.Wait(ctx) 1787 if err != nil { 1788 t.Fatal(err) 1789 } 1790 1791 _, err = fieldValue(reflect.ValueOf(simVm), "snapshot") 1792 if err == errEmptyField { 1793 t.Fatal("snapshot property should not be 'nil' if there are snapshots") 1794 } 1795 // NOTE: fieldValue cannot be used for nil check 1796 if len(simVm.(*VirtualMachine).RootSnapshot) == 0 { 1797 t.Fatal("rootSnapshot property should have elements if there are snapshots") 1798 } 1799 1800 _, err = vm.FindSnapshot(ctx, "child") 1801 if err == nil { 1802 t.Fatal("child should be removed") 1803 } 1804 1805 task, err = vm.RemoveAllSnapshot(ctx, nil) 1806 if err != nil { 1807 t.Fatal(err) 1808 } 1809 1810 err = task.Wait(ctx) 1811 if err != nil { 1812 t.Fatal(err) 1813 } 1814 1815 _, err = fieldValue(reflect.ValueOf(simVm), "snapshot") 1816 if err != errEmptyField { 1817 t.Fatal("snapshot property should be 'nil' if there are no snapshots") 1818 } 1819 // NOTE: fieldValue cannot be used for nil check 1820 if len(simVm.(*VirtualMachine).RootSnapshot) != 0 { 1821 t.Fatal("rootSnapshot property should not have elements if there are no snapshots") 1822 } 1823 1824 _, err = vm.FindSnapshot(ctx, "root") 1825 if err == nil { 1826 t.Fatal("all snapshots should be removed") 1827 } 1828 } 1829 1830 func TestVmMarkAsTemplate(t *testing.T) { 1831 ctx := context.Background() 1832 1833 m := VPX() 1834 defer m.Remove() 1835 err := m.Create() 1836 if err != nil { 1837 t.Fatal(err) 1838 } 1839 1840 s := m.Service.NewServer() 1841 defer s.Close() 1842 1843 c, err := govmomi.NewClient(ctx, s.URL, true) 1844 if err != nil { 1845 t.Fatal(err) 1846 } 1847 1848 vm := object.NewVirtualMachine(c.Client, Map.Any("VirtualMachine").Reference()) 1849 1850 err = vm.MarkAsTemplate(ctx) 1851 if err == nil { 1852 t.Fatal("cannot create template for a powered on vm") 1853 } 1854 1855 task, err := vm.PowerOff(ctx) 1856 if err != nil { 1857 t.Fatal(err) 1858 } 1859 1860 task.Wait(ctx) 1861 1862 err = vm.MarkAsTemplate(ctx) 1863 if err != nil { 1864 t.Fatal(err) 1865 } 1866 1867 _, err = vm.PowerOn(ctx) 1868 if err == nil { 1869 t.Fatal("cannot PowerOn a template") 1870 } 1871 } 1872 1873 func TestVmRefreshStorageInfo(t *testing.T) { 1874 ctx := context.Background() 1875 1876 m := ESX() 1877 defer m.Remove() 1878 err := m.Create() 1879 if err != nil { 1880 t.Fatal(err) 1881 } 1882 1883 s := m.Service.NewServer() 1884 defer s.Close() 1885 1886 c, err := govmomi.NewClient(ctx, s.URL, true) 1887 if err != nil { 1888 t.Fatal(err) 1889 } 1890 1891 vmm := Map.Any("VirtualMachine").(*VirtualMachine) 1892 vm := object.NewVirtualMachine(c.Client, vmm.Reference()) 1893 1894 // take snapshot 1895 task, err := vm.CreateSnapshot(ctx, "root", "description", true, true) 1896 if err != nil { 1897 t.Fatal(err) 1898 } 1899 1900 err = task.Wait(ctx) 1901 if err != nil { 1902 t.Fatal(err) 1903 } 1904 1905 snapshot, err := vm.FindSnapshot(ctx, "root") 1906 if err != nil { 1907 t.Fatal(err) 1908 } 1909 1910 // check vm.Layout.Snapshot 1911 found := false 1912 for _, snapLayout := range vmm.Layout.Snapshot { 1913 if snapLayout.Key == *snapshot { 1914 found = true 1915 } 1916 } 1917 1918 if found == false { 1919 t.Fatal("could not find new snapshot in vm.Layout.Snapshot") 1920 } 1921 1922 // check vm.LayoutEx.Snapshot 1923 found = false 1924 for _, snapLayoutEx := range vmm.LayoutEx.Snapshot { 1925 if snapLayoutEx.Key == *snapshot { 1926 found = true 1927 } 1928 } 1929 1930 if found == false { 1931 t.Fatal("could not find new snapshot in vm.LayoutEx.Snapshot") 1932 } 1933 1934 // remove snapshot 1935 task, err = vm.RemoveAllSnapshot(ctx, nil) 1936 if err != nil { 1937 t.Fatal(err) 1938 } 1939 1940 err = task.Wait(ctx) 1941 if err != nil { 1942 t.Fatal(err) 1943 } 1944 1945 if len(vmm.Layout.Snapshot) != 0 { 1946 t.Fatal("expected vm.Layout.Snapshot to be empty") 1947 } 1948 1949 if len(vmm.LayoutEx.Snapshot) != 0 { 1950 t.Fatal("expected vm.LayoutEx.Snapshot to be empty") 1951 } 1952 1953 device, err := vm.Device(ctx) 1954 if err != nil { 1955 t.Fatal(err) 1956 } 1957 1958 disks := device.SelectByType((*types.VirtualDisk)(nil)) 1959 if len(disks) < 1 { 1960 t.Fatal("expected VM to have at least 1 disk") 1961 } 1962 1963 findDiskFile := func(vmdkName string) *types.VirtualMachineFileLayoutExFileInfo { 1964 for _, dFile := range vmm.LayoutEx.File { 1965 if dFile.Name == vmdkName { 1966 return &dFile 1967 } 1968 } 1969 1970 return nil 1971 } 1972 1973 findDsStorage := func(dsName string) *types.VirtualMachineUsageOnDatastore { 1974 host := Map.Get(*vmm.Runtime.Host).(*HostSystem) 1975 ds := Map.FindByName(dsName, host.Datastore).(*Datastore) 1976 1977 for _, dsUsage := range vmm.Storage.PerDatastoreUsage { 1978 if dsUsage.Datastore == ds.Self { 1979 return &dsUsage 1980 } 1981 } 1982 1983 return nil 1984 } 1985 1986 for _, d := range disks { 1987 disk := d.(*types.VirtualDisk) 1988 info := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 1989 diskLayoutCount := len(vmm.Layout.Disk) 1990 summaryStorageNew := vmm.Summary.Storage 1991 1992 p, fault := parseDatastorePath(info.FileName) 1993 if fault != nil { 1994 t.Fatalf("could not parse datastore path for disk file: %s", info.FileName) 1995 } 1996 1997 storageNew := findDsStorage(p.Datastore) 1998 if storageNew == nil { 1999 t.Fatalf("could not find vm usage on datastore: %s", p.Datastore) 2000 } 2001 2002 diskFile := findDiskFile(info.FileName) 2003 if diskFile == nil { 2004 t.Fatal("could not find disk file in vm.LayoutEx.File") 2005 } 2006 2007 // remove disk 2008 if err = vm.RemoveDevice(ctx, false, d); err != nil { 2009 t.Error(err) 2010 } 2011 2012 summaryStorageOld := summaryStorageNew 2013 summaryStorageNew = vmm.Summary.Storage 2014 2015 storageOld := storageNew 2016 storageNew = findDsStorage(p.Datastore) 2017 if storageNew == nil { 2018 t.Fatalf("could not find vm usage on datastore: %s", p.Datastore) 2019 } 2020 2021 tests := []struct { 2022 got int64 2023 expected int64 2024 }{ 2025 {int64(len(vmm.Layout.Disk)), int64(diskLayoutCount - 1)}, 2026 {summaryStorageNew.Committed, summaryStorageOld.Committed - diskFile.Size}, 2027 {summaryStorageNew.Unshared, summaryStorageOld.Unshared - diskFile.Size}, 2028 {summaryStorageNew.Uncommitted, summaryStorageOld.Uncommitted - disk.CapacityInBytes + diskFile.Size}, 2029 {storageNew.Committed, storageOld.Committed - diskFile.Size}, 2030 {storageNew.Unshared, storageOld.Unshared - diskFile.Size}, 2031 {storageNew.Uncommitted, storageOld.Uncommitted - disk.CapacityInBytes + diskFile.Size}, 2032 } 2033 2034 for _, test := range tests { 2035 if test.got != test.expected { 2036 t.Errorf("expected %d, got %d", test.expected, test.got) 2037 } 2038 } 2039 2040 // add disk 2041 disk.CapacityInBytes = 1000000000 2042 if err = vm.AddDevice(ctx, d); err != nil { 2043 t.Error(err) 2044 } 2045 2046 summaryStorageOld = summaryStorageNew 2047 summaryStorageNew = vmm.Summary.Storage 2048 2049 storageOld = storageNew 2050 storageNew = findDsStorage(p.Datastore) 2051 if storageNew == nil { 2052 t.Fatalf("could not find vm usage on datastore: %s", p.Datastore) 2053 } 2054 2055 diskFile = findDiskFile(info.FileName) 2056 if diskFile == nil { 2057 t.Fatal("could not find disk file in vm.LayoutEx.File") 2058 } 2059 2060 tests = []struct { 2061 got int64 2062 expected int64 2063 }{ 2064 {int64(len(vmm.Layout.Disk)), int64(diskLayoutCount)}, 2065 {summaryStorageNew.Committed, summaryStorageOld.Committed + diskFile.Size}, 2066 {summaryStorageNew.Unshared, summaryStorageOld.Unshared + diskFile.Size}, 2067 {summaryStorageNew.Uncommitted, summaryStorageOld.Uncommitted + disk.CapacityInBytes - diskFile.Size}, 2068 {storageNew.Committed, storageOld.Committed + diskFile.Size}, 2069 {storageNew.Unshared, storageOld.Unshared + diskFile.Size}, 2070 {storageNew.Uncommitted, storageOld.Uncommitted + disk.CapacityInBytes - diskFile.Size}, 2071 } 2072 2073 for _, test := range tests { 2074 if test.got != test.expected { 2075 t.Errorf("expected %d, got %d", test.expected, test.got) 2076 } 2077 } 2078 } 2079 2080 // manually create log file 2081 fileLayoutExCount := len(vmm.LayoutEx.File) 2082 2083 p, fault := parseDatastorePath(vmm.Config.Files.LogDirectory) 2084 if fault != nil { 2085 t.Fatalf("could not parse datastore path: %s", vmm.Config.Files.LogDirectory) 2086 } 2087 2088 f, fault := vmm.createFile(p.String(), "test.log", false) 2089 if fault != nil { 2090 t.Fatal("could not create log file") 2091 } 2092 2093 if len(vmm.LayoutEx.File) != fileLayoutExCount { 2094 t.Errorf("expected %d, got %d", fileLayoutExCount, len(vmm.LayoutEx.File)) 2095 } 2096 2097 if err = vm.RefreshStorageInfo(ctx); err != nil { 2098 t.Error(err) 2099 } 2100 2101 if len(vmm.LayoutEx.File) != fileLayoutExCount+1 { 2102 t.Errorf("expected %d, got %d", fileLayoutExCount+1, len(vmm.LayoutEx.File)) 2103 } 2104 2105 err = f.Close() 2106 if err != nil { 2107 t.Fatalf("f.Close failure: %v", err) 2108 } 2109 err = os.Remove(f.Name()) 2110 if err != nil { 2111 t.Fatalf("os.Remove(%s) failure: %v", f.Name(), err) 2112 } 2113 2114 if err = vm.RefreshStorageInfo(ctx); err != nil { 2115 t.Error(err) 2116 } 2117 2118 if len(vmm.LayoutEx.File) != fileLayoutExCount { 2119 t.Errorf("expected %d, got %d", fileLayoutExCount, len(vmm.LayoutEx.File)) 2120 } 2121 } 2122 2123 func TestApplyExtraConfig(t *testing.T) { 2124 2125 applyAndAssertExtraConfigValue := func( 2126 ctx context.Context, 2127 vm *object.VirtualMachine, 2128 val string, 2129 assertDoesNotExist bool) { 2130 2131 task, err := vm.Reconfigure(ctx, types.VirtualMachineConfigSpec{ 2132 ExtraConfig: []types.BaseOptionValue{ 2133 &types.OptionValue{ 2134 Key: "hello", 2135 Value: val, 2136 }, 2137 }, 2138 }) 2139 if err != nil { 2140 t.Fatal(err) 2141 } 2142 if err := task.Wait(ctx); err != nil { 2143 t.Fatal(err) 2144 } 2145 2146 var moVM mo.VirtualMachine 2147 if err := vm.Properties( 2148 ctx, 2149 vm.Reference(), 2150 []string{"config.extraConfig"}, 2151 &moVM); err != nil { 2152 t.Fatal(err) 2153 } 2154 if moVM.Config == nil { 2155 t.Fatal("nil config") 2156 } 2157 var found bool 2158 for i := range moVM.Config.ExtraConfig { 2159 bov := moVM.Config.ExtraConfig[i] 2160 if bov == nil { 2161 continue 2162 } 2163 ov := bov.GetOptionValue() 2164 if ov == nil { 2165 continue 2166 } 2167 if ov.Key == "hello" { 2168 if ov.Value != val { 2169 t.Fatalf("invalid ExtraConfig value: expected=%s, actual=%v", val, ov.Value) 2170 } 2171 found = true 2172 } 2173 } 2174 if !assertDoesNotExist && !found { 2175 t.Fatal("failed to apply ExtraConfig") 2176 } 2177 } 2178 2179 Test(func(ctx context.Context, c *vim25.Client) { 2180 vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference()) 2181 applyAndAssertExtraConfigValue(ctx, vm, "world", false) 2182 applyAndAssertExtraConfigValue(ctx, vm, "there", false) 2183 applyAndAssertExtraConfigValue(ctx, vm, "", true) 2184 }) 2185 } 2186 2187 func TestLastModifiedAndChangeVersionAreUpdated(t *testing.T) { 2188 Test(func(ctx context.Context, c *vim25.Client) { 2189 vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference()) 2190 var vmMo mo.VirtualMachine 2191 if err := vm.Properties( 2192 ctx, 2193 vm.Reference(), 2194 []string{"config.modified", "config.changeVersion"}, 2195 &vmMo); err != nil { 2196 2197 t.Fatalf("failed to fetch initial vm props: %v", err) 2198 } 2199 2200 oldModified := vmMo.Config.Modified 2201 oldChangeVersion := vmMo.Config.ChangeVersion 2202 2203 tsk, err := vm.Reconfigure(ctx, types.VirtualMachineConfigSpec{ 2204 ExtraConfig: []types.BaseOptionValue{ 2205 &types.OptionValue{ 2206 Key: "hello", 2207 Value: "world", 2208 }, 2209 }, 2210 }) 2211 if err != nil { 2212 t.Fatalf("failed to call reconfigure api: %v", err) 2213 } 2214 if err := tsk.WaitEx(ctx); err != nil { 2215 t.Fatalf("failed to reconfigure: %v", err) 2216 } 2217 2218 if err := vm.Properties( 2219 ctx, 2220 vm.Reference(), 2221 []string{"config.modified", "config.changeVersion"}, 2222 &vmMo); err != nil { 2223 2224 t.Fatalf("failed to fetch vm props after reconfigure: %v", err) 2225 } 2226 2227 newModified := vmMo.Config.Modified 2228 newChangeVersion := vmMo.Config.ChangeVersion 2229 2230 if a, e := newModified, oldModified; a == e { 2231 t.Errorf("config.modified was not updated: %v", a) 2232 } 2233 2234 if a, e := newChangeVersion, oldChangeVersion; a == e { 2235 t.Errorf("config.changeVersion was not updated: %v", a) 2236 } 2237 }) 2238 } 2239 2240 func TestUpgradeVm(t *testing.T) { 2241 2242 const ( 2243 vmx1 = "vmx-1" 2244 vmx2 = "vmx-2" 2245 vmx15 = "vmx-15" 2246 vmx17 = "vmx-17" 2247 vmx19 = "vmx-19" 2248 vmx20 = "vmx-20" 2249 vmx21 = "vmx-21" 2250 vmx22 = "vmx-22" 2251 ) 2252 2253 model := VPX() 2254 model.Autostart = false 2255 model.Cluster = 1 2256 model.ClusterHost = 1 2257 model.Host = 1 2258 2259 Test(func(ctx context.Context, c *vim25.Client) { 2260 props := []string{"config.version", "summary.config.hwVersion"} 2261 2262 vm := object.NewVirtualMachine(c, Map.Any("VirtualMachine").Reference()) 2263 vm2 := Map.Get(vm.Reference()).(*VirtualMachine) 2264 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2265 vm2.Config.Version = vmx15 2266 }) 2267 2268 host, err := vm.HostSystem(ctx) 2269 if err != nil { 2270 t.Fatalf("failed to get vm's host: %v", err) 2271 } 2272 host2 := Map.Get(host.Reference()).(*HostSystem) 2273 2274 var eb *EnvironmentBrowser 2275 { 2276 ref := Map.Get(host.Reference()).(*HostSystem).Parent 2277 switch ref.Type { 2278 case "ClusterComputeResource": 2279 obj := Map.Get(*ref).(*ClusterComputeResource) 2280 eb = Map.Get(*obj.EnvironmentBrowser).(*EnvironmentBrowser) 2281 case "ComputeResource": 2282 obj := Map.Get(*ref).(*mo.ComputeResource) 2283 eb = Map.Get(*obj.EnvironmentBrowser).(*EnvironmentBrowser) 2284 } 2285 } 2286 2287 baseline := func() { 2288 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2289 vm2.Config.Version = vmx15 2290 vm2.Config.Template = false 2291 vm2.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff 2292 }) 2293 Map.WithLock(SpoofContext(), host2.Reference(), func() { 2294 host2.Runtime.InMaintenanceMode = false 2295 }) 2296 Map.WithLock(SpoofContext(), eb.Reference(), func() { 2297 for i := range eb.QueryConfigOptionDescriptorResponse.Returnval { 2298 cod := &eb.QueryConfigOptionDescriptorResponse.Returnval[i] 2299 hostFound := false 2300 for j := range cod.Host { 2301 if cod.Host[j].Value == host2.Reference().Value { 2302 hostFound = true 2303 break 2304 } 2305 } 2306 if !hostFound { 2307 cod.Host = append(cod.Host, host2.Reference()) 2308 } 2309 } 2310 }) 2311 } 2312 2313 t.Run("InvalidPowerState", func(t *testing.T) { 2314 baseline() 2315 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2316 vm2.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOn 2317 }) 2318 2319 tsk, err := vm.UpgradeVM(ctx, vmx15) 2320 if err != nil { 2321 t.Fatalf("failed to call upgradeVm api: %v", err) 2322 } 2323 if _, err := tsk.WaitForResultEx(ctx); err == nil { 2324 t.Fatal("expected error did not occur") 2325 } else if err2, ok := err.(task.Error); !ok { 2326 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2327 } else if f := err2.Fault(); f == nil { 2328 t.Fatal("fault is nil") 2329 } else if f2, ok := f.(*types.InvalidPowerStateFault); !ok { 2330 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2331 } else { 2332 if f2.ExistingState != types.VirtualMachinePowerStatePoweredOn { 2333 t.Errorf("unexpected existing state: %v", f2.ExistingState) 2334 } 2335 if f2.RequestedState != types.VirtualMachinePowerStatePoweredOff { 2336 t.Errorf("unexpected requested state: %v", f2.RequestedState) 2337 } 2338 } 2339 }) 2340 2341 t.Run("InvalidState", func(t *testing.T) { 2342 t.Run("MaintenanceMode", func(t *testing.T) { 2343 baseline() 2344 Map.WithLock(SpoofContext(), host2.Reference(), func() { 2345 host2.Runtime.InMaintenanceMode = true 2346 }) 2347 2348 if tsk, err := vm.UpgradeVM(ctx, vmx15); err != nil { 2349 t.Fatalf("failed to call upgradeVm api: %v", err) 2350 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2351 t.Fatal("expected error did not occur") 2352 } else if err, ok := err.(task.Error); !ok { 2353 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2354 } else if f := err.Fault(); f == nil { 2355 t.Fatal("fault is nil") 2356 } else if f2, ok := f.(*types.InvalidState); !ok { 2357 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2358 } else if fc := f2.FaultCause; fc == nil { 2359 t.Fatal("fault cause is nil") 2360 } else if fc.LocalizedMessage != fmt.Sprintf("%s in maintenance mode", host.Reference().Value) { 2361 t.Fatalf("unexpected error message: %s", fc.LocalizedMessage) 2362 } 2363 }) 2364 2365 t.Run("Template", func(t *testing.T) { 2366 baseline() 2367 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2368 vm2.Config.Template = true 2369 }) 2370 2371 if tsk, err := vm.UpgradeVM(ctx, vmx15); err != nil { 2372 t.Fatalf("failed to call upgradeVm api: %v", err) 2373 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2374 t.Fatal("expected error did not occur") 2375 } else if err, ok := err.(task.Error); !ok { 2376 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2377 } else if f := err.Fault(); f == nil { 2378 t.Fatal("fault is nil") 2379 } else if f2, ok := f.(*types.InvalidState); !ok { 2380 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2381 } else if fc := f2.FaultCause; fc == nil { 2382 t.Fatal("fault cause is nil") 2383 } else if fc.LocalizedMessage != fmt.Sprintf("%s is template", vm.Reference().Value) { 2384 t.Fatalf("unexpected error message: %s", fc.LocalizedMessage) 2385 } 2386 }) 2387 2388 t.Run("LatestHardwareVersion", func(t *testing.T) { 2389 baseline() 2390 Map.WithLock(SpoofContext(), vm.Reference(), func() { 2391 vm2.Config.Version = vmx21 2392 }) 2393 2394 if tsk, err := vm.UpgradeVM(ctx, vmx21); err != nil { 2395 t.Fatalf("failed to call upgradeVm api: %v", err) 2396 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2397 t.Fatal("expected error did not occur") 2398 } else if err, ok := err.(task.Error); !ok { 2399 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2400 } else if f := err.Fault(); f == nil { 2401 t.Fatal("fault is nil") 2402 } else if f2, ok := f.(*types.InvalidState); !ok { 2403 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2404 } else if fc := f2.FaultCause; fc == nil { 2405 t.Fatal("fault cause is nil") 2406 } else if fc.LocalizedMessage != fmt.Sprintf("%s is latest version", vm.Reference().Value) { 2407 t.Fatalf("unexpected error message: %s", fc.LocalizedMessage) 2408 } 2409 }) 2410 }) 2411 2412 t.Run("NotSupported", func(t *testing.T) { 2413 t.Run("AtAll", func(t *testing.T) { 2414 baseline() 2415 2416 if tsk, err := vm.UpgradeVM(ctx, vmx22); err != nil { 2417 t.Fatalf("failed to call upgradeVm api: %v", err) 2418 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2419 t.Fatal("expected error did not occur") 2420 } else if err, ok := err.(task.Error); !ok { 2421 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2422 } else if f := err.Fault(); f == nil { 2423 t.Fatal("fault is nil") 2424 } else if f2, ok := f.(*types.NotSupported); !ok { 2425 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2426 } else if fc := f2.FaultCause; fc == nil { 2427 t.Fatal("fault cause is nil") 2428 } else if fc.LocalizedMessage != "vmx-22 not supported" { 2429 t.Fatalf("unexpected error message: %s", fc.LocalizedMessage) 2430 } 2431 }) 2432 t.Run("OnVmHost", func(t *testing.T) { 2433 baseline() 2434 Map.WithLock(SpoofContext(), eb.Reference(), func() { 2435 for i := range eb.QueryConfigOptionDescriptorResponse.Returnval { 2436 cod := &eb.QueryConfigOptionDescriptorResponse.Returnval[i] 2437 if cod.Key == vmx17 { 2438 cod.Host = nil 2439 } 2440 } 2441 }) 2442 2443 if tsk, err := vm.UpgradeVM(ctx, vmx17); err != nil { 2444 t.Fatalf("failed to call upgradeVm api: %v", err) 2445 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2446 t.Fatal("expected error did not occur") 2447 } else if err, ok := err.(task.Error); !ok { 2448 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2449 } else if f := err.Fault(); f == nil { 2450 t.Fatal("fault is nil") 2451 } else if f2, ok := f.(*types.NotSupported); !ok { 2452 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2453 } else if fc := f2.FaultCause; fc == nil { 2454 t.Fatal("fault cause is nil") 2455 } else if fc.LocalizedMessage != "vmx-17 not supported" { 2456 t.Fatalf("unexpected error message: %s", fc.LocalizedMessage) 2457 } 2458 }) 2459 }) 2460 2461 t.Run("AlreadyUpgraded", func(t *testing.T) { 2462 t.Run("EqualToTargetVersion", func(t *testing.T) { 2463 baseline() 2464 if tsk, err := vm.UpgradeVM(ctx, vmx15); err != nil { 2465 t.Fatalf("failed to call upgradeVm api: %v", err) 2466 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2467 t.Fatal("expected error did not occur") 2468 } else if err, ok := err.(task.Error); !ok { 2469 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2470 } else if f := err.Fault(); f == nil { 2471 t.Fatal("fault is nil") 2472 } else if _, ok := f.(*types.AlreadyUpgradedFault); !ok { 2473 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2474 } 2475 }) 2476 2477 t.Run("GreaterThanTargetVersion", func(t *testing.T) { 2478 baseline() 2479 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2480 vm2.Config.Version = vmx20 2481 }) 2482 if tsk, err := vm.UpgradeVM(ctx, vmx17); err != nil { 2483 t.Fatalf("failed to call upgradeVm api: %v", err) 2484 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2485 t.Fatal("expected error did not occur") 2486 } else if err, ok := err.(task.Error); !ok { 2487 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2488 } else if f := err.Fault(); f == nil { 2489 t.Fatal("fault is nil") 2490 } else if _, ok := f.(*types.AlreadyUpgradedFault); !ok { 2491 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2492 } 2493 }) 2494 }) 2495 2496 t.Run("InvalidArgument", func(t *testing.T) { 2497 baseline() 2498 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2499 vm2.Config.Version = vmx1 2500 }) 2501 Map.WithLock(SpoofContext(), eb.Reference(), func() { 2502 eb.QueryConfigOptionDescriptorResponse.Returnval = append( 2503 eb.QueryConfigOptionDescriptorResponse.Returnval, 2504 types.VirtualMachineConfigOptionDescriptor{ 2505 Key: vmx2, 2506 Host: []types.ManagedObjectReference{host.Reference()}, 2507 }) 2508 }) 2509 2510 if tsk, err := vm.UpgradeVM(ctx, vmx2); err != nil { 2511 t.Fatalf("failed to call upgradeVm api: %v", err) 2512 } else if _, err := tsk.WaitForResultEx(ctx); err == nil { 2513 t.Fatal("expected error did not occur") 2514 } else if err, ok := err.(task.Error); !ok { 2515 t.Fatalf("unexpected error: %[1]T %+[1]v", err) 2516 } else if f := err.Fault(); f == nil { 2517 t.Fatal("fault is nil") 2518 } else if _, ok := f.(*types.InvalidArgument); !ok { 2519 t.Fatalf("unexpected fault: %[1]T %+[1]v", f) 2520 } 2521 }) 2522 2523 t.Run("UpgradeToLatest", func(t *testing.T) { 2524 baseline() 2525 2526 if tsk, err := vm.UpgradeVM(ctx, ""); err != nil { 2527 t.Fatalf("failed to call upgradeVm api: %v", err) 2528 } else if _, err := tsk.WaitForResultEx(ctx); err != nil { 2529 t.Fatalf("failed to upgrade vm: %v", err) 2530 } 2531 var vmMo mo.VirtualMachine 2532 if err := vm.Properties( 2533 ctx, 2534 vm.Reference(), 2535 props, 2536 &vmMo); err != nil { 2537 2538 t.Fatalf("failed to fetch vm props after upgrade: %v", err) 2539 } 2540 if v := vmMo.Config.Version; v != vmx21 { 2541 t.Fatalf("unexpected config.version %v", v) 2542 } 2543 if v := vmMo.Summary.Config.HwVersion; v != vmx21 { 2544 t.Fatalf("unexpected summary.config.hwVersion %v", v) 2545 } 2546 }) 2547 2548 t.Run("UpgradeFrom15To17", func(t *testing.T) { 2549 const targetVersion = vmx17 2550 baseline() 2551 2552 if tsk, err := vm.UpgradeVM(ctx, targetVersion); err != nil { 2553 t.Fatalf("failed to call upgradeVm api: %v", err) 2554 } else if _, err := tsk.WaitForResultEx(ctx); err != nil { 2555 t.Fatalf("failed to upgrade vm: %v", err) 2556 } 2557 var vmMo mo.VirtualMachine 2558 if err := vm.Properties( 2559 ctx, 2560 vm.Reference(), 2561 props, 2562 &vmMo); err != nil { 2563 2564 t.Fatalf("failed to fetch vm props after upgrade: %v", err) 2565 } 2566 if v := vmMo.Config.Version; v != targetVersion { 2567 t.Fatalf("unexpected config.version %v", v) 2568 } 2569 if v := vmMo.Summary.Config.HwVersion; v != targetVersion { 2570 t.Fatalf("unexpected summary.config.hwVersion %v", v) 2571 } 2572 }) 2573 2574 t.Run("UpgradeFrom17To20", func(t *testing.T) { 2575 const targetVersion = vmx20 2576 baseline() 2577 Map.WithLock(SpoofContext(), vm2.Reference(), func() { 2578 vm2.Config.Version = vmx17 2579 }) 2580 2581 if tsk, err := vm.UpgradeVM(ctx, targetVersion); err != nil { 2582 t.Fatalf("failed to call upgradeVm api: %v", err) 2583 } else if _, err := tsk.WaitForResultEx(ctx); err != nil { 2584 t.Fatalf("failed to upgrade vm: %v", err) 2585 } 2586 var vmMo mo.VirtualMachine 2587 if err := vm.Properties( 2588 ctx, 2589 vm.Reference(), 2590 props, 2591 &vmMo); err != nil { 2592 2593 t.Fatalf("failed to fetch vm props after upgrade: %v", err) 2594 } 2595 if v := vmMo.Config.Version; v != targetVersion { 2596 t.Fatalf("unexpected config.version %v", v) 2597 } 2598 if v := vmMo.Summary.Config.HwVersion; v != targetVersion { 2599 t.Fatalf("unexpected summary.config.hwVersion %v", v) 2600 } 2601 }) 2602 2603 t.Run("UpgradeFrom15To17To20", func(t *testing.T) { 2604 const ( 2605 targetVersion1 = vmx17 2606 targetVersion2 = vmx20 2607 ) 2608 baseline() 2609 2610 if tsk, err := vm.UpgradeVM(ctx, targetVersion1); err != nil { 2611 t.Fatalf("failed to call upgradeVm api first time: %v", err) 2612 } else if _, err := tsk.WaitForResultEx(ctx); err != nil { 2613 t.Fatalf("failed to upgrade vm first time: %v", err) 2614 } 2615 var vmMo mo.VirtualMachine 2616 if err := vm.Properties( 2617 ctx, 2618 vm.Reference(), 2619 props, 2620 &vmMo); err != nil { 2621 2622 t.Fatalf("failed to fetch vm props after first upgrade: %v", err) 2623 } 2624 if v := vmMo.Config.Version; v != targetVersion1 { 2625 t.Fatalf("unexpected config.version after first upgrade %v", v) 2626 } 2627 if v := vmMo.Summary.Config.HwVersion; v != targetVersion1 { 2628 t.Fatalf("unexpected summary.config.hwVersion after first upgrade %v", v) 2629 } 2630 2631 if tsk, err := vm.UpgradeVM(ctx, targetVersion2); err != nil { 2632 t.Fatalf("failed to call upgradeVm api second time: %v", err) 2633 } else if _, err := tsk.WaitForResultEx(ctx); err != nil { 2634 t.Fatalf("failed to upgrade vm second time: %v", err) 2635 } 2636 if err := vm.Properties( 2637 ctx, 2638 vm.Reference(), 2639 props, 2640 &vmMo); err != nil { 2641 2642 t.Fatalf("failed to fetch vm props after second upgrade: %v", err) 2643 } 2644 if v := vmMo.Config.Version; v != targetVersion2 { 2645 t.Fatalf("unexpected config.version after second upgrade %v", v) 2646 } 2647 if v := vmMo.Summary.Config.HwVersion; v != targetVersion2 { 2648 t.Fatalf("unexpected summary.config.hwVersion after second upgrade %v", v) 2649 } 2650 }) 2651 2652 }, model) 2653 }