github.com/vmware/govmomi@v0.43.0/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  // FindSATAController will find the named SATA or AHCI controller if given, otherwise will pick an available controller.
   365  // An error is returned if the named controller is not found or not a SATA or AHCI controller. Or, if name is not
   366  // given and no available controller can be found.
   367  func (l VirtualDeviceList) FindSATAController(name string) (types.BaseVirtualController, error) {
   368  	if name != "" {
   369  		d := l.Find(name)
   370  		if d == nil {
   371  			return nil, fmt.Errorf("device '%s' not found", name)
   372  		}
   373  		switch c := d.(type) {
   374  		case *types.VirtualSATAController:
   375  			return c, nil
   376  		case *types.VirtualAHCIController:
   377  			return c, nil
   378  		default:
   379  			return nil, fmt.Errorf("%s is not a SATA or AHCI controller", name)
   380  		}
   381  	}
   382  
   383  	c := l.PickController((*types.VirtualSATAController)(nil))
   384  	if c == nil {
   385  		c = l.PickController((*types.VirtualAHCIController)(nil))
   386  	}
   387  	if c == nil {
   388  		return nil, errors.New("no available SATA or AHCI controller")
   389  	}
   390  
   391  	switch c := c.(type) {
   392  	case *types.VirtualSATAController:
   393  		return c, nil
   394  	case *types.VirtualAHCIController:
   395  		return c, nil
   396  	}
   397  
   398  	return nil, errors.New("unexpected controller type")
   399  }
   400  
   401  // CreateSATAController creates a new SATA controller.
   402  func (l VirtualDeviceList) CreateSATAController() (types.BaseVirtualDevice, error) {
   403  	sata := &types.VirtualAHCIController{}
   404  	sata.BusNumber = l.newSATABusNumber()
   405  	sata.Key = l.NewKey()
   406  
   407  	return sata, nil
   408  }
   409  
   410  var sataBusNumbers = []int{0, 1, 2, 3}
   411  
   412  // newSATABusNumber returns the bus number to use for adding a new SATA bus device.
   413  // -1 is returned if there are no bus numbers available.
   414  func (l VirtualDeviceList) newSATABusNumber() int32 {
   415  	var used []int
   416  
   417  	for _, d := range l.SelectByType((*types.VirtualSATAController)(nil)) {
   418  		num := d.(types.BaseVirtualController).GetVirtualController().BusNumber
   419  		if num >= 0 {
   420  			used = append(used, int(num))
   421  		} // else caller is creating a new vm using SATAControllerTypes
   422  	}
   423  
   424  	sort.Ints(used)
   425  
   426  	for i, n := range sataBusNumbers {
   427  		if i == len(used) || n != used[i] {
   428  			return int32(n)
   429  		}
   430  	}
   431  
   432  	return -1
   433  }
   434  
   435  // FindDiskController will find an existing ide or scsi disk controller.
   436  func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualController, error) {
   437  	switch {
   438  	case name == "ide":
   439  		return l.FindIDEController("")
   440  	case name == "scsi" || name == "":
   441  		return l.FindSCSIController("")
   442  	case name == "nvme":
   443  		return l.FindNVMEController("")
   444  	case name == "sata":
   445  		return l.FindSATAController("")
   446  	default:
   447  		if c, ok := l.Find(name).(types.BaseVirtualController); ok {
   448  			return c, nil
   449  		}
   450  		return nil, fmt.Errorf("%s is not a valid controller", name)
   451  	}
   452  }
   453  
   454  // PickController returns a controller of the given type(s).
   455  // If no controllers are found or have no available slots, then nil is returned.
   456  func (l VirtualDeviceList) PickController(kind types.BaseVirtualController) types.BaseVirtualController {
   457  	l = l.SelectByType(kind.(types.BaseVirtualDevice)).Select(func(device types.BaseVirtualDevice) bool {
   458  		num := len(device.(types.BaseVirtualController).GetVirtualController().Device)
   459  
   460  		switch device.(type) {
   461  		case types.BaseVirtualSCSIController:
   462  			return num < 15
   463  		case *types.VirtualIDEController:
   464  			return num < 2
   465  		case types.BaseVirtualSATAController:
   466  			return num < 30
   467  		case *types.VirtualNVMEController:
   468  			return num < 8
   469  		default:
   470  			return true
   471  		}
   472  	})
   473  
   474  	if len(l) == 0 {
   475  		return nil
   476  	}
   477  
   478  	return l[0].(types.BaseVirtualController)
   479  }
   480  
   481  // newUnitNumber returns the unit number to use for attaching a new device to the given controller.
   482  func (l VirtualDeviceList) newUnitNumber(c types.BaseVirtualController, offset int) int32 {
   483  	units := make([]bool, 30)
   484  
   485  	for i := 0; i < offset; i++ {
   486  		units[i] = true
   487  	}
   488  
   489  	switch sc := c.(type) {
   490  	case types.BaseVirtualSCSIController:
   491  		//  The SCSI controller sits on its own bus
   492  		units[sc.GetVirtualSCSIController().ScsiCtlrUnitNumber] = true
   493  	}
   494  
   495  	key := c.GetVirtualController().Key
   496  
   497  	for _, device := range l {
   498  		d := device.GetVirtualDevice()
   499  
   500  		if d.ControllerKey == key && d.UnitNumber != nil {
   501  			units[int(*d.UnitNumber)] = true
   502  		}
   503  	}
   504  
   505  	for unit, used := range units {
   506  		if !used {
   507  			return int32(unit)
   508  		}
   509  	}
   510  
   511  	return -1
   512  }
   513  
   514  // NewKey returns the key to use for adding a new device to the device list.
   515  // The device list we're working with here may not be complete (e.g. when
   516  // we're only adding new devices), so any positive keys could conflict with device keys
   517  // that are already in use. To avoid this type of conflict, we can use negative keys
   518  // here, which will be resolved to positive keys by vSphere as the reconfiguration is done.
   519  func (l VirtualDeviceList) NewKey() int32 {
   520  	var key int32 = -200
   521  
   522  	for _, device := range l {
   523  		d := device.GetVirtualDevice()
   524  		if d.Key < key {
   525  			key = d.Key
   526  		}
   527  	}
   528  
   529  	return key - 1
   530  }
   531  
   532  // AssignController assigns a device to a controller.
   533  func (l VirtualDeviceList) AssignController(device types.BaseVirtualDevice, c types.BaseVirtualController) {
   534  	d := device.GetVirtualDevice()
   535  	d.ControllerKey = c.GetVirtualController().Key
   536  	d.UnitNumber = new(int32)
   537  
   538  	offset := 0
   539  	switch device.(type) {
   540  	case types.BaseVirtualEthernetCard:
   541  		offset = 7
   542  	}
   543  	*d.UnitNumber = l.newUnitNumber(c, offset)
   544  
   545  	if d.Key == 0 {
   546  		d.Key = l.newRandomKey()
   547  	}
   548  }
   549  
   550  // newRandomKey returns a random negative device key.
   551  // The generated key can be used for devices you want to add so that it does not collide with existing ones.
   552  func (l VirtualDeviceList) newRandomKey() int32 {
   553  	// NOTE: rand.Uint32 cannot be used here because conversion from uint32 to int32 may change the sign
   554  	key := rand.Int31() * -1
   555  	if key == 0 {
   556  		return -1
   557  	}
   558  
   559  	return key
   560  }
   561  
   562  // CreateDisk creates a new VirtualDisk device which can be added to a VM.
   563  func (l VirtualDeviceList) CreateDisk(c types.BaseVirtualController, ds types.ManagedObjectReference, name string) *types.VirtualDisk {
   564  	// If name is not specified, one will be chosen for you.
   565  	// But if when given, make sure it ends in .vmdk, otherwise it will be treated as a directory.
   566  	if len(name) > 0 && filepath.Ext(name) != ".vmdk" {
   567  		name += ".vmdk"
   568  	}
   569  
   570  	device := &types.VirtualDisk{
   571  		VirtualDevice: types.VirtualDevice{
   572  			Backing: &types.VirtualDiskFlatVer2BackingInfo{
   573  				DiskMode:        string(types.VirtualDiskModePersistent),
   574  				ThinProvisioned: types.NewBool(true),
   575  				VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   576  					FileName:  name,
   577  					Datastore: &ds,
   578  				},
   579  			},
   580  		},
   581  	}
   582  
   583  	l.AssignController(device, c)
   584  	return device
   585  }
   586  
   587  // ChildDisk creates a new VirtualDisk device, linked to the given parent disk, which can be added to a VM.
   588  func (l VirtualDeviceList) ChildDisk(parent *types.VirtualDisk) *types.VirtualDisk {
   589  	disk := *parent
   590  	backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
   591  	p := new(DatastorePath)
   592  	p.FromString(backing.FileName)
   593  	p.Path = ""
   594  
   595  	// Use specified disk as parent backing to a new disk.
   596  	disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{
   597  		VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   598  			FileName:  p.String(),
   599  			Datastore: backing.Datastore,
   600  		},
   601  		Parent:          backing,
   602  		DiskMode:        backing.DiskMode,
   603  		ThinProvisioned: backing.ThinProvisioned,
   604  	}
   605  
   606  	return &disk
   607  }
   608  
   609  func (l VirtualDeviceList) connectivity(device types.BaseVirtualDevice, v bool) error {
   610  	c := device.GetVirtualDevice().Connectable
   611  	if c == nil {
   612  		return fmt.Errorf("%s is not connectable", l.Name(device))
   613  	}
   614  
   615  	c.Connected = v
   616  	c.StartConnected = v
   617  
   618  	return nil
   619  }
   620  
   621  // Connect changes the device to connected, returns an error if the device is not connectable.
   622  func (l VirtualDeviceList) Connect(device types.BaseVirtualDevice) error {
   623  	return l.connectivity(device, true)
   624  }
   625  
   626  // Disconnect changes the device to disconnected, returns an error if the device is not connectable.
   627  func (l VirtualDeviceList) Disconnect(device types.BaseVirtualDevice) error {
   628  	return l.connectivity(device, false)
   629  }
   630  
   631  // FindCdrom finds a cdrom device with the given name, defaulting to the first cdrom device if any.
   632  func (l VirtualDeviceList) FindCdrom(name string) (*types.VirtualCdrom, error) {
   633  	if name != "" {
   634  		d := l.Find(name)
   635  		if d == nil {
   636  			return nil, fmt.Errorf("device '%s' not found", name)
   637  		}
   638  		if c, ok := d.(*types.VirtualCdrom); ok {
   639  			return c, nil
   640  		}
   641  		return nil, fmt.Errorf("%s is not a cdrom device", name)
   642  	}
   643  
   644  	c := l.SelectByType((*types.VirtualCdrom)(nil))
   645  	if len(c) == 0 {
   646  		return nil, errors.New("no cdrom device found")
   647  	}
   648  
   649  	return c[0].(*types.VirtualCdrom), nil
   650  }
   651  
   652  // CreateCdrom creates a new VirtualCdrom device which can be added to a VM.
   653  func (l VirtualDeviceList) CreateCdrom(c *types.VirtualIDEController) (*types.VirtualCdrom, error) {
   654  	device := &types.VirtualCdrom{}
   655  
   656  	l.AssignController(device, c)
   657  
   658  	l.setDefaultCdromBacking(device)
   659  
   660  	device.Connectable = &types.VirtualDeviceConnectInfo{
   661  		AllowGuestControl: true,
   662  		Connected:         true,
   663  		StartConnected:    true,
   664  	}
   665  
   666  	return device, nil
   667  }
   668  
   669  // InsertIso changes the cdrom device backing to use the given iso file.
   670  func (l VirtualDeviceList) InsertIso(device *types.VirtualCdrom, iso string) *types.VirtualCdrom {
   671  	device.Backing = &types.VirtualCdromIsoBackingInfo{
   672  		VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   673  			FileName: iso,
   674  		},
   675  	}
   676  
   677  	return device
   678  }
   679  
   680  // EjectIso removes the iso file based backing and replaces with the default cdrom backing.
   681  func (l VirtualDeviceList) EjectIso(device *types.VirtualCdrom) *types.VirtualCdrom {
   682  	l.setDefaultCdromBacking(device)
   683  	return device
   684  }
   685  
   686  func (l VirtualDeviceList) setDefaultCdromBacking(device *types.VirtualCdrom) {
   687  	device.Backing = &types.VirtualCdromAtapiBackingInfo{
   688  		VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
   689  			DeviceName:    fmt.Sprintf("%s-%d-%d", DeviceTypeCdrom, device.ControllerKey, device.UnitNumber),
   690  			UseAutoDetect: types.NewBool(false),
   691  		},
   692  	}
   693  }
   694  
   695  // FindFloppy finds a floppy device with the given name, defaulting to the first floppy device if any.
   696  func (l VirtualDeviceList) FindFloppy(name string) (*types.VirtualFloppy, error) {
   697  	if name != "" {
   698  		d := l.Find(name)
   699  		if d == nil {
   700  			return nil, fmt.Errorf("device '%s' not found", name)
   701  		}
   702  		if c, ok := d.(*types.VirtualFloppy); ok {
   703  			return c, nil
   704  		}
   705  		return nil, fmt.Errorf("%s is not a floppy device", name)
   706  	}
   707  
   708  	c := l.SelectByType((*types.VirtualFloppy)(nil))
   709  	if len(c) == 0 {
   710  		return nil, errors.New("no floppy device found")
   711  	}
   712  
   713  	return c[0].(*types.VirtualFloppy), nil
   714  }
   715  
   716  // CreateFloppy creates a new VirtualFloppy device which can be added to a VM.
   717  func (l VirtualDeviceList) CreateFloppy() (*types.VirtualFloppy, error) {
   718  	device := &types.VirtualFloppy{}
   719  
   720  	c := l.PickController((*types.VirtualSIOController)(nil))
   721  	if c == nil {
   722  		return nil, errors.New("no available SIO controller")
   723  	}
   724  
   725  	l.AssignController(device, c)
   726  
   727  	l.setDefaultFloppyBacking(device)
   728  
   729  	device.Connectable = &types.VirtualDeviceConnectInfo{
   730  		AllowGuestControl: true,
   731  		Connected:         true,
   732  		StartConnected:    true,
   733  	}
   734  
   735  	return device, nil
   736  }
   737  
   738  // InsertImg changes the floppy device backing to use the given img file.
   739  func (l VirtualDeviceList) InsertImg(device *types.VirtualFloppy, img string) *types.VirtualFloppy {
   740  	device.Backing = &types.VirtualFloppyImageBackingInfo{
   741  		VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   742  			FileName: img,
   743  		},
   744  	}
   745  
   746  	return device
   747  }
   748  
   749  // EjectImg removes the img file based backing and replaces with the default floppy backing.
   750  func (l VirtualDeviceList) EjectImg(device *types.VirtualFloppy) *types.VirtualFloppy {
   751  	l.setDefaultFloppyBacking(device)
   752  	return device
   753  }
   754  
   755  func (l VirtualDeviceList) setDefaultFloppyBacking(device *types.VirtualFloppy) {
   756  	device.Backing = &types.VirtualFloppyDeviceBackingInfo{
   757  		VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
   758  			DeviceName:    fmt.Sprintf("%s-%d", DeviceTypeFloppy, device.UnitNumber),
   759  			UseAutoDetect: types.NewBool(false),
   760  		},
   761  	}
   762  }
   763  
   764  // FindSerialPort finds a serial port device with the given name, defaulting to the first serial port device if any.
   765  func (l VirtualDeviceList) FindSerialPort(name string) (*types.VirtualSerialPort, error) {
   766  	if name != "" {
   767  		d := l.Find(name)
   768  		if d == nil {
   769  			return nil, fmt.Errorf("device '%s' not found", name)
   770  		}
   771  		if c, ok := d.(*types.VirtualSerialPort); ok {
   772  			return c, nil
   773  		}
   774  		return nil, fmt.Errorf("%s is not a serial port device", name)
   775  	}
   776  
   777  	c := l.SelectByType((*types.VirtualSerialPort)(nil))
   778  	if len(c) == 0 {
   779  		return nil, errors.New("no serial port device found")
   780  	}
   781  
   782  	return c[0].(*types.VirtualSerialPort), nil
   783  }
   784  
   785  // CreateSerialPort creates a new VirtualSerialPort device which can be added to a VM.
   786  func (l VirtualDeviceList) CreateSerialPort() (*types.VirtualSerialPort, error) {
   787  	device := &types.VirtualSerialPort{
   788  		YieldOnPoll: true,
   789  	}
   790  
   791  	c := l.PickController((*types.VirtualSIOController)(nil))
   792  	if c == nil {
   793  		return nil, errors.New("no available SIO controller")
   794  	}
   795  
   796  	l.AssignController(device, c)
   797  
   798  	l.setDefaultSerialPortBacking(device)
   799  
   800  	return device, nil
   801  }
   802  
   803  // ConnectSerialPort connects a serial port to a server or client uri.
   804  func (l VirtualDeviceList) ConnectSerialPort(device *types.VirtualSerialPort, uri string, client bool, proxyuri string) *types.VirtualSerialPort {
   805  	if strings.HasPrefix(uri, "[") {
   806  		device.Backing = &types.VirtualSerialPortFileBackingInfo{
   807  			VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   808  				FileName: uri,
   809  			},
   810  		}
   811  
   812  		return device
   813  	}
   814  
   815  	direction := types.VirtualDeviceURIBackingOptionDirectionServer
   816  	if client {
   817  		direction = types.VirtualDeviceURIBackingOptionDirectionClient
   818  	}
   819  
   820  	device.Backing = &types.VirtualSerialPortURIBackingInfo{
   821  		VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
   822  			Direction:  string(direction),
   823  			ServiceURI: uri,
   824  			ProxyURI:   proxyuri,
   825  		},
   826  	}
   827  
   828  	return device
   829  }
   830  
   831  // DisconnectSerialPort disconnects the serial port backing.
   832  func (l VirtualDeviceList) DisconnectSerialPort(device *types.VirtualSerialPort) *types.VirtualSerialPort {
   833  	l.setDefaultSerialPortBacking(device)
   834  	return device
   835  }
   836  
   837  func (l VirtualDeviceList) setDefaultSerialPortBacking(device *types.VirtualSerialPort) {
   838  	device.Backing = &types.VirtualSerialPortURIBackingInfo{
   839  		VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
   840  			Direction:  "client",
   841  			ServiceURI: "localhost:0",
   842  		},
   843  	}
   844  }
   845  
   846  // CreateEthernetCard creates a new VirtualEthernetCard of the given name name and initialized with the given backing.
   847  func (l VirtualDeviceList) CreateEthernetCard(name string, backing types.BaseVirtualDeviceBackingInfo) (types.BaseVirtualDevice, error) {
   848  	ctypes := EthernetCardTypes()
   849  
   850  	if name == "" {
   851  		name = ctypes.deviceName(ctypes[0])
   852  	}
   853  
   854  	found := ctypes.Select(func(device types.BaseVirtualDevice) bool {
   855  		return l.deviceName(device) == name
   856  	})
   857  
   858  	if len(found) == 0 {
   859  		return nil, fmt.Errorf("unknown ethernet card type '%s'", name)
   860  	}
   861  
   862  	c, ok := found[0].(types.BaseVirtualEthernetCard)
   863  	if !ok {
   864  		return nil, fmt.Errorf("invalid ethernet card type '%s'", name)
   865  	}
   866  
   867  	c.GetVirtualEthernetCard().Backing = backing
   868  
   869  	return c.(types.BaseVirtualDevice), nil
   870  }
   871  
   872  // PrimaryMacAddress returns the MacAddress field of the primary VirtualEthernetCard
   873  func (l VirtualDeviceList) PrimaryMacAddress() string {
   874  	eth0 := l.Find("ethernet-0")
   875  
   876  	if eth0 == nil {
   877  		return ""
   878  	}
   879  
   880  	return eth0.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard().MacAddress
   881  }
   882  
   883  // convert a BaseVirtualDevice to a BaseVirtualMachineBootOptionsBootableDevice
   884  var bootableDevices = map[string]func(device types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice{
   885  	DeviceTypeNone: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
   886  		return &types.VirtualMachineBootOptionsBootableDevice{}
   887  	},
   888  	DeviceTypeCdrom: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
   889  		return &types.VirtualMachineBootOptionsBootableCdromDevice{}
   890  	},
   891  	DeviceTypeDisk: func(d types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
   892  		return &types.VirtualMachineBootOptionsBootableDiskDevice{
   893  			DeviceKey: d.GetVirtualDevice().Key,
   894  		}
   895  	},
   896  	DeviceTypeEthernet: func(d types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
   897  		return &types.VirtualMachineBootOptionsBootableEthernetDevice{
   898  			DeviceKey: d.GetVirtualDevice().Key,
   899  		}
   900  	},
   901  	DeviceTypeFloppy: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
   902  		return &types.VirtualMachineBootOptionsBootableFloppyDevice{}
   903  	},
   904  }
   905  
   906  // BootOrder returns a list of devices which can be used to set boot order via VirtualMachine.SetBootOptions.
   907  // The order can be any of "ethernet", "cdrom", "floppy" or "disk" or by specific device name.
   908  // A value of "-" will clear the existing boot order on the VC/ESX side.
   909  func (l VirtualDeviceList) BootOrder(order []string) []types.BaseVirtualMachineBootOptionsBootableDevice {
   910  	var devices []types.BaseVirtualMachineBootOptionsBootableDevice
   911  
   912  	for _, name := range order {
   913  		if kind, ok := bootableDevices[name]; ok {
   914  			if name == DeviceTypeNone {
   915  				// Not covered in the API docs, nor obvious, but this clears the boot order on the VC/ESX side.
   916  				devices = append(devices, new(types.VirtualMachineBootOptionsBootableDevice))
   917  				continue
   918  			}
   919  
   920  			for _, device := range l {
   921  				if l.Type(device) == name {
   922  					devices = append(devices, kind(device))
   923  				}
   924  			}
   925  			continue
   926  		}
   927  
   928  		if d := l.Find(name); d != nil {
   929  			if kind, ok := bootableDevices[l.Type(d)]; ok {
   930  				devices = append(devices, kind(d))
   931  			}
   932  		}
   933  	}
   934  
   935  	return devices
   936  }
   937  
   938  // SelectBootOrder returns an ordered list of devices matching the given bootable device order
   939  func (l VirtualDeviceList) SelectBootOrder(order []types.BaseVirtualMachineBootOptionsBootableDevice) VirtualDeviceList {
   940  	var devices VirtualDeviceList
   941  
   942  	for _, bd := range order {
   943  		for _, device := range l {
   944  			if kind, ok := bootableDevices[l.Type(device)]; ok {
   945  				if reflect.DeepEqual(kind(device), bd) {
   946  					devices = append(devices, device)
   947  				}
   948  			}
   949  		}
   950  	}
   951  
   952  	return devices
   953  }
   954  
   955  // TypeName returns the vmodl type name of the device
   956  func (l VirtualDeviceList) TypeName(device types.BaseVirtualDevice) string {
   957  	dtype := reflect.TypeOf(device)
   958  	if dtype == nil {
   959  		return ""
   960  	}
   961  	return dtype.Elem().Name()
   962  }
   963  
   964  var deviceNameRegexp = regexp.MustCompile(`(?:Virtual)?(?:Machine)?(\w+?)(?:Card|EthernetCard|Device|Controller)?$`)
   965  
   966  func (l VirtualDeviceList) deviceName(device types.BaseVirtualDevice) string {
   967  	name := "device"
   968  	typeName := l.TypeName(device)
   969  
   970  	m := deviceNameRegexp.FindStringSubmatch(typeName)
   971  	if len(m) == 2 {
   972  		name = strings.ToLower(m[1])
   973  	}
   974  
   975  	return name
   976  }
   977  
   978  // Type returns a human-readable name for the given device
   979  func (l VirtualDeviceList) Type(device types.BaseVirtualDevice) string {
   980  	switch device.(type) {
   981  	case types.BaseVirtualEthernetCard:
   982  		return DeviceTypeEthernet
   983  	case *types.ParaVirtualSCSIController:
   984  		return "pvscsi"
   985  	case *types.VirtualLsiLogicSASController:
   986  		return "lsilogic-sas"
   987  	case *types.VirtualPrecisionClock:
   988  		return "clock"
   989  	default:
   990  		return l.deviceName(device)
   991  	}
   992  }
   993  
   994  // Name returns a stable, human-readable name for the given device
   995  func (l VirtualDeviceList) Name(device types.BaseVirtualDevice) string {
   996  	var key string
   997  	var UnitNumber int32
   998  	d := device.GetVirtualDevice()
   999  	if d.UnitNumber != nil {
  1000  		UnitNumber = *d.UnitNumber
  1001  	}
  1002  
  1003  	dtype := l.Type(device)
  1004  	switch dtype {
  1005  	case DeviceTypeEthernet:
  1006  		// Ethernet devices of UnitNumber 7-19 are non-SRIOV. Ethernet devices of
  1007  		// UnitNumber 45-36 descending are SRIOV
  1008  		if UnitNumber <= 45 && UnitNumber >= 36 {
  1009  			key = fmt.Sprintf("sriov-%d", 45-UnitNumber)
  1010  		} else {
  1011  			key = fmt.Sprintf("%d", UnitNumber-7)
  1012  		}
  1013  	case DeviceTypeDisk:
  1014  		key = fmt.Sprintf("%d-%d", d.ControllerKey, UnitNumber)
  1015  	default:
  1016  		key = fmt.Sprintf("%d", d.Key)
  1017  	}
  1018  
  1019  	return fmt.Sprintf("%s-%s", dtype, key)
  1020  }
  1021  
  1022  // ConfigSpec creates a virtual machine configuration spec for
  1023  // the specified operation, for the list of devices in the device list.
  1024  func (l VirtualDeviceList) ConfigSpec(op types.VirtualDeviceConfigSpecOperation) ([]types.BaseVirtualDeviceConfigSpec, error) {
  1025  	var fop types.VirtualDeviceConfigSpecFileOperation
  1026  	switch op {
  1027  	case types.VirtualDeviceConfigSpecOperationAdd:
  1028  		fop = types.VirtualDeviceConfigSpecFileOperationCreate
  1029  	case types.VirtualDeviceConfigSpecOperationEdit:
  1030  		fop = types.VirtualDeviceConfigSpecFileOperationReplace
  1031  	case types.VirtualDeviceConfigSpecOperationRemove:
  1032  		fop = types.VirtualDeviceConfigSpecFileOperationDestroy
  1033  	default:
  1034  		panic("unknown op")
  1035  	}
  1036  
  1037  	var res []types.BaseVirtualDeviceConfigSpec
  1038  	for _, device := range l {
  1039  		config := &types.VirtualDeviceConfigSpec{
  1040  			Device:        device,
  1041  			Operation:     op,
  1042  			FileOperation: diskFileOperation(op, fop, device),
  1043  		}
  1044  
  1045  		res = append(res, config)
  1046  	}
  1047  
  1048  	return res, nil
  1049  }