feat: SystemIdentityMixin reads a host's SMBIOS identity (4.9.0)
What ties a server to its BMC: the BMC reports the server's serial number and system UUID, the host reads the same values from its DMI tables. Both drivers that read DMI (napalm-linux, napalm-proxmox) had a copy of the shell for it, and neither read the board serial. SystemIdentityMixin.get_system_identity() reads product_uuid, product_serial and board_serial in one framed command and returns them with vendor placeholders empty: a Supermicro board's product serial is 0123456789 on every one of them, others read "To be filled by O.E.M." or "Default string", and many cheap boards share the UUID 03000200-0400-0500-0006-000700080009. is_placeholder_identity() is the same filter for a BMC driver's side. A driver supplies only _run_system_identity_command(command, privileged=); the files are root's.
This commit is contained in:
1 parent
a1da0337e2
commit
fde6d717a8
6 files changed
+247
-1
No files matched your search
@@ -0,0 +1,102 @@
|
||||
"""SystemIdentityMixin: the SMBIOS identity of a host, for matching it to its BMC."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from napalm_device_types import (
|
||||
SYSTEM_IDENTITY_COMMAND,
|
||||
SystemIdentityMixin,
|
||||
is_placeholder_identity,
|
||||
parse_system_identity,
|
||||
)
|
||||
|
||||
UUID = "31303043-534D-C40C-7A72-6F1300000000"
|
||||
|
||||
|
||||
def _out(uuid: str, product_serial: str, board_serial: str) -> str:
|
||||
return f"IDBEGIN\n{uuid}\n{product_serial}\n{board_serial}\nIDEND\n"
|
||||
|
||||
|
||||
# ── parsing ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_reads_uuid_and_both_serials():
|
||||
assert parse_system_identity(_out(UUID, "S123", "ZM155S026022")) == {
|
||||
"system_uuid": "31303043-534d-c40c-7a72-6f1300000000",
|
||||
"product_serial": "S123",
|
||||
"board_serial": "ZM155S026022",
|
||||
}
|
||||
|
||||
|
||||
def test_a_supermicro_placeholder_serial_is_dropped():
|
||||
# pve-dual, a Supermicro X10DRL-i: the product serial is the board vendor's default.
|
||||
identity = parse_system_identity(_out(UUID, "0123456789", "ZM155S026022"))
|
||||
assert identity["product_serial"] == ""
|
||||
assert identity["board_serial"] == "ZM155S026022"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
["", "None", "N/A", "Not Specified", "To be filled by O.E.M.", "Default string",
|
||||
"System Serial Number", "0123456789", "1234567890", "0", "00000000", "Not Applicable"],
|
||||
)
|
||||
def test_placeholders(value):
|
||||
assert is_placeholder_identity(value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uuid",
|
||||
["00000000-0000-0000-0000-000000000000", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF",
|
||||
# A UUID many cheap boards ship, the same on every one of them.
|
||||
"03000200-0400-0500-0006-000700080009"],
|
||||
)
|
||||
def test_placeholder_uuids(uuid):
|
||||
assert parse_system_identity(_out(uuid, "", ""))["system_uuid"] == ""
|
||||
|
||||
|
||||
def test_real_values_are_not_placeholders():
|
||||
assert not is_placeholder_identity("ZM155S026022")
|
||||
assert not is_placeholder_identity(UUID)
|
||||
|
||||
|
||||
def test_unreadable_files_read_as_empty():
|
||||
# product_serial and product_uuid are root's alone; without root they come back empty.
|
||||
assert parse_system_identity(_out("", "", "BS1")) == {
|
||||
"system_uuid": "", "product_serial": "", "board_serial": "BS1"}
|
||||
|
||||
|
||||
def test_output_without_its_frame_is_not_a_reading():
|
||||
with pytest.raises(ValueError):
|
||||
parse_system_identity("sudo: a password is required\n")
|
||||
|
||||
|
||||
def test_terminal_noise_around_the_frame_is_ignored():
|
||||
assert parse_system_identity("\x1b[?2004h" + _out(UUID, "S1", "B1"))["board_serial"] == "B1"
|
||||
|
||||
|
||||
# ── the mixin ─────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class _Host(SystemIdentityMixin):
|
||||
def __init__(self, output: str):
|
||||
self.output = output
|
||||
self.calls: list = []
|
||||
|
||||
def _run_system_identity_command(self, command: str, *, privileged: bool) -> str:
|
||||
self.calls.append((command, privileged))
|
||||
return self.output
|
||||
|
||||
|
||||
def test_the_driver_only_carries_the_command_across():
|
||||
host = _Host(_out(UUID, "S1", "B1"))
|
||||
|
||||
assert host.get_system_identity()["board_serial"] == "B1"
|
||||
((command, privileged),) = host.calls
|
||||
assert command == SYSTEM_IDENTITY_COMMAND
|
||||
assert privileged is True
|
||||
|
||||
|
||||
def test_the_command_reads_the_three_dmi_files():
|
||||
for name in ("product_uuid", "product_serial", "board_serial"):
|
||||
assert f"/sys/class/dmi/id/{name}" in SYSTEM_IDENTITY_COMMAND
|
||||
Reference in new issue
Block a user