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,57 @@
|
||||
"""Convert pyVmomi objects into plain, JSON-safe Python values.
|
||||
|
||||
The drivers never parse pyVmomi objects directly. Everything fetched from the
|
||||
vSphere API goes through :func:`to_plain` first, so a parser sees the same
|
||||
shape whether its input came from a live host or from a JSON fixture that
|
||||
``tools/harvest.py`` captured.
|
||||
|
||||
Conventions:
|
||||
|
||||
* a managed object (``HostSystem``, ``VirtualMachine`` ...) becomes its MoRef
|
||||
id string, e.g. ``"host-21"``
|
||||
* a data object becomes a dict of its set properties plus ``"_type"``, the
|
||||
WSDL type name (``"VirtualDisk"``) -- device lists are told apart by it
|
||||
* enums become ``str``, datetimes become epoch seconds, binary data ``None``
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import Any
|
||||
|
||||
from pyVmomi import VmomiSupport
|
||||
|
||||
_NOISE = frozenset({"dynamicType", "dynamicProperty"})
|
||||
|
||||
|
||||
def to_plain(value: Any) -> Any:
|
||||
"""Recursively convert ``value`` into dicts, lists and scalars."""
|
||||
if value is None or isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, VmomiSupport.ManagedObject):
|
||||
return value._moId
|
||||
if isinstance(value, VmomiSupport.DataObject):
|
||||
return _data_object(value)
|
||||
if isinstance(value, str):
|
||||
return str(value)
|
||||
if isinstance(value, (int, float)):
|
||||
return value
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value.timestamp()
|
||||
if isinstance(value, (bytes, bytearray)):
|
||||
return None
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [to_plain(v) for v in value]
|
||||
return str(value)
|
||||
|
||||
|
||||
def _data_object(obj: Any) -> dict[str, Any]:
|
||||
out: dict[str, Any] = {"_type": obj._wsdlName}
|
||||
for prop in obj._GetPropertyList():
|
||||
if prop.name in _NOISE:
|
||||
continue
|
||||
val = getattr(obj, prop.name, None)
|
||||
if val is None or (isinstance(val, list) and not val):
|
||||
continue
|
||||
out[prop.name] = to_plain(val)
|
||||
return out
|
||||
Reference in New Issue
Block a user