diff --git a/vmm/src/api/http.rs b/vmm/src/api/http.rs index 1a7649068..7ea683bb2 100644 --- a/vmm/src/api/http.rs +++ b/vmm/src/api/http.rs @@ -5,7 +5,7 @@ extern crate threadpool; -use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo}; +use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmShutdown}; use crate::api::{ApiRequest, VmAction}; use crate::{Error, Result}; use micro_http::{HttpConnection, Request, Response, StatusCode, Version}; @@ -60,6 +60,7 @@ lazy_static! { r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {})); r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown))); r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot))); + r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {})); r }; diff --git a/vmm/src/api/http_endpoint.rs b/vmm/src/api/http_endpoint.rs index 22b0dccfb..79746609e 100644 --- a/vmm/src/api/http_endpoint.rs +++ b/vmm/src/api/http_endpoint.rs @@ -5,8 +5,8 @@ use crate::api::http::EndpointHandler; use crate::api::{ - vm_boot, vm_create, vm_delete, vm_info, vm_reboot, vm_shutdown, ApiError, ApiRequest, - ApiResult, VmAction, VmConfig, + vm_boot, vm_create, vm_delete, vm_info, vm_reboot, vm_shutdown, vmm_shutdown, ApiError, + ApiRequest, ApiResult, VmAction, VmConfig, }; use micro_http::{Body, Method, Request, Response, StatusCode, Version}; use serde_json::Error as SerdeError; @@ -37,6 +37,9 @@ pub enum HttpError { /// Could not act on a VM VmAction(ApiError), + + /// Could not shut the VMM down + VmmShutdown(ApiError), } fn error_response(error: HttpError, status: StatusCode) -> Response { @@ -155,3 +158,25 @@ impl EndpointHandler for VmInfo { } } } + +// /api/v1/vmm.shutdown handler +pub struct VmmShutdown {} + +impl EndpointHandler for VmmShutdown { + fn handle_request( + &self, + req: &Request, + api_notifier: EventFd, + api_sender: Sender, + ) -> Response { + match req.method() { + Method::Put => { + match vmm_shutdown(api_notifier, api_sender).map_err(HttpError::VmmShutdown) { + Ok(_) => Response::new(Version::Http11, StatusCode::OK), + Err(e) => error_response(e, StatusCode::InternalServerError), + } + } + _ => Response::new(Version::Http11, StatusCode::BadRequest), + } + } +} diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 6f67a8794..3b4ed8626 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -87,6 +87,9 @@ pub enum ApiError { /// The VM could not reboot. VmReboot(VmError), + + /// The VMM could not shutdown. + VmmShutdown(VmError), } pub type ApiResult = std::result::Result; @@ -138,6 +141,11 @@ pub enum ApiRequest { /// If the VM was not previously booted or created, the VMM API server /// will send a VmReboot error back. VmReboot(Sender), + + /// Shut the VMM down. + /// This will shutdown and delete the current VM, if any, and then exit the + /// VMM process. + VmmShutdown(Sender), } pub fn vm_create( @@ -226,3 +234,17 @@ pub fn vm_info(api_evt: EventFd, api_sender: Sender) -> ApiResult Err(ApiError::ResponsePayloadType), } } + +pub fn vmm_shutdown(api_evt: EventFd, api_sender: Sender) -> ApiResult<()> { + let (response_sender, response_receiver) = channel(); + + // Send the VMM shutdown request. + api_sender + .send(ApiRequest::VmmShutdown(response_sender)) + .map_err(ApiError::RequestSend)?; + api_evt.write(1).map_err(ApiError::EventFdWrite)?; + + response_receiver.recv().map_err(ApiError::ResponseRecv)??; + + Ok(()) +} diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 2db9e8b2f..aa6d3aee3 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -23,6 +23,15 @@ paths: schema: $ref: '#/components/schemas/VmmInfo' + /vmm.shutdown: + put: + summary: Shuts the cloud-hypervisor VMM. + operationId: shutdownVMM + responses: + 201: + description: The VMM successfully shutdown. + content: {} + /vm.info: get: summary: Returns general information about the cloud-hypervisor Virtual Machine (VM) instance. diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 8cf7b136c..5f7115107 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -69,6 +69,9 @@ pub enum Error { /// Cannot create VMM thread VmmThreadSpawn(io::Error), + + /// Cannot shut the VMM down + VmmShutdown(VmError), } pub type Result = result::Result; @@ -168,14 +171,7 @@ pub fn start_vmm_thread( // Continue and restart the VMM control loop continue 'outer; } - Ok(ExitBehaviour::Shutdown) => { - // The VMM control loop exites with a shutdown behaviour. - // We have to stop the VM and we exit thr thread. - if let Some(ref mut vm) = vmm.vm { - vm.shutdown().map_err(Error::VmShutdown)?; - } - break 'outer; - } + Ok(ExitBehaviour::Shutdown) => break 'outer, Err(e) => return Err(e), } } @@ -320,6 +316,10 @@ impl Vmm { Ok(()) } + fn vmm_shutdown(&mut self) -> result::Result<(), VmError> { + self.vm_delete() + } + fn control_loop(&mut self, api_receiver: Arc>) -> Result { const EPOLL_EVENTS_LEN: usize = 100; @@ -354,6 +354,7 @@ impl Vmm { EpollDispatch::Exit => { // Consume the event. self.exit_evt.read().map_err(Error::EventFdRead)?; + self.vmm_shutdown().map_err(Error::VmmShutdown)?; exit_behaviour = ExitBehaviour::Shutdown; break 'outer; @@ -438,6 +439,17 @@ impl Vmm { sender.send(response).map_err(Error::ApiResponseSend)?; } + ApiRequest::VmmShutdown(sender) => { + let response = self + .vmm_shutdown() + .map_err(ApiError::VmmShutdown) + .map(|_| ApiResponsePayload::Empty); + + sender.send(response).map_err(Error::ApiResponseSend)?; + + exit_behaviour = ExitBehaviour::Shutdown; + break 'outer; + } } } }