From 2371325f9cfc00bab8e3c7d365a215afc218bc3f Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 18 Sep 2019 11:14:49 +0200 Subject: [PATCH] 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 --- Cargo.lock | 25 +++++++++ vmm/Cargo.toml | 3 ++ vmm/src/api/http.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++ vmm/src/api/mod.rs | 6 +++ vmm/src/lib.rs | 9 ++++ 5 files changed, 170 insertions(+) create mode 100644 vmm/src/api/http.rs diff --git a/Cargo.lock b/Cargo.lock index ba0010128..528326a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 53a637fdb..40f5e3a5e 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -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" diff --git a/vmm/src/api/http.rs b/vmm/src/api/http.rs new file mode 100644 index 000000000..930eebca3 --- /dev/null +++ b/vmm/src/api/http.rs @@ -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, + ) -> Response; +} + +/// An HTTP routes structure. +pub struct HttpRoutes { + /// routes is a hash table mapping endpoint URIs to their endpoint handlers. + pub routes: HashMap>, +} + +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( + http_connection: &mut HttpConnection, + api_notifier: EventFd, + api_sender: Sender, +) { + 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, +) -> 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) +} diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index e71f9e151..16166b49e 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -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; diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 9c098f75f..4cc765e82 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -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),