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