部署/滚动重启非 gateway 实例时,先把该实例在内网 CLB(app-internal/pay-internal) 上的后端权重摘到 0 并等待排空,实例恢复健康后再还原权重。 此前只做 Nacos 摘流,但业务服务之间(如 live -> other)经内网 CLB 固定 VIP 互调,Nacos 摘流对这部分流量无效:容器被杀后 CLB 健康检查(5s x 3 次)摘除 死后端之前,调用方请求持续打到死后端,把调用方线程池拖垮,表现为 “部署 other 时 live 挂掉一个”(health 探测超时 60-70 秒)。 通过 INTERNAL_CLB_IDS 配置启用;未配置时保持历史行为。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
128 lines
4.6 KiB
Python
128 lines
4.6 KiB
Python
from __future__ import annotations
|
||
|
||
import unittest
|
||
from unittest.mock import patch
|
||
|
||
from monitors.yumi.tencent_clb import (
|
||
collect_internal_bindings,
|
||
collect_internal_health_states,
|
||
prepare_internal_clb_snapshot,
|
||
)
|
||
|
||
|
||
APP1_OTHER = {
|
||
"host": "app-1",
|
||
"service": "other",
|
||
"ip": "10.2.11.3",
|
||
"instanceId": "ins-n566mmeg",
|
||
"port": 2400,
|
||
}
|
||
|
||
L4_LISTENERS = [
|
||
{
|
||
"ListenerId": "lbl-other",
|
||
"Protocol": "TCP",
|
||
"Port": 2400,
|
||
"Targets": [
|
||
{"InstanceId": "ins-n566mmeg", "Port": 2400, "Weight": 10, "PrivateIpAddresses": ["10.2.11.3"]},
|
||
{"InstanceId": "ins-kauk17la", "Port": 2400, "Weight": 10, "PrivateIpAddresses": ["10.2.12.6"]},
|
||
],
|
||
},
|
||
{
|
||
"ListenerId": "lbl-live",
|
||
"Protocol": "TCP",
|
||
"Port": 2500,
|
||
"Targets": [
|
||
{"InstanceId": "ins-n566mmeg", "Port": 2500, "Weight": 10, "PrivateIpAddresses": ["10.2.11.3"]},
|
||
],
|
||
},
|
||
]
|
||
|
||
|
||
class InternalClbBindingTests(unittest.TestCase):
|
||
def test_collect_internal_bindings_matches_only_service_port_and_host(self) -> None:
|
||
# 同一台宿主机同时挂 other-2400 / live-2500,只允许命中当前服务自己的监听器。
|
||
bindings = collect_internal_bindings(L4_LISTENERS, APP1_OTHER)
|
||
|
||
self.assertEqual(len(bindings), 1)
|
||
self.assertEqual(bindings[0]["listenerId"], "lbl-other")
|
||
self.assertEqual(bindings[0]["port"], 2400)
|
||
self.assertEqual(bindings[0]["weight"], 10)
|
||
self.assertEqual(bindings[0]["instanceId"], "ins-n566mmeg")
|
||
|
||
@patch("monitors.yumi.tencent_clb.INTERNAL_CLB_IDS", ())
|
||
def test_prepare_snapshot_disabled_without_config(self) -> None:
|
||
# 未配置 INTERNAL_CLB_IDS 时必须整体跳过,保持历史行为。
|
||
self.assertIsNone(prepare_internal_clb_snapshot(APP1_OTHER))
|
||
|
||
@patch("monitors.yumi.tencent_clb.call_clb_api")
|
||
@patch("monitors.yumi.tencent_clb.INTERNAL_CLB_IDS", ("lb-app", "lb-pay"))
|
||
def test_prepare_snapshot_collects_matching_clbs_only(self, call_clb_api_mock) -> None:
|
||
def fake_describe(method: str, payload: dict) -> dict:
|
||
self.assertEqual(method, "DescribeTargets")
|
||
if payload["LoadBalancerId"] == "lb-app":
|
||
return {"Listeners": L4_LISTENERS}
|
||
return {"Listeners": []}
|
||
|
||
call_clb_api_mock.side_effect = fake_describe
|
||
|
||
snapshot = prepare_internal_clb_snapshot(APP1_OTHER)
|
||
|
||
self.assertIsNotNone(snapshot)
|
||
self.assertEqual(len(snapshot["groups"]), 1)
|
||
group = snapshot["groups"][0]
|
||
self.assertEqual(group["loadBalancerId"], "lb-app")
|
||
self.assertEqual(group["listenerIds"], ["lbl-other"])
|
||
self.assertEqual(len(group["bindings"]), 1)
|
||
self.assertEqual(int(group["bindings"][0]["weight"]), 10)
|
||
|
||
@patch("monitors.yumi.tencent_clb.call_clb_api")
|
||
@patch("monitors.yumi.tencent_clb.INTERNAL_CLB_IDS", ("lb-app",))
|
||
def test_prepare_snapshot_none_when_service_not_behind_clb(self, call_clb_api_mock) -> None:
|
||
call_clb_api_mock.return_value = {"Listeners": []}
|
||
|
||
self.assertIsNone(prepare_internal_clb_snapshot(APP1_OTHER))
|
||
|
||
|
||
class InternalClbHealthTests(unittest.TestCase):
|
||
def test_collect_internal_health_states_flattens_l4_rules(self) -> None:
|
||
load_balancers = [
|
||
{
|
||
"LoadBalancerId": "lb-app",
|
||
"Listeners": [
|
||
{
|
||
"ListenerId": "lbl-other",
|
||
"Rules": [
|
||
{
|
||
"Targets": [
|
||
{
|
||
"TargetId": "ins-n566mmeg",
|
||
"Port": 2400,
|
||
"HealthStatus": True,
|
||
"HealthStatusDetail": "Alive",
|
||
},
|
||
{
|
||
"TargetId": "ins-kauk17la",
|
||
"Port": 2400,
|
||
"HealthStatus": False,
|
||
"HealthStatusDetail": "Dead",
|
||
},
|
||
]
|
||
}
|
||
],
|
||
}
|
||
],
|
||
}
|
||
]
|
||
snapshot = {"service": APP1_OTHER}
|
||
|
||
states = collect_internal_health_states(load_balancers, snapshot)
|
||
|
||
self.assertEqual(len(states), 1)
|
||
self.assertEqual(states[0]["targetId"], "ins-n566mmeg")
|
||
self.assertTrue(states[0]["healthStatus"])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|