diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 9972ac8bf..dbc0064ab 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -50,6 +50,12 @@ pub enum ApiError { /// The VM could not boot. VmBoot(VmError), + + /// The VM could not shutdown. + VmShutdown(VmError), + + /// The VM could not reboot. + VmReboot, } pub enum ApiResponsePayload { @@ -72,6 +78,16 @@ pub enum ApiRequest { /// If the VM was not previously created, the VMM API server will send a /// VmBoot error back. VmBoot(Sender), + + /// Shut the previously booted virtual machine down. + /// If the VM was not previously booted or created, the VMM API server + /// will send a VmShutdown error back. + VmShutdown(Sender), + + /// Reboot the previously booted virtual machine. + /// If the VM was not previously booted or created, the VMM API server + /// will send a VmReboot error back. + VmReboot(Sender), } pub fn vm_create( @@ -95,19 +111,67 @@ pub fn vm_create( Ok(()) } -pub fn vm_boot(api_evt: EventFd, api_sender: Sender) -> Result<()> { +/// Represents a VM related action. +/// This is mostly used to factorize code between VM routines +/// that only differ by the IPC command they send. +pub enum VmAction { + /// Boot a VM + Boot, + + /// Shut a VM down + Shutdown, + + /// Reboot a VM + Reboot, +} + +fn vm_action(api_evt: EventFd, api_sender: Sender, action: VmAction) -> Result<()> { let (response_sender, response_receiver) = channel(); - // Send the VM boot request. - api_sender - .send(ApiRequest::VmBoot(response_sender)) - .map_err(Error::ApiRequestSend)?; + let request = match action { + VmAction::Boot => ApiRequest::VmBoot(response_sender), + VmAction::Shutdown => ApiRequest::VmShutdown(response_sender), + VmAction::Reboot => ApiRequest::VmReboot(response_sender), + }; + + // Send the VM request. + api_sender.send(request).map_err(Error::ApiRequestSend)?; api_evt.write(1).map_err(Error::EventFdWrite)?; - response_receiver - .recv() - .map_err(Error::ApiResponseRecv)? - .map_err(Error::ApiVmBoot)?; + match action { + VmAction::Boot => { + response_receiver + .recv() + .map_err(Error::ApiResponseRecv)? + .map_err(Error::ApiVmBoot)?; + } + + VmAction::Shutdown => { + response_receiver + .recv() + .map_err(Error::ApiResponseRecv)? + .map_err(Error::ApiVmShutdown)?; + } + + VmAction::Reboot => { + response_receiver + .recv() + .map_err(Error::ApiResponseRecv)? + .map_err(Error::ApiVmReboot)?; + } + } Ok(()) } + +pub fn vm_boot(api_evt: EventFd, api_sender: Sender) -> Result<()> { + vm_action(api_evt, api_sender, VmAction::Boot) +} + +pub fn vm_shutdown(api_evt: EventFd, api_sender: Sender) -> Result<()> { + vm_action(api_evt, api_sender, VmAction::Shutdown) +} + +pub fn vm_reboot(api_evt: EventFd, api_sender: Sender) -> Result<()> { + vm_action(api_evt, api_sender, VmAction::Reboot) +} diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 5465e5821..c0b7d238d 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -51,6 +51,12 @@ pub enum Error { /// Cannot boot a VM from the API ApiVmBoot(ApiError), + /// 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 Bind(io::Error), @@ -180,33 +186,7 @@ pub fn start_vmm_thread( // based on the same VM config, boot it and restart // the control loop. - // Without ACPI, a reset is equivalent to a shutdown - #[cfg(not(feature = "acpi"))] - { - if let Some(ref mut vm) = vmm.vm { - vm.shtudown().map_err(Error::VmShutdown)?; - break 'outer; - } - } - - // First we stop the current VM and create a new one. - if let Some(ref mut vm) = vmm.vm { - let config = vm.get_config(); - vm.shutdown().map_err(Error::VmShutdown)?; - - let exit_evt = vmm.exit_evt.try_clone().map_err(Error::EventFdClone)?; - let reset_evt = - vmm.reset_evt.try_clone().map_err(Error::EventFdClone)?; - - vmm.vm = Some( - Vm::new(config, exit_evt, reset_evt).map_err(Error::VmCreate)?, - ); - } - - // Then we boot the new VM. - if let Some(ref mut vm) = vmm.vm { - vm.boot().map_err(Error::VmBoot)?; - } + vmm.vm_reboot()?; // Continue and restart the VMM control loop continue 'outer; @@ -272,6 +252,35 @@ impl Vmm { }) } + fn vm_reboot(&mut self) -> Result<()> { + // Without ACPI, a reset is equivalent to a shutdown + #[cfg(not(feature = "acpi"))] + { + if let Some(ref mut vm) = self.vm { + vm.shutdown().map_err(Error::VmShutdown)?; + return Ok(()); + } + } + + // First we stop the current VM and create a new one. + if let Some(ref mut vm) = self.vm { + let config = vm.get_config(); + vm.shutdown().map_err(Error::VmShutdown)?; + + let exit_evt = self.exit_evt.try_clone().map_err(Error::EventFdClone)?; + let reset_evt = self.reset_evt.try_clone().map_err(Error::EventFdClone)?; + + self.vm = Some(Vm::new(config, exit_evt, reset_evt).map_err(Error::VmCreate)?); + } + + // Then we start the new VM. + if let Some(ref mut vm) = self.vm { + vm.boot().map_err(Error::VmBoot)?; + } + + Ok(()) + } + fn control_loop(&mut self, api_receiver: Arc>) -> Result { const EPOLL_EVENTS_LEN: usize = 100; @@ -355,6 +364,24 @@ impl Vmm { sender.send(response).map_err(Error::ApiResponseSend)?; } } + ApiRequest::VmShutdown(sender) => { + if let Some(ref mut vm) = self.vm { + let response = match vm.shutdown() { + Ok(_) => Ok(ApiResponsePayload::Empty), + Err(e) => Err(ApiError::VmShutdown(e)), + }; + + sender.send(response).map_err(Error::ApiResponseSend)?; + } + } + ApiRequest::VmReboot(sender) => { + let response = match self.vm_reboot() { + Ok(_) => Ok(ApiResponsePayload::Empty), + Err(_) => Err(ApiError::VmReboot), + }; + + sender.send(response).map_err(Error::ApiResponseSend)?; + } } } }