1/* 2Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package vm 18 19import ( 20 "context" 21 "flag" 22 "fmt" 23 "strings" 24 25 "github.com/vmware/govmomi/govc/cli" 26 "github.com/vmware/govmomi/govc/flags" 27 "github.com/vmware/govmomi/object" 28 "github.com/vmware/govmomi/property" 29 "github.com/vmware/govmomi/units" 30 "github.com/vmware/govmomi/vim25" 31 "github.com/vmware/govmomi/vim25/mo" 32 "github.com/vmware/govmomi/vim25/types" 33) 34 35type create struct { 36 *flags.ClientFlag 37 *flags.DatacenterFlag 38 *flags.DatastoreFlag 39 *flags.StoragePodFlag 40 *flags.ResourcePoolFlag 41 *flags.HostSystemFlag 42 *flags.NetworkFlag 43 *flags.FolderFlag 44 45 name string 46 memory int 47 cpus int 48 guestID string 49 link bool 50 on bool 51 force bool 52 controller string 53 annotation string 54 firmware string 55 56 iso string 57 isoDatastoreFlag *flags.DatastoreFlag 58 isoDatastore *object.Datastore 59 60 disk string 61 diskDatastoreFlag *flags.DatastoreFlag 62 diskDatastore *object.Datastore 63 64 // Only set if the disk argument is a byte size, which means the disk 65 // doesn't exist yet and should be created 66 diskByteSize int64 67 68 Client *vim25.Client 69 Datacenter *object.Datacenter 70 Datastore *object.Datastore 71 StoragePod *object.StoragePod 72 ResourcePool *object.ResourcePool 73 HostSystem *object.HostSystem 74 Folder *object.Folder 75} 76 77func init() { 78 cli.Register("vm.create", &create{}) 79} 80 81func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { 82 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 83 cmd.ClientFlag.Register(ctx, f) 84 85 cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) 86 cmd.DatacenterFlag.Register(ctx, f) 87 88 cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) 89 cmd.DatastoreFlag.Register(ctx, f) 90 91 cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx) 92 cmd.StoragePodFlag.Register(ctx, f) 93 94 cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) 95 cmd.ResourcePoolFlag.Register(ctx, f) 96 97 cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx) 98 cmd.HostSystemFlag.Register(ctx, f) 99 100 cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx) 101 cmd.NetworkFlag.Register(ctx, f) 102 103 cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) 104 cmd.FolderFlag.Register(ctx, f) 105 106 f.IntVar(&cmd.memory, "m", 1024, "Size in MB of memory") 107 f.IntVar(&cmd.cpus, "c", 1, "Number of CPUs") 108 f.StringVar(&cmd.guestID, "g", "otherGuest", "Guest OS ID") 109 f.BoolVar(&cmd.link, "link", true, "Link specified disk") 110 f.BoolVar(&cmd.on, "on", true, "Power on VM. Default is true if -disk argument is given.") 111 f.BoolVar(&cmd.force, "force", false, "Create VM if vmx already exists") 112 f.StringVar(&cmd.controller, "disk.controller", "scsi", "Disk controller type") 113 f.StringVar(&cmd.annotation, "annotation", "", "VM description") 114 115 firmwareTypes := []string{ 116 string(types.GuestOsDescriptorFirmwareTypeBios), 117 string(types.GuestOsDescriptorFirmwareTypeEfi), 118 } 119 120 f.StringVar(&cmd.firmware, "firmware", firmwareTypes[0], 121 fmt.Sprintf("Firmware type [%s]", strings.Join(firmwareTypes, "|"))) 122 123 f.StringVar(&cmd.iso, "iso", "", "ISO path") 124 cmd.isoDatastoreFlag, ctx = flags.NewCustomDatastoreFlag(ctx) 125 f.StringVar(&cmd.isoDatastoreFlag.Name, "iso-datastore", "", "Datastore for ISO file") 126 127 f.StringVar(&cmd.disk, "disk", "", "Disk path (to use existing) OR size (to create new, e.g. 20GB)") 128 cmd.diskDatastoreFlag, _ = flags.NewCustomDatastoreFlag(ctx) 129 f.StringVar(&cmd.diskDatastoreFlag.Name, "disk-datastore", "", "Datastore for disk file") 130} 131 132func (cmd *create) Process(ctx context.Context) error { 133 if err := cmd.ClientFlag.Process(ctx); err != nil { 134 return err 135 } 136 if err := cmd.DatacenterFlag.Process(ctx); err != nil { 137 return err 138 } 139 if err := cmd.DatastoreFlag.Process(ctx); err != nil { 140 return err 141 } 142 if err := cmd.StoragePodFlag.Process(ctx); err != nil { 143 return err 144 } 145 if err := cmd.ResourcePoolFlag.Process(ctx); err != nil { 146 return err 147 } 148 if err := cmd.HostSystemFlag.Process(ctx); err != nil { 149 return err 150 } 151 if err := cmd.NetworkFlag.Process(ctx); err != nil { 152 return err 153 } 154 if err := cmd.FolderFlag.Process(ctx); err != nil { 155 return err 156 } 157 158 // Default iso/disk datastores to the VM's datastore 159 if cmd.isoDatastoreFlag.Name == "" { 160 cmd.isoDatastoreFlag = cmd.DatastoreFlag 161 } 162 if cmd.diskDatastoreFlag.Name == "" { 163 cmd.diskDatastoreFlag = cmd.DatastoreFlag 164 } 165 166 return nil 167} 168 169func (cmd *create) Usage() string { 170 return "NAME" 171} 172 173func (cmd *create) Description() string { 174 return `Create VM. 175 176For a list of possible '-g' IDs, see: 177http://pubs.vmware.com/vsphere-6-5/topic/com.vmware.wssdk.apiref.doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html 178 179Examples: 180 govc vm.create vm-name 181 govc vm.create -m 2048 -c 2 -g freebsd64Guest -net.adapter vmxnet3 -disk.controller pvscsi vm-name` 182} 183 184func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { 185 var err error 186 187 if len(f.Args()) != 1 { 188 return flag.ErrHelp 189 } 190 191 cmd.name = f.Arg(0) 192 if cmd.name == "" { 193 return flag.ErrHelp 194 } 195 196 cmd.Client, err = cmd.ClientFlag.Client() 197 if err != nil { 198 return err 199 } 200 201 cmd.Datacenter, err = cmd.DatacenterFlag.Datacenter() 202 if err != nil { 203 return err 204 } 205 206 if cmd.StoragePodFlag.Isset() { 207 cmd.StoragePod, err = cmd.StoragePodFlag.StoragePod() 208 if err != nil { 209 return err 210 } 211 } else { 212 cmd.Datastore, err = cmd.DatastoreFlag.Datastore() 213 if err != nil { 214 return err 215 } 216 } 217 218 cmd.HostSystem, err = cmd.HostSystemFlag.HostSystemIfSpecified() 219 if err != nil { 220 return err 221 } 222 223 if cmd.HostSystem != nil { 224 if cmd.ResourcePool, err = cmd.HostSystem.ResourcePool(ctx); err != nil { 225 return err 226 } 227 } else { 228 // -host is optional 229 if cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool(); err != nil { 230 return err 231 } 232 } 233 234 if cmd.Folder, err = cmd.FolderFlag.Folder(); err != nil { 235 return err 236 } 237 238 // Verify ISO exists 239 if cmd.iso != "" { 240 _, err = cmd.isoDatastoreFlag.Stat(ctx, cmd.iso) 241 if err != nil { 242 return err 243 } 244 245 cmd.isoDatastore, err = cmd.isoDatastoreFlag.Datastore() 246 if err != nil { 247 return err 248 } 249 } 250 251 // Verify disk exists 252 if cmd.disk != "" { 253 var b units.ByteSize 254 255 // If disk can be parsed as byte units, don't stat 256 err = b.Set(cmd.disk) 257 if err == nil { 258 cmd.diskByteSize = int64(b) 259 } else { 260 _, err = cmd.diskDatastoreFlag.Stat(ctx, cmd.disk) 261 if err != nil { 262 return err 263 } 264 265 cmd.diskDatastore, err = cmd.diskDatastoreFlag.Datastore() 266 if err != nil { 267 return err 268 } 269 } 270 } 271 272 task, err := cmd.createVM(ctx) 273 if err != nil { 274 return err 275 } 276 277 info, err := task.WaitForResult(ctx, nil) 278 if err != nil { 279 return err 280 } 281 282 vm := object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)) 283 284 if cmd.on { 285 task, err := vm.PowerOn(ctx) 286 if err != nil { 287 return err 288 } 289 290 _, err = task.WaitForResult(ctx, nil) 291 if err != nil { 292 return err 293 } 294 } 295 296 return nil 297} 298 299func (cmd *create) createVM(ctx context.Context) (*object.Task, error) { 300 var devices object.VirtualDeviceList 301 var err error 302 303 spec := &types.VirtualMachineConfigSpec{ 304 Name: cmd.name, 305 GuestId: cmd.guestID, 306 NumCPUs: int32(cmd.cpus), 307 MemoryMB: int64(cmd.memory), 308 Annotation: cmd.annotation, 309 Firmware: cmd.firmware, 310 } 311 312 devices, err = cmd.addStorage(nil) 313 if err != nil { 314 return nil, err 315 } 316 317 devices, err = cmd.addNetwork(devices) 318 if err != nil { 319 return nil, err 320 } 321 322 deviceChange, err := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) 323 if err != nil { 324 return nil, err 325 } 326 327 spec.DeviceChange = deviceChange 328 329 var datastore *object.Datastore 330 331 // If storage pod is specified, collect placement recommendations 332 if cmd.StoragePod != nil { 333 datastore, err = cmd.recommendDatastore(ctx, spec) 334 if err != nil { 335 return nil, err 336 } 337 } else { 338 datastore = cmd.Datastore 339 } 340 341 if !cmd.force { 342 vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name) 343 344 _, err := datastore.Stat(ctx, vmxPath) 345 if err == nil { 346 dsPath := cmd.Datastore.Path(vmxPath) 347 return nil, fmt.Errorf("File %s already exists", dsPath) 348 } 349 } 350 351 folder := cmd.Folder 352 353 spec.Files = &types.VirtualMachineFileInfo{ 354 VmPathName: fmt.Sprintf("[%s]", datastore.Name()), 355 } 356 357 return folder.CreateVM(ctx, *spec, cmd.ResourcePool, cmd.HostSystem) 358} 359 360func (cmd *create) addStorage(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) { 361 if cmd.controller != "ide" { 362 if cmd.controller == "nvme" { 363 nvme, err := devices.CreateNVMEController() 364 if err != nil { 365 return nil, err 366 } 367 368 devices = append(devices, nvme) 369 cmd.controller = devices.Name(nvme) 370 } else { 371 scsi, err := devices.CreateSCSIController(cmd.controller) 372 if err != nil { 373 return nil, err 374 } 375 376 devices = append(devices, scsi) 377 cmd.controller = devices.Name(scsi) 378 } 379 } 380 381 // If controller is specified to be IDE or if an ISO is specified, add IDE controller. 382 if cmd.controller == "ide" || cmd.iso != "" { 383 ide, err := devices.CreateIDEController() 384 if err != nil { 385 return nil, err 386 } 387 388 devices = append(devices, ide) 389 } 390 391 if cmd.diskByteSize != 0 { 392 controller, err := devices.FindDiskController(cmd.controller) 393 if err != nil { 394 return nil, err 395 } 396 397 disk := &types.VirtualDisk{ 398 VirtualDevice: types.VirtualDevice{ 399 Key: devices.NewKey(), 400 Backing: &types.VirtualDiskFlatVer2BackingInfo{ 401 DiskMode: string(types.VirtualDiskModePersistent), 402 ThinProvisioned: types.NewBool(true), 403 }, 404 }, 405 CapacityInKB: cmd.diskByteSize / 1024, 406 } 407 408 devices.AssignController(disk, controller) 409 devices = append(devices, disk) 410 } else if cmd.disk != "" { 411 controller, err := devices.FindDiskController(cmd.controller) 412 if err != nil { 413 return nil, err 414 } 415 416 ds := cmd.diskDatastore.Reference() 417 path := cmd.diskDatastore.Path(cmd.disk) 418 disk := devices.CreateDisk(controller, ds, path) 419 420 if cmd.link { 421 disk = devices.ChildDisk(disk) 422 } 423 424 devices = append(devices, disk) 425 } 426 427 if cmd.iso != "" { 428 ide, err := devices.FindIDEController("") 429 if err != nil { 430 return nil, err 431 } 432 433 cdrom, err := devices.CreateCdrom(ide) 434 if err != nil { 435 return nil, err 436 } 437 438 cdrom = devices.InsertIso(cdrom, cmd.isoDatastore.Path(cmd.iso)) 439 devices = append(devices, cdrom) 440 } 441 442 return devices, nil 443} 444 445func (cmd *create) addNetwork(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) { 446 netdev, err := cmd.NetworkFlag.Device() 447 if err != nil { 448 return nil, err 449 } 450 451 devices = append(devices, netdev) 452 return devices, nil 453} 454 455func (cmd *create) recommendDatastore(ctx context.Context, spec *types.VirtualMachineConfigSpec) (*object.Datastore, error) { 456 sp := cmd.StoragePod.Reference() 457 458 // Build pod selection spec from config spec 459 podSelectionSpec := types.StorageDrsPodSelectionSpec{ 460 StoragePod: &sp, 461 } 462 463 // Keep list of disks that need to be placed 464 var disks []*types.VirtualDisk 465 466 // Collect disks eligible for placement 467 for _, deviceConfigSpec := range spec.DeviceChange { 468 s := deviceConfigSpec.GetVirtualDeviceConfigSpec() 469 if s.Operation != types.VirtualDeviceConfigSpecOperationAdd { 470 continue 471 } 472 473 if s.FileOperation != types.VirtualDeviceConfigSpecFileOperationCreate { 474 continue 475 } 476 477 d, ok := s.Device.(*types.VirtualDisk) 478 if !ok { 479 continue 480 } 481 482 podConfigForPlacement := types.VmPodConfigForPlacement{ 483 StoragePod: sp, 484 Disk: []types.PodDiskLocator{ 485 { 486 DiskId: d.Key, 487 DiskBackingInfo: d.Backing, 488 }, 489 }, 490 } 491 492 podSelectionSpec.InitialVmConfig = append(podSelectionSpec.InitialVmConfig, podConfigForPlacement) 493 disks = append(disks, d) 494 } 495 496 sps := types.StoragePlacementSpec{ 497 Type: string(types.StoragePlacementSpecPlacementTypeCreate), 498 ResourcePool: types.NewReference(cmd.ResourcePool.Reference()), 499 PodSelectionSpec: podSelectionSpec, 500 ConfigSpec: spec, 501 } 502 503 srm := object.NewStorageResourceManager(cmd.Client) 504 result, err := srm.RecommendDatastores(ctx, sps) 505 if err != nil { 506 return nil, err 507 } 508 509 // Use result to pin disks to recommended datastores 510 recs := result.Recommendations 511 if len(recs) == 0 { 512 return nil, fmt.Errorf("no recommendations") 513 } 514 515 ds := recs[0].Action[0].(*types.StoragePlacementAction).Destination 516 517 var mds mo.Datastore 518 err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, ds, []string{"name"}, &mds) 519 if err != nil { 520 return nil, err 521 } 522 523 datastore := object.NewDatastore(cmd.Client, ds) 524 datastore.InventoryPath = mds.Name 525 526 // Apply recommendation to eligible disks 527 for _, disk := range disks { 528 backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 529 backing.Datastore = &ds 530 } 531 532 return datastore, nil 533} 534