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