feat(interfaces): report the assigned interface name alongside the physical one
CI / test (3.10) (push) Failing after 8s
CI / test (3.11) (push) Failing after 7s
CI / test (3.12) (push) Failing after 10s
CI / test (3.9) (push) Failing after 8s

get_interfaces() keys entries by the physical device ("em0"), which is what
every other call in this driver speaks. Wake-on-LAN is the exception: it needs
the name OPNsense assigned ("lan", "opt1") and silently rejects anything else
with an empty {} at HTTP 200.

The overview export already carries it, so pass it through as "identifier".
Empty for interfaces OPNsense has not assigned.
This commit is contained in:
Christian Manivong
2026-08-20 11:10:25 +07:00
parent 8ba95a0709
commit 0c5670981d
2 changed files with 45 additions and 0 deletions
+6
View File
@@ -220,6 +220,11 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver):
Each entry contains NAPALM standard keys: Each entry contains NAPALM standard keys:
``is_up``, ``is_enabled``, ``description``, ``last_flapped``, ``is_up``, ``is_enabled``, ``description``, ``last_flapped``,
``mac_address``, ``speed``, ``mtu``. ``mac_address``, ``speed``, ``mtu``.
Plus one non-standard key, ``identifier``: OPNsense's *assigned*
interface name ("lan", "opt1"), as opposed to the physical device the
dict is keyed by ("em0"). Empty for unassigned interfaces. This is the
name :meth:`send_wake_on_lan` requires — see the note there.
""" """
data = self._get("/api/interfaces/overview/export") data = self._get("/api/interfaces/overview/export")
interfaces: dict[str, dict[str, Any]] = {} interfaces: dict[str, dict[str, Any]] = {}
@@ -240,6 +245,7 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver):
"mac_address": (iface.get("macaddr") or iface.get("mac") or "").lower(), "mac_address": (iface.get("macaddr") or iface.get("mac") or "").lower(),
"speed": float(iface["speed_mbps"]) if iface.get("speed_mbps") else 0.0, "speed": float(iface["speed_mbps"]) if iface.get("speed_mbps") else 0.0,
"mtu": int(iface["mtu"]) if iface.get("mtu") else 0, "mtu": int(iface["mtu"]) if iface.get("mtu") else 0,
"identifier": iface.get("identifier") or "",
} }
return interfaces return interfaces
+39
View File
@@ -61,6 +61,31 @@ INTERFACES_RESPONSE = {
] ]
} }
# Bare-list shape with the "identifier" field: OPNsense's assigned interface
# name, distinct from the physical "device" the dict is keyed by. Wake-on-LAN
# is addressed by the former, everything else in this driver by the latter.
IDENTIFIED_INTERFACES_RESPONSE = [
{
"device": "em0",
"identifier": "lan",
"description": "LAN",
"status": "up",
"enabled": True,
"macaddr": "AA:BB:CC:DD:EE:FF",
"speed_mbps": 1000,
"mtu": 1500,
},
{
"device": "em1",
"identifier": "opt1",
"description": "GUEST",
"status": "down",
"enabled": True,
"macaddr": "AA:BB:CC:DD:EE:00",
"mtu": 1500,
},
]
# Real shape confirmed against a live OPNsense 24.7 instance's # Real shape confirmed against a live OPNsense 24.7 instance's
# GET /api/interfaces/overview/export (see issue #14): a bare top-level list # GET /api/interfaces/overview/export (see issue #14): a bare top-level list
# of interface dicts, addressed via "device" and CIDR "addr4"/"addr6" # of interface dicts, addressed via "device" and CIDR "addr4"/"addr6"
@@ -262,6 +287,20 @@ class TestGetInterfaces:
ifaces = driver.get_interfaces() ifaces = driver.get_interfaces()
assert ifaces["em0"]["last_flapped"] == -1.0 assert ifaces["em0"]["last_flapped"] == -1.0
def test_identifier_passed_through(self, driver):
# OPNsense's *assigned* name, which is what send_wake_on_lan() needs —
# the dict key is the physical device ("em0"), which it rejects.
driver._get = lambda path: IDENTIFIED_INTERFACES_RESPONSE
ifaces = driver.get_interfaces()
assert ifaces["em0"]["identifier"] == "lan"
assert ifaces["em1"]["identifier"] == "opt1"
def test_identifier_empty_when_unassigned(self, driver):
# An interface OPNsense has not assigned carries no identifier.
driver._get = lambda path: INTERFACES_RESPONSE
ifaces = driver.get_interfaces()
assert ifaces["em0"]["identifier"] == ""
def test_empty_response(self, driver): def test_empty_response(self, driver):
driver._get = lambda path: {"interfaces": []} driver._get = lambda path: {"interfaces": []}
assert driver.get_interfaces() == {} assert driver.get_interfaces() == {}