chore: adopt the NAPALM driver standard #9

Merged
christianmanivong merged 4 commits from chore/driver-standard into main 2026-10-23 14:44:39 +00:00
3 changed files with 38 additions and 26 deletions
Showing only changes of commit 6d82fad80a - Show all commits

No files matched your search

+24 -25
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 # Licensed under the Apache License, Version 2.0
"""NAPALM driver for OpenMediaVault NAS systems. """NAPALM driver for OpenMediaVault NAS systems.
@@ -22,7 +21,7 @@ import json as _json
import re import re
from typing import Any from typing import Any
from napalm_device_types import FingerprintRule from napalm_device_types import FingerprintRule, StorageDriver
from napalm_device_types.models import ( from napalm_device_types.models import (
DiskPoolDict, DiskPoolDict,
LogicalVolumeDict, LogicalVolumeDict,
@@ -33,7 +32,6 @@ from napalm_device_types.models import (
StorageServiceDict, StorageServiceDict,
VolumeSnapshotDict, VolumeSnapshotDict,
) )
from napalm_device_types import StorageDriver
from napalm_linux.linux import LinuxDriver from napalm_linux.linux import LinuxDriver
@@ -76,9 +74,7 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
"""Return device facts, overriding OMV version from dpkg.""" """Return device facts, overriding OMV version from dpkg."""
facts = super().get_facts() facts = super().get_facts()
try: try:
ver = self._send( ver = self._send("dpkg-query -W -f='${Version}' openmediavault 2>/dev/null").strip()
"dpkg-query -W -f='${Version}' openmediavault 2>/dev/null"
).strip()
if ver: if ver:
facts["os_version"] = f"OMV {ver}" facts["os_version"] = f"OMV {ver}"
except Exception: except Exception:
@@ -159,9 +155,7 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
rpm = 0 rpm = 0
try: try:
smart = self._sudo( smart = self._sudo(f"smartctl -iA /dev/{name} 2>/dev/null", read_timeout=15)
f"smartctl -iA /dev/{name} 2>/dev/null", read_timeout=15
)
if "PASSED" in smart: if "PASSED" in smart:
health = "healthy" health = "healthy"
elif "FAILED" in smart: elif "FAILED" in smart:
@@ -180,7 +174,8 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
except Exception: except Exception:
pass pass
result.append(PhysicalDiskDict( result.append(
PhysicalDiskDict(
slot=f"/dev/{name}", slot=f"/dev/{name}",
model=model, model=model,
serial=serial, serial=serial,
@@ -191,7 +186,8 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
temperature=temperature, temperature=temperature,
health=health, health=health,
pool=self._disk_to_pool(name), pool=self._disk_to_pool(name),
)) )
)
return result return result
@@ -200,7 +196,7 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
try: try:
mdstat = self._send("cat /proc/mdstat 2>/dev/null") mdstat = self._send("cat /proc/mdstat 2>/dev/null")
for line in mdstat.splitlines(): for line in mdstat.splitlines():
if disk_name in line and re.match(r'^md\d+', line): if disk_name in line and re.match(r"^md\d+", line):
return line.split(":")[0].strip() return line.split(":")[0].strip()
except Exception: except Exception:
pass pass
@@ -251,19 +247,17 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
return result return result
for line in mdstat.splitlines(): for line in mdstat.splitlines():
m = re.match(r'^(md\d+)\s*:\s+(\S+)\s+(\S+)\s+(.*)', line) m = re.match(r"^(md\d+)\s*:\s+(\S+)\s+(\S+)\s+(.*)", line)
if not m: if not m:
continue continue
md_name, state_word, level_raw, members_raw = m.groups() md_name, state_word, level_raw, members_raw = m.groups()
member_disks = re.findall(r'(\w+)\[\d+\]', members_raw) member_disks = re.findall(r"(\w+)\[\d+\]", members_raw)
status = "online" if state_word == "active" else "offline" status = "online" if state_word == "active" else "offline"
total = 0 total = 0
try: try:
detail = self._sudo( detail = self._sudo(f"mdadm --detail /dev/{md_name} 2>/dev/null", read_timeout=10)
f"mdadm --detail /dev/{md_name} 2>/dev/null", read_timeout=10
)
size_m = re.search(r"Array Size\s*:\s*(\d+)\s*\(", detail) size_m = re.search(r"Array Size\s*:\s*(\d+)\s*\(", detail)
if size_m: if size_m:
total = int(size_m.group(1)) * 1024 # kB → bytes total = int(size_m.group(1)) * 1024 # kB → bytes
@@ -359,10 +353,13 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
key = mountpoint.lstrip("/").replace("/", "-") or "root" key = mountpoint.lstrip("/").replace("/", "-") or "root"
pool = device.removeprefix("/dev/") pool = device.removeprefix("/dev/")
readonly = bool(re.search( readonly = bool(
rf'^{re.escape(device)}\s+\S+\s+\S+\s+(?:[^,\s]*,)*ro(?:,|\s|$)', re.search(
mounts, re.MULTILINE, rf"^{re.escape(device)}\s+\S+\s+\S+\s+(?:[^,\s]*,)*ro(?:,|\s|$)",
)) mounts,
re.MULTILINE,
)
)
result[key] = LogicalVolumeDict( result[key] = LogicalVolumeDict(
name=key, name=key,
@@ -478,7 +475,11 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
except Exception: except Exception:
running = enabled = False running = enabled = False
result[key] = StorageServiceDict( result[key] = StorageServiceDict(
name=key, enabled=enabled, running=running, port=port, version="", name=key,
enabled=enabled,
running=running,
port=port,
version="",
) )
return result return result
@@ -486,9 +487,7 @@ class OpenMediaVaultDriver(StorageDriver, LinuxDriver):
def get_volume_snapshots(self, volume: str = "") -> list[VolumeSnapshotDict]: def get_volume_snapshots(self, volume: str = "") -> list[VolumeSnapshotDict]:
"""Return volume snapshots (not supported on OpenMediaVault).""" """Return volume snapshots (not supported on OpenMediaVault)."""
raise NotImplementedError( raise NotImplementedError("OpenMediaVault does not expose volume snapshots via this driver")
"OpenMediaVault does not expose volume snapshots via this driver"
)
def get_replication_jobs(self) -> list[ReplicationJobDict]: def get_replication_jobs(self) -> list[ReplicationJobDict]:
"""Return replication jobs (not supported on OpenMediaVault).""" """Return replication jobs (not supported on OpenMediaVault)."""
+11
View File
@@ -41,6 +41,17 @@ Repository = "https://github.com/chrismanivong/napalm-openmediavault"
where = ["."] where = ["."]
include = ["napalm_openmediavault*"] include = ["napalm_openmediavault*"]
[tool.ruff]
line-length = 100
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
[tool.ruff.lint.per-file-ignores]
# Recorded device output keeps its lines.
"tests/**" = ["E501"]
[tool.mypy] [tool.mypy]
python_version = "3.9" python_version = "3.9"
strict = true strict = true
+3 -1
View File
@@ -9,10 +9,12 @@ def test_type_label_is_storage():
def test_type_label_overrides_linux(): def test_type_label_overrides_linux():
from napalm_linux.linux import LinuxDriver from napalm_linux.linux import LinuxDriver
assert LinuxDriver.TYPE_LABEL == "Linux" assert LinuxDriver.TYPE_LABEL == "Linux"
assert OpenMediaVaultDriver.TYPE_LABEL != LinuxDriver.TYPE_LABEL assert OpenMediaVaultDriver.TYPE_LABEL != LinuxDriver.TYPE_LABEL
def test_storage_device_flag_removed(): def test_storage_device_flag_removed():
assert not hasattr(OpenMediaVaultDriver, "storage_device"), \ assert not hasattr(OpenMediaVaultDriver, "storage_device"), (
"storage_device is superseded by TYPE_LABEL = 'Storage'" "storage_device is superseded by TYPE_LABEL = 'Storage'"
)