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