use thiserror::Error;
#[derive(Debug, Error)]
pub enum VmmError {
#[error("VM not found: {0}")]
NotFound(String),
#[error("VM already exists: {0}")]
AlreadyExists(String),
#[error("VM '{id}' is in wrong state: expected {expected}, got {actual}")]
WrongState {
id: String,
expected: String,
actual: String,
},
#[error("sandbox '{0}' is paused")]
Paused(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("fc-sdk error: {0}")]
Sdk(#[from] fc_sdk::Error),
#[error("network error: {0}")]
Network(String),
#[error("snapshot error: {0}")]
Snapshot(String),
#[error("device-mapper error: {0}")]
DeviceMapper(String),
#[error("process error: {0}")]
Process(String),
#[error("configuration error: {0}")]
Config(String),
#[error("vsock error: {0}")]
Vsock(String),
#[error("path not found: {0}")]
PathNotFound(String),
#[error("not a directory: {0}")]
NotADirectory(String),
#[error("directory not empty: {0}")]
DirectoryNotEmpty(String),
#[error("template not found: {0}")]
TemplateNotFound(String),
#[error("template version already exists: {0}")]
TemplateVersionExists(String),
#[error("failed precondition: {0}")]
FailedPrecondition(String),
#[error("deadline exceeded: {0}")]
DeadlineExceeded(String),
#[error("service unavailable: {0}")]
Unavailable(String),
#[error("sandbox {id} committed, but ACK durability is unconfirmed: {detail}")]
AckUnconfirmed {
id: String,
detail: String,
},
#[error("stdin offset {offset} is past the {accepted} accepted bytes")]
StdinGap {
accepted: u64,
offset: u64,
},
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, VmmError>;
impl From<arcbox_snapshot::SnapshotError> for VmmError {
fn from(err: arcbox_snapshot::SnapshotError) -> Self {
use arcbox_error::CommonError;
use arcbox_snapshot::SnapshotError as S;
match err {
S::Common(CommonError::Io(io)) => Self::Io(io),
S::Common(CommonError::Config(msg)) => Self::Config(msg),
S::Common(CommonError::NotFound(msg)) => Self::NotFound(msg),
S::Common(CommonError::AlreadyExists(msg)) => Self::AlreadyExists(msg),
S::Common(other) => Self::Other(other.to_string()),
S::Snapshot(msg) => Self::Snapshot(msg),
S::DeviceMapper(msg) => Self::DeviceMapper(msg),
S::TemplateNotFound(msg) => Self::TemplateNotFound(msg),
S::TemplateVersionExists(msg) => Self::TemplateVersionExists(msg),
S::FailedPrecondition(msg) => Self::FailedPrecondition(msg),
S::Unavailable(msg) => Self::Unavailable(msg),
}
}
}
impl From<arcbox_atomic_file::AtomicWriteError> for VmmError {
fn from(err: arcbox_atomic_file::AtomicWriteError) -> Self {
use arcbox_atomic_file::AtomicWriteError;
match err {
AtomicWriteError::NotCommitted { source, .. } => Self::Io(source),
error @ AtomicWriteError::DurabilityUncertain { .. } => {
Self::Unavailable(error.to_string())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_not_found_display() {
let e = VmmError::NotFound("vm-123".into());
assert_eq!(e.to_string(), "VM not found: vm-123");
}
#[test]
fn test_already_exists_display() {
let e = VmmError::AlreadyExists("my-vm".into());
assert_eq!(e.to_string(), "VM already exists: my-vm");
}
#[test]
fn test_wrong_state_display() {
let e = VmmError::WrongState {
id: "vm-1".into(),
expected: "running".into(),
actual: "stopped".into(),
};
let s = e.to_string();
assert!(s.contains("vm-1"));
assert!(s.contains("running"));
assert!(s.contains("stopped"));
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let vmm_err = VmmError::from(io_err);
assert!(matches!(vmm_err, VmmError::Io(_)));
assert!(vmm_err.to_string().contains("I/O error"));
}
#[test]
fn test_network_error_display() {
let e = VmmError::Network("TAP creation failed".into());
assert_eq!(e.to_string(), "network error: TAP creation failed");
}
}