233 lines
9.3 KiB
Markdown
233 lines
9.3 KiB
Markdown
# 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 测试分层
|
||
|
||
1) **单元测试(fast)**
|
||
- 纯 Python 逻辑:路径策略、SFTPGo client、retention 计算、文件移动/删除策略(用临时目录)。
|
||
- 不依赖真实 Ray、不依赖 docker、不依赖网络。
|
||
|
||
2) **组件测试(中等)**
|
||
- FastAPI 路由(含 WebUI 路由):`fastapi.testclient.TestClient`
|
||
- mock/stub SFTPGo client 与 ray client
|
||
|
||
3) **端到端(慢)**
|
||
- 在 `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>`
|
||
- 全程严格 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` 落一个一键脚本(或手册)跑通:
|
||
1) `docker compose up -d` 拉起 Ray(head+2 worker)+ SFTPGo
|
||
2) admin 创建用户 alice(联动创建 SFTPGo 用户 + password)
|
||
3) alice 通过 SFTP 上传:
|
||
- 数据集到 `/private/users/alice/datasets/...`
|
||
- (可选)本地模型到 `/private/users/alice/models/...`
|
||
4) alice 通过 API/WebUI 提交任务引用上述路径
|
||
5) 任务成功后:
|
||
- 从 `jobs/<sid>` 下载 logs/checkpoints
|
||
- 把权重移动到 `models/`,验证不会被清理
|
||
6) 把 retention 配置调小,验证 jobs→trash→purge
|
||
|
||
**交付建议**
|
||
- 新增脚本(命名示例):
|
||
- `scripts/run_all_v30_api.sh`
|
||
- `scripts/run_e2e_v30_cases.sh`
|
||
- 新增 `docker-compose.yaml` 中的 `sftpgo` service(或 `docker-compose.v30.yaml` 叠加文件)
|
||
|
||
**验收点**
|
||
- `v3.0_acceptance.md` 全部 MUST 用例通过。
|
||
|
||
---
|
||
|
||
## 2. 风险与测试关注点
|
||
|
||
1) **权限与路径逃逸**
|
||
- path policy 必须覆盖:train/val/model_id(local)/output dirs(jobs/trash)
|
||
- 所有删除/移动必须做 prefix 校验
|
||
|
||
2) **并发与竞态**
|
||
- janitor 只处理 terminal tasks,避免清理正在写入的目录
|
||
- move 使用同文件系统 `os.replace`(原子)
|
||
|
||
3) **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 解释)
|
||
|