feat(system): report remote syslog, LuCI state and bridge STP

get_system_config() already parsed the whole system section but returned
only a slice of it, so netOrk had no IST side for three AP-profile fields
and could not compare them at all — the fields were editable, stored and
silently ineffective.

Adds:
  * syslog_remote / syslog_ip / syslog_port / syslog_proto — read from the
    system section that was already being parsed. log_remote gates the
    others: OpenWrt ships nothing without it, so a leftover log_ip must
    not read as an active target.
  * luci_enabled — from the uhttpd init script rather than its listener
    config, so the answer stays "is the web UI served" and re-enabling
    restores whatever was configured before.
  * bridge_stp — None when there is no br-ap at all, which is a different
    statement from "bridge without STP" and has to stay distinguishable.

netOrk #164
This commit is contained in:
Christian Manivong
2026-08-29 22:41:36 +07:00
parent fd972a427e
commit d45c082355
2 changed files with 172 additions and 0 deletions
+133
View File
@@ -1734,3 +1734,136 @@ class TestUciSectionParser:
def test_blank_and_malformed_lines_ignored(self, driver):
parsed = driver._parse_uci_sections("\n\nnot a uci line\nfirewall.x=rule\n")
assert list(parsed) == ["x"]
# ---------------------------------------------------------------------------
# get_system_config — remote syslog, LuCI and bridge STP (netOrk #164)
# ---------------------------------------------------------------------------
UCI_SYSTEM_WITH_SYSLOG = """\
system.@system[0]=system
system.@system[0].hostname='ap-eze-Garten'
system.@system[0].timezone='CET-1CEST,M3.5.0,M10.5.0/3'
system.@system[0].zonename='Europe/Berlin'
system.@system[0].log_remote='1'
system.@system[0].log_ip='10.10.40.2'
system.@system[0].log_port='5514'
system.@system[0].log_proto='udp'
system.ntp=timeserver
system.ntp.server='0.openwrt.pool.ntp.org' '1.openwrt.pool.ntp.org'
"""
UCI_SYSTEM_NO_SYSLOG = """\
system.@system[0]=system
system.@system[0].hostname='ap-eze-Parkplatz'
system.@system[0].timezone='GMT0'
system.@system[0].zonename='UTC'
system.ntp=timeserver
system.ntp.server='0.openwrt.pool.ntp.org'
"""
UCI_DROPBEAR_SYS = """\
dropbear.@dropbear[0]=dropbear
dropbear.@dropbear[0].Port='22'
dropbear.@dropbear[0].PasswordAuth='on'
dropbear.@dropbear[0].RootPasswordAuth='on'
"""
UCI_NETWORK_STP_ON = """\
network.ap_bridge=device
network.ap_bridge.name='br-ap'
network.ap_bridge.type='bridge'
network.ap_bridge.stp='1'
network.lan=interface
"""
UCI_NETWORK_STP_OFF = """\
network.ap_bridge=device
network.ap_bridge.name='br-ap'
network.ap_bridge.type='bridge'
network.lan=interface
"""
def _system_send(system_out=UCI_SYSTEM_WITH_SYSLOG, luci_out="1", network_out=UCI_NETWORK_STP_ON):
"""Dispatch _send_command by the command it receives."""
def _send(cmd, **kw):
if "uci show system" in cmd:
return system_out
if "uci show dropbear" in cmd:
return UCI_DROPBEAR_SYS
if "uci show network" in cmd:
return network_out
if "uhttpd" in cmd:
return luci_out
return ""
return _send
class TestGetSystemConfigSyslog:
def test_remote_syslog_target_is_reported(self, driver):
driver._send_command = _system_send()
cfg = driver.get_system_config()
assert cfg["syslog_remote"] is True
assert cfg["syslog_ip"] == "10.10.40.2"
assert cfg["syslog_port"] == 5514
def test_absent_syslog_reports_as_disabled(self, driver):
driver._send_command = _system_send(UCI_SYSTEM_NO_SYSLOG)
cfg = driver.get_system_config()
assert cfg["syslog_remote"] is False
assert cfg["syslog_ip"] == ""
def test_log_ip_without_log_remote_is_not_active(self, driver):
# OpenWrt only ships logs when log_remote is set, whatever log_ip says.
out = UCI_SYSTEM_NO_SYSLOG + "system.@system[0].log_ip='10.10.40.2'\n"
driver._send_command = _system_send(out)
assert driver.get_system_config()["syslog_remote"] is False
def test_port_falls_back_to_the_openwrt_default(self, driver):
out = UCI_SYSTEM_NO_SYSLOG + (
"system.@system[0].log_remote='1'\nsystem.@system[0].log_ip='10.10.40.2'\n"
)
driver._send_command = _system_send(out)
assert driver.get_system_config()["syslog_port"] == 514
def test_existing_fields_are_untouched(self, driver):
driver._send_command = _system_send()
cfg = driver.get_system_config()
assert cfg["timezone"] == "CET-1CEST,M3.5.0,M10.5.0/3"
assert cfg["zonename"] == "Europe/Berlin"
assert cfg["dropbear_port"] == 22
assert cfg["ntp_servers"] == ["0.openwrt.pool.ntp.org", "1.openwrt.pool.ntp.org"]
class TestGetSystemConfigLuci:
def test_enabled_uhttpd_reports_luci_as_reachable(self, driver):
driver._send_command = _system_send(luci_out="1")
assert driver.get_system_config()["luci_enabled"] is True
def test_disabled_uhttpd_reports_luci_as_unreachable(self, driver):
driver._send_command = _system_send(luci_out="0")
assert driver.get_system_config()["luci_enabled"] is False
def test_missing_uhttpd_reports_as_unreachable(self, driver):
# No uhttpd installed at all — LuCI cannot be served.
driver._send_command = _system_send(luci_out="")
assert driver.get_system_config()["luci_enabled"] is False
class TestGetSystemConfigBridgeStp:
def test_stp_enabled_is_reported(self, driver):
driver._send_command = _system_send(network_out=UCI_NETWORK_STP_ON)
assert driver.get_system_config()["bridge_stp"] is True
def test_absent_stp_option_means_disabled(self, driver):
# UCI defaults stp to 0 when the option is not present.
driver._send_command = _system_send(network_out=UCI_NETWORK_STP_OFF)
assert driver.get_system_config()["bridge_stp"] is False
def test_no_ap_bridge_reports_none(self, driver):
# Nothing to have an opinion about — distinct from "STP is off".
driver._send_command = _system_send(network_out="network.lan=interface\n")
assert driver.get_system_config()["bridge_stp"] is None