feat: ARM hardware detection via device tree and /proc/cpuinfo
On ARM boards (Raspberry Pi, ODROID, etc.) /sys/class/dmi/id/ does not
exist. _collect_platform_info() now falls back to:
- /sys/firmware/devicetree/base/model (preferred)
- /proc/cpuinfo Model: / Serial: (fallback)
Three bugs fixed in the process:
1. systemd-detect-virt exits 1 on bare metal, so the old
"|| echo none" pattern produced d="none\nnone" (two lines),
shifting all subsequent fields by one. Fixed with ${d:-none}.
2. _send() calls .strip() on output, silently eating the five leading
blank lines that represent empty DMI fields on ARM. Fixed by
prefixing the printf output with a DMIBEGIN sentinel so the parser
can locate field 0 regardless of leading whitespace.
3. Vendor was always empty for ARM, falling back to the generic "Linux"
constant. Added _ARM_VENDOR_PREFIXES lookup table and
_arm_vendor_from_model() to derive the canonical vendor name from
the model string (e.g. "Raspberry Pi Foundation" for any RPi board).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
450aa193ba
commit
1bfc8dd245
+101
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from napalm_linux.linux import LinuxDriver
|
||||
from napalm_linux.linux import LinuxDriver, _arm_vendor_from_model
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -368,8 +368,18 @@ def _dmi_output(
|
||||
product_serial: str,
|
||||
product_uuid: str,
|
||||
detect_virt: str,
|
||||
dt_model: str = "",
|
||||
cpuinfo_serial: str = "",
|
||||
cpuinfo_model: str = "",
|
||||
) -> str:
|
||||
return "\n".join([sys_vendor, product_name, product_version, product_serial, product_uuid, detect_virt])
|
||||
# Mirrors the real shell output: DMIBEGIN sentinel followed by 9 fields.
|
||||
# The sentinel prevents _send()'s .strip() from eating leading blank lines
|
||||
# (which occur on ARM where all DMI files are absent).
|
||||
return "\n".join([
|
||||
"DMIBEGIN",
|
||||
sys_vendor, product_name, product_version, product_serial,
|
||||
product_uuid, detect_virt, dt_model, cpuinfo_serial, cpuinfo_model,
|
||||
])
|
||||
|
||||
|
||||
class TestCollectPlatformInfo:
|
||||
@@ -482,6 +492,95 @@ class TestCollectPlatformInfo:
|
||||
assert info["model"] == ""
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_arm_device_tree_fallback(self, driver):
|
||||
raw = _dmi_output(
|
||||
"", "", "", "", "", "none",
|
||||
dt_model="Raspberry Pi 4 Model B Rev 1.4",
|
||||
cpuinfo_serial="100000002a6d96dc",
|
||||
cpuinfo_model="Raspberry Pi 4 Model B Rev 1.4",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Raspberry Pi Foundation"
|
||||
assert info["model"] == "Raspberry Pi 4 Model B Rev 1.4"
|
||||
assert info["serial"] == "100000002a6d96dc"
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_arm_cpuinfo_model_when_no_dt(self, driver):
|
||||
raw = _dmi_output(
|
||||
"", "", "", "", "", "none",
|
||||
dt_model="",
|
||||
cpuinfo_serial="0000000012345678",
|
||||
cpuinfo_model="Raspberry Pi 3 Model B Plus Rev 1.3",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Raspberry Pi Foundation"
|
||||
assert info["model"] == "Raspberry Pi 3 Model B Plus Rev 1.3"
|
||||
assert info["serial"] == "0000000012345678"
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_arm_no_fallback_data_returns_empty(self, driver):
|
||||
raw = _dmi_output("", "", "", "", "", "none")
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == ""
|
||||
assert info["model"] == ""
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_bare_metal_detect_virt_exit1_does_not_shift_arm_fields(self, driver):
|
||||
# systemd-detect-virt exits 1 on bare metal, old "|| echo none" caused
|
||||
# d="none\nnone" which shifted subsequent lines off by one.
|
||||
# With the ${d:-none} fix this no longer happens.
|
||||
raw = _dmi_output(
|
||||
"", "", "", "", "", "none",
|
||||
dt_model="",
|
||||
cpuinfo_serial="0000000012345678",
|
||||
cpuinfo_model="Raspberry Pi 3 Model B Rev 1.2",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Raspberry Pi Foundation"
|
||||
assert info["model"] == "Raspberry Pi 3 Model B Rev 1.2"
|
||||
assert info["serial"] == "0000000012345678"
|
||||
|
||||
def test_arm_fallback_ignored_when_dmi_present(self, driver):
|
||||
raw = _dmi_output(
|
||||
"Dell Inc.", "PowerEdge R720", "Not Specified", "XYZ999",
|
||||
"8a2e3f00-dead-beef-0000-123456789abc", "none",
|
||||
dt_model="some-dt-model",
|
||||
cpuinfo_serial="deadbeef",
|
||||
cpuinfo_model="some cpuinfo model",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Dell Inc."
|
||||
assert info["model"] == "PowerEdge R720"
|
||||
assert info["serial"] == "XYZ999"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _arm_vendor_from_model
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model,expected", [
|
||||
("Raspberry Pi 4 Model B Rev 1.4", "Raspberry Pi Foundation"),
|
||||
("Raspberry Pi 3 Model B Rev 1.2", "Raspberry Pi Foundation"),
|
||||
("ODROID-N2L", "Hardkernel"),
|
||||
("NVIDIA Jetson Nano Developer Kit", "NVIDIA"),
|
||||
("Rock Pi 4C", "Radxa"),
|
||||
("Orange Pi 5 Plus", "Xunlong Software"),
|
||||
("Banana Pi BPI-R3", "SinoVoip"),
|
||||
("NanoPi R4S", "FriendlyElec"),
|
||||
("PINE64 RockPro64", "Pine64"),
|
||||
("BeagleBone Black", "BeagleBoard.org"),
|
||||
("Unknown Board 1.0", "Unknown Board"),
|
||||
("SomeSingleWordBoard", "SomeSingleWordBoard"),
|
||||
])
|
||||
def test_arm_vendor_from_model(model, expected):
|
||||
assert _arm_vendor_from_model(model) == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_facts uses _collect_platform_info
|
||||
|
||||
Reference in New Issue
Block a user