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,126 @@
|
||||
"""Port groups, distributed port groups, and which one a VM NIC is on."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from napalm_vmware.parse.networks import (
|
||||
NetworkIndex,
|
||||
dv_vlan,
|
||||
network_index,
|
||||
nic_network,
|
||||
virtual_networks,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def vc_index(vcenter_data):
|
||||
objs = vcenter_data["objects"]
|
||||
return network_index(objs["HostSystem"], objs["DistributedVirtualPortgroup"])
|
||||
|
||||
|
||||
class TestDvVlan:
|
||||
@pytest.mark.parametrize(
|
||||
("vlan", "expected"),
|
||||
[
|
||||
({"_type": "VmwareDistributedVirtualSwitchVlanIdSpec", "vlanId": 30}, 30),
|
||||
({"_type": "VmwareDistributedVirtualSwitchTrunkVlanSpec", "vlanId": []}, 0),
|
||||
({"_type": "VmwareDistributedVirtualSwitchPvlanSpec", "pvlanId": 101}, 101),
|
||||
],
|
||||
)
|
||||
def test_kinds(self, vlan, expected):
|
||||
assert dv_vlan({"vlan": vlan}) == expected
|
||||
|
||||
def test_missing(self):
|
||||
assert dv_vlan(None) == 0
|
||||
|
||||
|
||||
class TestNetworkIndex:
|
||||
def test_standard_and_distributed(self, vc_index):
|
||||
assert vc_index.standard["VM Network"] == 0
|
||||
assert vc_index.distributed["dvportgroup-12"] == ("DC0_DVPG0", 0)
|
||||
|
||||
def test_standard_vlan(self, esxi_host):
|
||||
esxi_host["config.network.portgroup"][0]["spec"]["vlanId"] = 40
|
||||
index = network_index([esxi_host], [])
|
||||
assert index.standard["VM Network"] == 40
|
||||
|
||||
|
||||
class TestNicNetwork:
|
||||
def test_standard_backing(self):
|
||||
index = NetworkIndex(standard={"VM Network": 20}, distributed={})
|
||||
nic = {
|
||||
"backing": {
|
||||
"_type": "VirtualEthernetCardNetworkBackingInfo",
|
||||
"deviceName": "VM Network",
|
||||
}
|
||||
}
|
||||
assert nic_network(nic, index) == ("VM Network", 20)
|
||||
|
||||
def test_distributed_backing(self, vc_index):
|
||||
nic = {
|
||||
"backing": {
|
||||
"_type": "VirtualEthernetCardDistributedVirtualPortBackingInfo",
|
||||
"port": {"portgroupKey": "dvportgroup-12"},
|
||||
}
|
||||
}
|
||||
assert nic_network(nic, vc_index) == ("DC0_DVPG0", 0)
|
||||
|
||||
def test_unknown_distributed_portgroup_keeps_its_key(self, vc_index):
|
||||
nic = {
|
||||
"backing": {
|
||||
"_type": "VirtualEthernetCardDistributedVirtualPortBackingInfo",
|
||||
"port": {"portgroupKey": "dvportgroup-99"},
|
||||
}
|
||||
}
|
||||
assert nic_network(nic, vc_index) == ("dvportgroup-99", 0)
|
||||
|
||||
def test_nsx_opaque_network(self, vc_index):
|
||||
nic = {
|
||||
"backing": {
|
||||
"_type": "VirtualEthernetCardOpaqueNetworkBackingInfo",
|
||||
"opaqueNetworkId": "ls-web",
|
||||
}
|
||||
}
|
||||
assert nic_network(nic, vc_index) == ("ls-web", 0)
|
||||
|
||||
def test_no_backing(self, vc_index):
|
||||
assert nic_network({}, vc_index) == ("", 0)
|
||||
|
||||
|
||||
class TestVirtualNetworks:
|
||||
def test_esxi_standard_portgroups(self, esxi_host):
|
||||
nets = virtual_networks([esxi_host], [], [])
|
||||
assert nets["VM Network"] == {
|
||||
"name": "VM Network",
|
||||
"type": "portgroup",
|
||||
"bridge": "vSwitch0",
|
||||
"vlan_id": 0,
|
||||
"autostart": True,
|
||||
"active": True,
|
||||
}
|
||||
assert set(nets) == {"VM Network", "Management Network"}
|
||||
|
||||
def test_vcenter_adds_distributed_portgroups_but_not_uplinks(self, vcenter_data):
|
||||
objs = vcenter_data["objects"]
|
||||
nets = virtual_networks(
|
||||
objs["HostSystem"],
|
||||
objs["DistributedVirtualPortgroup"],
|
||||
objs["DistributedVirtualSwitch"],
|
||||
)
|
||||
assert nets["DC0_DVPG0"] == {
|
||||
"name": "DC0_DVPG0",
|
||||
"type": "dvportgroup",
|
||||
"bridge": "DVS0",
|
||||
"vlan_id": 0,
|
||||
"autostart": True,
|
||||
"active": True,
|
||||
}
|
||||
assert "DVS0-DVUplinks-8" not in nets
|
||||
|
||||
def test_uplink_flag_wins_over_the_name(self, vcenter_data):
|
||||
objs = vcenter_data["objects"]
|
||||
pgs = objs["DistributedVirtualPortgroup"]
|
||||
pgs[1]["config.uplink"] = True
|
||||
nets = virtual_networks([], pgs, objs["DistributedVirtualSwitch"])
|
||||
assert "DC0_DVPG0" not in nets
|
||||
Reference in New Issue
Block a user