From 4f3a176771eea293609e78b819364815a40d98e4 Mon Sep 17 00:00:00 2001 From: host Date: Fri, 6 Nov 2026 17:59:53 +0000 Subject: [PATCH] =?UTF-8?q?fix(chv):=20CHV=2054=20VmConfig=20=E5=BD=A2?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94payload.kernel/cmdline=20=E7=BA=AF?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=20+=20=E7=A3=81=E7=9B=98=E6=98=BE?= =?UTF-8?q?=E5=BC=8F=20image=5Ftype=20Raw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机 CHV 54.0.0 实测:旧顶层 kernel:{path}/cmdline:{args} create 时 204 照放、vm.boot 400 'No bootitem provided'; 补 payload 后又报 'Image type required for disk'(raw 盘 无魔数、探测失败),磁盘须显式 image_type,枚举原名 Raw(JSON 写小写 raw 报 unknown variant)。 --- cloud/chv/executor.py | 33 ++++++++++++++++++++++------ cloud/tests/test_chv_arch95.py | 2 +- cloud/tests/test_chv_fix.py | 3 +++ cloud/tests/test_cloud_hypervisor.py | 12 ++++++---- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/cloud/chv/executor.py b/cloud/chv/executor.py index efff2f1..415c7a9 100644 --- a/cloud/chv/executor.py +++ b/cloud/chv/executor.py @@ -965,7 +965,14 @@ class CloudHypervisorExecutor(Executor): """VmConfig 磁盘列表(to10 提取,含 L2 白名单校验)。 任务盘 + 可选 seed 只读盘(CIDATA ISO,cloud-init NoCloud 数据源);显式给的盘若不是基础镜像本身, - 须在 state_dir 内,否则 VmConfig 不能挂进宿主任意文件。""" + 须在 state_dir 内,否则 VmConfig 不能挂进宿主任意文件。 + image_type 必须显式给 Raw:CHV 54 的磁盘类型靠文件 + 魔数探测,raw 盘无魔数、探测失败即 vm.boot 400 + "Image type required for disk"(2026-11-07 WSL 真机 + CHV 54.0.0 实测);值须是枚举原名 Raw(CLI 的小写 + raw 是另一层映射,JSON 里写 raw 会报 unknown variant); + CLI 各轮一直传 image_type=raw(seed ISO 亦然,客内 + 仍以 /dev/vdb 读到 CIDATA)。""" # 旧文案逐字保留:任务盘“任务磁盘须在…”,seed 盘 # “seed 磁盘须在…”(label 本身含“磁盘”与空格)。 for path, label in ((task_disk, "任务磁盘"), @@ -976,28 +983,40 @@ class CloudHypervisorExecutor(Executor): raise ChvFailure( "disk_path_not_allowed", "%s须在 state_dir 内:%s" % (label, path)) - disks: list = [{"path": task_disk or self.rootfs}] + disks: list = [{"path": task_disk or self.rootfs, + "image_type": "Raw"}] if seed_disk: - disks.append({"path": seed_disk, "readonly": True}) + disks.append({"path": seed_disk, "readonly": True, + "image_type": "Raw"}) return disks def build_vm_config(self, task_disk: str | None = None, seed_disk: str | None = None) -> dict: """API 模式的 VmConfig(vm.create 请求体,纯函数): cpus{boot_vcpus,max_vcpus}、memory{size: 字节}、 - kernel{path}、cmdline{args}、disks[{path: 每任务磁盘}]、 + payload{kernel, cmdline}、disks[{path: 每任务磁盘}]、 rng{/dev/urandom},以及 CHV_NET_JSON 给出的 net 列表 (不给则不带 net——客机无网卡时 SSH 就绪会超时失败, 这是 fail-fast 前置条件,见类 docstring)。 - task_disk 不给时用基础镜像路径(仅 lab/reuse 模式)。""" + task_disk 不给时用基础镜像路径(仅 lab/reuse 模式)。 + payload 形态(CHV 54 API):内核与命令行须放在 + payload 对象里(kernel/cmdline 均为直接字符串), + 旧版顶层 kernel{path}/cmdline{args} 会被服务端忽略, + vm.create 照常 204、vm.boot 报 400 "No bootitem + provided: neither firmware nor kernel"(2026-11-07 + WSL 真机 CHV 54.0.0 实测)。""" try: cpus = int(self.cpus) except ValueError: raise ValueError("CHV_CPU 必须是整数:%r" % (self.cpus,)) + payload: dict = {} + if self.kernel: + payload["kernel"] = self.kernel + if self.cmdline: + payload["cmdline"] = self.cmdline config = { "cpus": {"boot_vcpus": cpus, "max_vcpus": cpus}, "memory": {"size": _parse_memory_bytes(self.memory)}, - "kernel": {"path": self.kernel}, - "cmdline": {"args": self.cmdline}, + "payload": payload, "disks": self._vm_config_disks(task_disk, seed_disk), "rng": {"src": "/dev/urandom"}, } diff --git a/cloud/tests/test_chv_arch95.py b/cloud/tests/test_chv_arch95.py index 706b543..6454e7b 100644 --- a/cloud/tests/test_chv_arch95.py +++ b/cloud/tests/test_chv_arch95.py @@ -111,7 +111,7 @@ class Arch95Case(unittest.TestCase): "第一盘必须是任务盘") self.assertTrue(disks[1].endswith("seed.iso"), "第二盘必须是 seed 盘,缺 seed 即假保证") - self.assertTrue(payload.get("kernel", {}).get("path")) + self.assertTrue(payload.get("payload", {}).get("kernel")) nets = payload.get("net", []) taps = [n.get("tap") for n in nets] self.assertEqual(len(taps), len(set(taps)), "net tap 重复") diff --git a/cloud/tests/test_chv_fix.py b/cloud/tests/test_chv_fix.py index 39b212e..2123390 100644 --- a/cloud/tests/test_chv_fix.py +++ b/cloud/tests/test_chv_fix.py @@ -158,6 +158,9 @@ class SeedDiskTest(ChvFixCase): self.assertEqual(len(cfg["disks"]), 2) self.assertEqual(cfg["disks"][1]["path"], seed) self.assertTrue(cfg["disks"][1]["readonly"]) + # CHV 54 要求磁盘显式 image_type(缺了 vm.boot 400)。 + self.assertEqual(cfg["disks"][0]["image_type"], "Raw") + self.assertEqual(cfg["disks"][1]["image_type"], "Raw") cfg2 = ex.build_vm_config(disk) self.assertEqual(len(cfg2["disks"]), 1) diff --git a/cloud/tests/test_cloud_hypervisor.py b/cloud/tests/test_cloud_hypervisor.py index fd3d062..65efe9b 100644 --- a/cloud/tests/test_cloud_hypervisor.py +++ b/cloud/tests/test_cloud_hypervisor.py @@ -243,9 +243,10 @@ class ConfigCommandTest(ChvCase): self.assertEqual(cfg["cpus"], {"boot_vcpus": 2, "max_vcpus": 2}) self.assertEqual(cfg["memory"]["size"], 2 * 1024 ** 3) - self.assertEqual(cfg["kernel"]["path"], self.kernel) - self.assertEqual(cfg["disks"], [{"path": disk}]) - self.assertEqual(cfg["cmdline"]["args"], + self.assertEqual(cfg["payload"]["kernel"], self.kernel) + self.assertEqual(cfg["disks"], + [{"path": disk, "image_type": "Raw"}]) + self.assertEqual(cfg["payload"]["cmdline"], "console=hvc0 root=/dev/vda1 rw") def test_memory_parsing(self): @@ -346,7 +347,10 @@ class WakeApiTest(ChvCase): self.assertEqual(kinds[1], ("PUT", "/vm.create")) self.assertEqual(kinds[2], ("PUT", "/vm.boot")) create_payload = self.api_calls[1][2] - self.assertEqual(create_payload["kernel"]["path"], self.kernel) + # CHV 54:内核走 payload.kernel 纯字符串(旧顶层 + # kernel:{path} 会被忽略、vm.boot 400)。 + self.assertEqual(create_payload["payload"]["kernel"], self.kernel) + self.assertEqual(create_payload["disks"][0]["image_type"], "Raw") self.assertTrue(create_payload["disks"][0]["path"].endswith( "disk.raw")) events = [e for e, _, _, _ in self.audit]