terraform/vendor/github.com/vmware/govmomi/object/virtual_device_list.go

755 lines
22 KiB
Go

/*
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"errors"
"fmt"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"github.com/vmware/govmomi/vim25/types"
)
// Type values for use in BootOrder
const (
DeviceTypeCdrom = "cdrom"
DeviceTypeDisk = "disk"
DeviceTypeEthernet = "ethernet"
DeviceTypeFloppy = "floppy"
)
// VirtualDeviceList provides helper methods for working with a list of virtual devices.
type VirtualDeviceList []types.BaseVirtualDevice
// SCSIControllerTypes are used for adding a new SCSI controller to a VM.
func SCSIControllerTypes() VirtualDeviceList {
// Return a mutable list of SCSI controller types, initialized with defaults.
return VirtualDeviceList([]types.BaseVirtualDevice{
&types.VirtualLsiLogicController{},
&types.VirtualBusLogicController{},
&types.ParaVirtualSCSIController{},
&types.VirtualLsiLogicSASController{},
}).Select(func(device types.BaseVirtualDevice) bool {
c := device.(types.BaseVirtualSCSIController).GetVirtualSCSIController()
c.SharedBus = types.VirtualSCSISharingNoSharing
c.BusNumber = -1
return true
})
}
// EthernetCardTypes are used for adding a new ethernet card to a VM.
func EthernetCardTypes() VirtualDeviceList {
return VirtualDeviceList([]types.BaseVirtualDevice{
&types.VirtualE1000{},
&types.VirtualE1000e{},
&types.VirtualVmxnet3{},
}).Select(func(device types.BaseVirtualDevice) bool {
c := device.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard()
c.AddressType = string(types.VirtualEthernetCardMacTypeGenerated)
c.GetVirtualDevice().Key = -1
return true
})
}
// Select returns a new list containing all elements of the list for which the given func returns true.
func (l VirtualDeviceList) Select(f func(device types.BaseVirtualDevice) bool) VirtualDeviceList {
var found VirtualDeviceList
for _, device := range l {
if f(device) {
found = append(found, device)
}
}
return found
}
// SelectByType returns a new list with devices that are equal to or extend the given type.
func (l VirtualDeviceList) SelectByType(deviceType types.BaseVirtualDevice) VirtualDeviceList {
dtype := reflect.TypeOf(deviceType)
dname := dtype.Elem().Name()
return l.Select(func(device types.BaseVirtualDevice) bool {
t := reflect.TypeOf(device)
if t == dtype {
return true
}
_, ok := t.Elem().FieldByName(dname)
return ok
})
}
// SelectByBackingInfo returns a new list with devices matching the given backing info.
// If the value of backing is nil, any device with a backing of the same type will be returned.
func (l VirtualDeviceList) SelectByBackingInfo(backing types.BaseVirtualDeviceBackingInfo) VirtualDeviceList {
t := reflect.TypeOf(backing)
return l.Select(func(device types.BaseVirtualDevice) bool {
db := device.GetVirtualDevice().Backing
if db == nil {
return false
}
if reflect.TypeOf(db) != t {
return false
}
if reflect.ValueOf(backing).IsNil() {
// selecting by backing type
return true
}
switch a := db.(type) {
case *types.VirtualEthernetCardNetworkBackingInfo:
b := backing.(*types.VirtualEthernetCardNetworkBackingInfo)
return a.DeviceName == b.DeviceName
case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo:
b := backing.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo)
return a.Port.SwitchUuid == b.Port.SwitchUuid
case *types.VirtualDiskFlatVer2BackingInfo:
b := backing.(*types.VirtualDiskFlatVer2BackingInfo)
if a.Parent != nil && b.Parent != nil {
return a.Parent.FileName == b.Parent.FileName
}
return a.FileName == b.FileName
case *types.VirtualSerialPortURIBackingInfo:
b := backing.(*types.VirtualSerialPortURIBackingInfo)
return a.ServiceURI == b.ServiceURI
case types.BaseVirtualDeviceFileBackingInfo:
b := backing.(types.BaseVirtualDeviceFileBackingInfo)
return a.GetVirtualDeviceFileBackingInfo().FileName == b.GetVirtualDeviceFileBackingInfo().FileName
default:
return false
}
})
}
// Find returns the device matching the given name.
func (l VirtualDeviceList) Find(name string) types.BaseVirtualDevice {
for _, device := range l {
if l.Name(device) == name {
return device
}
}
return nil
}
// FindByKey returns the device matching the given key.
func (l VirtualDeviceList) FindByKey(key int) types.BaseVirtualDevice {
for _, device := range l {
if device.GetVirtualDevice().Key == key {
return device
}
}
return nil
}
// FindIDEController will find the named IDE controller if given, otherwise will pick an available controller.
// An error is returned if the named controller is not found or not an IDE controller. Or, if name is not
// given and no available controller can be found.
func (l VirtualDeviceList) FindIDEController(name string) (*types.VirtualIDEController, error) {
if name != "" {
d := l.Find(name)
if d == nil {
return nil, fmt.Errorf("device '%s' not found", name)
}
if c, ok := d.(*types.VirtualIDEController); ok {
return c, nil
}
return nil, fmt.Errorf("%s is not an IDE controller", name)
}
c := l.PickController((*types.VirtualIDEController)(nil))
if c == nil {
return nil, errors.New("no available IDE controller")
}
return c.(*types.VirtualIDEController), nil
}
// FindSCSIController will find the named SCSI controller if given, otherwise will pick an available controller.
// An error is returned if the named controller is not found or not an SCSI controller. Or, if name is not
// given and no available controller can be found.
func (l VirtualDeviceList) FindSCSIController(name string) (*types.VirtualSCSIController, error) {
if name != "" {
d := l.Find(name)
if d == nil {
return nil, fmt.Errorf("device '%s' not found", name)
}
if c, ok := d.(types.BaseVirtualSCSIController); ok {
return c.GetVirtualSCSIController(), nil
}
return nil, fmt.Errorf("%s is not an SCSI controller", name)
}
c := l.PickController((*types.VirtualSCSIController)(nil))
if c == nil {
return nil, errors.New("no available SCSI controller")
}
return c.(types.BaseVirtualSCSIController).GetVirtualSCSIController(), nil
}
// CreateSCSIController creates a new SCSI controller of type name if given, otherwise defaults to lsilogic.
func (l VirtualDeviceList) CreateSCSIController(name string) (types.BaseVirtualDevice, error) {
ctypes := SCSIControllerTypes()
if name == "scsi" || name == "" {
name = ctypes.Type(ctypes[0])
}
found := ctypes.Select(func(device types.BaseVirtualDevice) bool {
return l.Type(device) == name
})
if len(found) == 0 {
return nil, fmt.Errorf("unknown SCSI controller type '%s'", name)
}
c, ok := found[0].(types.BaseVirtualSCSIController)
if !ok {
return nil, fmt.Errorf("invalid SCSI controller type '%s'", name)
}
scsi := c.GetVirtualSCSIController()
scsi.BusNumber = l.newSCSIBusNumber()
return c.(types.BaseVirtualDevice), nil
}
var scsiBusNumbers = []int{0, 1, 2, 3}
// newSCSIBusNumber returns the bus number to use for adding a new SCSI bus device.
// -1 is returned if there are no bus numbers available.
func (l VirtualDeviceList) newSCSIBusNumber() int {
var used []int
for _, d := range l.SelectByType((*types.VirtualSCSIController)(nil)) {
num := d.(types.BaseVirtualSCSIController).GetVirtualSCSIController().BusNumber
if num >= 0 {
used = append(used, num)
} // else caller is creating a new vm using SCSIControllerTypes
}
sort.Ints(used)
for i, n := range scsiBusNumbers {
if i == len(used) || n != used[i] {
return n
}
}
return -1
}
// FindDiskController will find an existing ide or scsi disk controller.
func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualController, error) {
switch {
case name == "ide":
return l.FindIDEController("")
case name == "scsi" || name == "":
return l.FindSCSIController("")
default:
if c, ok := l.Find(name).(types.BaseVirtualController); ok {
return c, nil
}
return nil, fmt.Errorf("%s is not a valid controller", name)
}
}
// PickController returns a controller of the given type(s).
// If no controllers are found or have no available slots, then nil is returned.
func (l VirtualDeviceList) PickController(kind types.BaseVirtualController) types.BaseVirtualController {
l = l.SelectByType(kind.(types.BaseVirtualDevice)).Select(func(device types.BaseVirtualDevice) bool {
num := len(device.(types.BaseVirtualController).GetVirtualController().Device)
switch device.(type) {
case types.BaseVirtualSCSIController:
return num < 15
case *types.VirtualIDEController:
return num < 2
default:
return true
}
})
if len(l) == 0 {
return nil
}
return l[0].(types.BaseVirtualController)
}
// newUnitNumber returns the unit number to use for attaching a new device to the given controller.
func (l VirtualDeviceList) newUnitNumber(c types.BaseVirtualController) int {
key := c.GetVirtualController().Key
max := -1
for _, device := range l {
d := device.GetVirtualDevice()
if d.ControllerKey == key {
if d.UnitNumber > max {
max = d.UnitNumber
}
}
}
return max + 1
}
// AssignController assigns a device to a controller.
func (l VirtualDeviceList) AssignController(device types.BaseVirtualDevice, c types.BaseVirtualController) {
d := device.GetVirtualDevice()
d.ControllerKey = c.GetVirtualController().Key
d.UnitNumber = l.newUnitNumber(c)
d.Key = -1
}
// CreateDisk creates a new VirtualDisk device which can be added to a VM.
func (l VirtualDeviceList) CreateDisk(c types.BaseVirtualController, name string) *types.VirtualDisk {
// If name is not specified, one will be chosen for you.
// But if when given, make sure it ends in .vmdk, otherwise it will be treated as a directory.
if len(name) > 0 && filepath.Ext(name) != ".vmdk" {
name += ".vmdk"
}
device := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(true),
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: name,
},
},
},
}
l.AssignController(device, c)
if device.UnitNumber == 0 {
device.UnitNumber = -1 // TODO: this field is annotated as omitempty
}
return device
}
// ChildDisk creates a new VirtualDisk device, linked to the given parent disk, which can be added to a VM.
func (l VirtualDeviceList) ChildDisk(parent *types.VirtualDisk) *types.VirtualDisk {
disk := *parent
backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
ds := strings.SplitN(backing.FileName[1:], "]", 2)
// Use specified disk as parent backing to a new disk.
disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: fmt.Sprintf("[%s]", ds[0]),
},
Parent: backing,
DiskMode: backing.DiskMode,
ThinProvisioned: backing.ThinProvisioned,
}
return &disk
}
func (l VirtualDeviceList) connectivity(device types.BaseVirtualDevice, v bool) error {
c := device.GetVirtualDevice().Connectable
if c == nil {
return fmt.Errorf("%s is not connectable", l.Name(device))
}
c.Connected = v
c.StartConnected = v
return nil
}
// Connect changes the device to connected, returns an error if the device is not connectable.
func (l VirtualDeviceList) Connect(device types.BaseVirtualDevice) error {
return l.connectivity(device, true)
}
// Disconnect changes the device to disconnected, returns an error if the device is not connectable.
func (l VirtualDeviceList) Disconnect(device types.BaseVirtualDevice) error {
return l.connectivity(device, false)
}
// FindCdrom finds a cdrom device with the given name, defaulting to the first cdrom device if any.
func (l VirtualDeviceList) FindCdrom(name string) (*types.VirtualCdrom, error) {
if name != "" {
d := l.Find(name)
if d == nil {
return nil, fmt.Errorf("device '%s' not found", name)
}
if c, ok := d.(*types.VirtualCdrom); ok {
return c, nil
}
return nil, fmt.Errorf("%s is not a cdrom device", name)
}
c := l.SelectByType((*types.VirtualCdrom)(nil))
if len(c) == 0 {
return nil, errors.New("no cdrom device found")
}
return c[0].(*types.VirtualCdrom), nil
}
// CreateCdrom creates a new VirtualCdrom device which can be added to a VM.
func (l VirtualDeviceList) CreateCdrom(c *types.VirtualIDEController) (*types.VirtualCdrom, error) {
device := &types.VirtualCdrom{}
l.AssignController(device, c)
l.setDefaultCdromBacking(device)
device.Connectable = &types.VirtualDeviceConnectInfo{
AllowGuestControl: true,
Connected: true,
StartConnected: true,
}
return device, nil
}
// InsertIso changes the cdrom device backing to use the given iso file.
func (l VirtualDeviceList) InsertIso(device *types.VirtualCdrom, iso string) *types.VirtualCdrom {
device.Backing = &types.VirtualCdromIsoBackingInfo{
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: iso,
},
}
return device
}
// EjectIso removes the iso file based backing and replaces with the default cdrom backing.
func (l VirtualDeviceList) EjectIso(device *types.VirtualCdrom) *types.VirtualCdrom {
l.setDefaultCdromBacking(device)
return device
}
func (l VirtualDeviceList) setDefaultCdromBacking(device *types.VirtualCdrom) {
device.Backing = &types.VirtualCdromAtapiBackingInfo{
VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
DeviceName: fmt.Sprintf("%s-%d-%d", DeviceTypeCdrom, device.ControllerKey, device.UnitNumber),
UseAutoDetect: types.NewBool(false),
},
}
}
// FindFloppy finds a floppy device with the given name, defaulting to the first floppy device if any.
func (l VirtualDeviceList) FindFloppy(name string) (*types.VirtualFloppy, error) {
if name != "" {
d := l.Find(name)
if d == nil {
return nil, fmt.Errorf("device '%s' not found", name)
}
if c, ok := d.(*types.VirtualFloppy); ok {
return c, nil
}
return nil, fmt.Errorf("%s is not a floppy device", name)
}
c := l.SelectByType((*types.VirtualFloppy)(nil))
if len(c) == 0 {
return nil, errors.New("no floppy device found")
}
return c[0].(*types.VirtualFloppy), nil
}
// CreateFloppy creates a new VirtualFloppy device which can be added to a VM.
func (l VirtualDeviceList) CreateFloppy() (*types.VirtualFloppy, error) {
device := &types.VirtualFloppy{}
c := l.PickController((*types.VirtualSIOController)(nil))
if c == nil {
return nil, errors.New("no available SIO controller")
}
l.AssignController(device, c)
l.setDefaultFloppyBacking(device)
device.Connectable = &types.VirtualDeviceConnectInfo{
AllowGuestControl: true,
Connected: true,
StartConnected: true,
}
return device, nil
}
// InsertImg changes the floppy device backing to use the given img file.
func (l VirtualDeviceList) InsertImg(device *types.VirtualFloppy, img string) *types.VirtualFloppy {
device.Backing = &types.VirtualFloppyImageBackingInfo{
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: img,
},
}
return device
}
// EjectImg removes the img file based backing and replaces with the default floppy backing.
func (l VirtualDeviceList) EjectImg(device *types.VirtualFloppy) *types.VirtualFloppy {
l.setDefaultFloppyBacking(device)
return device
}
func (l VirtualDeviceList) setDefaultFloppyBacking(device *types.VirtualFloppy) {
device.Backing = &types.VirtualFloppyDeviceBackingInfo{
VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
DeviceName: fmt.Sprintf("%s-%d", DeviceTypeFloppy, device.UnitNumber),
UseAutoDetect: types.NewBool(false),
},
}
}
// FindSerialPort finds a serial port device with the given name, defaulting to the first serial port device if any.
func (l VirtualDeviceList) FindSerialPort(name string) (*types.VirtualSerialPort, error) {
if name != "" {
d := l.Find(name)
if d == nil {
return nil, fmt.Errorf("device '%s' not found", name)
}
if c, ok := d.(*types.VirtualSerialPort); ok {
return c, nil
}
return nil, fmt.Errorf("%s is not a serial port device", name)
}
c := l.SelectByType((*types.VirtualSerialPort)(nil))
if len(c) == 0 {
return nil, errors.New("no serial port device found")
}
return c[0].(*types.VirtualSerialPort), nil
}
// CreateSerialPort creates a new VirtualSerialPort device which can be added to a VM.
func (l VirtualDeviceList) CreateSerialPort() (*types.VirtualSerialPort, error) {
device := &types.VirtualSerialPort{
YieldOnPoll: true,
}
c := l.PickController((*types.VirtualSIOController)(nil))
if c == nil {
return nil, errors.New("no available SIO controller")
}
l.AssignController(device, c)
l.setDefaultSerialPortBacking(device)
return device, nil
}
// ConnectSerialPort connects a serial port to a server or client uri.
func (l VirtualDeviceList) ConnectSerialPort(device *types.VirtualSerialPort, uri string, client bool) *types.VirtualSerialPort {
direction := types.VirtualDeviceURIBackingOptionDirectionServer
if client {
direction = types.VirtualDeviceURIBackingOptionDirectionClient
}
device.Backing = &types.VirtualSerialPortURIBackingInfo{
VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
Direction: string(direction),
ServiceURI: uri,
},
}
return device
}
// DisconnectSerialPort disconnects the serial port backing.
func (l VirtualDeviceList) DisconnectSerialPort(device *types.VirtualSerialPort) *types.VirtualSerialPort {
l.setDefaultSerialPortBacking(device)
return device
}
func (l VirtualDeviceList) setDefaultSerialPortBacking(device *types.VirtualSerialPort) {
device.Backing = &types.VirtualSerialPortURIBackingInfo{
VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
Direction: "client",
ServiceURI: "localhost:0",
},
}
}
// CreateEthernetCard creates a new VirtualEthernetCard of the given name name and initialized with the given backing.
func (l VirtualDeviceList) CreateEthernetCard(name string, backing types.BaseVirtualDeviceBackingInfo) (types.BaseVirtualDevice, error) {
ctypes := EthernetCardTypes()
if name == "" {
name = ctypes.deviceName(ctypes[0])
}
found := ctypes.Select(func(device types.BaseVirtualDevice) bool {
return l.deviceName(device) == name
})
if len(found) == 0 {
return nil, fmt.Errorf("unknown ethernet card type '%s'", name)
}
c, ok := found[0].(types.BaseVirtualEthernetCard)
if !ok {
return nil, fmt.Errorf("invalid ethernet card type '%s'", name)
}
c.GetVirtualEthernetCard().Backing = backing
return c.(types.BaseVirtualDevice), nil
}
// PrimaryMacAddress returns the MacAddress field of the primary VirtualEthernetCard
func (l VirtualDeviceList) PrimaryMacAddress() string {
eth0 := l.Find("ethernet-0")
if eth0 == nil {
return ""
}
return eth0.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard().MacAddress
}
// convert a BaseVirtualDevice to a BaseVirtualMachineBootOptionsBootableDevice
var bootableDevices = map[string]func(device types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice{
DeviceTypeCdrom: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
return &types.VirtualMachineBootOptionsBootableCdromDevice{}
},
DeviceTypeDisk: func(d types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
return &types.VirtualMachineBootOptionsBootableDiskDevice{
DeviceKey: d.GetVirtualDevice().Key,
}
},
DeviceTypeEthernet: func(d types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
return &types.VirtualMachineBootOptionsBootableEthernetDevice{
DeviceKey: d.GetVirtualDevice().Key,
}
},
DeviceTypeFloppy: func(types.BaseVirtualDevice) types.BaseVirtualMachineBootOptionsBootableDevice {
return &types.VirtualMachineBootOptionsBootableFloppyDevice{}
},
}
// BootOrder returns a list of devices which can be used to set boot order via VirtualMachine.SetBootOptions.
// The order can any of "ethernet", "cdrom", "floppy" or "disk" or by specific device name.
func (l VirtualDeviceList) BootOrder(order []string) []types.BaseVirtualMachineBootOptionsBootableDevice {
var devices []types.BaseVirtualMachineBootOptionsBootableDevice
for _, name := range order {
if kind, ok := bootableDevices[name]; ok {
for _, device := range l {
if l.Type(device) == name {
devices = append(devices, kind(device))
}
}
continue
}
if d := l.Find(name); d != nil {
if kind, ok := bootableDevices[l.Type(d)]; ok {
devices = append(devices, kind(d))
}
}
}
return devices
}
// SelectBootOrder returns an ordered list of devices matching the given bootable device order
func (l VirtualDeviceList) SelectBootOrder(order []types.BaseVirtualMachineBootOptionsBootableDevice) VirtualDeviceList {
var devices VirtualDeviceList
for _, bd := range order {
for _, device := range l {
if kind, ok := bootableDevices[l.Type(device)]; ok {
if reflect.DeepEqual(kind(device), bd) {
devices = append(devices, device)
}
}
}
}
return devices
}
// TypeName returns the vmodl type name of the device
func (l VirtualDeviceList) TypeName(device types.BaseVirtualDevice) string {
return reflect.TypeOf(device).Elem().Name()
}
var deviceNameRegexp = regexp.MustCompile(`(?:Virtual)?(?:Machine)?(\w+?)(?:Card|Device|Controller)?$`)
func (l VirtualDeviceList) deviceName(device types.BaseVirtualDevice) string {
name := "device"
typeName := l.TypeName(device)
m := deviceNameRegexp.FindStringSubmatch(typeName)
if len(m) == 2 {
name = strings.ToLower(m[1])
}
return name
}
// Type returns a human-readable name for the given device
func (l VirtualDeviceList) Type(device types.BaseVirtualDevice) string {
switch device.(type) {
case types.BaseVirtualEthernetCard:
return DeviceTypeEthernet
case *types.ParaVirtualSCSIController:
return "pvscsi"
case *types.VirtualLsiLogicSASController:
return "lsilogic-sas"
default:
return l.deviceName(device)
}
}
// Name returns a stable, human-readable name for the given device
func (l VirtualDeviceList) Name(device types.BaseVirtualDevice) string {
var key string
d := device.GetVirtualDevice()
dtype := l.Type(device)
switch dtype {
case DeviceTypeEthernet:
key = fmt.Sprintf("%d", d.UnitNumber-7)
case DeviceTypeDisk:
key = fmt.Sprintf("%d-%d", d.ControllerKey, d.UnitNumber)
default:
key = fmt.Sprintf("%d", d.Key)
}
return fmt.Sprintf("%s-%s", dtype, key)
}