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