feat: licenses, a hypervisor's hosts and the package proxy as facts (4.14.0) #43

Merged
christianmanivong merged 1 commits from feat/fact-getters-1111 into main 2026-10-17 07:19:34 +00:00
7 changed files with 114 additions and 1 deletions

No files matched your search

+6
View File
@@ -90,6 +90,7 @@ from napalm_device_types.fixes import (
run_fix_steps,
)
from napalm_device_types.health_metrics import HealthMetricsMixin
from napalm_device_types.licenses import LicenseMixin
from napalm_device_types.host_reboot import HostRebootMixin
from napalm_device_types.interface_filter import InterfaceFilterMixin
from napalm_device_types.kernel import KERNEL_FACTS_COMMAND, KernelFactsMixin, parse_kernel_facts
@@ -114,6 +115,8 @@ from napalm_device_types.models import (
ManagementProtocolsDict,
PowerAction,
PowerStateDict,
HypervisorHostDict,
LicenseDict,
FixResultDict,
FixStepDict,
FixStepOutcomeDict,
@@ -167,6 +170,9 @@ from napalm_device_types.storage import StorageDriver
from napalm_device_types.switch import SwitchDriver
__all__ = [
"HypervisorHostDict",
"LicenseDict",
"LicenseMixin",
"EnsurePackageMixin",
"FixResultDict",
"FixStepDict",
+6
View File
@@ -140,6 +140,12 @@ class PackageProxyMixin:
"""Have the package manager fetch through *url*; an empty *url* removes it."""
...
def get_package_proxy(self) -> Optional[str]:
"""The proxy the package manager fetches through now; None for none.
A fact: whether it is the right one is the caller's to judge."""
...
__all__ = [
"EnsurePackageMixin",
+7
View File
@@ -16,6 +16,7 @@ from napalm_device_types.packages import PackageManagementMixin
from napalm_device_types.health_metrics import HealthMetricsMixin
from napalm_device_types.provisioning import QEMU_GUEST_AGENTS, GuestAgents
from napalm_device_types.models import (
HypervisorHostDict,
NICConfigDict,
NetworkTargetDict,
SnapshotDict,
@@ -637,6 +638,12 @@ class HypervisorDriver(PackageManagementMixin, HealthMetricsMixin, DeviceTypeDri
"""
...
def get_hypervisor_hosts(self) -> List[HypervisorHostDict]:
"""The hosts this hypervisor or its manager runs, and how each stands:
connected, in maintenance, with configuration issues, under a central
manager. Facts; what is a problem is the caller's to decide."""
...
def get_vm_cpu_types(self) -> List[VMCpuTypeDict]:
"""
List the virtual CPU models a new VM may be given on the node it will
+27
View File
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""Licenses and subscriptions a device reports.
A fact for the caller (docs/STRUCTURE.md): whether a free edition, a missing
subscription or a license a feature needs is a problem is decided above the
driver. Declared only; a driver that can read licenses implements it.
"""
from __future__ import annotations
from typing import List, TYPE_CHECKING
from napalm_device_types.models import LicenseDict
class LicenseMixin:
if TYPE_CHECKING: # pragma: no cover - declared for type checkers only
def get_licenses(self) -> List[LicenseDict]:
"""Every license or subscription the device knows of, with its status.
Example::
[{"name": "pve-subscription", "status": "none"},
{"name": "vSphere", "status": "active", "edition": "Free"}]
"""
...
+27
View File
@@ -993,6 +993,33 @@ class HealthMetricsDict(TypedDict):
interfaces: NotRequired[Dict[str, HealthMetricsIfaceDict]]
LicenseStatus = Literal["active", "missing", "expired", "invalid", "none", "unknown"]
class LicenseDict(TypedDict):
"""A license or subscription the device reports (``LicenseMixin.get_licenses``).
A fact, not a judgement: whether a free edition or a missing subscription is
a problem is the caller's to decide.
"""
name: str # what it licenses, as the device names it
status: LicenseStatus # "none": the device runs without one, as installed
edition: NotRequired[str] # the edition or level, e.g. "Free", "Enterprise Plus"
expires: NotRequired[Optional[str]] # ISO date, None for no expiry
detail: NotRequired[str] # the device's own words, e.g. why a feature is refused
class HypervisorHostDict(TypedDict):
"""One host of a hypervisor or cluster (``HypervisorDriver.get_hypervisor_hosts``)."""
name: str
connected: Optional[bool] # reachable by its manager; None where it cannot tell
maintenance: Optional[bool] # in maintenance mode
config_issues: List[str] # the manager's configuration warnings, verbatim
managed_by: NotRequired[Optional[str]] # the address of a central manager, if any
class FixStepOutcomeDict(TypedDict):
"""What one step of a fix routine reports (see ``napalm_device_types.fixes``)."""
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "napalm-device-types"
version = "4.13.0"
version = "4.14.0"
description = "Abstract device-type base classes for NAPALM drivers"
readme = "README.md"
requires-python = ">=3.10"
+40
View File
@@ -0,0 +1,40 @@
"""Facts instead of warnings (docs/STRUCTURE.md, NetOrk/netork#1111).
A driver reports what it reads; the application decides what is a warning. Three
facts that drivers so far only reported as warning codes get contracts of their
own: the package manager's proxy, licenses and subscriptions, and the state of a
hypervisor's hosts. All are declared only, so a capability check stays truthful.
"""
from __future__ import annotations
from typing import get_type_hints
from napalm_device_types import (
HypervisorDriver,
HypervisorHostDict,
LicenseDict,
LicenseMixin,
PackageProxyMixin,
)
def test_a_license_says_what_it_is_and_whether_it_holds():
hints = get_type_hints(LicenseDict)
assert {"name", "status"} <= set(hints)
assert LicenseDict.__required_keys__ == frozenset({"name", "status"})
assert set(hints) == {"name", "status", "edition", "expires", "detail"}
def test_a_hypervisor_host_says_how_it_stands():
hints = get_type_hints(HypervisorHostDict)
assert set(hints) == {"name", "connected", "maintenance", "config_issues", "managed_by"}
assert HypervisorHostDict.__required_keys__ == frozenset(
{"name", "connected", "maintenance", "config_issues"}
)
def test_the_getters_are_declared_only():
assert not hasattr(PackageProxyMixin, "get_package_proxy")
assert not hasattr(LicenseMixin, "get_licenses")
assert not hasattr(HypervisorDriver, "get_hypervisor_hosts")