Files
codex-app-server/cloud/tests/test_chv_arch95.py
T

262 lines
10 KiB
Python
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.
"""CHV 架构 9.5 提升轨测试(feat-chv-arch95,堆叠于 #246)。
只用标准库与 mock,绝不启动真实 VM。本文件是“严格测试替身”
的载体:替身不再无条件放行——fake API 校验 VmConfig 内容
(任务盘 + seed 盘、net 条目),fake SSH 校验 known_hosts
钉死与 StrictHostKeyChecking=yes,mock 再也不能把被测逻辑
本身也 mock 掉。测试里只用 FAKE 占位密钥,绝无真实密钥。
"""
import os
import sys
import tempfile
import unittest
from pathlib import Path
try:
from cloud.chv.experimental_pool import WarmPool
from cloud.sandbox import (
ChvFailure,
CloudHypervisorExecutor,
DockerExecutor,
parse_goal_result,
)
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.chv.experimental_pool import WarmPool
from cloud.sandbox import (
ChvFailure,
CloudHypervisorExecutor,
DockerExecutor,
parse_goal_result,
)
class FakeResult:
def __init__(self, returncode=0, stdout="", stderr=""):
self.returncode = returncode
self.stdout = stdout
self.stderr = stderr
class FakeProc:
def __init__(self, alive=True):
self._alive = alive
def poll(self):
return None if self._alive else 1
def terminate(self):
self._alive = False
def wait(self, timeout=None):
return 0
def kill(self):
self._alive = False
def _write(path, content=b"x"):
with open(path, "wb") as fh:
fh.write(content)
os.chmod(path, 0o755)
return path
class Arch95Case(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.binary = _write(os.path.join(self.dir, "cloud-hypervisor"))
self.ch_remote = _write(os.path.join(self.dir, "ch-remote"))
self.kernel = _write(os.path.join(self.dir, "vmlinux"))
self.rootfs = _write(os.path.join(self.dir, "disk.raw"), b"R" * 100)
self.ssh_key = _write(os.path.join(self.dir, "id_chv"))
self.kvm = _write(os.path.join(self.dir, "kvm"))
self.cpuinfo = os.path.join(self.dir, "cpuinfo")
with open(self.cpuinfo, "w") as fh:
fh.write("flags : fpu vmx sse\n")
self.state_dir = os.path.join(self.dir, "state")
self.calls = []
self.audit = []
self.api_payloads = []
def tearDown(self):
self.tmp.cleanup()
def _runner(self, cmd, timeout=None):
self.calls.append(list(cmd))
if cmd and cmd[0] == "ssh-keygen":
priv = cmd[cmd.index("-f") + 1]
with open(priv, "w") as fh:
fh.write("-----BEGIN OPENSSH PRIVATE KEY-----\nFAKE\n")
os.chmod(priv, 0o600)
with open(priv + ".pub", "w") as fh:
fh.write("ssh-ed25519 AAAAfake1 vm-key\n")
return FakeResult(0, "", "")
if cmd and cmd[0] == "genisoimage":
out = cmd[cmd.index("-output") + 1]
with open(out, "wb") as fh:
fh.write(b"CIDATA" * 16)
return FakeResult(0, "", "")
return FakeResult(0, "", "")
def _strict_api(self, socket_path, method, path, payload=None):
"""严格替身:vm.create 必须带任务盘 + seed 盘、net 无重复。"""
if path == "/vm.create":
self.api_payloads.append(payload)
disks = [d.get("path") for d in payload.get("disks", [])]
self.assertEqual(len(disks), 2, "VmConfig 必须恰好两盘")
self.assertTrue(disks[0].endswith("disk.raw"),
"第一盘必须是任务盘")
self.assertTrue(disks[1].endswith("seed.iso"),
"第二盘必须是 seed 盘,缺 seed 即假保证")
self.assertTrue(payload.get("kernel", {}).get("path"))
nets = payload.get("net", [])
taps = [n.get("tap") for n in nets]
self.assertEqual(len(taps), len(set(taps)), "net tap 重复")
return (200, {})
def make_exec(self, api=None, runner=None, env_extra=None):
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
"CHV_SSH_FORWARD": "1"}
if env_extra:
env.update(env_extra)
ex = CloudHypervisorExecutor(
binary=self.binary, ch_remote=self.ch_remote,
kernel=self.kernel, rootfs=self.rootfs,
ssh_key=self.ssh_key, kvm_path=self.kvm,
cpuinfo_path=self.cpuinfo, state_dir=self.state_dir,
runner=runner or self._runner,
launcher=lambda cmd: FakeProc(alive=True),
api_client=api or self._strict_api,
sleeper=lambda s: None, env=env)
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
(e, t.get("id") if isinstance(t, dict) else t, d, k))
return ex
class WarmPoolDecisionTest(Arch95Case):
def test_pool_is_experimental_and_dispatch_is_cold_only(self):
self.assertFalse(WarmPool.wired_to_dispatch)
ex = self.make_exec()
pool = WarmPool(ex)
# 手工放一个 warm 条目:dispatch 必须不消耗它(冷启动路径)。
pool._entries["warm-1"] = {"state": "warm",
"task": {"id": "warm-1"},
"vm": "chv-warm-1",
"snapshot_url": None}
ex.wake_vm({"id": "t1"})
self.assertEqual(pool._entries["warm-1"]["state"], "warm")
self.assertEqual(pool.warm_count(), 1)
class StrictVmConfigTest(Arch95Case):
def test_wake_vm_config_has_task_disk_and_seed_disk(self):
ex = self.make_exec()
out = ex.wake_vm({"id": "t1"})
self.assertEqual(out["state"], "running")
self.assertEqual(len(self.api_payloads), 1)
# known_hosts 钉死:文件存在且含预置公钥,SSH 命令带钉死选项。
kh = ex.known_hosts_path({"id": "t1"})
with open(kh) as fh:
content = fh.read()
self.assertIn("AAAAfake1", content)
cmd = ex.build_ssh_command({"id": "t1"}, ["true"])
self.assertIn("StrictHostKeyChecking=yes", " ".join(cmd))
self.assertIn(kh, " ".join(cmd))
def test_strict_ssh_runner_rejects_missing_known_hosts(self):
def strict_runner(cmd, timeout=None):
if cmd and cmd[0] == "ssh":
self.assertIn("StrictHostKeyChecking=yes", cmd)
found = False
for i, a in enumerate(cmd):
if a.startswith("UserKnownHostsFile="):
path = a.split("=", 1)[1]
found = True
elif a == "UserKnownHostsFile":
path = cmd[i + 1]
found = True
else:
continue
with open(path) as fh:
self.assertTrue(fh.read().strip(),
"known_hosts 为空即假钉死")
self.assertTrue(found, "SSH 命令缺 known_hosts 钉死选项")
return self._runner(cmd, timeout=timeout)
ex = self.make_exec(runner=strict_runner)
ex.wake_vm({"id": "t1"})
# 严格 runner 在 _run_guest 里再跑一次 status 风格调用:
# 这里直接验证 build 出的命令能过严格 runner 的检查。
cmd = ex.build_ssh_command({"id": "t1"}, ["true"])
strict_runner(cmd)
class ConfigUnifiedTest(Arch95Case):
def test_preflight_unified_config_invalid(self):
cases = [
("ssh_port", 0), ("ssh_port", 70000),
("max_vms", -1), ("disk_gb", 0),
("api_timeout", 0), ("ready_timeout", -1),
("ready_interval", 0), ("warm_pool_size", -1),
("snapshot_max_count", -1),
("snapshot_max_total_gb", -1),
("snapshot_retention_days", -1),
("host_key_mode", "bogus"),
]
for field, bad in cases:
with self.subTest(field=field, bad=bad):
ex = self.make_exec()
setattr(ex, field, bad)
with self.assertRaises(ChvFailure) as ctx:
ex.preflight()
self.assertEqual(ctx.exception.kind, "config_invalid")
class DockerSyncGuardTest(unittest.TestCase):
"""Docker goal 内联版与正典 parse_goal_result 的同步守卫:
7 个真实标记用例 + 已存在撞名用例,两者状态必须全等;
任一实现改了分叉,测试即 fail(不再只靠文档清单)。"""
def _docker_status(self, stdout, returncode, stderr=""):
old = os.environ.get("XCM_API_KEY")
os.environ["XCM_API_KEY"] = "FAKE-TEST-KEY"
try:
ex = DockerExecutor(runner=lambda cmd, timeout=None:
FakeResult(returncode, stdout, stderr))
# to10:goal 解析是纯函数测试,绝不依赖本机 docker
# 二进制(无 docker 的机器上 _check_docker 会直接炸)。
ex._check_docker = lambda: None
ex._call_runner = lambda cmd, timeout=None, env=None: \
FakeResult(returncode, stdout, stderr)
return ex.goal_task({"id": "t1", "goal": "do it",
"goal_max_rounds": 3})["status"]
finally:
if old is None:
del os.environ["XCM_API_KEY"]
else:
os.environ["XCM_API_KEY"] = old
def test_docker_and_canonical_goal_status_equal(self):
cases = [
("goal met after rounds\n", 0, ""),
("goal unmet after rounds\nround 1\nverify_out=fail\n", 0, ""),
("goal interrupted after rounds: boom\n", 1, ""),
("", 1, "boom"),
("KeyError: breakpoint\n", 1, ""),
("", 0, ""),
("goal-resume 拒绝:breakpoint lost\n", 1, ""),
("任务已存在\n", 1, ""),
]
for stdout, rc, stderr in cases:
with self.subTest(stdout=stdout[:30], rc=rc):
canon = parse_goal_result(
"t1", FakeResult(rc, stdout, stderr), "local")
docker = self._docker_status(stdout, rc, stderr)
self.assertEqual(docker, canon["status"])
if __name__ == "__main__":
unittest.main()