Files
FabledSteward/tests/plugins/host_agent/test_downtime.py
T
bvandeusenandClaude Opus 5.5 c7ff5bc556
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m24s
CI / unit (push) Failing after 1m29s
CI / publish (push) Skipped
feat(host_agent): a reboot reads as a reboot, and lost contact as its span (#5756)
#5756 "The agent says when its host rebooted and when it lost contact, so
downtime is recorded as what it was". Agent 1.14.0 posts events with the
next post that gets through: started (with the host's boot time), stopping
(on SIGTERM, with whether systemd says the host is going down) and
lost_contact (from its first unanswered post to the one carrying it).

Steward keeps them as the host's downtime (host_downtime): reboot,
agent_stop, unclean (a boot after the agent was last heard that nobody
announced) or lost_contact. Retried posts apply once.

An announced stop explains the silence for reboot_grace_seconds (Settings
-> Plugins -> host_agent, 10 minutes): seconds_since_seen reads 0 until
then, and the host's other rules go quiet with it (agents_away beside
#5721's registry). Past the grace, agent offline fires.

The host's Agent card lists the last 7 days of downtime. Rows are pruned
at data.retention_days.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:08:16 -04:00

34 lines
1.4 KiB
Python

"""How a host's downtime reads on its page (#5756)."""
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
from plugins.host_agent.downtime import describe, restart_grace_seconds
NOW = datetime(2026, 10, 14, 12, 0, tzinfo=timezone.utc)
def _row(kind, minutes_ago, lasted=None):
start = NOW - timedelta(minutes=minutes_ago)
return SimpleNamespace(kind=kind, started_at=start,
ended_at=start + timedelta(minutes=lasted) if lasted is not None else None)
def test_each_kind_reads_as_what_it_was():
assert describe(_row("reboot", 30, 2), NOW)["label"] == "Rebooted"
assert describe(_row("unclean", 30, 9), NOW)["label"] == "Restarted without shutting down"
assert describe(_row("lost_contact", 30, 20), NOW)["label"] == "Lost contact"
assert describe(_row("agent_stop", 30, 1), NOW)["label"] == "Agent restarted"
assert describe(_row("agent_stop", 3), NOW)["label"] == "Agent stopped"
def test_one_still_going_counts_to_now():
got = describe(_row("reboot", 4), NOW)
assert got["ongoing"] and got["seconds"] == 240.0
done = describe(_row("reboot", 30, 2), NOW)
assert not done["ongoing"] and done["seconds"] == 120.0
def test_the_grace_comes_from_the_plugin_settings():
assert restart_grace_seconds({}) == 600
assert restart_grace_seconds({"PLUGINS": {"host_agent": {"reboot_grace_seconds": 120}}}) == 120