github.com/vmware/govmomi@v0.37.2/object/virtual_device_list.go (about)

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