// Copyright © 2019 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // use crate::api::http_endpoint::{ VmActionHandler, VmAddDevice, VmAddDisk, VmAddFs, VmAddNet, VmAddPmem, VmAddVsock, VmCreate, VmInfo, VmRemoveDevice, VmResize, VmRestore, VmSnapshot, VmmPing, VmmShutdown, }; use crate::api::{ApiError, ApiRequest, VmAction}; use crate::seccomp_filters::{get_seccomp_filter, Thread}; use crate::{Error, Result}; use micro_http::{Body, HttpServer, MediaType, Method, Request, Response, StatusCode, Version}; use seccomp::{SeccompFilter, SeccompLevel}; use serde_json::Error as SerdeError; use std::collections::HashMap; use std::path::PathBuf; use std::sync::mpsc::Sender; use std::thread; use vmm_sys_util::eventfd::EventFd; /// Errors associated with VMM management #[derive(Debug)] pub enum HttpError { /// API request receive error SerdeJsonDeserialize(SerdeError), /// Attempt to access unsupported HTTP method BadRequest, /// Could not create a VM VmCreate(ApiError), /// Could not boot a VM VmBoot(ApiError), /// Could not delete a VM VmDelete(ApiError), /// Could not get the VM information VmInfo(ApiError), /// Could not pause the VM VmPause(ApiError), /// Could not pause the VM VmResume(ApiError), /// Could not shut a VM down VmShutdown(ApiError), /// Could not reboot a VM VmReboot(ApiError), /// Could not snapshot a VM VmSnapshot(ApiError), /// Could not restore a VM VmRestore(ApiError), /// Could not act on a VM VmAction(ApiError), /// Could not resize a VM VmResize(ApiError), /// Could not add a device to a VM VmAddDevice(ApiError), /// Could not remove a device from a VM VmRemoveDevice(ApiError), /// Could not shut the VMM down VmmShutdown(ApiError), /// Could not handle VMM ping VmmPing(ApiError), /// Could not add a disk to a VM VmAddDisk(ApiError), /// Could not add a fs to a VM VmAddFs(ApiError), /// Could not add a pmem device to a VM VmAddPmem(ApiError), /// Could not add a network device to a VM VmAddNet(ApiError), /// Could not add a vsock device to a VM VmAddVsock(ApiError), } impl From for HttpError { fn from(e: serde_json::Error) -> Self { HttpError::SerdeJsonDeserialize(e) } } const HTTP_ROOT: &str = "/api/v1"; pub fn error_response(error: HttpError, status: StatusCode) -> Response { let mut response = Response::new(Version::Http11, status); response.set_body(Body::new(format!("{:?}", error))); response } /// An HTTP endpoint handler interface pub trait EndpointHandler: Sync + Send { /// Handles an HTTP request. /// After parsing the request, the handler could decide to send an /// associated API request down to the VMM API server to e.g. create /// or start a VM. The request will block waiting for an answer from the /// API server and translate that into an HTTP response. fn handle_request( &self, req: &Request, api_notifier: EventFd, api_sender: Sender, ) -> Response { match req.method() { Method::Put => match self.put_handler(api_notifier, api_sender, &req.body) { Ok(_) => Response::new(Version::Http11, StatusCode::NoContent), Err(e @ HttpError::BadRequest) => error_response(e, StatusCode::BadRequest), Err(e @ HttpError::SerdeJsonDeserialize(_)) => { error_response(e, StatusCode::BadRequest) } Err(e) => error_response(e, StatusCode::InternalServerError), }, _ => Response::new(Version::Http11, StatusCode::BadRequest), } } fn put_handler( &self, _api_notifier: EventFd, _api_sender: Sender, _body: &Option, ) -> std::result::Result<(), HttpError> { Err(HttpError::BadRequest) } } /// An HTTP routes structure. pub struct HttpRoutes { /// routes is a hash table mapping endpoint URIs to their endpoint handlers. pub routes: HashMap>, } macro_rules! endpoint { ($path:expr) => { format!("{}{}", HTTP_ROOT, $path) }; } lazy_static! { /// HTTP_ROUTES contain all the cloud-hypervisor HTTP routes. pub static ref HTTP_ROUTES: HttpRoutes = { let mut r = HttpRoutes { routes: HashMap::new(), }; r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {})); r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot))); r.routes.insert(endpoint!("/vm.delete"), Box::new(VmActionHandler::new(VmAction::Delete))); r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {})); r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause))); r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume))); 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!("/vm.snapshot"), Box::new(VmSnapshot {})); r.routes.insert(endpoint!("/vm.restore"), Box::new(VmRestore {})); r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {})); r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {})); r.routes.insert(endpoint!("/vm.resize"), Box::new(VmResize {})); r.routes.insert(endpoint!("/vm.add-device"), Box::new(VmAddDevice {})); r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmRemoveDevice {})); r.routes.insert(endpoint!("/vm.add-disk"), Box::new(VmAddDisk {})); r.routes.insert(endpoint!("/vm.add-fs"), Box::new(VmAddFs {})); r.routes.insert(endpoint!("/vm.add-pmem"), Box::new(VmAddPmem {})); r.routes.insert(endpoint!("/vm.add-net"), Box::new(VmAddNet {})); r.routes.insert(endpoint!("/vm.add-vsock"), Box::new(VmAddVsock {})); r }; } fn handle_http_request( request: &Request, api_notifier: &EventFd, api_sender: &Sender, ) -> Response { let path = request.uri().get_abs_path().to_string(); let mut response = match HTTP_ROUTES.routes.get(&path) { Some(route) => match api_notifier.try_clone() { Ok(notifier) => route.handle_request(&request, notifier, api_sender.clone()), Err(_) => Response::new(Version::Http11, StatusCode::InternalServerError), }, None => Response::new(Version::Http11, StatusCode::NotFound), }; response.set_server("Cloud Hypervisor API"); response.set_content_type(MediaType::ApplicationJson); response } pub fn start_http_thread( path: &str, api_notifier: EventFd, api_sender: Sender, seccomp_level: &SeccompLevel, ) -> Result>> { std::fs::remove_file(path).unwrap_or_default(); let socket_path = PathBuf::from(path); // Retrieve seccomp filter for API thread let api_seccomp_filter = get_seccomp_filter(seccomp_level, Thread::Api).map_err(Error::CreateSeccompFilter)?; thread::Builder::new() .name("http-server".to_string()) .spawn(move || { // Apply seccomp filter for API thread. SeccompFilter::apply(api_seccomp_filter).map_err(Error::ApplySeccompFilter)?; let mut server = HttpServer::new(socket_path).unwrap(); server.start_server().unwrap(); loop { match server.requests() { Ok(request_vec) => { for server_request in request_vec { server .respond(server_request.process(|request| { handle_http_request(request, &api_notifier, &api_sender) })) .or_else(|e| { error!("HTTP server error on response: {}", e); Ok(()) })?; } } Err(e) => { error!( "HTTP server error on retrieving incoming request. Error: {}", e ); } } } }) .map_err(Error::HttpThreadSpawn) }