|
@@ -1608,6 +1608,69 @@ def main():
|
|
|
and "ONE final review" in notice and "saddle review" in notice,
|
|
and "ONE final review" in notice and "saddle review" in notice,
|
|
|
"resume notice ends with the final-review instruction")
|
|
"resume notice ends with the final-review instruction")
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ print("== R4 B3: dispatch-failure observability ==")
|
|
|
|
|
+ # A dead saddle that alone carries the required tag attracts the
|
|
|
|
|
+ # dispatch; the push fails and the task learns why via a bot comment.
|
|
|
|
|
+ saddles_state = slot_public(auth, "/saddles", "saddles")
|
|
|
|
|
+ act(auth, saddles_state["register"], {"name": "dead-saddle",
|
|
|
|
|
+ "host": "127.0.0.1:1"})
|
|
|
|
|
+ dead_row = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
|
|
|
|
|
+ if s["name"] == "dead-saddle"][0]
|
|
|
|
|
+ act(auth, dead_row["attach_capability"], {"capability_id": str(gpu_option)})
|
|
|
|
|
+ act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
|
|
|
|
|
+ "project_id": "1",
|
|
|
|
|
+ "title": "Dead saddle task",
|
|
|
|
|
+ "capability_id": [str(gpu_option)],
|
|
|
|
|
+ })
|
|
|
|
|
+ dead_task = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
|
|
|
|
|
+ if t["title"] == "Dead saddle task"][0])
|
|
|
|
|
+ check(wait_for(lambda: any("assignment push failed" in c["body"]
|
|
|
|
|
+ for c in task_state(auth, dead_task).get("comments", []))
|
|
|
|
|
+ or False, timeout=60, what="push-failure comment") is not None,
|
|
|
|
|
+ "failed push writes the reason on the task")
|
|
|
|
|
+ check(task_state(auth, dead_task)["status"] == "queued",
|
|
|
|
|
+ "push-failed task returns to the queue")
|
|
|
|
|
+ dead_rows = [s for s in slot_public(auth, "/saddles", "saddles")["saddles"]
|
|
|
|
|
+ if s["name"] == "dead-saddle"]
|
|
|
|
|
+ check(dead_rows[0]["status"] == "offline",
|
|
|
|
|
+ "the failing saddle goes offline")
|
|
|
|
|
+ act(auth, dead_rows[0]["unregister"], {})
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ conn.execute("delete from task_capabilities where capability_id = ?"
|
|
|
|
|
+ " and task_id = ?", (gpu_option, dead_task))
|
|
|
|
|
+ conn.commit()
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ check(wait_task_done(auth, dead_task, timeout=60) is not None,
|
|
|
|
|
+ "the task recovers on the healthy saddle")
|
|
|
|
|
+
|
|
|
|
|
+ # Tasks caught working on a saddle that went offline requeue with a
|
|
|
|
|
+ # bot comment each tick.
|
|
|
|
|
+ fake.suspend_after_start = False
|
|
|
|
|
+ act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
|
|
|
|
|
+ "project_id": "1",
|
|
|
|
|
+ "title": "Offline saddle task",
|
|
|
|
|
+ "priority": "2",
|
|
|
|
|
+ })
|
|
|
|
|
+ offline_task = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
|
|
|
|
|
+ if t["title"] == "Offline saddle task"][0])
|
|
|
|
|
+ check(wait_for(lambda: task_state(auth, offline_task).get("status") == "working"
|
|
|
|
|
+ or False, timeout=60, what="offline task starts working") is not None,
|
|
|
|
|
+ "task reaches working before its saddle disappears")
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ conn.execute("update saddles set status = 'offline' where name = 'fake-saddle'")
|
|
|
|
|
+ conn.commit()
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ # The requeue frees the saddle in the same tick, so 'queued' is
|
|
|
|
|
+ # transient; the durable evidence is the bot comment (and the task
|
|
|
|
|
+ # completing again on the healthy daemon).
|
|
|
|
|
+ check(wait_for(lambda: any(c["body"] == "saddle went offline; task requeued"
|
|
|
|
|
+ for c in task_state(auth, offline_task).get("comments", []))
|
|
|
|
|
+ or False, timeout=30, what="offline requeue comment") is not None,
|
|
|
|
|
+ "offline saddle requeues the task")
|
|
|
|
|
+ check(wait_task_done(auth, offline_task, timeout=60) is not None,
|
|
|
|
|
+ "requeued task completes again on the healthy daemon")
|
|
|
|
|
+
|
|
|
finally:
|
|
finally:
|
|
|
# Exit-code diagnostic: distinguishes a crashed app (negative
|
|
# Exit-code diagnostic: distinguishes a crashed app (negative
|
|
|
# returncode, e.g. -11 SIGSEGV) from a clean shutdown. When
|
|
# returncode, e.g. -11 SIGSEGV) from a clean shutdown. When
|