Files
cloud-hypervisor/vmm/src/api/http.rs
William Douglas b8779ddc9e vmm: Create the api socket fd to pass to the http server
Instead of using the http server's method to have it create the
fd (causing the http thread to need to support the socket, bind and
listen syscalls). Create the socket fd in the vmm thread and use the
http server's new method supporting passing in this fd for the api
socket.

Signed-off-by: William Douglas <william.douglas@intel.com>
2021-04-29 09:44:40 +01:00

324 lines
11 KiB
Rust

// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo, 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::{SeccompAction, SeccompFilter};
use serde_json::Error as SerdeError;
use std::collections::HashMap;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::os::unix::net::UnixListener;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::sync::Arc;
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,
/// Undefined endpoints
NotFound,
/// Internal Server Error
InternalServerError,
/// 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 resize a memory zone
VmResizeZone(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),
/// Could not get counters from VM
VmCounters(ApiError),
/// Error setting up migration received
VmReceiveMigration(ApiError),
/// Error setting up migration sender
VmSendMigration(ApiError),
/// Error activating power button
VmPowerButton(ApiError),
}
impl From<serde_json::Error> 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<ApiRequest>,
) -> Response {
let res = match req.method() {
Method::Put => self.put_handler(api_notifier, api_sender, &req.body),
Method::Get => self.get_handler(api_notifier, api_sender, &req.body),
_ => return Response::new(Version::Http11, StatusCode::BadRequest),
};
match res {
Ok(response_body) => {
if let Some(body) = response_body {
let mut response = Response::new(Version::Http11, StatusCode::OK);
response.set_body(body);
response
} else {
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),
}
}
fn put_handler(
&self,
_api_notifier: EventFd,
_api_sender: Sender<ApiRequest>,
_body: &Option<Body>,
) -> std::result::Result<Option<Body>, HttpError> {
Err(HttpError::BadRequest)
}
fn get_handler(
&self,
_api_notifier: EventFd,
_api_sender: Sender<ApiRequest>,
_body: &Option<Body>,
) -> std::result::Result<Option<Body>, 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<String, Box<dyn EndpointHandler + Sync + Send>>,
}
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.add-device"), Box::new(VmActionHandler::new(VmAction::AddDevice(Arc::default()))));
r.routes.insert(endpoint!("/vm.add-disk"), Box::new(VmActionHandler::new(VmAction::AddDisk(Arc::default()))));
r.routes.insert(endpoint!("/vm.add-fs"), Box::new(VmActionHandler::new(VmAction::AddFs(Arc::default()))));
r.routes.insert(endpoint!("/vm.add-net"), Box::new(VmActionHandler::new(VmAction::AddNet(Arc::default()))));
r.routes.insert(endpoint!("/vm.add-pmem"), Box::new(VmActionHandler::new(VmAction::AddPmem(Arc::default()))));
r.routes.insert(endpoint!("/vm.add-vsock"), Box::new(VmActionHandler::new(VmAction::AddVsock(Arc::default()))));
r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot)));
r.routes.insert(endpoint!("/vm.counters"), Box::new(VmActionHandler::new(VmAction::Counters)));
r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
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.power-button"), Box::new(VmActionHandler::new(VmAction::PowerButton)));
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
r.routes.insert(endpoint!("/vm.receive-migration"), Box::new(VmActionHandler::new(VmAction::ReceiveMigration(Arc::default()))));
r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmActionHandler::new(VmAction::RemoveDevice(Arc::default()))));
r.routes.insert(endpoint!("/vm.resize"), Box::new(VmActionHandler::new(VmAction::Resize(Arc::default()))));
r.routes.insert(endpoint!("/vm.resize-zone"), Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))));
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))));
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
r.routes.insert(endpoint!("/vm.send-migration"), Box::new(VmActionHandler::new(VmAction::SendMigration(Arc::default()))));
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))));
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
r
};
}
fn handle_http_request(
request: &Request,
api_notifier: &EventFd,
api_sender: &Sender<ApiRequest>,
) -> 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(_) => error_response(
HttpError::InternalServerError,
StatusCode::InternalServerError,
),
},
None => error_response(HttpError::NotFound, StatusCode::NotFound),
};
response.set_server("Cloud Hypervisor API");
response.set_content_type(MediaType::ApplicationJson);
response
}
fn start_http_thread(
mut server: HttpServer,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
seccomp_action: &SeccompAction,
) -> Result<thread::JoinHandle<Result<()>>> {
// Retrieve seccomp filter for API thread
let api_seccomp_filter =
get_seccomp_filter(seccomp_action, 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)?;
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)
}
pub fn start_http_path_thread(
path: &str,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
seccomp_action: &SeccompAction,
) -> Result<thread::JoinHandle<Result<()>>> {
std::fs::remove_file(path).unwrap_or_default();
let socket_path = PathBuf::from(path);
let socket_fd = UnixListener::bind(socket_path).map_err(Error::CreateApiServerSocket)?;
let server =
HttpServer::new_from_fd(socket_fd.into_raw_fd()).map_err(Error::CreateApiServer)?;
start_http_thread(server, api_notifier, api_sender, seccomp_action)
}
pub fn start_http_fd_thread(
fd: RawFd,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
seccomp_action: &SeccompAction,
) -> Result<thread::JoinHandle<Result<()>>> {
let server = HttpServer::new_from_fd(fd).map_err(Error::CreateApiServer)?;
start_http_thread(server, api_notifier, api_sender, seccomp_action)
}