2019-09-18 09:14:49 +00:00
|
|
|
// Copyright © 2019 Intel Corporation
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//
|
|
|
|
|
|
|
|
extern crate threadpool;
|
|
|
|
|
2019-10-01 12:07:15 +00:00
|
|
|
use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo};
|
2019-09-26 23:37:06 +00:00
|
|
|
use crate::api::{ApiRequest, VmAction};
|
2019-09-18 09:14:49 +00:00
|
|
|
use crate::{Error, Result};
|
|
|
|
use micro_http::{HttpConnection, Request, Response, StatusCode, Version};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::io::{Read, Write};
|
|
|
|
use std::os::unix::net::UnixListener;
|
|
|
|
use std::sync::mpsc::Sender;
|
|
|
|
use std::thread;
|
|
|
|
use threadpool::ThreadPool;
|
|
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
|
|
|
|
const HTTP_ROOT: &str = "/api/v1";
|
|
|
|
const NUM_THREADS: usize = 4;
|
|
|
|
|
|
|
|
/// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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(),
|
|
|
|
};
|
|
|
|
|
2019-09-26 23:37:06 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
|
|
|
|
r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot)));
|
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-09-26 23:37:06 +00:00
|
|
|
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
|
|
|
|
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
|
|
|
|
|
2019-09-18 09:14:49 +00:00
|
|
|
r
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn http_serve<T: Read + Write>(
|
|
|
|
http_connection: &mut HttpConnection<T>,
|
|
|
|
api_notifier: EventFd,
|
|
|
|
api_sender: Sender<ApiRequest>,
|
|
|
|
) {
|
|
|
|
if http_connection.try_read().is_err() {
|
|
|
|
http_connection.enqueue_response(Response::new(
|
|
|
|
Version::Http11,
|
|
|
|
StatusCode::InternalServerError,
|
|
|
|
));
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while let Some(request) = http_connection.pop_parsed_request() {
|
|
|
|
let sender = api_sender.clone();
|
|
|
|
let path = request.uri().get_abs_path().to_string();
|
2019-10-04 06:46:54 +00:00
|
|
|
let mut response = match HTTP_ROUTES.routes.get(&path) {
|
2019-09-18 09:14:49 +00:00
|
|
|
Some(route) => match api_notifier.try_clone() {
|
|
|
|
Ok(notifier) => route.handle_request(&request, notifier, sender),
|
|
|
|
Err(_) => Response::new(Version::Http11, StatusCode::InternalServerError),
|
|
|
|
},
|
|
|
|
None => Response::new(Version::Http11, StatusCode::NotFound),
|
|
|
|
};
|
|
|
|
|
2019-10-04 06:46:54 +00:00
|
|
|
response.set_server("Cloud Hypervisor API");
|
2019-09-18 09:14:49 +00:00
|
|
|
http_connection.enqueue_response(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_http_thread(
|
|
|
|
path: &str,
|
|
|
|
api_notifier: EventFd,
|
|
|
|
api_sender: Sender<ApiRequest>,
|
|
|
|
) -> Result<thread::JoinHandle<Result<()>>> {
|
|
|
|
let listener = UnixListener::bind(path).map_err(Error::Bind)?;
|
|
|
|
let pool = ThreadPool::new(NUM_THREADS);
|
|
|
|
|
|
|
|
thread::Builder::new()
|
|
|
|
.name("http-server".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
for stream in listener.incoming() {
|
|
|
|
match stream {
|
|
|
|
Ok(s) => {
|
|
|
|
let sender = api_sender.clone();
|
|
|
|
let notifier = api_notifier.try_clone().map_err(Error::EventFdClone)?;
|
|
|
|
|
|
|
|
pool.execute(move || {
|
|
|
|
let mut http_connection = HttpConnection::new(s);
|
|
|
|
|
|
|
|
http_serve(&mut http_connection, notifier, sender);
|
|
|
|
|
|
|
|
// It's ok to panic from a threadpool managed thread,
|
|
|
|
// it won't make parent threads crash.
|
|
|
|
http_connection.try_write().unwrap();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(_) => continue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pool.join();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.map_err(Error::HttpThreadSpawn)
|
|
|
|
}
|