fix(reliability): converge runtime after profile restore (park dummy STA, re-raise monitors)

This commit is contained in:
2026-08-22 18:26:21 -06:00
parent 61c929d891
commit 5625310977
2 changed files with 39 additions and 4 deletions
@@ -4894,6 +4894,21 @@ def h_profile_save(ctx):
return (200, {'ok': True}) if ok else (502, {'ok': False}) return (200, {'ok': True}) if ok else (502, {'ok': False})
def _post_restore_converge():
"""After any profile restore: re-apply invariants the restored config may
not carry. Factory-default configs enable dummy_radio0 (the phy0
channel-pinner) and may drop monitors; park and re-raise best-effort.
Uses ungated primitives the restore is already inside its own gate."""
try:
_park_dummy_sta()
except Exception:
pass
try:
_raise_monitors()
except Exception:
pass
def h_profile_restore(ctx): def h_profile_restore(ctx):
import mk8_events import mk8_events
import mk8_gate import mk8_gate
@@ -4911,6 +4926,7 @@ def h_profile_restore(ctx):
ok = bool(result.get('ok')) ok = bool(result.get('ok'))
if ok: if ok:
device_run(['wifi', 'reload'], timeout=45) device_run(['wifi', 'reload'], timeout=45)
_post_restore_converge()
try: try:
mk8_events.log_event('profile_restore', mk8_events.log_event('profile_restore',
sev='info' if ok else 'warn', sev='info' if ok else 'warn',
@@ -5001,6 +5017,16 @@ def _sta_uplink_enabled():
return cfg.get('mode') == 'sta' and cfg.get('disabled') != '1' return cfg.get('mode') == 'sta' and cfg.get('disabled') != '1'
def _park_dummy_sta():
"""Ungated primitive: disable dummy_radio0 + take wlan0 down. Callers
that already run inside a gate (post-restore convergence) use this to
avoid spawning a nested watchdog."""
device_run(['uci', 'set', 'wireless.dummy_radio0.disabled=1'])
device_run(['uci', 'commit', 'wireless'])
for iface in ('wlan0',):
rc, out, err = device_run(['ip', 'link', 'set', iface, 'down'], timeout=10)
def _disable_sta_uplink(): def _disable_sta_uplink():
"""Disable the dummy_radio0 STA without bouncing the radios. The UCI flag """Disable the dummy_radio0 STA without bouncing the radios. The UCI flag
keeps it off across reboots/wifi reloads; taking wlan0 down immediately keeps it off across reboots/wifi reloads; taking wlan0 down immediately
@@ -5008,10 +5034,7 @@ def _disable_sta_uplink():
tears down live APs and drops the monitors mid-assessment.""" tears down live APs and drops the monitors mid-assessment."""
import mk8_gate import mk8_gate
mk8_gate.enter('uplink_disable') mk8_gate.enter('uplink_disable')
device_run(['uci', 'set', 'wireless.dummy_radio0.disabled=1']) _park_dummy_sta()
device_run(['uci', 'commit', 'wireless'])
for iface in ('wlan0',):
rc, out, err = device_run(['ip', 'link', 'set', iface, 'down'], timeout=10)
def env_check(): def env_check():
@@ -7402,6 +7425,7 @@ if __name__ == '__main__':
import mk8_profiles import mk8_profiles
result = mk8_profiles.restore(name) result = mk8_profiles.restore(name)
device_run(['wifi', 'reload'], timeout=90) device_run(['wifi', 'reload'], timeout=90)
_post_restore_converge()
import mk8_events import mk8_events
mk8_events.log_event('rollback', sev='warn', mk8_events.log_event('rollback', sev='warn',
msg='watchdog restored %s' % name, msg='watchdog restored %s' % name,
+11
View File
@@ -208,6 +208,17 @@ class ReliabilityApiTest(unittest.TestCase):
self.assertEqual(calls['restored'], 'p1') self.assertEqual(calls['restored'], 'p1')
reloads = [r for r in self.runs if r[0][:2] == ['wifi', 'reload']] reloads = [r for r in self.runs if r[0][:2] == ['wifi', 'reload']]
self.assertEqual(len(reloads), 1) self.assertEqual(len(reloads), 1)
# post-restore convergence parks the factory-enabled dummy STA
parked = [r for r in self.runs
if r[0][:3] == ['uci', 'set',
'wireless.dummy_radio0.disabled=1']]
self.assertEqual(len(parked), 1)
downs = [r for r in self.runs if r[0][:4] == ['ip', 'link',
'set', 'wlan0']]
self.assertTrue(downs)
raises = [r for r in self.runs if r[0][:4] == ['ip', 'link',
'set', 'wlan0mon']]
self.assertTrue(raises)
self.assertEqual(calls['events'][-1][0], 'profile_restore') self.assertEqual(calls['events'][-1][0], 'profile_restore')
status, result = server.h_profile_restore( status, result = server.h_profile_restore(