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

80 lines
2.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Dump what the drivers read from an ESXi host or vCenter into one JSON file.
Usage::
tools/harvest.py <host> <user> <label> [--port 443] [--verify-ssl]
The password is prompted for (or taken from ``$VSPHERE_PASSWORD``). Output
goes to ``tools/harvest-out/<label>.json``, which is gitignored: it holds
serial numbers, MACs, IPs and names. Run ``tools/sanitize.py`` on it and read
the result before anything goes into ``tests/fixtures/``.
The file has the shape the test fake (``tests/fake_inventory.py``) serves::
{"about": {...}, "licenses": [...], "objects": {"HostSystem": [...], ...}}
It reads exactly the property paths in ``napalm_vmware.paths``, the same ones
the drivers ask for, so a fixture never contains a field no parser uses and
never lacks one a parser needs.
"""
from __future__ import annotations
import argparse
import getpass
import json
import os
import ssl
import sys
from pathlib import Path
from pyVim.connect import Disconnect, SmartConnect
from napalm_vmware import paths
from napalm_vmware._inventory import Inventory
OUT_DIR = Path(__file__).resolve().parent / "harvest-out"
def harvest(inventory: Inventory) -> dict:
"""Everything the drivers read, as one plain dict."""
return {
"about": inventory.about(),
"licenses": inventory.licenses(),
"objects": {name: inventory.collect(name, p) for name, p in paths.ALL.items()},
}
def main() -> int: # pragma: no cover - opens a live vSphere session
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("host")
parser.add_argument("user")
parser.add_argument("label")
parser.add_argument("--port", type=int, default=443)
parser.add_argument("--verify-ssl", action="store_true")
args = parser.parse_args()
password = os.environ.get("VSPHERE_PASSWORD") or getpass.getpass()
context = ssl.create_default_context()
if not args.verify_ssl:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
si = SmartConnect(
host=args.host, port=args.port, user=args.user, pwd=password, sslContext=context
)
try:
data = harvest(Inventory(si.RetrieveContent()))
finally:
Disconnect(si)
OUT_DIR.mkdir(exist_ok=True)
out = OUT_DIR / f"{args.label}.json"
out.write_text(json.dumps(data, indent=1, sort_keys=True))
print(f"wrote {out} -- sanitise it before committing anything", file=sys.stderr)
return 0
if __name__ == "__main__": # pragma: no cover
sys.exit(main())