github.com/vmware/govmomi@v0.37.1/object/virtual_machine.go (about) 1 /* 2 Copyright (c) 2015-2023 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 object 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net" 24 "path" 25 "strings" 26 27 "github.com/vmware/govmomi/nfc" 28 "github.com/vmware/govmomi/property" 29 "github.com/vmware/govmomi/vim25" 30 "github.com/vmware/govmomi/vim25/methods" 31 "github.com/vmware/govmomi/vim25/mo" 32 "github.com/vmware/govmomi/vim25/types" 33 ) 34 35 const ( 36 PropRuntimePowerState = "summary.runtime.powerState" 37 PropConfigTemplate = "summary.config.template" 38 ) 39 40 type VirtualMachine struct { 41 Common 42 } 43 44 // extractDiskLayoutFiles is a helper function used to extract file keys for 45 // all disk files attached to the virtual machine at the current point of 46 // running. 47 func extractDiskLayoutFiles(diskLayoutList []types.VirtualMachineFileLayoutExDiskLayout) []int { 48 var result []int 49 50 for _, layoutExDisk := range diskLayoutList { 51 for _, link := range layoutExDisk.Chain { 52 for i := range link.FileKey { // diskDescriptor, diskExtent pairs 53 result = append(result, int(link.FileKey[i])) 54 } 55 } 56 } 57 58 return result 59 } 60 61 // removeKey is a helper function for removing a specific file key from a list 62 // of keys associated with disks attached to a virtual machine. 63 func removeKey(l *[]int, key int) { 64 for i, k := range *l { 65 if k == key { 66 *l = append((*l)[:i], (*l)[i+1:]...) 67 break 68 } 69 } 70 } 71 72 func NewVirtualMachine(c *vim25.Client, ref types.ManagedObjectReference) *VirtualMachine { 73 return &VirtualMachine{ 74 Common: NewCommon(c, ref), 75 } 76 } 77 78 func (v VirtualMachine) PowerState(ctx context.Context) (types.VirtualMachinePowerState, error) { 79 var o mo.VirtualMachine 80 81 err := v.Properties(ctx, v.Reference(), []string{PropRuntimePowerState}, &o) 82 if err != nil { 83 return "", err 84 } 85 86 return o.Summary.Runtime.PowerState, nil 87 } 88 89 func (v VirtualMachine) IsTemplate(ctx context.Context) (bool, error) { 90 var o mo.VirtualMachine 91 92 err := v.Properties(ctx, v.Reference(), []string{PropConfigTemplate}, &o) 93 if err != nil { 94 return false, err 95 } 96 97 return o.Summary.Config.Template, nil 98 } 99 100 func (v VirtualMachine) PowerOn(ctx context.Context) (*Task, error) { 101 req := types.PowerOnVM_Task{ 102 This: v.Reference(), 103 } 104 105 res, err := methods.PowerOnVM_Task(ctx, v.c, &req) 106 if err != nil { 107 return nil, err 108 } 109 110 return NewTask(v.c, res.Returnval), nil 111 } 112 113 func (v VirtualMachine) PowerOff(ctx context.Context) (*Task, error) { 114 req := types.PowerOffVM_Task{ 115 This: v.Reference(), 116 } 117 118 res, err := methods.PowerOffVM_Task(ctx, v.c, &req) 119 if err != nil { 120 return nil, err 121 } 122 123 return NewTask(v.c, res.Returnval), nil 124 } 125 126 func (v VirtualMachine) PutUsbScanCodes(ctx context.Context, spec types.UsbScanCodeSpec) (int32, error) { 127 req := types.PutUsbScanCodes{ 128 This: v.Reference(), 129 Spec: spec, 130 } 131 132 res, err := methods.PutUsbScanCodes(ctx, v.c, &req) 133 if err != nil { 134 return 0, err 135 } 136 137 return res.Returnval, nil 138 } 139 140 func (v VirtualMachine) Reset(ctx context.Context) (*Task, error) { 141 req := types.ResetVM_Task{ 142 This: v.Reference(), 143 } 144 145 res, err := methods.ResetVM_Task(ctx, v.c, &req) 146 if err != nil { 147 return nil, err 148 } 149 150 return NewTask(v.c, res.Returnval), nil 151 } 152 153 func (v VirtualMachine) Suspend(ctx context.Context) (*Task, error) { 154 req := types.SuspendVM_Task{ 155 This: v.Reference(), 156 } 157 158 res, err := methods.SuspendVM_Task(ctx, v.c, &req) 159 if err != nil { 160 return nil, err 161 } 162 163 return NewTask(v.c, res.Returnval), nil 164 } 165 166 func (v VirtualMachine) ShutdownGuest(ctx context.Context) error { 167 req := types.ShutdownGuest{ 168 This: v.Reference(), 169 } 170 171 _, err := methods.ShutdownGuest(ctx, v.c, &req) 172 return err 173 } 174 175 func (v VirtualMachine) StandbyGuest(ctx context.Context) error { 176 req := types.StandbyGuest{ 177 This: v.Reference(), 178 } 179 180 _, err := methods.StandbyGuest(ctx, v.c, &req) 181 return err 182 } 183 184 func (v VirtualMachine) RebootGuest(ctx context.Context) error { 185 req := types.RebootGuest{ 186 This: v.Reference(), 187 } 188 189 _, err := methods.RebootGuest(ctx, v.c, &req) 190 return err 191 } 192 193 func (v VirtualMachine) Destroy(ctx context.Context) (*Task, error) { 194 req := types.Destroy_Task{ 195 This: v.Reference(), 196 } 197 198 res, err := methods.Destroy_Task(ctx, v.c, &req) 199 if err != nil { 200 return nil, err 201 } 202 203 return NewTask(v.c, res.Returnval), nil 204 } 205 206 func (v VirtualMachine) Clone(ctx context.Context, folder *Folder, name string, config types.VirtualMachineCloneSpec) (*Task, error) { 207 req := types.CloneVM_Task{ 208 This: v.Reference(), 209 Folder: folder.Reference(), 210 Name: name, 211 Spec: config, 212 } 213 214 res, err := methods.CloneVM_Task(ctx, v.c, &req) 215 if err != nil { 216 return nil, err 217 } 218 219 return NewTask(v.c, res.Returnval), nil 220 } 221 222 func (v VirtualMachine) InstantClone(ctx context.Context, config types.VirtualMachineInstantCloneSpec) (*Task, error) { 223 req := types.InstantClone_Task{ 224 This: v.Reference(), 225 Spec: config, 226 } 227 228 res, err := methods.InstantClone_Task(ctx, v.c, &req) 229 if err != nil { 230 return nil, err 231 } 232 233 return NewTask(v.c, res.Returnval), nil 234 } 235 236 func (v VirtualMachine) Customize(ctx context.Context, spec types.CustomizationSpec) (*Task, error) { 237 req := types.CustomizeVM_Task{ 238 This: v.Reference(), 239 Spec: spec, 240 } 241 242 res, err := methods.CustomizeVM_Task(ctx, v.c, &req) 243 if err != nil { 244 return nil, err 245 } 246 247 return NewTask(v.c, res.Returnval), nil 248 } 249 250 func (v VirtualMachine) Relocate(ctx context.Context, config types.VirtualMachineRelocateSpec, priority types.VirtualMachineMovePriority) (*Task, error) { 251 req := types.RelocateVM_Task{ 252 This: v.Reference(), 253 Spec: config, 254 Priority: priority, 255 } 256 257 res, err := methods.RelocateVM_Task(ctx, v.c, &req) 258 if err != nil { 259 return nil, err 260 } 261 262 return NewTask(v.c, res.Returnval), nil 263 } 264 265 func (v VirtualMachine) Reconfigure(ctx context.Context, config types.VirtualMachineConfigSpec) (*Task, error) { 266 req := types.ReconfigVM_Task{ 267 This: v.Reference(), 268 Spec: config, 269 } 270 271 res, err := methods.ReconfigVM_Task(ctx, v.c, &req) 272 if err != nil { 273 return nil, err 274 } 275 276 return NewTask(v.c, res.Returnval), nil 277 } 278 279 func (v VirtualMachine) RefreshStorageInfo(ctx context.Context) error { 280 req := types.RefreshStorageInfo{ 281 This: v.Reference(), 282 } 283 284 _, err := methods.RefreshStorageInfo(ctx, v.c, &req) 285 return err 286 } 287 288 // WaitForIP waits for the VM guest.ipAddress property to report an IP address. 289 // Waits for an IPv4 address if the v4 param is true. 290 func (v VirtualMachine) WaitForIP(ctx context.Context, v4 ...bool) (string, error) { 291 var ip string 292 293 p := property.DefaultCollector(v.c) 294 err := property.Wait(ctx, p, v.Reference(), []string{"guest.ipAddress"}, func(pc []types.PropertyChange) bool { 295 for _, c := range pc { 296 if c.Name != "guest.ipAddress" { 297 continue 298 } 299 if c.Op != types.PropertyChangeOpAssign { 300 continue 301 } 302 if c.Val == nil { 303 continue 304 } 305 306 ip = c.Val.(string) 307 if len(v4) == 1 && v4[0] { 308 if net.ParseIP(ip).To4() == nil { 309 return false 310 } 311 } 312 return true 313 } 314 315 return false 316 }) 317 318 if err != nil { 319 return "", err 320 } 321 322 return ip, nil 323 } 324 325 // WaitForNetIP waits for the VM guest.net property to report an IP address for all VM NICs. 326 // Only consider IPv4 addresses if the v4 param is true. 327 // By default, wait for all NICs to get an IP address, unless 1 or more device is given. 328 // A device can be specified by the MAC address or the device name, e.g. "ethernet-0". 329 // Returns a map with MAC address as the key and IP address list as the value. 330 func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool, device ...string) (map[string][]string, error) { 331 macs := make(map[string][]string) 332 eths := make(map[string]string) 333 334 p := property.DefaultCollector(v.c) 335 336 // Wait for all NICs to have a MacAddress, which may not be generated yet. 337 err := property.Wait(ctx, p, v.Reference(), []string{"config.hardware.device"}, func(pc []types.PropertyChange) bool { 338 for _, c := range pc { 339 if c.Op != types.PropertyChangeOpAssign { 340 continue 341 } 342 343 devices := VirtualDeviceList(c.Val.(types.ArrayOfVirtualDevice).VirtualDevice) 344 for _, d := range devices { 345 if nic, ok := d.(types.BaseVirtualEthernetCard); ok { 346 // Convert to lower so that e.g. 00:50:56:83:3A:5D is treated the 347 // same as 00:50:56:83:3a:5d 348 mac := strings.ToLower(nic.GetVirtualEthernetCard().MacAddress) 349 if mac == "" { 350 return false 351 } 352 macs[mac] = nil 353 eths[devices.Name(d)] = mac 354 } 355 } 356 } 357 358 return true 359 }) 360 361 if err != nil { 362 return nil, err 363 } 364 365 if len(device) != 0 { 366 // Only wait for specific NIC(s) 367 macs = make(map[string][]string) 368 for _, mac := range device { 369 if eth, ok := eths[mac]; ok { 370 mac = eth // device name, e.g. "ethernet-0" 371 } 372 macs[mac] = nil 373 } 374 } 375 376 err = property.Wait(ctx, p, v.Reference(), []string{"guest.net"}, func(pc []types.PropertyChange) bool { 377 for _, c := range pc { 378 if c.Op != types.PropertyChangeOpAssign { 379 continue 380 } 381 382 nics := c.Val.(types.ArrayOfGuestNicInfo).GuestNicInfo 383 for _, nic := range nics { 384 // Convert to lower so that e.g. 00:50:56:83:3A:5D is treated the 385 // same as 00:50:56:83:3a:5d 386 mac := strings.ToLower(nic.MacAddress) 387 if mac == "" || nic.IpConfig == nil { 388 continue 389 } 390 391 for _, ip := range nic.IpConfig.IpAddress { 392 if _, ok := macs[mac]; !ok { 393 continue // Ignore any that don't correspond to a VM device 394 } 395 if v4 && net.ParseIP(ip.IpAddress).To4() == nil { 396 continue // Ignore non IPv4 address 397 } 398 macs[mac] = append(macs[mac], ip.IpAddress) 399 } 400 } 401 } 402 403 for _, ips := range macs { 404 if len(ips) == 0 { 405 return false 406 } 407 } 408 409 return true 410 }) 411 412 if err != nil { 413 return nil, err 414 } 415 416 return macs, nil 417 } 418 419 // Device returns the VirtualMachine's config.hardware.device property. 420 func (v VirtualMachine) Device(ctx context.Context) (VirtualDeviceList, error) { 421 var o mo.VirtualMachine 422 423 err := v.Properties(ctx, v.Reference(), []string{"config.hardware.device", "summary.runtime.connectionState"}, &o) 424 if err != nil { 425 return nil, err 426 } 427 428 // Quoting the SDK doc: 429 // The virtual machine configuration is not guaranteed to be available. 430 // For example, the configuration information would be unavailable if the server 431 // is unable to access the virtual machine files on disk, and is often also unavailable 432 // during the initial phases of virtual machine creation. 433 if o.Config == nil { 434 return nil, fmt.Errorf("%s Config is not available, connectionState=%s", 435 v.Reference(), o.Summary.Runtime.ConnectionState) 436 } 437 438 return VirtualDeviceList(o.Config.Hardware.Device), nil 439 } 440 441 func (v VirtualMachine) EnvironmentBrowser(ctx context.Context) (*EnvironmentBrowser, error) { 442 var vm mo.VirtualMachine 443 444 err := v.Properties(ctx, v.Reference(), []string{"environmentBrowser"}, &vm) 445 if err != nil { 446 return nil, err 447 } 448 449 return NewEnvironmentBrowser(v.c, vm.EnvironmentBrowser), nil 450 } 451 452 func (v VirtualMachine) HostSystem(ctx context.Context) (*HostSystem, error) { 453 var o mo.VirtualMachine 454 455 err := v.Properties(ctx, v.Reference(), []string{"summary.runtime.host"}, &o) 456 if err != nil { 457 return nil, err 458 } 459 460 host := o.Summary.Runtime.Host 461 if host == nil { 462 return nil, errors.New("VM doesn't have a HostSystem") 463 } 464 465 return NewHostSystem(v.c, *host), nil 466 } 467 468 func (v VirtualMachine) ResourcePool(ctx context.Context) (*ResourcePool, error) { 469 var o mo.VirtualMachine 470 471 err := v.Properties(ctx, v.Reference(), []string{"resourcePool"}, &o) 472 if err != nil { 473 return nil, err 474 } 475 476 rp := o.ResourcePool 477 if rp == nil { 478 return nil, errors.New("VM doesn't have a resourcePool") 479 } 480 481 return NewResourcePool(v.c, *rp), nil 482 } 483 484 func diskFileOperation(op types.VirtualDeviceConfigSpecOperation, fop types.VirtualDeviceConfigSpecFileOperation, device types.BaseVirtualDevice) types.VirtualDeviceConfigSpecFileOperation { 485 if disk, ok := device.(*types.VirtualDisk); ok { 486 // Special case to attach an existing disk 487 if op == types.VirtualDeviceConfigSpecOperationAdd && disk.CapacityInKB == 0 && disk.CapacityInBytes == 0 { 488 childDisk := false 489 if b, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok { 490 childDisk = b.Parent != nil 491 } 492 493 if !childDisk { 494 fop = "" // existing disk 495 } 496 } 497 return fop 498 } 499 500 return "" 501 } 502 503 func (v VirtualMachine) configureDevice(ctx context.Context, op types.VirtualDeviceConfigSpecOperation, fop types.VirtualDeviceConfigSpecFileOperation, devices ...types.BaseVirtualDevice) error { 504 spec := types.VirtualMachineConfigSpec{} 505 506 for _, device := range devices { 507 config := &types.VirtualDeviceConfigSpec{ 508 Device: device, 509 Operation: op, 510 FileOperation: diskFileOperation(op, fop, device), 511 } 512 513 spec.DeviceChange = append(spec.DeviceChange, config) 514 } 515 516 task, err := v.Reconfigure(ctx, spec) 517 if err != nil { 518 return err 519 } 520 521 return task.Wait(ctx) 522 } 523 524 // AddDevice adds the given devices to the VirtualMachine 525 func (v VirtualMachine) AddDevice(ctx context.Context, device ...types.BaseVirtualDevice) error { 526 return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationAdd, types.VirtualDeviceConfigSpecFileOperationCreate, device...) 527 } 528 529 // EditDevice edits the given (existing) devices on the VirtualMachine 530 func (v VirtualMachine) EditDevice(ctx context.Context, device ...types.BaseVirtualDevice) error { 531 return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationEdit, types.VirtualDeviceConfigSpecFileOperationReplace, device...) 532 } 533 534 // RemoveDevice removes the given devices on the VirtualMachine 535 func (v VirtualMachine) RemoveDevice(ctx context.Context, keepFiles bool, device ...types.BaseVirtualDevice) error { 536 fop := types.VirtualDeviceConfigSpecFileOperationDestroy 537 if keepFiles { 538 fop = "" 539 } 540 return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, fop, device...) 541 } 542 543 // AttachDisk attaches the given disk to the VirtualMachine 544 func (v VirtualMachine) AttachDisk(ctx context.Context, id string, datastore *Datastore, controllerKey int32, unitNumber int32) error { 545 req := types.AttachDisk_Task{ 546 This: v.Reference(), 547 DiskId: types.ID{Id: id}, 548 Datastore: datastore.Reference(), 549 ControllerKey: controllerKey, 550 UnitNumber: &unitNumber, 551 } 552 553 res, err := methods.AttachDisk_Task(ctx, v.c, &req) 554 if err != nil { 555 return err 556 } 557 558 task := NewTask(v.c, res.Returnval) 559 return task.Wait(ctx) 560 } 561 562 // DetachDisk detaches the given disk from the VirtualMachine 563 func (v VirtualMachine) DetachDisk(ctx context.Context, id string) error { 564 req := types.DetachDisk_Task{ 565 This: v.Reference(), 566 DiskId: types.ID{Id: id}, 567 } 568 569 res, err := methods.DetachDisk_Task(ctx, v.c, &req) 570 if err != nil { 571 return err 572 } 573 574 task := NewTask(v.c, res.Returnval) 575 return task.Wait(ctx) 576 } 577 578 // BootOptions returns the VirtualMachine's config.bootOptions property. 579 func (v VirtualMachine) BootOptions(ctx context.Context) (*types.VirtualMachineBootOptions, error) { 580 var o mo.VirtualMachine 581 582 err := v.Properties(ctx, v.Reference(), []string{"config.bootOptions"}, &o) 583 if err != nil { 584 return nil, err 585 } 586 587 return o.Config.BootOptions, nil 588 } 589 590 // SetBootOptions reconfigures the VirtualMachine with the given options. 591 func (v VirtualMachine) SetBootOptions(ctx context.Context, options *types.VirtualMachineBootOptions) error { 592 spec := types.VirtualMachineConfigSpec{} 593 594 spec.BootOptions = options 595 596 task, err := v.Reconfigure(ctx, spec) 597 if err != nil { 598 return err 599 } 600 601 return task.Wait(ctx) 602 } 603 604 // Answer answers a pending question. 605 func (v VirtualMachine) Answer(ctx context.Context, id, answer string) error { 606 req := types.AnswerVM{ 607 This: v.Reference(), 608 QuestionId: id, 609 AnswerChoice: answer, 610 } 611 612 _, err := methods.AnswerVM(ctx, v.c, &req) 613 if err != nil { 614 return err 615 } 616 617 return nil 618 } 619 620 func (v VirtualMachine) AcquireTicket(ctx context.Context, kind string) (*types.VirtualMachineTicket, error) { 621 req := types.AcquireTicket{ 622 This: v.Reference(), 623 TicketType: kind, 624 } 625 626 res, err := methods.AcquireTicket(ctx, v.c, &req) 627 if err != nil { 628 return nil, err 629 } 630 631 return &res.Returnval, nil 632 } 633 634 // CreateSnapshot creates a new snapshot of a virtual machine. 635 func (v VirtualMachine) CreateSnapshot(ctx context.Context, name string, description string, memory bool, quiesce bool) (*Task, error) { 636 req := types.CreateSnapshot_Task{ 637 This: v.Reference(), 638 Name: name, 639 Description: description, 640 Memory: memory, 641 Quiesce: quiesce, 642 } 643 644 res, err := methods.CreateSnapshot_Task(ctx, v.c, &req) 645 if err != nil { 646 return nil, err 647 } 648 649 return NewTask(v.c, res.Returnval), nil 650 } 651 652 // RemoveAllSnapshot removes all snapshots of a virtual machine 653 func (v VirtualMachine) RemoveAllSnapshot(ctx context.Context, consolidate *bool) (*Task, error) { 654 req := types.RemoveAllSnapshots_Task{ 655 This: v.Reference(), 656 Consolidate: consolidate, 657 } 658 659 res, err := methods.RemoveAllSnapshots_Task(ctx, v.c, &req) 660 if err != nil { 661 return nil, err 662 } 663 664 return NewTask(v.c, res.Returnval), nil 665 } 666 667 type snapshotMap map[string][]types.ManagedObjectReference 668 669 func (m snapshotMap) add(parent string, tree []types.VirtualMachineSnapshotTree) { 670 for i, st := range tree { 671 sname := st.Name 672 names := []string{sname, st.Snapshot.Value} 673 674 if parent != "" { 675 sname = path.Join(parent, sname) 676 // Add full path as an option to resolve duplicate names 677 names = append(names, sname) 678 } 679 680 for _, name := range names { 681 m[name] = append(m[name], tree[i].Snapshot) 682 } 683 684 m.add(sname, st.ChildSnapshotList) 685 } 686 } 687 688 // SnapshotSize calculates the size of a given snapshot in bytes. If the 689 // snapshot is current, disk files not associated with any parent snapshot are 690 // included in size calculations. This allows for measuring and including the 691 // growth from the last fixed snapshot to the present state. 692 func SnapshotSize(info types.ManagedObjectReference, parent *types.ManagedObjectReference, vmlayout *types.VirtualMachineFileLayoutEx, isCurrent bool) int { 693 var fileKeyList []int 694 var parentFiles []int 695 var allSnapshotFiles []int 696 697 diskFiles := extractDiskLayoutFiles(vmlayout.Disk) 698 699 for _, layout := range vmlayout.Snapshot { 700 diskLayout := extractDiskLayoutFiles(layout.Disk) 701 allSnapshotFiles = append(allSnapshotFiles, diskLayout...) 702 703 if layout.Key.Value == info.Value { 704 fileKeyList = append(fileKeyList, int(layout.DataKey)) // The .vmsn file 705 fileKeyList = append(fileKeyList, diskLayout...) // The .vmdk files 706 } else if parent != nil && layout.Key.Value == parent.Value { 707 parentFiles = append(parentFiles, diskLayout...) 708 } 709 } 710 711 for _, parentFile := range parentFiles { 712 removeKey(&fileKeyList, parentFile) 713 } 714 715 for _, file := range allSnapshotFiles { 716 removeKey(&diskFiles, file) 717 } 718 719 fileKeyMap := make(map[int]types.VirtualMachineFileLayoutExFileInfo) 720 for _, file := range vmlayout.File { 721 fileKeyMap[int(file.Key)] = file 722 } 723 724 size := 0 725 726 for _, fileKey := range fileKeyList { 727 file := fileKeyMap[fileKey] 728 if parent != nil || 729 (file.Type != string(types.VirtualMachineFileLayoutExFileTypeDiskDescriptor) && 730 file.Type != string(types.VirtualMachineFileLayoutExFileTypeDiskExtent)) { 731 size += int(file.Size) 732 } 733 } 734 735 if isCurrent { 736 for _, diskFile := range diskFiles { 737 file := fileKeyMap[diskFile] 738 size += int(file.Size) 739 } 740 } 741 742 return size 743 } 744 745 // FindSnapshot supports snapshot lookup by name, where name can be: 746 // 1) snapshot ManagedObjectReference.Value (unique) 747 // 2) snapshot name (may not be unique) 748 // 3) snapshot tree path (may not be unique) 749 func (v VirtualMachine) FindSnapshot(ctx context.Context, name string) (*types.ManagedObjectReference, error) { 750 var o mo.VirtualMachine 751 752 err := v.Properties(ctx, v.Reference(), []string{"snapshot"}, &o) 753 if err != nil { 754 return nil, err 755 } 756 757 if o.Snapshot == nil || len(o.Snapshot.RootSnapshotList) == 0 { 758 return nil, errors.New("no snapshots for this VM") 759 } 760 761 m := make(snapshotMap) 762 m.add("", o.Snapshot.RootSnapshotList) 763 764 s := m[name] 765 switch len(s) { 766 case 0: 767 return nil, fmt.Errorf("snapshot %q not found", name) 768 case 1: 769 return &s[0], nil 770 default: 771 return nil, fmt.Errorf("%q resolves to %d snapshots", name, len(s)) 772 } 773 } 774 775 // RemoveSnapshot removes a named snapshot 776 func (v VirtualMachine) RemoveSnapshot(ctx context.Context, name string, removeChildren bool, consolidate *bool) (*Task, error) { 777 snapshot, err := v.FindSnapshot(ctx, name) 778 if err != nil { 779 return nil, err 780 } 781 782 req := types.RemoveSnapshot_Task{ 783 This: snapshot.Reference(), 784 RemoveChildren: removeChildren, 785 Consolidate: consolidate, 786 } 787 788 res, err := methods.RemoveSnapshot_Task(ctx, v.c, &req) 789 if err != nil { 790 return nil, err 791 } 792 793 return NewTask(v.c, res.Returnval), nil 794 } 795 796 // RevertToCurrentSnapshot reverts to the current snapshot 797 func (v VirtualMachine) RevertToCurrentSnapshot(ctx context.Context, suppressPowerOn bool) (*Task, error) { 798 req := types.RevertToCurrentSnapshot_Task{ 799 This: v.Reference(), 800 SuppressPowerOn: types.NewBool(suppressPowerOn), 801 } 802 803 res, err := methods.RevertToCurrentSnapshot_Task(ctx, v.c, &req) 804 if err != nil { 805 return nil, err 806 } 807 808 return NewTask(v.c, res.Returnval), nil 809 } 810 811 // RevertToSnapshot reverts to a named snapshot 812 func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppressPowerOn bool) (*Task, error) { 813 snapshot, err := v.FindSnapshot(ctx, name) 814 if err != nil { 815 return nil, err 816 } 817 818 req := types.RevertToSnapshot_Task{ 819 This: snapshot.Reference(), 820 SuppressPowerOn: types.NewBool(suppressPowerOn), 821 } 822 823 res, err := methods.RevertToSnapshot_Task(ctx, v.c, &req) 824 if err != nil { 825 return nil, err 826 } 827 828 return NewTask(v.c, res.Returnval), nil 829 } 830 831 // IsToolsRunning returns true if VMware Tools is currently running in the guest OS, and false otherwise. 832 func (v VirtualMachine) IsToolsRunning(ctx context.Context) (bool, error) { 833 var o mo.VirtualMachine 834 835 err := v.Properties(ctx, v.Reference(), []string{"guest.toolsRunningStatus"}, &o) 836 if err != nil { 837 return false, err 838 } 839 840 return o.Guest.ToolsRunningStatus == string(types.VirtualMachineToolsRunningStatusGuestToolsRunning), nil 841 } 842 843 // Wait for the VirtualMachine to change to the desired power state. 844 func (v VirtualMachine) WaitForPowerState(ctx context.Context, state types.VirtualMachinePowerState) error { 845 p := property.DefaultCollector(v.c) 846 err := property.Wait(ctx, p, v.Reference(), []string{PropRuntimePowerState}, func(pc []types.PropertyChange) bool { 847 for _, c := range pc { 848 if c.Name != PropRuntimePowerState { 849 continue 850 } 851 if c.Val == nil { 852 continue 853 } 854 855 ps := c.Val.(types.VirtualMachinePowerState) 856 if ps == state { 857 return true 858 } 859 } 860 return false 861 }) 862 863 return err 864 } 865 866 func (v VirtualMachine) MarkAsTemplate(ctx context.Context) error { 867 req := types.MarkAsTemplate{ 868 This: v.Reference(), 869 } 870 871 _, err := methods.MarkAsTemplate(ctx, v.c, &req) 872 if err != nil { 873 return err 874 } 875 876 return nil 877 } 878 879 func (v VirtualMachine) MarkAsVirtualMachine(ctx context.Context, pool ResourcePool, host *HostSystem) error { 880 req := types.MarkAsVirtualMachine{ 881 This: v.Reference(), 882 Pool: pool.Reference(), 883 } 884 885 if host != nil { 886 ref := host.Reference() 887 req.Host = &ref 888 } 889 890 _, err := methods.MarkAsVirtualMachine(ctx, v.c, &req) 891 if err != nil { 892 return err 893 } 894 895 return nil 896 } 897 898 func (v VirtualMachine) Migrate(ctx context.Context, pool *ResourcePool, host *HostSystem, priority types.VirtualMachineMovePriority, state types.VirtualMachinePowerState) (*Task, error) { 899 req := types.MigrateVM_Task{ 900 This: v.Reference(), 901 Priority: priority, 902 State: state, 903 } 904 905 if pool != nil { 906 ref := pool.Reference() 907 req.Pool = &ref 908 } 909 910 if host != nil { 911 ref := host.Reference() 912 req.Host = &ref 913 } 914 915 res, err := methods.MigrateVM_Task(ctx, v.c, &req) 916 if err != nil { 917 return nil, err 918 } 919 920 return NewTask(v.c, res.Returnval), nil 921 } 922 923 func (v VirtualMachine) Unregister(ctx context.Context) error { 924 req := types.UnregisterVM{ 925 This: v.Reference(), 926 } 927 928 _, err := methods.UnregisterVM(ctx, v.Client(), &req) 929 return err 930 } 931 932 func (v VirtualMachine) MountToolsInstaller(ctx context.Context) error { 933 req := types.MountToolsInstaller{ 934 This: v.Reference(), 935 } 936 937 _, err := methods.MountToolsInstaller(ctx, v.Client(), &req) 938 return err 939 } 940 941 func (v VirtualMachine) UnmountToolsInstaller(ctx context.Context) error { 942 req := types.UnmountToolsInstaller{ 943 This: v.Reference(), 944 } 945 946 _, err := methods.UnmountToolsInstaller(ctx, v.Client(), &req) 947 return err 948 } 949 950 func (v VirtualMachine) UpgradeTools(ctx context.Context, options string) (*Task, error) { 951 req := types.UpgradeTools_Task{ 952 This: v.Reference(), 953 InstallerOptions: options, 954 } 955 956 res, err := methods.UpgradeTools_Task(ctx, v.Client(), &req) 957 if err != nil { 958 return nil, err 959 } 960 961 return NewTask(v.c, res.Returnval), nil 962 } 963 964 func (v VirtualMachine) Export(ctx context.Context) (*nfc.Lease, error) { 965 req := types.ExportVm{ 966 This: v.Reference(), 967 } 968 969 res, err := methods.ExportVm(ctx, v.Client(), &req) 970 if err != nil { 971 return nil, err 972 } 973 974 return nfc.NewLease(v.c, res.Returnval), nil 975 } 976 977 func (v VirtualMachine) UpgradeVM(ctx context.Context, version string) (*Task, error) { 978 req := types.UpgradeVM_Task{ 979 This: v.Reference(), 980 Version: version, 981 } 982 983 res, err := methods.UpgradeVM_Task(ctx, v.Client(), &req) 984 if err != nil { 985 return nil, err 986 } 987 988 return NewTask(v.c, res.Returnval), nil 989 } 990 991 // UUID is a helper to get the UUID of the VirtualMachine managed object. 992 // This method returns an empty string if an error occurs when retrieving UUID from the VirtualMachine object. 993 func (v VirtualMachine) UUID(ctx context.Context) string { 994 var o mo.VirtualMachine 995 996 err := v.Properties(ctx, v.Reference(), []string{"config.uuid"}, &o) 997 if err != nil { 998 return "" 999 } 1000 if o.Config != nil { 1001 return o.Config.Uuid 1002 } 1003 return "" 1004 } 1005 1006 func (v VirtualMachine) QueryChangedDiskAreas(ctx context.Context, baseSnapshot, curSnapshot *types.ManagedObjectReference, disk *types.VirtualDisk, offset int64) (types.DiskChangeInfo, error) { 1007 var noChange types.DiskChangeInfo 1008 var err error 1009 1010 if offset > disk.CapacityInBytes { 1011 return noChange, fmt.Errorf("offset is greater than the disk size (%#x and %#x)", offset, disk.CapacityInBytes) 1012 } else if offset == disk.CapacityInBytes { 1013 return types.DiskChangeInfo{StartOffset: offset, Length: 0}, nil 1014 } 1015 1016 var b mo.VirtualMachineSnapshot 1017 err = v.Properties(ctx, baseSnapshot.Reference(), []string{"config.hardware"}, &b) 1018 if err != nil { 1019 return noChange, fmt.Errorf("failed to fetch config.hardware of snapshot %s: %s", baseSnapshot, err) 1020 } 1021 1022 var changeId *string 1023 for _, vd := range b.Config.Hardware.Device { 1024 d := vd.GetVirtualDevice() 1025 if d.Key != disk.Key { 1026 continue 1027 } 1028 1029 // As per VDDK programming guide, these are the four types of disks 1030 // that support CBT, see "Gathering Changed Block Information". 1031 if b, ok := d.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok { 1032 changeId = &b.ChangeId 1033 break 1034 } 1035 if b, ok := d.Backing.(*types.VirtualDiskSparseVer2BackingInfo); ok { 1036 changeId = &b.ChangeId 1037 break 1038 } 1039 if b, ok := d.Backing.(*types.VirtualDiskRawDiskMappingVer1BackingInfo); ok { 1040 changeId = &b.ChangeId 1041 break 1042 } 1043 if b, ok := d.Backing.(*types.VirtualDiskRawDiskVer2BackingInfo); ok { 1044 changeId = &b.ChangeId 1045 break 1046 } 1047 1048 return noChange, fmt.Errorf("disk %d has backing info without .ChangeId: %t", disk.Key, d.Backing) 1049 } 1050 if changeId == nil || *changeId == "" { 1051 return noChange, fmt.Errorf("CBT is not enabled on disk %d", disk.Key) 1052 } 1053 1054 req := types.QueryChangedDiskAreas{ 1055 This: v.Reference(), 1056 Snapshot: curSnapshot, 1057 DeviceKey: disk.Key, 1058 StartOffset: offset, 1059 ChangeId: *changeId, 1060 } 1061 1062 res, err := methods.QueryChangedDiskAreas(ctx, v.Client(), &req) 1063 if err != nil { 1064 return noChange, err 1065 } 1066 1067 return res.Returnval, nil 1068 } 1069 1070 // ExportSnapshot exports all VMDK-files up to (but not including) a specified snapshot. This 1071 // is useful when exporting a running VM. 1072 func (v *VirtualMachine) ExportSnapshot(ctx context.Context, snapshot *types.ManagedObjectReference) (*nfc.Lease, error) { 1073 req := types.ExportSnapshot{ 1074 This: *snapshot, 1075 } 1076 resp, err := methods.ExportSnapshot(ctx, v.Client(), &req) 1077 if err != nil { 1078 return nil, err 1079 } 1080 return nfc.NewLease(v.c, resp.Returnval), nil 1081 }