feat!: report hosts and licenses as facts, not warnings (0.2.0)
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.
This commit is contained in:
Christian Manivong committed 2026-10-17 09:41:47 +02:00
1 parent 276276bd9f
commit 990dfe2f3c
10 files changed
+154 -100

No files matched your search

+11 -5
View File
@@ -6,8 +6,10 @@ from pathlib import Path
from typing import Any, ClassVar
from napalm.base.exceptions import ConnectionException
from napalm_device_types import HypervisorDriver
from napalm_device_types import HypervisorDriver, LicenseMixin
from napalm_device_types.models import (
HypervisorHostDict,
LicenseDict,
SnapshotDict,
StorageVolumeDict,
VirtualNetworkDict,
@@ -17,12 +19,13 @@ from napalm_device_types.models import (
from napalm_vmware import _session, paths
from napalm_vmware._inventory import Inventory
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.networks import NetworkIndex, network_index, virtual_networks
from napalm_vmware.parse.snapshots import snapshot_list
from napalm_vmware.parse.storage import storage_pools
from napalm_vmware.parse.vm_config import vm_config
from napalm_vmware.parse.vms import vm_list
from napalm_vmware.parse.warnings import host_warnings
from napalm_vmware.provision.image import DEFAULT_CACHE_DIR
_DEFAULT_PORT = 443
@@ -30,7 +33,7 @@ _DEFAULT_PORT = 443
_DRIVER_FOR_API = {"HostAgent": "vmware_esxi", "VirtualCenter": "vmware_vcenter"}
class VmwareBaseDriver(HypervisorDriver):
class VmwareBaseDriver(LicenseMixin, HypervisorDriver):
"""Shared implementation. Concrete drivers set ``API_TYPE`` and ``STANDALONE``."""
VENDOR = "VMware"
@@ -154,5 +157,8 @@ class VmwareBaseDriver(HypervisorDriver):
def get_environment(self) -> dict[str, Any]:
return environment(self._hosts())
def get_device_warnings(self) -> list[dict[str, Any]]:
return host_warnings(self._hosts(), self._inventory.licenses(), standalone=self.STANDALONE)
def get_hypervisor_hosts(self) -> list[HypervisorHostDict]:
return hypervisor_hosts(self._hosts(), standalone=self.STANDALONE)
def get_licenses(self) -> list[LicenseDict]:
return license_list(self._inventory.licenses())
+39
View File
@@ -0,0 +1,39 @@
"""The hosts a driver sees, as facts (``HypervisorDriver.get_hypervisor_hosts``).
Whether a host in maintenance mode or one managed by a vCenter is a problem is
decided by the caller, never here.
"""
from __future__ import annotations
from typing import Any
from napalm_device_types.models import HypervisorHostDict
_CONNECTED = "connected"
_UNMANAGED = {None, "", "0.0.0.0"}
def _host(host: dict[str, Any], standalone: bool) -> HypervisorHostDict:
manager = host.get("summary.managementServerIp")
return {
"name": host.get("name", host.get("_moref", "")),
# A disconnected host could not have answered the ESXi driver.
"connected": None
if standalone
else host.get("runtime.connectionState", _CONNECTED) == _CONNECTED,
"maintenance": bool(host.get("runtime.inMaintenanceMode")),
"config_issues": [
i["fullFormattedMessage"]
for i in host.get("configIssue") or []
if i.get("fullFormattedMessage")
],
# Under a vCenter every host names that vCenter; only a host the ESXi
# driver reached directly can be managed by someone else.
"managed_by": manager if standalone and manager not in _UNMANAGED else None,
}
def hypervisor_hosts(hosts: list[dict[str, Any]], *, standalone: bool) -> list[HypervisorHostDict]:
"""``standalone`` is True for the ESXi driver, False for vCenter."""
return [_host(host, standalone) for host in hosts]
+19
View File
@@ -0,0 +1,19 @@
"""Licenses assigned on an endpoint, as facts (``LicenseMixin.get_licenses``).
The edition key says which edition runs -- ``esxBasic`` is the free vSphere
Hypervisor, whose API refuses every write; what follows from that is the
caller's to decide. The license key itself is never reported.
"""
from __future__ import annotations
from typing import Any
from napalm_device_types.models import LicenseDict
def license_list(licenses: list[dict[str, Any]]) -> list[LicenseDict]:
return [
{"name": lic.get("name", ""), "status": "active", "edition": str(lic.get("editionKey", ""))}
for lic in licenses
]
-53
View File
@@ -1,53 +0,0 @@
"""Raw device warnings: ``{"code", "meta"}`` only.
What a code means to a user -- severity, wording, the fix -- is decided in
netOrk's ``WARNING_CATALOG``, never here.
"""
from __future__ import annotations
from typing import Any
_CONNECTED = "connected"
_UNMANAGED = {None, "", "0.0.0.0"}
#: License edition of the free vSphere Hypervisor. The vSphere API refuses
#: every write on it (power, snapshots) with a RestrictedVersion fault.
_FREE_EDITIONS = ("esxBasic",)
def _host_codes(host: dict[str, Any], standalone: bool) -> list[dict[str, Any]]:
name = host.get("name", host.get("_moref", ""))
found: list[dict[str, Any]] = []
state = host.get("runtime.connectionState", _CONNECTED)
if not standalone and state != _CONNECTED:
found.append({"code": "vmware_host_disconnected", "meta": {"host": name, "state": state}})
if host.get("runtime.inMaintenanceMode"):
found.append({"code": "vmware_host_maintenance_mode", "meta": {"host": name}})
issues = [
i["fullFormattedMessage"]
for i in host.get("configIssue") or []
if i.get("fullFormattedMessage")
]
if issues:
found.append({"code": "vmware_host_config_issue", "meta": {"host": name, "issues": issues}})
manager = host.get("summary.managementServerIp")
if standalone and manager not in _UNMANAGED:
found.append({"code": "vmware_host_managed_by_vcenter", "meta": {"vcenter": manager}})
return found
def host_warnings(
hosts: list[dict[str, Any]], licenses: list[dict[str, Any]], *, standalone: bool
) -> list[dict[str, Any]]:
"""Warnings for the hosts this driver sees.
``standalone`` is True for the ESXi driver. It alone reports a host being
managed by a vCenter -- a hint to add the vCenter instead -- and it never
reports disconnection, since a disconnected host could not have answered.
"""
found = [w for host in hosts for w in _host_codes(host, standalone)]
for lic in licenses:
if str(lic.get("editionKey", "")).startswith(_FREE_EDITIONS):
found.append({"code": "vmware_api_read_only", "meta": {"edition": lic.get("name", "")}})
break
return found