modify: inline 插件配置按模式对齐(网关侧兜底,兼容未升级的 worker 包)
现象:cache 模式下应用重启后插件仍按 toml 跑,而前端(读缓存)显示的是缓存值。 根因:跑起来的产物里 chongming-worker 是索引里的发布版(uv run build / uv sync 会把 uv pip install -e utils/worker 的可编辑安装替换回锁文件版本),旧版 Plugin.create_workers 在 on_init 时只拿到 config/workers/<type>/config.toml 展平出来的配置。 改动(两层对齐,规则与 SDK 一致且幂等): - WorkerManager.align_inline_config:创建 inline 副本之前按 [cache].launch_worker_count 对齐 [workers.config]——cache 模式取缓存值(缺失才用 toml 引导并写回),toml 模式以 文件为准并把该段覆盖写入缓存;未配置缓存后端时原样返回。网关侧这层让网关与 worker 包版本解耦:worker 包仍是旧版时 cache 模式同样生效。 - _discover_plugins 透出 [workers.config] 展平前的原始段;load_workers 在注册 starter/ 创建副本之前完成对齐(插件实例 on_init 用的就是这份配置)。 - test/test_inline_config_align.py:两模式 + 无缓存/无段的边界用例,并校验缓存键与 SDK 一致。 - README:补打包陷阱(可编辑安装会被 uv sync 替换回发布版)与对齐时机说明。
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
inline 插件 [workers.config] 对齐(网关侧兜底)单测 —— WorkerManager.align_inline_config。
|
||||
|
||||
网关侧在创建 inline 副本之前,按「配置优先来源模式」(根 config.toml 的
|
||||
[cache].launch_worker_count)对齐插件配置:cache 模式取共享缓存里的段,
|
||||
toml 模式以 config/workers/<type>/config.toml 为准并覆盖缓存。目的是让网关与
|
||||
worker 包版本解耦——worker 包仍是旧版时,旧版 on_init 只拿到 toml 展平配置,
|
||||
会出现“前端显示缓存值、实际跑 toml”。
|
||||
|
||||
用 dict 作缓存桩,验证两种模式下插件拿到的配置与缓存/文件两侧的取值一致。
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(REPO, "src"))
|
||||
os.chdir(REPO) # 网关模块导入期读根 config.toml / 日志配置
|
||||
|
||||
from chongming.app.core.worker_manager import _CONFIGS_KEY, WorkerManager # noqa: E402
|
||||
|
||||
PASS = 0
|
||||
FAIL = 0
|
||||
WT = "aibox_sophon_adapter"
|
||||
|
||||
# _discover_plugins 展平出来的形态(toml 值)
|
||||
FLAT = {"type": WT, "count": 1, "data_dir": "./data", "mediamtx_addr": "rtsp://toml:8554"}
|
||||
# 该插件 config.toml 的 [workers.config] 原始段
|
||||
SECTION = {"data_dir": "./data", "mediamtx_addr": "rtsp://toml:8554", "heartbeat": {"timeout": 15}}
|
||||
# 前端下发后缓存里的段(改过 mediamtx_addr 与 heartbeat)
|
||||
CACHED = {"data_dir": "./data", "mediamtx_addr": "rtsp://cache:9999", "heartbeat": {"timeout": 30}}
|
||||
|
||||
|
||||
def report(name, cond, detail=""):
|
||||
global PASS, FAIL
|
||||
if cond:
|
||||
PASS += 1
|
||||
print(f"✅ {name}" + (f" — {detail}" if detail else ""))
|
||||
else:
|
||||
FAIL += 1
|
||||
print(f"❌ {name}" + (f" — {detail}" if detail else ""))
|
||||
|
||||
|
||||
def manager(cache, mode):
|
||||
m = WorkerManager.__new__(WorkerManager) # 免会话构造:仅用到 cache + config_mode
|
||||
m.cache = cache
|
||||
m._launch_worker_count = mode
|
||||
return m
|
||||
|
||||
|
||||
def run() -> int:
|
||||
key = _CONFIGS_KEY.format(type=WT)
|
||||
|
||||
# ── 1. cache 模式:缓存有段 → 插件拿到缓存值(本次修复目标)──────────
|
||||
cache = {key: dict(CACHED)}
|
||||
got = manager(cache, "cache").align_inline_config(WT, dict(FLAT), dict(SECTION))
|
||||
report(
|
||||
"cache 模式:插件配置取缓存值(覆盖 toml 同名键)",
|
||||
got.get("mediamtx_addr") == "rtsp://cache:9999"
|
||||
and got.get("heartbeat") == {"timeout": 30},
|
||||
f"mediamtx_addr={got.get('mediamtx_addr')}",
|
||||
)
|
||||
report("cache 模式:不覆盖已存在的缓存段", cache[key] == CACHED, f"cache={cache[key]}")
|
||||
|
||||
# ── 2. cache 模式:缓存缺失 → 用 toml 段引导并写回 ──────────────────
|
||||
cache2 = {}
|
||||
got2 = manager(cache2, "cache").align_inline_config(WT, dict(FLAT), dict(SECTION))
|
||||
report(
|
||||
"cache 模式:缓存缺失用 config.toml 引导并写回",
|
||||
cache2.get(key) == SECTION and got2.get("mediamtx_addr") == "rtsp://toml:8554",
|
||||
f"cache={cache2.get(key)}",
|
||||
)
|
||||
|
||||
# ── 3. toml 模式:以文件为准并覆盖缓存(前端随之显示文件值)─────────
|
||||
cache3 = {key: dict(CACHED)}
|
||||
got3 = manager(cache3, "toml").align_inline_config(WT, dict(FLAT), dict(SECTION))
|
||||
report(
|
||||
"toml 模式:插件配置为 toml 值",
|
||||
got3.get("mediamtx_addr") == "rtsp://toml:8554",
|
||||
f"mediamtx_addr={got3.get('mediamtx_addr')}",
|
||||
)
|
||||
report(
|
||||
"toml 模式:启动时用 toml 覆盖缓存(前端与运行值一致)",
|
||||
cache3.get(key) == SECTION,
|
||||
f"cache={cache3.get(key)}",
|
||||
)
|
||||
|
||||
# ── 4. 边界:无缓存后端 / 无段(旧 config/workers/config.toml 兼容路径)──
|
||||
cache4 = {key: dict(CACHED)}
|
||||
m4 = manager(cache4, "cache")
|
||||
m4.cache = None
|
||||
report(
|
||||
"无缓存后端:原样返回(经典 toml 行为)",
|
||||
m4.align_inline_config(WT, dict(FLAT), dict(SECTION)) == FLAT
|
||||
and cache4[key] == CACHED,
|
||||
)
|
||||
cache5 = {}
|
||||
got5 = manager(cache5, "toml").align_inline_config(WT, dict(FLAT), None)
|
||||
report("无 [workers.config] 段(旧写法):不写缓存、原样返回", got5 == FLAT and not cache5)
|
||||
|
||||
# ── 5. 与 SDK 侧一致:缓存键必须相同;SDK 为本地版本时才比对解析结果 ──
|
||||
# 注意:venv 里的 chongming-worker 可能是索引里的发布版(uv sync 会把可编辑安装
|
||||
# 换回锁文件版本),新版才有 resolve_startup_config / 正确的 toml 分支语义。
|
||||
sys.path.insert(0, os.path.join(REPO, "utils", "worker", "src"))
|
||||
import chongming_worker.runtime as r # noqa: E402
|
||||
|
||||
report(
|
||||
"缓存键与 SDK 一致(两端必须读写同一键)",
|
||||
r._configs_cache_key(WT) == _CONFIGS_KEY.format(type=WT),
|
||||
f"sdk={r._configs_cache_key(WT)}",
|
||||
)
|
||||
local_src = os.path.join(REPO, "utils", "worker", "src") in os.path.abspath(r.__file__)
|
||||
if not local_src:
|
||||
print(f"⚠️ 跳过解析结果比对:当前 SDK 为 {r.__file__}(非本地源码,缺本次改动)")
|
||||
else:
|
||||
c = {r._configs_cache_key(WT): dict(CACHED)}
|
||||
sdk = r.resolve_runtime_config(WT, dict(FLAT), c, "cache")
|
||||
report(
|
||||
"cache 模式:网关侧兜底与 SDK 解析结果一致",
|
||||
sdk.get("mediamtx_addr") == CACHED["mediamtx_addr"],
|
||||
f"sdk={sdk.get('mediamtx_addr')}",
|
||||
)
|
||||
|
||||
# ── 6. 端到端:网关对齐 + 插件初始化(部署组合:网关新、worker 包为发布版)──
|
||||
# 该断言即线上现象的复现与验证:插件 on_init 拿到的必须是网关对齐后的缓存值,
|
||||
# 与 venv 里 chongming-worker 是否为含本次改动的版本无关。
|
||||
import asyncio
|
||||
|
||||
from chongming_worker.plugin import Plugin # noqa: E402
|
||||
|
||||
seen = {}
|
||||
|
||||
class DemoPlugin(Plugin):
|
||||
type = WT
|
||||
auto_bind = False
|
||||
|
||||
async def _bootstrap(self): # 隔离数据库/对象存储引导,只看配置来源
|
||||
return None
|
||||
|
||||
async def on_init(self):
|
||||
seen["on_init"] = self.config.get("mediamtx_addr")
|
||||
|
||||
def _create_workers_impl(self, session, config, count=1, worker_manager=None):
|
||||
seen["impl"] = (config or {}).get("mediamtx_addr")
|
||||
return []
|
||||
|
||||
aligned = manager({key: dict(CACHED)}, "cache").align_inline_config(
|
||||
WT, dict(FLAT), dict(SECTION)
|
||||
)
|
||||
asyncio.run(DemoPlugin().create_workers(None, aligned, 1))
|
||||
report(
|
||||
"端到端:网关对齐后插件 on_init 读到缓存值(不依赖 worker 包版本)",
|
||||
seen.get("on_init") == CACHED["mediamtx_addr"] == seen.get("impl"),
|
||||
f"on_init={seen.get('on_init')}, impl={seen.get('impl')}, "
|
||||
f"sdk={os.path.abspath(__import__('chongming_worker').__file__)}",
|
||||
)
|
||||
|
||||
print("\n" + "=" * 56)
|
||||
print(f"测试结果: ✅ {PASS} 通过 ❌ {FAIL} 失败")
|
||||
return 0 if FAIL == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run())
|
||||
Reference in New Issue
Block a user