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,53 @@
|
||||
"""Raw device warnings: ``{"code", "meta"}`` only.
|
||||
|
||||
What a code means to a user -- severity, wording, the fix -- is decided in
|
||||
netOrk's ``WARNING_CATALOG``, never here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
_CONNECTED = "connected"
|
||||
_UNMANAGED = {None, "", "0.0.0.0"}
|
||||
#: License edition of the free vSphere Hypervisor. The vSphere API refuses
|
||||
#: every write on it (power, snapshots) with a RestrictedVersion fault.
|
||||
_FREE_EDITIONS = ("esxBasic",)
|
||||
|
||||
|
||||
def _host_codes(host: dict[str, Any], standalone: bool) -> list[dict[str, Any]]:
|
||||
name = host.get("name", host.get("_moref", ""))
|
||||
found: list[dict[str, Any]] = []
|
||||
state = host.get("runtime.connectionState", _CONNECTED)
|
||||
if not standalone and state != _CONNECTED:
|
||||
found.append({"code": "vmware_host_disconnected", "meta": {"host": name, "state": state}})
|
||||
if host.get("runtime.inMaintenanceMode"):
|
||||
found.append({"code": "vmware_host_maintenance_mode", "meta": {"host": name}})
|
||||
issues = [
|
||||
i["fullFormattedMessage"]
|
||||
for i in host.get("configIssue") or []
|
||||
if i.get("fullFormattedMessage")
|
||||
]
|
||||
if issues:
|
||||
found.append({"code": "vmware_host_config_issue", "meta": {"host": name, "issues": issues}})
|
||||
manager = host.get("summary.managementServerIp")
|
||||
if standalone and manager not in _UNMANAGED:
|
||||
found.append({"code": "vmware_host_managed_by_vcenter", "meta": {"vcenter": manager}})
|
||||
return found
|
||||
|
||||
|
||||
def host_warnings(
|
||||
hosts: list[dict[str, Any]], licenses: list[dict[str, Any]], *, standalone: bool
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Warnings for the hosts this driver sees.
|
||||
|
||||
``standalone`` is True for the ESXi driver. It alone reports a host being
|
||||
managed by a vCenter -- a hint to add the vCenter instead -- and it never
|
||||
reports disconnection, since a disconnected host could not have answered.
|
||||
"""
|
||||
found = [w for host in hosts for w in _host_codes(host, standalone)]
|
||||
for lic in licenses:
|
||||
if str(lic.get("editionKey", "")).startswith(_FREE_EDITIONS):
|
||||
found.append({"code": "vmware_api_read_only", "meta": {"edition": lic.get("name", "")}})
|
||||
break
|
||||
return found
|
||||
Reference in New Issue
Block a user