#!/usr/bin/env python3 """R-626 — does a removed app come back? Measured through the product on 9202. deploy navidrome -> remove through the product -> `docker events` for the compose project for 5 min, a controller restart at +150 s -> then a GUEST REBOOT and a look after it. Positive observable: the events stream must show the remove's own destroy events (proves the watcher saw the project), and any `create` after the remove's answer is the defect. """ import sys, time, json sys.path.insert(0, ".") import walk as w APP = sys.argv[1] if len(sys.argv) > 1 else "navidrome" w.login() w.say(f"R-626 run on guest {w.GUEST}, app {APP}") if not w.deploy(APP, APP): sys.exit("deploy failed") w.wait_app(APP, "/") w.say("starting docker events watcher (400 s) in the guest") w.guest(f"rm -f /root/r626-events.txt; nohup timeout 400 docker events --format '{{{{.Time}}}} {{{{.Type}}}} {{{{.Action}}}} {{{{.Actor.Attributes.name}}}} project={{{{index .Actor.Attributes \"com.docker.compose.project\"}}}}' --filter label=com.docker.compose.project={APP} > /root/r626-events.txt 2>&1 &\nsleep 1; echo started") time.sleep(3) t_remove = time.time() code = w.remove(APP) w.say(f"remove answered {code} at epoch {int(t_remove)}") checks = {} for at in (60, 150, 300, 390): while time.time() - t_remove < at: time.sleep(2) if at == 150: w.say("+150 s: restarting the controller (docker restart felhom-controller)") w.guest("docker restart felhom-controller >/dev/null; echo restarted") time.sleep(20) w.login() out = w.guest(f"docker ps -a --filter label=com.docker.compose.project={APP} --format '{{{{.Names}}}} {{{{.Status}}}}'; docker volume ls -q | grep -c '^{APP}_' || true") st = w.stack(APP) checks[at] = {"containers_and_volcount": out.strip(), "deployed": st.get("deployed")} w.say(f"+{at} s: {checks[at]}") ev = w.guest("cat /root/r626-events.txt") w.say("events after the remove:\n" + ev) w.say("rebooting guest 9202 (pct reboot)") w.sh(["ssh", w.HP, f"pct reboot {w.GUEST}"], timeout=300) for _ in range(60): time.sleep(5) r = w.sh(["ssh", w.HP, f"pct exec {w.GUEST} -- docker ps --format '{{{{.Names}}}}'"], timeout=30) if "felhom-controller" in (r.stdout or ""): break time.sleep(60) w.login() after = w.guest(f"docker ps -a --filter label=com.docker.compose.project={APP} --format '{{{{.Names}}}} {{{{.Status}}}}'; docker volume ls -q | grep '^{APP}_' || true; ls -d /opt/docker/stacks/{APP} 2>&1") st = w.stack(APP) w.say(f"after reboot (+60 s): containers/volumes/dir = {after.strip()!r} deployed={st.get('deployed')}") creates = [l for l in ev.splitlines() if " create " in l] destroys = [l for l in ev.splitlines() if " destroy " in l] verdict = {"app": APP, "remove_http": code, "checks": checks, "events_create_after_remove": creates, "events_destroy": destroys, "after_reboot": after.strip(), "deployed_after_reboot": st.get("deployed"), "positive_observable_destroy_events_seen": bool(destroys), "came_back": bool(creates) or bool([l for l in after.splitlines() if l.strip() and "No such file" not in l and not l.startswith("ls:")])} json.dump(verdict, open("A3-r626-verdict.json", "w"), indent=2) w.say("VERDICT " + json.dumps({k: verdict[k] for k in ("came_back", "positive_observable_destroy_events_seen")})) open("A3-r626-log.txt", "w").write("\n".join(w.LOG) + "\n")