From f20cbc79cd7d149c532282298cb23e492008ada6 Mon Sep 17 00:00:00 2001 From: host Date: Sat, 7 Nov 2026 10:30:45 +0000 Subject: [PATCH] =?UTF-8?q?part=204=20=E9=99=84=E5=B8=A6=EF=BC=9Avm.boot/v?= =?UTF-8?q?m.create=20500=20=E8=AE=B0=E5=93=8D=E5=BA=94=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _boot_vm 只留 status 导致间歇 500 根因无法定位(两轮已提, 未改)。_api_call 的两个调用点现在都把响应体(截断到 500 字符单行)放进 ChvFailure 消息:vm_create_failed 与 vm_boot_failed 均含 status 与 body。body 是 VMM 的报错文本, 不含秘密;超长截断并标明。 新测试:test_wake_vm_boot_failed_records_body、 test_wake_vm_create_failed_records_body、 test_wake_vm_boot_failed_body_truncated。 --- cloud/chv/executor.py | 23 +++++++++++++++++---- cloud/tests/test_chv_closeup.py | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/cloud/chv/executor.py b/cloud/chv/executor.py index 3346196..1124d0a 100644 --- a/cloud/chv/executor.py +++ b/cloud/chv/executor.py @@ -1227,6 +1227,19 @@ class CloudHypervisorExecutor(Executor): return normalize_api_result( self.api_client(socket_path, method, path, payload)) + @staticmethod + def _truncate_api_body(body, limit=500): + """把 API 响应体压成单行短串(放进失败消息里定位用)。 + vm.create/vm.boot 的错误体是 VMM 的报错文本,不含秘密; + 超长即截断并标明,避免日志被巨体淹没。""" + if body is None: + return "" + text = body if isinstance(body, str) else repr(body) + text = " ".join(text.split()) + if len(text) > limit: + return text[:limit] + "…(已截断)" + return text + def _call_runner(self, cmd, timeout=None): """runner 调用(共享 helper 在 cloud/sandbox.py): timeout 给定时优先传 timeout,假 runner 只接受 cmd 时 @@ -1869,13 +1882,14 @@ class CloudHypervisorExecutor(Executor): self._launch_api_only(task) self.wait_api_ready(task) self._check_api_socket_permissions(socket_path) - status, _ = self._api_call( + status, body = self._api_call( socket_path, "PUT", "/vm.create", self.build_vm_config(task_disk, seed_disk=seed_disk)) if status not in (200, 204): raise ChvFailure( "vm_create_failed", - "vm.create 失败:status=%s" % status) + "vm.create 失败:status=%s body=%s" + % (status, self._truncate_api_body(body))) self._emit("vm_create", task, "vm=%s disk=%s" % (name, task_disk)) if net_entries: @@ -1886,11 +1900,12 @@ class CloudHypervisorExecutor(Executor): "net_registry_write_failed", "vm=%s net 登记写失败,拒绝启动(缺登记会" "漏判 tap/mac/ip 冲突)" % name) - status, _ = self._api_call(socket_path, "PUT", "/vm.boot") + status, body = self._api_call(socket_path, "PUT", "/vm.boot") if status not in (200, 204): raise ChvFailure( "vm_boot_failed", - "vm.boot 失败:status=%s" % status) + "vm.boot 失败:status=%s body=%s" + % (status, self._truncate_api_body(body))) self._emit("vm_boot", task, "vm=%s" % name) self.wait_guest_ready(task) # V-9 移植:密钥已投递时等 guest-env 对 SSH 用户可读再报 diff --git a/cloud/tests/test_chv_closeup.py b/cloud/tests/test_chv_closeup.py index 9cd94af..bce9c49 100644 --- a/cloud/tests/test_chv_closeup.py +++ b/cloud/tests/test_chv_closeup.py @@ -213,6 +213,42 @@ class WakeFailureTest(CloseupCase): with self.assertRaises(ChvFailure) as ctx: ex.wake_vm(self._task()) self.assertEqual(ctx.exception.kind, "vm_boot_failed") + + def test_wake_vm_boot_failed_records_body(self): + # part 4:vm.boot 500 只留 status 即无法定位——body 要进 + # 失败消息(截断后,见 _truncate_api_body)。 + def api(socket_path, method, path, payload=None): + if method == "PUT" and path == "/vm.boot": + return (500, {"error": "boom-detail-500"}) + return (200, {}) + ex = self.make_exec(api_client=api) + with self.assertRaises(ChvFailure) as ctx: + ex.wake_vm(self._task()) + self.assertEqual(ctx.exception.kind, "vm_boot_failed") + self.assertIn("boom-detail-500", str(ctx.exception)) + self.assertIn("status=500", str(ctx.exception)) + + def test_wake_vm_create_failed_records_body(self): + def api(socket_path, method, path, payload=None): + if method == "PUT" and path == "/vm.create": + return (500, "No bootitem named default") + return (200, {}) + ex = self.make_exec(api_client=api) + with self.assertRaises(ChvFailure) as ctx: + ex.wake_vm(self._task()) + self.assertEqual(ctx.exception.kind, "vm_create_failed") + self.assertIn("No bootitem named default", str(ctx.exception)) + + def test_wake_vm_boot_failed_body_truncated(self): + def api(socket_path, method, path, payload=None): + if method == "PUT" and path == "/vm.boot": + return (500, {"error": "x" * 2000}) + return (200, {}) + ex = self.make_exec(api_client=api) + with self.assertRaises(ChvFailure) as ctx: + ex.wake_vm(self._task()) + self.assertIn("已截断", str(ctx.exception)) + self.assertLess(len(str(ctx.exception)), 1200) self.assertIn("vm_failed", self._audit_events()) self.assertEqual(ex._vms, {})