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.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""NAPALM driver for a VMware ESXi host, addressed directly."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from napalm_device_types import FingerprintRule
|
||||
from pyVmomi import vim
|
||||
|
||||
from napalm_vmware._plain import to_plain
|
||||
from napalm_vmware._tasks import invoke
|
||||
from napalm_vmware.actions import VmwareActionsMixin
|
||||
from napalm_vmware.base import VmwareBaseDriver
|
||||
from napalm_vmware.parse.facts import esxi_facts
|
||||
from napalm_vmware.parse.interfaces import host_interfaces, host_interfaces_ip
|
||||
from napalm_vmware.parse.lldp import lldp_neighbors
|
||||
|
||||
|
||||
class VmwareEsxiDriver(VmwareActionsMixin, VmwareBaseDriver):
|
||||
"""One ESXi host. Its VMs, vmnics, vmkernel NICs, datastores and port groups."""
|
||||
|
||||
DRIVER_NAME = "vmware_esxi"
|
||||
API_TYPE = "HostAgent"
|
||||
STANDALONE = True
|
||||
# A host reboot runs through hardware POST before ESXi starts its services.
|
||||
REBOOT_SETTLE_SECONDS = 300
|
||||
# Unverified against real hardware: the Host Client landing page says
|
||||
# "VMware ESXi"; a vCenter's does not, and it says "vCenter".
|
||||
HTTP_FINGERPRINT = [
|
||||
FingerprintRule("vmware esxi", weight=9.0, mandatory=True),
|
||||
FingerprintRule("vcenter", weight=9.0, negative=True),
|
||||
]
|
||||
|
||||
def _host(self) -> dict[str, Any]:
|
||||
hosts = self._hosts()
|
||||
if not hosts:
|
||||
raise RuntimeError(f"{self.hostname} shows no host in its inventory")
|
||||
return hosts[0]
|
||||
|
||||
def get_facts(self) -> dict[str, Any]:
|
||||
return esxi_facts(self._inventory.about(), self._host())
|
||||
|
||||
def get_interfaces(self) -> dict[str, dict[str, Any]]:
|
||||
return host_interfaces(self._host())
|
||||
|
||||
def get_interfaces_ip(self) -> dict[str, dict[str, Any]]:
|
||||
return host_interfaces_ip(self._host())
|
||||
|
||||
def get_lldp_neighbors(self) -> dict[str, list[dict[str, str]]]:
|
||||
network_system = self._host().get("configManager.networkSystem")
|
||||
if not network_system:
|
||||
return {}
|
||||
mo = self._mo(vim.host.NetworkSystem, network_system)
|
||||
try:
|
||||
hints = invoke(mo.QueryNetworkHint)
|
||||
except RuntimeError:
|
||||
return {}
|
||||
return lldp_neighbors(to_plain(hints) or [])
|
||||
Reference in New Issue
Block a user