mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-12-22 13:45:20 +00:00
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:
parent
e50f4418a2
commit
2371325f9c
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -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"
|
||||
|
@ -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
127
vmm/src/api/http.rs
Normal 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)
|
||||
}
|
@ -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;
|
||||
|
@ -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),
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user