Don't let an unreachable radio restart-loop the updater #85
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/radio-recover-restart-loop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
On a host where AppArmor refuses the container's D-Bus connection to NetworkManager,
radio.recover()inrun()raisedRadioErrorunguarded, somain()exited non-zero and compose restarted the container forever.recover()is best-effort tidy-up of a hotspot and uplink an earlier run died holding, not a precondition — nothing else the service does needs the radio, and a session that needs a hotspot already fails on its own. It's now guarded the same wayrestore()already is, with one clear_LOGGER.errorline at start saying firmware updates won't work on this host, rather than a stack trace repeating every restart.restore()on the way out is downgraded to a debug line with no traceback when the radio was already known unreachable fromrecover(), so stopping doesn't repeat the same traceback for no new information.Fixes CCS-UHA-26.
Tested: rewrote the existing
test_a_radio_that_will_not_answer_stops_the_service_saying_why(whose old assertion — thatrun()raises — was the bug's contract) to cover the new behaviour, and added a test that stopping produces no traceback for a radio unreachable from the start. Full suite passes (1211 tests).Verified the fix end to end: with a
FakeNmclirefusingconnection show,run()now reachesserve(), stops cleanly on SIGTERM, returns 0, and logs one ERROR line naming the host as broken for updates;restore()'s repeat failure on the way out logs at DEBUG with noexc_info.unreachableis a local inrun()so it can't leak between invocations. No flag threaded intoserve()/UpdateSession. Tabs, British spelling, ruff and the fulltest_updater_radio.pysuite (48 tests) all clean.Two small, non-blocking points:
@ -644,1 +644,4 @@nmcli.refuse = "connection show"running = asyncio.create_task(run(Settings(tmp_path, ""), Radio(nmcli)))await asyncio.sleep(0)if running.done():This
asyncio.sleep(0)+if running.done(): running.result()guard is new to the file (other tests justawait asyncio.sleep(0)and send the signal). It's doing real work — it protects againstcreate_task+immediate-raise_signalsending a real SIGTERM before the task has run far enough to install its handlers, and it turns a regression back to the unguarded-recover()bug into a clean re-raisedRadioErrorinside the test instead of a killed pytest process. Worth a one-line comment saying that, since a future editor is likely to "simplify" it away as dead code.@ -650,0 +660,4 @@async def test_a_radio_unreachable_from_the_start_gets_one_traceback_not_two(tmp_path: Path, caplog: pytest.LogCaptureFixtureThese two tests share identical setup (same
FakeNmcli, same task, same sleep(0)/done() guard) and only differ in thecaploglevel and final assertion. Given this repo's preference for less structure, consider folding into one test asserting all three things (return 0, the ERROR line, noexc_infoanywhere) under a singlecaplog.at_level(logging.DEBUG, ...).