feat: CHV 审核修复 P2(范围恢复为全做)

P2-1:preflight reuse/CPU/内存校验块小提取为
_preflight_config_checks(纯移动,深耦合处如实停)。
P2-2:build_vm_command 降级为私有 _build_vm_command;
Docker goal 内联解析顶部加正典分叉说明 + 同步检查清单。
P2-3:gaps grep 文本断言改成真实 Worker._run_attempt
行为断言。P2-4:README/build.sh 口径对齐(章节顺序、
BUILDINFO 名字列表、Node.js 不装写实)。P2-5:stop_vm
删除失败记审计。P2-6:CPU/内存校验统一进 preflight。
测试:原 84 + 新增 33 = 117 项,mock,无真 VM。
This commit is contained in:
host committed 2026-10-31 02:01:03 +00:00
1 parent 9771ed9e8c
commit 445376743d
9 files changed
+169 -44

No files matched your search

+52 -18
View File
@@ -96,7 +96,7 @@ class CloudHypervisorExecutor(Executor):
wake_vm() 先 preflight,再经 launcher 以 --api-socket
只启动 VMM 进程(CLI 上不带内核/磁盘配置),等
vmm.ping 通,再 PUT vm.create(VmConfig)+ PUT vm.boot。
build_vm_command() 保留的直接启动命令只是文档里的手动
_build_vm_command() 保留的直接启动命令只是文档里的手动
等价写法(运维手工起 VM 时用),执行器代码里不走它。
- 就绪判定只选 SSH,不做 vsock 第二套:boot 后轮询 SSH
“true”命令,直到 CHV_READY_TIMEOUT(默认 120 秒,
@@ -207,7 +207,7 @@ class CloudHypervisorExecutor(Executor):
且可读、SSH key 存在且可读。任一项缺失都 raise
RuntimeError,绝不静默回落 Docker/Local。_validate()
即 preflight(旧名保留,供旧调用点用)。
命令构造(build_vm_command/build_ssh_command/
命令构造(_build_vm_command/build_ssh_command/
build_vm_config)是纯函数,不做文件存在校验,单测可
直接断言命令与 VmConfig 形状。
@@ -314,6 +314,35 @@ class CloudHypervisorExecutor(Executor):
return os.path.isfile(path) and os.access(path, os.X_OK)
return shutil.which(path) is not None
def _preflight_config_checks(self, missing):
"""preflight 的配置校验块(feat-chv-fix,P2-1 小提取:
仅把 reuse 拦截与 CPU/内存校验从 preflight() 里抽出,
语义逐行不变;其余 preflight 检查项与 config.py 的
字段解析深耦合,本轨如实停、不硬拆)。"""
# P0-3:reuse 生产拦截。一行配置再也不能击穿租户隔离:
# 只有显式实验二次确认 CHV_REUSE_SINGLE_TENANT_
# EXPERIMENT=1 时才放行,且放行本身在 wake_vm 里审计;
# 否则 preflight 直接拒绝(kind=disk_mode_reuse_blocked)。
if getattr(self, "disk_mode", "copy") == "reuse" \
and not getattr(self, "reuse_experiment", False):
missing.append(("disk_mode_reuse_blocked",
"CHV_DISK_MODE=reuse 未经显式实验确认:"
"须同时设 CHV_REUSE_SINGLE_TENANT_"
"EXPERIMENT=1(仅单租户实验)"))
# P2-6:配置校验时机统一到 preflight。CPU 非整数、内存
# 字符串非法过去只在 build_vm_config 才炸,现在 preflight
# 一次报出(kind=config_invalid)。
try:
int(self.cpus)
except (TypeError, ValueError):
missing.append(("config_invalid",
"CHV_CPU 必须是整数:%r" % (self.cpus,)))
try:
_parse_memory_bytes(self.memory)
except ValueError:
missing.append(("config_invalid",
"CHV_MEM 格式非法:%r" % (self.memory,)))
def preflight(self):
"""预检(缺一不可则 fail-fast):收集全部缺失项一次报出
具体缺哪一项,绝不笼统报错;失败为 ChvFailure(带 kind,
@@ -411,17 +440,7 @@ class CloudHypervisorExecutor(Executor):
if not isinstance(_net, (list, dict)):
missing.append(("net_config_invalid",
"CHV_NET_JSON 须为 JSON list 或 dict"))
# feat-chv-fix(P0-3):reuse 生产拦截。一行配置再也
# 不能击穿租户隔离:只有显式实验二次确认
# CHV_REUSE_SINGLE_TENANT_EXPERIMENT=1 时才放行,
# 且放行本身在 wake_vm 里审计;否则 preflight 直接
# 拒绝(kind=disk_mode_reuse_blocked)。
if getattr(self, "disk_mode", "copy") == "reuse" \
and not getattr(self, "reuse_experiment", False):
missing.append(("disk_mode_reuse_blocked",
"CHV_DISK_MODE=reuse 未经显式实验确认:"
"须同时设 CHV_REUSE_SINGLE_TENANT_"
"EXPERIMENT=1(仅单租户实验)"))
self._preflight_config_checks(missing)
if missing:
kind = missing[0][0]
detail = "; ".join(text for _, text in missing)
@@ -451,8 +470,14 @@ class CloudHypervisorExecutor(Executor):
pass
# -- 命令构造(纯函数,不做文件校验) -------------------------------
def build_vm_command(self, task):
"""构造 cloud-hypervisor 启动命令(干跑可测)。
def _build_vm_command(self, task):
"""构造 cloud-hypervisor 启动命令(干跑可测,仅私有)。
feat-chv-fix(P2-2):原公开 build_vm_command() 降级为
私有——执行器唯一启动路径是 API 模式(wake_vm),公开
的第二启动路径容易被后来人误当成可走的启动入口;手动
等价写法保留在本方法与类 docstring 里,运维手工起 VM
时仍可按此形状拼命令,不再经公开方法调用。
顺序固定:<二进制> --api-socket <socket> --cpus boot=N
--memory size=M --kernel <内核> --cmdline <命令行>
@@ -924,7 +949,7 @@ class CloudHypervisorExecutor(Executor):
def start_vm(self, task):
"""旧名保留:即 wake_vm(API 模式)。直接启动命令
build_vm_command() 只作手动等价写法,不再是执行器
_build_vm_command() 只作手动等价写法(私有),不再是执行器
的启动路径——两条启动路径并存是上一版的半吊子,
本版只认 API 模式。"""
return self.wake_vm(task)
@@ -1102,8 +1127,17 @@ class CloudHypervisorExecutor(Executor):
_p = os.path.join(self.state_dir, name, _suffix)
if os.path.exists(_p):
os.remove(_p)
except Exception:
pass
except Exception as _cleanup_exc:
# feat-chv-fix(P2-5):seed/私钥删除失败不再静默
# 吞掉——记 vm_key_cleanup_failed 审计;且下次同名
# VM 预置时若私钥残留,generate_host_keypair 的
# “复用已有对”语义不变,但审计痕迹让“一性”破
# 坏可被发现(彻底根治须另做“清理失败即拒起”,
# 见 PR 描述的遗留项,不在本轨假装已根治)。
task = task_or_name if isinstance(task_or_name, dict) \
else {"id": task_or_name}
self._emit("vm_key_cleanup_failed", task,
"vm=%s error=%s" % (name, _cleanup_exc))
task = task_or_name if isinstance(task_or_name, dict) \
else {"id": task_or_name}
if had:
+1 -1
View File
@@ -16,7 +16,7 @@
seed 目录 state_dir/<vm>/seed/ 含 meta-data(instance-id
即 VM 名)、user-data(write_files 写 /etc/ssh/ 的 host
key 对并重启 sshd)。客机镜像硬性要求(见
chv-guest/README.md §6):必须装 cloud-init 且支持
chv-guest/README.md §5):必须装 cloud-init 且支持
NoCloud seed、首启时先应用 seed 再启动 sshd、且不得在
seed 应用前/后无条件重新生成 host key(否则预置即失效)。
seed 磁盘/目录是 per-VM 敏感物,VM 关闭/回收时必须删。