feat(docker): a removed container's logs and events stay reachable by name
#5010 "Logs of a removed container cannot be reached by name" - get_container and search_container_logs fall back to the names Steward still holds logs or events for when no live container matches: the exact name from logs and events, or a part of it that matches one removed container's events. - get_container answers with removed: true, last_seen_at, its history and events; search_container_logs marks container_removed and notes when it was last seen, and says when that is older than the range. - A miss now says the container may have been removed, that Swarm replaces a dead task under a new name, and where to look instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ def test_docker_and_agent_tools_over_seeded_rows(app):
|
||||
host_name, quiet_host = f"dock-{tag}", f"quiet-{tag}"
|
||||
web, db_, job, svc = f"web-{tag}", f"db-{tag}", f"job-{tag}", f"svc-{tag}"
|
||||
replaced = f"{svc}.1.old"
|
||||
removed = f"{svc}.2.gone{tag}" # a dead task Docker removed: logs and events only
|
||||
|
||||
def _ago(minutes):
|
||||
return (now - timedelta(minutes=minutes)).isoformat().replace("+00:00", "Z")
|
||||
@@ -95,6 +96,14 @@ def test_docker_and_agent_tools_over_seeded_rows(app):
|
||||
"INSERT INTO docker_events (id, host_id, container_name, event, detail, at) "
|
||||
"VALUES (:id, :h, :n, 'die', 'exit 1', :at)"),
|
||||
{"id": str(uuid.uuid4()), "h": hid, "n": name, "at": now - timedelta(minutes=10)})
|
||||
await s.execute(text(
|
||||
"INSERT INTO docker_events (id, host_id, container_name, event, detail, at) "
|
||||
"VALUES (:id, :h, :n, 'die', 'exit 1', :at)"),
|
||||
{"id": str(uuid.uuid4()), "h": hid, "n": removed, "at": now - timedelta(minutes=12)})
|
||||
await s.execute(text(
|
||||
"INSERT INTO docker_logs (id, host_id, container_name, ts, stream, line) "
|
||||
"VALUES (:id, :h, :n, :ts, 'stderr', 'FATAL: cannot open database')"),
|
||||
{"id": str(uuid.uuid4()), "h": hid, "n": removed, "ts": now - timedelta(minutes=13)})
|
||||
for i, (stream, line) in enumerate((
|
||||
("stdout", "starting up"), ("stdout", "connected to db"),
|
||||
("stderr", "ERROR: 100% of disk_used"), ("stdout", "retrying"),
|
||||
@@ -194,6 +203,19 @@ def test_docker_and_agent_tools_over_seeded_rows(app):
|
||||
with pytest.raises(ValueError, match="context needs `container`"):
|
||||
await dtools.search_container_logs(text="x", context=2)
|
||||
|
||||
# ── a container Docker removed is still reachable by name ─────────
|
||||
gone = await dtools.get_container(removed)
|
||||
assert gone["removed"] is True and gone["host_name"] == host_name
|
||||
assert [e["event"] for e in gone["events"]] == ["die"] and "no longer exists" in gone["note"]
|
||||
assert (await dtools.get_container(web))["removed"] is False
|
||||
lines = await dtools.search_container_logs(container=removed, range="1h")
|
||||
assert lines["container_removed"] is True
|
||||
assert [m["line"] for m in lines["matches"]] == ["FATAL: cannot open database"]
|
||||
# A unique part of its name finds it through its events.
|
||||
assert (await dtools.get_container(f"2.gone{tag}"))["container_name"] == removed
|
||||
with pytest.raises(ValueError, match="may have been removed"):
|
||||
await dtools.get_container(f"nothing-{tag}")
|
||||
|
||||
# ── swarm, disk ────────────────────────────────────────────────────
|
||||
sw = await dtools.list_swarm_services(limit=500)
|
||||
s = next(x for x in sw["services"] if x["service_name"] == svc)
|
||||
|
||||
Reference in New Issue
Block a user