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.
80 lines
2.5 KiB
Python
Executable File
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())
|