Commit Graph
4 Commits
Author SHA1 Message Date
host a859d731d5 fix(chv-hybrid): p2 第三轮:attach 轮询等挂盘落地 + detach 走 sudo 窄规则 umount
ci / test (push) Failing after 47m51s
第 2 轮复测(0dc8328)p2 仍 FAIL 的两处根因:
1. 挂盘时序竞态:add-disk 返回 06:59:10,root hotplug 路径
   (cloud-init-hotplugd → cloud-final runcmd → chv-data-mount)
   06:59:14–16 才挂上;旧 attach 以 chv 调 chv-data-mount 并判
   rc==0,但 /dev/vd? 属 root:disk、chv 的 blkid/mkfs 必败,
   脚本又静默 exit 0,rc 检查是空洞的成功假象。
2. 卸载权限:挂载是 root 做的,chv 的 plain umount 恒 rc=32
   must be superuser,release 按现写法永远失败、VM 留置。

修法选 (a) 轮询,不选 (b) root 特权挂载:挂盘本身已有 root
路径在跑,host 侧不另开新的 root 挂载通道(新特权面只扩
攻击面);轮询以 chv 只读判定挂载态,零新增特权。
- attach:删 chv 调 chv-data-mount,改为轮询 mountpoint -q
  /home/chv/workspace && test -w(默认 60 秒/2 秒,可经
  CHV_DATA_MOUNT_TIMEOUT_SECONDS / CHV_DATA_MOUNT_INTERVAL_SECONDS
  注入短值);超时记 data_disk_mount_timeout 审计并抛同名
  kind,不写归属标记。attach 返回成功 = 挂盘真实发生。
  resume_seconds 因此真实包含挂盘等待(§2.1 口径),≤2 秒
  目标不改口径、不改目标。
- detach:改走 sudo -n /usr/bin/umount /home/chv/workspace;
  失败即 False,release 留置 VM 不回收(fail-closed)。
- chv-data-mount.sh:盘已找到后 mkfs/mount 失败改 exit 1
  (原全部失败路径 exit 0);无盘仍 exit 0(启动期 no-op,
  不挡 boot)。本脚本只由 root 跑(service/runcmd)。
- build.sh:apt 加 sudo;写 /etc/sudoers.d/chv-data-umount
  窄规则(chv 仅能以 root 跑 umount /home/chv/workspace
  这一条精确 argv,NOPASSWD,无 shell/其它命令/挂载权),
  0440 + visudo -cf 作构建门;buildinfo/lockfile 登记
  sudo 与 chv-data-umount。

特权边界:本轮唯一新增提权面就是上述一条 sudoers 规则;
attach 零提权(只读轮询),挂载权仍只在 root 的
hotplug/service 路径手里。旧镜像(无 sudo/无规则)上
detach 返回 False、release 留置,须重建镜像才恢复回收。

测试:新增 10 项(attach 等三次轮询才成功、超时 loud 失败、
永不以 chv 调 chv-data-mount、detach 精确 sudo argv、
sudo 失败 False、卸载失败 release 留置、客机脚本 exit 码、
build.sh sudo/窄规则/buildinfo 登记);另修 2 处旧断言
(attach 不再调挂载脚本)与 stage4 lockfile 夹具(补 sudo
dpkg 条目),并把 attach 热插段抽为 _hotplug_tenant_disk
(50 行函数上限)。全量 Ran 4688,OK (skipped=15,均为
环境 skip:无 PG DSN/驱动、缺 SearXNG_URL、fallback state
文件在位)。
2026-11-09 08:17:01 +00:00
host 0dc8328ecc fix(chv-hybrid): p2 热插盘 body 补 image_type + 非 2xx 记 CHV 原话
ci / test (push) Failing after 48m54s
- attach_tenant_data_disk:PUT /vm.add-disk body 由 {"path"} 改为
  {"path", "image_type"}(复用 _disk_image_type,与 create 时盘
  同款;租户数据盘 raw → Raw)。CHV v54 已移除盘格式自动探测,
  只发 path 时真机回 400(p2 复测);证据:v54 release notes
  “autodetection removed”、ch-remote v54.0 add-disk --help 含
  image_type=<raw,qcow2,...>、本仓 _vm_config_disks 的 vm.boot
  400 "Image type required for disk" 真机实测(2026-11-07)。
- 非 2xx 时 body 原样记入 data_disk_attach_failed 审计事件与
  ChvFailure detail(新增 _api_error_body_text,截断上限 500
  字符:CHV 400 体通常一行 JSON <200 字符,500 足够装完整原话;
  超长截断并标 "…(已截断)",保审计与异常串有界);同类问题一并
  修 _create_vm(vm_create_failed 带 body)。fail-closed 语义
  不变(失败仍销毁/回落,不把未挂盘的 VM 当已绑定)。
- 单测:attach 成功断言 image_type=Raw;新增 400 body 记录用例
  与超长截断用例;vm.create 失败 body 用例。
- 真机探针:chv-multi-host/probe-add-disk.py(px2,旧形状 A/B +
  产品函数成功路径 + 两条 400 记录路径,待复测代理跑)。

全量:Ran 4678,OK (skipped=14),0 失败/错误。
2026-11-09 06:12:21 +00:00
host 99d9e5771b merge(chv-hybrid): 合入 B 轨——租约 + 排队 + 升级提示 + 网关门
合入 feat-chv-hybrid-b (667a427)。executor.py 两处内容冲突按 hunk
解决,全部取 B 侧;合入时发现 merge 复制出第二个 suspend_after_task
定义(A 版截断体),已删除。

集成统一(A/B 语义冲突三处,设计 §4.1 的 state 模型为准):
- state 模型:A 的 flag 模型(resident: True + legacy state)与 B
  的 state 模型(state ∈ resident-active/paused/hibernated/demoted)
  统一为 state 模型;_mark_resident_record 标记常驻时同时置
  resident-active,_ensure_resident_capacity 同时认 flag 与 state。
- 容量等待:A 的 bool 版 _wait_for_capacity 改名 _poll_capacity,
  B 的 raise 版保留原名;_wait_for_resident_slot 豁免已有常驻登记
  的租户(它已占一个槽位,再等等于把自己的槽位算成满——A 轨
  test_resident_cap_blocks_new_resident 第二腿在合入时因此失败,
  已修并通过)。
- 档位大小写:A 用小写 paid/heavy,B 用 Paid/Heavy;比较一律走
  _norm_tier + HYBRID_RESIDENT_TIERS(resident_tier_eligible 与
  promote_to_resident 两处),fail-closed 语义不变。
- 严格快照校验翻为默认开启(设计 §2.2):_snapshot_strict_tenant
  默认 True,显式 CHV_SNAPSHOT_STRICT_TENANT=0 才关闭;旧恢复
  测试的 fixture 已登记带匹配 tenant 的快照,A 轨 legacy 测试改
  为显式 opt-out 覆盖。

生产接线:worker 只调 dispatch_task,dispatch_task_hybrid 在生产
路径上无调用方;dispatch_task 顶部加一道门,warm_pool_size>=1 时
走 dispatch_task_hybrid,==0 时保持旧冷启语义(不成环:hybrid 的
<=0 分支退化为 dispatch_task_persistent,后者 <=0 调回 dispatch_task)。

旧网关测试更新(B 的语义变更):test_worker_gateway_guard_allows_chv
前加常驻登记;新增 test_worker_gateway_guard_denies_pool_vm。

测试:test_chv_hybrid_a/b 64 项 + 受影响旧测试 16 项(Gateway、
RestoreNoFallback、RestoreSnapshotTest)全部通过;全量待跑。
2026-11-08 22:00:12 +00:00
host 172accb2ec feat(chv): hybrid executor core — routing, clean pool, data disk, snapshot tenant check
Track A (§9.1 items 1-4, DESIGN-HYBRID.md):
- dispatch_task_hybrid: route by tier + lease state (resident resume/cold
  vs pool hit/cold/queue, 300s queue timeout -> host_capacity_exceeded)
- clean pool prewarm split from prewarm_persistent_vm: tenant-less birth
  (no data disk, no seed, no Gitea sync wait), binding deferred to
  acquire; warm_pool_sync_incomplete fail-closed at allocation
- tenant data disk mount at acquire (attach_tenant_data_disk, PUT
  /vm.add-disk + chv-data-mount) and unmount+sync at release;
  _verify_task_disk_source invariant: task disk must be the tenant's
  data disk, never the clean snapshot disk
- restore_vm validates tenant_id before restore: unregistered/unmarked
  snapshot denied in strict (hybrid) mode, mismatch -> snapshot_tenant_
  mismatch fail-closed, audit vm_restore_denied (host_id + tenant only)
- settled values: 2 warm per host, pool cap 50% of low-freq peak,
  resident cap 50% of VM slots, 1 snapshot per tenant per 30 days

Tests: cloud/tests/test_chv_hybrid_a.py (27 tests, all passing)
Full cloud suite: 4606 tests, 0 failures, 0 errors, 15 skipped
(TMPDIR on /home; ~91 min, slowed by concurrent track-C suite)
2026-11-08 21:45:03 +00:00