Source code for saltext.vcf.clients.vim_vm_nic

"""VM NIC lifecycle via SOAP ``VirtualMachine.ReconfigVM_Task``."""

from pyVmomi import vim

from saltext.vcf.utils import vim as soap

_NIC_TYPES = {
    "vmxnet3": vim.vm.device.VirtualVmxnet3,
    "vmxnet2": vim.vm.device.VirtualVmxnet2,
    "e1000": vim.vm.device.VirtualE1000,
    "e1000e": vim.vm.device.VirtualE1000e,
    "pcnet32": vim.vm.device.VirtualPCNet32,
    "sriov": vim.vm.device.VirtualSriovEthernetCard,
}


def _vm(opts, vm_id_or_name, profile=None):
    content = soap.content(opts, profile=profile)
    container = content.viewManager.CreateContainerView(
        content.rootFolder, [vim.VirtualMachine], True
    )
    try:
        for vm in container.view:
            if vm_id_or_name in (vm._moId, vm.name):  # noqa: SLF001
                return vm
    finally:
        container.Destroy()
    raise LookupError(f"VM {vm_id_or_name!r} not found")


def _find_network(opts, name_or_id, profile=None):
    """Locate a ``vim.Network`` by MOID or name.

    Standard port groups surface as ``vim.Network`` in the datacenter's
    ``networkFolder`` on both ESXi and vCenter.  Callers that pass a
    port-group name get resolved to the underlying MOID here.
    """
    content = soap.content(opts, profile=profile)
    container = content.viewManager.CreateContainerView(content.rootFolder, [vim.Network], True)
    try:
        for net in container.view:
            if name_or_id in (net._moId, net.name):  # noqa: SLF001
                return net
    finally:
        container.Destroy()
    raise LookupError(f"network {name_or_id!r} not found")


[docs] def list_(opts, vm_id_or_name, profile=None): """Return every Ethernet device on the VM as a list of dicts.""" vm = _vm(opts, vm_id_or_name, profile=profile) out = [] for dev in vm.config.hardware.device or []: if not isinstance(dev, vim.vm.device.VirtualEthernetCard): continue backing = dev.backing backing_kind = type(backing).__name__ if backing else None network = getattr(backing, "network", None) portgroup_key = getattr(getattr(backing, "port", None), "portgroupKey", None) out.append( { "key": dev.key, "label": dev.deviceInfo.label, "summary": dev.deviceInfo.summary, "mac_address": dev.macAddress, "mac_type": dev.addressType, "device_type": type(dev).__name__, "connected": dev.connectable.connected if dev.connectable else None, "start_connected": (dev.connectable.startConnected if dev.connectable else None), "backing_kind": backing_kind, "network_moid": network._moId if network else None, # noqa: SLF001 "portgroup_key": portgroup_key, } ) return out
[docs] def add( opts, vm_id_or_name, *, nic_type="vmxnet3", network=None, network_moid=None, portgroup_key=None, dvs_uuid=None, mac_address=None, start_connected=True, profile=None, ): """Add a new NIC. Backing options: * *network* — legacy port-group name (looked up via ``vim.Network``). Convenience for callers who only know the port-group label — typical for standalone-ESXi and single-vSwitch setups. * *network_moid* — legacy port group by MOID (bypasses the lookup). * *portgroup_key* + *dvs_uuid* — distributed port group. """ # Port groups on a standalone-ESXi standard vSwitch that only # carry VMkernel traffic (no VMs) are NOT advertised as # ``vim.Network`` MOs. ``_find_network`` will miss them. Fall # back to a ``deviceName``-only backing (no ``network`` MO # reference) — ESXi resolves the port group by name at attach # time. Works for both regular port groups and vmk-only ones. network_obj = None device_name = None if network and not network_moid: try: network_obj = _find_network(opts, network, profile=profile) network_moid = network_obj._moId # noqa: SLF001 except LookupError: device_name = network if ( not network_obj and not network_moid and not device_name and not (portgroup_key and dvs_uuid) ): raise ValueError("provide network OR network_moid OR (portgroup_key AND dvs_uuid)") nic_cls = _NIC_TYPES.get(nic_type.lower()) if nic_cls is None: raise ValueError(f"unknown nic_type {nic_type!r}; valid: {sorted(_NIC_TYPES)}") nic = nic_cls(key=-1) if portgroup_key and dvs_uuid: backing = vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo() backing.port = vim.dvs.PortConnection(portgroupKey=portgroup_key, switchUuid=dvs_uuid) elif device_name is not None: # deviceName-only path: ESXi resolves by name. Used for # port groups that don't have a vim.Network MO (e.g. # vmk-only port groups on standalone ESXi). backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo() backing.deviceName = device_name else: # NetworkBackingInfo needs the actual vim.Network managed # object reference, not a synthesised one — ESXi rejects a # bare-MoID Network with "Invalid configuration for device". if network_obj is None: network_obj = _find_network(opts, network_moid, profile=profile) backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo() backing.network = network_obj try: backing.deviceName = network_obj.name except AttributeError: backing.deviceName = "" nic.backing = backing if mac_address: nic.macAddress = mac_address nic.addressType = "manual" else: # 'generated' works on both ESXi and vCenter; 'assigned' is # vCenter-only (VC pre-computes the MAC and hands it back). # Standalone ESXi rejects 'assigned' with "Invalid # configuration for device 0". nic.addressType = "generated" nic.connectable = vim.vm.device.VirtualDevice.ConnectInfo( startConnected=start_connected, allowGuestControl=True ) spec = vim.vm.ConfigSpec( deviceChange=[vim.vm.device.VirtualDeviceSpec(operation="add", device=nic)] ) vm = _vm(opts, vm_id_or_name, profile=profile) task = vm.ReconfigVM_Task(spec=spec) soap.wait_for_task(task) return task._moId # noqa: SLF001
[docs] def update_backing( opts, vm_id_or_name, nic_key, *, network_moid=None, portgroup_key=None, dvs_uuid=None, profile=None, ): """Reattach an existing NIC to a different network / port group.""" vm = _vm(opts, vm_id_or_name, profile=profile) nic = _find_nic(vm, nic_key) if portgroup_key and dvs_uuid: backing = vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo() backing.port = vim.dvs.PortConnection(portgroupKey=portgroup_key, switchUuid=dvs_uuid) elif network_moid: backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo() backing.network = vim.Network(network_moid, None) backing.deviceName = "" else: raise ValueError("provide network_moid OR (portgroup_key AND dvs_uuid)") nic.backing = backing spec = vim.vm.ConfigSpec( deviceChange=[vim.vm.device.VirtualDeviceSpec(operation="edit", device=nic)] ) task = vm.ReconfigVM_Task(spec=spec) soap.wait_for_task(task) return task._moId # noqa: SLF001
[docs] def set_connected(opts, vm_id_or_name, nic_key, connected, profile=None): """Hot-toggle a NIC's connected state.""" vm = _vm(opts, vm_id_or_name, profile=profile) nic = _find_nic(vm, nic_key) if nic.connectable is None: nic.connectable = vim.vm.device.VirtualDevice.ConnectInfo() nic.connectable.connected = bool(connected) nic.connectable.startConnected = bool(connected) spec = vim.vm.ConfigSpec( deviceChange=[vim.vm.device.VirtualDeviceSpec(operation="edit", device=nic)] ) task = vm.ReconfigVM_Task(spec=spec) soap.wait_for_task(task) return task._moId # noqa: SLF001
def remove(opts, vm_id_or_name, nic_key, profile=None): vm = _vm(opts, vm_id_or_name, profile=profile) nic = _find_nic(vm, nic_key) spec = vim.vm.ConfigSpec( deviceChange=[vim.vm.device.VirtualDeviceSpec(operation="remove", device=nic)] ) task = vm.ReconfigVM_Task(spec=spec) soap.wait_for_task(task) return task._moId # noqa: SLF001 def _find_nic(vm, key): for dev in vm.config.hardware.device or []: if isinstance(dev, vim.vm.device.VirtualEthernetCard) and dev.key == int(key): return dev raise LookupError(f"NIC key {key!r} not found on VM {vm.name!r}")