CI / test (3.10) (push) Successful in 27s
CI / test (3.11) (push) Successful in 25s
CI / test (3.12) (push) Successful in 27s
CI / test (3.10) (pull_request) Successful in 27s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (pull_request) Successful in 26s
get_device_warnings is gone; netOrk names the warnings from facts (netOrk #1111). get_hypervisor_hosts() reports each host's connection state, maintenance mode, configuration issues and managing vCenter; get_licenses() the assigned licenses with the edition key as edition, never the key itself. The reach stays: the ESXi driver reports connected as None (a disconnected host could not have answered), and managed_by only for a host it reached directly. parse/warnings.py is replaced by parse/hosts.py and parse/licenses.py.
202 lines
7.9 KiB
Python
202 lines
7.9 KiB
Python
"""Datastores, sensors/utilisation, CDP/LLDP neighbours, host and license facts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from napalm_vmware.parse.environment import environment
|
|
from napalm_vmware.parse.hosts import hypervisor_hosts
|
|
from napalm_vmware.parse.licenses import license_list
|
|
from napalm_vmware.parse.lldp import lldp_neighbors
|
|
from napalm_vmware.parse.storage import storage_pools
|
|
|
|
|
|
class TestStoragePools:
|
|
def test_from_vcsim(self, esxi_data):
|
|
pools = storage_pools(esxi_data["objects"]["Datastore"])
|
|
(name,) = pools
|
|
pool = pools[name]
|
|
assert pool["name"] == name
|
|
assert pool["type"] == "other"
|
|
assert pool["used"] == pool["total"] - pool["available"]
|
|
assert pool["enabled"] is True
|
|
assert pool["shared"] is False
|
|
|
|
def test_shared_vmfs(self):
|
|
ds = {
|
|
"_moref": "datastore-1",
|
|
"name": "san01",
|
|
"summary": {
|
|
"type": "VMFS",
|
|
"capacity": 1000,
|
|
"freeSpace": 250,
|
|
"accessible": False,
|
|
"multipleHostAccess": True,
|
|
},
|
|
}
|
|
assert storage_pools([ds])["san01"] == {
|
|
"name": "san01",
|
|
"type": "vmfs",
|
|
"total": 1000,
|
|
"used": 750,
|
|
"available": 250,
|
|
"enabled": False,
|
|
"shared": True,
|
|
}
|
|
|
|
|
|
def _sensor(name, kind, reading, health="green", modifier=0):
|
|
return {
|
|
"name": name,
|
|
"sensorType": kind,
|
|
"currentReading": reading,
|
|
"unitModifier": modifier,
|
|
"healthState": {"key": health},
|
|
}
|
|
|
|
|
|
class TestEnvironment:
|
|
def test_utilisation_of_one_host(self, esxi_host):
|
|
esxi_host["summary.hardware"].update(cpuMhz=2000, numCpuCores=4, memorySize=8 * 1024**3)
|
|
esxi_host["summary.quickStats"].update(overallCpuUsage=2000, overallMemoryUsage=2048)
|
|
env = environment([esxi_host])
|
|
assert env["cpu"] == {"0": {"%usage": 25.0}}
|
|
assert env["memory"] == {"available_ram": 8 * 1024**3, "used_ram": 2048 * 1024**2}
|
|
|
|
def test_sensors(self, esxi_host):
|
|
esxi_host["runtime.healthSystemRuntime.systemHealthInfo.numericSensorInfo"] = [
|
|
_sensor("CPU1 Temp", "temperature", 4500, modifier=-2),
|
|
_sensor("Inlet Temp", "temperature", 41, health="yellow"),
|
|
_sensor("PCH Temp", "temperature", 90, health="red"),
|
|
_sensor("FAN1", "fan", 5400),
|
|
_sensor("FAN2", "fan", 0, health="red"),
|
|
_sensor("PSU1 Output", "power", 180),
|
|
_sensor("12V", "voltage", 12),
|
|
]
|
|
env = environment([esxi_host])
|
|
assert env["temperature"] == {
|
|
"CPU1 Temp": {"temperature": 45.0, "is_alert": False, "is_critical": False},
|
|
"Inlet Temp": {"temperature": 41.0, "is_alert": True, "is_critical": False},
|
|
"PCH Temp": {"temperature": 90.0, "is_alert": True, "is_critical": True},
|
|
}
|
|
assert env["fans"] == {"FAN1": {"status": True}, "FAN2": {"status": False}}
|
|
assert env["power"] == {"PSU1 Output": {"status": True, "capacity": -1.0, "output": 180.0}}
|
|
|
|
def test_several_hosts_are_keyed_by_host(self, vcenter_data):
|
|
hosts = vcenter_data["objects"]["HostSystem"]
|
|
hosts[0]["runtime.healthSystemRuntime.systemHealthInfo.numericSensorInfo"] = [
|
|
_sensor("Inlet Temp", "temperature", 30)
|
|
]
|
|
env = environment(hosts)
|
|
assert set(env["cpu"]) == {h["name"] for h in hosts}
|
|
assert f"{hosts[0]['name']}/Inlet Temp" in env["temperature"]
|
|
assert env["memory"]["available_ram"] == sum(
|
|
h["summary.hardware"]["memorySize"] for h in hosts
|
|
)
|
|
|
|
def test_host_without_stats(self):
|
|
env = environment([{"_moref": "host-1", "name": "h"}])
|
|
assert env["cpu"] == {"0": {"%usage": 0.0}}
|
|
assert env["memory"] == {"available_ram": 0, "used_ram": 0}
|
|
|
|
|
|
class TestLldpNeighbors:
|
|
def test_lldp_preferred_over_cdp(self):
|
|
hints = [
|
|
{
|
|
"device": "vmnic0",
|
|
"connectedSwitchPort": {"devId": "cdp-sw", "portId": "Gi1/0/1"},
|
|
"lldpInfo": {
|
|
"chassisId": "00:11:22:33:44:55",
|
|
"portId": "1/1/7",
|
|
"parameter": [{"key": "System Name", "value": "core-sw"}],
|
|
},
|
|
},
|
|
{"device": "vmnic1", "connectedSwitchPort": {"devId": "cdp-sw", "portId": "Gi1/0/2"}},
|
|
{"device": "vmnic2", "lldpInfo": {"chassisId": "aa:bb", "portId": "5"}},
|
|
{"device": "vmnic3"},
|
|
]
|
|
assert lldp_neighbors(hints) == {
|
|
"vmnic0": [{"hostname": "core-sw", "port": "1/1/7"}],
|
|
"vmnic1": [{"hostname": "cdp-sw", "port": "Gi1/0/2"}],
|
|
"vmnic2": [{"hostname": "aa:bb", "port": "5"}],
|
|
}
|
|
|
|
def test_nothing_heard(self):
|
|
assert lldp_neighbors([]) == {}
|
|
|
|
|
|
class TestHypervisorHosts:
|
|
"""Facts only (netOrk #1111): which of them is a warning is the caller's call."""
|
|
|
|
def test_healthy_vcsim_host(self, esxi_host):
|
|
assert hypervisor_hosts([esxi_host], standalone=True) == [
|
|
{
|
|
"name": "localhost.localdomain",
|
|
"connected": None,
|
|
"maintenance": False,
|
|
"config_issues": [],
|
|
"managed_by": None,
|
|
}
|
|
]
|
|
|
|
def test_maintenance_mode(self, esxi_host):
|
|
esxi_host["runtime.inMaintenanceMode"] = True
|
|
assert hypervisor_hosts([esxi_host], standalone=True)[0]["maintenance"] is True
|
|
|
|
@pytest.mark.parametrize("state", ["disconnected", "notResponding"])
|
|
def test_disconnected_host_under_vcenter(self, esxi_host, state):
|
|
esxi_host["runtime.connectionState"] = state
|
|
assert hypervisor_hosts([esxi_host], standalone=False)[0]["connected"] is False
|
|
|
|
def test_connected_host_under_vcenter(self, esxi_host):
|
|
assert hypervisor_hosts([esxi_host], standalone=False)[0]["connected"] is True
|
|
|
|
def test_a_standalone_host_cannot_tell(self, esxi_host):
|
|
"""A disconnected host could not have answered the ESXi driver."""
|
|
esxi_host["runtime.connectionState"] = "disconnected"
|
|
assert hypervisor_hosts([esxi_host], standalone=True)[0]["connected"] is None
|
|
|
|
def test_config_issues(self, esxi_host):
|
|
esxi_host["configIssue"] = [
|
|
{"fullFormattedMessage": "SSH for the host has been enabled"},
|
|
{"fullFormattedMessage": ""},
|
|
]
|
|
assert hypervisor_hosts([esxi_host], standalone=True)[0]["config_issues"] == [
|
|
"SSH for the host has been enabled"
|
|
]
|
|
|
|
def test_standalone_driver_on_a_vcenter_managed_host(self, esxi_host):
|
|
esxi_host["summary.managementServerIp"] = "192.0.2.5"
|
|
assert hypervisor_hosts([esxi_host], standalone=True)[0]["managed_by"] == "192.0.2.5"
|
|
|
|
@pytest.mark.parametrize("ip", ["", "0.0.0.0", None])
|
|
def test_unmanaged(self, esxi_host, ip):
|
|
esxi_host["summary.managementServerIp"] = ip
|
|
assert hypervisor_hosts([esxi_host], standalone=True)[0]["managed_by"] is None
|
|
|
|
def test_vcenter_driver_does_not_report_its_own_management(self, esxi_host):
|
|
esxi_host["summary.managementServerIp"] = "192.0.2.5"
|
|
assert hypervisor_hosts([esxi_host], standalone=False)[0]["managed_by"] is None
|
|
|
|
|
|
class TestLicenseList:
|
|
def test_the_edition_key_is_the_edition(self):
|
|
licenses = [
|
|
{
|
|
"editionKey": "esxBasic",
|
|
"name": "VMware vSphere 8 Hypervisor",
|
|
"licenseKey": "AAAAA-BBBBB-CCCCC-DDDDD-EEEEE",
|
|
}
|
|
]
|
|
assert license_list(licenses) == [
|
|
{"name": "VMware vSphere 8 Hypervisor", "status": "active", "edition": "esxBasic"}
|
|
]
|
|
|
|
def test_the_key_is_never_reported(self):
|
|
licenses = [{"editionKey": "eval", "name": "Evaluation Mode", "licenseKey": "00000"}]
|
|
assert "00000" not in str(license_list(licenses))
|
|
|
|
def test_none(self):
|
|
assert license_list([]) == []
|