diff --git a/napalm_device_types/licenses.py b/napalm_device_types/licenses.py index df0c672..c5a6b83 100644 --- a/napalm_device_types/licenses.py +++ b/napalm_device_types/licenses.py @@ -22,6 +22,11 @@ class LicenseMixin: Example:: [{"name": "pve-subscription", "status": "none"}, - {"name": "vSphere", "status": "active", "edition": "Free"}] + {"name": "vSphere", "status": "active", "edition": "Free"}, + {"name": "DCMS", "status": "missing", + "withholds": ["drives", "firmware inventory"]}] + + ``withholds`` names what a device keeps back for want of a license while + the rest of it answers; a missing license without it may keep back all. """ ... diff --git a/napalm_device_types/models.py b/napalm_device_types/models.py index 91cbc63..0a4e9d9 100644 --- a/napalm_device_types/models.py +++ b/napalm_device_types/models.py @@ -1008,6 +1008,11 @@ class LicenseDict(TypedDict): 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 + # What the device withholds without it while the rest works, e.g. ["drives", + # "firmware inventory"] on a BMC that answers but wants a further license. + # Absent: the device says nothing of the kind, or withholds everything it + # would offer with it. + withholds: NotRequired[List[str]] class HypervisorHostDict(TypedDict): diff --git a/pyproject.toml b/pyproject.toml index 2415910..9eab2a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "napalm-device-types" -version = "4.14.0" +version = "4.15.0" description = "Abstract device-type base classes for NAPALM drivers" readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_fact_getters.py b/tests/test_fact_getters.py index 11ddcca..c59e29d 100644 --- a/tests/test_fact_getters.py +++ b/tests/test_fact_getters.py @@ -23,7 +23,15 @@ 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"} + assert set(hints) == {"name", "status", "edition", "expires", "detail", "withholds"} + + +def test_a_missing_license_may_say_what_the_device_withholds_without_it(): + # A Supermicro X10 with only SFT-OOB-LIC answers Redfish but keeps its drives and + # firmware inventory until DCMS is activated: partly licensed, not unlicensed. + hints = get_type_hints(LicenseDict) + assert hints["withholds"] == list[str] or str(hints["withholds"]).endswith("List[str]") + assert "withholds" not in LicenseDict.__required_keys__ def test_a_hypervisor_host_says_how_it_stands():