1/*
2Copyright (c) 2015-2017 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 object
18
19import (
20	"errors"
21	"fmt"
22	"path/filepath"
23	"reflect"
24	"regexp"
25	"sort"
26	"strings"
27
28	"github.com/vmware/govmomi/vim25/types"
29)
30
31// Type values for use in BootOrder
32const (
33	DeviceTypeNone     = "-"
34	DeviceTypeCdrom    = "cdrom"
35	DeviceTypeDisk     = "disk"
36	DeviceTypeEthernet = "ethernet"
37	DeviceTypeFloppy   = "floppy"
38)
39
40// VirtualDeviceList provides helper methods for working with a list of virtual devices.
41type VirtualDeviceList []types.BaseVirtualDevice
42
43// SCSIControllerTypes are used for adding a new SCSI controller to a VM.
44func SCSIControllerTypes() VirtualDeviceList {
45	// Return a mutable list of SCSI controller types, initialized with defaults.
46	return VirtualDeviceList([]types.BaseVirtualDevice{
47		&types.VirtualLsiLogicController{},
48		&types.VirtualBusLogicController{},
49		&types.ParaVirtualSCSIController{},
50		&types.VirtualLsiLogicSASController{},
51	}).Select(func(device types.BaseVirtualDevice) bool {
52		c := device.(types.BaseVirtualSCSIController).GetVirtualSCSIController()
53		c.SharedBus = types.VirtualSCSISharingNoSharing
54		c.BusNumber = -1
55		return true
56	})
57}
58
59// EthernetCardTypes are used for adding a new ethernet card to a VM.
60func EthernetCardTypes() VirtualDeviceList {
61	return VirtualDeviceList([]types.BaseVirtualDevice{
62		&types.VirtualE1000{},
63		&types.VirtualE1000e{},
64		&types.VirtualVmxnet3{},
65	}).Select(func(device types.BaseVirtualDevice) bool {
66		c := device.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard()
67		c.GetVirtualDevice().Key = -1
68		return true
69	})
70}
71
72// Select returns a new list containing all elements of the list for which the given func returns true.
73func (l VirtualDeviceList) Select(f func(device types.BaseVirtualDevice) bool) VirtualDeviceList {
74	var found VirtualDeviceList
75
76	for _, device := range l {
77		if f(device) {
78			found = append(found, device)
79		}
80	}
81
82	return found
83}
84
85// SelectByType returns a new list with devices that are equal to or extend the given type.
86func (l VirtualDeviceList) SelectByType(deviceType types.BaseVirtualDevice) VirtualDeviceList {
87	dtype := reflect.TypeOf(deviceType)
88	if dtype == nil {
89		return nil
90	}
91	dname := dtype.Elem().Name()
92
93	return l.Select(func(device types.BaseVirtualDevice) bool {
94		t := reflect.TypeOf(device)
95
96		if t == dtype {
97			return true
98		}
99
100		_, ok := t.Elem().FieldByName(dname)
101
102		return ok
103	})
104}
105
106// SelectByBackingInfo returns a new list with devices matching the given backing info.
107// If the value of backing is nil, any device with a backing of the same type will be returned.
108func (l VirtualDeviceList) SelectByBackingInfo(backing types.BaseVirtualDeviceBackingInfo) VirtualDeviceList {
109	t := reflect.TypeOf(backing)
110
111	return l.Select(func(device types.BaseVirtualDevice) bool {
112		db := device.GetVirtualDevice().Backing
113		if db == nil {
114			return false
115		}
116
117		if reflect.TypeOf(db) != t {
118			return false
119		}
120
121		if reflect.ValueOf(backing).IsNil() {
122			// selecting by backing type
123			return true
124		}
125
126		switch a := db.(type) {
127		case *types.VirtualEthernetCardNetworkBackingInfo:
128			b := backing.(*types.VirtualEthernetCardNetworkBackingInfo)
129			return a.DeviceName == b.DeviceName
130		case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo:
131			b := backing.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo)
132			return a.Port.SwitchUuid == b.Port.SwitchUuid &&
133				a.Port.PortgroupKey == b.Port.PortgroupKey
134		case *types.VirtualDiskFlatVer2BackingInfo:
135			b := backing.(*types.VirtualDiskFlatVer2BackingInfo)
136			if a.Parent != nil && b.Parent != nil {
137				return a.Parent.FileName == b.Parent.FileName
138			}
139			return a.FileName == b.FileName
140		case *types.VirtualSerialPortURIBackingInfo:
141			b := backing.(*types.VirtualSerialPortURIBackingInfo)
142			return a.ServiceURI == b.ServiceURI
143		case types.BaseVirtualDeviceFileBackingInfo:
144			b := backing.(types.BaseVirtualDeviceFileBackingInfo)
145			return a.GetVirtualDeviceFileBackingInfo().FileName == b.GetVirtualDeviceFileBackingInfo().FileName
146		default:
147			return false
148		}
149	})
150}
151
152// Find returns the device matching the given name.
153func (l VirtualDeviceList) Find(name string) types.BaseVirtualDevice {
154	for _, device := range l {
155		if l.Name(device) == name {
156			return device
157		}
158	}
159	return nil
160}
161
162// FindByKey returns the device matching the given key.
163func (l VirtualDeviceList) FindByKey(key int32) types.BaseVirtualDevice {
164	for _, device := range l {
165		if device.GetVirtualDevice().Key == key {
166			return device
167		}
168	}
169	return nil
170}
171
172// FindIDEController will find the named IDE controller if given, otherwise will pick an available controller.
173// An error is returned if the named controller is not found or not an IDE controller.  Or, if name is not
174// given and no available controller can be found.
175func (l VirtualDeviceList) FindIDEController(name string) (*types.VirtualIDEController, error) {
176	if name != "" {
177		d := l.Find(name)
178		if d == nil {
179			return nil, fmt.Errorf("device '%s' not found", name)
180		}
181		if c, ok := d.(*types.VirtualIDEController); ok {
182			return c, nil
183		}
184		return nil, fmt.Errorf("%s is not an IDE controller", name)
185	}
186
187	c := l.PickController((*types.VirtualIDEController)(nil))
188	if c == nil {
189		return nil, errors.New("no available IDE controller")
190	}
191
192	return c.(*types.VirtualIDEController), nil
193}
194
195// CreateIDEController creates a new IDE controller.
196func (l VirtualDeviceList) CreateIDEController() (types.BaseVirtualDevice, error) {
197	ide := &types.VirtualIDEController{}
198	ide.Key = l.NewKey()
199	return ide, nil
200}
201
202// FindSCSIController will find the named SCSI controller if given, otherwise will pick an available controller.
203// An error is returned if the named controller is not found or not an SCSI controller.  Or, if name is not
204// given and no available controller can be found.
205func (l VirtualDeviceList) FindSCSIController(name string) (*types.VirtualSCSIController, error) {
206	if name != "" {
207		d := l.Find(name)
208		if d == nil {
209			return nil, fmt.Errorf("device '%s' not found", name)
210		}
211		if c, ok := d.(types.BaseVirtualSCSIController); ok {
212			return c.GetVirtualSCSIController(), nil
213		}
214		return nil, fmt.Errorf("%s is not an SCSI controller", name)
215	}
216
217	c := l.PickController((*types.VirtualSCSIController)(nil))
218	if c == nil {
219		return nil, errors.New("no available SCSI controller")
220	}
221
222	return c.(types.BaseVirtualSCSIController).GetVirtualSCSIController(), nil
223}
224
225// CreateSCSIController creates a new SCSI controller of type name if given, otherwise defaults to lsilogic.
226func (l VirtualDeviceList) CreateSCSIController(name string) (types.BaseVirtualDevice, error) {
227	ctypes := SCSIControllerTypes()
228
229	if name == "scsi" || name == "" {
230		name = ctypes.Type(ctypes[0])
231	}
232
233	found := ctypes.Select(func(device types.BaseVirtualDevice) bool {
234		return l.Type(device) == name
235	})
236
237	if len(found) == 0 {
238		return nil, fmt.Errorf("unknown SCSI controller type '%s'", name)
239	}
240
241	c, ok := found[0].(types.BaseVirtualSCSIController)
242	if !ok {
243		return nil, fmt.Errorf("invalid SCSI controller type '%s'", name)
244	}
245
246	scsi := c.GetVirtualSCSIController()
247	scsi.BusNumber = l.newSCSIBusNumber()
248	scsi.Key = l.NewKey()
249	scsi.ScsiCtlrUnitNumber = 7
250	return c.(types.BaseVirtualDevice), nil
251}
252
253var scsiBusNumbers = []int{0, 1, 2, 3}
254
255// newSCSIBusNumber returns the bus number to use for adding a new SCSI bus device.
256// -1 is returned if there are no bus numbers available.
257func (l VirtualDeviceList) newSCSIBusNumber() int32 {
258	var used []int
259
260	for _, d := range l.SelectByType((*types.VirtualSCSIController)(nil)) {
261		num := d.(types.BaseVirtualSCSIController).GetVirtualSCSIController().BusNumber
262		if num >= 0 {
263			used = append(used, int(num))
264		} // else caller is creating a new vm using SCSIControllerTypes
265	}
266
267	sort.Ints(used)
268
269	for i, n := range scsiBusNumbers {
270		if i == len(used) || n != used[i] {
271			return int32(n)
272		}
273	}
274
275	return -1
276}
277
278// FindNVMEController will find the named NVME controller if given, otherwise will pick an available controller.
279// An error is returned if the named controller is not found or not an NVME controller.  Or, if name is not
280// given and no available controller can be found.
281func (l VirtualDeviceList) FindNVMEController(name string) (*types.VirtualNVMEController, error) {
282	if name != "" {
283		d := l.Find(name)
284		if d == nil {
285			return nil, fmt.Errorf("device '%s' not found", name)
286		}
287		if c, ok := d.(*types.VirtualNVMEController); ok {
288			return c, nil
289		}
290		return nil, fmt.Errorf("%s is not an NVME controller", name)
291	}
292
293	c := l.PickController((*types.VirtualNVMEController)(nil))
294	if c == nil {
295		return nil, errors.New("no available NVME controller")
296	}
297
298	return c.(*types.VirtualNVMEController), nil
299}
300
301// CreateNVMEController creates a new NVMWE controller.
302func (l VirtualDeviceList) CreateNVMEController() (types.BaseVirtualDevice, error) {
303	nvme := &types.VirtualNVMEController{}
304	nvme.BusNumber = l.newNVMEBusNumber()
305	nvme.Key = l.NewKey()
306
307	return nvme, nil
308}
309
310var nvmeBusNumbers = []int{0, 1, 2, 3}
311
312// newNVMEBusNumber returns the bus number to use for adding a new NVME bus device.
313// -1 is returned if there are no bus numbers available.
314func (l VirtualDeviceList) newNVMEBusNumber() int32 {
315	var used []int
316
317	for _, d := range l.SelectByType((*types.VirtualNVMEController)(nil)) {
318		num := d.(types.BaseVirtualController).GetVirtualController().BusNumber
319		if num >= 0 {
320			used = append(used, int(num))
321		} // else caller is creating a new vm using NVMEControllerTypes
322	}
323
324	sort.Ints(used)
325
326	for i, n := range nvmeBusNumbers {
327		if i == len(used) || n != used[i] {
328			return int32(n)
329		}
330	}
331
332	return -1
333}
334
335// FindDiskController will find an existing ide or scsi disk controller.
336func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualController, error) {
337	switch {
338	case name == "ide":
339		return l.FindIDEController("")
340	case name == "scsi" || name == "":
341		return l.FindSCSIController("")
342	case name == "nvme":
343		return l.FindNVMEController("")
344	default:
345		if c, ok := l.Find(name).(types.BaseVirtualController); ok {
346			return c, nil
347		}
348		return nil, fmt.Errorf("%s is not a valid controller", name)
349	}
350}
351
352// PickController returns a controller of the given type(s).
353// If no controllers are found or have no available slots, then nil is returned.
354func (l VirtualDeviceList) PickController(kind types.BaseVirtualController) types.BaseVirtualController {
355	l = l.SelectByType(kind.(types.BaseVirtualDevice)).Select(func(device types.BaseVirtualDevice) bool {
356		num := len(device.(types.BaseVirtualController).GetVirtualController().Device)
357
358		switch device.(type) {
359		case types.BaseVirtualSCSIController:
360			return num < 15
361		case *types.VirtualIDEController:
362			return num < 2
363		case *types.VirtualNVMEController:
364			return num < 8
365		default:
366			return true
367		}
368	})
369
370	if len(l) == 0 {
371		return nil
372	}
373
374	return l[0].(types.BaseVirtualController)
375}
376
377// newUnitNumber returns the unit number to use for attaching a new device to the given controller.
378func (l VirtualDeviceList) newUnitNumber(c types.BaseVirtualController) int32 {
379	units := make([]bool, 30)
380
381	switch sc := c.(type) {
382	case types.BaseVirtualSCSIController:
383		//  The SCSI controller sits on its own bus
384		units[sc.GetVirtualSCSIController().ScsiCtlrUnitNumber] = true
385	}
386
387	key := c.GetVirtualController().Key
388
389	for _, device := range l {
390		d := device.GetVirtualDevice()
391
392		if d.ControllerKey == key && d.UnitNumber != nil {
393			units[int(*d.UnitNumber)] = true
394		}
395	}
396
397	for unit, used := range units {
398		if !used {
399			return int32(unit)
400		}
401	}
402
403	return -1
404}
405
406// NewKey returns the key to use for adding a new device to the device list.
407// The device list we're working with here may not be complete (e.g. when
408// we're only adding new devices), so any positive keys could conflict with device keys
409// that are already in use. To avoid this type of conflict, we can use negative keys
410// here, which will be resolved to positive keys by vSphere as the reconfiguration is done.
411func (l VirtualDeviceList) NewKey() int32 {
412	var key int32 = -200
413
414	for _, device := range l {
415		d := device.GetVirtualDevice()
416		if d.Key < key {
417			key = d.Key
418		}
419	}
420
421	return key - 1
422}
423
424// AssignController assigns a device to a controller.
425func (l VirtualDeviceList) AssignController(device types.BaseVirtualDevice, c types.BaseVirtualController) {
426	d := device.GetVirtualDevice()
427	d.ControllerKey = c.GetVirtualController().Key
428	d.UnitNumber = new(int32)
429	*d.UnitNumber = l.newUnitNumber(c)
430	if d.Key == 0 {
431		d.Key = -1
432	}
433}
434
435// CreateDisk creates a new VirtualDisk device which can be added to a VM.
436func (l VirtualDeviceList) CreateDisk(c types.BaseVirtualController, ds types.ManagedObjectReference, name string) *types.VirtualDisk {
437	// If name is not specified, one will be chosen for you.
438	// But if when given, make sure it ends in .vmdk, otherwise it will be treated as a directory.
439	if len(name) > 0 && filepath.Ext(name) != ".vmdk" {
440		name += ".vmdk"
441	}
442
443	device := &types.VirtualDisk{
444		VirtualDevice: types.VirtualDevice{
445			Backing: &types.VirtualDiskFlatVer2BackingInfo{
446				DiskMode:        string(types.VirtualDiskModePersistent),
447				ThinProvisioned: types.NewBool(true),
448				VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
449					FileName:  name,
450					Datastore: &ds,
451				},
452			},
453		},
454	}
455
456	l.AssignController(device, c)
457	return device
458}
459
460// ChildDisk creates a new VirtualDisk device, linked to the given parent disk, which can be added to a VM.
461func (l VirtualDeviceList) ChildDisk(parent *types.VirtualDisk) *types.VirtualDisk {
462	disk := *parent
463	backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
464	p := new(DatastorePath)
465	p.FromString(backing.FileName)
466	p.Path = ""
467
468	// Use specified disk as parent backing to a new disk.
469	disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{
470		VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
471			FileName:  p.String(),
472			Datastore: backing.Datastore,
473		},
474		Parent:          backing,
475		DiskMode:        backing.DiskMode,
476		ThinProvisioned: backing.ThinProvisioned,
477	}
478
479	return &disk
480}
481
482func (l VirtualDeviceList) connectivity(device types.BaseVirtualDevice, v bool) error {
483	c := device.GetVirtualDevice().Connectable
484	if c == nil {
485		return fmt.Errorf("%s is not connectable", l.Name(device))
486	}
487
488	c.Connected = v
489	c.StartConnected = v
490
491	return nil
492}
493
494// Connect changes the device to connected, returns an error if the device is not connectable.
495func (l VirtualDeviceList) Connect(device types.BaseVirtualDevice) error {
496	return l.connectivity(device, true)
497}
498
499// Disconnect changes the device to disconnected, returns an error if the device is not connectable.
500func (l VirtualDeviceList) Disconnect(device types.BaseVirtualDevice) error {
501	return l.connectivity(device, false)
502}
503
504// FindCdrom finds a cdrom device with the given name, defaulting to the first cdrom device if any.
505func (l VirtualDeviceList) FindCdrom(name string) (*types.VirtualCdrom, error) {
506	if name != "" {
507		d := l.Find(name)
508		if d == nil {
509			return nil, fmt.Errorf("device '%s' not found", name)
510		}
511		if c, ok := d.(*types.VirtualCdrom); ok {
512			return c, nil
513		}
514		return nil, fmt.Errorf("%s is not a cdrom device", name)
515	}
516
517	c := l.SelectByType((*types.VirtualCdrom)(nil))
518	if len(c) == 0 {
519		return nil, errors.New("no cdrom device found")
520	}
521
522	return c[0].(*types.VirtualCdrom), nil
523}
524
525// CreateCdrom creates a new VirtualCdrom device which can be added to a VM.
526func (l VirtualDeviceList) CreateCdrom(c *types.VirtualIDEController) (*types.VirtualCdrom, error) {
527	device := &types.VirtualCdrom{}
528
529	l.AssignController(device, c)
530
531	l.setDefaultCdromBacking(device)
532
533	device.Connectable = &types.VirtualDeviceConnectInfo{
534		AllowGuestControl: true,
535		Connected:         true,
536		StartConnected:    true,
537	}
538
539	return device, nil
540}
541
542// InsertIso changes the cdrom device backing to use the given iso file.
543func (l VirtualDeviceList) InsertIso(device *types.VirtualCdrom, iso string) *types.VirtualCdrom {
544	device.Backing = &types.VirtualCdromIsoBackingInfo{
545		VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
546			FileName: iso,
547		},
548	}
549
550	return device
551}
552
553// EjectIso removes the iso file based backing and replaces with the default cdrom backing.
554func (l VirtualDeviceList) EjectIso(device *types.VirtualCdrom) *types.VirtualCdrom {
555	l.setDefaultCdromBacking(device)
556	return device
557}
558
559func (l VirtualDeviceList) setDefaultCdromBacking(device *types.VirtualCdrom) {
560	device.Backing = &types.VirtualCdromAtapiBackingInfo{
561		VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
562			DeviceName:    fmt.Sprintf("%s-%d-%d", DeviceTypeCdrom, device.ControllerKey, device.UnitNumber),
563			UseAutoDetect: types.NewBool(false),
564		},
565	}
566}
567
568// FindFloppy finds a floppy device with the given name, defaulting to the first floppy device if any.
569func (l VirtualDeviceList) FindFloppy(name string) (*types.VirtualFloppy, error) {
570	if name != "" {
571		d := l.Find(name)
572		if d == nil {
573			return nil, fmt.Errorf("device '%s' not found", name)
574		}
575		if c, ok := d.(*types.VirtualFloppy); ok {
576			return c, nil
577		}
578		return nil, fmt.Errorf("%s is not a floppy device", name)
579	}
580
581	c := l.SelectByType((*types.VirtualFloppy)(nil))
582	if len(c) == 0 {
583		return nil, errors.New("no floppy device found")
584	}
585
586	return c[0].(*types.VirtualFloppy), nil
587}
588
589// CreateFloppy creates a new VirtualFloppy device which can be added to a VM.
590func (l VirtualDeviceList) CreateFloppy() (*types.VirtualFloppy, error) {
591	device := &types.VirtualFloppy{}
592
593	c := l.PickController((*types.VirtualSIOController)(nil))
594	if c == nil {
595		return nil, errors.New("no available SIO controller")
596	}
597
598	l.AssignController(device, c)
599
600	l.setDefaultFloppyBacking(device)
601
602	device.Connectable = &types.VirtualDeviceConnectInfo{
603		AllowGuestControl: true,
604		Connected:         true,
605		StartConnected:    true,
606	}
607
608	return device, nil
609}
610
611// InsertImg changes the floppy device backing to use the given img file.
612func (l VirtualDeviceList) InsertImg(device *types.VirtualFloppy, img string) *types.VirtualFloppy {
613	device.Backing = &types.VirtualFloppyImageBackingInfo{
614		VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
615			FileName: img,
616		},
617	}
618
619	return device
620}
621
622// EjectImg removes the img file based backing and replaces with the default floppy backing.
623func (l VirtualDeviceList) EjectImg(device *types.VirtualFloppy) *types.VirtualFloppy {
624	l.setDefaultFloppyBacking(device)
625	return device
626}
627
628func (l VirtualDeviceList) setDefaultFloppyBacking(device *types.VirtualFloppy) {
629	device.Backing = &types.VirtualFloppyDeviceBackingInfo{
630		VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
631			DeviceName:    fmt.Sprintf("%s-%d", DeviceTypeFloppy, device.UnitNumber),
632			UseAutoDetect: types.NewBool(false),
633		},
634	}
635}
636
637// FindSerialPort finds a serial port device with the given name, defaulting to the first serial port device if any.
638func (l VirtualDeviceList) FindSerialPort(name string) (*types.VirtualSerialPort, error) {
639	if name != "" {
640		d := l.Find(name)
641		if d == nil {
642			return nil, fmt.Errorf("device '%s' not found", name)
643		}
644		if c, ok := d.(*types.VirtualSerialPort); ok {
645			return c, nil
646		}
647		return nil, fmt.Errorf("%s is not a serial port device", name)
648	}
649
650	c := l.SelectByType((*types.VirtualSerialPort)(nil))
651	if len(c) == 0 {
652		return nil, errors.New("no serial port device found")
653	}
654
655	return c[0].(*types.VirtualSerialPort), nil
656}
657
658// CreateSerialPort creates a new VirtualSerialPort device which can be added to a VM.
659func (l VirtualDeviceList) CreateSerialPort() (*types.VirtualSerialPort, error) {
660	device := &types.VirtualSerialPort{
661		YieldOnPoll: true,
662	}
663
664	c := l.PickController((*types.VirtualSIOController)(nil))
665	if c == nil {
666		return nil, errors.New("no available SIO controller")
667	}
668
669	l.AssignController(device, c)
670
671	l.setDefaultSerialPortBacking(device)
672
673	return device, nil
674}
675
676// ConnectSerialPort connects a serial port to a server or client uri.
677func (l VirtualDeviceList) ConnectSerialPort(device *types.VirtualSerialPort, uri string, client bool, proxyuri string) *types.VirtualSerialPort {
678	if strings.HasPrefix(uri, "[") {
679		device.Backing = &types.VirtualSerialPortFileBackingInfo{
680			VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
681				FileName: uri,
682			},
683		}
684
685		return device
686	}
687
688	direction := types.VirtualDeviceURIBackingOptionDirectionServer
689	if client {
690		direction = types.VirtualDeviceURIBackingOptionDirectionClient
691	}
692
693	device.Backing = &types.VirtualSerialPortURIBackingInfo{
694		VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
695			Direction:  string(direction),
696			ServiceURI: uri,
697			ProxyURI:   proxyuri,
698		},
699	}
700
701	return device
702}
703
704// DisconnectSerialPort disconnects the serial port backing.
705func (l VirtualDeviceList) DisconnectSerialPort(device *types.VirtualSerialPort) *types.VirtualSerialPort {
706	l.setDefaultSerialPortBacking(device)
707	return device
708}
709
710func (l VirtualDeviceList) setDefaultSerialPortBacking(device *types.VirtualSerialPort) {
711	device.Backing = &types.VirtualSerialPortURIBackingInfo{
712		VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
713			Direction:  "client",
714			ServiceURI: "localhost:0",
715		},
716	}
717}
718
719// CreateEthernetCard creates a new VirtualEthernetCard of the given name name and initialized with the given backing.
720func (l VirtualDeviceList) CreateEthernetCard(name string, backing types.BaseVirtualDeviceBackingInfo) (types.BaseVirtualDevice, error) {
721	ctypes := EthernetCardTypes()
722
723	if name == "" {
724		name = ctypes.deviceName(ctypes[0])
725	}
726
727	found := ctypes.Select(func(device types.BaseVirtualDevice) bool {
728		return l.deviceName(device) == name
729	})
730
731	if len(found) == 0 {
732		return nil, fmt.Errorf("unknown ethernet card type '%s'", name)
733	}
734
735	c, ok := found[0].(types.BaseVirtualEthernetCard)
736	if !ok {
737		return nil, fmt.Errorf("invalid ethernet card type '%s'", name)
738	}
739
740	c.GetVirtualEthernetCard().Backing = backing
741
742	return c.(types.BaseVirtualDevice), nil
743}
744
745// PrimaryMacAddress returns the MacAddress field of the primary VirtualEthernetCard
746func (l VirtualDeviceList) PrimaryMacAddress() string {
747	eth0 := l.Find("ethernet-0")
748
749	if eth0 == nil {
750		return ""
751	}
752
753	return eth0.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard().MacAddress
754}
755
756// convert a BaseVirtualDevice to a BaseVirtualMachineBootOptionsBootableDevice
757var bootableDevices = map[string]func(device types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice{
758	DeviceTypeNone: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
759		return &types.VirtualMachineBootOptionsBootableDevice{}
760	},
761	DeviceTypeCdrom: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
762		return &types.VirtualMachineBootOptionsBootableCdromDevice{}
763	},
764	DeviceTypeDisk: func(d types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
765		return &types.VirtualMachineBootOptionsBootableDiskDevice{
766			DeviceKey: d.GetVirtualDevice().Key,
767		}
768	},
769	DeviceTypeEthernet: func(d types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
770		return &types.VirtualMachineBootOptionsBootableEthernetDevice{
771			DeviceKey: d.GetVirtualDevice().Key,
772		}
773	},
774	DeviceTypeFloppy: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
775		return &types.VirtualMachineBootOptionsBootableFloppyDevice{}
776	},
777}
778
779// BootOrder returns a list of devices which can be used to set boot order via VirtualMachine.SetBootOptions.
780// The order can be any of "ethernet", "cdrom", "floppy" or "disk" or by specific device name.
781// A value of "-" will clear the existing boot order on the VC/ESX side.
782func (l VirtualDeviceList) BootOrder(order []string) []types.BaseVirtualMachineBootOptionsBootableDevice {
783	var devices []types.BaseVirtualMachineBootOptionsBootableDevice
784
785	for _, name := range order {
786		if kind, ok := bootableDevices[name]; ok {
787			if name == DeviceTypeNone {
788				// Not covered in the API docs, nor obvious, but this clears the boot order on the VC/ESX side.
789				devices = append(devices, new(types.VirtualMachineBootOptionsBootableDevice))
790				continue
791			}
792
793			for _, device := range l {
794				if l.Type(device) == name {
795					devices = append(devices, kind(device))
796				}
797			}
798			continue
799		}
800
801		if d := l.Find(name); d != nil {
802			if kind, ok := bootableDevices[l.Type(d)]; ok {
803				devices = append(devices, kind(d))
804			}
805		}
806	}
807
808	return devices
809}
810
811// SelectBootOrder returns an ordered list of devices matching the given bootable device order
812func (l VirtualDeviceList) SelectBootOrder(order []types.BaseVirtualMachineBootOptionsBootableDevice) VirtualDeviceList {
813	var devices VirtualDeviceList
814
815	for _, bd := range order {
816		for _, device := range l {
817			if kind, ok := bootableDevices[l.Type(device)]; ok {
818				if reflect.DeepEqual(kind(device), bd) {
819					devices = append(devices, device)
820				}
821			}
822		}
823	}
824
825	return devices
826}
827
828// TypeName returns the vmodl type name of the device
829func (l VirtualDeviceList) TypeName(device types.BaseVirtualDevice) string {
830	dtype := reflect.TypeOf(device)
831	if dtype == nil {
832		return ""
833	}
834	return dtype.Elem().Name()
835}
836
837var deviceNameRegexp = regexp.MustCompile(`(?:Virtual)?(?:Machine)?(\w+?)(?:Card|Device|Controller)?$`)
838
839func (l VirtualDeviceList) deviceName(device types.BaseVirtualDevice) string {
840	name := "device"
841	typeName := l.TypeName(device)
842
843	m := deviceNameRegexp.FindStringSubmatch(typeName)
844	if len(m) == 2 {
845		name = strings.ToLower(m[1])
846	}
847
848	return name
849}
850
851// Type returns a human-readable name for the given device
852func (l VirtualDeviceList) Type(device types.BaseVirtualDevice) string {
853	switch device.(type) {
854	case types.BaseVirtualEthernetCard:
855		return DeviceTypeEthernet
856	case *types.ParaVirtualSCSIController:
857		return "pvscsi"
858	case *types.VirtualLsiLogicSASController:
859		return "lsilogic-sas"
860	case *types.VirtualNVMEController:
861		return "nvme"
862	default:
863		return l.deviceName(device)
864	}
865}
866
867// Name returns a stable, human-readable name for the given device
868func (l VirtualDeviceList) Name(device types.BaseVirtualDevice) string {
869	var key string
870	var UnitNumber int32
871	d := device.GetVirtualDevice()
872	if d.UnitNumber != nil {
873		UnitNumber = *d.UnitNumber
874	}
875
876	dtype := l.Type(device)
877	switch dtype {
878	case DeviceTypeEthernet:
879		key = fmt.Sprintf("%d", UnitNumber-7)
880	case DeviceTypeDisk:
881		key = fmt.Sprintf("%d-%d", d.ControllerKey, UnitNumber)
882	default:
883		key = fmt.Sprintf("%d", d.Key)
884	}
885
886	return fmt.Sprintf("%s-%s", dtype, key)
887}
888
889// ConfigSpec creates a virtual machine configuration spec for
890// the specified operation, for the list of devices in the device list.
891func (l VirtualDeviceList) ConfigSpec(op types.VirtualDeviceConfigSpecOperation) ([]types.BaseVirtualDeviceConfigSpec, error) {
892	var fop types.VirtualDeviceConfigSpecFileOperation
893	switch op {
894	case types.VirtualDeviceConfigSpecOperationAdd:
895		fop = types.VirtualDeviceConfigSpecFileOperationCreate
896	case types.VirtualDeviceConfigSpecOperationEdit:
897		fop = types.VirtualDeviceConfigSpecFileOperationReplace
898	case types.VirtualDeviceConfigSpecOperationRemove:
899		fop = types.VirtualDeviceConfigSpecFileOperationDestroy
900	default:
901		panic("unknown op")
902	}
903
904	var res []types.BaseVirtualDeviceConfigSpec
905	for _, device := range l {
906		config := &types.VirtualDeviceConfigSpec{
907			Device:    device,
908			Operation: op,
909		}
910
911		if disk, ok := device.(*types.VirtualDisk); ok {
912			config.FileOperation = fop
913
914			// Special case to attach an existing disk
915			if op == types.VirtualDeviceConfigSpecOperationAdd && disk.CapacityInKB == 0 {
916				childDisk := false
917				if b, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
918					childDisk = b.Parent != nil
919				}
920
921				if !childDisk {
922					// Existing disk, clear file operation
923					config.FileOperation = ""
924				}
925			}
926		}
927
928		res = append(res, config)
929	}
930
931	return res, nil
932}
933