自动补测、推送审计/退避重试、监控指标与监控页面
This commit is contained in:
1 parent
7445dcc51a
commit
24afb9e1dc
9 files changed
+831
-23
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user