feat: declare all three roles, and refuse QPKG writes deliberately

A QNAP is a NAS, a hypervisor and a Linux host. It can now say so, because role
bases in napalm-device-types v1.0 declare their methods without implementing
them:

    class QnapQtsDriver(StorageDriver, HypervisorDriver, LinuxDriver):

device_class comes from the first base, so DEVICE_CLASS is gone. The get_services
forwarder is gone with it — nothing shadows LinuxDriver's version any more — and
so is the TestMroForwarding class that guarded the collisions.

Removing the shadowing exposed something the shadowing had been hiding by
accident. This driver never implemented package management; StorageDriver's stub
for install_package covered LinuxDriver's working one, so calling it raised and
looked correct. It was a coincidence, not a decision: without the stub the call
falls through to LinuxDriver, which would run apt or dnf against a NAS that has
neither. QTS uses QPKG.

get_packages, install_package and uninstall_package are therefore overridden
here to refuse with a reason, until the device harvest supplies real QPKG
parsing. See netork#114.
This commit is contained in:
Christian Manivong
2026-08-21 12:50:27 +07:00
parent 35c47bdb24
commit 927e39bf36
2 changed files with 77 additions and 51 deletions
+32 -25
View File
@@ -24,7 +24,12 @@ from __future__ import annotations
import re
from typing import Any
from napalm_device_types import FingerprintRule, PortSpec, StorageDriver
from napalm_device_types import (
FingerprintRule,
HypervisorDriver,
PortSpec,
StorageDriver,
)
from napalm_linux.linux import LinuxDriver
#: QNAP Systems' IANA enterprise number. Used by discovery to recognise a NAS
@@ -42,27 +47,22 @@ _DOCKER_GLOBS = (
_VERSION_RE = re.compile(r"(\d+)\.")
class QnapQtsDriver(StorageDriver, LinuxDriver):
class QnapQtsDriver(StorageDriver, HypervisorDriver, LinuxDriver):
"""NAPALM driver for QNAP NAS systems running QTS.
**Inheritance order matters.** ``StorageDriver`` precedes ``LinuxDriver`` in
the MRO, so its ``NotImplementedError`` stubs shadow LinuxDriver's working
implementations wherever the names collide — ``get_services``,
``get_packages`` and ``install_package``. Each collision is resolved
explicitly below rather than left to the MRO; see ``TestMroForwarding``.
Declares all three roles it fills. The role bases carry no implementations,
so listing them alongside ``LinuxDriver`` costs nothing and shadows nothing;
what the driver can actually do is whatever it implements below.
NAS services are exposed as ``get_storage_services()``, not
``get_services()``, because netOrk's poller reads the former for the storage
snapshot and the latter for the OS service list. Same convention as
napalm-openmediavault.
NAS services are ``get_storage_services()``; the OS service list inherited
from ``LinuxDriver`` stays ``get_services()``. Two names because they are two
different things with different return shapes, not because they collided.
"""
# A QNAP is three things at once, and the order says which one leads:
# storage first, so netOrk shows it as a NAS. Nothing has to restate that
# -- device_class is read from this line.
TYPE_LABEL = "Storage"
# Declared outright: the driver inherits LinuxDriver for the OS surface, so
# netOrk's issubclass chain would reach OSDriver first and classify a QNAP
# as "linux", hiding its Storage tab. VMs and containers stay visible
# through capability introspection, not through this key.
DEVICE_CLASS = "storage"
VENDOR = "QNAP"
DRIVER_NAME = "qnap_qts"
driver_name = "qnap_qts"
@@ -143,15 +143,22 @@ class QnapQtsDriver(StorageDriver, LinuxDriver):
"""Override LinuxDriver's hook: docker is not on PATH under QTS."""
return getattr(self, "_docker_path", None) or "docker"
# ── MRO collision resolution ──────────────────────────────────────────────
# ── QPKG, not apt ─────────────────────────────────────────────────────────
#
# StorageDriver comes first in the MRO and its stubs would otherwise win.
# QTS is Linux, so LinuxDriver's package methods are in the MRO and would
# run happily -- against a package manager QTS does not have. They used to
# be shadowed by StorageDriver's stubs, which made a QNAP refuse them by
# accident. Now that role bases implement nothing, the refusal has to be
# deliberate. QPKG parsing lands with the device harvest; until then,
# refusing is the only honest answer.
def get_services(self) -> Any:
"""OS services, not NAS services — this is what netOrk's poller reads.
def get_packages(self) -> Any:
raise NotImplementedError(
"QTS uses QPKG, not a Linux package manager; QPKG parsing is not implemented yet"
)
Forwarded explicitly past ``StorageDriver.get_services``, which shadows
it and returns a different shape (dict of NAS services vs. list of OS
services).
"""
return LinuxDriver.get_services(self)
def install_package(self, name: str, version: str = "") -> None:
raise NotImplementedError("QPKG install is not implemented yet")
def uninstall_package(self, name: str) -> None:
raise NotImplementedError("QPKG removal is not implemented yet")