vmm: api: Add HTTP server

The Cloud Hyper HTTP server runs a synchronous, multi-threaded
loop that receives HTTP requests and tries to call the corresponding
endpoint handlers for the requests URIs.

An endpoint handler will parse the HTTP request and potentially
translate it into and IPC request. The handler holds an notifier and an
mspc Sender for respectively notifying and sending the IPC payload to
the VMM API server. The handler then waits for an API server response
and translate it back into an HTTP response.
The HTTP server is responsible for sending the reponse back to the
caller.

The HTTP server uses a static routes hash table that maps URIs to
endpoint handlers.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Samuel Ortiz 2019-09-18 11:14:49 +02:00
parent e50f4418a2
commit 2371325f9c
5 changed files with 170 additions and 0 deletions

25
Cargo.lock generated
View File

@ -403,6 +403,10 @@ name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "micro_http"
version = "0.1.0"
[[package]]
name = "net_gen"
version = "0.1.0"
@ -429,6 +433,14 @@ name = "nodrop"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl-sys"
version = "0.9.50"
@ -921,6 +933,14 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "threadpool"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.6"
@ -1056,13 +1076,16 @@ dependencies = [
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"micro_http 0.1.0",
"net_util 0.1.0",
"pci 0.1.0",
"qcow 0.1.0",
"signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"vfio 0.0.1",
"vm-allocator 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
@ -1167,6 +1190,7 @@ dependencies = [
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
"checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6"
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
"checksum pnet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63d693c84430248366146e3181ff9d330243464fa9e6146c372b2f3eb2e2d8e7"
@ -1219,6 +1243,7 @@ dependencies = [
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"

View File

@ -18,8 +18,10 @@ devices = { path = "../devices" }
epoll = "4.1.0"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
lazy_static = "1.4.0"
libc = "0.2.62"
log = "0.4.8"
micro_http = { path = "../micro_http" }
net_util = { path = "../net_util" }
pci = {path = "../pci", optional = true}
qcow = { path = "../qcow" }
@ -28,6 +30,7 @@ vm-virtio = { path = "../vm-virtio" }
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
signal-hook = "0.1.10"
threadpool = "1.0"
[dependencies.linux-loader]
git = "https://github.com/rust-vmm/linux-loader"

127
vmm/src/api/http.rs Normal file
View File

@ -0,0 +1,127 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
extern crate threadpool;
extern crate vmm_sys_util;
use crate::api::ApiRequest;
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 = {
let r = HttpRoutes {
routes: HashMap::new(),
};
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();
let response = match HTTP_ROUTES.routes.get(&path) {
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),
};
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)
}

View File

@ -28,6 +28,12 @@
//! response channel Receiver.
//! 5. The thread handles the response and forwards potential errors.
extern crate micro_http;
pub use self::http::start_http_thread;
pub mod http;
use crate::config::VmConfig;
use crate::vm::Error;
use std::sync::mpsc::Sender;

View File

@ -3,6 +3,9 @@
// SPDX-License-Identifier: Apache-2.0
//
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate vmm_sys_util;
@ -47,6 +50,9 @@ pub enum Error {
/// Cannot start a VM from the API
ApiVmStart(ApiError),
/// Cannot bind to the UNIX domain socket path
Bind(io::Error),
/// Cannot clone EventFd.
EventFdClone(io::Error),
@ -62,6 +68,9 @@ pub enum Error {
/// Cannot create epoll context.
Epoll(io::Error),
/// Cannot create HTTP thread
HttpThreadSpawn(io::Error),
/// Cannot handle the VM STDIN stream
Stdin(VmError),