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 测试机。
This commit is contained in:
1 parent
06c3e172fe
commit
78098c5594
13 files changed
+2527
-36
No files matched your search
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/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))
|
||||
Reference in new issue
Block a user