feat: CHV 执行器优化(拆模块/去重复/SSH 钉死/池持久化/中途死亡分类)
- 拆模块:CHV 从 cloud/sandbox.py 巨类拆到 cloud/chv/(executor/ pool/config/api_client/ssh_transport/disk/errors),sandbox 只留 兼容 re-export,旧导入路径与新路径是同一对象 - 去重复:goal 解析、host 顶层参数、runner 超时抽成共享 helper, Local 与 CHV 在矩阵输入下结论全等(Docker goal-resume 变体 有意保留内联版,见 PR 描述) - 安全:SSH host key 按 VM 钉死(state_dir/<vm>/known_hosts, ssh-keyscan 首次捕获 TOFU),废除 no + /dev/null;CHV_SSH_USER 空串与 CHV_SSH_PORT 越界直接拒绝,不静默 fallback - execute 语义:只读已运行 VM,未运行报 vm_not_running,不为 status 起 VM - 预热池:registry 落盘、重启载入只当孤儿、reconcile 清理、 损坏 quarantine;条目仍一次性,绝不复用脏 VM - 长任务:VM 中途死亡判 interrupted(断点可续跑);中途心跳 经评估不做(无客内进度信号可采) - #215 未合并:只列复用清单,不复制模块 测试:原 42 项 + 新 24 项全过;相关旧套件与 cloud 全量 1496 项 全过(skipped=4)。验证仍是 mock + 干跑 + 单元测试,未做真机、 压测、安全审计;堆叠于 #216 之上,base 为 feat-cloud-hypervisor。
This commit is contained in:
1 parent
c371bbdf1e
commit
fde7bf4c3e
14 files changed
+2706
-1408
No files matched your search
@@ -0,0 +1,146 @@
|
||||
"""Cloud Hypervisor 执行器配置解析(叶子模块,不依赖
|
||||
cloud.sandbox,避免循环导入)。
|
||||
|
||||
职责只有两件:内存字符串解析,以及把构造参数 + 环境变量
|
||||
解析成一份显式配置。参数优先于环境变量、环境变量优先于
|
||||
默认值——与 #216 原 __init__ 的 `or` 链完全一致,只收紧了
|
||||
两处原来会静默 fallback 的 nonsense 输入(见下文),收紧点
|
||||
在 PR 描述里明写、单测钉住:
|
||||
|
||||
- CHV_SSH_USER 显式传空字符串:原来 `or` 链会静默 fallback
|
||||
成 root(调用方以为没给用户、实际跑了 root),现在直接
|
||||
ValueError。
|
||||
- CHV_SSH_PORT 显式传 0、或解析后超出 1-65535:原来 0 会
|
||||
静默 fallback 成 2222、其它越界值会原样进 ssh 命令,现在
|
||||
直接 ValueError。
|
||||
|
||||
与 #215 的关系(只列清单,不复制模块):#215 未合并,
|
||||
本轨不假装复用它。#215 合并后应再复用的点:超时类
|
||||
(api_timeout/ready_timeout/ready_interval/task_timeout →
|
||||
timeouts 模块)、门禁类(preflight fail-fast、容量门、磁盘
|
||||
配额门、egress fail-closed → gates 模块)、配额类
|
||||
(max_vms、disk_gb、任务成本走 worker quota → quota 模块)、
|
||||
隔离类(单任务单 VM、磁盘 copy 模式、egress 代理、SSH 钉死
|
||||
→ sandbox_policy 模块)。见 PR 描述的复用清单。
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def _parse_memory_bytes(value):
|
||||
"""内存字符串转字节:2G/512M/1024K/2048(无单位=字节)。
|
||||
非法值 ValueError,绝不静默按默认算。"""
|
||||
m = re.match(r"^\s*(\d+)\s*([KMGkmg]?)\s*[Bb]?\s*$",
|
||||
str(value))
|
||||
if not m:
|
||||
raise ValueError("CHV_MEM 格式非法:%r" % (value,))
|
||||
mult = {"": 1, "K": 1024, "M": 1024 ** 2, "G": 1024 ** 3}
|
||||
return int(m.group(1)) * mult[m.group(2).upper()]
|
||||
|
||||
|
||||
class ChvConfig:
|
||||
"""解析后的 CHV 配置:纯数据对象,字段名与
|
||||
CloudHypervisorExecutor 原 __init__ 的属性名一一对应,
|
||||
执行器 __init__ 直接把字段抄到 self 上,旧测试读属性
|
||||
的方式全部不变。"""
|
||||
|
||||
def __init__(self, **fields):
|
||||
for key, value in fields.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
def build_chv_config(params, env):
|
||||
"""把显式构造参数(params,None=未给)与环境变量(env,
|
||||
默认为 os.environ 的调用方传入 dict)解析成 ChvConfig。
|
||||
|
||||
显式参数只有 __init__ 本来就有的那些;max_vms、磁盘模式、
|
||||
磁盘配额、egress、快照、预热池目标数本来就没有显式参数,
|
||||
仍只走环境变量(与 #216 一致,不悄悄加新参数面)。"""
|
||||
env = env if env is not None else {}
|
||||
binary = params.get("binary") or env.get("CHV_BINARY") \
|
||||
or "cloud-hypervisor"
|
||||
kernel = params.get("kernel") or env.get("CHV_KERNEL")
|
||||
rootfs = params.get("rootfs") or params.get("image") \
|
||||
or env.get("CHV_ROOTFS") or env.get("CHV_IMAGE")
|
||||
cpus = str(params.get("cpus") or env.get("CHV_CPU") or "2")
|
||||
memory = params.get("memory") or env.get("CHV_MEM") or "2G"
|
||||
state_dir = params.get("state_dir") or env.get("CHV_STATE_DIR") \
|
||||
or "/tmp/chv-vms"
|
||||
ssh_key = params.get("ssh_key") or env.get("CHV_SSH_KEY")
|
||||
if params.get("ssh_user") is not None \
|
||||
and params.get("ssh_user") == "":
|
||||
raise ValueError("CHV_SSH_USER 不能为空字符串")
|
||||
ssh_user = params.get("ssh_user") or env.get("CHV_SSH_USER") \
|
||||
or "root"
|
||||
if params.get("ssh_port") is not None:
|
||||
raw_port = params.get("ssh_port")
|
||||
else:
|
||||
raw_port = env.get("CHV_SSH_PORT") or 2222
|
||||
try:
|
||||
ssh_port = int(raw_port)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("CHV_SSH_PORT 必须是整数")
|
||||
if not 1 <= ssh_port <= 65535:
|
||||
raise ValueError("CHV_SSH_PORT 必须在 1-65535 之间")
|
||||
guest_host_py = params.get("guest_host_py") or "/app/host.py"
|
||||
cmdline = params.get("cmdline") or env.get("CHV_CMDLINE") \
|
||||
or "console=hvc0 root=/dev/vda1 rw"
|
||||
net = params.get("net") or env.get("CHV_NET")
|
||||
net_json = params.get("net_json") or env.get("CHV_NET_JSON")
|
||||
kvm_path = params.get("kvm_path") \
|
||||
if params.get("kvm_path") is not None else "/dev/kvm"
|
||||
cpuinfo_path = params.get("cpuinfo_path") \
|
||||
if params.get("cpuinfo_path") is not None else "/proc/cpuinfo"
|
||||
ch_remote = params.get("ch_remote") or env.get("CHV_REMOTE") \
|
||||
or "ch-remote"
|
||||
model = params.get("model")
|
||||
try:
|
||||
max_vms = int(env.get("CHV_MAX_VMS") or 4)
|
||||
except ValueError:
|
||||
raise ValueError("CHV_MAX_VMS 必须是整数")
|
||||
disk_mode = env.get("CHV_DISK_MODE") or "copy"
|
||||
if disk_mode not in ("copy", "reuse"):
|
||||
raise ValueError(
|
||||
"CHV_DISK_MODE 只能是 copy 或 reuse:%s" % disk_mode)
|
||||
disk_gb_raw = env.get("CHV_DISK_GB")
|
||||
disk_gb = float(disk_gb_raw) if disk_gb_raw else None
|
||||
egress_proxy = env.get("CHV_EGRESS_PROXY")
|
||||
allow_direct_egress = env.get("CHV_ALLOW_DIRECT_EGRESS") == "1"
|
||||
task_timeout_raw = env.get("CHV_TASK_TIMEOUT")
|
||||
task_timeout = float(task_timeout_raw) \
|
||||
if task_timeout_raw else None
|
||||
snapshot_url = env.get("CHV_SNAPSHOT_URL")
|
||||
try:
|
||||
warm_pool_size = int(env.get("CHV_WARM_POOL_SIZE") or 0)
|
||||
except ValueError:
|
||||
raise ValueError("CHV_WARM_POOL_SIZE 必须是整数")
|
||||
try:
|
||||
api_timeout = float(
|
||||
params.get("api_timeout")
|
||||
if params.get("api_timeout") is not None
|
||||
else env.get("CHV_API_TIMEOUT") or 30)
|
||||
ready_timeout = float(
|
||||
params.get("ready_timeout")
|
||||
if params.get("ready_timeout") is not None
|
||||
else env.get("CHV_READY_TIMEOUT") or 120)
|
||||
ready_interval = float(
|
||||
params.get("ready_interval")
|
||||
if params.get("ready_interval") is not None
|
||||
else env.get("CHV_READY_INTERVAL") or 5)
|
||||
except ValueError:
|
||||
raise ValueError("CHV_*_TIMEOUT/INTERVAL 必须是数字")
|
||||
if ready_interval <= 0:
|
||||
ready_interval = 5
|
||||
return ChvConfig(
|
||||
binary=binary, kernel=kernel, rootfs=rootfs, cpus=cpus,
|
||||
memory=memory, state_dir=state_dir, ssh_key=ssh_key,
|
||||
ssh_user=ssh_user, ssh_port=ssh_port,
|
||||
guest_host_py=guest_host_py, cmdline=cmdline, net=net,
|
||||
net_json=net_json, kvm_path=kvm_path,
|
||||
cpuinfo_path=cpuinfo_path, ch_remote=ch_remote, model=model,
|
||||
max_vms=max_vms, disk_mode=disk_mode, disk_gb=disk_gb,
|
||||
egress_proxy=egress_proxy,
|
||||
allow_direct_egress=allow_direct_egress,
|
||||
task_timeout=task_timeout, snapshot_url=snapshot_url,
|
||||
warm_pool_size=warm_pool_size, api_timeout=api_timeout,
|
||||
ready_timeout=ready_timeout, ready_interval=ready_interval)
|
||||
Reference in new issue
Block a user