架构:WarmPool 从 cloud/chv/pool.py 移到 experimental_pool.py, 生产残留清理彻底分文件到 cloud/chv/orphans.py(facade 直接读 registry 文件清理,不构造池、不碰 prewarm/acquire/release); cloud/chv 与 cloud/sandbox 不再导出 WarmPool,旧路径一律 ImportError(CHV 未合并、无生产用户,兼容保留已无保留理由); worker 改走 orphans facade,不再持有 _warm_pool;#215 对齐从 接口形状推进到 REPLACEMENT_POINTS 替换清单(R1/R2 可机械替换、 R3 永不替换,#215 模块当前不存在由测试钉住)。 可维护性:build_chv_config 拆成 base/ssh/network/quota/disk/ timeout 六片段(组装只做 dict 合并,键冲突即报错;38 字段集 与取值不变,单项非法报错文案逐字一致);ADR-12 模块职责图 与禁 import 清单(AST 钉住);cloud/chv/ 公开 API 全量类型 标注、无 Any 逃逸,并修正三处旧标注错误;死代码扫一遍 (删实验池已无调用的 _quarantine 委托与未用 _LOGGER)。 前端(只读侧,不做控制按钮):chv.ts 新增纯逻辑谓词与文案 (执行器精确匹配、附件守卫、VM 状态文案、列表徽标);列表 行新增 VM 已报告死亡徽标;详情对全部 CHV 任务显示 VM 行 (已报告死亡/未报告死亡,不猜 live);WorkersSection 与成本 卡沿用已有数据,不新开池管理/成本页;服务端未新增第二个 生命周期字段(持久化事实只有三项,chv_vm_dead 已是完整派生), 只把派生抽成 _chv_vm_dead_from_row 并补 goal 回落形态测试。
106 lines
4.7 KiB
TypeScript
106 lines
4.7 KiB
TypeScript
import type { Worker } from "./types";
|
||
import { workerAgeText } from "./client";
|
||
|
||
// CHV 可用性纯逻辑:不发请求、不拼 URL,只从 ctx 已有的
|
||
// /workers 数据推断。非管理员的 /workers 只返回聚合数
|
||
// (workers_total/online/offline),没有按执行器的分项,
|
||
// 此时 CHV 在线数是未知,绝不能把未知写成 0,也绝不能
|
||
// 把聚合在线数当成 CHV 在线数。
|
||
export function isWorkerAlive(w: Worker): boolean {
|
||
if (w.alive !== undefined && w.alive !== null) return !!w.alive;
|
||
const info = workerAgeText(w);
|
||
return info.age !== null && info.age <= 120;
|
||
}
|
||
|
||
export function countChvWorkers(workers: Worker[]): { online: number; total: number } {
|
||
const chv = workers.filter((w) => w.executor === "cloud-hypervisor");
|
||
return { online: chv.filter(isWorkerAlive).length, total: chv.length };
|
||
}
|
||
|
||
export type ChvAvailability =
|
||
| { state: "known"; online: number; total: number }
|
||
| { state: "unknown"; reason: "not-loaded" | "aggregate-only" | "load-failed" };
|
||
|
||
export function chvAvailability(
|
||
workers: Worker[] | null,
|
||
workersSummary: string,
|
||
workersError: string,
|
||
): ChvAvailability {
|
||
if (workers) return { state: "known", ...countChvWorkers(workers) };
|
||
if (workersError) return { state: "unknown", reason: "load-failed" };
|
||
if (workersSummary.includes("无 worker 注册")) return { state: "known", online: 0, total: 0 };
|
||
if (workersSummary.includes("完整 worker 清单仅全局管理员可见")) {
|
||
return { state: "unknown", reason: "aggregate-only" };
|
||
}
|
||
return { state: "unknown", reason: "not-loaded" };
|
||
}
|
||
|
||
// 执行器选择谓词:派发下拉与列表筛选都以字符串相等为准,
|
||
// 不做大小写/前缀宽松匹配(后端白名单是精确值,见 server
|
||
// POST /tasks 的 executor 白名单;宽松匹配会把非法值放进
|
||
// “CHV 任务”分支,造成前端提示与后端拒绝不一致)。
|
||
export function isChvExecutor(executor: string | undefined | null): boolean {
|
||
return executor === "cloud-hypervisor";
|
||
}
|
||
|
||
// 附件守卫谓词:CHV + 带附件 → 后端建任务即拒绝(server
|
||
// POST /tasks 的附件绑定分支,错误文案“附件不支持
|
||
// cloud-hypervisor 执行器”)。本函数只作前端提示口径,
|
||
// 绝不代替后端白名单:前端照常提交,后端拒绝为准。
|
||
export function chvAttachmentBlocked(
|
||
executor: string | undefined | null,
|
||
hasAttachments: boolean,
|
||
): boolean {
|
||
return isChvExecutor(executor) && hasAttachments;
|
||
}
|
||
|
||
// VM 只读状态文案:输入只有后端已返回的两个字段,输出
|
||
// 三种情形之一,绝不编造第四种:
|
||
// - chv_vm_dead 为 true → 已报告死亡(来源:worker 终局
|
||
// 写回的 interrupted_reason,见 server _chv_vm_dead_from_row);
|
||
// - CHV 任务但无该字段 → 未报告死亡,且明写“后端无 live
|
||
// 状态,不代表 VM 活着”(server 没有 VM 登记表);
|
||
// - 非 CHV 任务 → 空串(不显示 VM 行)。
|
||
export function chvVmStatusText(task: {
|
||
executor?: string | null;
|
||
chv_vm_dead?: boolean;
|
||
}): string {
|
||
// 死亡文案同样先过执行器门:后端只对 CHV 任务派生该字段,
|
||
// 万一某天非 CHV 行误带该字段,前端也不把 docker 任务
|
||
// 写成“VM 死亡”。
|
||
if (task.chv_vm_dead && isChvExecutor(task.executor)) {
|
||
return "VM 中途死亡,任务未完成、断点可续跑(后端只读字段 chv_vm_dead)";
|
||
}
|
||
if (isChvExecutor(task.executor)) {
|
||
return "未报告 VM 死亡(后端无 live 状态字段,不代表 VM 活着)";
|
||
}
|
||
return "";
|
||
}
|
||
|
||
// 列表行徽标:只有已报告死亡时才出现短徽标;未报告时
|
||
// 列表行不写 VM 状态(详情里才写“未报告”长文案,避免
|
||
// 列表行被未知状态刷屏)。
|
||
export function chvVmBadge(task: {
|
||
executor?: string | null;
|
||
chv_vm_dead?: boolean;
|
||
}): string {
|
||
return task.chv_vm_dead && isChvExecutor(task.executor)
|
||
? "VM 已报告死亡"
|
||
: "";
|
||
}
|
||
|
||
// 排队原因提示:只在“已知零 CHV worker 在线 + CHV 任务排队”时
|
||
// 给出,文案明确写“可能原因”,且不编造后端未返回的
|
||
// VM 状态/IP/快照。未知时返回空串(不提示、不猜测)。
|
||
export function chvQueueHint(
|
||
executor: string | undefined | null,
|
||
status: string | undefined | null,
|
||
availability: ChvAvailability,
|
||
): string {
|
||
if (executor !== "cloud-hypervisor" || status !== "queued") return "";
|
||
if (availability.state === "known" && availability.online === 0) {
|
||
return "可能原因:当前无 cloud-hypervisor worker 在线,任务在排队(基于 /workers 推断;VM 状态/IP/快照等后端未返回字段不展示)。请先确认 KVM 宿主上的 CHV worker 已起,worker 上线后可认领。";
|
||
}
|
||
return "";
|
||
}
|