"""Port groups: what exists, which VLAN each carries, and which one a NIC uses. Two kinds of network can back a VM NIC. A *standard* port group lives on one host's vSwitch and is named in the NIC's backing directly. A *distributed* port group lives on a vCenter-managed vDS, and the NIC names it by key (``dvportgroup-12``) -- so its display name and VLAN have to be looked up. """ from __future__ import annotations import re from dataclasses import dataclass, field from typing import Any #: vCenter's default name for a vDS's uplink port group, used when the server #: is too old to report ``config.uplink``. _UPLINK_NAME = re.compile(r"-DVUplinks-\d+$") @dataclass class NetworkIndex: """Port group name -> VLAN, and dvPortgroup key -> (name, VLAN).""" standard: dict[str, int] = field(default_factory=dict) distributed: dict[str, tuple[str, int]] = field(default_factory=dict) def dv_vlan(port_config: dict[str, Any] | None) -> int: """The single VLAN of a dvPortgroup, or 0 for untagged/trunk.""" vlan = (port_config or {}).get("vlan") or {} kind = vlan.get("_type", "") if kind.endswith("PvlanSpec"): return int(vlan.get("pvlanId", 0)) if kind.endswith("VlanIdSpec"): return int(vlan.get("vlanId", 0)) return 0 def _standard_portgroups(hosts: list[dict[str, Any]]) -> list[dict[str, Any]]: return [ pg.get("spec") or {} for host in hosts for pg in host.get("config.network.portgroup") or [] ] def network_index(hosts: list[dict[str, Any]], dv_portgroups: list[dict[str, Any]]) -> NetworkIndex: index = NetworkIndex() for spec in _standard_portgroups(hosts): index.standard.setdefault(spec.get("name", ""), int(spec.get("vlanId", 0))) for pg in dv_portgroups: vlan = dv_vlan(pg.get("config.defaultPortConfig")) index.distributed[pg["_moref"]] = (pg.get("name", pg["_moref"]), vlan) return index def nic_network(nic: dict[str, Any], index: NetworkIndex) -> tuple[str, int]: """``(network name, VLAN)`` a virtual NIC is connected to.""" backing = nic.get("backing") or {} kind = backing.get("_type", "") if kind.endswith("DistributedVirtualPortBackingInfo"): key = (backing.get("port") or {}).get("portgroupKey", "") return index.distributed.get(key, (key, 0)) if kind.endswith("OpaqueNetworkBackingInfo"): return backing.get("opaqueNetworkId", ""), 0 name = backing.get("deviceName", "") return name, index.standard.get(name, 0) def _network(name: str, kind: str, bridge: str, vlan: int) -> dict[str, Any]: return { "name": name, "type": kind, "bridge": bridge, "vlan_id": vlan, "autostart": True, "active": True, } def _is_uplink(pg: dict[str, Any]) -> bool: if "config.uplink" in pg: return bool(pg["config.uplink"]) return bool(_UPLINK_NAME.search(pg.get("name", ""))) def virtual_networks( hosts: list[dict[str, Any]], dv_portgroups: list[dict[str, Any]], dv_switches: list[dict[str, Any]], ) -> dict[str, dict[str, Any]]: """Every network a VM can attach to, as ``get_virtual_networks`` returns it.""" result: dict[str, dict[str, Any]] = {} for spec in _standard_portgroups(hosts): name = spec.get("name", "") result.setdefault( name, _network(name, "portgroup", spec.get("vswitchName", ""), int(spec.get("vlanId", 0))), ) switch_names = {s["_moref"]: s.get("name", s["_moref"]) for s in dv_switches} for pg in dv_portgroups: if _is_uplink(pg): continue dvs = pg.get("config.distributedVirtualSwitch", "") vlan = dv_vlan(pg.get("config.defaultPortConfig")) name = pg.get("name", pg["_moref"]) result[name] = _network(name, "dvportgroup", switch_names.get(dvs, dvs), vlan) return result