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.
59 lines
2.1 KiB
Python
59 lines
2.1 KiB
Python
"""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 [])
|