resident P0+P1: DNS-level SSRF pin, batch budget, daily cap/cooldown/retention, PG schema/migration, dual-connection deadlock isolation, retarget baseline reset, proxy and structured failures, metrics
- DNS 级 SSRF pin:解析后 pin IP 建连,防 DNS 重绑定 - 批量总预算与每日上限/抖动冷却/保留期 - PG schema 与迁移清单 - 双连接死锁隔离 - 改目标重置基线 - 显式代理配置与结构化失败原因 - /metrics resident 口径
This commit is contained in:
1 parent
60997b138f
commit
b391fe9320
9 files changed
+1906
-74
No files matched your search
@@ -8,10 +8,15 @@
|
||||
- 间隔仍是生产下限 60 秒;到期不是改间隔,而是给驱动传一个
|
||||
更晚的 now(模拟时间流逝),与上一轮单测同款时间注入。
|
||||
- 目标 URL 仍走生产校验:主机名 website-loop.test 不是 IP 字
|
||||
面、不是 localhost,所以校验通过;测试进程内只把这个主机
|
||||
名的 DNS 解析指向本地测试 HTTP 服务(相当于 /etc/hosts 的
|
||||
一条测试条目),抓取本身走真实 default_fetch(urllib、超时、
|
||||
1MB 截断),不注入假 fetcher、不绕过生产代码。
|
||||
面、不是 localhost,所以校验通过;抓取本身走真实
|
||||
default_fetch(DNS 门、pin 连接、超时、1MB 截断),不注入
|
||||
假 fetcher。注意:生产默认 DNS 门会把解析到 127.0.0.1 的
|
||||
目标拦住,所以本文件用两段测试接缝:worker 的
|
||||
resident_resolver 把该主机名解析成公网测试 IP(门正常放
|
||||
行),测试进程内再在 socket 层把该 IP:内容端口重定向回
|
||||
本地服务(传输层终点是测试替身,门与 pin 全程真实跑);
|
||||
DNS 门本身由 test_resident_p0_fixes.py 的默认解析器用例
|
||||
覆盖,不在本文件重复证明。
|
||||
- 内容服务是线程内真实 http.server,可变内容、也可停服;
|
||||
控制面是真实 ThreadingHTTPServer + 真实 SQLite 临时库;
|
||||
变化建出的任务、轮询、审计都直接读库/接口核对。
|
||||
@@ -112,12 +117,39 @@ class WebsiteLoopTestCase(unittest.TestCase):
|
||||
env={"XCM_API_KEY": "test-placeholder"},
|
||||
runner=lambda cmd: SimpleNamespace(
|
||||
returncode=0, stdout="{}", stderr=""))
|
||||
# 两段测试接缝(见文件头说明):resolver 返回公网测试
|
||||
# IP,socket 层把该 IP:内容端口重定向回本地服务。
|
||||
try:
|
||||
from cloud.resident import _default_resolver
|
||||
except ImportError:
|
||||
_default_resolver = None
|
||||
|
||||
def _loop_resolver(host, port, _default=_default_resolver):
|
||||
if host == HOST:
|
||||
return ["93.184.216.34"]
|
||||
if _default is not None:
|
||||
return _default(host, port)
|
||||
return [socket.getaddrinfo(host, port)[0][4][0]]
|
||||
|
||||
self.worker.resident_resolver = _loop_resolver
|
||||
self._orig_create_connection = socket.create_connection
|
||||
|
||||
def _patched_create_connection(address, *args, **kwargs):
|
||||
if address[0] == "93.184.216.34" \
|
||||
and address[1] == self.content_port:
|
||||
address = ("127.0.0.1", self.content_port)
|
||||
return self._orig_create_connection(address, *args,
|
||||
**kwargs)
|
||||
|
||||
socket.create_connection = _patched_create_connection
|
||||
|
||||
def tearDown(self):
|
||||
# setUp 中途失败时属性可能未建:getattr 防御,避免
|
||||
# tearDown 的 AttributeError 掩盖原始失败。
|
||||
if getattr(self, "_orig_getaddrinfo", None) is not None:
|
||||
socket.getaddrinfo = self._orig_getaddrinfo
|
||||
if getattr(self, "_orig_create_connection", None) is not None:
|
||||
socket.create_connection = self._orig_create_connection
|
||||
if getattr(self, "_old_no_proxy", None) is None:
|
||||
if "_old_no_proxy" in self.__dict__:
|
||||
os.environ.pop("no_proxy", None)
|
||||
@@ -275,12 +307,15 @@ class WebsiteLoopTestCase(unittest.TestCase):
|
||||
_content_hash("version two"))
|
||||
|
||||
# 5. 恢复:用旧游标比对,报出暂停期间的变化并建任务,
|
||||
# 不是从头建基线。
|
||||
# 不是从头建基线。轮询时刻取 t0+540:距上次建任务
|
||||
# (t0+240)已过 300 秒抖动冷却,否则新冷却语义会
|
||||
# 正确地记 changed_cooldown 而不是 changed(冷却期
|
||||
# 内的变化不丢,冷却过后复查仍可报,见 P0 批单测)。
|
||||
status, body = self._req(
|
||||
"POST", self._url("/%s/resume" % wid), {}, self.owner_key)
|
||||
self.assertEqual(status, 200, body)
|
||||
self.assertEqual(body["status"], "running")
|
||||
polls = self.worker.process_due_resident_watches(now=t0 + 400)
|
||||
polls = self.worker.process_due_resident_watches(now=t0 + 540)
|
||||
self.assertEqual(len(polls), 1, polls)
|
||||
self.assertEqual(polls[0]["status"], "changed")
|
||||
tasks = self._tasks()
|
||||
|
||||
Reference in new issue
Block a user