fix(alerts): an alert that fired and ended inside maintenance isn't mailed resolved (#6097)
During Docker02's maintenance reboot its Agent offline alert fired muted by the job's window, then resolved just after the window closed: the resolution was mailed for a firing nobody had been told of. A resolution whose firing was held and never released is now recorded with a note, not sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,60 @@ def test_maintenance_mutes_but_the_state_moves_and_notifies_when_it_ends(app):
|
||||
asyncio.run(_go())
|
||||
|
||||
|
||||
def test_an_alert_that_fired_and_ended_unheard_is_not_mailed_resolved(app):
|
||||
"""#5769: an agent offline for its own maintenance reboot fired inside the
|
||||
job's window (muted) and resolved just after the window closed, before
|
||||
the minute's release. Nobody heard it fire, so its end isn't mailed."""
|
||||
from sqlalchemy import update
|
||||
from steward.core import alerts
|
||||
from steward.models.alerts import MaintenanceWindow
|
||||
|
||||
sent = []
|
||||
|
||||
async def fake_dispatch(app_, notif_type, rule, value, event_id, note=None):
|
||||
sent.append((notif_type, value))
|
||||
|
||||
async def _go():
|
||||
tag = uuid.uuid4().hex[:8]
|
||||
rule_id, res, user_id = await _rule(app, tag)
|
||||
now = datetime.now(timezone.utc)
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
window = MaintenanceWindow(
|
||||
name=f"reboot {tag}", start_at=now - timedelta(minutes=1),
|
||||
end_at=now + timedelta(hours=1), scope_source="host_agent",
|
||||
scope_resource=res, created_by=user_id)
|
||||
s.add(window)
|
||||
window_id = window.id
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
await alerts.record_metric(s, "host_agent", res, "cpu_pct", 95.0)
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
await s.execute(update(MaintenanceWindow).where(MaintenanceWindow.id == window_id)
|
||||
.values(end_at=datetime.now(timezone.utc)))
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
await alerts.record_metric(s, "host_agent", res, "cpu_pct", 5.0)
|
||||
await alerts.notify_after_maintenance(app) # nothing left firing to release
|
||||
await _settle()
|
||||
assert sent == []
|
||||
events = await _events(app, rule_id)
|
||||
assert [(frm, to) for frm, to, _, _ in events] == [("inactive", "firing"), ("firing", "resolved")]
|
||||
assert events[1][2] == alerts.HELD
|
||||
|
||||
# A firing that was told (released, or never muted) still gets its end.
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
await alerts.record_metric(s, "host_agent", res, "cpu_pct", 96.0)
|
||||
await alerts.record_metric(s, "host_agent", res, "cpu_pct", 4.0)
|
||||
await _settle()
|
||||
assert sent == [("firing", 96.0), ("resolved", 4.0)]
|
||||
|
||||
with patch.object(alerts, "_dispatch_notification", fake_dispatch):
|
||||
asyncio.run(_go())
|
||||
|
||||
|
||||
def test_an_alert_action_outside_its_source_is_refused(app, tmp_path):
|
||||
from steward.core import alerts
|
||||
from steward.models.ansible import AnsibleRun
|
||||
|
||||
Reference in New Issue
Block a user