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

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