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.
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""A VM's snapshot tree, flattened, and snapshot lookup by name."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterator
|
|
from typing import Any
|
|
|
|
from napalm_device_types.models import SnapshotDict
|
|
|
|
|
|
def _walk(nodes: list[dict[str, Any]], parent: str) -> Iterator[tuple[dict[str, Any], str]]:
|
|
for node in nodes:
|
|
yield node, parent
|
|
yield from _walk(node.get("childSnapshotList", []), node.get("name", ""))
|
|
|
|
|
|
def _nodes(vm: dict[str, Any]) -> Iterator[tuple[dict[str, Any], str]]:
|
|
return _walk((vm.get("snapshot") or {}).get("rootSnapshotList", []), "")
|
|
|
|
|
|
def snapshot_list(vm: dict[str, Any]) -> list[SnapshotDict]:
|
|
"""Every snapshot, depth first, each naming its parent (``""`` for a root)."""
|
|
return [
|
|
{
|
|
"name": node.get("name", ""),
|
|
"vm": vm.get("name", ""),
|
|
"created": float(node.get("createTime", 0.0)),
|
|
"description": node.get("description", ""),
|
|
# A snapshot taken with memory records the VM as powered on.
|
|
"has_memory": node.get("state") == "poweredOn",
|
|
"parent": parent,
|
|
}
|
|
for node, parent in _nodes(vm)
|
|
]
|
|
|
|
|
|
def find_snapshot(vm: dict[str, Any], name: str) -> str:
|
|
"""MoRef of the one snapshot called ``name``.
|
|
|
|
Raises ``ValueError`` when there is none -- or more than one: vSphere does
|
|
not require snapshot names to be unique, and choosing one of two
|
|
candidates could delete or roll back to the wrong state.
|
|
"""
|
|
matches = [node["snapshot"] for node, _ in _nodes(vm) if node.get("name") == name]
|
|
if not matches:
|
|
raise ValueError(f"VM {vm.get('name')!r} has no snapshot named {name!r}")
|
|
if len(matches) > 1:
|
|
raise ValueError(
|
|
f"VM {vm.get('name')!r} has {len(matches)} snapshots named {name!r}; "
|
|
"rename one in vSphere first"
|
|
)
|
|
return matches[0]
|