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