"""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.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.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)) 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()