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),