From 2f1ff23066a7f696bf04a5b14a8910b28a1ff915 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 25 Sep 2019 14:40:14 +0200 Subject: [PATCH] vmm: (Re-)Introduce a VMM structure Unlike the Vmm structure we removed with commit bdfd1a3f, this new one is really meant to represent the VM monitoring/management object. For that, we implement a control loop that will replace the one that's currently embedded within the Vm structure itself. This will allow us to decouple the VM lifecycle management from the VM object itself, by having a constantly running VMM control loop. Besides the VM specific events (exit, reset, stdin for now), the VMM control loop also handles all the Cloud Hypervisor IPC requests. Signed-off-by: Samuel Ortiz --- src/main.rs | 2 +- vmm/src/lib.rs | 216 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 187 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index 76ba05115..aa84a2769 100755 --- a/src/main.rs +++ b/src/main.rs @@ -286,7 +286,7 @@ fn main() { ); if let Err(e) = vmm::start_vm_loop(Arc::new(vm_config)) { - println!("Guest boot failed: {}", e); + println!("Guest boot failed: {:?}", e); process::exit(1); } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index ebd2f4ae1..1f823ed0c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -7,12 +7,14 @@ extern crate log; extern crate vmm_sys_util; +use crate::api::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload}; +use crate::vm::{Error as VmError, ExitBehaviour, Vm}; use libc::EFD_NONBLOCK; -use std::fmt::{self, Display}; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; -use std::result; +use std::sync::mpsc::{channel, Receiver, RecvError, SendError, Sender}; use std::sync::Arc; +use std::{result, thread}; use vmm_sys_util::eventfd::EventFd; pub mod api; @@ -21,38 +23,59 @@ pub mod device_manager; pub mod vm; use self::config::VmConfig; -use self::vm::{ExitBehaviour, Vm}; +//use self::vm::{ExitBehaviour, Vm}; -/// Errors associated with VM management +/// Errors associated with VMM management #[derive(Debug)] +#[allow(clippy::large_enum_variant)] pub enum Error { + /// API request receive error + ApiRequestRecv(RecvError), + + /// API response receive error + ApiResponseRecv(RecvError), + + /// API request send error + ApiRequestSend(SendError), + + /// API response send error + ApiResponseSend(SendError), + + /// Cannot create a VM from the API + ApiVmCreate(ApiError), + + /// Cannot start a VM from the API + ApiVmStart(ApiError), + + /// Cannot clone EventFd. + EventFdClone(io::Error), + /// Cannot create EventFd. - EventFd(io::Error), + EventFdCreate(io::Error), - /// Cannot create a new VM. - VmNew(vm::Error), + /// Cannot read from EventFd. + EventFdRead(io::Error), - /// Cannot start a VM. - VmStart(vm::Error), + /// Cannot write to EventFd. + EventFdWrite(io::Error), - /// Cannot stop a VM. - VmStop(vm::Error), + /// Cannot create epoll context. + Epoll(io::Error), + + /// Cannot handle the VM STDIN stream + Stdin(VmError), + + /// Cannot create a VM + VmCreate(VmError), + + /// Cannot start a VM + VmStart(VmError), + + /// Cannot stop a VM + VmStop(VmError), } pub type Result = result::Result; -impl Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::Error::*; - - match self { - EventFd(e) => write!(f, "Can not create EventFd: {:?}", e), - VmNew(e) => write!(f, "Can not create a new virtual machine: {:?}", e), - VmStart(e) => write!(f, "Can not start a new virtual machine: {:?}", e), - VmStop(e) => write!(f, "Can not stop a virtual machine: {:?}", e), - } - } -} - #[derive(Debug, Clone, Copy, PartialEq)] pub enum EpollDispatch { Exit, @@ -121,9 +144,142 @@ impl AsRawFd for EpollContext { } } +pub struct Vmm { + epoll: EpollContext, + exit_evt: EventFd, + reset_evt: EventFd, + api_evt: EventFd, + vm: Option, +} + +impl Vmm { + fn new(api_evt: EventFd) -> Result { + let mut epoll = EpollContext::new().map_err(Error::Epoll)?; + let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; + let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; + + if unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 { + epoll.add_stdin().map_err(Error::Epoll)?; + } + + epoll + .add_event(&exit_evt, EpollDispatch::Exit) + .map_err(Error::Epoll)?; + + epoll + .add_event(&reset_evt, EpollDispatch::Reset) + .map_err(Error::Epoll)?; + + epoll + .add_event(&api_evt, EpollDispatch::Api) + .map_err(Error::Epoll)?; + + Ok(Vmm { + epoll, + exit_evt, + reset_evt, + api_evt, + vm: None, + }) + } + + fn control_loop(&mut self, api_receiver: Arc>) -> Result { + const EPOLL_EVENTS_LEN: usize = 100; + + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + let epoll_fd = self.epoll.as_raw_fd(); + + let exit_behaviour; + + 'outer: loop { + let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { + Ok(res) => res, + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + // It's well defined from the epoll_wait() syscall + // documentation that the epoll loop can be interrupted + // before any of the requested events occurred or the + // timeout expired. In both those cases, epoll_wait() + // returns an error of type EINTR, but this should not + // be considered as a regular error. Instead it is more + // appropriate to retry, by calling into epoll_wait(). + continue; + } + return Err(Error::Epoll(e)); + } + }; + + for event in events.iter().take(num_events) { + let dispatch_idx = event.data as usize; + + if let Some(dispatch_type) = self.epoll.dispatch_table[dispatch_idx] { + match dispatch_type { + EpollDispatch::Exit => { + // Consume the event. + self.exit_evt.read().map_err(Error::EventFdRead)?; + exit_behaviour = ExitBehaviour::Shutdown; + + break 'outer; + } + EpollDispatch::Reset => { + // Consume the event. + self.reset_evt.read().map_err(Error::EventFdRead)?; + exit_behaviour = ExitBehaviour::Reset; + + break 'outer; + } + EpollDispatch::Stdin => { + if let Some(ref vm) = self.vm { + vm.handle_stdin().map_err(Error::Stdin)?; + } + } + EpollDispatch::Api => { + // Consume the event. + self.api_evt.read().map_err(Error::EventFdRead)?; + + // Read from the API receiver channel + let api_request = api_receiver.recv().map_err(Error::ApiRequestRecv)?; + + match api_request { + ApiRequest::VmCreate(config, sender) => { + let exit_evt = + self.exit_evt.try_clone().map_err(Error::EventFdClone)?; + let reset_evt = + self.reset_evt.try_clone().map_err(Error::EventFdClone)?; + let response = match Vm::new(config, exit_evt, reset_evt) { + Ok(vm) => { + self.vm = Some(vm); + Ok(ApiResponsePayload::Empty) + } + Err(e) => Err(ApiError::VmCreate(e)), + }; + + sender.send(response).map_err(Error::ApiResponseSend)?; + } + ApiRequest::VmStart(sender) => { + if let Some(ref mut vm) = self.vm { + let response = match vm.start() { + Ok(_) => Ok(ApiResponsePayload::Empty), + Err(e) => Err(ApiError::VmStart(e)), + }; + + sender.send(response).map_err(Error::ApiResponseSend)?; + } + } + } + } + } + } + } + } + + Ok(exit_behaviour) + } +} + pub fn start_vm_loop(config: Arc) -> Result<()> { - let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?; - let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?; + let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; + let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; loop { let mut vm = Vm::new( @@ -131,14 +287,14 @@ pub fn start_vm_loop(config: Arc) -> Result<()> { exit_evt.try_clone().unwrap(), reset_evt.try_clone().unwrap(), ) - .map_err(Error::VmNew)?; + .expect("Could not create VM"); - if vm.start().map_err(Error::VmStart)? == ExitBehaviour::Shutdown { - vm.stop().map_err(Error::VmStop)?; + if vm.start().expect("Could not start VM") == ExitBehaviour::Shutdown { + vm.stop().expect("Could not stop VM"); break; } - vm.stop().map_err(Error::VmStop)?; + vm.stop().expect("Could not stop VM"); #[cfg(not(feature = "acpi"))] break;