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 <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
This commit is contained in:
Philipp Schuster
2025-05-19 11:23:47 +02:00
committed by Rob Bradford
parent 1433763d40
commit 060c9de07f
4 changed files with 43 additions and 5 deletions

View File

@@ -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)
};
}

27
src/lib.rs Normal file
View File

@@ -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:?}");
}

View File

@@ -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
}
};

View File

@@ -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
}