Two drivers from one package, both in the hypervisor role: - vmware_esxi talks to one host directly: facts, vmnics and vmkernel NICs, CDP/LLDP neighbours, sensors, VMs, datastores and port groups. - vmware_vcenter talks to a vCenter: every VM of every host it manages, with the host as the VM's node, plus distributed port groups. It reports no interfaces of its own; the hosts' NICs belong to the hosts. Both implement the HypervisorDriver VM contract: get_vms, get_vm_config, start/stop/reboot/suspend_vm and the four snapshot methods, and emit raw device warnings (maintenance mode, disconnected host, config issues, host managed by a vCenter, free license making the API read-only). Every read is a PropertyCollector query for the explicit paths in paths.py, converted by to_plain() into dicts and lists; the parsers only ever see that. tools/harvest.py dumps exactly those paths to JSON and tools/sanitize.py scrubs the dump, so a real host can become a test fixture without code changes. A VM's vmid is its instance UUID, which survives vMotion and re-registration; a MoRef does not. Tested against govmomi's vcsim in ESXi and vCenter mode, including real power and snapshot tasks. Not yet tested against real hardware.
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""NAPALM interface getters for an ESXi host.
|
|
|
|
A host has two kinds of interface worth reporting: physical uplinks
|
|
(``vmnicN``) and VMkernel adapters (``vmkN``), which carry the host's own IP
|
|
addresses. Port groups are not interfaces; they are reported by
|
|
``get_virtual_networks``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ipaddress
|
|
from typing import Any
|
|
|
|
_DEFAULT_MTU = 1500
|
|
_UNASSIGNED = {"", "0.0.0.0"}
|
|
|
|
|
|
def _uplink_mtus(host: dict[str, Any]) -> dict[str, int]:
|
|
"""pnic key -> MTU of the standard vSwitch that uses it as an uplink."""
|
|
mtus: dict[str, int] = {}
|
|
for vswitch in host.get("config.network.vswitch") or []:
|
|
for pnic_key in vswitch.get("pnic", []):
|
|
mtus[pnic_key] = int(vswitch.get("mtu") or _DEFAULT_MTU)
|
|
return mtus
|
|
|
|
|
|
def _iface(up: bool, description: str, speed: float, mtu: int, mac: str) -> dict[str, Any]:
|
|
return {
|
|
"is_up": up,
|
|
"is_enabled": True,
|
|
"description": description,
|
|
"last_flapped": -1.0,
|
|
"speed": speed,
|
|
"mtu": mtu,
|
|
"mac_address": mac.upper(),
|
|
}
|
|
|
|
|
|
def host_interfaces(host: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
"""vmnics and vmks in the shape of NAPALM's ``get_interfaces``."""
|
|
mtus = _uplink_mtus(host)
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for pnic in host.get("config.network.pnic") or []:
|
|
link = pnic.get("linkSpeed") or {}
|
|
description = " ".join(p for p in (pnic.get("driver"), pnic.get("pci")) if p)
|
|
result[pnic["device"]] = _iface(
|
|
up=bool(link),
|
|
description=description,
|
|
speed=float(link.get("speedMb", 0)),
|
|
mtu=mtus.get(pnic.get("key", ""), _DEFAULT_MTU),
|
|
mac=pnic.get("mac", ""),
|
|
)
|
|
for vnic in host.get("config.network.vnic") or []:
|
|
spec = vnic.get("spec") or {}
|
|
result[vnic["device"]] = _iface(
|
|
up=True,
|
|
description=vnic.get("portgroup") or spec.get("portgroup", ""),
|
|
speed=0.0,
|
|
mtu=int(spec.get("mtu") or _DEFAULT_MTU),
|
|
mac=spec.get("mac", ""),
|
|
)
|
|
return result
|
|
|
|
|
|
def _ipv4(ip: dict[str, Any]) -> dict[str, dict[str, int]]:
|
|
address = ip.get("ipAddress", "")
|
|
if address in _UNASSIGNED:
|
|
return {}
|
|
network = ipaddress.IPv4Network(f"0.0.0.0/{ip.get('subnetMask') or '32'}")
|
|
return {address: {"prefix_length": network.prefixlen}}
|
|
|
|
|
|
def _ipv6(ip: dict[str, Any]) -> dict[str, dict[str, int]]:
|
|
entries = (ip.get("ipV6Config") or {}).get("ipV6Address", [])
|
|
return {e["ipAddress"]: {"prefix_length": int(e.get("prefixLength", 128))} for e in entries}
|
|
|
|
|
|
def host_interfaces_ip(host: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
"""Addresses of the VMkernel adapters, as NAPALM's ``get_interfaces_ip``."""
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for vnic in host.get("config.network.vnic") or []:
|
|
ip = (vnic.get("spec") or {}).get("ip") or {}
|
|
families = {"ipv4": _ipv4(ip), "ipv6": _ipv6(ip)}
|
|
families = {k: v for k, v in families.items() if v}
|
|
if families:
|
|
result[vnic["device"]] = families
|
|
return result
|