Files
codex-app-server/cloud/chv/guest/vsock_notify.py
T
host 78098c5594 feat(chv-muse-p1): 常驻 VM + WarmPool 扶正 + 气球 + vsock,对齐 Muse
每用户一台常驻 VM(暂停态,resume 到 SSH 5 秒内为待验证目标),
池空/失败回落冷启动;CHV 配置加 virtio-balloon + free-page-reporting
与 vsock notify 最小实现(SSH 仍是命令通道与就绪判定)。
新模块 resident_pool.py / vsock.py + 客内 vsock_notify.py;
_config 增加 CHV_RESIDENT_IDLE_MINUTES / CHV_BALLOON / CHV_VSOCK;
worker_adapter 接常驻孤儿清理 facade;ADR-17 差距表见
docs/chv-muse-p1-gap.md。

恢复收尾(分离后回归,见 gap.md §4):VmDeathTest 死亡模拟改杀
实际跑命令的 VM;两处冷路径旧测试显式关常驻;_balloon_vsock_payload
与 listen_notify 拆到 50 行内;resident_pool.py:208 mypy 修掉。
executor.py out 未注解与 marker_tools.py:142 是基线旧错,未动。

测试:全量 cloud 套件 1934 OK(skipped=8);mypy 在改动文件上
干净(仅基线旧错 4 处)。真机验证待第三棒让出 WSL 测试机。
2026-11-06 21:31:48 +00:00

137 lines
4.6 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""客内 vsock notify 脚本(P1,feat-chv-muse-p1)。
启动后连宿主 CID 2 的 1024 端口发 ready,之后每 30 秒发一次
heartbeat。消息是 JSON 行:
{"type": "ready"|"heartbeat", "vm": "<vm 名>", "ts": <unix 秒>}
宿主在 <vsock socket>_1024 这个 Unix socket 上监听接收
(cloud/chv/vsock.py 的 wait_ready/listen_notify)。
失败一律静默退出(exit 0,不打日志、不留 traceback):vsock
只是就绪辅助信号,SSH 仍是唯一命令通道与就绪判定——本脚本
绝不能因为连不上、发失败而影响 sshd 或任务。调试时设
VSOCK_NOTIFY_DEBUG=1 可在 stderr 看到异常。
================ P2 镜像集成接入说明(本轨只写脚本,不改构建) ================
本文件不由 chv-guest/build.sh 安装,镜像集成是 P2 的范围。
P2 接入时做这三件:
1. 把本文件拷进镜像,例如 /app/vsock_notify.py(须 python3,
客机镜像已有 Python;若最小镜像无 python3,P2 须先补);
2. 加 systemd 单元,例如 /etc/systemd/system/vsock-notify.service:
[Unit]
Description=vsock notify to host (ready/heartbeat)
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /app/vsock_notify.py %H
Restart=no
[Install]
WantedBy=multi-user.target
(%H 是客机 hostname;也可直接写 VM 名,或由 cloud-init
写进环境变量 CHV_VM_NAME。Restart=no:本脚本失败即静默
退出,不由 systemd 反复拉起打扰宿主监听。)
3. 前置条件(缺一则本脚本静默退出,任务仍走 SSH,不报错):
- 客机内核须含 CONFIG_VIRTIO_VSOCKETS;
- 起 VM 时 CHV_VSOCK=1(默认开),VmConfig 带 vsock 设备
(cloud/chv/executor.py 的 _balloon_vsock_payload);
- 宿主侧须在 VM boot 前后监听 <socket>_1024(否则客机
首次 connect 失败,本脚本在 30 秒内重试,仍失败即退出)。
端口 1024 与宿主侧 cloud/chv/vsock.py 的 VSOCK_NOTIFY_PORT
须一致;改一处必须改另一处。
===============================================================================
"""
import json
import os
import socket
import sys
import time
# 宿主 CID(vsock 众所周知值,CHV 官方 docs/vsock.md)。
HOST_CID = 2
# notify 端口(与宿主侧 VSOCK_NOTIFY_PORT 一致)。
NOTIFY_PORT = 1024
# 首次 connect 重试窗口(秒):宿主监听可能比客机启动晚到。
CONNECT_RETRY_SECONDS = 30
# 心跳间隔(秒)。
HEARTBEAT_INTERVAL = 30
def _vm_name(argv) -> str:
"""VM 名:argv[1] > CHV_VM_NAME > hostname。"""
if len(argv) > 1 and argv[1]:
return argv[1]
env_name = os.environ.get("CHV_VM_NAME") or os.environ.get(
"VSOCK_VM_NAME")
if env_name:
return env_name
try:
return socket.gethostname()
except OSError:
return "unknown"
def _send(sock, message_type: str, vm: str) -> None:
line = json.dumps({"type": message_type, "vm": vm,
"ts": time.time()},
separators=(",", ":"))
sock.sendall((line + "\n").encode("utf-8"))
def _connect(deadline: float):
"""在 deadline 前反复连 (HOST_CID, NOTIFY_PORT),成功返回
socket,超时返回 None。"""
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
return None
sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
try:
sock.settimeout(min(remaining, 5))
sock.connect((HOST_CID, NOTIFY_PORT))
sock.settimeout(None)
return sock
except OSError as exc:
try:
sock.close()
except OSError:
pass
if os.environ.get("VSOCK_NOTIFY_DEBUG") == "1":
print("vsock_notify connect 失败:%r" % (exc,),
file=sys.stderr, flush=True)
time.sleep(1)
def main(argv) -> int:
vm = _vm_name(argv)
deadline = time.monotonic() + CONNECT_RETRY_SECONDS
sock = _connect(deadline)
if sock is None:
return 0
try:
_send(sock, "ready", vm)
while True:
time.sleep(HEARTBEAT_INTERVAL)
_send(sock, "heartbeat", vm)
except (OSError, ValueError):
# 任何发送失败都静默退出,不影响 sshd/任务。
return 0
except KeyboardInterrupt:
return 0
finally:
try:
sock.close()
except OSError:
pass
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))