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