github.com/vmware/govmomi@v0.43.0/govc/vm/create.go (about) 1 /* 2 Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vm 18 19 import ( 20 "context" 21 "flag" 22 "fmt" 23 "io" 24 "strings" 25 "text/tabwriter" 26 27 "github.com/vmware/govmomi/find" 28 "github.com/vmware/govmomi/govc/cli" 29 "github.com/vmware/govmomi/govc/flags" 30 "github.com/vmware/govmomi/object" 31 "github.com/vmware/govmomi/property" 32 "github.com/vmware/govmomi/units" 33 "github.com/vmware/govmomi/vim25" 34 "github.com/vmware/govmomi/vim25/mo" 35 "github.com/vmware/govmomi/vim25/types" 36 ) 37 38 var ( 39 FirmwareTypes = types.GuestOsDescriptorFirmwareType("").Strings() 40 41 FirmwareUsage = fmt.Sprintf("Firmware type [%s]", strings.Join(FirmwareTypes, "|")) 42 ) 43 44 type create struct { 45 *flags.ClientFlag 46 *flags.ClusterFlag 47 *flags.DatacenterFlag 48 *flags.DatastoreFlag 49 *flags.StoragePodFlag 50 *flags.ResourcePoolFlag 51 *flags.HostSystemFlag 52 *flags.NetworkFlag 53 *flags.FolderFlag 54 *flags.StorageProfileFlag 55 56 name string 57 memory int 58 cpus int 59 guestID string 60 link bool 61 on bool 62 force bool 63 controller string 64 eager bool 65 thick bool 66 annotation string 67 firmware string 68 version string 69 place bool 70 71 iso string 72 isoDatastoreFlag *flags.DatastoreFlag 73 74 disk string 75 diskDatastoreFlag *flags.DatastoreFlag 76 diskDatastore *object.Datastore 77 78 // Only set if the disk argument is a byte size, which means the disk 79 // doesn't exist yet and should be created 80 diskByteSize int64 81 82 Client *vim25.Client 83 Cluster *object.ClusterComputeResource 84 Datacenter *object.Datacenter 85 Datastore *object.Datastore 86 StoragePod *object.StoragePod 87 ResourcePool *object.ResourcePool 88 HostSystem *object.HostSystem 89 Folder *object.Folder 90 } 91 92 func init() { 93 cli.Register("vm.create", &create{}) 94 } 95 96 func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { 97 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 98 cmd.ClientFlag.Register(ctx, f) 99 100 cmd.ClusterFlag, ctx = flags.NewClusterFlag(ctx) 101 cmd.ClusterFlag.RegisterPlacement(ctx, f) 102 103 cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) 104 cmd.DatacenterFlag.Register(ctx, f) 105 106 cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) 107 cmd.DatastoreFlag.Register(ctx, f) 108 109 cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx) 110 cmd.StoragePodFlag.Register(ctx, f) 111 112 cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) 113 cmd.ResourcePoolFlag.Register(ctx, f) 114 115 cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx) 116 cmd.HostSystemFlag.Register(ctx, f) 117 118 cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx) 119 cmd.NetworkFlag.Register(ctx, f) 120 121 cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) 122 cmd.FolderFlag.Register(ctx, f) 123 124 cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) 125 cmd.StorageProfileFlag.Register(ctx, f) 126 127 f.IntVar(&cmd.memory, "m", 1024, "Size in MB of memory") 128 f.IntVar(&cmd.cpus, "c", 1, "Number of CPUs") 129 f.StringVar(&cmd.guestID, "g", "otherGuest", "Guest OS ID") 130 f.BoolVar(&cmd.link, "link", true, "Link specified disk") 131 f.BoolVar(&cmd.on, "on", true, "Power on VM") 132 f.BoolVar(&cmd.force, "force", false, "Create VM if vmx already exists") 133 f.StringVar(&cmd.controller, "disk.controller", "scsi", "Disk controller type") 134 f.BoolVar(&cmd.eager, "disk.eager", false, "Eagerly scrub new disk") 135 f.BoolVar(&cmd.thick, "disk.thick", false, "Thick provision new disk") 136 f.StringVar(&cmd.annotation, "annotation", "", "VM description") 137 f.StringVar(&cmd.firmware, "firmware", FirmwareTypes[0], FirmwareUsage) 138 if cli.ShowUnreleased() { 139 f.BoolVar(&cmd.place, "place", false, "Place VM without creating") 140 } 141 142 esxiVersions := types.GetESXiVersions() 143 esxiVersionStrings := make([]string, len(esxiVersions)) 144 for i := range esxiVersions { 145 esxiVersionStrings[i] = esxiVersions[i].String() 146 } 147 f.StringVar(&cmd.version, "version", "", 148 fmt.Sprintf("ESXi hardware version [%s]", strings.Join(esxiVersionStrings, "|"))) 149 150 f.StringVar(&cmd.iso, "iso", "", "ISO path") 151 cmd.isoDatastoreFlag, ctx = flags.NewCustomDatastoreFlag(ctx) 152 f.StringVar(&cmd.isoDatastoreFlag.Name, "iso-datastore", "", "Datastore for ISO file") 153 154 f.StringVar(&cmd.disk, "disk", "", "Disk path (to use existing) OR size (to create new, e.g. 20GB)") 155 cmd.diskDatastoreFlag, _ = flags.NewCustomDatastoreFlag(ctx) 156 f.StringVar(&cmd.diskDatastoreFlag.Name, "disk-datastore", "", "Datastore for disk file") 157 } 158 159 func (cmd *create) Process(ctx context.Context) error { 160 if err := cmd.ClientFlag.Process(ctx); err != nil { 161 return err 162 } 163 if err := cmd.ClusterFlag.Process(ctx); err != nil { 164 return err 165 } 166 if err := cmd.DatacenterFlag.Process(ctx); err != nil { 167 return err 168 } 169 if err := cmd.DatastoreFlag.Process(ctx); err != nil { 170 return err 171 } 172 if err := cmd.StoragePodFlag.Process(ctx); err != nil { 173 return err 174 } 175 if err := cmd.ResourcePoolFlag.Process(ctx); err != nil { 176 return err 177 } 178 if err := cmd.HostSystemFlag.Process(ctx); err != nil { 179 return err 180 } 181 if err := cmd.NetworkFlag.Process(ctx); err != nil { 182 return err 183 } 184 if err := cmd.FolderFlag.Process(ctx); err != nil { 185 return err 186 } 187 if err := cmd.StorageProfileFlag.Process(ctx); err != nil { 188 return err 189 } 190 191 // Default iso/disk datastores to the VM's datastore 192 if cmd.isoDatastoreFlag.Name == "" { 193 cmd.isoDatastoreFlag = cmd.DatastoreFlag 194 } 195 if cmd.diskDatastoreFlag.Name == "" { 196 cmd.diskDatastoreFlag = cmd.DatastoreFlag 197 } 198 199 return nil 200 } 201 202 func (cmd *create) Usage() string { 203 return "NAME" 204 } 205 206 func (cmd *create) Description() string { 207 return `Create VM. 208 209 For a list of possible '-g' IDs, use 'govc vm.option.info' or see: 210 https://code.vmware.com/apis/358/vsphere/doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html 211 212 Examples: 213 govc vm.create -on=false vm-name 214 govc vm.create -iso library:/boot/linux/ubuntu.iso vm-name # Content Library ISO 215 govc vm.create -cluster cluster1 vm-name # use compute cluster placement 216 govc vm.create -datastore-cluster dscluster vm-name # use datastore cluster placement 217 govc vm.create -m 2048 -c 2 -g freebsd64Guest -net.adapter vmxnet3 -disk.controller pvscsi vm-name` 218 } 219 220 func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { 221 var err error 222 223 if len(f.Args()) != 1 { 224 return flag.ErrHelp 225 } 226 227 cmd.name = f.Arg(0) 228 if cmd.name == "" { 229 return flag.ErrHelp 230 } 231 232 cmd.Client, err = cmd.ClientFlag.Client() 233 if err != nil { 234 return err 235 } 236 237 cmd.Cluster, err = cmd.ClusterFlag.ClusterIfSpecified() 238 if err != nil { 239 return err 240 } 241 242 cmd.Datacenter, err = cmd.DatacenterFlag.Datacenter() 243 if err != nil { 244 return err 245 } 246 247 if cmd.StoragePodFlag.Isset() { 248 cmd.StoragePod, err = cmd.StoragePodFlag.StoragePod() 249 if err != nil { 250 return err 251 } 252 } else if cmd.Cluster == nil { 253 cmd.Datastore, err = cmd.DatastoreFlag.Datastore() 254 if err != nil { 255 return err 256 } 257 } 258 259 cmd.HostSystem, err = cmd.HostSystemFlag.HostSystemIfSpecified() 260 if err != nil { 261 return err 262 } 263 264 if cmd.HostSystem != nil { 265 if cmd.ResourcePool, err = cmd.HostSystem.ResourcePool(ctx); err != nil { 266 return err 267 } 268 } else { 269 if cmd.Cluster == nil { 270 // -host is optional 271 if cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool(); err != nil { 272 return err 273 } 274 } else { 275 if cmd.ResourcePool, err = cmd.Cluster.ResourcePool(ctx); err != nil { 276 return err 277 } 278 } 279 } 280 281 if cmd.Folder, err = cmd.FolderFlag.Folder(); err != nil { 282 return err 283 } 284 285 // Verify ISO exists 286 if cmd.iso != "" { 287 iso, err := cmd.isoDatastoreFlag.FileBacking(ctx, cmd.iso, true) 288 if err != nil { 289 return err 290 } 291 cmd.iso = iso 292 } 293 294 // Verify disk exists 295 if cmd.disk != "" { 296 var b units.ByteSize 297 298 // If disk can be parsed as byte units, don't stat 299 err = b.Set(cmd.disk) 300 if err == nil { 301 cmd.diskByteSize = int64(b) 302 } else { 303 _, err = cmd.diskDatastoreFlag.Stat(ctx, cmd.disk) 304 if err != nil { 305 return err 306 } 307 308 cmd.diskDatastore, err = cmd.diskDatastoreFlag.Datastore() 309 if err != nil { 310 return err 311 } 312 } 313 } 314 315 task, err := cmd.createVM(ctx) 316 if err != nil { 317 return err 318 } 319 if cmd.place || cmd.Spec { 320 return nil 321 } 322 info, err := task.WaitForResult(ctx, nil) 323 if err != nil { 324 return err 325 } 326 327 vm := object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)) 328 329 if cmd.on { 330 task, err := vm.PowerOn(ctx) 331 if err != nil { 332 return err 333 } 334 335 _, err = task.WaitForResult(ctx, nil) 336 if err != nil { 337 return err 338 } 339 } 340 341 return nil 342 } 343 344 type place struct { 345 Spec types.PlacementSpec `json:"spec"` 346 Recommendations []types.ClusterRecommendation `json:"recommendations"` 347 348 ctx context.Context 349 cmd *create 350 } 351 352 func (p *place) Dump() interface{} { 353 return p.Recommendations 354 } 355 356 func (p *place) action(w io.Writer, r types.ClusterRecommendation, a *types.PlacementAction) error { 357 spec := a.RelocateSpec 358 if spec == nil { 359 return nil 360 } 361 362 fields := []struct { 363 name string 364 moid *types.ManagedObjectReference 365 }{ 366 {"Target", r.Target}, 367 {" Folder", spec.Folder}, 368 {" Datastore", spec.Datastore}, 369 {" Pool", spec.Pool}, 370 {" Host", spec.Host}, 371 } 372 373 for _, f := range fields { 374 if f.moid == nil { 375 continue 376 } 377 path, err := find.InventoryPath(p.ctx, p.cmd.Client, *f.moid) 378 if err != nil { 379 return err 380 } 381 fmt.Fprintf(w, "%s:\t%s\n", f.name, path) 382 } 383 384 return nil 385 } 386 387 func (p *place) Write(w io.Writer) error { 388 tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0) 389 390 for _, r := range p.Recommendations { 391 for _, a := range r.Action { 392 p.action(tw, r, a.(*types.PlacementAction)) 393 } 394 } 395 396 return tw.Flush() 397 } 398 399 func (cmd *create) createVM(ctx context.Context) (*object.Task, error) { 400 var devices object.VirtualDeviceList 401 var err error 402 403 if cmd.version != "" { 404 if v, _ := types.ParseESXiVersion(cmd.version); v.IsValid() { 405 cmd.version = v.HardwareVersion().String() 406 } else if v, _ := types.ParseHardwareVersion(cmd.version); v.IsValid() { 407 cmd.version = v.String() 408 } else { 409 return nil, fmt.Errorf("invalid version: %s", cmd.version) 410 } 411 } 412 413 spec := &types.VirtualMachineConfigSpec{ 414 Name: cmd.name, 415 GuestId: cmd.guestID, 416 NumCPUs: int32(cmd.cpus), 417 MemoryMB: int64(cmd.memory), 418 Annotation: cmd.annotation, 419 Firmware: cmd.firmware, 420 Version: cmd.version, 421 } 422 423 spec.VmProfile, err = cmd.StorageProfileSpec(ctx) 424 if err != nil { 425 return nil, err 426 } 427 428 devices, err = cmd.addStorage(nil) 429 if err != nil { 430 return nil, err 431 } 432 433 devices, err = cmd.addNetwork(devices) 434 if err != nil { 435 return nil, err 436 } 437 438 deviceChange, err := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) 439 if err != nil { 440 return nil, err 441 } 442 443 spec.DeviceChange = deviceChange 444 445 var datastore *object.Datastore 446 447 // If storage pod is specified, collect placement recommendations 448 if cmd.StoragePod != nil { 449 datastore, err = cmd.recommendDatastore(ctx, spec) 450 if err != nil { 451 return nil, err 452 } 453 } else if cmd.Datastore != nil { 454 datastore = cmd.Datastore 455 } else if cmd.Cluster != nil { 456 pspec := types.PlacementSpec{ 457 PlacementType: string(types.PlacementSpecPlacementTypeCreate), 458 ConfigSpec: spec, 459 } 460 result, err := cmd.Cluster.PlaceVm(ctx, pspec) 461 if err != nil { 462 return nil, err 463 } 464 465 recs := result.Recommendations 466 if cmd.place { 467 return nil, cmd.WriteResult(&place{pspec, recs, ctx, cmd}) 468 } 469 if len(recs) == 0 { 470 return nil, fmt.Errorf("no cluster recommendations") 471 } 472 473 rspec := *recs[0].Action[0].(*types.PlacementAction).RelocateSpec 474 if rspec.Datastore != nil { 475 datastore = object.NewDatastore(cmd.Client, *rspec.Datastore) 476 datastore.InventoryPath, _ = datastore.ObjectName(ctx) 477 cmd.Datastore = datastore 478 } 479 if rspec.Host != nil { 480 cmd.HostSystem = object.NewHostSystem(cmd.Client, *rspec.Host) 481 } 482 if rspec.Pool != nil { 483 cmd.ResourcePool = object.NewResourcePool(cmd.Client, *rspec.Pool) 484 } 485 } else { 486 return nil, fmt.Errorf("please provide either a cluster, datastore or datastore-cluster") 487 } 488 489 if !cmd.force && !cmd.Spec { 490 vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name) 491 492 _, err := datastore.Stat(ctx, vmxPath) 493 if err == nil { 494 dsPath := cmd.Datastore.Path(vmxPath) 495 return nil, fmt.Errorf("file %s already exists", dsPath) 496 } 497 } 498 499 folder := cmd.Folder 500 501 spec.Files = &types.VirtualMachineFileInfo{ 502 VmPathName: fmt.Sprintf("[%s]", datastore.Name()), 503 } 504 505 if cmd.Spec { 506 return nil, cmd.WriteAny(spec) 507 } 508 509 return folder.CreateVM(ctx, *spec, cmd.ResourcePool, cmd.HostSystem) 510 } 511 512 func (cmd *create) addStorage(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) { 513 if cmd.controller != "ide" { 514 if cmd.controller == "nvme" { 515 nvme, err := devices.CreateNVMEController() 516 if err != nil { 517 return nil, err 518 } 519 520 devices = append(devices, nvme) 521 cmd.controller = devices.Name(nvme) 522 } else if cmd.controller == "sata" { 523 sata, err := devices.CreateSATAController() 524 if err != nil { 525 return nil, err 526 } 527 528 devices = append(devices, sata) 529 cmd.controller = devices.Name(sata) 530 } else { 531 scsi, err := devices.CreateSCSIController(cmd.controller) 532 if err != nil { 533 return nil, err 534 } 535 536 devices = append(devices, scsi) 537 cmd.controller = devices.Name(scsi) 538 } 539 } 540 541 // If controller is specified to be IDE or if an ISO is specified, add IDE controller. 542 if cmd.controller == "ide" || cmd.iso != "" { 543 ide, err := devices.CreateIDEController() 544 if err != nil { 545 return nil, err 546 } 547 548 devices = append(devices, ide) 549 } 550 551 if cmd.diskByteSize != 0 { 552 controller, err := devices.FindDiskController(cmd.controller) 553 if err != nil { 554 return nil, err 555 } 556 557 backing := &types.VirtualDiskFlatVer2BackingInfo{ 558 DiskMode: string(types.VirtualDiskModePersistent), 559 ThinProvisioned: types.NewBool(!cmd.thick), 560 } 561 if cmd.thick { 562 backing.EagerlyScrub = &cmd.eager 563 } 564 disk := &types.VirtualDisk{ 565 VirtualDevice: types.VirtualDevice{ 566 Key: devices.NewKey(), 567 Backing: backing, 568 }, 569 CapacityInKB: cmd.diskByteSize / 1024, 570 } 571 572 devices.AssignController(disk, controller) 573 devices = append(devices, disk) 574 } else if cmd.disk != "" { 575 controller, err := devices.FindDiskController(cmd.controller) 576 if err != nil { 577 return nil, err 578 } 579 580 ds := cmd.diskDatastore.Reference() 581 path := cmd.diskDatastore.Path(cmd.disk) 582 disk := devices.CreateDisk(controller, ds, path) 583 584 if cmd.link { 585 disk = devices.ChildDisk(disk) 586 } 587 588 devices = append(devices, disk) 589 } 590 591 if cmd.iso != "" { 592 ide, err := devices.FindIDEController("") 593 if err != nil { 594 return nil, err 595 } 596 597 cdrom, err := devices.CreateCdrom(ide) 598 if err != nil { 599 return nil, err 600 } 601 602 cdrom = devices.InsertIso(cdrom, cmd.iso) 603 devices = append(devices, cdrom) 604 } 605 606 return devices, nil 607 } 608 609 func (cmd *create) addNetwork(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) { 610 netdev, err := cmd.NetworkFlag.Device() 611 if err != nil { 612 return nil, err 613 } 614 615 devices = append(devices, netdev) 616 return devices, nil 617 } 618 619 func (cmd *create) recommendDatastore(ctx context.Context, spec *types.VirtualMachineConfigSpec) (*object.Datastore, error) { 620 sp := cmd.StoragePod.Reference() 621 622 // Build pod selection spec from config spec 623 podSelectionSpec := types.StorageDrsPodSelectionSpec{ 624 StoragePod: &sp, 625 } 626 627 // Keep list of disks that need to be placed 628 var disks []*types.VirtualDisk 629 630 // Collect disks eligible for placement 631 for _, deviceConfigSpec := range spec.DeviceChange { 632 s := deviceConfigSpec.GetVirtualDeviceConfigSpec() 633 if s.Operation != types.VirtualDeviceConfigSpecOperationAdd { 634 continue 635 } 636 637 if s.FileOperation != types.VirtualDeviceConfigSpecFileOperationCreate { 638 continue 639 } 640 641 d, ok := s.Device.(*types.VirtualDisk) 642 if !ok { 643 continue 644 } 645 646 podConfigForPlacement := types.VmPodConfigForPlacement{ 647 StoragePod: sp, 648 Disk: []types.PodDiskLocator{ 649 { 650 DiskId: d.Key, 651 DiskBackingInfo: d.Backing, 652 }, 653 }, 654 } 655 656 podSelectionSpec.InitialVmConfig = append(podSelectionSpec.InitialVmConfig, podConfigForPlacement) 657 disks = append(disks, d) 658 } 659 660 sps := types.StoragePlacementSpec{ 661 Type: string(types.StoragePlacementSpecPlacementTypeCreate), 662 ResourcePool: types.NewReference(cmd.ResourcePool.Reference()), 663 PodSelectionSpec: podSelectionSpec, 664 ConfigSpec: spec, 665 } 666 667 srm := object.NewStorageResourceManager(cmd.Client) 668 result, err := srm.RecommendDatastores(ctx, sps) 669 if err != nil { 670 return nil, err 671 } 672 673 // Use result to pin disks to recommended datastores 674 recs := result.Recommendations 675 if len(recs) == 0 { 676 return nil, fmt.Errorf("no datastore-cluster recommendations") 677 } 678 679 ds := recs[0].Action[0].(*types.StoragePlacementAction).Destination 680 681 var mds mo.Datastore 682 err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, ds, []string{"name"}, &mds) 683 if err != nil { 684 return nil, err 685 } 686 687 datastore := object.NewDatastore(cmd.Client, ds) 688 datastore.InventoryPath = mds.Name 689 690 // Apply recommendation to eligible disks 691 for _, disk := range disks { 692 backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 693 backing.Datastore = &ds 694 } 695 696 return datastore, nil 697 }