Files
iAOP/deploy/k8s/healthz/test_probe.py
T

105 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""可用性监控探针测试(issue #61)。
用本地 ThreadingHTTPServer 模拟健康端点:
1. 稳定 200 → 可用率 100% ≥ 99.8%;
2. 间歇 503 → 可用率按失败比例下降,探针统计正确;
3. 阈值判断:间歇失败超 0.2% 时 meets_target=False;
4. 失败明细记录(URL + 原因)。
"""
import http.server
import os
import sys
import threading
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from probe_availability import AvailabilityProbe # noqa: E402
class _HealthHandler(http.server.BaseHTTPRequestHandler):
"""可配置成功/失败模式的 /health 处理器。"""
fail_mode = [] # [True/False...] 按请求序号循环
def do_GET(self):
if self.path == "/health":
if self.fail_mode and self.fail_mode[0]:
self.fail_mode[0] = False # 一次性失败
self.send_response(503)
else:
self.send_response(200)
else:
self.send_response(404)
self.end_headers()
def log_message(self, *args): # 静默日志
pass
def start_server(fail_once=False):
handler = _HealthHandler
handler.fail_mode = [True] if fail_once else []
srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler)
thread = threading.Thread(target=srv.serve_forever, daemon=True)
thread.start()
return srv
class TestProbe(unittest.TestCase):
"""探针统计与阈值判断。"""
def test_stable_endpoint_100pct(self):
srv = start_server()
try:
url = f"http://127.0.0.1:{srv.server_port}"
probe = AvailabilityProbe([url], timeout_seconds=2)
report = probe.run(rounds=10, interval=0)
finally:
srv.shutdown()
self.assertEqual(report.total, 10)
self.assertEqual(report.success, 10)
self.assertEqual(report.availability, 1.0)
self.assertTrue(report.meets_target())
def test_single_failure_below_threshold(self):
srv = start_server(fail_once=True)
try:
url = f"http://127.0.0.1:{srv.server_port}"
probe = AvailabilityProbe([url], timeout_seconds=2)
report = probe.run(rounds=500, interval=0) # 1/500 = 0.2%
finally:
srv.shutdown()
self.assertEqual(len(report.failures), 1)
# 1/500 = 0.002 = 99.8% —— 恰好达标(>= target 判定通过)
self.assertGreaterEqual(report.availability, report.target)
self.assertTrue(report.meets_target())
# urllib 对非 200 抛 HTTPError → 记为不可用
self.assertFalse(report.failures[0].ok)
self.assertIn("503", report.failures[0].detail)
def test_high_failure_rate_fails_threshold(self):
# 构造 50% 失败的端点(fail_mode 每次循环交替:用两个处理器状态不可行,
# 改为直接 mock probe_once)
probe = AvailabilityProbe(["http://x"], timeout_seconds=1)
results = [probe.probe_once.__self__.__class__] # noqa - 占位
from unittest import mock
ok_result = mock.MagicMock()
ok_result.ok = True
bad_result = mock.MagicMock()
bad_result.ok = False
bad_result.url = "http://x"
bad_result.detail = "http-503"
bad_result.latency_ms = 1.0
# 交替 200/503 → 6 次探测 3 成功 → 50% 可用率,远低于 99.8%
with mock.patch.object(probe, "probe_once",
side_effect=[ok_result, bad_result] * 3):
report = probe.run(rounds=6, interval=0)
self.assertEqual(report.availability, 0.5)
self.assertFalse(report.meets_target())
self.assertEqual(len(report.failures), 3)
if __name__ == "__main__":
unittest.main()