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