github.com/vmware/govmomi@v0.51.0/simulator/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 simulator 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "fmt" 11 "log" 12 "net" 13 "os" 14 "path" 15 "path/filepath" 16 "reflect" 17 "slices" 18 "strconv" 19 "strings" 20 "sync/atomic" 21 "time" 22 23 "github.com/google/uuid" 24 25 "github.com/vmware/govmomi/internal" 26 "github.com/vmware/govmomi/object" 27 "github.com/vmware/govmomi/simulator/esx" 28 "github.com/vmware/govmomi/units" 29 "github.com/vmware/govmomi/vim25/methods" 30 "github.com/vmware/govmomi/vim25/mo" 31 "github.com/vmware/govmomi/vim25/soap" 32 "github.com/vmware/govmomi/vim25/types" 33 ) 34 35 type VirtualMachine struct { 36 mo.VirtualMachine 37 DataSets map[string]*DataSet 38 39 log string 40 sid int32 41 svm *simVM 42 uid uuid.UUID 43 imc *types.CustomizationSpec 44 } 45 46 func asVirtualMachineMO(obj mo.Reference) (*mo.VirtualMachine, bool) { 47 vm, ok := getManagedObject(obj).Addr().Interface().(*mo.VirtualMachine) 48 return vm, ok 49 } 50 51 func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) { 52 vm := &VirtualMachine{} 53 vm.Parent = &parent 54 ctx.Map.reference(vm) 55 56 folder := ctx.Map.Get(parent) 57 58 if spec.Name == "" { 59 return vm, &types.InvalidVmConfig{Property: "configSpec.name"} 60 } 61 62 if spec.Files == nil || spec.Files.VmPathName == "" { 63 return vm, &types.InvalidVmConfig{Property: "configSpec.files.vmPathName"} 64 } 65 66 rspec := types.DefaultResourceConfigSpec() 67 vm.Guest = &types.GuestInfo{} 68 vm.Config = &types.VirtualMachineConfigInfo{ 69 ExtraConfig: []types.BaseOptionValue{&types.OptionValue{Key: "govcsim", Value: "TRUE"}}, 70 Tools: &types.ToolsConfigInfo{}, 71 MemoryAllocation: &rspec.MemoryAllocation, 72 CpuAllocation: &rspec.CpuAllocation, 73 LatencySensitivity: &types.LatencySensitivity{Level: types.LatencySensitivitySensitivityLevelNormal}, 74 BootOptions: &types.VirtualMachineBootOptions{}, 75 CreateDate: types.NewTime(time.Now()), 76 } 77 vm.Layout = &types.VirtualMachineFileLayout{} 78 vm.LayoutEx = &types.VirtualMachineFileLayoutEx{ 79 Timestamp: time.Now(), 80 } 81 vm.Snapshot = nil // intentionally set to nil until a snapshot is created 82 vm.Storage = &types.VirtualMachineStorageInfo{ 83 Timestamp: time.Now(), 84 } 85 vm.Summary.Guest = &types.VirtualMachineGuestSummary{} 86 vm.Summary.Vm = &vm.Self 87 vm.Summary.Storage = &types.VirtualMachineStorageSummary{ 88 Timestamp: time.Now(), 89 } 90 91 vmx := vm.vmx(spec) 92 if vmx.Path == "" { 93 // Append VM Name as the directory name if not specified 94 vmx.Path = spec.Name 95 } 96 97 dc := ctx.Map.getEntityDatacenter(folder.(mo.Entity)) 98 ds := ctx.Map.FindByName(vmx.Datastore, dc.Datastore).(*Datastore) 99 dir := ds.resolve(ctx, vmx.Path) 100 101 if path.Ext(vmx.Path) == ".vmx" { 102 dir = path.Dir(dir) 103 // Ignore error here, deferring to createFile 104 _ = os.Mkdir(dir, 0700) 105 } else { 106 // Create VM directory, renaming if already exists 107 name := dir 108 109 for i := 0; i < 1024; /* just in case */ i++ { 110 err := os.Mkdir(name, 0700) 111 if err != nil { 112 if os.IsExist(err) { 113 name = fmt.Sprintf("%s (%d)", dir, i) 114 continue 115 } 116 return nil, &types.FileFault{File: name} 117 } 118 break 119 } 120 vmx.Path = path.Join(path.Base(name), spec.Name+".vmx") 121 } 122 123 spec.Files.VmPathName = vmx.String() 124 125 dsPath := path.Dir(spec.Files.VmPathName) 126 vm.uid = internal.OID(spec.Files.VmPathName) 127 128 defaults := types.VirtualMachineConfigSpec{ 129 NumCPUs: 1, 130 NumCoresPerSocket: 1, 131 MemoryMB: 32, 132 Uuid: vm.uid.String(), 133 InstanceUuid: newUUID(strings.ToUpper(spec.Files.VmPathName)), 134 Version: esx.HardwareVersion, 135 Firmware: string(types.GuestOsDescriptorFirmwareTypeBios), 136 VAppConfig: spec.VAppConfig, 137 Files: &types.VirtualMachineFileInfo{ 138 SnapshotDirectory: dsPath, 139 SuspendDirectory: dsPath, 140 LogDirectory: dsPath, 141 }, 142 } 143 144 // Add the default devices 145 defaults.DeviceChange, _ = object.VirtualDeviceList(esx.VirtualDevice).ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) 146 147 err := vm.configure(ctx, &defaults) 148 if err != nil { 149 return vm, err 150 } 151 152 vm.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff 153 vm.Runtime.ConnectionState = types.VirtualMachineConnectionStateConnected 154 vm.Summary.Runtime = vm.Runtime 155 156 vm.Capability.ChangeTrackingSupported = types.NewBool(changeTrackingSupported(spec)) 157 158 vm.Summary.QuickStats.GuestHeartbeatStatus = types.ManagedEntityStatusGray 159 vm.Summary.OverallStatus = types.ManagedEntityStatusGreen 160 vm.ConfigStatus = types.ManagedEntityStatusGreen 161 vm.DataSets = make(map[string]*DataSet) 162 163 // put vm in the folder only if no errors occurred 164 f, _ := asFolderMO(folder) 165 folderPutChild(ctx, f, vm) 166 167 return vm, nil 168 } 169 170 func (o *VirtualMachine) RenameTask(ctx *Context, r *types.Rename_Task) soap.HasFault { 171 return RenameTask(ctx, o, r) 172 } 173 174 func (*VirtualMachine) Reload(*types.Reload) soap.HasFault { 175 return &methods.ReloadBody{Res: new(types.ReloadResponse)} 176 } 177 178 func (vm *VirtualMachine) event(ctx *Context) types.VmEvent { 179 host := ctx.Map.Get(*vm.Runtime.Host).(*HostSystem) 180 181 return types.VmEvent{ 182 Event: types.Event{ 183 Datacenter: datacenterEventArgument(ctx, host), 184 ComputeResource: host.eventArgumentParent(ctx), 185 Host: host.eventArgument(), 186 Ds: ctx.Map.Get(vm.Datastore[0]).(*Datastore).eventArgument(), 187 Vm: &types.VmEventArgument{ 188 EntityEventArgument: types.EntityEventArgument{Name: vm.Name}, 189 Vm: vm.Self, 190 }, 191 }, 192 } 193 } 194 195 func (vm *VirtualMachine) hostInMM(ctx *Context) bool { 196 return ctx.Map.Get(*vm.Runtime.Host).(*HostSystem).Runtime.InMaintenanceMode 197 } 198 199 func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) { 200 if spec.Files == nil { 201 spec.Files = new(types.VirtualMachineFileInfo) 202 } 203 204 apply := []struct { 205 src string 206 dst *string 207 }{ 208 {spec.AlternateGuestName, &vm.Config.AlternateGuestName}, 209 {spec.Annotation, &vm.Config.Annotation}, 210 {spec.Firmware, &vm.Config.Firmware}, 211 {spec.InstanceUuid, &vm.Config.InstanceUuid}, 212 {spec.LocationId, &vm.Config.LocationId}, 213 {spec.NpivWorldWideNameType, &vm.Config.NpivWorldWideNameType}, 214 {spec.Name, &vm.Name}, 215 {spec.Name, &vm.Config.Name}, 216 {spec.Name, &vm.Summary.Config.Name}, 217 {spec.GuestId, &vm.Config.GuestId}, 218 {spec.GuestId, &vm.Config.GuestFullName}, 219 {spec.GuestId, &vm.Summary.Guest.GuestId}, 220 {spec.GuestId, &vm.Summary.Config.GuestId}, 221 {spec.GuestId, &vm.Summary.Config.GuestFullName}, 222 {spec.Uuid, &vm.Config.Uuid}, 223 {spec.Uuid, &vm.Summary.Config.Uuid}, 224 {spec.InstanceUuid, &vm.Config.InstanceUuid}, 225 {spec.InstanceUuid, &vm.Summary.Config.InstanceUuid}, 226 {spec.Version, &vm.Config.Version}, 227 {spec.Version, &vm.Summary.Config.HwVersion}, 228 {spec.Files.VmPathName, &vm.Config.Files.VmPathName}, 229 {spec.Files.VmPathName, &vm.Summary.Config.VmPathName}, 230 {spec.Files.SnapshotDirectory, &vm.Config.Files.SnapshotDirectory}, 231 {spec.Files.SuspendDirectory, &vm.Config.Files.SuspendDirectory}, 232 {spec.Files.LogDirectory, &vm.Config.Files.LogDirectory}, 233 {spec.FtEncryptionMode, &vm.Config.FtEncryptionMode}, 234 {spec.MigrateEncryption, &vm.Config.MigrateEncryption}, 235 } 236 237 for _, f := range apply { 238 if f.src != "" { 239 *f.dst = f.src 240 } 241 } 242 243 applyb := []struct { 244 src *bool 245 dst **bool 246 }{ 247 {spec.NestedHVEnabled, &vm.Config.NestedHVEnabled}, 248 {spec.CpuHotAddEnabled, &vm.Config.CpuHotAddEnabled}, 249 {spec.CpuHotRemoveEnabled, &vm.Config.CpuHotRemoveEnabled}, 250 {spec.GuestAutoLockEnabled, &vm.Config.GuestAutoLockEnabled}, 251 {spec.MemoryHotAddEnabled, &vm.Config.MemoryHotAddEnabled}, 252 {spec.MemoryReservationLockedToMax, &vm.Config.MemoryReservationLockedToMax}, 253 {spec.MessageBusTunnelEnabled, &vm.Config.MessageBusTunnelEnabled}, 254 {spec.NpivTemporaryDisabled, &vm.Config.NpivTemporaryDisabled}, 255 {spec.NpivOnNonRdmDisks, &vm.Config.NpivOnNonRdmDisks}, 256 {spec.ChangeTrackingEnabled, &vm.Config.ChangeTrackingEnabled}, 257 } 258 259 for _, f := range applyb { 260 if f.src != nil { 261 *f.dst = f.src 262 } 263 } 264 265 if spec.Flags != nil { 266 vm.Config.Flags = *spec.Flags 267 } 268 269 if spec.LatencySensitivity != nil { 270 vm.Config.LatencySensitivity = spec.LatencySensitivity 271 } 272 273 if spec.ManagedBy != nil { 274 if spec.ManagedBy.ExtensionKey == "" { 275 spec.ManagedBy = nil 276 } 277 vm.Config.ManagedBy = spec.ManagedBy 278 vm.Summary.Config.ManagedBy = spec.ManagedBy 279 } 280 281 if spec.BootOptions != nil { 282 vm.Config.BootOptions = spec.BootOptions 283 } 284 285 if spec.RepConfig != nil { 286 vm.Config.RepConfig = spec.RepConfig 287 } 288 289 if spec.Tools != nil { 290 vm.Config.Tools = spec.Tools 291 } 292 293 if spec.ConsolePreferences != nil { 294 vm.Config.ConsolePreferences = spec.ConsolePreferences 295 } 296 297 if spec.CpuAffinity != nil { 298 vm.Config.CpuAffinity = spec.CpuAffinity 299 } 300 301 if spec.CpuAllocation != nil { 302 vm.Config.CpuAllocation = spec.CpuAllocation 303 } 304 305 if spec.MemoryAffinity != nil { 306 vm.Config.MemoryAffinity = spec.MemoryAffinity 307 } 308 309 if spec.MemoryAllocation != nil { 310 vm.Config.MemoryAllocation = spec.MemoryAllocation 311 } 312 313 if spec.LatencySensitivity != nil { 314 vm.Config.LatencySensitivity = spec.LatencySensitivity 315 } 316 317 if spec.MemoryMB != 0 { 318 vm.Config.Hardware.MemoryMB = int32(spec.MemoryMB) 319 vm.Summary.Config.MemorySizeMB = vm.Config.Hardware.MemoryMB 320 } 321 322 if spec.NumCPUs != 0 { 323 vm.Config.Hardware.NumCPU = spec.NumCPUs 324 vm.Summary.Config.NumCpu = vm.Config.Hardware.NumCPU 325 } 326 327 if spec.NumCoresPerSocket != 0 { 328 vm.Config.Hardware.NumCoresPerSocket = spec.NumCoresPerSocket 329 } 330 331 if spec.GuestId != "" { 332 vm.Guest.GuestFamily = guestFamily(spec.GuestId) 333 } 334 335 vm.Config.Modified = time.Now() 336 } 337 338 // updateVAppProperty updates the simulator VM with the specified VApp properties. 339 func (vm *VirtualMachine) updateVAppProperty(spec *types.VmConfigSpec) types.BaseMethodFault { 340 if vm.Config.VAppConfig == nil { 341 vm.Config.VAppConfig = &types.VmConfigInfo{} 342 } 343 344 info := vm.Config.VAppConfig.GetVmConfigInfo() 345 propertyInfo := info.Property 346 productInfo := info.Product 347 348 for _, prop := range spec.Property { 349 var foundIndex int 350 exists := false 351 // Check if the specified property exists or not. This helps rejecting invalid 352 // operations (e.g., Adding a VApp property that already exists) 353 for i, p := range propertyInfo { 354 if p.Key == prop.Info.Key { 355 exists = true 356 foundIndex = i 357 break 358 } 359 } 360 361 switch prop.Operation { 362 case types.ArrayUpdateOperationAdd: 363 if exists { 364 return new(types.InvalidArgument) 365 } 366 propertyInfo = append(propertyInfo, *prop.Info) 367 case types.ArrayUpdateOperationEdit: 368 if !exists { 369 return new(types.InvalidArgument) 370 } 371 propertyInfo[foundIndex] = *prop.Info 372 case types.ArrayUpdateOperationRemove: 373 if !exists { 374 return new(types.InvalidArgument) 375 } 376 propertyInfo = append(propertyInfo[:foundIndex], propertyInfo[foundIndex+1:]...) 377 } 378 } 379 380 for _, prod := range spec.Product { 381 var foundIndex int 382 exists := false 383 // Check if the specified product exists or not. This helps rejecting invalid 384 // operations (e.g., Adding a VApp product that already exists) 385 for i, p := range productInfo { 386 if p.Key == prod.Info.Key { 387 exists = true 388 foundIndex = i 389 break 390 } 391 } 392 393 switch prod.Operation { 394 case types.ArrayUpdateOperationAdd: 395 if exists { 396 return new(types.InvalidArgument) 397 } 398 productInfo = append(productInfo, *prod.Info) 399 case types.ArrayUpdateOperationEdit: 400 if !exists { 401 return new(types.InvalidArgument) 402 } 403 productInfo[foundIndex] = *prod.Info 404 case types.ArrayUpdateOperationRemove: 405 if !exists { 406 return new(types.InvalidArgument) 407 } 408 productInfo = append(productInfo[:foundIndex], productInfo[foundIndex+1:]...) 409 } 410 } 411 412 info.Product = productInfo 413 info.Property = propertyInfo 414 415 return nil 416 } 417 418 var extraConfigAlias = map[string]string{ 419 "ip0": "SET.guest.ipAddress", 420 } 421 422 func extraConfigKey(key string) string { 423 if k, ok := extraConfigAlias[key]; ok { 424 return k 425 } 426 return key 427 } 428 429 func (vm *VirtualMachine) applyExtraConfig(ctx *Context, spec *types.VirtualMachineConfigSpec) types.BaseMethodFault { 430 if len(spec.ExtraConfig) == 0 { 431 return nil 432 } 433 var removedContainerBacking bool 434 var changes []types.PropertyChange 435 field := mo.Field{Path: "config.extraConfig"} 436 437 for _, c := range spec.ExtraConfig { 438 val := c.GetOptionValue() 439 key := strings.TrimPrefix(extraConfigKey(val.Key), "SET.") 440 if key == val.Key { 441 field.Key = key 442 op := types.PropertyChangeOpAssign 443 keyIndex := -1 444 for i := range vm.Config.ExtraConfig { 445 bov := vm.Config.ExtraConfig[i] 446 if bov == nil { 447 continue 448 } 449 ov := bov.GetOptionValue() 450 if ov == nil { 451 continue 452 } 453 if ov.Key == key { 454 keyIndex = i 455 break 456 } 457 } 458 if keyIndex < 0 { 459 op = types.PropertyChangeOpAdd 460 vm.Config.ExtraConfig = append(vm.Config.ExtraConfig, c) 461 } else { 462 if s, ok := val.Value.(string); ok && s == "" { 463 op = types.PropertyChangeOpRemove 464 if key == ContainerBackingOptionKey { 465 removedContainerBacking = true 466 } 467 // Remove existing element 468 vm.Config.ExtraConfig = append( 469 vm.Config.ExtraConfig[:keyIndex], 470 vm.Config.ExtraConfig[keyIndex+1:]...) 471 val = nil 472 } else { 473 // Update existing element 474 vm.Config.ExtraConfig[keyIndex] = val 475 } 476 } 477 478 changes = append(changes, types.PropertyChange{Name: field.String(), Val: val, Op: op}) 479 continue 480 } 481 changes = append(changes, types.PropertyChange{Name: key, Val: val.Value}) 482 483 switch key { 484 case "guest.ipAddress": 485 if len(vm.Guest.Net) > 0 { 486 ip := val.Value.(string) 487 vm.Guest.Net[0].IpAddress = []string{ip} 488 changes = append(changes, 489 types.PropertyChange{Name: "summary." + key, Val: ip}, 490 types.PropertyChange{Name: "guest.net", Val: vm.Guest.Net}, 491 ) 492 } 493 case "guest.hostName": 494 changes = append(changes, 495 types.PropertyChange{Name: "summary." + key, Val: val.Value}, 496 ) 497 } 498 } 499 500 // create the container backing before we publish the updates so the simVM is available before handlers 501 // get triggered 502 var fault types.BaseMethodFault 503 if vm.svm == nil { 504 vm.svm = createSimulationVM(vm) 505 506 // check to see if the VM is already powered on - if so we need to retroactively hit that path here 507 if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn { 508 err := vm.svm.start(ctx) 509 if err != nil { 510 // don't attempt to undo the changes already made - just return an error 511 // we'll retry the svm.start operation on pause/restart calls 512 fault = &types.VAppConfigFault{ 513 VimFault: types.VimFault{ 514 MethodFault: types.MethodFault{ 515 FaultCause: &types.LocalizedMethodFault{ 516 Fault: &types.SystemErrorFault{Reason: err.Error()}, 517 LocalizedMessage: err.Error()}}}} 518 } 519 } 520 } else if removedContainerBacking { 521 err := vm.svm.remove(ctx) 522 if err == nil { 523 // remove link from container to VM so callbacks no longer reflect state 524 vm.svm.vm = nil 525 // nil container backing reference to return this to a pure in-mem simulated VM 526 vm.svm = nil 527 528 } else { 529 // don't attempt to undo the changes already made - just return an error 530 // we'll retry the svm.start operation on pause/restart calls 531 fault = &types.VAppConfigFault{ 532 VimFault: types.VimFault{ 533 MethodFault: types.MethodFault{ 534 FaultCause: &types.LocalizedMethodFault{ 535 Fault: &types.SystemErrorFault{Reason: err.Error()}, 536 LocalizedMessage: err.Error()}}}} 537 } 538 } 539 540 change := types.PropertyChange{Name: field.Path, Val: vm.Config.ExtraConfig} 541 ctx.Update(vm, append(changes, change)) 542 543 return fault 544 } 545 546 func validateGuestID(id string) types.BaseMethodFault { 547 for _, x := range GuestID { 548 if id == string(x) { 549 return nil 550 } 551 } 552 553 return &types.InvalidArgument{InvalidProperty: "configSpec.guestId"} 554 } 555 556 func (vm *VirtualMachine) configure(ctx *Context, spec *types.VirtualMachineConfigSpec) (result types.BaseMethodFault) { 557 defer func() { 558 if result == nil { 559 vm.updateLastModifiedAndChangeVersion(ctx) 560 } 561 }() 562 563 vm.apply(spec) 564 565 if spec.MemoryAllocation != nil { 566 if err := updateResourceAllocation("memory", spec.MemoryAllocation, vm.Config.MemoryAllocation); err != nil { 567 return err 568 } 569 } 570 571 if spec.CpuAllocation != nil { 572 if err := updateResourceAllocation("cpu", spec.CpuAllocation, vm.Config.CpuAllocation); err != nil { 573 return err 574 } 575 } 576 577 if spec.GuestId != "" { 578 if err := validateGuestID(spec.GuestId); err != nil { 579 return err 580 } 581 } 582 583 if o := spec.BootOptions; o != nil { 584 if isTrue(o.EfiSecureBootEnabled) && vm.Config.Firmware != string(types.GuestOsDescriptorFirmwareTypeEfi) { 585 return &types.InvalidVmConfig{Property: "msg.hostd.configSpec.efi"} 586 } 587 } 588 589 if spec.VAppConfig != nil { 590 if err := vm.updateVAppProperty(spec.VAppConfig.GetVmConfigSpec()); err != nil { 591 return err 592 } 593 } 594 595 if spec.Crypto != nil { 596 if err := vm.updateCrypto(ctx, spec.Crypto); err != nil { 597 return err 598 } 599 } 600 601 return vm.configureDevices(ctx, spec) 602 } 603 604 func getVMFileType(fileName string) types.VirtualMachineFileLayoutExFileType { 605 var fileType types.VirtualMachineFileLayoutExFileType 606 607 fileExt := path.Ext(fileName) 608 fileNameNoExt := strings.TrimSuffix(fileName, fileExt) 609 610 switch fileExt { 611 case ".vmx": 612 fileType = types.VirtualMachineFileLayoutExFileTypeConfig 613 case ".core": 614 fileType = types.VirtualMachineFileLayoutExFileTypeCore 615 case ".vmdk": 616 fileType = types.VirtualMachineFileLayoutExFileTypeDiskDescriptor 617 if strings.HasSuffix(fileNameNoExt, "-digest") { 618 fileType = types.VirtualMachineFileLayoutExFileTypeDigestDescriptor 619 } 620 621 extentSuffixes := []string{"-flat", "-delta", "-s", "-rdm", "-rdmp"} 622 for _, suffix := range extentSuffixes { 623 if strings.HasSuffix(fileNameNoExt, suffix) { 624 fileType = types.VirtualMachineFileLayoutExFileTypeDiskExtent 625 } else if strings.HasSuffix(fileNameNoExt, "-digest"+suffix) { 626 fileType = types.VirtualMachineFileLayoutExFileTypeDigestExtent 627 } 628 } 629 case ".psf": 630 fileType = types.VirtualMachineFileLayoutExFileTypeDiskReplicationState 631 case ".vmxf": 632 fileType = types.VirtualMachineFileLayoutExFileTypeExtendedConfig 633 case ".vmft": 634 fileType = types.VirtualMachineFileLayoutExFileTypeFtMetadata 635 case ".log": 636 fileType = types.VirtualMachineFileLayoutExFileTypeLog 637 case ".nvram": 638 fileType = types.VirtualMachineFileLayoutExFileTypeNvram 639 case ".png", ".bmp": 640 fileType = types.VirtualMachineFileLayoutExFileTypeScreenshot 641 case ".vmsn": 642 fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotData 643 case ".vmsd": 644 fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotList 645 case ".xml": 646 if strings.HasSuffix(fileNameNoExt, "-aux") { 647 fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotManifestList 648 } 649 case ".stat": 650 fileType = types.VirtualMachineFileLayoutExFileTypeStat 651 case ".vmss": 652 fileType = types.VirtualMachineFileLayoutExFileTypeSuspend 653 case ".vmem": 654 if strings.Contains(fileNameNoExt, "Snapshot") { 655 fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotMemory 656 } else { 657 fileType = types.VirtualMachineFileLayoutExFileTypeSuspendMemory 658 } 659 case ".vswp": 660 if strings.HasPrefix(fileNameNoExt, "vmx-") { 661 fileType = types.VirtualMachineFileLayoutExFileTypeUwswap 662 } else { 663 fileType = types.VirtualMachineFileLayoutExFileTypeSwap 664 } 665 case "": 666 if strings.HasPrefix(fileNameNoExt, "imcf-") { 667 fileType = types.VirtualMachineFileLayoutExFileTypeGuestCustomization 668 } 669 } 670 671 return fileType 672 } 673 674 func (vm *VirtualMachine) addFileLayoutEx(ctx *Context, datastorePath object.DatastorePath, fileSize int64) int32 { 675 var newKey int32 676 for _, layoutFile := range vm.LayoutEx.File { 677 if layoutFile.Name == datastorePath.String() { 678 return layoutFile.Key 679 } 680 681 if layoutFile.Key >= newKey { 682 newKey = layoutFile.Key + 1 683 } 684 } 685 686 fileType := getVMFileType(filepath.Base(datastorePath.Path)) 687 688 switch fileType { 689 case types.VirtualMachineFileLayoutExFileTypeNvram, types.VirtualMachineFileLayoutExFileTypeSnapshotList: 690 if !slices.Contains(vm.Layout.ConfigFile, datastorePath.Path) { 691 vm.Layout.ConfigFile = append(vm.Layout.ConfigFile, datastorePath.Path) 692 } 693 case types.VirtualMachineFileLayoutExFileTypeLog: 694 if !slices.Contains(vm.Layout.LogFile, datastorePath.Path) { 695 vm.Layout.LogFile = append(vm.Layout.LogFile, datastorePath.Path) 696 } 697 case types.VirtualMachineFileLayoutExFileTypeSwap: 698 vm.Layout.SwapFile = datastorePath.String() 699 } 700 701 vm.LayoutEx.File = append(vm.LayoutEx.File, types.VirtualMachineFileLayoutExFileInfo{ 702 Accessible: types.NewBool(true), 703 BackingObjectId: "", 704 Key: newKey, 705 Name: datastorePath.String(), 706 Size: fileSize, 707 Type: string(fileType), 708 UniqueSize: fileSize, 709 }) 710 711 vm.LayoutEx.Timestamp = time.Now() 712 713 vm.updateStorage(ctx) 714 715 return newKey 716 } 717 718 func (vm *VirtualMachine) addSnapshotLayout(ctx *Context, snapshot types.ManagedObjectReference, dataKey int32) { 719 for _, snapshotLayout := range vm.Layout.Snapshot { 720 if snapshotLayout.Key == snapshot { 721 return 722 } 723 } 724 725 var snapshotFiles []string 726 for _, file := range vm.LayoutEx.File { 727 if file.Key == dataKey || file.Type == "diskDescriptor" { 728 snapshotFiles = append(snapshotFiles, file.Name) 729 } 730 } 731 732 vm.Layout.Snapshot = append(vm.Layout.Snapshot, types.VirtualMachineFileLayoutSnapshotLayout{ 733 Key: snapshot, 734 SnapshotFile: snapshotFiles, 735 }) 736 } 737 738 func (vm *VirtualMachine) addSnapshotLayoutEx(ctx *Context, snapshot types.ManagedObjectReference, dataKey int32, memoryKey int32) { 739 for _, snapshotLayoutEx := range vm.LayoutEx.Snapshot { 740 if snapshotLayoutEx.Key == snapshot { 741 return 742 } 743 } 744 745 vm.LayoutEx.Snapshot = append(vm.LayoutEx.Snapshot, types.VirtualMachineFileLayoutExSnapshotLayout{ 746 DataKey: dataKey, 747 Disk: vm.LayoutEx.Disk, 748 Key: snapshot, 749 MemoryKey: memoryKey, 750 }) 751 752 vm.LayoutEx.Timestamp = time.Now() 753 754 vm.updateStorage(ctx) 755 } 756 757 // Updates both vm.Layout.Disk and vm.LayoutEx.Disk 758 func (vm *VirtualMachine) updateDiskLayouts(ctx *Context) types.BaseMethodFault { 759 var disksLayout []types.VirtualMachineFileLayoutDiskLayout 760 var disksLayoutEx []types.VirtualMachineFileLayoutExDiskLayout 761 762 disks := object.VirtualDeviceList(vm.Config.Hardware.Device).SelectByType((*types.VirtualDisk)(nil)) 763 for _, disk := range disks { 764 disk := disk.(*types.VirtualDisk) 765 diskBacking := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 766 767 diskLayout := &types.VirtualMachineFileLayoutDiskLayout{Key: disk.Key} 768 diskLayoutEx := &types.VirtualMachineFileLayoutExDiskLayout{Key: disk.Key} 769 770 // Iterate through disk and its parents 771 for { 772 dFileName := diskBacking.GetVirtualDeviceFileBackingInfo().FileName 773 774 var fileKeys []int32 775 776 // Add disk descriptor and extent files 777 for _, diskName := range vdmNames(dFileName) { 778 // get full path including datastore location 779 p, fault := parseDatastorePath(diskName) 780 if fault != nil { 781 return fault 782 } 783 784 datastore := vm.useDatastore(ctx, p.Datastore) 785 dFilePath := datastore.resolve(ctx, p.Path) 786 787 var fileSize int64 788 // If file can not be opened - fileSize will be 0 789 if dFileInfo, err := os.Stat(dFilePath); err == nil { 790 fileSize = dFileInfo.Size() 791 } 792 793 diskKey := vm.addFileLayoutEx(ctx, *p, fileSize) 794 fileKeys = append(fileKeys, diskKey) 795 } 796 797 diskLayout.DiskFile = append(diskLayout.DiskFile, dFileName) 798 diskLayoutEx.Chain = append(diskLayoutEx.Chain, types.VirtualMachineFileLayoutExDiskUnit{ 799 FileKey: fileKeys, 800 }) 801 802 if parent := diskBacking.Parent; parent != nil { 803 diskBacking = parent 804 } else { 805 break 806 } 807 } 808 809 disksLayout = append(disksLayout, *diskLayout) 810 disksLayoutEx = append(disksLayoutEx, *diskLayoutEx) 811 } 812 813 vm.Layout.Disk = disksLayout 814 815 vm.LayoutEx.Disk = disksLayoutEx 816 vm.LayoutEx.Timestamp = time.Now() 817 818 vm.updateStorage(ctx) 819 820 return nil 821 } 822 823 func (vm *VirtualMachine) updateStorage(ctx *Context) types.BaseMethodFault { 824 // Committed - sum of Size for each file in vm.LayoutEx.File 825 // Unshared - sum of Size for each disk (.vmdk) in vm.LayoutEx.File 826 // Uncommitted - disk capacity minus disk usage (only currently used disk) 827 var datastoresUsage []types.VirtualMachineUsageOnDatastore 828 829 disks := object.VirtualDeviceList(vm.Config.Hardware.Device).SelectByType((*types.VirtualDisk)(nil)) 830 831 for _, file := range vm.LayoutEx.File { 832 p, fault := parseDatastorePath(file.Name) 833 if fault != nil { 834 return fault 835 } 836 837 datastore := vm.useDatastore(ctx, p.Datastore) 838 dsUsage := &types.VirtualMachineUsageOnDatastore{ 839 Datastore: datastore.Self, 840 } 841 842 for idx, usage := range datastoresUsage { 843 if usage.Datastore == datastore.Self { 844 datastoresUsage = append(datastoresUsage[:idx], datastoresUsage[idx+1:]...) 845 dsUsage = &usage 846 break 847 } 848 } 849 850 dsUsage.Committed += file.Size 851 852 if path.Ext(file.Name) == ".vmdk" { 853 dsUsage.Unshared += file.Size 854 } 855 856 for _, disk := range disks { 857 disk := disk.(*types.VirtualDisk) 858 backing := disk.Backing.(types.BaseVirtualDeviceFileBackingInfo).GetVirtualDeviceFileBackingInfo() 859 860 if backing.FileName == file.Name { 861 dsUsage.Uncommitted += disk.CapacityInBytes 862 } 863 } 864 865 datastoresUsage = append(datastoresUsage, *dsUsage) 866 } 867 868 vm.Storage.PerDatastoreUsage = datastoresUsage 869 vm.Storage.Timestamp = time.Now() 870 871 storageSummary := &types.VirtualMachineStorageSummary{ 872 Timestamp: time.Now(), 873 } 874 875 for i, usage := range datastoresUsage { 876 datastoresUsage[i].Uncommitted -= usage.Committed 877 storageSummary.Committed += usage.Committed 878 storageSummary.Uncommitted += usage.Uncommitted 879 storageSummary.Unshared += usage.Unshared 880 } 881 882 vm.Summary.Storage = storageSummary 883 884 return nil 885 } 886 887 func (vm *VirtualMachine) RefreshStorageInfo(ctx *Context, req *types.RefreshStorageInfo) soap.HasFault { 888 body := new(methods.RefreshStorageInfoBody) 889 890 if vm.Runtime.Host == nil { 891 // VM not fully created 892 return body 893 } 894 895 // Validate that all files in vm.LayoutEx.File can still be found 896 for idx := len(vm.LayoutEx.File) - 1; idx >= 0; idx-- { 897 file := vm.LayoutEx.File[idx] 898 899 p, fault := parseDatastorePath(file.Name) 900 if fault != nil { 901 body.Fault_ = Fault("", fault) 902 return body 903 } 904 905 if _, err := os.Stat(p.String()); err != nil { 906 vm.LayoutEx.File = append(vm.LayoutEx.File[:idx], vm.LayoutEx.File[idx+1:]...) 907 } 908 } 909 910 vmPathName := vm.Config.Files.VmPathName 911 // vm.Config.Files.VmPathName can be a directory or full path to .vmx 912 if path.Ext(vmPathName) == ".vmx" { 913 vmPathName = path.Dir(vmPathName) 914 } 915 916 // Directories will be used to locate VM files. 917 // Does not include information about virtual disk file locations. 918 locations := []string{ 919 vmPathName, 920 vm.Config.Files.SnapshotDirectory, 921 vm.Config.Files.LogDirectory, 922 vm.Config.Files.SuspendDirectory, 923 vm.Config.Files.FtMetadataDirectory, 924 } 925 926 for _, directory := range slices.Compact(locations) { 927 if directory == "" { 928 continue 929 } 930 931 p, fault := parseDatastorePath(directory) 932 if fault != nil { 933 body.Fault_ = Fault("", fault) 934 return body 935 } 936 937 datastore := vm.useDatastore(ctx, p.Datastore) 938 directory := datastore.resolve(ctx, p.Path) 939 940 if _, err := os.Stat(directory); err != nil { 941 // Can not access the directory 942 continue 943 } 944 945 files, err := os.ReadDir(directory) 946 if err != nil { 947 body.Fault_ = Fault("", ctx.Map.FileManager().fault(directory, err, new(types.CannotAccessFile))) 948 return body 949 } 950 951 for _, file := range files { 952 datastorePath := object.DatastorePath{ 953 Datastore: p.Datastore, 954 Path: path.Join(p.Path, file.Name()), 955 } 956 info, _ := file.Info() 957 vm.addFileLayoutEx(ctx, datastorePath, info.Size()) 958 } 959 } 960 961 fault := vm.updateDiskLayouts(ctx) 962 if fault != nil { 963 body.Fault_ = Fault("", fault) 964 return body 965 } 966 967 vm.LayoutEx.Timestamp = time.Now() 968 969 body.Res = new(types.RefreshStorageInfoResponse) 970 971 return body 972 } 973 974 func (vm *VirtualMachine) findDatastore(ctx *Context, name string) *Datastore { 975 host := ctx.Map.Get(*vm.Runtime.Host).(*HostSystem) 976 977 return ctx.Map.FindByName(name, host.Datastore).(*Datastore) 978 } 979 980 func (vm *VirtualMachine) useDatastore(ctx *Context, name string) *Datastore { 981 ds := vm.findDatastore(ctx, name) 982 if FindReference(vm.Datastore, ds.Self) == nil { 983 vm.Datastore = append(vm.Datastore, ds.Self) 984 } 985 986 return ds 987 } 988 989 func (vm *VirtualMachine) vmx(spec *types.VirtualMachineConfigSpec) object.DatastorePath { 990 var p object.DatastorePath 991 vmx := vm.Config.Files.VmPathName 992 if spec != nil { 993 vmx = spec.Files.VmPathName 994 } 995 p.FromString(vmx) 996 return p 997 } 998 999 func (vm *VirtualMachine) createFile(ctx *Context, spec string, name string, register bool) (*os.File, types.BaseMethodFault) { 1000 p, fault := parseDatastorePath(spec) 1001 if fault != nil { 1002 return nil, fault 1003 } 1004 1005 ds := vm.useDatastore(ctx, p.Datastore) 1006 1007 nhost := len(ds.Host) 1008 if internal.IsDatastoreVSAN(ds.Datastore) && nhost < 3 { 1009 fault := new(types.CannotCreateFile) 1010 fault.FaultMessage = []types.LocalizableMessage{ 1011 { 1012 Key: "vob.vsanprovider.object.creation.failed", 1013 Message: "Failed to create object.", 1014 }, 1015 { 1016 Key: "vob.vsan.clomd.needMoreFaultDomains2", 1017 Message: fmt.Sprintf("There are currently %d usable fault domains. The operation requires %d more usable fault domains.", nhost, 3-nhost), 1018 }, 1019 } 1020 fault.File = p.Path 1021 return nil, fault 1022 } 1023 1024 file := ds.resolve(ctx, p.Path) 1025 1026 if name != "" { 1027 if path.Ext(p.Path) == ".vmx" { 1028 file = path.Dir(file) // vm.Config.Files.VmPathName can be a directory or full path to .vmx 1029 } 1030 1031 file = path.Join(file, name) 1032 } 1033 1034 if register { 1035 f, err := os.Open(filepath.Clean(file)) 1036 if err != nil { 1037 log.Printf("register %s: %s", vm.Reference(), err) 1038 if os.IsNotExist(err) { 1039 return nil, &types.NotFound{} 1040 } 1041 1042 return nil, &types.InvalidArgument{} 1043 } 1044 1045 return f, nil 1046 } 1047 1048 _, err := os.Stat(file) 1049 if err == nil { 1050 fault := &types.FileAlreadyExists{FileFault: types.FileFault{File: file}} 1051 log.Printf("%T: %s", fault, file) 1052 return nil, fault 1053 } 1054 1055 // Create parent directory if needed 1056 dir := path.Dir(file) 1057 _, err = os.Stat(dir) 1058 if err != nil { 1059 if os.IsNotExist(err) { 1060 _ = os.Mkdir(dir, 0700) 1061 } 1062 } 1063 1064 f, err := os.Create(file) 1065 if err != nil { 1066 log.Printf("create(%s): %s", file, err) 1067 return nil, &types.FileFault{ 1068 File: file, 1069 } 1070 } 1071 1072 return f, nil 1073 } 1074 1075 // Rather than keep an fd open for each VM, open/close the log for each messages. 1076 // This is ok for now as we do not do any heavy VM logging. 1077 func (vm *VirtualMachine) logPrintf(format string, v ...any) { 1078 f, err := os.OpenFile(vm.log, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0) 1079 if err != nil { 1080 log.Println(err) 1081 return 1082 } 1083 log.New(f, "vmx ", log.Flags()).Printf(format, v...) 1084 _ = f.Close() 1085 } 1086 1087 func (vm *VirtualMachine) create(ctx *Context, spec *types.VirtualMachineConfigSpec, register bool) types.BaseMethodFault { 1088 vm.apply(spec) 1089 1090 if spec.Version != "" { 1091 v := strings.TrimPrefix(spec.Version, "vmx-") 1092 _, err := strconv.Atoi(v) 1093 if err != nil { 1094 log.Printf("unsupported hardware version: %s", spec.Version) 1095 return new(types.NotSupported) 1096 } 1097 } 1098 1099 files := []struct { 1100 spec string 1101 name string 1102 use *string 1103 }{ 1104 {vm.Config.Files.VmPathName, "", nil}, 1105 {vm.Config.Files.VmPathName, fmt.Sprintf("%s.nvram", vm.Name), nil}, 1106 {vm.Config.Files.LogDirectory, "vmware.log", &vm.log}, 1107 } 1108 1109 for _, file := range files { 1110 f, err := vm.createFile(ctx, file.spec, file.name, register) 1111 if err != nil { 1112 return err 1113 } 1114 if file.use != nil { 1115 *file.use = f.Name() 1116 } 1117 _ = f.Close() 1118 } 1119 1120 vm.logPrintf("created") 1121 1122 return vm.configureDevices(ctx, spec) 1123 } 1124 1125 var vmwOUI = net.HardwareAddr([]byte{0x0, 0xc, 0x29}) 1126 1127 // From https://techdocs.broadcom.com/us/en/vmware-cis/vsphere/vsphere/8-0/vsphere-networking-8-0/mac-addresses/mac-address-generation-on-esxi-hosts.html 1128 // > The host generates generateMAC addresses that consists of the VMware OUI 00:0C:29 and the last three octets in hexadecimal 1129 // > format of the virtual machine UUID. The virtual machine UUID is based on a hash calculated by using the UUID of the 1130 // > ESXi physical machine and the path to the configuration file (.vmx) of the virtual machine. 1131 func (vm *VirtualMachine) generateMAC(unit int32) string { 1132 id := []byte(vm.Config.Uuid) 1133 1134 offset := len(id) - len(vmwOUI) 1135 key := id[offset] + byte(unit) // add device unit number, giving each VM NIC a unique MAC 1136 id = append([]byte{key}, id[offset+1:]...) 1137 1138 mac := append(vmwOUI, id...) 1139 1140 return mac.String() 1141 } 1142 1143 func numberToString(n int64, sep rune) string { 1144 buf := &bytes.Buffer{} 1145 if n < 0 { 1146 n = -n 1147 buf.WriteRune('-') 1148 } 1149 s := strconv.FormatInt(n, 10) 1150 pos := 3 - (len(s) % 3) 1151 for i := 0; i < len(s); i++ { 1152 if pos == 3 { 1153 if i != 0 { 1154 buf.WriteRune(sep) 1155 } 1156 pos = 0 1157 } 1158 pos++ 1159 buf.WriteByte(s[i]) 1160 } 1161 1162 return buf.String() 1163 } 1164 1165 func getDiskSize(disk *types.VirtualDisk) int64 { 1166 if disk.CapacityInBytes == 0 { 1167 return disk.CapacityInKB * 1024 1168 } 1169 return disk.CapacityInBytes 1170 } 1171 1172 func changedDiskSize(oldDisk *types.VirtualDisk, newDiskSpec *types.VirtualDisk) (int64, bool) { 1173 // capacity cannot be decreased 1174 if newDiskSpec.CapacityInBytes > 0 && newDiskSpec.CapacityInBytes < oldDisk.CapacityInBytes { 1175 return 0, false 1176 } 1177 if newDiskSpec.CapacityInKB > 0 && newDiskSpec.CapacityInKB < oldDisk.CapacityInKB { 1178 return 0, false 1179 } 1180 1181 // NOTE: capacity is ignored if specified value is same as before 1182 if newDiskSpec.CapacityInBytes == oldDisk.CapacityInBytes { 1183 return newDiskSpec.CapacityInKB * 1024, true 1184 } 1185 if newDiskSpec.CapacityInKB == oldDisk.CapacityInKB { 1186 return newDiskSpec.CapacityInBytes, true 1187 } 1188 1189 // if both set, CapacityInBytes and CapacityInKB must be the same 1190 if newDiskSpec.CapacityInBytes > 0 && newDiskSpec.CapacityInKB > 0 { 1191 if newDiskSpec.CapacityInBytes != newDiskSpec.CapacityInKB*1024 { 1192 return 0, false 1193 } 1194 } 1195 1196 return newDiskSpec.CapacityInBytes, true 1197 } 1198 1199 func (vm *VirtualMachine) validateSwitchMembers(ctx *Context, id string) types.BaseMethodFault { 1200 var dswitch *DistributedVirtualSwitch 1201 1202 var find func(types.ManagedObjectReference) 1203 find = func(child types.ManagedObjectReference) { 1204 s, ok := ctx.Map.Get(child).(*DistributedVirtualSwitch) 1205 if ok && s.Uuid == id { 1206 dswitch = s 1207 return 1208 } 1209 walk(ctx.Map.Get(child), find) 1210 } 1211 f := ctx.Map.getEntityDatacenter(vm).NetworkFolder 1212 walk(ctx.Map.Get(f), find) // search in NetworkFolder and any sub folders 1213 1214 if dswitch == nil { 1215 log.Printf("DVS %s cannot be found", id) 1216 return new(types.NotFound) 1217 } 1218 1219 h := ctx.Map.Get(*vm.Runtime.Host).(*HostSystem) 1220 c := hostParent(ctx, &h.HostSystem) 1221 isMember := func(val types.ManagedObjectReference) bool { 1222 for _, mem := range dswitch.Summary.HostMember { 1223 if mem == val { 1224 return true 1225 } 1226 } 1227 log.Printf("%s is not a member of VDS %s", h.Name, dswitch.Name) 1228 return false 1229 } 1230 1231 for _, ref := range c.Host { 1232 if !isMember(ref) { 1233 return &types.InvalidArgument{InvalidProperty: "spec.deviceChange.device.port.switchUuid"} 1234 } 1235 } 1236 1237 return nil 1238 } 1239 1240 func (vm *VirtualMachine) configureDevice( 1241 ctx *Context, 1242 devices object.VirtualDeviceList, 1243 spec *types.VirtualDeviceConfigSpec, 1244 oldDevice types.BaseVirtualDevice) types.BaseMethodFault { 1245 1246 device := spec.Device 1247 d := device.GetVirtualDevice() 1248 var controller types.BaseVirtualController 1249 1250 key := d.Key 1251 if d.Key <= 0 { 1252 // Keys can't be negative; Key 0 is reserved 1253 d.Key = devices.NewKey() 1254 d.Key *= -1 1255 } 1256 1257 // Update device controller's key reference 1258 if key != d.Key { 1259 if device := devices.FindByKey(d.ControllerKey); device != nil { 1260 if c, ok := device.(types.BaseVirtualController); ok { 1261 c := c.GetVirtualController() 1262 for i := range c.Device { 1263 if c.Device[i] == key { 1264 c.Device[i] = d.Key 1265 break 1266 } 1267 } 1268 } 1269 } 1270 } 1271 1272 // Choose a unique key 1273 for { 1274 if devices.FindByKey(d.Key) == nil { 1275 break 1276 } 1277 d.Key++ 1278 } 1279 1280 label := devices.Name(device) 1281 summary := label 1282 dc := ctx.Map.getEntityDatacenter(ctx.Map.Get(*vm.Parent).(mo.Entity)) 1283 1284 switch x := device.(type) { 1285 case types.BaseVirtualEthernetCard: 1286 controller = devices.PickController((*types.VirtualPCIController)(nil)) 1287 var net types.ManagedObjectReference 1288 var name string 1289 1290 switch b := d.Backing.(type) { 1291 case *types.VirtualEthernetCardNetworkBackingInfo: 1292 name = b.DeviceName 1293 summary = name 1294 net = ctx.Map.FindByName(b.DeviceName, dc.Network).Reference() 1295 b.Network = &net 1296 case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo: 1297 summary = fmt.Sprintf("DVSwitch: %s", b.Port.SwitchUuid) 1298 net.Type = "DistributedVirtualPortgroup" 1299 net.Value = b.Port.PortgroupKey 1300 if err := vm.validateSwitchMembers(ctx, b.Port.SwitchUuid); err != nil { 1301 return err 1302 } 1303 } 1304 1305 ctx.Update(vm, []types.PropertyChange{ 1306 {Name: "summary.config.numEthernetCards", Val: vm.Summary.Config.NumEthernetCards + 1}, 1307 {Name: "network", Val: append(vm.Network, net)}, 1308 }) 1309 1310 c := x.GetVirtualEthernetCard() 1311 if c.MacAddress == "" { 1312 if c.UnitNumber == nil { 1313 devices.AssignController(device, controller) 1314 } 1315 c.MacAddress = vm.generateMAC(*c.UnitNumber - 7) // Note 7 == PCI offset 1316 } 1317 1318 vm.Guest.Net = append(vm.Guest.Net, types.GuestNicInfo{ 1319 Network: name, 1320 IpAddress: nil, 1321 MacAddress: c.MacAddress, 1322 Connected: true, 1323 DeviceConfigId: c.Key, 1324 }) 1325 1326 if spec.Operation == types.VirtualDeviceConfigSpecOperationAdd { 1327 if c.ResourceAllocation == nil { 1328 c.ResourceAllocation = &types.VirtualEthernetCardResourceAllocation{ 1329 Reservation: types.NewInt64(0), 1330 Share: types.SharesInfo{ 1331 Shares: 50, 1332 Level: "normal", 1333 }, 1334 Limit: types.NewInt64(-1), 1335 } 1336 } 1337 } 1338 case *types.VirtualDisk: 1339 if oldDevice == nil { 1340 // NOTE: either of capacityInBytes and capacityInKB may not be specified 1341 x.CapacityInBytes = getDiskSize(x) 1342 x.CapacityInKB = getDiskSize(x) / 1024 1343 } else { 1344 if oldDisk, ok := oldDevice.(*types.VirtualDisk); ok { 1345 diskSize, ok := changedDiskSize(oldDisk, x) 1346 if !ok { 1347 return &types.InvalidDeviceOperation{} 1348 } 1349 x.CapacityInBytes = diskSize 1350 x.CapacityInKB = diskSize / 1024 1351 } 1352 } 1353 1354 switch b := d.Backing.(type) { 1355 case *types.VirtualDiskSparseVer2BackingInfo: 1356 // Sparse disk creation not supported in ESX 1357 return &types.DeviceUnsupportedForVmPlatform{ 1358 InvalidDeviceSpec: types.InvalidDeviceSpec{ 1359 InvalidVmConfig: types.InvalidVmConfig{Property: "VirtualDeviceSpec.device.backing"}, 1360 }, 1361 } 1362 case types.BaseVirtualDeviceFileBackingInfo: 1363 parent := "" 1364 1365 switch backing := d.Backing.(type) { 1366 case *types.VirtualDiskFlatVer2BackingInfo: 1367 if backing.Parent != nil { 1368 parent = backing.Parent.FileName 1369 } 1370 case *types.VirtualDiskSeSparseBackingInfo: 1371 if backing.Parent != nil { 1372 parent = backing.Parent.FileName 1373 } 1374 case *types.VirtualDiskSparseVer2BackingInfo: 1375 if backing.Parent != nil { 1376 parent = backing.Parent.FileName 1377 } 1378 } 1379 1380 if parent != "" { 1381 desc, _, err := ctx.Map.FileManager().DiskDescriptor(ctx, &dc.Self, parent) 1382 if err != nil { 1383 return &types.InvalidDeviceSpec{ 1384 InvalidVmConfig: types.InvalidVmConfig{ 1385 Property: "virtualDeviceSpec.device.backing.parent.fileName", 1386 }, 1387 } 1388 } 1389 1390 // Disk Capacity is always same as the parent's 1391 x.CapacityInBytes = int64(desc.Capacity()) 1392 x.CapacityInKB = x.CapacityInBytes / 1024 1393 } 1394 1395 summary = fmt.Sprintf("%s KB", numberToString(x.CapacityInKB, ',')) 1396 1397 info := b.GetVirtualDeviceFileBackingInfo() 1398 var path object.DatastorePath 1399 path.FromString(info.FileName) 1400 1401 if path.Path == "" { 1402 filename, err := vm.genVmdkPath(ctx, path) 1403 if err != nil { 1404 return err 1405 } 1406 1407 info.FileName = filename 1408 } 1409 1410 err := vdmCreateVirtualDisk(ctx, spec.FileOperation, &types.CreateVirtualDisk_Task{ 1411 Datacenter: &dc.Self, 1412 Name: info.FileName, 1413 Spec: &types.FileBackedVirtualDiskSpec{CapacityKb: x.CapacityInKB}, 1414 }) 1415 if err != nil { 1416 return err 1417 } 1418 1419 ctx.Update(vm, []types.PropertyChange{ 1420 {Name: "summary.config.numVirtualDisks", Val: vm.Summary.Config.NumVirtualDisks + 1}, 1421 }) 1422 1423 p, _ := parseDatastorePath(info.FileName) 1424 ds := vm.findDatastore(ctx, p.Datastore) 1425 info.Datastore = &ds.Self 1426 1427 if oldDevice != nil { 1428 if oldDisk, ok := oldDevice.(*types.VirtualDisk); ok { 1429 // add previous capacity to datastore freespace 1430 ctx.WithLock(ds, func() { 1431 ds.Summary.FreeSpace += getDiskSize(oldDisk) 1432 ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace 1433 }) 1434 } 1435 } 1436 1437 // then subtract new capacity from datastore freespace 1438 // XXX: compare disk size and free space until windows stat is supported 1439 ctx.WithLock(ds, func() { 1440 ds.Summary.FreeSpace -= getDiskSize(x) 1441 ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace 1442 }) 1443 1444 vm.updateDiskLayouts(ctx) 1445 1446 if disk, ok := b.(*types.VirtualDiskFlatVer2BackingInfo); ok { 1447 // These properties default to false 1448 props := []**bool{ 1449 &disk.EagerlyScrub, 1450 &disk.ThinProvisioned, 1451 &disk.WriteThrough, 1452 &disk.Split, 1453 &disk.DigestEnabled, 1454 } 1455 for _, prop := range props { 1456 if *prop == nil { 1457 *prop = types.NewBool(false) 1458 } 1459 } 1460 disk.Uuid = virtualDiskUUID(&dc.Self, info.FileName) 1461 } 1462 } 1463 case *types.VirtualCdrom: 1464 if b, ok := d.Backing.(types.BaseVirtualDeviceFileBackingInfo); ok { 1465 summary = "ISO " + b.GetVirtualDeviceFileBackingInfo().FileName 1466 } 1467 case *types.VirtualFloppy: 1468 if b, ok := d.Backing.(types.BaseVirtualDeviceFileBackingInfo); ok { 1469 summary = "Image " + b.GetVirtualDeviceFileBackingInfo().FileName 1470 } 1471 case *types.VirtualSerialPort: 1472 switch b := d.Backing.(type) { 1473 case types.BaseVirtualDeviceFileBackingInfo: 1474 summary = "File " + b.GetVirtualDeviceFileBackingInfo().FileName 1475 case *types.VirtualSerialPortURIBackingInfo: 1476 summary = "Remote " + b.ServiceURI 1477 } 1478 } 1479 1480 if d.UnitNumber == nil && controller != nil { 1481 devices.AssignController(device, controller) 1482 } 1483 1484 if d.DeviceInfo == nil { 1485 d.DeviceInfo = &types.Description{ 1486 Label: label, 1487 Summary: summary, 1488 } 1489 } else { 1490 info := d.DeviceInfo.GetDescription() 1491 if info.Label == "" { 1492 info.Label = label 1493 } 1494 if info.Summary == "" { 1495 info.Summary = summary 1496 } 1497 } 1498 1499 switch device.(type) { 1500 case types.BaseVirtualEthernetCard, *types.VirtualCdrom, *types.VirtualFloppy, *types.VirtualUSB, *types.VirtualSerialPort: 1501 if d.Connectable == nil { 1502 d.Connectable = &types.VirtualDeviceConnectInfo{StartConnected: true, Connected: true} 1503 } 1504 } 1505 1506 // device can be connected only if vm is powered on 1507 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOn { 1508 if d.Connectable != nil { 1509 d.Connectable.Connected = false 1510 } 1511 } 1512 1513 return nil 1514 } 1515 1516 func (vm *VirtualMachine) removeDevice(ctx *Context, devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) object.VirtualDeviceList { 1517 key := spec.Device.GetVirtualDevice().Key 1518 1519 for i, d := range devices { 1520 if d.GetVirtualDevice().Key != key { 1521 continue 1522 } 1523 1524 devices = append(devices[:i], devices[i+1:]...) 1525 1526 switch device := spec.Device.(type) { 1527 case *types.VirtualDisk: 1528 if spec.FileOperation == types.VirtualDeviceConfigSpecFileOperationDestroy { 1529 var file string 1530 1531 switch b := device.Backing.(type) { 1532 case types.BaseVirtualDeviceFileBackingInfo: 1533 file = b.GetVirtualDeviceFileBackingInfo().FileName 1534 1535 p, _ := parseDatastorePath(file) 1536 ds := vm.findDatastore(ctx, p.Datastore) 1537 1538 ctx.WithLock(ds, func() { 1539 ds.Summary.FreeSpace += getDiskSize(device) 1540 ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace 1541 }) 1542 } 1543 1544 if file != "" { 1545 dc := ctx.Map.getEntityDatacenter(vm) 1546 dm := ctx.Map.VirtualDiskManager() 1547 if dc == nil { 1548 continue // parent was destroyed 1549 } 1550 res := dm.DeleteVirtualDiskTask(ctx, &types.DeleteVirtualDisk_Task{ 1551 Name: file, 1552 Datacenter: &dc.Self, 1553 }) 1554 ctask := ctx.Map.Get(res.(*methods.DeleteVirtualDisk_TaskBody).Res.Returnval).(*Task) 1555 ctask.Wait() 1556 } 1557 } 1558 ctx.Update(vm, []types.PropertyChange{ 1559 {Name: "summary.config.numVirtualDisks", Val: vm.Summary.Config.NumVirtualDisks - 1}, 1560 }) 1561 1562 vm.RefreshStorageInfo(ctx, nil) 1563 case types.BaseVirtualEthernetCard: 1564 var net types.ManagedObjectReference 1565 1566 switch b := device.GetVirtualEthernetCard().Backing.(type) { 1567 case *types.VirtualEthernetCardNetworkBackingInfo: 1568 net = *b.Network 1569 case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo: 1570 net.Type = "DistributedVirtualPortgroup" 1571 net.Value = b.Port.PortgroupKey 1572 } 1573 1574 for j, nicInfo := range vm.Guest.Net { 1575 if nicInfo.DeviceConfigId == key { 1576 vm.Guest.Net = append(vm.Guest.Net[:j], vm.Guest.Net[j+1:]...) 1577 break 1578 } 1579 } 1580 1581 networks := vm.Network 1582 RemoveReference(&networks, net) 1583 ctx.Update(vm, []types.PropertyChange{ 1584 {Name: "summary.config.numEthernetCards", Val: vm.Summary.Config.NumEthernetCards - 1}, 1585 {Name: "network", Val: networks}, 1586 }) 1587 } 1588 1589 break 1590 } 1591 1592 return devices 1593 } 1594 1595 func (vm *VirtualMachine) genVmdkPath(ctx *Context, p object.DatastorePath) (string, types.BaseMethodFault) { 1596 if p.Datastore == "" { 1597 p.FromString(vm.Config.Files.VmPathName) 1598 } 1599 if p.Path == "" { 1600 p.Path = vm.Config.Name 1601 } else { 1602 p.Path = path.Dir(p.Path) 1603 } 1604 vmdir := p.String() 1605 index := 0 1606 for { 1607 var filename string 1608 if index == 0 { 1609 filename = fmt.Sprintf("%s.vmdk", vm.Config.Name) 1610 } else { 1611 filename = fmt.Sprintf("%s_%d.vmdk", vm.Config.Name, index) 1612 } 1613 1614 f, err := vm.createFile(ctx, vmdir, filename, false) 1615 if err != nil { 1616 switch err.(type) { 1617 case *types.FileAlreadyExists: 1618 index++ 1619 continue 1620 default: 1621 return "", err 1622 } 1623 } 1624 1625 _ = f.Close() 1626 _ = os.Remove(f.Name()) 1627 1628 return path.Join(vmdir, filename), nil 1629 } 1630 } 1631 1632 // Encrypt requires powered off VM with no snapshots. 1633 // Decrypt requires powered off VM. 1634 // Deep recrypt requires powered off VM with no snapshots. 1635 // Shallow recrypt works with VMs in any power state and even if snapshots are 1636 // present as long as it is a single chain and not a tree. 1637 func (vm *VirtualMachine) updateCrypto( 1638 ctx *Context, 1639 spec types.BaseCryptoSpec) types.BaseMethodFault { 1640 1641 const configKeyId = "config.keyId" 1642 1643 assertEncrypted := func() types.BaseMethodFault { 1644 if vm.Config.KeyId == nil { 1645 return newInvalidStateFault("vm is not encrypted") 1646 } 1647 return nil 1648 } 1649 1650 assertPoweredOff := func() types.BaseMethodFault { 1651 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOff { 1652 return &types.InvalidPowerState{ 1653 ExistingState: vm.Runtime.PowerState, 1654 RequestedState: types.VirtualMachinePowerStatePoweredOff, 1655 } 1656 } 1657 return nil 1658 } 1659 1660 assertNoSnapshots := func(allowSingleChain bool) types.BaseMethodFault { 1661 hasSnapshots := vm.Snapshot != nil && vm.Snapshot.CurrentSnapshot != nil 1662 if !hasSnapshots { 1663 return nil 1664 } 1665 if !allowSingleChain { 1666 return newInvalidStateFault("vm has snapshots") 1667 } 1668 type node = types.VirtualMachineSnapshotTree 1669 var isTreeFn func(nodes []node) types.BaseMethodFault 1670 isTreeFn = func(nodes []node) types.BaseMethodFault { 1671 switch len(nodes) { 1672 case 0: 1673 return nil 1674 case 1: 1675 return isTreeFn(nodes[0].ChildSnapshotList) 1676 default: 1677 return newInvalidStateFault("vm has snapshot tree") 1678 } 1679 } 1680 return isTreeFn(vm.Snapshot.RootSnapshotList) 1681 } 1682 1683 doRecrypt := func(newKeyID types.CryptoKeyId) types.BaseMethodFault { 1684 if err := assertEncrypted(); err != nil { 1685 return err 1686 } 1687 1688 var providerID *types.KeyProviderId 1689 if pid := newKeyID.ProviderId; pid != nil { 1690 providerID = &types.KeyProviderId{ 1691 Id: pid.Id, 1692 } 1693 } 1694 1695 keyID := newKeyID.KeyId 1696 if providerID == nil { 1697 if p, k := getDefaultProvider(ctx, vm, true); p != "" && k != "" { 1698 providerID = &types.KeyProviderId{ 1699 Id: p, 1700 } 1701 keyID = k 1702 } 1703 } else if keyID == "" { 1704 keyID = generateKeyForProvider(ctx, providerID.Id) 1705 } 1706 1707 ctx.Update(vm, []types.PropertyChange{ 1708 { 1709 Name: configKeyId, 1710 Op: types.PropertyChangeOpAssign, 1711 Val: &types.CryptoKeyId{ 1712 KeyId: keyID, 1713 ProviderId: providerID, 1714 }, 1715 }, 1716 }) 1717 return nil 1718 } 1719 1720 switch tspec := spec.(type) { 1721 case *types.CryptoSpecDecrypt: 1722 if err := assertPoweredOff(); err != nil { 1723 return err 1724 } 1725 if err := assertNoSnapshots(false); err != nil { 1726 return err 1727 } 1728 if err := assertEncrypted(); err != nil { 1729 return err 1730 } 1731 ctx.Update(vm, []types.PropertyChange{ 1732 { 1733 Name: configKeyId, 1734 Op: types.PropertyChangeOpRemove, 1735 Val: nil, 1736 }, 1737 }) 1738 1739 case *types.CryptoSpecDeepRecrypt: 1740 if err := assertPoweredOff(); err != nil { 1741 return err 1742 } 1743 if err := assertNoSnapshots(false); err != nil { 1744 return err 1745 } 1746 return doRecrypt(tspec.NewKeyId) 1747 1748 case *types.CryptoSpecShallowRecrypt: 1749 if err := assertNoSnapshots(true); err != nil { 1750 return err 1751 } 1752 return doRecrypt(tspec.NewKeyId) 1753 1754 case *types.CryptoSpecEncrypt: 1755 if err := assertPoweredOff(); err != nil { 1756 return err 1757 } 1758 if err := assertNoSnapshots(false); err != nil { 1759 return err 1760 } 1761 if vm.Config.KeyId != nil { 1762 return newInvalidStateFault("vm is already encrypted") 1763 } 1764 1765 var providerID *types.KeyProviderId 1766 if pid := tspec.CryptoKeyId.ProviderId; pid != nil { 1767 providerID = &types.KeyProviderId{ 1768 Id: pid.Id, 1769 } 1770 } 1771 1772 keyID := tspec.CryptoKeyId.KeyId 1773 if providerID == nil { 1774 if p, k := getDefaultProvider(ctx, vm, true); p != "" && k != "" { 1775 providerID = &types.KeyProviderId{ 1776 Id: p, 1777 } 1778 keyID = k 1779 } 1780 } else if keyID == "" { 1781 keyID = generateKeyForProvider(ctx, providerID.Id) 1782 } 1783 1784 ctx.Update(vm, []types.PropertyChange{ 1785 { 1786 Name: configKeyId, 1787 Op: types.PropertyChangeOpAssign, 1788 Val: &types.CryptoKeyId{ 1789 KeyId: keyID, 1790 ProviderId: providerID, 1791 }, 1792 }, 1793 }) 1794 1795 case *types.CryptoSpecNoOp, 1796 *types.CryptoSpecRegister: 1797 1798 // No-op 1799 } 1800 1801 return nil 1802 } 1803 1804 func (vm *VirtualMachine) configureDevices(ctx *Context, spec *types.VirtualMachineConfigSpec) types.BaseMethodFault { 1805 var changes []types.PropertyChange 1806 field := mo.Field{Path: "config.hardware.device"} 1807 devices := object.VirtualDeviceList(vm.Config.Hardware.Device) 1808 1809 var err types.BaseMethodFault 1810 for i, change := range spec.DeviceChange { 1811 dspec := change.GetVirtualDeviceConfigSpec() 1812 device := dspec.Device.GetVirtualDevice() 1813 invalid := &types.InvalidDeviceSpec{DeviceIndex: int32(i)} 1814 change := types.PropertyChange{} 1815 1816 switch dspec.FileOperation { 1817 case types.VirtualDeviceConfigSpecFileOperationCreate: 1818 switch dspec.Device.(type) { 1819 case *types.VirtualDisk: 1820 if device.UnitNumber == nil { 1821 return invalid 1822 } 1823 } 1824 } 1825 1826 switch dspec.Operation { 1827 case types.VirtualDeviceConfigSpecOperationAdd: 1828 change.Op = types.PropertyChangeOpAdd 1829 1830 if devices.FindByKey(device.Key) != nil && device.ControllerKey == 0 { 1831 // Note: real ESX does not allow adding base controllers (ControllerKey = 0) 1832 // after VM is created (returns success but device is not added). 1833 continue 1834 } else if device.UnitNumber != nil && devices.SelectByType(dspec.Device).Select(func(d types.BaseVirtualDevice) bool { 1835 base := d.GetVirtualDevice() 1836 if base.UnitNumber != nil { 1837 if base.ControllerKey != device.ControllerKey { 1838 return false 1839 } 1840 return *base.UnitNumber == *device.UnitNumber 1841 } 1842 return false 1843 }) != nil { 1844 // UnitNumber for this device type is taken 1845 return invalid 1846 } 1847 1848 key := device.Key 1849 err = vm.configureDevice(ctx, devices, dspec, nil) 1850 if err != nil { 1851 return err 1852 } 1853 1854 devices = append(devices, dspec.Device) 1855 change.Val = dspec.Device 1856 if key != device.Key { 1857 // Update ControllerKey refs 1858 for i := range spec.DeviceChange { 1859 ckey := &spec.DeviceChange[i].GetVirtualDeviceConfigSpec().Device.GetVirtualDevice().ControllerKey 1860 if *ckey == key { 1861 *ckey = device.Key 1862 } 1863 } 1864 } 1865 case types.VirtualDeviceConfigSpecOperationEdit: 1866 rspec := *dspec 1867 oldDevice := devices.FindByKey(device.Key) 1868 if oldDevice == nil { 1869 return invalid 1870 } 1871 rspec.Device = oldDevice 1872 devices = vm.removeDevice(ctx, devices, &rspec) 1873 if device.DeviceInfo != nil { 1874 device.DeviceInfo.GetDescription().Summary = "" // regenerate summary 1875 } 1876 1877 err = vm.configureDevice(ctx, devices, dspec, oldDevice) 1878 if err != nil { 1879 return err 1880 } 1881 1882 devices = append(devices, dspec.Device) 1883 change.Val = dspec.Device 1884 case types.VirtualDeviceConfigSpecOperationRemove: 1885 change.Op = types.PropertyChangeOpRemove 1886 1887 devices = vm.removeDevice(ctx, devices, dspec) 1888 } 1889 1890 field.Key = device.Key 1891 change.Name = field.String() 1892 changes = append(changes, change) 1893 } 1894 1895 if len(changes) != 0 { 1896 change := types.PropertyChange{Name: field.Path, Val: []types.BaseVirtualDevice(devices)} 1897 ctx.Update(vm, append(changes, change)) 1898 } 1899 1900 err = vm.updateDiskLayouts(ctx) 1901 if err != nil { 1902 return err 1903 } 1904 1905 // Do this after device config, as some may apply to the devices themselves (e.g. ethernet -> guest.net) 1906 err = vm.applyExtraConfig(ctx, spec) 1907 if err != nil { 1908 return err 1909 } 1910 1911 return nil 1912 } 1913 1914 type powerVMTask struct { 1915 *VirtualMachine 1916 1917 state types.VirtualMachinePowerState 1918 ctx *Context 1919 } 1920 1921 func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) { 1922 c.logPrintf("running power task: requesting %s, existing %s", 1923 c.state, c.VirtualMachine.Runtime.PowerState) 1924 1925 if c.VirtualMachine.Runtime.PowerState == c.state { 1926 return nil, &types.InvalidPowerState{ 1927 RequestedState: c.state, 1928 ExistingState: c.VirtualMachine.Runtime.PowerState, 1929 } 1930 } 1931 1932 var boot types.AnyType 1933 if c.state == types.VirtualMachinePowerStatePoweredOn { 1934 boot = time.Now() 1935 } 1936 1937 event := c.event(c.ctx) 1938 switch c.state { 1939 case types.VirtualMachinePowerStatePoweredOn: 1940 if c.VirtualMachine.hostInMM(c.ctx) { 1941 return nil, new(types.InvalidState) 1942 } 1943 1944 err := c.svm.start(c.ctx) 1945 if err != nil { 1946 return nil, &types.MissingPowerOnConfiguration{ 1947 VAppConfigFault: types.VAppConfigFault{ 1948 VimFault: types.VimFault{ 1949 MethodFault: types.MethodFault{ 1950 FaultCause: &types.LocalizedMethodFault{ 1951 Fault: &types.SystemErrorFault{Reason: err.Error()}, 1952 LocalizedMessage: err.Error()}}}}} 1953 } 1954 c.ctx.postEvent( 1955 &types.VmStartingEvent{VmEvent: event}, 1956 &types.VmPoweredOnEvent{VmEvent: event}, 1957 ) 1958 c.customize(c.ctx) 1959 case types.VirtualMachinePowerStatePoweredOff: 1960 c.svm.stop(c.ctx) 1961 c.ctx.postEvent( 1962 &types.VmStoppingEvent{VmEvent: event}, 1963 &types.VmPoweredOffEvent{VmEvent: event}, 1964 ) 1965 case types.VirtualMachinePowerStateSuspended: 1966 if c.VirtualMachine.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOn { 1967 return nil, &types.InvalidPowerState{ 1968 RequestedState: types.VirtualMachinePowerStatePoweredOn, 1969 ExistingState: c.VirtualMachine.Runtime.PowerState, 1970 } 1971 } 1972 1973 c.svm.pause(c.ctx) 1974 c.ctx.postEvent( 1975 &types.VmSuspendingEvent{VmEvent: event}, 1976 &types.VmSuspendedEvent{VmEvent: event}, 1977 ) 1978 } 1979 1980 // copy devices to prevent data race 1981 devices := c.VirtualMachine.cloneDevice() 1982 for _, d := range devices { 1983 conn := d.GetVirtualDevice().Connectable 1984 if conn == nil { 1985 continue 1986 } 1987 1988 if c.state == types.VirtualMachinePowerStatePoweredOn { 1989 // apply startConnected to current connection 1990 conn.Connected = conn.StartConnected 1991 } else { 1992 conn.Connected = false 1993 } 1994 } 1995 1996 c.ctx.Update(c.VirtualMachine, []types.PropertyChange{ 1997 {Name: "runtime.powerState", Val: c.state}, 1998 {Name: "summary.runtime.powerState", Val: c.state}, 1999 {Name: "summary.runtime.bootTime", Val: boot}, 2000 {Name: "config.hardware.device", Val: devices}, 2001 }) 2002 2003 return nil, nil 2004 } 2005 2006 func (vm *VirtualMachine) PowerOnVMTask(ctx *Context, c *types.PowerOnVM_Task) soap.HasFault { 2007 if vm.Config.Template { 2008 return &methods.PowerOnVM_TaskBody{ 2009 Fault_: Fault("cannot powerOn a template", &types.InvalidState{}), 2010 } 2011 } 2012 2013 runner := &powerVMTask{vm, types.VirtualMachinePowerStatePoweredOn, ctx} 2014 task := CreateTask(runner.Reference(), "powerOn", runner.Run) 2015 2016 return &methods.PowerOnVM_TaskBody{ 2017 Res: &types.PowerOnVM_TaskResponse{ 2018 Returnval: task.Run(ctx), 2019 }, 2020 } 2021 } 2022 2023 func (vm *VirtualMachine) PowerOffVMTask(ctx *Context, c *types.PowerOffVM_Task) soap.HasFault { 2024 runner := &powerVMTask{vm, types.VirtualMachinePowerStatePoweredOff, ctx} 2025 task := CreateTask(runner.Reference(), "powerOff", runner.Run) 2026 2027 return &methods.PowerOffVM_TaskBody{ 2028 Res: &types.PowerOffVM_TaskResponse{ 2029 Returnval: task.Run(ctx), 2030 }, 2031 } 2032 } 2033 2034 func (vm *VirtualMachine) SuspendVMTask(ctx *Context, req *types.SuspendVM_Task) soap.HasFault { 2035 runner := &powerVMTask{vm, types.VirtualMachinePowerStateSuspended, ctx} 2036 task := CreateTask(runner.Reference(), "suspend", runner.Run) 2037 2038 return &methods.SuspendVM_TaskBody{ 2039 Res: &types.SuspendVM_TaskResponse{ 2040 Returnval: task.Run(ctx), 2041 }, 2042 } 2043 } 2044 2045 func (vm *VirtualMachine) ResetVMTask(ctx *Context, req *types.ResetVM_Task) soap.HasFault { 2046 task := CreateTask(vm, "reset", func(task *Task) (types.AnyType, types.BaseMethodFault) { 2047 res := vm.PowerOffVMTask(ctx, &types.PowerOffVM_Task{This: vm.Self}) 2048 ctask := ctx.Map.Get(res.(*methods.PowerOffVM_TaskBody).Res.Returnval).(*Task) 2049 ctask.Wait() 2050 if ctask.Info.Error != nil { 2051 return nil, ctask.Info.Error.Fault 2052 } 2053 2054 res = vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{This: vm.Self}) 2055 ctask = ctx.Map.Get(res.(*methods.PowerOnVM_TaskBody).Res.Returnval).(*Task) 2056 ctask.Wait() 2057 2058 return nil, nil 2059 }) 2060 2061 return &methods.ResetVM_TaskBody{ 2062 Res: &types.ResetVM_TaskResponse{ 2063 Returnval: task.Run(ctx), 2064 }, 2065 } 2066 } 2067 2068 func (vm *VirtualMachine) RebootGuest(ctx *Context, req *types.RebootGuest) soap.HasFault { 2069 body := new(methods.RebootGuestBody) 2070 2071 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOn { 2072 body.Fault_ = Fault("", &types.InvalidPowerState{ 2073 RequestedState: types.VirtualMachinePowerStatePoweredOn, 2074 ExistingState: vm.Runtime.PowerState, 2075 }) 2076 return body 2077 } 2078 2079 if vm.Guest.ToolsRunningStatus == string(types.VirtualMachineToolsRunningStatusGuestToolsRunning) { 2080 vm.svm.restart(ctx) 2081 body.Res = new(types.RebootGuestResponse) 2082 } else { 2083 body.Fault_ = Fault("", new(types.ToolsUnavailable)) 2084 } 2085 2086 return body 2087 } 2088 2089 func (vm *VirtualMachine) ReconfigVMTask(ctx *Context, req *types.ReconfigVM_Task) soap.HasFault { 2090 task := CreateTask(vm, "reconfigVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2091 ctx.postEvent(&types.VmReconfiguredEvent{ 2092 VmEvent: vm.event(ctx), 2093 ConfigSpec: req.Spec, 2094 }) 2095 2096 if vm.Config.Template { 2097 expect := types.VirtualMachineConfigSpec{ 2098 Name: req.Spec.Name, 2099 Annotation: req.Spec.Annotation, 2100 } 2101 if !reflect.DeepEqual(&req.Spec, &expect) { 2102 log.Printf("template reconfigure only allows name and annotation change") 2103 return nil, new(types.NotSupported) 2104 } 2105 } 2106 2107 err := vm.configure(ctx, &req.Spec) 2108 2109 return nil, err 2110 }) 2111 2112 return &methods.ReconfigVM_TaskBody{ 2113 Res: &types.ReconfigVM_TaskResponse{ 2114 Returnval: task.Run(ctx), 2115 }, 2116 } 2117 } 2118 2119 func (vm *VirtualMachine) UpgradeVMTask(ctx *Context, req *types.UpgradeVM_Task) soap.HasFault { 2120 body := &methods.UpgradeVM_TaskBody{} 2121 2122 task := CreateTask(vm, "upgradeVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2123 2124 // InvalidPowerState 2125 // 2126 // 1. Is VM's power state anything other than powered off? 2127 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOff { 2128 return nil, &types.InvalidPowerStateFault{ 2129 ExistingState: vm.Runtime.PowerState, 2130 RequestedState: types.VirtualMachinePowerStatePoweredOff, 2131 } 2132 } 2133 2134 // InvalidState 2135 // 2136 // 1. Is host on which VM is scheduled in maintenance mode? 2137 // 2. Is VM a template? 2138 // 3. Is VM already the latest hardware version? 2139 var ( 2140 ebRef *types.ManagedObjectReference 2141 latestHardwareVersion string 2142 hostRef = vm.Runtime.Host 2143 supportedHardwareVersions = map[string]struct{}{} 2144 vmHardwareVersionString = vm.Config.Version 2145 ) 2146 if hostRef != nil { 2147 var hostInMaintenanceMode bool 2148 ctx.WithLock(*hostRef, func() { 2149 host := ctx.Map.Get(*hostRef).(*HostSystem) 2150 hostInMaintenanceMode = host.Runtime.InMaintenanceMode 2151 switch host.Parent.Type { 2152 case "ClusterComputeResource": 2153 obj := ctx.Map.Get(*host.Parent).(*ClusterComputeResource) 2154 ebRef = obj.EnvironmentBrowser 2155 case "ComputeResource": 2156 obj := ctx.Map.Get(*host.Parent).(*mo.ComputeResource) 2157 ebRef = obj.EnvironmentBrowser 2158 } 2159 }) 2160 if hostInMaintenanceMode { 2161 return nil, newInvalidStateFault("%s in maintenance mode", hostRef.Value) 2162 } 2163 } 2164 if vm.Config.Template { 2165 return nil, newInvalidStateFault("%s is template", vm.Reference().Value) 2166 } 2167 if ebRef != nil { 2168 ctx.WithLock(*ebRef, func() { 2169 eb := ctx.Map.Get(*ebRef).(*EnvironmentBrowser) 2170 for i := range eb.QueryConfigOptionDescriptorResponse.Returnval { 2171 cod := eb.QueryConfigOptionDescriptorResponse.Returnval[i] 2172 for j := range cod.Host { 2173 if cod.Host[j].Value == hostRef.Value { 2174 supportedHardwareVersions[cod.Key] = struct{}{} 2175 } 2176 if latestHardwareVersion == "" { 2177 if def := cod.DefaultConfigOption; def != nil && *def { 2178 latestHardwareVersion = cod.Key 2179 } 2180 } 2181 } 2182 } 2183 }) 2184 } 2185 2186 if latestHardwareVersion == "" { 2187 latestHardwareVersion = esx.HardwareVersion 2188 } 2189 if vmHardwareVersionString == latestHardwareVersion { 2190 return nil, newInvalidStateFault("%s is latest version", vm.Reference().Value) 2191 } 2192 if req.Version == "" { 2193 req.Version = latestHardwareVersion 2194 } 2195 2196 // NotSupported 2197 targetVersion, _ := types.ParseHardwareVersion(req.Version) 2198 if targetVersion.IsValid() { 2199 req.Version = targetVersion.String() 2200 } 2201 if _, ok := supportedHardwareVersions[req.Version]; !ok { 2202 msg := fmt.Sprintf("%s not supported", req.Version) 2203 return nil, &types.NotSupported{ 2204 RuntimeFault: types.RuntimeFault{ 2205 MethodFault: types.MethodFault{ 2206 FaultCause: &types.LocalizedMethodFault{ 2207 Fault: &types.SystemErrorFault{ 2208 Reason: msg, 2209 }, 2210 LocalizedMessage: msg, 2211 }, 2212 }, 2213 }, 2214 } 2215 } 2216 2217 // AlreadyUpgraded 2218 vmHardwareVersion, _ := types.ParseHardwareVersion(vmHardwareVersionString) 2219 if targetVersion.IsValid() && vmHardwareVersion.IsValid() && 2220 targetVersion <= vmHardwareVersion { 2221 2222 return nil, &types.AlreadyUpgradedFault{} 2223 } 2224 2225 // InvalidArgument 2226 if targetVersion < types.VMX3 { 2227 return nil, &types.InvalidArgument{} 2228 } 2229 2230 ctx.Update(vm, []types.PropertyChange{ 2231 { 2232 Name: "config.version", Val: targetVersion.String(), 2233 }, 2234 { 2235 Name: "summary.config.hwVersion", Val: targetVersion.String(), 2236 }, 2237 }) 2238 2239 return nil, nil 2240 }) 2241 2242 body.Res = &types.UpgradeVM_TaskResponse{ 2243 Returnval: task.Run(ctx), 2244 } 2245 2246 return body 2247 } 2248 2249 func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault { 2250 dc := ctx.Map.getEntityDatacenter(vm) 2251 2252 task := CreateTask(vm, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2253 if dc == nil { 2254 return nil, &types.ManagedObjectNotFound{Obj: vm.Self} // If our Parent was destroyed, so were we. 2255 // TODO: should this also trigger container removal? 2256 } 2257 2258 r := vm.UnregisterVM(ctx, &types.UnregisterVM{ 2259 This: req.This, 2260 }) 2261 2262 if r.Fault() != nil { 2263 return nil, r.Fault().VimFault().(types.BaseMethodFault) 2264 } 2265 2266 // Remove all devices 2267 devices := object.VirtualDeviceList(vm.Config.Hardware.Device) 2268 spec, _ := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationRemove) 2269 vm.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: spec}) 2270 2271 // Delete VM files from the datastore (ignoring result for now) 2272 m := ctx.Map.FileManager() 2273 2274 _ = m.DeleteDatastoreFileTask(ctx, &types.DeleteDatastoreFile_Task{ 2275 This: m.Reference(), 2276 Name: vm.Config.Files.LogDirectory, 2277 Datacenter: &dc.Self, 2278 }) 2279 2280 err := vm.svm.remove(ctx) 2281 if err != nil { 2282 return nil, &types.RuntimeFault{ 2283 MethodFault: types.MethodFault{ 2284 FaultCause: &types.LocalizedMethodFault{ 2285 Fault: &types.SystemErrorFault{Reason: err.Error()}, 2286 LocalizedMessage: err.Error()}}} 2287 } 2288 2289 return nil, nil 2290 }) 2291 2292 return &methods.Destroy_TaskBody{ 2293 Res: &types.Destroy_TaskResponse{ 2294 Returnval: task.Run(ctx), 2295 }, 2296 } 2297 } 2298 2299 func (vm *VirtualMachine) SetCustomValue(ctx *Context, req *types.SetCustomValue) soap.HasFault { 2300 return SetCustomValue(ctx, req) 2301 } 2302 2303 func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap.HasFault { 2304 r := &methods.UnregisterVMBody{} 2305 2306 if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn { 2307 r.Fault_ = Fault("", &types.InvalidPowerState{ 2308 RequestedState: types.VirtualMachinePowerStatePoweredOff, 2309 ExistingState: vm.Runtime.PowerState, 2310 }) 2311 2312 return r 2313 } 2314 2315 host := ctx.Map.Get(*vm.Runtime.Host).(*HostSystem) 2316 ctx.Map.RemoveReference(ctx, host, &host.Vm, vm.Self) 2317 2318 if vm.ResourcePool != nil { 2319 switch pool := ctx.Map.Get(*vm.ResourcePool).(type) { 2320 case *ResourcePool: 2321 ctx.Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self) 2322 case *VirtualApp: 2323 ctx.Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self) 2324 } 2325 } 2326 2327 for i := range vm.Datastore { 2328 ds := ctx.Map.Get(vm.Datastore[i]).(*Datastore) 2329 ctx.Map.RemoveReference(ctx, ds, &ds.Vm, vm.Self) 2330 } 2331 2332 ctx.postEvent(&types.VmRemovedEvent{VmEvent: vm.event(ctx)}) 2333 if f, ok := asFolderMO(ctx.Map.getEntityParent(vm, "Folder")); ok { 2334 folderRemoveChild(ctx, f, c.This) 2335 } 2336 2337 r.Res = new(types.UnregisterVMResponse) 2338 2339 return r 2340 } 2341 2342 type vmFolder interface { 2343 CreateVMTask(ctx *Context, c *types.CreateVM_Task) soap.HasFault 2344 } 2345 2346 func (vm *VirtualMachine) cloneDevice() []types.BaseVirtualDevice { 2347 src := types.ArrayOfVirtualDevice{ 2348 VirtualDevice: vm.Config.Hardware.Device, 2349 } 2350 dst := types.ArrayOfVirtualDevice{} 2351 deepCopy(src, &dst) 2352 return dst.VirtualDevice 2353 } 2354 2355 func (vm *VirtualMachine) worldID() int { 2356 return int(binary.BigEndian.Uint32(vm.uid[0:4])) 2357 } 2358 2359 func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soap.HasFault { 2360 pool := req.Spec.Location.Pool 2361 if pool == nil { 2362 if !vm.Config.Template { 2363 pool = vm.ResourcePool 2364 } 2365 } 2366 2367 destHost := vm.Runtime.Host 2368 2369 if req.Spec.Location.Host != nil { 2370 destHost = req.Spec.Location.Host 2371 } 2372 2373 folder, _ := asFolderMO(ctx.Map.Get(req.Folder)) 2374 host := ctx.Map.Get(*destHost).(*HostSystem) 2375 event := vm.event(ctx) 2376 2377 ctx.postEvent(&types.VmBeingClonedEvent{ 2378 VmCloneEvent: types.VmCloneEvent{ 2379 VmEvent: event, 2380 }, 2381 DestFolder: folderEventArgument(folder), 2382 DestName: req.Name, 2383 DestHost: *host.eventArgument(), 2384 }) 2385 2386 vmx := vm.vmx(nil) 2387 vmx.Path = req.Name 2388 if ref := req.Spec.Location.Datastore; ref != nil { 2389 ds := ctx.Map.Get(*ref).(*Datastore).Name 2390 vmx.Datastore = ds 2391 } 2392 2393 task := CreateTask(vm, "cloneVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2394 if pool == nil { 2395 return nil, &types.InvalidArgument{InvalidProperty: "spec.location.pool"} 2396 } 2397 if obj := ctx.Map.FindByName(req.Name, folder.ChildEntity); obj != nil { 2398 return nil, &types.DuplicateName{ 2399 Name: req.Name, 2400 Object: obj.Reference(), 2401 } 2402 } 2403 config := types.VirtualMachineConfigSpec{ 2404 Name: req.Name, 2405 Version: vm.Config.Version, 2406 GuestId: vm.Config.GuestId, 2407 Files: &types.VirtualMachineFileInfo{ 2408 VmPathName: vmx.String(), 2409 }, 2410 } 2411 2412 // Copying hardware properties 2413 config.NumCPUs = vm.Config.Hardware.NumCPU 2414 config.MemoryMB = int64(vm.Config.Hardware.MemoryMB) 2415 config.NumCoresPerSocket = vm.Config.Hardware.NumCoresPerSocket 2416 config.VirtualICH7MPresent = vm.Config.Hardware.VirtualICH7MPresent 2417 config.VirtualSMCPresent = vm.Config.Hardware.VirtualSMCPresent 2418 2419 defaultDevices := object.VirtualDeviceList(esx.VirtualDevice) 2420 devices := vm.cloneDevice() 2421 2422 for _, device := range devices { 2423 var fop types.VirtualDeviceConfigSpecFileOperation 2424 2425 if defaultDevices.Find(object.VirtualDeviceList(devices).Name(device)) != nil { 2426 // Default devices are added during CreateVMTask 2427 continue 2428 } 2429 2430 switch disk := device.(type) { 2431 case *types.VirtualDisk: 2432 // TODO: consider VirtualMachineCloneSpec.DiskMoveType 2433 fop = types.VirtualDeviceConfigSpecFileOperationCreate 2434 2435 // Leave FileName empty so CreateVM will just create a new one under VmPathName 2436 disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo).FileName = "" 2437 disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo).Parent = nil 2438 } 2439 2440 config.DeviceChange = append(config.DeviceChange, &types.VirtualDeviceConfigSpec{ 2441 Operation: types.VirtualDeviceConfigSpecOperationAdd, 2442 Device: device, 2443 FileOperation: fop, 2444 }) 2445 } 2446 2447 if dst, src := config, req.Spec.Config; src != nil { 2448 dst.ExtraConfig = src.ExtraConfig 2449 copyNonEmptyValue(&dst.Uuid, &src.Uuid) 2450 copyNonEmptyValue(&dst.InstanceUuid, &src.InstanceUuid) 2451 copyNonEmptyValue(&dst.NumCPUs, &src.NumCPUs) 2452 copyNonEmptyValue(&dst.MemoryMB, &src.MemoryMB) 2453 } 2454 2455 res := ctx.Map.Get(req.Folder).(vmFolder).CreateVMTask(ctx, &types.CreateVM_Task{ 2456 This: folder.Self, 2457 Config: config, 2458 Pool: *pool, 2459 Host: destHost, 2460 }) 2461 2462 ctask := ctx.Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task) 2463 ctask.Wait() 2464 if ctask.Info.Error != nil { 2465 return nil, ctask.Info.Error.Fault 2466 } 2467 2468 ref := ctask.Info.Result.(types.ManagedObjectReference) 2469 clone := ctx.Map.Get(ref).(*VirtualMachine) 2470 clone.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Location.DeviceChange}) 2471 if req.Spec.Config != nil && req.Spec.Config.DeviceChange != nil { 2472 clone.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Config.DeviceChange}) 2473 } 2474 clone.DataSets = copyDataSetsForVmClone(vm.DataSets) 2475 2476 if req.Spec.Template { 2477 _ = clone.MarkAsTemplate(&types.MarkAsTemplate{This: clone.Self}) 2478 } 2479 2480 ctx.postEvent(&types.VmClonedEvent{ 2481 VmCloneEvent: types.VmCloneEvent{VmEvent: clone.event(ctx)}, 2482 SourceVm: *event.Vm, 2483 }) 2484 2485 return ref, nil 2486 }) 2487 2488 return &methods.CloneVM_TaskBody{ 2489 Res: &types.CloneVM_TaskResponse{ 2490 Returnval: task.Run(ctx), 2491 }, 2492 } 2493 } 2494 2495 func copyNonEmptyValue[T comparable](dst, src *T) { 2496 if dst == nil || src == nil { 2497 return 2498 } 2499 var t T 2500 if *src == t { 2501 return 2502 } 2503 *dst = *src 2504 } 2505 2506 func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Task) soap.HasFault { 2507 task := CreateTask(vm, "relocateVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2508 var changes []types.PropertyChange 2509 2510 if ref := req.Spec.Datastore; ref != nil { 2511 ds := ctx.Map.Get(*ref).(*Datastore) 2512 ctx.Map.RemoveReference(ctx, ds, &ds.Vm, *ref) 2513 2514 // TODO: migrate vm.Config.Files, vm.Summary.Config.VmPathName, vm.Layout and vm.LayoutEx 2515 2516 changes = append(changes, types.PropertyChange{Name: "datastore", Val: []types.ManagedObjectReference{*ref}}) 2517 } 2518 2519 if ref := req.Spec.Pool; ref != nil { 2520 pool := ctx.Map.Get(*ref).(*ResourcePool) 2521 ctx.Map.RemoveReference(ctx, pool, &pool.Vm, *ref) 2522 2523 changes = append(changes, types.PropertyChange{Name: "resourcePool", Val: ref}) 2524 } 2525 2526 if ref := req.Spec.Host; ref != nil { 2527 host := ctx.Map.Get(*ref).(*HostSystem) 2528 ctx.Map.RemoveReference(ctx, host, &host.Vm, *ref) 2529 2530 changes = append(changes, 2531 types.PropertyChange{Name: "runtime.host", Val: ref}, 2532 types.PropertyChange{Name: "summary.runtime.host", Val: ref}, 2533 ) 2534 } 2535 2536 if ref := req.Spec.Folder; ref != nil { 2537 folder := ctx.Map.Get(*ref).(*Folder) 2538 ctx.WithLock(folder, func() { 2539 res := folder.MoveIntoFolderTask(ctx, &types.MoveIntoFolder_Task{ 2540 List: []types.ManagedObjectReference{vm.Self}, 2541 }).(*methods.MoveIntoFolder_TaskBody).Res 2542 // Wait for task to complete while we hold the Folder lock 2543 ctx.Map.Get(res.Returnval).(*Task).Wait() 2544 }) 2545 } 2546 2547 cspec := &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.DeviceChange} 2548 if err := vm.configureDevices(ctx, cspec); err != nil { 2549 return nil, err 2550 } 2551 2552 ctx.postEvent(&types.VmMigratedEvent{ 2553 VmEvent: vm.event(ctx), 2554 SourceHost: *ctx.Map.Get(*vm.Runtime.Host).(*HostSystem).eventArgument(), 2555 SourceDatacenter: datacenterEventArgument(ctx, vm), 2556 SourceDatastore: ctx.Map.Get(vm.Datastore[0]).(*Datastore).eventArgument(), 2557 }) 2558 2559 ctx.Update(vm, changes) 2560 2561 return nil, nil 2562 }) 2563 2564 return &methods.RelocateVM_TaskBody{ 2565 Res: &types.RelocateVM_TaskResponse{ 2566 Returnval: task.Run(ctx), 2567 }, 2568 } 2569 } 2570 2571 func (vm *VirtualMachine) customize(ctx *Context) { 2572 if vm.imc == nil { 2573 return 2574 } 2575 2576 event := types.CustomizationEvent{VmEvent: vm.event(ctx)} 2577 ctx.postEvent(&types.CustomizationStartedEvent{CustomizationEvent: event}) 2578 2579 changes := []types.PropertyChange{ 2580 {Name: "config.tools.pendingCustomization", Val: ""}, 2581 } 2582 2583 hostname := "" 2584 address := "" 2585 2586 switch c := vm.imc.Identity.(type) { 2587 case *types.CustomizationLinuxPrep: 2588 hostname = customizeName(vm, c.HostName) 2589 case *types.CustomizationSysprep: 2590 hostname = customizeName(vm, c.UserData.ComputerName) 2591 } 2592 2593 cards := object.VirtualDeviceList(vm.Config.Hardware.Device).SelectByType((*types.VirtualEthernetCard)(nil)) 2594 2595 for i, s := range vm.imc.NicSettingMap { 2596 nic := &vm.Guest.Net[i] 2597 if s.MacAddress != "" { 2598 nic.MacAddress = strings.ToLower(s.MacAddress) // MacAddress in guest will always be lowercase 2599 card := cards[i].(types.BaseVirtualEthernetCard).GetVirtualEthernetCard() 2600 card.MacAddress = s.MacAddress // MacAddress in Virtual NIC can be any case 2601 card.AddressType = string(types.VirtualEthernetCardMacTypeManual) 2602 } 2603 if nic.DnsConfig == nil { 2604 nic.DnsConfig = new(types.NetDnsConfigInfo) 2605 } 2606 if s.Adapter.DnsDomain != "" { 2607 nic.DnsConfig.DomainName = s.Adapter.DnsDomain 2608 } 2609 if len(s.Adapter.DnsServerList) != 0 { 2610 nic.DnsConfig.IpAddress = s.Adapter.DnsServerList 2611 } 2612 if hostname != "" { 2613 nic.DnsConfig.HostName = hostname 2614 } 2615 if len(vm.imc.GlobalIPSettings.DnsSuffixList) != 0 { 2616 nic.DnsConfig.SearchDomain = vm.imc.GlobalIPSettings.DnsSuffixList 2617 } 2618 if nic.IpConfig == nil { 2619 nic.IpConfig = new(types.NetIpConfigInfo) 2620 } 2621 2622 switch ip := s.Adapter.Ip.(type) { 2623 case *types.CustomizationCustomIpGenerator: 2624 case *types.CustomizationDhcpIpGenerator: 2625 case *types.CustomizationFixedIp: 2626 if address == "" { 2627 address = ip.IpAddress 2628 } 2629 nic.IpAddress = []string{ip.IpAddress} 2630 nic.IpConfig.IpAddress = []types.NetIpConfigInfoIpAddress{{ 2631 IpAddress: ip.IpAddress, 2632 }} 2633 case *types.CustomizationUnknownIpGenerator: 2634 } 2635 } 2636 2637 if len(vm.imc.NicSettingMap) != 0 { 2638 changes = append(changes, types.PropertyChange{Name: "guest.net", Val: vm.Guest.Net}) 2639 } 2640 if hostname != "" { 2641 changes = append(changes, types.PropertyChange{Name: "guest.hostName", Val: hostname}) 2642 changes = append(changes, types.PropertyChange{Name: "summary.guest.hostName", Val: hostname}) 2643 } 2644 if address != "" { 2645 changes = append(changes, types.PropertyChange{Name: "guest.ipAddress", Val: address}) 2646 changes = append(changes, types.PropertyChange{Name: "summary.guest.ipAddress", Val: address}) 2647 } 2648 2649 vm.imc = nil 2650 ctx.Update(vm, changes) 2651 ctx.postEvent(&types.CustomizationSucceeded{CustomizationEvent: event}) 2652 } 2653 2654 func (vm *VirtualMachine) CustomizeVMTask(ctx *Context, req *types.CustomizeVM_Task) soap.HasFault { 2655 task := CreateTask(vm, "customizeVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2656 if vm.hostInMM(ctx) { 2657 return nil, new(types.InvalidState) 2658 } 2659 2660 if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn { 2661 return nil, &types.InvalidPowerState{ 2662 RequestedState: types.VirtualMachinePowerStatePoweredOff, 2663 ExistingState: vm.Runtime.PowerState, 2664 } 2665 } 2666 if vm.Config.Tools.PendingCustomization != "" { 2667 return nil, new(types.CustomizationPending) 2668 } 2669 if len(vm.Guest.Net) != len(req.Spec.NicSettingMap) { 2670 return nil, &types.NicSettingMismatch{ 2671 NumberOfNicsInSpec: int32(len(req.Spec.NicSettingMap)), 2672 NumberOfNicsInVM: int32(len(vm.Guest.Net)), 2673 } 2674 } 2675 2676 vm.imc = &req.Spec 2677 vm.Config.Tools.PendingCustomization = uuid.New().String() 2678 2679 return nil, nil 2680 }) 2681 2682 return &methods.CustomizeVM_TaskBody{ 2683 Res: &types.CustomizeVM_TaskResponse{ 2684 Returnval: task.Run(ctx), 2685 }, 2686 } 2687 } 2688 2689 func (vm *VirtualMachine) CreateSnapshotTask(ctx *Context, req *types.CreateSnapshot_Task) soap.HasFault { 2690 body := &methods.CreateSnapshot_TaskBody{} 2691 2692 r := &types.CreateSnapshotEx_Task{ 2693 Name: req.Name, 2694 Description: req.Description, 2695 Memory: req.Memory, 2696 } 2697 2698 if req.Quiesce { 2699 r.QuiesceSpec = &types.VirtualMachineGuestQuiesceSpec{} 2700 } 2701 2702 res := vm.CreateSnapshotExTask(ctx, r) 2703 2704 if res.Fault() != nil { 2705 body.Fault_ = res.Fault() 2706 } else { 2707 body.Res = &types.CreateSnapshot_TaskResponse{ 2708 Returnval: res.(*methods.CreateSnapshotEx_TaskBody).Res.Returnval, 2709 } 2710 } 2711 2712 return body 2713 } 2714 2715 func (vm *VirtualMachine) CreateSnapshotExTask(ctx *Context, req *types.CreateSnapshotEx_Task) soap.HasFault { 2716 task := CreateTask(vm, "createSnapshotEx", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2717 var changes []types.PropertyChange 2718 2719 if vm.Snapshot == nil { 2720 vm.Snapshot = &types.VirtualMachineSnapshotInfo{} 2721 } 2722 2723 snapshot := &VirtualMachineSnapshot{} 2724 snapshot.Vm = vm.Reference() 2725 snapshot.Config = *vm.Config 2726 snapshot.DataSets = copyDataSetsForVmClone(vm.DataSets) 2727 2728 ctx.Map.Put(snapshot) 2729 2730 quiesced := false 2731 if req.QuiesceSpec != nil { 2732 quiesced = true 2733 } 2734 2735 treeItem := types.VirtualMachineSnapshotTree{ 2736 Snapshot: snapshot.Self, 2737 Vm: snapshot.Vm, 2738 Name: req.Name, 2739 Description: req.Description, 2740 Id: atomic.AddInt32(&vm.sid, 1), 2741 CreateTime: time.Now(), 2742 State: vm.Runtime.PowerState, 2743 Quiesced: quiesced, 2744 BackupManifest: "", 2745 ReplaySupported: types.NewBool(false), 2746 } 2747 2748 cur := vm.Snapshot.CurrentSnapshot 2749 if cur != nil { 2750 parent := ctx.Map.Get(*cur).(*VirtualMachineSnapshot) 2751 parent.ChildSnapshot = append(parent.ChildSnapshot, snapshot.Self) 2752 2753 ss := findSnapshotInTree(vm.Snapshot.RootSnapshotList, *cur) 2754 ss.ChildSnapshotList = append(ss.ChildSnapshotList, treeItem) 2755 } else { 2756 changes = append(changes, types.PropertyChange{ 2757 Name: "snapshot.rootSnapshotList", 2758 Val: append(vm.Snapshot.RootSnapshotList, treeItem), 2759 }) 2760 changes = append(changes, types.PropertyChange{ 2761 Name: "rootSnapshot", 2762 Val: append(vm.RootSnapshot, treeItem.Snapshot), 2763 }) 2764 } 2765 2766 snapshot.createSnapshotFiles(ctx) 2767 2768 changes = append(changes, types.PropertyChange{Name: "snapshot.currentSnapshot", Val: snapshot.Self}) 2769 ctx.Update(vm, changes) 2770 2771 return snapshot.Self, nil 2772 }) 2773 2774 return &methods.CreateSnapshotEx_TaskBody{ 2775 Res: &types.CreateSnapshotEx_TaskResponse{ 2776 Returnval: task.Run(ctx), 2777 }, 2778 } 2779 } 2780 2781 func (vm *VirtualMachine) RevertToCurrentSnapshotTask(ctx *Context, req *types.RevertToCurrentSnapshot_Task) soap.HasFault { 2782 body := &methods.RevertToCurrentSnapshot_TaskBody{} 2783 2784 if vm.Snapshot == nil || vm.Snapshot.CurrentSnapshot == nil { 2785 body.Fault_ = Fault("snapshot not found", &types.NotFound{}) 2786 2787 return body 2788 } 2789 snapshot := ctx.Map.Get(*vm.Snapshot.CurrentSnapshot).(*VirtualMachineSnapshot) 2790 2791 task := CreateTask(vm, "revertSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2792 vm.DataSets = copyDataSetsForVmClone(snapshot.DataSets) 2793 return nil, nil 2794 }) 2795 2796 body.Res = &types.RevertToCurrentSnapshot_TaskResponse{ 2797 Returnval: task.Run(ctx), 2798 } 2799 2800 return body 2801 } 2802 2803 func (vm *VirtualMachine) RemoveAllSnapshotsTask(ctx *Context, req *types.RemoveAllSnapshots_Task) soap.HasFault { 2804 task := CreateTask(vm, "RemoveAllSnapshots", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2805 if vm.Snapshot == nil { 2806 return nil, nil 2807 } 2808 2809 refs := allSnapshotsInTree(vm.Snapshot.RootSnapshotList) 2810 2811 ctx.Update(vm, []types.PropertyChange{ 2812 {Name: "snapshot", Val: nil}, 2813 {Name: "rootSnapshot", Val: nil}, 2814 }) 2815 2816 for _, ref := range refs { 2817 ctx.Map.Get(ref).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx) 2818 ctx.Map.Remove(ctx, ref) 2819 } 2820 2821 return nil, nil 2822 }) 2823 2824 return &methods.RemoveAllSnapshots_TaskBody{ 2825 Res: &types.RemoveAllSnapshots_TaskResponse{ 2826 Returnval: task.Run(ctx), 2827 }, 2828 } 2829 } 2830 2831 func (vm *VirtualMachine) fcd(ctx *Context, ds types.ManagedObjectReference, id types.ID) *VStorageObject { 2832 m := ctx.Map.VStorageObjectManager() 2833 if ds.Value != "" { 2834 return m.objects[ds][id] 2835 } 2836 for _, set := range m.objects { 2837 for key, val := range set { 2838 if key == id { 2839 return val 2840 } 2841 } 2842 } 2843 return nil 2844 } 2845 2846 func (vm *VirtualMachine) AttachDiskTask(ctx *Context, req *types.AttachDisk_Task) soap.HasFault { 2847 task := CreateTask(vm, "attachDisk", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2848 fcd := vm.fcd(ctx, req.Datastore, req.DiskId) 2849 if fcd == nil { 2850 return nil, new(types.InvalidArgument) 2851 } 2852 2853 fcd.Config.ConsumerId = []types.ID{{Id: vm.Config.Uuid}} 2854 2855 // TODO: add device 2856 2857 return nil, nil 2858 }) 2859 2860 return &methods.AttachDisk_TaskBody{ 2861 Res: &types.AttachDisk_TaskResponse{ 2862 Returnval: task.Run(ctx), 2863 }, 2864 } 2865 } 2866 2867 func (vm *VirtualMachine) DetachDiskTask(ctx *Context, req *types.DetachDisk_Task) soap.HasFault { 2868 task := CreateTask(vm, "detachDisk", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2869 fcd := vm.fcd(ctx, types.ManagedObjectReference{}, req.DiskId) 2870 if fcd == nil { 2871 return nil, new(types.InvalidArgument) 2872 } 2873 2874 fcd.Config.ConsumerId = nil 2875 2876 // TODO: remove device 2877 2878 return nil, nil 2879 }) 2880 2881 return &methods.DetachDisk_TaskBody{ 2882 Res: &types.DetachDisk_TaskResponse{ 2883 Returnval: task.Run(ctx), 2884 }, 2885 } 2886 } 2887 2888 func (vm *VirtualMachine) PromoteDisksTask(ctx *Context, req *types.PromoteDisks_Task) soap.HasFault { 2889 task := CreateTask(vm, "promoteDisks", func(t *Task) (types.AnyType, types.BaseMethodFault) { 2890 devices := object.VirtualDeviceList(vm.Config.Hardware.Device) 2891 devices = devices.SelectByType((*types.VirtualDisk)(nil)) 2892 var cap int64 2893 2894 for i := range req.Disks { 2895 d := devices.FindByKey(req.Disks[i].Key) 2896 if d == nil { 2897 return nil, &types.InvalidArgument{InvalidProperty: "disks"} 2898 } 2899 2900 disk := d.(*types.VirtualDisk) 2901 2902 switch backing := disk.Backing.(type) { 2903 case *types.VirtualDiskFlatVer2BackingInfo: 2904 if backing.Parent != nil { 2905 cap += disk.CapacityInBytes 2906 if req.Unlink { 2907 backing.Parent = nil 2908 } 2909 } 2910 case *types.VirtualDiskSeSparseBackingInfo: 2911 if backing.Parent != nil { 2912 cap += disk.CapacityInBytes 2913 if req.Unlink { 2914 backing.Parent = nil 2915 } 2916 } 2917 case *types.VirtualDiskSparseVer2BackingInfo: 2918 if backing.Parent != nil { 2919 cap += disk.CapacityInBytes 2920 if req.Unlink { 2921 backing.Parent = nil 2922 } 2923 } 2924 } 2925 } 2926 2927 // Built-in default delay. `simulator.TaskDelay` can be used to add additional time 2928 // Translates to roughly 1s per 1GB 2929 sleep := time.Duration(cap/units.MB) * time.Millisecond 2930 if sleep > 0 { 2931 log.Printf("%s: sleep %s for %s", t.Info.DescriptionId, sleep, units.ByteSize(cap)) 2932 time.Sleep(sleep) 2933 } 2934 2935 return nil, nil 2936 }) 2937 2938 return &methods.PromoteDisks_TaskBody{ 2939 Res: &types.PromoteDisks_TaskResponse{ 2940 Returnval: task.Run(ctx), 2941 }, 2942 } 2943 } 2944 2945 func (vm *VirtualMachine) ShutdownGuest(ctx *Context, c *types.ShutdownGuest) soap.HasFault { 2946 r := &methods.ShutdownGuestBody{} 2947 2948 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOn { 2949 r.Fault_ = Fault("", &types.InvalidPowerState{ 2950 RequestedState: types.VirtualMachinePowerStatePoweredOn, 2951 ExistingState: vm.Runtime.PowerState, 2952 }) 2953 2954 return r 2955 } 2956 2957 event := vm.event(ctx) 2958 ctx.postEvent(&types.VmGuestShutdownEvent{VmEvent: event}) 2959 2960 _ = CreateTask(vm, "shutdownGuest", func(*Task) (types.AnyType, types.BaseMethodFault) { 2961 vm.svm.stop(ctx) 2962 2963 ctx.Update(vm, []types.PropertyChange{ 2964 {Name: "runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff}, 2965 {Name: "summary.runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff}, 2966 }) 2967 2968 ctx.postEvent(&types.VmPoweredOffEvent{VmEvent: event}) 2969 2970 return nil, nil 2971 }).Run(ctx) 2972 2973 r.Res = new(types.ShutdownGuestResponse) 2974 2975 return r 2976 } 2977 2978 func (vm *VirtualMachine) StandbyGuest(ctx *Context, c *types.StandbyGuest) soap.HasFault { 2979 r := &methods.StandbyGuestBody{} 2980 2981 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOn { 2982 r.Fault_ = Fault("", &types.InvalidPowerState{ 2983 RequestedState: types.VirtualMachinePowerStatePoweredOn, 2984 ExistingState: vm.Runtime.PowerState, 2985 }) 2986 2987 return r 2988 } 2989 2990 event := vm.event(ctx) 2991 ctx.postEvent(&types.VmGuestStandbyEvent{VmEvent: event}) 2992 2993 _ = CreateTask(vm, "standbyGuest", func(*Task) (types.AnyType, types.BaseMethodFault) { 2994 vm.svm.pause(ctx) 2995 2996 ctx.Update(vm, []types.PropertyChange{ 2997 {Name: "runtime.powerState", Val: types.VirtualMachinePowerStateSuspended}, 2998 {Name: "summary.runtime.powerState", Val: types.VirtualMachinePowerStateSuspended}, 2999 }) 3000 3001 ctx.postEvent(&types.VmSuspendedEvent{VmEvent: event}) 3002 3003 return nil, nil 3004 }).Run(ctx) 3005 3006 r.Res = new(types.StandbyGuestResponse) 3007 3008 return r 3009 } 3010 3011 func (vm *VirtualMachine) MarkAsTemplate(req *types.MarkAsTemplate) soap.HasFault { 3012 r := &methods.MarkAsTemplateBody{} 3013 3014 if vm.Config.Template { 3015 r.Fault_ = Fault("", new(types.NotSupported)) 3016 return r 3017 } 3018 3019 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOff { 3020 r.Fault_ = Fault("", &types.InvalidPowerState{ 3021 RequestedState: types.VirtualMachinePowerStatePoweredOff, 3022 ExistingState: vm.Runtime.PowerState, 3023 }) 3024 return r 3025 } 3026 3027 vm.Config.Template = true 3028 vm.Summary.Config.Template = true 3029 vm.ResourcePool = nil 3030 3031 r.Res = new(types.MarkAsTemplateResponse) 3032 3033 return r 3034 } 3035 3036 func (vm *VirtualMachine) MarkAsVirtualMachine(req *types.MarkAsVirtualMachine) soap.HasFault { 3037 r := &methods.MarkAsVirtualMachineBody{} 3038 3039 if !vm.Config.Template { 3040 r.Fault_ = Fault("", new(types.NotSupported)) 3041 return r 3042 } 3043 3044 if vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOff { 3045 r.Fault_ = Fault("", &types.InvalidPowerState{ 3046 RequestedState: types.VirtualMachinePowerStatePoweredOff, 3047 ExistingState: vm.Runtime.PowerState, 3048 }) 3049 return r 3050 } 3051 3052 vm.Config.Template = false 3053 vm.Summary.Config.Template = false 3054 vm.ResourcePool = &req.Pool 3055 if req.Host != nil { 3056 vm.Runtime.Host = req.Host 3057 } 3058 3059 r.Res = new(types.MarkAsVirtualMachineResponse) 3060 3061 return r 3062 } 3063 3064 func findSnapshotInTree(tree []types.VirtualMachineSnapshotTree, ref types.ManagedObjectReference) *types.VirtualMachineSnapshotTree { 3065 if tree == nil { 3066 return nil 3067 } 3068 3069 for i, ss := range tree { 3070 if ss.Snapshot == ref { 3071 return &tree[i] 3072 } 3073 3074 target := findSnapshotInTree(ss.ChildSnapshotList, ref) 3075 if target != nil { 3076 return target 3077 } 3078 } 3079 3080 return nil 3081 } 3082 3083 func findParentSnapshot(tree types.VirtualMachineSnapshotTree, ref types.ManagedObjectReference) *types.ManagedObjectReference { 3084 for _, ss := range tree.ChildSnapshotList { 3085 if ss.Snapshot == ref { 3086 return &tree.Snapshot 3087 } 3088 3089 res := findParentSnapshot(ss, ref) 3090 if res != nil { 3091 return res 3092 } 3093 } 3094 3095 return nil 3096 } 3097 3098 func findParentSnapshotInTree(tree []types.VirtualMachineSnapshotTree, ref types.ManagedObjectReference) *types.ManagedObjectReference { 3099 if tree == nil { 3100 return nil 3101 } 3102 3103 for _, ss := range tree { 3104 res := findParentSnapshot(ss, ref) 3105 if res != nil { 3106 return res 3107 } 3108 } 3109 3110 return nil 3111 } 3112 3113 func removeSnapshotInTree(tree []types.VirtualMachineSnapshotTree, ref types.ManagedObjectReference, removeChildren bool) []types.VirtualMachineSnapshotTree { 3114 if tree == nil { 3115 return tree 3116 } 3117 3118 var result []types.VirtualMachineSnapshotTree 3119 3120 for _, ss := range tree { 3121 if ss.Snapshot == ref { 3122 if !removeChildren { 3123 result = append(result, ss.ChildSnapshotList...) 3124 } 3125 } else { 3126 ss.ChildSnapshotList = removeSnapshotInTree(ss.ChildSnapshotList, ref, removeChildren) 3127 result = append(result, ss) 3128 } 3129 } 3130 3131 return result 3132 } 3133 3134 func allSnapshotsInTree(tree []types.VirtualMachineSnapshotTree) []types.ManagedObjectReference { 3135 var result []types.ManagedObjectReference 3136 3137 if tree == nil { 3138 return result 3139 } 3140 3141 for _, ss := range tree { 3142 result = append(result, ss.Snapshot) 3143 result = append(result, allSnapshotsInTree(ss.ChildSnapshotList)...) 3144 } 3145 3146 return result 3147 } 3148 3149 func changeTrackingSupported(spec *types.VirtualMachineConfigSpec) bool { 3150 for _, device := range spec.DeviceChange { 3151 if dev, ok := device.GetVirtualDeviceConfigSpec().Device.(*types.VirtualDisk); ok { 3152 switch dev.Backing.(type) { 3153 case *types.VirtualDiskFlatVer2BackingInfo: 3154 return true 3155 case *types.VirtualDiskSparseVer2BackingInfo: 3156 return true 3157 case *types.VirtualDiskRawDiskMappingVer1BackingInfo: 3158 return true 3159 case *types.VirtualDiskRawDiskVer2BackingInfo: 3160 return true 3161 default: 3162 return false 3163 } 3164 } 3165 } 3166 return false 3167 } 3168 3169 func (vm *VirtualMachine) updateLastModifiedAndChangeVersion(ctx *Context) { 3170 modified := time.Now() 3171 ctx.Update(vm, []types.PropertyChange{ 3172 { 3173 Name: "config.changeVersion", 3174 Val: fmt.Sprintf("%d", modified.UnixNano()), 3175 Op: types.PropertyChangeOpAssign, 3176 }, 3177 { 3178 Name: "config.modified", 3179 Val: modified, 3180 Op: types.PropertyChangeOpAssign, 3181 }, 3182 }) 3183 }