part 4 附带:vm.boot/vm.create 500 记响应体
_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。
This commit is contained in:
1 parent
7a227323f2
commit
f20cbc79cd
2 files changed
+55
-4
No files matched your search
+19
-4
@@ -1227,6 +1227,19 @@ class CloudHypervisorExecutor(Executor):
|
|||||||
return normalize_api_result(
|
return normalize_api_result(
|
||||||
self.api_client(socket_path, method, path, payload))
|
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):
|
def _call_runner(self, cmd, timeout=None):
|
||||||
"""runner 调用(共享 helper 在 cloud/sandbox.py):
|
"""runner 调用(共享 helper 在 cloud/sandbox.py):
|
||||||
timeout 给定时优先传 timeout,假 runner 只接受 cmd 时
|
timeout 给定时优先传 timeout,假 runner 只接受 cmd 时
|
||||||
@@ -1869,13 +1882,14 @@ class CloudHypervisorExecutor(Executor):
|
|||||||
self._launch_api_only(task)
|
self._launch_api_only(task)
|
||||||
self.wait_api_ready(task)
|
self.wait_api_ready(task)
|
||||||
self._check_api_socket_permissions(socket_path)
|
self._check_api_socket_permissions(socket_path)
|
||||||
status, _ = self._api_call(
|
status, body = self._api_call(
|
||||||
socket_path, "PUT", "/vm.create",
|
socket_path, "PUT", "/vm.create",
|
||||||
self.build_vm_config(task_disk, seed_disk=seed_disk))
|
self.build_vm_config(task_disk, seed_disk=seed_disk))
|
||||||
if status not in (200, 204):
|
if status not in (200, 204):
|
||||||
raise ChvFailure(
|
raise ChvFailure(
|
||||||
"vm_create_failed",
|
"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,
|
self._emit("vm_create", task,
|
||||||
"vm=%s disk=%s" % (name, task_disk))
|
"vm=%s disk=%s" % (name, task_disk))
|
||||||
if net_entries:
|
if net_entries:
|
||||||
@@ -1886,11 +1900,12 @@ class CloudHypervisorExecutor(Executor):
|
|||||||
"net_registry_write_failed",
|
"net_registry_write_failed",
|
||||||
"vm=%s net 登记写失败,拒绝启动(缺登记会"
|
"vm=%s net 登记写失败,拒绝启动(缺登记会"
|
||||||
"漏判 tap/mac/ip 冲突)" % name)
|
"漏判 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):
|
if status not in (200, 204):
|
||||||
raise ChvFailure(
|
raise ChvFailure(
|
||||||
"vm_boot_failed",
|
"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._emit("vm_boot", task, "vm=%s" % name)
|
||||||
self.wait_guest_ready(task)
|
self.wait_guest_ready(task)
|
||||||
# V-9 移植:密钥已投递时等 guest-env 对 SSH 用户可读再报
|
# V-9 移植:密钥已投递时等 guest-env 对 SSH 用户可读再报
|
||||||
|
|||||||
@@ -213,6 +213,42 @@ class WakeFailureTest(CloseupCase):
|
|||||||
with self.assertRaises(ChvFailure) as ctx:
|
with self.assertRaises(ChvFailure) as ctx:
|
||||||
ex.wake_vm(self._task())
|
ex.wake_vm(self._task())
|
||||||
self.assertEqual(ctx.exception.kind, "vm_boot_failed")
|
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.assertIn("vm_failed", self._audit_events())
|
||||||
self.assertEqual(ex._vms, {})
|
self.assertEqual(ex._vms, {})
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user