Files
felhom.eu/documentation/audits/night-2026-09-24/tools/chaos_schedule.py
T
2026-09-24 13:05:29 +02:00

66 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Part E's schedule — night 2026-09-24. Deterministic: seed 20260924, 12 rounds of action x accident.
Committed BEFORE round 1 and printed into DRILL-night-2026-09-24.md, so the schedule cannot be chosen after
the fact. Constraints from the brief: every action at least once; controller killed at most 3 times, at
least 20 minutes apart (rounds are ~6 min, so kills sit >= 4 rounds apart); a whole-box backup only on 9202.
python3 chaos_schedule.py # prints the table
python3 chaos_schedule.py --json # for the runner
"""
import json
import random
import sys
SEED = 20260924
ROUNDS = 12
ROUND_MIN = 6 # planned length of one round, minutes
ACTIONS = [
"ladder_step", # a proven ladder step (Update)
"failing_step", # a step whose probe fails -> undo
"cutoff_undo_file_app", # a file app's undo copy cut off -> the second-drive whole restore
"crash_loop", # a drill image that exits -> the box stops it
"oom_app", # a tiny mem_limit -> out-of-memory kills
"install",
"remove_held", # Remove on a held app
"start_after_unhealthy",# Start after the box stopped an app
]
ACCIDENTS = ["none", "power_cut", "controller_killed", "docker_restart", "disk_to_floor_plus_1g",
"whole_box_backup"]
MAX_KILLS, KILL_GAP_MIN = 3, 20
def schedule(seed=SEED):
rng = random.Random(seed)
for _ in range(10000):
acts = ACTIONS + [rng.choice(ACTIONS) for _ in range(ROUNDS - len(ACTIONS))]
rng.shuffle(acts)
accs = [rng.choice(ACCIDENTS) for _ in range(ROUNDS)]
kills = [i for i, a in enumerate(accs) if a == "controller_killed"]
if len(kills) > MAX_KILLS:
continue
if any((b - a) * ROUND_MIN < KILL_GAP_MIN for a, b in zip(kills, kills[1:])):
continue
# Start-after-unhealthy needs an app the box stopped: never before the first crash_loop/oom_app.
first_stop = min(i for i, a in enumerate(acts) if a in ("crash_loop", "oom_app"))
if acts.index("start_after_unhealthy") < first_stop:
continue
# Remove-on-held needs a held app: after a failing_step or cutoff.
first_hold = min(i for i, a in enumerate(acts) if a in ("failing_step", "cutoff_undo_file_app"))
if acts.index("remove_held") < first_hold:
continue
return [{"round": i + 1, "action": acts[i], "accident": accs[i]} for i in range(ROUNDS)]
raise SystemExit("no schedule satisfies the constraints")
if __name__ == "__main__":
s = schedule()
if "--json" in sys.argv:
print(json.dumps(s, indent=1))
else:
print(f"seed {SEED}, {ROUNDS} rounds")
print("| round | action | accident |\n|---|---|---|")
for r in s:
print(f"| {r['round']} | {r['action']} | {r['accident']} |")