9.3 KiB
9.3 KiB
MVP v3.0 开发计划(TDD 驱动)
本文是 v3.0 的工程化开发计划,强调“先写测试,再写实现”(TDD),并将每个里程碑拆成可独立验收的小闭环。
输入依据:
- 路线图:
specs/mvp/mvp_roadmap_v2.md - v3.0 设计:
specs/mvp/v3.0/v3.0_design.md - v3.0 API:
specs/mvp/v3.0/v3.0_api.md - v3.0 验收:
specs/mvp/v3.0/v3.0_acceptance.md - 现状基线:v2.5(Task queue + User mgmt + Stateless ray pool + 单镜像节点守护)
v3.0 已确认约束:
- 允许用户数据集路径:
/private/users/<me>/datasets/... - 允许用户本地模型路径:
/private/users/<me>/models/... - 不允许执行用户自定义代码(不注入 user code 到 PYTHONPATH;
code_path仍只允许/private/common/...) - SFTPGo 先用 password 方案(方案 A:API 联动创建/管理 SFTPGo 用户)
- jobs retention:3 天移入回收站(trash/jobs),再 7 天永久删除;不提供 keep/延长保留标记
- janitor:API server 内置后台线程;删除/移动采用文件系统直接操作(不依赖 SFTPGo API)
0. TDD 规范(所有功能都遵循)
0.1 测试分层
- 单元测试(fast)
- 纯 Python 逻辑:路径策略、SFTPGo client、retention 计算、文件移动/删除策略(用临时目录)。
- 不依赖真实 Ray、不依赖 docker、不依赖网络。
- 组件测试(中等)
- FastAPI 路由(含 WebUI 路由):
fastapi.testclient.TestClient - mock/stub SFTPGo client 与 ray client
- 端到端(慢)
- 在
argus@h1通过 docker compose + scripts:- Ray 集群自动起来(head+2 worker)
- SFTPGo 服务可用
- 上传数据 → 提交训练 → 下载产物 → jobs 回收站/清理
0.2 代码与测试约定
- 测试目录:
src/mvp/py/tests/ - 新功能必须先补齐测试用例,并让其在未实现时失败(红)
- 最小实现让测试变绿(绿)
- 再做重构(重构)
- 覆盖率:继续沿用当前阈值(>= 90%)
1. 里程碑拆分(v3.0 = 5 个可验证闭环)
M1:TaskSpec 路径策略升级(允许 user datasets/models;code_path 仍仅 common)
目标
- API submit 时的路径校验从 v2.5 的 “仅
/private/common/” 升级为:train_file/val_file:允许/private/common/...与/private/users/<me>/...- 本地模型路径:允许
/private/users/<me>/models/...(不改变 YAML 结构,见实现建议) code_path:仍仅允许/private/common/...
- 阻止越权路径(
/private/users/other/...)与非/private/...路径。
实现建议(不扩展 TaskSpec)
model_id字段保持不变:- 若
model_id以/private/开头 → 视作本地模型路径 - 否则视作 HuggingFace repo id(如
Qwen/...)
- 若
TDD 用例(先写测试)
- 单测:
test_paths_allow_common_and_own_user_prefix()test_paths_reject_other_user_prefix()test_model_id_local_path_allowed_only_under_users_models()test_code_path_still_common_only()
- API 测试:
test_submit_accepts_user_datasets_paths()test_submit_rejects_cross_user_paths_404_or_400()(按约定返回 400/403)
验收点
v3.0_acceptance.md的 D 类安全隔离用例可由 API 测试覆盖。
M2:SFTPGo 集成(方案 A:用户联动创建 + password)
目标
- 引入
data management (SFTPGo):- admin 创建用户时联动创建 SFTPGo 用户(home=/private/users/<user_id>,chroot)
- password 模式:生成一次性密码(reset/create)并返回给 admin(明文只返回一次)
- 提供用户自助信息:
GET /api/v2/me返回 SFTP 连接信息、目录约定、retention 提示。
实现建议
- 新增
SFTPGoAdminClient(同步调用):- 通过
urllib或httpx(建议urllib,减少依赖;禁止 hard-code requests 使用) - 支持:create user / disable user / reset password(最小集合)
- 通过
- API server 启动时校验配置(enabled 时必须具备 admin 密码 env)。
- 同步创建用户目录结构(文件系统):
/private/users/<u>/{datasets,models,code,jobs,trash/jobs}(最小必需)
TDD 用例(先写测试)
- 单测:
test_sftpgo_client_builds_correct_requests()(不发真实网络;mock urlopen)test_user_dirs_created_on_user_create()(tmp dir 断言目录存在)
- API 测试:
test_create_user_calls_sftpgo_client()(stub client,断言调用参数)test_me_returns_sftp_info_and_paths()(含 trash/jobs 与 TTL 字段)
验收点
v3.0_acceptance.md的 A 类(用户/凭据)与 B 类(上传闭环前置)覆盖。
M3:WebUI(最小可用,多页面 + 侧边栏)
目标
- WebUI 由 API server 托管(同源,无额外 CORS):
/ui/login:token 粘贴登录(localStorage)/ui/tasks:任务列表 + 过滤(最小)/ui/tasks/new:YAML 提交(优先)+(可选)表单生成 YAML/ui/tasks/{task_id}:详情页/ui/tasks/{task_id}/logs:日志 tail + 可选自动刷新/ui/data:SFTP 信息 + 目录/retention 提示- (可选)
/ui/admin/users:管理员用户管理(若时间允许,强烈建议)
实现建议
- 先不引入 Node 构建:
- HTML 模板可用最简单的字符串拼接或 Jinja2(若引入 jinja2,则补齐依赖与测试)
- 页面通过 fetch 调用
/api/v2/...,并复用 token header
TDD 用例(先写测试)
- 组件测试(TestClient):
test_ui_routes_render_200()test_ui_contains_sidebar_links()(简单断言文本包含导航链接)test_ui_tasks_detail_shows_ids()(包含 task_id、state、ray_submission_id)
验收点
- WebUI 能完成:登录→创建任务→查看任务→查看日志→看到 data 页提示。
M4:Jobs Retention janitor(3 天移入 trash,7 天后 purge)
目标
- API server 内置 janitor 后台线程:
- 周期性扫描 DB 中 terminal tasks
- 到期后执行:
- move:
/private/users/<u>/jobs/<sid>→/private/users/<u>/trash/jobs/<sid> - purge:递归删除
/private/users/<u>/trash/jobs/<sid>
- move:
- 全程严格 path 校验,禁止越界删除
- 清理操作记录到 DB events(审计)
实现建议(数据与状态)
- 需要稳定的时间锚点与幂等:
- 使用 attempts.end_time 作为 job 结束时间(latest attempt)
- 在 tasks 表新增字段(或新表)记录:
trashed_at(首次成功 move 时间)purged_at(成功删除时间)trash_path(可选)
- 幂等:重复运行不会报错(目录不存在视为已处理)
TDD 用例(先写测试)
- 单测(用 tmpdir 构造 jobs/trash 目录):
test_janitor_moves_job_to_trash_after_threshold()test_janitor_purges_trash_after_threshold()test_janitor_never_touches_models_or_datasets()test_janitor_path_escape_rejected()(恶意 path 不可删)
- API/组件测试:
test_me_includes_retention_fields()(jobs_trash_after_days/jobs_purge_after_days)
验收点
v3.0_acceptance.md的 C2 用例可按“把阈值调小到分钟级”完成验证。
M5:端到端(h1)— SFTP 上传→训练→产物下载→回收站/清理
目标
- 在
argus@h1落一个一键脚本(或手册)跑通:docker compose up -d拉起 Ray(head+2 worker)+ SFTPGo- admin 创建用户 alice(联动创建 SFTPGo 用户 + password)
- alice 通过 SFTP 上传:
- 数据集到
/private/users/alice/datasets/... - (可选)本地模型到
/private/users/alice/models/...
- 数据集到
- alice 通过 API/WebUI 提交任务引用上述路径
- 任务成功后:
- 从
jobs/<sid>下载 logs/checkpoints - 把权重移动到
models/,验证不会被清理
- 从
- 把 retention 配置调小,验证 jobs→trash→purge
交付建议
- 新增脚本(命名示例):
scripts/run_all_v30_api.shscripts/run_e2e_v30_cases.sh
- 新增
docker-compose.yaml中的sftpgoservice(或docker-compose.v30.yaml叠加文件)
验收点
v3.0_acceptance.md全部 MUST 用例通过。
2. 风险与测试关注点
- 权限与路径逃逸
- path policy 必须覆盖:train/val/model_id(local)/output dirs(jobs/trash)
- 所有删除/移动必须做 prefix 校验
- 并发与竞态
- janitor 只处理 terminal tasks,避免清理正在写入的目录
- move 使用同文件系统
os.replace(原子)
- SFTPGo 可用性
- SFTPGo 不在线不应影响训练与 API 核心功能(除了用户创建联动)
- janitor 不依赖 SFTPGo(文件系统直连)
3. 交付清单(代码/配置/脚本/文档)
3.1 代码
- Path policy(v3.0)
- SFTPGoAdminClient + user create/disable/reset password 联动
/api/v2/me扩展(SFTP/目录/retention)- WebUI 路由与静态资源
- janitor(trash+purge)后台线程 + DB 记录
3.2 配置
configs/dev.yaml增加data.sftpgo、data.retention段(详见设计文档)
3.3 scripts / compose
- compose 增加
sftpgo(或新增 overlay compose 文件) - v3.0 e2e 脚本(上传/下载/清理验证)
3.4 文档
- 更新
specs/mvp/v3.0/*与src/mvp/README.md(运行方式、路径约定、SFTP 操作、retention 解释)