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:
1 parent
ba5df980bf
commit
c39715659e
7 files changed
+850
-51
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user