578cc33cc0
Wires the agent's enriched + swarm payloads through the docker.persist_host_
samples capability:
* Swarm topology — persist sample["swarm"] into docker_swarm_services /
docker_swarm_nodes (upsert + prune stale, host-scoped so two managers don't
clobber). Migration docker_005 adds services.placement_json for the
task→node placement the agent now reports.
* Lifecycle events — _derive_events (pure, unit-tested) diffs the newest
snapshot against stored per-container state: start / stop / die (non-zero
exit) / oom / health_change → docker_events rows. Skipped on a host's first
snapshot so the baseline doesn't emit a start per existing container.
* Alerts — record restart_count (always) and is_healthy (1.0/0.0, only when a
HEALTHCHECK exists) alongside cpu/mem, under host-scoped resource names;
METRIC_CATALOG[docker] gains restart_count + is_healthy so they're alertable.
host_agent ingest captures the newest sample's swarm object and threads it to
the capability (now persist_host_docker(session, host, snapshots, swarm=None));
invoked when containers OR swarm are present, under the same SAVEPOINT. Unit
tests cover the event-diff matrix; integration tests cover event derivation
across two snapshots and swarm topology round-trip (incl. placement).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
68 lines
2.8 KiB
Python
68 lines
2.8 KiB
Python
"""Unit tests for the docker plugin's lifecycle-event derivation.
|
|
|
|
_derive_events is a pure function (no DB), and ingest.py imports its models
|
|
lazily inside functions, so importing it here doesn't register ORM tables —
|
|
safe for the no-DB unit lane.
|
|
"""
|
|
from plugins.docker.ingest import _derive_events
|
|
|
|
|
|
def _c(name, status, **kw):
|
|
return {"name": name, "status": status, **kw}
|
|
|
|
|
|
def test_start_on_running_after_stopped():
|
|
old = {"web": {"status": "exited", "health": None, "oom_killed": False, "exit_code": 0}}
|
|
events = _derive_events(old, [_c("web", "running", image="nginx")])
|
|
assert events == [("web", "start", "nginx")]
|
|
|
|
|
|
def test_start_for_new_container_when_state_exists():
|
|
# old_state is non-empty (host already seen), a fresh container appears running.
|
|
old = {"other": {"status": "running", "health": None, "oom_killed": False, "exit_code": None}}
|
|
events = _derive_events(old, [
|
|
_c("other", "running"),
|
|
_c("new", "running", image="redis"),
|
|
])
|
|
assert ("new", "start", "redis") in events
|
|
# the unchanged 'other' produces nothing
|
|
assert all(e[0] != "other" for e in events)
|
|
|
|
|
|
def test_stop_on_clean_exit():
|
|
old = {"job": {"status": "running", "health": None, "oom_killed": False, "exit_code": None}}
|
|
events = _derive_events(old, [_c("job", "exited", exit_code=0)])
|
|
assert events == [("job", "stop", None)]
|
|
|
|
|
|
def test_die_on_nonzero_exit():
|
|
old = {"job": {"status": "running", "health": None, "oom_killed": False, "exit_code": None}}
|
|
events = _derive_events(old, [_c("job", "exited", exit_code=137)])
|
|
assert events == [("job", "die", "exit 137")]
|
|
|
|
|
|
def test_oom_on_flip_to_true():
|
|
old = {"hog": {"status": "running", "health": None, "oom_killed": False, "exit_code": None}}
|
|
# OOM-killed → also no longer running with a non-zero exit, so die + oom both fire.
|
|
events = _derive_events(old, [_c("hog", "exited", exit_code=137, oom_killed=True)])
|
|
assert ("hog", "oom", None) in events
|
|
assert ("hog", "die", "exit 137") in events
|
|
|
|
|
|
def test_health_change_emitted():
|
|
old = {"svc": {"status": "running", "health": "healthy", "oom_killed": False, "exit_code": None}}
|
|
events = _derive_events(old, [_c("svc", "running", health="unhealthy")])
|
|
assert events == [("svc", "health_change", "healthy→unhealthy")]
|
|
|
|
|
|
def test_removed_running_container_stops():
|
|
old = {"gone": {"status": "running", "health": None, "oom_killed": False, "exit_code": None}}
|
|
events = _derive_events(old, []) # vanished from the listing entirely
|
|
assert events == [("gone", "stop", "removed")]
|
|
|
|
|
|
def test_no_event_when_unchanged():
|
|
old = {"web": {"status": "running", "health": "healthy", "oom_killed": False, "exit_code": None}}
|
|
events = _derive_events(old, [_c("web", "running", health="healthy")])
|
|
assert events == []
|