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,105 @@
|
||||
"""End-to-end against govmomi's vcsim: real sessions, real tasks, no mocks.
|
||||
|
||||
Skipped unless the simulators are running::
|
||||
|
||||
docker run -d --rm -p 127.0.0.1:8989:8989 vmware/vcsim -l 0.0.0.0:8989
|
||||
docker run -d --rm -p 127.0.0.1:8990:8989 vmware/vcsim -esx -l 0.0.0.0:8989
|
||||
VCSIM_VCENTER_PORT=8989 VCSIM_ESXI_PORT=8990 pytest -m vcsim
|
||||
|
||||
vcsim accepts any credentials. It is a simulator: this proves the drivers speak
|
||||
the vSphere API correctly, not that they understand a particular real host.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from napalm.base.exceptions import ConnectionException
|
||||
|
||||
from napalm_vmware import VmwareEsxiDriver, VmwareVcenterDriver
|
||||
|
||||
pytestmark = pytest.mark.vcsim
|
||||
|
||||
|
||||
def _driver(cls, env):
|
||||
port = os.environ.get(env)
|
||||
if not port:
|
||||
pytest.skip(f"{env} not set; vcsim not running")
|
||||
driver = cls("127.0.0.1", "user", "pass", optional_args={"port": port, "verify_ssl": False})
|
||||
driver.open()
|
||||
yield driver
|
||||
driver.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def esxi():
|
||||
yield from _driver(VmwareEsxiDriver, "VCSIM_ESXI_PORT")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def vcenter():
|
||||
yield from _driver(VmwareVcenterDriver, "VCSIM_VCENTER_PORT")
|
||||
|
||||
|
||||
def test_esxi_reads(esxi):
|
||||
assert esxi.is_alive() == {"is_alive": True}
|
||||
assert esxi.get_facts()["os_version"].startswith("VMware ESXi")
|
||||
assert "vmnic0" in esxi.get_interfaces()
|
||||
assert esxi.get_vms()
|
||||
assert esxi.get_vm_storage_pools()
|
||||
assert esxi.get_virtual_networks()
|
||||
assert "cpu" in esxi.get_environment()
|
||||
assert isinstance(esxi.get_device_warnings(), list)
|
||||
assert isinstance(esxi.get_lldp_neighbors(), dict)
|
||||
|
||||
|
||||
def test_vcenter_reads(vcenter):
|
||||
assert vcenter.get_facts()["model"] == "VMware vCenter Server"
|
||||
vms = vcenter.get_vms()
|
||||
assert len({vm["node"] for vm in vms}) > 1
|
||||
assert vcenter.get_vm_config(vms[0]["vmid"])["vmid"] == vms[0]["vmid"]
|
||||
|
||||
|
||||
def test_power_cycle(esxi):
|
||||
vm = esxi.get_vms()[0]
|
||||
esxi.stop_vm(vm["vmid"], force=True)
|
||||
assert _status(esxi, vm) == "stopped"
|
||||
with pytest.raises(RuntimeError, match="not running"):
|
||||
esxi.suspend_vm(vm["vmid"])
|
||||
esxi.start_vm(vm["vmid"])
|
||||
assert _status(esxi, vm) == "running"
|
||||
esxi.suspend_vm(vm["vmid"])
|
||||
assert _status(esxi, vm) == "suspended"
|
||||
esxi.start_vm(vm["vmid"])
|
||||
esxi.reboot_vm(vm["vmid"], force=True)
|
||||
assert _status(esxi, vm) == "running"
|
||||
|
||||
|
||||
def test_snapshot_lifecycle(vcenter):
|
||||
vm = vcenter.get_vms()[0]
|
||||
vcenter.create_vm_snapshot(vm["vmid"], "netork-test", description="from pytest")
|
||||
try:
|
||||
(snap,) = [s for s in vcenter.get_vm_snapshots(vm["vmid"]) if s["name"] == "netork-test"]
|
||||
assert snap["description"] == "from pytest"
|
||||
with pytest.raises(ValueError, match="already"):
|
||||
vcenter.create_vm_snapshot(vm["vmid"], "netork-test")
|
||||
vcenter.rollback_vm_snapshot(vm["vmid"], "netork-test")
|
||||
finally:
|
||||
vcenter.delete_vm_snapshot(vm["vmid"], "netork-test")
|
||||
assert not [s for s in vcenter.get_vm_snapshots(vm["vmid"]) if s["name"] == "netork-test"]
|
||||
|
||||
|
||||
def test_wrong_driver_is_refused():
|
||||
port = os.environ.get("VCSIM_VCENTER_PORT")
|
||||
if not port:
|
||||
pytest.skip("VCSIM_VCENTER_PORT not set")
|
||||
driver = VmwareEsxiDriver(
|
||||
"127.0.0.1", "u", "p", optional_args={"port": port, "verify_ssl": False}
|
||||
)
|
||||
with pytest.raises(ConnectionException, match="vmware_vcenter"):
|
||||
driver.open()
|
||||
|
||||
|
||||
def _status(driver, vm):
|
||||
return next(v["status"] for v in driver.get_vms() if v["vmid"] == vm["vmid"])
|
||||
Reference in New Issue
Block a user