9.7 KiB
9.7 KiB
MVP v2.5 开发计划(TDD 驱动)
本文是 v2.5 的工程化开发计划,强调“先写测试,再写实现”(TDD),并将每个里程碑拆成可独立验收的小闭环。
输入依据:
- 路线图:
specs/mvp/mvp_roadmap_v2.md - v2.5 设计:
specs/mvp/v2.5/v2.5_design.md - v2.5 API 草案:
specs/mvp/v2.5/v2.5_api.md - v2.5 验收:
specs/mvp/v2.5/v2.5_acceptance.md
v2.5 约束(已确认):
- 不扩展 TaskSpec:沿用 v2.0/v2.1 的 YAML 结构化字段与语义。
- 不支持自定义 reward function / 不支持用户自定义 verl 代码。
- 训练输入(verl 代码、HF cache、datasets)统一使用
/private/common/...。 - 多用户隔离 v2.5 先只隔离 jobs 输出目录:
/private/users/<uid>/jobs/<ray_submission_id>/...。
0. TDD 规范(所有功能都遵循)
0.1 测试分层
- 单元测试(fast)
- 纯 Python 逻辑:DB、鉴权、ID、路径派生、head.json 解析/TTL、watchdog 决策逻辑。
- 目标:不依赖真实 Ray、不依赖 docker、不依赖网络。
- 组件测试(中等)
- FastAPI 路由:使用
fastapi.testclient.TestClient(现有 v2.0 已采用)。 - 目标:验证 auth/权限隔离、API 行为、状态机。
- 端到端(慢/手工或脚本)
- 在
argus@h1上通过 scripts/compose 跑一次“head publish → worker auto-connect → API submit”闭环。 - 目标:验证无状态 worker + watchdog 的真实行为。
0.2 测试约定
- 测试目录:
src/mvp/py/tests/ - 新增功能必须先补齐测试用例,并让其在未实现时失败(红)。
- 实现最小改动让测试变绿(绿)。
- 重构/去重复(重构)。
注:现有测试通过
src/mvp/py/tests/conftest.py注入 ray stub,确保单测不依赖真实 ray 包;v2.5 新增模块也应复用此模式。
1. 里程碑拆分(v2.5 = 4 个可验证闭环)
M1:User 表/Token 表 + 基础鉴权(不影响现有内部 token 兼容)
目标
- 引入 user/token 的持久化与鉴权映射(token → user_id)。
- 兼容现有
Authorization: Bearer <MVP_INTERNAL_TOKEN>的“单租户模式”,避免一次性破坏 v2.0 用法:- v2.5 可以先支持两种 token 模式:
- legacy:环境变量
MVP_INTERNAL_TOKEN(全局单租户); - user token:DB 内签发 token(多用户)。
- legacy:环境变量
- v2.5 可以先支持两种 token 模式:
- admin 能创建用户、签发 token、禁用用户。
TDD 用例(先写测试)
单测:
test_user_db_create_disable()- 创建用户 ACTIVE;禁用后状态变为 DISABLED;重复创建返回冲突或幂等(按最终约定)。
test_token_hashing()- 签发 token 时 DB 中只保存 hash,不保存明文。
API 测试(TestClient):
test_admin_create_user_and_issue_token()- admin token 可创建用户并签发 token(明文 token 只返回一次)。
test_disabled_user_token_rejected()- 用户被禁用后,使用旧 token 调用 API 返回 401/403。
实现落点(建议模块)
argus.service.auth:token 校验与 user_id 解析(兼容 legacy 模式)argus.service.db:新增users、api_tokens表与 CRUDargus.service.app:新增 user 管理 endpoints(admin scope)configs/dev.yaml:补充 admin token/env 相关配置(保持 YAML 风格)
验收点
v2.5_acceptance.md:U1 可通过自动化 API 测试覆盖。
M2:Task 绑定 user_id + API 可见性隔离(仍不改 TaskSpec)
目标
- 提交 task 时由 token 推导
user_id,写入tasks.user_id。 - task 查询/取消/日志默认只允许 owner;他人访问返回 404(避免泄露存在性)。
- queue 默认只返回当前用户队列;admin 可查询全局队列(可选)。
TDD 用例(先写测试)
单测:
test_tasks_table_has_user_id():创建任务必须落user_id,且list_queue(user_id=...)只返回该用户任务。
API 测试:
test_task_visibility_isolated()- user A 创建 task;user B 查询
/api/v2/tasks/{id}返回 404; - user B cancel/logs 也返回 404。
- user A 创建 task;user B 查询
test_queue_isolated()- A/B 各自创建 task;
GET /api/v2/queue只看到自己的。
- A/B 各自创建 task;
实现落点
argus.service.app:为 task endpoints 增加 user scopeargus.service.db:tasks 表增加 user_id 字段、索引、按 user 过滤的查询方法argus.service.scheduler:pick_next_runnable_task 等仍按“全局 FIFO”或“按 user FIFO”- v2.5 先保持“全局 FIFO”最简单(但 API queue 视角是按 user 过滤)。
验收点
v2.5_acceptance.md:U2 可通过 API 测试覆盖。
M3:Jobs 输出目录按 user 隔离(只改输出,不改输入)
目标
- Ray Job 的 job_root 目录由服务端统一计算到:
/private/users/<uid>/jobs/<ray_submission_id>/...
- TaskSpec 内与输入相关的路径字段必须是
/private/common/...(v2.5 输入统一 common)。 - 任何用户无法通过 TaskSpec 指定输出写到非 user jobs 目录(避免越权写)。
TDD 用例(先写测试)
单测:
test_job_root_derivation_per_user()- 给定 user_id 与 ray_submission_id,派生 job_root 固定且正确。
test_reject_non_common_inputs()- TaskSpec 中 train_file / val_file / code_path / hf 路径等若不以
/private/common/开头则拒绝(HTTP 400)。
- TaskSpec 中 train_file / val_file / code_path / hf 路径等若不以
API 测试:
test_job_dir_written_under_user_jobs()- 提交 task 后,在 DB 或 submit payload 中能看到 job_root 在 user jobs 下(可通过 mock RayJobTool.submit 捕获 spec)。
实现落点(建议最小侵入)
- 在 service 层派生
job_root并注入到 RayJobTool/builders(而不是让用户从 TaskSpec 指定)。 - RayJobTool
_job_dir()改为接收“job_root 生成器”或直接接收job_root参数(由服务层提供)。- 目标:保持 RayJobTool 的职责清晰:提交 Ray job;路径策略由 service 决定。
验收点
v2.5_acceptance.md:U3/U4 可通过 API/单测覆盖。
M4:Stateless Ray Node Pool(head.json + worker watchdog)+ 端到端脚本验证
目标
- head 启动后持续写入
/private/ray/discovery/<cluster_name>/head.json(包含 TTL)。 - worker 容器内运行 watchdog(或启动脚本 + watchdog),无需平台显式传 head 地址:
- 读取 head.json(存在且未过期)→
ray start --address=<head_ip>:<gcs_port> - head.json 变化 →
ray stop+ray start重连
- 读取 head.json(存在且未过期)→
- 在 dev 环境(docker compose)提供一键脚本复现(e2e)。
TDD 用例(先写测试)
单测(不跑真实 ray):
test_head_json_read_validate_ttl()- 文件不存在/过期 → 返回“不可用”
- 未过期 → 返回 head 地址
test_watchdog_decision_on_change()- head_ip 变化 → 触发重连动作
- only updated_at 变化(地址不变)→ 不重连(或按策略重连,需确定)
组件/脚本级测试(可选):
- 如果 watchdog 用 Python 实现,可对“执行命令”层做 stub(不真正跑
ray start),只验证会调用什么命令。
端到端脚本(手工/慢):
- 提供脚本
scripts/run_all_v25_stateless.sh(命名示例):- 起 head(Ray head + API)
- 启动 head publisher(写 head.json)
- 起 2 个 worker(每个 4 GPU),worker 只跑 watchdog,不传 head 地址
ray status显示 1 head + 2 worker 且 GPU=8- 通过 API 创建用户/签发 token,提交 PPO/GRPO/SFT
- 重启 head(或更新 head.json 指向新地址)验证 worker 自动重连
实现落点(建议实现策略)
为了可测试性(TDD),推荐把“读 head.json/判定 TTL/生成 ray start 命令”做成 Python 模块:
argus.ray.discovery:read/write head.json(原子写、TTL)argus.ray.worker_watchdog:watch loop(polling + change detection),执行命令可注入(便于单测 stub)
脚本层保持薄:
scripts/负责 docker exec / compose 编排与进程守护;- watchdog 进程由容器内 python 模块运行(更可测、更易移植到生产平台的 entrypoint/command)。
验收点
v2.5_acceptance.md:A1/A2/A3 主要通过 e2e 脚本 + dashboard/日志验证。
2. 回归策略(确保 v2.0 不被破坏)
在 v2.5 过程中保留并持续回归以下用例(至少单测覆盖):
- 旧的内部 token 模式仍可访问
GET /api/v2/queue与提交 task(若决定保留兼容)。 - scheduler 的“资源不足 → PENDING_RESOURCES → 延迟重试”行为不变(现有
test_scheduler.py覆盖)。 ray entrypoint_resources强制 driver 落 worker(继续使用worker_node自定义资源)。
3. 交付清单(代码/脚本/文档)
3.1 代码
- user/tokens:DB schema + auth + API endpoints
- tasks:绑定 user_id + 权限隔离
- job_root:按 user jobs 输出目录派生(输入仍 common)
- discovery/watchdog:head.json + worker 自愈
3.2 scripts(dev e2e)
- head:启动 Ray head + head publisher
- workers:以无状态方式启动(不传 head addr)+ watchdog
run_all:一键跑通(含 API submit + 查询 + cancel + 观察队列)
3.3 文档
- 更新
specs/mvp/v2.5/*(设计/API/验收/开发计划) - 补充
src/mvp/README.md的 v2.5 使用方式(如需要)
4. 关键待确认点(开始实现前必须定稿)
- legacy token 是否继续兼容
- 方案 A:保留
MVP_INTERNAL_TOKEN(单租户)+ 新增 user token(多租户) - 方案 B:v2.5 直接切换到 user token(破坏兼容,但更清晰)
- 调度公平性
- v2.5 先全局 FIFO(简单);后续 v3 再引入 per-user 公平调度/配额。
- head.json 的生产写入者
- 方案 A:与 API 同进程线程(最少组件)
- 方案 B:独立进程(更独立、易运维)