fix: 12h cooldown before reopening manually closed host_silence (0.9.9)
Track resolved_by on problems; suppress scan/ingest recreate after manual resolve; reopen same problem after cooldown; auto-close via heartbeat unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -95,6 +95,85 @@ def test_scan_skips_host_without_heartbeat(db_session, scan_settings):
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_scan_suppresses_after_manual_resolve_within_cooldown(db_session, scan_settings, monkeypatch):
|
||||
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
|
||||
get_settings.cache_clear()
|
||||
|
||||
hb = _ingest(
|
||||
db_session,
|
||||
type="agent.heartbeat",
|
||||
category="agent",
|
||||
severity="info",
|
||||
title="hb",
|
||||
summary="heartbeat",
|
||||
)
|
||||
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
|
||||
db_session.flush()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
first = run_host_silence_scan(db_session, now=now)
|
||||
assert first[0].created is True
|
||||
problem = first[0].problem
|
||||
problem.status = "resolved"
|
||||
problem.resolved_by = "manual"
|
||||
problem.updated_at = now
|
||||
db_session.flush()
|
||||
|
||||
second = run_host_silence_scan(db_session, now=now + timedelta(minutes=10))
|
||||
assert second == []
|
||||
|
||||
open_count = db_session.scalar(
|
||||
select(Problem).where(
|
||||
Problem.host_id == hb.host_id,
|
||||
Problem.rule_id == RULE_HOST_SILENCE,
|
||||
Problem.status == "open",
|
||||
)
|
||||
)
|
||||
assert open_count is None
|
||||
|
||||
resolved_count = len(
|
||||
db_session.scalars(
|
||||
select(Problem).where(
|
||||
Problem.host_id == hb.host_id,
|
||||
Problem.rule_id == RULE_HOST_SILENCE,
|
||||
Problem.status == "resolved",
|
||||
)
|
||||
).all()
|
||||
)
|
||||
assert resolved_count == 1
|
||||
|
||||
|
||||
def test_scan_reopens_after_manual_cooldown_expires(db_session, scan_settings, monkeypatch):
|
||||
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
|
||||
get_settings.cache_clear()
|
||||
|
||||
hb = _ingest(
|
||||
db_session,
|
||||
type="agent.heartbeat",
|
||||
category="agent",
|
||||
severity="info",
|
||||
title="hb",
|
||||
summary="heartbeat",
|
||||
)
|
||||
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
|
||||
db_session.flush()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
first = run_host_silence_scan(db_session, now=now)
|
||||
problem = first[0].problem
|
||||
problem.status = "resolved"
|
||||
problem.resolved_by = "manual"
|
||||
problem.updated_at = now - timedelta(hours=13)
|
||||
db_session.flush()
|
||||
|
||||
later = run_host_silence_scan(db_session, now=now)
|
||||
assert len(later) == 1
|
||||
assert later[0].created is True
|
||||
assert later[0].problem.id == problem.id
|
||||
assert later[0].problem.status == "open"
|
||||
assert later[0].problem.resolved_by is None
|
||||
|
||||
|
||||
def test_scan_notifies_only_on_create(db_session, scan_settings):
|
||||
hb = _ingest(
|
||||
db_session,
|
||||
|
||||
@@ -182,3 +182,99 @@ def test_heartbeat_resolves_host_silence(db_session, rule_settings):
|
||||
maybe_create_problem(db_session, fresh_hb)
|
||||
db_session.refresh(open_silence)
|
||||
assert open_silence.status == "resolved"
|
||||
assert open_silence.resolved_by == "auto"
|
||||
|
||||
|
||||
def test_host_silence_suppressed_after_manual_resolve_on_ingest(db_session, rule_settings, monkeypatch):
|
||||
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
|
||||
get_settings.cache_clear()
|
||||
|
||||
hb = _ingest(
|
||||
db_session,
|
||||
type="agent.heartbeat",
|
||||
category="agent",
|
||||
severity="info",
|
||||
title="hb",
|
||||
summary="heartbeat",
|
||||
)
|
||||
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
|
||||
db_session.flush()
|
||||
|
||||
trigger = _ingest(
|
||||
db_session,
|
||||
type="ssh.login.success",
|
||||
severity="info",
|
||||
title="ok",
|
||||
summary="login",
|
||||
)
|
||||
problem, created = maybe_create_problem(db_session, trigger)
|
||||
assert created is True
|
||||
problem.status = "resolved"
|
||||
problem.resolved_by = "manual"
|
||||
problem.updated_at = datetime.now(timezone.utc)
|
||||
db_session.flush()
|
||||
|
||||
trigger2 = _ingest(
|
||||
db_session,
|
||||
type="ssh.login.success",
|
||||
severity="info",
|
||||
title="ok2",
|
||||
summary="login2",
|
||||
)
|
||||
problem2, created2 = maybe_create_problem(db_session, trigger2)
|
||||
assert problem2 is None
|
||||
assert created2 is False
|
||||
|
||||
open_silence = db_session.scalar(
|
||||
select(Problem).where(
|
||||
Problem.rule_id == RULE_HOST_SILENCE,
|
||||
Problem.status == "open",
|
||||
)
|
||||
)
|
||||
assert open_silence is None
|
||||
|
||||
|
||||
def test_host_silence_reopens_after_auto_resolve_when_still_stale(db_session, rule_settings, monkeypatch):
|
||||
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
|
||||
get_settings.cache_clear()
|
||||
|
||||
hb = _ingest(
|
||||
db_session,
|
||||
type="agent.heartbeat",
|
||||
category="agent",
|
||||
severity="info",
|
||||
title="hb",
|
||||
summary="heartbeat",
|
||||
)
|
||||
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
|
||||
db_session.flush()
|
||||
|
||||
trigger = _ingest(
|
||||
db_session,
|
||||
type="ssh.login.success",
|
||||
severity="info",
|
||||
title="ok",
|
||||
summary="login",
|
||||
)
|
||||
problem, created = maybe_create_problem(db_session, trigger)
|
||||
assert created is True
|
||||
|
||||
from app.services.problem_rules import resolve_host_silence_problems
|
||||
|
||||
resolve_host_silence_problems(db_session, hb.host_id)
|
||||
db_session.refresh(problem)
|
||||
assert problem.status == "resolved"
|
||||
assert problem.resolved_by == "auto"
|
||||
|
||||
trigger2 = _ingest(
|
||||
db_session,
|
||||
type="ssh.login.success",
|
||||
severity="info",
|
||||
title="ok2",
|
||||
summary="login2",
|
||||
)
|
||||
problem2, created2 = maybe_create_problem(db_session, trigger2)
|
||||
assert created2 is True
|
||||
assert problem2 is not None
|
||||
assert problem2.status == "open"
|
||||
assert problem2.id != problem.id
|
||||
|
||||
Reference in New Issue
Block a user