2019-09-18 09:14:49 +00:00
|
|
|
// Copyright © 2019 Intel Corporation
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//
|
|
|
|
|
2020-05-14 12:34:41 +00:00
|
|
|
use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdown};
|
2020-05-01 14:56:32 +00:00
|
|
|
use crate::api::{ApiError, ApiRequest, VmAction};
|
2020-03-20 16:57:03 +00:00
|
|
|
use crate::seccomp_filters::{get_seccomp_filter, Thread};
|
2019-09-18 09:14:49 +00:00
|
|
|
use crate::{Error, Result};
|
2020-05-01 15:11:13 +00:00
|
|
|
use micro_http::{Body, HttpServer, MediaType, Method, Request, Response, StatusCode, Version};
|
2020-07-30 21:21:58 +00:00
|
|
|
use seccomp::{SeccompAction, SeccompFilter};
|
2020-05-01 14:56:32 +00:00
|
|
|
use serde_json::Error as SerdeError;
|
2019-09-18 09:14:49 +00:00
|
|
|
use std::collections::HashMap;
|
2019-11-08 21:59:08 +00:00
|
|
|
use std::path::PathBuf;
|
2019-09-18 09:14:49 +00:00
|
|
|
use std::sync::mpsc::Sender;
|
2020-05-14 12:34:41 +00:00
|
|
|
use std::sync::Arc;
|
2019-09-18 09:14:49 +00:00
|
|
|
use std::thread;
|
|
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
|
2020-05-01 14:56:32 +00:00
|
|
|
/// Errors associated with VMM management
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum HttpError {
|
|
|
|
/// API request receive error
|
|
|
|
SerdeJsonDeserialize(SerdeError),
|
|
|
|
|
2020-05-01 15:11:13 +00:00
|
|
|
/// Attempt to access unsupported HTTP method
|
|
|
|
BadRequest,
|
|
|
|
|
2020-05-26 22:20:59 +00:00
|
|
|
/// Undefined endpoints
|
|
|
|
NotFound,
|
|
|
|
|
|
|
|
/// Internal Server Error
|
|
|
|
InternalServerError,
|
|
|
|
|
2020-05-01 14:56:32 +00:00
|
|
|
/// Could not create a VM
|
|
|
|
VmCreate(ApiError),
|
|
|
|
|
|
|
|
/// Could not boot a VM
|
|
|
|
VmBoot(ApiError),
|
|
|
|
|
2020-05-14 11:00:06 +00:00
|
|
|
/// Could not delete a VM
|
|
|
|
VmDelete(ApiError),
|
|
|
|
|
2020-05-01 14:56:32 +00:00
|
|
|
/// 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),
|
|
|
|
|
2020-09-10 15:34:15 +00:00
|
|
|
/// Could not resize a memory zone
|
|
|
|
VmResizeZone(ApiError),
|
|
|
|
|
2020-05-01 14:56:32 +00:00
|
|
|
/// 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),
|
2020-06-24 10:20:13 +00:00
|
|
|
|
|
|
|
/// Could not get counters from VM
|
|
|
|
VmCounters(ApiError),
|
2020-11-02 15:18:31 +00:00
|
|
|
|
|
|
|
/// Error setting up migration received
|
|
|
|
VmReceiveMigration(ApiError),
|
|
|
|
|
|
|
|
/// Error setting up migration sender
|
|
|
|
VmSendMigration(ApiError),
|
2021-01-13 10:06:36 +00:00
|
|
|
|
|
|
|
/// Error activating power button
|
|
|
|
VmPowerButton(ApiError),
|
2020-05-01 14:56:32 +00:00
|
|
|
}
|
|
|
|
|
2020-05-01 15:11:13 +00:00
|
|
|
impl From<serde_json::Error> for HttpError {
|
|
|
|
fn from(e: serde_json::Error) -> Self {
|
|
|
|
HttpError::SerdeJsonDeserialize(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 09:14:49 +00:00
|
|
|
const HTTP_ROOT: &str = "/api/v1";
|
|
|
|
|
2020-05-01 15:11:13 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-09-18 09:14:49 +00:00
|
|
|
/// 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>,
|
2020-05-01 15:11:13 +00:00
|
|
|
) -> Response {
|
2020-06-24 10:29:18 +00:00
|
|
|
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)
|
2020-06-11 10:01:04 +00:00
|
|
|
}
|
2020-06-24 10:29:18 +00:00
|
|
|
}
|
|
|
|
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),
|
2020-05-01 15:11:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn put_handler(
|
|
|
|
&self,
|
|
|
|
_api_notifier: EventFd,
|
|
|
|
_api_sender: Sender<ApiRequest>,
|
|
|
|
_body: &Option<Body>,
|
2020-06-11 10:01:04 +00:00
|
|
|
) -> std::result::Result<Option<Body>, HttpError> {
|
2020-05-01 15:11:13 +00:00
|
|
|
Err(HttpError::BadRequest)
|
|
|
|
}
|
2020-06-24 10:29:18 +00:00
|
|
|
|
|
|
|
fn get_handler(
|
|
|
|
&self,
|
|
|
|
_api_notifier: EventFd,
|
|
|
|
_api_sender: Sender<ApiRequest>,
|
|
|
|
_body: &Option<Body>,
|
|
|
|
) -> std::result::Result<Option<Body>, HttpError> {
|
|
|
|
Err(HttpError::BadRequest)
|
|
|
|
}
|
2019-09-18 09:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 = {
|
2019-09-26 23:37:06 +00:00
|
|
|
let mut r = HttpRoutes {
|
2019-09-18 09:14:49 +00:00
|
|
|
routes: HashMap::new(),
|
|
|
|
};
|
|
|
|
|
2020-06-24 09:55:03 +00:00
|
|
|
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()))));
|
2019-09-26 23:37:06 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot)));
|
2020-06-24 10:20:13 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.counters"), Box::new(VmActionHandler::new(VmAction::Counters)));
|
2020-06-24 09:55:03 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
|
2019-10-01 15:24:39 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.delete"), Box::new(VmActionHandler::new(VmAction::Delete)));
|
2019-10-01 12:07:15 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
|
2019-10-10 15:16:58 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause)));
|
2021-01-13 10:06:36 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.power-button"), Box::new(VmActionHandler::new(VmAction::PowerButton)));
|
2020-06-24 09:55:03 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
|
2020-11-02 15:18:31 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.receive-migration"), Box::new(VmActionHandler::new(VmAction::ReceiveMigration(Arc::default()))));
|
2020-06-24 09:55:03 +00:00
|
|
|
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()))));
|
2020-09-10 15:34:15 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.resize-zone"), Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))));
|
2020-06-24 09:55:03 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))));
|
2019-10-10 15:34:55 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
|
2020-11-02 15:18:31 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.send-migration"), Box::new(VmActionHandler::new(VmAction::SendMigration(Arc::default()))));
|
2019-09-26 23:37:06 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
|
2020-05-14 12:34:41 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))));
|
2019-11-04 18:06:45 +00:00
|
|
|
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
|
2020-06-24 09:55:03 +00:00
|
|
|
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
|
2019-09-26 23:37:06 +00:00
|
|
|
|
2019-09-18 09:14:49 +00:00
|
|
|
r
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-08 21:59:08 +00:00
|
|
|
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()),
|
2020-05-26 22:20:59 +00:00
|
|
|
Err(_) => error_response(
|
|
|
|
HttpError::InternalServerError,
|
|
|
|
StatusCode::InternalServerError,
|
|
|
|
),
|
2019-11-08 21:59:08 +00:00
|
|
|
},
|
2020-05-26 22:20:59 +00:00
|
|
|
None => error_response(HttpError::NotFound, StatusCode::NotFound),
|
2019-11-08 21:59:08 +00:00
|
|
|
};
|
2019-09-18 09:14:49 +00:00
|
|
|
|
2019-11-08 21:59:08 +00:00
|
|
|
response.set_server("Cloud Hypervisor API");
|
|
|
|
response.set_content_type(MediaType::ApplicationJson);
|
|
|
|
response
|
2019-09-18 09:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_http_thread(
|
|
|
|
path: &str,
|
|
|
|
api_notifier: EventFd,
|
|
|
|
api_sender: Sender<ApiRequest>,
|
2020-07-30 21:21:58 +00:00
|
|
|
seccomp_action: &SeccompAction,
|
2019-09-18 09:14:49 +00:00
|
|
|
) -> Result<thread::JoinHandle<Result<()>>> {
|
2019-11-08 21:59:08 +00:00
|
|
|
std::fs::remove_file(path).unwrap_or_default();
|
|
|
|
let socket_path = PathBuf::from(path);
|
2021-03-17 09:19:30 +00:00
|
|
|
let mut server = HttpServer::new(socket_path).map_err(Error::CreatingAPIServer)?;
|
2019-09-18 09:14:49 +00:00
|
|
|
|
2020-03-20 16:31:15 +00:00
|
|
|
// Retrieve seccomp filter for API thread
|
|
|
|
let api_seccomp_filter =
|
2020-07-30 21:21:58 +00:00
|
|
|
get_seccomp_filter(seccomp_action, Thread::Api).map_err(Error::CreateSeccompFilter)?;
|
2020-03-20 16:31:15 +00:00
|
|
|
|
2019-09-18 09:14:49 +00:00
|
|
|
thread::Builder::new()
|
|
|
|
.name("http-server".to_string())
|
|
|
|
.spawn(move || {
|
2020-03-20 16:31:15 +00:00
|
|
|
// Apply seccomp filter for API thread.
|
|
|
|
SeccompFilter::apply(api_seccomp_filter).map_err(Error::ApplySeccompFilter)?;
|
|
|
|
|
2019-11-08 21:59:08 +00:00
|
|
|
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
|
|
|
|
);
|
2019-09-18 09:14:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.map_err(Error::HttpThreadSpawn)
|
|
|
|
}
|