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,67 @@
|
||||
"""to_plain: pyVmomi objects -> JSON-safe Python values.
|
||||
|
||||
Every parser in this package works on the output of ``to_plain`` rather than
|
||||
on pyVmomi objects. That is what lets the harvest tool dump a real host to
|
||||
JSON and the tests feed that same JSON back in.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from pyVmomi import vim
|
||||
|
||||
from napalm_vmware._plain import to_plain
|
||||
|
||||
|
||||
def test_scalars_pass_through():
|
||||
assert to_plain("a") == "a"
|
||||
assert to_plain(3) == 3
|
||||
assert to_plain(True) is True
|
||||
assert to_plain(None) is None
|
||||
|
||||
|
||||
def test_enum_becomes_plain_str():
|
||||
value = to_plain(vim.VirtualMachine.PowerState.poweredOn)
|
||||
assert value == "poweredOn"
|
||||
assert type(value) is str
|
||||
|
||||
|
||||
def test_managed_object_becomes_its_moref():
|
||||
assert to_plain(vim.HostSystem("host-21", None)) == "host-21"
|
||||
|
||||
|
||||
def test_data_object_becomes_dict_with_type_and_without_unset_fields():
|
||||
nic = vim.host.PhysicalNic(
|
||||
device="vmnic0",
|
||||
linkSpeed=vim.host.PhysicalNic.LinkSpeedDuplex(speedMb=1000, duplex=True),
|
||||
)
|
||||
plain = to_plain(nic)
|
||||
assert plain["_type"] == "PhysicalNic"
|
||||
assert plain["device"] == "vmnic0"
|
||||
assert plain["linkSpeed"] == {"_type": "PhysicalNicLinkInfo", "speedMb": 1000, "duplex": True}
|
||||
assert "driver" not in plain # optional and unset
|
||||
|
||||
|
||||
def test_dynamic_property_noise_is_dropped():
|
||||
assert "dynamicProperty" not in to_plain(vim.host.PhysicalNic(device="vmnic0"))
|
||||
|
||||
|
||||
def test_lists_are_converted_elementwise():
|
||||
devices = [vim.vm.device.VirtualVmxnet3(key=4000), vim.vm.device.VirtualDisk(key=2000)]
|
||||
assert [d["_type"] for d in to_plain(devices)] == ["VirtualVmxnet3", "VirtualDisk"]
|
||||
|
||||
|
||||
def test_datetime_becomes_epoch_seconds():
|
||||
when = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
|
||||
assert to_plain(when) == 1767225600.0
|
||||
|
||||
|
||||
def test_bytes_are_dropped_to_none():
|
||||
assert to_plain(b"\x00\x01") is None
|
||||
|
||||
|
||||
def test_result_is_json_serialisable():
|
||||
nic = vim.host.PhysicalNic(device="vmnic0", pci="0000:0b:00.0")
|
||||
json.dumps(to_plain([nic, vim.HostSystem("host-1", None), 1.5]))
|
||||
Reference in New Issue
Block a user