mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-09-17 20:44:55 +00:00
d4316d0228
In order to let a separate process open a TAP device and pass the file descriptor through the control message mechanism, this patch adds the support for sending a file descriptor over to the Cloud Hypervisor process along with the add-net HTTP API command. The implementation uses the NetConfig structure mutably to update the list of fds with the one passed through control message. The list should always be empty prior to this, as it makes no sense to provide a list of fds once the Cloud Hypervisor process has already been started. It is important to note that reboot is supported since the file descriptor is duplicated upon receival, letting the VM only use the duplicated one. The original file descriptor is kept open in order to support a potential reboot. Fixes #2525 Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
327 lines
12 KiB
Rust
327 lines
12 KiB
Rust
// Copyright © 2019 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdown};
|
|
use crate::api::{ApiError, ApiRequest, VmAction};
|
|
use crate::seccomp_filters::{get_seccomp_filter, Thread};
|
|
use crate::{Error, Result};
|
|
use micro_http::{Body, HttpServer, MediaType, Method, Request, Response, StatusCode, Version};
|
|
use seccomp::{SeccompAction, SeccompFilter};
|
|
use serde_json::Error as SerdeError;
|
|
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
use std::os::unix::io::{IntoRawFd, RawFd};
|
|
use std::os::unix::net::UnixListener;
|
|
use std::path::PathBuf;
|
|
use std::sync::mpsc::Sender;
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
/// Errors associated with VMM management
|
|
#[derive(Debug)]
|
|
pub enum HttpError {
|
|
/// API request receive error
|
|
SerdeJsonDeserialize(SerdeError),
|
|
|
|
/// Attempt to access unsupported HTTP method
|
|
BadRequest,
|
|
|
|
/// Undefined endpoints
|
|
NotFound,
|
|
|
|
/// Internal Server Error
|
|
InternalServerError,
|
|
|
|
/// Could not create a VM
|
|
VmCreate(ApiError),
|
|
|
|
/// Could not boot a VM
|
|
VmBoot(ApiError),
|
|
|
|
/// Could not delete a VM
|
|
VmDelete(ApiError),
|
|
|
|
/// Could not get the VM information
|
|
VmInfo(ApiError),
|
|
|
|
/// Could not pause the VM
|
|
VmPause(ApiError),
|
|
|
|
/// Could not pause the VM
|
|
VmResume(ApiError),
|
|
|
|
/// Could not shut a VM down
|
|
VmShutdown(ApiError),
|
|
|
|
/// Could not reboot a VM
|
|
VmReboot(ApiError),
|
|
|
|
/// Could not snapshot a VM
|
|
VmSnapshot(ApiError),
|
|
|
|
/// Could not restore a VM
|
|
VmRestore(ApiError),
|
|
|
|
/// Could not act on a VM
|
|
VmAction(ApiError),
|
|
|
|
/// Could not resize a VM
|
|
VmResize(ApiError),
|
|
|
|
/// Could not resize a memory zone
|
|
VmResizeZone(ApiError),
|
|
|
|
/// Could not add a device to a VM
|
|
VmAddDevice(ApiError),
|
|
|
|
/// Could not remove a device from a VM
|
|
VmRemoveDevice(ApiError),
|
|
|
|
/// Could not shut the VMM down
|
|
VmmShutdown(ApiError),
|
|
|
|
/// Could not handle VMM ping
|
|
VmmPing(ApiError),
|
|
|
|
/// Could not add a disk to a VM
|
|
VmAddDisk(ApiError),
|
|
|
|
/// Could not add a fs to a VM
|
|
VmAddFs(ApiError),
|
|
|
|
/// Could not add a pmem device to a VM
|
|
VmAddPmem(ApiError),
|
|
|
|
/// Could not add a network device to a VM
|
|
VmAddNet(ApiError),
|
|
|
|
/// Could not add a vsock device to a VM
|
|
VmAddVsock(ApiError),
|
|
|
|
/// Could not get counters from VM
|
|
VmCounters(ApiError),
|
|
|
|
/// Error setting up migration received
|
|
VmReceiveMigration(ApiError),
|
|
|
|
/// Error setting up migration sender
|
|
VmSendMigration(ApiError),
|
|
|
|
/// Error activating power button
|
|
VmPowerButton(ApiError),
|
|
}
|
|
|
|
impl From<serde_json::Error> for HttpError {
|
|
fn from(e: serde_json::Error) -> Self {
|
|
HttpError::SerdeJsonDeserialize(e)
|
|
}
|
|
}
|
|
|
|
const HTTP_ROOT: &str = "/api/v1";
|
|
|
|
pub fn error_response(error: HttpError, status: StatusCode) -> Response {
|
|
let mut response = Response::new(Version::Http11, status);
|
|
response.set_body(Body::new(format!("{:?}", error)));
|
|
|
|
response
|
|
}
|
|
|
|
/// 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 {
|
|
let file = req.file.as_ref().map(|f| f.try_clone().unwrap());
|
|
let res = match req.method() {
|
|
Method::Put => self.put_handler(api_notifier, api_sender, &req.body, file),
|
|
Method::Get => self.get_handler(api_notifier, api_sender, &req.body),
|
|
_ => return Response::new(Version::Http11, StatusCode::BadRequest),
|
|
};
|
|
|
|
match res {
|
|
Ok(response_body) => {
|
|
if let Some(body) = response_body {
|
|
let mut response = Response::new(Version::Http11, StatusCode::OK);
|
|
response.set_body(body);
|
|
response
|
|
} else {
|
|
Response::new(Version::Http11, StatusCode::NoContent)
|
|
}
|
|
}
|
|
Err(e @ HttpError::BadRequest) => error_response(e, StatusCode::BadRequest),
|
|
Err(e @ HttpError::SerdeJsonDeserialize(_)) => {
|
|
error_response(e, StatusCode::BadRequest)
|
|
}
|
|
Err(e) => error_response(e, StatusCode::InternalServerError),
|
|
}
|
|
}
|
|
|
|
fn put_handler(
|
|
&self,
|
|
_api_notifier: EventFd,
|
|
_api_sender: Sender<ApiRequest>,
|
|
_body: &Option<Body>,
|
|
_file: Option<File>,
|
|
) -> std::result::Result<Option<Body>, HttpError> {
|
|
Err(HttpError::BadRequest)
|
|
}
|
|
|
|
fn get_handler(
|
|
&self,
|
|
_api_notifier: EventFd,
|
|
_api_sender: Sender<ApiRequest>,
|
|
_body: &Option<Body>,
|
|
) -> std::result::Result<Option<Body>, HttpError> {
|
|
Err(HttpError::BadRequest)
|
|
}
|
|
}
|
|
|
|
/// 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 mut r = HttpRoutes {
|
|
routes: HashMap::new(),
|
|
};
|
|
|
|
r.routes.insert(endpoint!("/vm.add-device"), Box::new(VmActionHandler::new(VmAction::AddDevice(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.add-disk"), Box::new(VmActionHandler::new(VmAction::AddDisk(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.add-fs"), Box::new(VmActionHandler::new(VmAction::AddFs(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.add-net"), Box::new(VmActionHandler::new(VmAction::AddNet(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.add-pmem"), Box::new(VmActionHandler::new(VmAction::AddPmem(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.add-vsock"), Box::new(VmActionHandler::new(VmAction::AddVsock(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot)));
|
|
r.routes.insert(endpoint!("/vm.counters"), Box::new(VmActionHandler::new(VmAction::Counters)));
|
|
r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
|
|
r.routes.insert(endpoint!("/vm.delete"), Box::new(VmActionHandler::new(VmAction::Delete)));
|
|
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
|
|
r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause)));
|
|
r.routes.insert(endpoint!("/vm.power-button"), Box::new(VmActionHandler::new(VmAction::PowerButton)));
|
|
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
|
|
r.routes.insert(endpoint!("/vm.receive-migration"), Box::new(VmActionHandler::new(VmAction::ReceiveMigration(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmActionHandler::new(VmAction::RemoveDevice(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.resize"), Box::new(VmActionHandler::new(VmAction::Resize(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.resize-zone"), Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
|
|
r.routes.insert(endpoint!("/vm.send-migration"), Box::new(VmActionHandler::new(VmAction::SendMigration(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
|
|
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))));
|
|
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
|
|
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
|
|
|
|
r
|
|
};
|
|
}
|
|
|
|
fn handle_http_request(
|
|
request: &Request,
|
|
api_notifier: &EventFd,
|
|
api_sender: &Sender<ApiRequest>,
|
|
) -> Response {
|
|
let path = request.uri().get_abs_path().to_string();
|
|
let mut response = match HTTP_ROUTES.routes.get(&path) {
|
|
Some(route) => match api_notifier.try_clone() {
|
|
Ok(notifier) => route.handle_request(request, notifier, api_sender.clone()),
|
|
Err(_) => error_response(
|
|
HttpError::InternalServerError,
|
|
StatusCode::InternalServerError,
|
|
),
|
|
},
|
|
None => error_response(HttpError::NotFound, StatusCode::NotFound),
|
|
};
|
|
|
|
response.set_server("Cloud Hypervisor API");
|
|
response.set_content_type(MediaType::ApplicationJson);
|
|
response
|
|
}
|
|
|
|
fn start_http_thread(
|
|
mut server: HttpServer,
|
|
api_notifier: EventFd,
|
|
api_sender: Sender<ApiRequest>,
|
|
seccomp_action: &SeccompAction,
|
|
) -> Result<thread::JoinHandle<Result<()>>> {
|
|
// Retrieve seccomp filter for API thread
|
|
let api_seccomp_filter =
|
|
get_seccomp_filter(seccomp_action, Thread::Api).map_err(Error::CreateSeccompFilter)?;
|
|
|
|
thread::Builder::new()
|
|
.name("http-server".to_string())
|
|
.spawn(move || {
|
|
// Apply seccomp filter for API thread.
|
|
SeccompFilter::apply(api_seccomp_filter).map_err(Error::ApplySeccompFilter)?;
|
|
|
|
server.start_server().unwrap();
|
|
loop {
|
|
match server.requests() {
|
|
Ok(request_vec) => {
|
|
for server_request in request_vec {
|
|
server
|
|
.respond(server_request.process(|request| {
|
|
handle_http_request(request, &api_notifier, &api_sender)
|
|
}))
|
|
.or_else(|e| {
|
|
error!("HTTP server error on response: {}", e);
|
|
Ok(())
|
|
})?;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!(
|
|
"HTTP server error on retrieving incoming request. Error: {}",
|
|
e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.map_err(Error::HttpThreadSpawn)
|
|
}
|
|
|
|
pub fn start_http_path_thread(
|
|
path: &str,
|
|
api_notifier: EventFd,
|
|
api_sender: Sender<ApiRequest>,
|
|
seccomp_action: &SeccompAction,
|
|
) -> Result<thread::JoinHandle<Result<()>>> {
|
|
std::fs::remove_file(path).unwrap_or_default();
|
|
let socket_path = PathBuf::from(path);
|
|
let socket_fd = UnixListener::bind(socket_path).map_err(Error::CreateApiServerSocket)?;
|
|
let server =
|
|
HttpServer::new_from_fd(socket_fd.into_raw_fd()).map_err(Error::CreateApiServer)?;
|
|
start_http_thread(server, api_notifier, api_sender, seccomp_action)
|
|
}
|
|
|
|
pub fn start_http_fd_thread(
|
|
fd: RawFd,
|
|
api_notifier: EventFd,
|
|
api_sender: Sender<ApiRequest>,
|
|
seccomp_action: &SeccompAction,
|
|
) -> Result<thread::JoinHandle<Result<()>>> {
|
|
let server = HttpServer::new_from_fd(fd).map_err(Error::CreateApiServer)?;
|
|
start_http_thread(server, api_notifier, api_sender, seccomp_action)
|
|
}
|