自动补测、推送审计/退避重试、监控指标与监控页面

This commit is contained in:
DeepFal committed 2026-10-19 13:29:30 +08:00
1 parent 7445dcc51a
commit 24afb9e1dc
9 files changed
+831 -23

No files matched your search

+49
View File
@@ -8,6 +8,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(HERE))
from graylog_ops import watch # noqa: E402
from graylog_ops.behavior import notify_fused # noqa: E402
NOW = "2026-10-17T16:00:00Z"
@@ -99,6 +100,54 @@ class FusedTests(unittest.TestCase):
store.close()
class PushRetryTests(unittest.TestCase):
def test_failure_backoff_and_audit(self):
with tempfile.TemporaryDirectory() as directory:
store = watch.Store(os.path.join(directory, "ips.db"))
try:
store.replace_behavior_profiles([_behavior("198.51.100.8")])
store.sync_fused()
with store.lock, store.conn:
store.conn.execute(
"UPDATE ip_fused SET last_pushed_at=NULL WHERE ip=?",
("198.51.100.8",))
class Failing:
kind = "dingtalk"
def link_for(self, ip): return ""
def post(self, text, title=None): raise RuntimeError("boom")
notify_fused(store, Failing())
notif = store.notification_stats(24)
self.assertEqual(notif["failed"], 1)
self.assertEqual(notif["sent"], 0)
row = store.fused_row("198.51.100.8")
self.assertEqual(row["push_fail_count"], 1)
self.assertTrue(row["push_next_at"])
self.assertEqual(store.due_pushes(24, 24), []) # backoff active
self.assertEqual(store.get_meta("push_consecutive_failures"), "1")
with store.lock, store.conn:
store.conn.execute(
"UPDATE ip_fused SET push_next_at=NULL WHERE ip=?",
("198.51.100.8",))
class Working:
kind = "dingtalk"
def link_for(self, ip): return ""
def post(self, text, title=None): return None
notify_fused(store, Working())
row = store.fused_row("198.51.100.8")
self.assertEqual(row["push_fail_count"], 0)
self.assertIsNone(row["push_next_at"])
self.assertEqual(store.get_meta("push_consecutive_failures"), "0")
notif = store.notification_stats(24)
self.assertEqual(notif["sent"], 1)
finally:
store.close()
class HistoricalBehaviourTests(unittest.TestCase):
def test_historical_profile_survives_live_refresh(self):
with tempfile.TemporaryDirectory() as directory:
+47
View File
@@ -341,6 +341,53 @@ class PathPrefixTests(unittest.TestCase):
store.close()
class MetricsTests(unittest.TestCase):
def test_metrics_and_monitor(self):
with tempfile.TemporaryDirectory() as directory:
store = watch.Store(os.path.join(directory, "ips.db"))
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
store.set_meta("detect_last_success", now)
store.set_meta("detect_last_error", "")
store.incr_meta("detect_rounds_total", 3)
store.incr_meta("detect_errors_total", 1)
store.set_meta("behavior_last_success", now)
store.set_meta("behavior_last_duration", 12.5)
store.set_meta("behavior_last_count", 7)
store.incr_meta("behavior_runs_total", 2)
store.incr_meta("push_sent_total", 5)
store.incr_meta("push_failed_total", 2)
store.upsert({"ip": "203.0.113.9", "tier": watch.TIER_MALICIOUS,
"score": 91.0, "total": 120, "window": 3600,
"rules": {"probe_java": 2},
"reasons": ["探测"]}, "2026-10-17T16:00:00Z")
store.sync_fused()
server = ThreadingHTTPServer(("127.0.0.1", 0),
watch.make_handler(store, 3600, 60))
threading.Thread(target=server.serve_forever, daemon=True).start()
base = "http://127.0.0.1:%d" % server.server_address[1]
try:
with urllib.request.urlopen(base + "/metrics", timeout=5) as response:
text = response.read().decode("utf-8")
self.assertIn("ipsec_detect_rounds_total 3", text)
self.assertIn("ipsec_detect_errors_total 1", text)
self.assertIn("ipsec_behavior_last_duration_seconds 12.5", text)
self.assertIn("ipsec_fused_total 1", text)
self.assertIn("ipsec_push_sent_total 5", text)
self.assertIn("ipsec_graylog_up 1", text)
with urllib.request.urlopen(base + "/api/monitor", timeout=5) as response:
payload = json.loads(response.read().decode("utf-8"))
self.assertEqual(payload["graylog"], "ok")
self.assertEqual(payload["detect"]["rounds"], 3)
self.assertEqual(payload["behavior"]["last_duration"], 12.5)
self.assertEqual(payload["push"]["sent_total"], 5)
self.assertEqual(payload["list"]["fused_total"], 1)
finally:
server.shutdown()
server.server_close()
store.close()
class PaginationTests(unittest.TestCase):
def test_pagination_and_search(self):
with tempfile.TemporaryDirectory() as directory: