mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-09-19 13:30:56 +00:00
c652625beb
Extend the EndpointHandler trait to include automatic support for handling PUT requests. This will allow the removal of lots of duplicated code in the following commit from the API handling code. Signed-off-by: Rob Bradford <robert.bradford@intel.com>
252 lines
8.4 KiB
Rust
252 lines
8.4 KiB
Rust
// 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 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<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 {
|
|
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<ApiRequest>,
|
|
_body: &Option<Body>,
|
|
) -> 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<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.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<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(_) => 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<ApiRequest>,
|
|
seccomp_level: &SeccompLevel,
|
|
) -> Result<thread::JoinHandle<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)
|
|
}
|