diff --git a/apps/api/src/services/incident_service.py b/apps/api/src/services/incident_service.py index d3686f1e..532a1576 100644 --- a/apps/api/src/services/incident_service.py +++ b/apps/api/src/services/incident_service.py @@ -126,7 +126,12 @@ def classify_alert_early(alertname: str, severity: str, _labels: dict) -> tuple[ return "config_drift", "TYPE-4D" if severity in ("info", "none"): return "info", "TYPE-1" - if any(kw in alertname_lower for kw in ("backup", "heartbeat")): + # backup/heartbeat 關鍵字只有 severity=info/none 才是純資訊 + # severity=warning/critical(例如 VeleroBackupFailed, HostBackupFailed)→ 繼續走 prefix 規則 + if severity in ("info", "none") and any(kw in alertname_lower for kw in ("backup", "heartbeat")): + return "backup", "TYPE-1" + # Watchdog/Heartbeat 永遠是 TYPE-1(Alertmanager 心跳) + if "watchdog" in alertname_lower or alertname in ("Heartbeat",): return "backup", "TYPE-1" if alertname.startswith(("Docker", "Host")): return "infrastructure", "TYPE-3" diff --git a/apps/api/tests/test_classify_alert_early.py b/apps/api/tests/test_classify_alert_early.py index 4e13c28b..22393bda 100644 --- a/apps/api/tests/test_classify_alert_early.py +++ b/apps/api/tests/test_classify_alert_early.py @@ -47,20 +47,25 @@ class TestInfoAlerts: assert nt == "TYPE-1" assert ac == "info" - def test_backup_keyword(self): + def test_backup_keyword_info_only(self): + # severity=info → severity 規則先命中,TYPE-1 + ac, nt = classify_alert_early("BackupJobComplete", "info", {}) + assert nt == "TYPE-1" + + def test_backup_keyword_warning_not_type1(self): + # BackupJobFailed severity=warning → 繼續走 prefix 規則,不應是 TYPE-1 ac, nt = classify_alert_early("BackupJobFailed", "warning", {}) - assert nt == "TYPE-1" - assert ac == "backup" + assert nt == "TYPE-3" - def test_heartbeat_keyword(self): - ac, nt = classify_alert_early("WatchdogHeartbeat", "warning", {}) + def test_watchdog_heartbeat(self): + # Watchdog (Alertmanager 心跳) severity=none → severity 規則先命中,TYPE-1 + ac, nt = classify_alert_early("Watchdog", "none", {}) assert nt == "TYPE-1" - assert ac == "backup" - def test_backup_case_insensitive(self): + def test_backup_critical_not_type1(self): + # critical backup 告警應走各自 prefix,不是純資訊 ac, nt = classify_alert_early("BACKUP_MISSING", "critical", {}) - assert nt == "TYPE-1" - assert ac == "backup" + assert nt == "TYPE-3" # --------------------------------------------------------------------------- # @@ -100,11 +105,16 @@ class TestKubernetes: assert nt == "TYPE-3" assert ac == "kubernetes" - def test_velero_backup_keyword_wins(self): - # VeleroBackupFailed 含 "backup" → backup 規則優先於 kubernetes prefix + def test_velero_backup_failed_is_kubernetes(self): + # VeleroBackupFailed severity=critical → backup 規則不命中,走 Velero prefix → kubernetes TYPE-3 ac, nt = classify_alert_early("VeleroBackupFailed", "critical", {}) + assert nt == "TYPE-3" + assert ac == "kubernetes" + + def test_velero_backup_success_info_is_type1(self): + # VeleroBackupSuccess severity=info → TYPE-1 + ac, nt = classify_alert_early("VeleroBackupSuccess", "info", {}) assert nt == "TYPE-1" - assert ac == "backup" # --------------------------------------------------------------------------- #