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