fix(chv): P2-20/P2-19/P2-17/P2-18 残留收尾 + NO_PROXY 合并

- P2-20:_guest_env_delivered 加专用锁,读写只走
  _delivered_secrets/_mark/_clear 三个入口,并发 hammer 测试钉死
- P2-19:_record_net_entries 失败发 net_registry_write_failed
  独立审计并返回 False;_boot_vm 见 False 即拒起(wake 随后清半醒 VM)
- P2-17:net.json 删失败发 vm_net_registry_delete_failed 审计;
  _write_dirty_marker 失败发 dirty_marker_write_failed 审计并返回
  False,stop 不抛的契约不变
- P2-18:新增 cloud/chv/atomic_file.py(mkstemp 随机名 + 文件
  fsync + rename + 目录 fsync),net.json/known_hosts/
  snapshots.json/pool registry/脏标记/spill 全部走它,固定 .tmp 清零
- H1 附带:NO_PROXY 实测确有覆盖 bug(env 重复赋值以后者为准,
  调用方 NO_PROXY 被 egress 的 localhost 值静默覆盖),改为合并
  单条发出;egress 已设时调用方代理变量(含小写)一律不发
- 测试:新增 cloud/tests/test_chv_residual.py(23 项),旧 closeup
  两处钉死旧语义的测试按新语义更新
This commit is contained in:
host committed 2026-11-06 18:41:50 +00:00
1 parent ba5df980bf
commit c39715659e
7 files changed
+850 -51

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
"""CHV 宿主侧小文件的原子写(P2-18 收尾)。
此前 net.json / snapshots.json / pool registry /
known_hosts 各自用固定名 `<path>.tmp` + 直接 os.replace,
存在两个问题:
1. 固定 tmp 名:同一进程内两个写者(或一次崩溃残留的
.tmp)会互相覆盖/误读;不同 VM 的文件虽在不同目录,
但同一目录内的快照登记与 net 登记仍共用可预测的
tmp 名,崩溃后残留的 .tmp 会被下一次写静默覆盖,
丢了“上次写了一半”的信号。
2. 无 fsync:os.replace 只保证目录项原子,不保证内容
已落盘;宿主断电/崩溃后新文件可能回到旧内容或
0 字节,而调用方已认为登记成功。
本模块是唯一写路径:mkstemp 随机名(0600)→ 写 →
flush + fsync 文件 → os.replace → fsync 父目录。
写失败时删临时文件再原样抛错,调用方按各自语义
处理(审计/拒绝/脏标记),本模块不吞错、不记日志。
"""
import os
import tempfile
def atomic_write(path: str, data: bytes, mode: int = 0o600) -> None:
"""原子写 bytes 到 path(随机 tmp 名 + 双 fsync)。
mode 是最终文件的权限(默认 0600:这些文件都在
0700 的 state_dir 内、且含登记/密钥类内容,按敏感
文件对待;调用方要放宽时显式传)。
"""
directory = os.path.dirname(path) or "."
fd, tmp_path = tempfile.mkstemp(prefix=".tmp-", dir=directory)
try:
os.fchmod(fd, mode)
with os.fdopen(fd, "wb") as fh:
fh.write(data)
fh.flush()
os.fsync(fh.fileno())
os.replace(tmp_path, path)
_fsync_dir(directory)
except BaseException:
try:
os.unlink(tmp_path)
except OSError:
pass
raise
def atomic_write_text(path: str, text: str, mode: int = 0o600) -> None:
"""atomic_write 的 str 版(UTF-8 编码)。"""
atomic_write(path, text.encode("utf-8"), mode=mode)
def _fsync_dir(directory: str) -> None:
"""fsync 父目录,让 rename 本身落盘(best-effort:
某些文件系统不支持目录 fsync,失败即跳过——文件
内容的 fsync 已在 atomic_write 里做完,目录 fsync
丢了只影响“rename 是否在崩溃后可见”,不影响内容)。
"""
try:
dir_fd = os.open(directory, os.O_RDONLY)
except OSError:
return
try:
os.fsync(dir_fd)
except OSError:
pass
finally:
os.close(dir_fd)