vmm: Clean Error handling up

We used to have errors definitions spread across vmm, vm, api,
and http.

We now have a cleaner separation: All API routines only return an
ApiResult. All VM operations, including the VMM wrappers, return a
VmResult. This makes it easier to carry errors up to the HTTP caller.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Samuel Ortiz 2019-10-01 16:41:50 +02:00
parent 42758244a0
commit 43b3642955
4 changed files with 73 additions and 102 deletions

View File

@ -4,9 +4,10 @@
// //
use crate::api::http::EndpointHandler; use crate::api::http::EndpointHandler;
use crate::api::VmConfig; use crate::api::{
use crate::api::{vm_boot, vm_create, vm_info, vm_reboot, vm_shutdown, ApiRequest, VmAction}; vm_boot, vm_create, vm_info, vm_reboot, vm_shutdown, ApiError, ApiRequest, ApiResult, VmAction,
use crate::{Error, Result}; VmConfig,
};
use micro_http::{Body, Method, Request, Response, StatusCode, Version}; use micro_http::{Body, Method, Request, Response, StatusCode, Version};
use serde_json::Error as SerdeError; use serde_json::Error as SerdeError;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
@ -20,22 +21,22 @@ pub enum HttpError {
SerdeJsonDeserialize(SerdeError), SerdeJsonDeserialize(SerdeError),
/// Could not create a VM /// Could not create a VM
VmCreate(Error), VmCreate(ApiError),
/// Could not boot a VM /// Could not boot a VM
VmBoot(Error), VmBoot(ApiError),
/// Could not get the VM information /// Could not get the VM information
VmInfo(Error), VmInfo(ApiError),
/// Could not shut a VM down /// Could not shut a VM down
VmShutdown(Error), VmShutdown(ApiError),
/// Could not reboot a VM /// Could not reboot a VM
VmReboot(Error), VmReboot(ApiError),
/// Could not act on a VM /// Could not act on a VM
VmAction(Error), VmAction(ApiError),
} }
fn error_response(error: HttpError, status: StatusCode) -> Response { fn error_response(error: HttpError, status: StatusCode) -> Response {
@ -90,7 +91,7 @@ pub struct VmActionHandler {
action_fn: VmActionFn, action_fn: VmActionFn,
} }
type VmActionFn = Box<dyn Fn(EventFd, Sender<ApiRequest>) -> Result<()> + Send + Sync>; type VmActionFn = Box<dyn Fn(EventFd, Sender<ApiRequest>) -> ApiResult<()> + Send + Sync>;
impl VmActionHandler { impl VmActionHandler {
pub fn new(action: VmAction) -> Self { pub fn new(action: VmAction) -> Self {
@ -114,9 +115,9 @@ impl EndpointHandler for VmActionHandler {
match req.method() { match req.method() {
Method::Put => { Method::Put => {
match (self.action_fn)(api_notifier, api_sender).map_err(|e| match e { match (self.action_fn)(api_notifier, api_sender).map_err(|e| match e {
Error::ApiVmBoot(_) => HttpError::VmBoot(e), ApiError::VmBoot(_) => HttpError::VmBoot(e),
Error::ApiVmShutdown(_) => HttpError::VmShutdown(e), ApiError::VmShutdown(_) => HttpError::VmShutdown(e),
Error::ApiVmReboot(_) => HttpError::VmReboot(e), ApiError::VmReboot(_) => HttpError::VmReboot(e),
_ => HttpError::VmAction(e), _ => HttpError::VmAction(e),
}) { }) {
Ok(_) => Response::new(Version::Http11, StatusCode::OK), Ok(_) => Response::new(Version::Http11, StatusCode::OK),

View File

@ -38,16 +38,25 @@ pub mod http_endpoint;
use crate::config::VmConfig; use crate::config::VmConfig;
use crate::vm::{Error as VmError, VmState}; use crate::vm::{Error as VmError, VmState};
use crate::{Error, Result}; use std::io;
use std::sync::mpsc::{channel, Sender}; use std::sync::mpsc::{channel, RecvError, SendError, Sender};
use std::sync::Arc; use std::sync::Arc;
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
/// API errors are sent back from the VMM API server through the ApiResponse. /// API errors are sent back from the VMM API server through the ApiResponse.
#[derive(Debug)] #[derive(Debug)]
pub enum ApiError { pub enum ApiError {
/// The VM could not be created. /// Cannot write to EventFd.
VmCreate(VmError), EventFdWrite(io::Error),
/// API request send error
RequestSend(SendError<ApiRequest>),
/// Wrong reponse payload type
ResponsePayloadType,
/// API response receive error
ResponseRecv(RecvError),
/// The VM could not boot. /// The VM could not boot.
VmBoot(VmError), VmBoot(VmError),
@ -55,8 +64,11 @@ pub enum ApiError {
/// The VM is already created. /// The VM is already created.
VmAlreadyCreated, VmAlreadyCreated,
/// The VM could not be created.
VmCreate(VmError),
/// The VM info is not available. /// The VM info is not available.
VmInfo, VmInfo(VmError),
/// The VM config is missing. /// The VM config is missing.
VmMissingConfig, VmMissingConfig,
@ -71,8 +83,9 @@ pub enum ApiError {
VmShutdown(VmError), VmShutdown(VmError),
/// The VM could not reboot. /// The VM could not reboot.
VmReboot, VmReboot(VmError),
} }
pub type ApiResult<T> = std::result::Result<T, ApiError>;
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
pub struct VmInfo { pub struct VmInfo {
@ -122,19 +135,16 @@ pub fn vm_create(
api_evt: EventFd, api_evt: EventFd,
api_sender: Sender<ApiRequest>, api_sender: Sender<ApiRequest>,
config: Arc<VmConfig>, config: Arc<VmConfig>,
) -> Result<()> { ) -> ApiResult<()> {
let (response_sender, response_receiver) = channel(); let (response_sender, response_receiver) = channel();
// Send the VM creation request. // Send the VM creation request.
api_sender api_sender
.send(ApiRequest::VmCreate(config, response_sender)) .send(ApiRequest::VmCreate(config, response_sender))
.map_err(Error::ApiRequestSend)?; .map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(Error::EventFdWrite)?; api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver response_receiver.recv().map_err(ApiError::ResponseRecv)??;
.recv()
.map_err(Error::ApiResponseRecv)?
.map_err(Error::ApiVmCreate)?;
Ok(()) Ok(())
} }
@ -153,7 +163,7 @@ pub enum VmAction {
Reboot, Reboot,
} }
fn vm_action(api_evt: EventFd, api_sender: Sender<ApiRequest>, action: VmAction) -> Result<()> { fn vm_action(api_evt: EventFd, api_sender: Sender<ApiRequest>, action: VmAction) -> ApiResult<()> {
let (response_sender, response_receiver) = channel(); let (response_sender, response_receiver) = channel();
let request = match action { let request = match action {
@ -163,63 +173,51 @@ fn vm_action(api_evt: EventFd, api_sender: Sender<ApiRequest>, action: VmAction)
}; };
// Send the VM request. // Send the VM request.
api_sender.send(request).map_err(Error::ApiRequestSend)?; api_sender.send(request).map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(Error::EventFdWrite)?; api_evt.write(1).map_err(ApiError::EventFdWrite)?;
match action { match action {
VmAction::Boot => { VmAction::Boot => {
response_receiver response_receiver.recv().map_err(ApiError::ResponseRecv)??;
.recv()
.map_err(Error::ApiResponseRecv)?
.map_err(Error::ApiVmBoot)?;
} }
VmAction::Shutdown => { VmAction::Shutdown => {
response_receiver response_receiver.recv().map_err(ApiError::ResponseRecv)??;
.recv()
.map_err(Error::ApiResponseRecv)?
.map_err(Error::ApiVmShutdown)?;
} }
VmAction::Reboot => { VmAction::Reboot => {
response_receiver response_receiver.recv().map_err(ApiError::ResponseRecv)??;
.recv()
.map_err(Error::ApiResponseRecv)?
.map_err(Error::ApiVmReboot)?;
} }
} }
Ok(()) Ok(())
} }
pub fn vm_boot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> Result<()> { pub fn vm_boot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Boot) vm_action(api_evt, api_sender, VmAction::Boot)
} }
pub fn vm_shutdown(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> Result<()> { pub fn vm_shutdown(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Shutdown) vm_action(api_evt, api_sender, VmAction::Shutdown)
} }
pub fn vm_reboot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> Result<()> { pub fn vm_reboot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Reboot) vm_action(api_evt, api_sender, VmAction::Reboot)
} }
pub fn vm_info(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> Result<VmInfo> { pub fn vm_info(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<VmInfo> {
let (response_sender, response_receiver) = channel(); let (response_sender, response_receiver) = channel();
// Send the VM request. // Send the VM request.
api_sender api_sender
.send(ApiRequest::VmInfo(response_sender)) .send(ApiRequest::VmInfo(response_sender))
.map_err(Error::ApiRequestSend)?; .map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(Error::EventFdWrite)?; api_evt.write(1).map_err(ApiError::EventFdWrite)?;
let vm_info = response_receiver let vm_info = response_receiver.recv().map_err(ApiError::ResponseRecv)??;
.recv()
.map_err(Error::ApiResponseRecv)?
.map_err(|_| Error::ApiVmInfo)?;
match vm_info { match vm_info {
ApiResponsePayload::VmInfo(info) => Ok(info), ApiResponsePayload::VmInfo(info) => Ok(info),
_ => Err(Error::ApiVmInfo), _ => Err(ApiError::ResponsePayloadType),
} }
} }

View File

@ -37,30 +37,9 @@ pub enum Error {
/// API request receive error /// API request receive error
ApiRequestRecv(RecvError), ApiRequestRecv(RecvError),
/// API response receive error
ApiResponseRecv(RecvError),
/// API request send error
ApiRequestSend(SendError<ApiRequest>),
/// API response send error /// API response send error
ApiResponseSend(SendError<ApiResponse>), ApiResponseSend(SendError<ApiResponse>),
/// Cannot create a VM from the API
ApiVmCreate(ApiError),
/// Cannot boot a VM from the API
ApiVmBoot(ApiError),
/// Cannot get the VM info
ApiVmInfo,
/// Cannot shut a VM down from the API
ApiVmShutdown(ApiError),
/// Cannot reboot a VM from the API
ApiVmReboot(ApiError),
/// Cannot bind to the UNIX domain socket path /// Cannot bind to the UNIX domain socket path
Bind(io::Error), Bind(io::Error),
@ -73,9 +52,6 @@ pub enum Error {
/// Cannot read from EventFd. /// Cannot read from EventFd.
EventFdRead(io::Error), EventFdRead(io::Error),
/// Cannot write to EventFd.
EventFdWrite(io::Error),
/// Cannot create epoll context. /// Cannot create epoll context.
Epoll(io::Error), Epoll(io::Error),
@ -85,17 +61,8 @@ pub enum Error {
/// Cannot handle the VM STDIN stream /// Cannot handle the VM STDIN stream
Stdin(VmError), Stdin(VmError),
/// Cannot create a VM /// Cannot reboot the VM
VmCreate(VmError), VmReboot(VmError),
/// Cannot boot a VM
VmBoot(VmError),
/// Cannot fetch the VM information
VmInfo,
/// The Vm is not created
VmNotCreated,
/// Cannot shut a VM down /// Cannot shut a VM down
VmShutdown(VmError), VmShutdown(VmError),
@ -196,7 +163,7 @@ pub fn start_vmm_thread(
// based on the same VM config, boot it and restart // based on the same VM config, boot it and restart
// the control loop. // the control loop.
vmm.vm_reboot()?; vmm.vm_reboot().map_err(Error::VmReboot)?;
// Continue and restart the VMM control loop // Continue and restart the VMM control loop
continue 'outer; continue 'outer;
@ -264,12 +231,12 @@ impl Vmm {
}) })
} }
fn vm_reboot(&mut self) -> Result<()> { fn vm_reboot(&mut self) -> result::Result<(), VmError> {
// Without ACPI, a reset is equivalent to a shutdown // Without ACPI, a reset is equivalent to a shutdown
#[cfg(not(feature = "acpi"))] #[cfg(not(feature = "acpi"))]
{ {
if let Some(ref mut vm) = self.vm { if let Some(ref mut vm) = self.vm {
vm.shutdown().map_err(Error::VmShutdown)?; vm.shutdown()?;
return Ok(()); return Ok(());
} }
} }
@ -277,29 +244,29 @@ impl Vmm {
// First we stop the current VM and create a new one. // First we stop the current VM and create a new one.
if let Some(ref mut vm) = self.vm { if let Some(ref mut vm) = self.vm {
let config = vm.get_config(); let config = vm.get_config();
vm.shutdown().map_err(Error::VmShutdown)?; vm.shutdown()?;
let exit_evt = self.exit_evt.try_clone().map_err(Error::EventFdClone)?; let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(Error::EventFdClone)?; let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
self.vm = Some(Vm::new(config, exit_evt, reset_evt).map_err(Error::VmCreate)?); self.vm = Some(Vm::new(config, exit_evt, reset_evt)?);
} }
// Then we start the new VM. // Then we start the new VM.
if let Some(ref mut vm) = self.vm { if let Some(ref mut vm) = self.vm {
vm.boot().map_err(Error::VmBoot)?; vm.boot()?;
} else { } else {
return Err(Error::VmNotCreated); return Err(VmError::VmNotCreated);
} }
Ok(()) Ok(())
} }
fn vm_info(&self) -> Result<VmInfo> { fn vm_info(&self) -> result::Result<VmInfo, VmError> {
match &self.vm_config { match &self.vm_config {
Some(config) => { Some(config) => {
let state = match &self.vm { let state = match &self.vm {
Some(vm) => vm.get_state().unwrap(), Some(vm) => vm.get_state()?,
None => VmState::Created, None => VmState::Created,
}; };
@ -308,7 +275,7 @@ impl Vmm {
state, state,
}) })
} }
None => Err(Error::VmNotCreated), None => Err(VmError::VmNotCreated),
} }
} }
@ -448,8 +415,7 @@ impl Vmm {
ApiRequest::VmReboot(sender) => { ApiRequest::VmReboot(sender) => {
let response = match self.vm_reboot() { let response = match self.vm_reboot() {
Ok(_) => Ok(ApiResponsePayload::Empty), Ok(_) => Ok(ApiResponsePayload::Empty),
Err(Error::VmNotCreated) => Err(ApiError::VmNotBooted), Err(e) => Err(ApiError::VmReboot(e)),
Err(_) => Err(ApiError::VmReboot),
}; };
sender.send(response).map_err(Error::ApiResponseSend)?; sender.send(response).map_err(Error::ApiResponseSend)?;
@ -457,7 +423,7 @@ impl Vmm {
ApiRequest::VmInfo(sender) => { ApiRequest::VmInfo(sender) => {
let response = match self.vm_info() { let response = match self.vm_info() {
Ok(info) => Ok(ApiResponsePayload::VmInfo(info)), Ok(info) => Ok(ApiResponsePayload::VmInfo(info)),
Err(_) => Err(ApiError::VmInfo), Err(e) => Err(ApiError::VmInfo(e)),
}; };
sender.send(response).map_err(Error::ApiResponseSend)?; sender.send(response).map_err(Error::ApiResponseSend)?;

View File

@ -209,6 +209,12 @@ pub enum Error {
/// Failed to create a new KVM instance /// Failed to create a new KVM instance
KvmNew(io::Error), KvmNew(io::Error),
/// VM is not created
VmNotCreated,
/// Cannot clone EventFd.
EventFdClone(io::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;