fix(maintenance): a late return, once back, stays back, and every reader agrees (#6175)
18032a4 re-checked a "not back in service" job every minute with no end,
so any later swarm wobble on the host (radarr's failed task during the
downloads move, say) flipped maintenance_failed back to 1: five false
firings and ten emails for Docker02 overnight on 10-19. And the overview's
finding never learned the rule at all, so it still said Docker02's
maintenance failed three days on.
The job now keeps came_back_at, set the first time its host is found back.
maintenance.still_failed(job) is the one answer: the figure, the overview,
the cadence page's run rows and the host panel all ask it. A failed update
or a timed-out job still flags its host until a job succeeds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,7 @@ def _job_row(job, names: dict, now) -> dict:
|
||||
"swarm_node": job.node_id,
|
||||
"drained_by": names.get(job.drain_host_id, job.drain_host_id),
|
||||
"drained_at": iso(job.drained_at),
|
||||
"came_back_at": iso(job.came_back_at),
|
||||
"docker_update": job.docker_update,
|
||||
"checked": job.checked,
|
||||
"detail": job.detail,
|
||||
@@ -205,7 +206,9 @@ async def get_maintenance_job(host: str = "", job: str = "", limit: int = 10) ->
|
||||
settling →] updating → [draining → settling →] [rebooting →]
|
||||
[undraining →] [recovering →] done, or failed / timed_out. recovering
|
||||
waits up to 10 min for those containers to run again; `detail` says why
|
||||
a job failed, naming any that didn't. `summary` is the host page's line
|
||||
a job failed, naming any that didn't. A job failed only for not coming
|
||||
back into service in time has `came_back_at`, when its host was first
|
||||
found back; from then it no longer flags the host. `summary` is the host page's line
|
||||
("Done: 12 packages updated, rebooted"). `gives_up_at` is when an active
|
||||
job's current state times out.
|
||||
"""
|
||||
@@ -312,7 +315,8 @@ async def run_maintenance(host: str, reboot: str = "if_required") -> dict:
|
||||
|
||||
async def _failed_jobs(db, now) -> list:
|
||||
"""Each host's latest finished job, when it failed or timed out within
|
||||
FAILED_JOB_DAYS (a later success clears it)."""
|
||||
FAILED_JOB_DAYS and still flags the host (a later success clears it, and
|
||||
so does a late return once the host is back, #6175)."""
|
||||
rows = (await db.execute(
|
||||
select(HostMaintenanceJob, Host.name).join(Host, Host.id == HostMaintenanceJob.host_id)
|
||||
.where(HostMaintenanceJob.state.in_(maintenance.FINISHED),
|
||||
@@ -321,7 +325,7 @@ async def _failed_jobs(db, now) -> list:
|
||||
latest = {}
|
||||
for j, name in rows:
|
||||
latest.setdefault(j.host_id, (j, name))
|
||||
return [(j, name) for j, name in latest.values() if j.state != "done"]
|
||||
return [(j, name) for j, name in latest.values() if maintenance.still_failed(j)]
|
||||
|
||||
|
||||
async def mcp_overview(db) -> list[dict]:
|
||||
|
||||
Reference in New Issue
Block a user