Files
Christian Manivong c8e472b4c6 feat: NAPALM drivers for VMware ESXi and vCenter
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.
2026-09-24 09:07:30 +02:00

66 lines
1.9 KiB
Python

"""Classify and read entries of a VM's ``config.hardware.device`` list."""
from __future__ import annotations
import re
from typing import Any
_NIC_TYPES = frozenset(
{
"VirtualE1000",
"VirtualE1000e",
"VirtualPCNet32",
"VirtualVmxnet",
"VirtualVmxnet2",
"VirtualVmxnet3",
"VirtualVmxnet3Vrdma",
"VirtualSriovEthernetCard",
}
)
_PASSTHROUGH_KIND = {"VirtualPCIPassthrough": "pci", "VirtualUSB": "usb"}
_DATASTORE_IN_PATH = re.compile(r"^\[([^\]]+)\]")
def devices(vm: dict[str, Any]) -> list[dict[str, Any]]:
return vm.get("config.hardware.device") or []
def is_nic(device: dict[str, Any]) -> bool:
return device.get("_type") in _NIC_TYPES
def is_disk(device: dict[str, Any]) -> bool:
return device.get("_type") == "VirtualDisk"
def passthrough_kind(device: dict[str, Any]) -> str | None:
return _PASSTHROUGH_KIND.get(device.get("_type", ""))
def label(device: dict[str, Any]) -> str:
"""The name vSphere shows for a device (``"Hard disk 1"``), else its key."""
info = device.get("deviceInfo") or {}
return info.get("label") or str(device.get("key", ""))
def summary(device: dict[str, Any]) -> str:
return (device.get("deviceInfo") or {}).get("summary", "")
def nic_model(device: dict[str, Any]) -> str:
"""``"vmxnet3"``, ``"e1000e"`` ... -- the type name without ``Virtual``."""
return device.get("_type", "").removeprefix("Virtual").lower()
def disk_bytes(device: dict[str, Any]) -> int:
if device.get("capacityInBytes"):
return int(device["capacityInBytes"])
return int(device.get("capacityInKB", 0)) * 1024
def disk_datastore(device: dict[str, Any]) -> str:
"""Datastore name from the backing file path ``"[datastore1] vm/vm.vmdk"``."""
path = (device.get("backing") or {}).get("fileName", "")
match = _DATASTORE_IN_PATH.match(path)
return match.group(1) if match else ""