From 060c9de07fe6670135f6d577e3a217652c6b3995 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 19 May 2025 11:23:47 +0200 Subject: [PATCH] vmm: introduce nice error messages on exit (CHV and ch-remote) With the foundations of each error type implementing std::error::Error, we can now nicely walk the `.source()` chain and print an error trace. This commit introduces improved user-facing error printing when: - Cloud Hypervisor fails with an error - ch-remote fails (client error) - ch-remote fails (remote error) The additional context is a clear improvement in UX for both users and developers. In the following example, the new behaviour is shown for a direct invocation of Cloud Hypervisor leading to a failure. This looks similar for ch-remote. ``` Old Style `target/release/cloud-hypervisor --api-socket /tmp/chv2.sock --kernel /etc/bootitems/linux/kernel_minimal/stable.bzImage --cmdline console=ttyS0 --serial tty --console off --disk path=img.raw --initramfs /etc/bootitems/linux/initrd_minimal/default` Error booting VM: VmBoot(LockingError(BlockError(LockDiskImage(AlreadyLocked))) ``` ``` `target/release/cloud-hypervisor --api-socket /tmp/chv2.sock --kernel /etc/bootitems/linux/kernel_minimal/stable.bzImage --cmdline console=ttyS0 --serial tty --console off --disk path=img.raw --initramfs /etc/bootitems/linux/initrd_minimal/default` Error: Cloud Hypervisor exited with the following chain of errors: 0: Error booting VM 1: The VM could not boot 2: Error locking disk images: Another instance likely holds a lock 3: Cannot lock images of all block devices 4: Failed to get Write lock for disk image: ./img.raw 5: The file is already locked Debug Info: VmBoot(VmBoot(LockingError(DiskLockError(LockDiskImage { error: AlreadyLocked, lock_type: Write, path: "./raw_disk.bin" }))) ``` Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- src/bin/ch-remote.rs | 4 ++-- src/lib.rs | 27 +++++++++++++++++++++++++++ src/main.rs | 4 ++-- vmm/src/api/http/mod.rs | 13 ++++++++++++- 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 src/lib.rs diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index f0c9b5e09..4ef0b0257 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -1131,8 +1131,8 @@ fn main() { } }; - if let Err(e) = target_api.do_command(&matches) { - eprintln!("Error running command: {e}"); + if let Err(top_error) = target_api.do_command(&matches) { + cloud_hypervisor::cli_print_error_chain(&top_error, "ch-remote"); process::exit(1) }; } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..ec941ea52 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +// Copyright © 2025 Cyberus Technology GmbH +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Prints a chain of errors to the user in a consistent manner. +/// The user will see a clear chain of errors, followed by debug output +/// for opening issues. +pub fn cli_print_error_chain(top_error: &dyn std::error::Error, component: &str) { + eprint!("Error: {component} exited with the following "); + if top_error.source().is_none() { + eprintln!("error:"); + eprintln!(" {top_error}"); + } else { + eprintln!("chain of errors:"); + std::iter::successors(Some(top_error), |sub_error| { + sub_error.source() + }) + .enumerate() + .for_each(|(level, error)| { + eprintln!(" {level}: {error}",); + }); + } + + eprintln!(); + eprintln!("Debug Info: {top_error:?}"); +} diff --git a/src/main.rs b/src/main.rs index 0520dcf7c..924cb19c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -883,8 +883,8 @@ fn main() { path.map(|s| std::fs::remove_file(s).ok()); 0 } - Err(e) => { - eprintln!("{e}"); + Err(top_error) => { + cloud_hypervisor::cli_print_error_chain(&top_error, "Cloud Hypervisor"); 1 } }; diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index 3493a3134..9b2f5d1d8 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -69,9 +69,20 @@ pub enum HttpError { const HTTP_ROOT: &str = "/api/v1"; +/// Creates the error response's body meant to be sent back to an API client. +/// The error message contained in the response is supposed to be user-facing, +/// thus insightful and helpful while balancing technical accuracy and +/// simplicity. pub fn error_response(error: HttpError, status: StatusCode) -> Response { let mut response = Response::new(Version::Http11, status); - response.set_body(Body::new(format!("{error}"))); + // We must use debug output here without `#`, as it is currently the only + // feasible option to get all relevant error details to the receiver, + // i.e., ch-remote, in a balanced form. The Display impl is not guaranteed + // to hold all relevant or helpful data. + // + // TODO: We might print a nice error chain here as well and send it to the + // remote, similar to the normal error reporting? + response.set_body(Body::new(format!("{error:?}"))); response }