vmm: http: graceful shutdown of the http api thread

This commit ensures that the HttpApi thread flushes all the responses
before the application shuts down. Without this step, in case of a
VmmShutdown request the application might terminate before the
thread sends a response.

Fixes: #6247

Signed-off-by: Alexandru Matei <alexandru.matei@uipath.com>
This commit is contained in:
Alexandru Matei 2024-02-22 11:41:14 +02:00 committed by Rob Bradford
parent 3f2ca5375e
commit 1091494320
4 changed files with 50 additions and 12 deletions

View File

@ -20,6 +20,7 @@ use std::sync::{Arc, Mutex};
use thiserror::Error; use thiserror::Error;
#[cfg(feature = "dbus_api")] #[cfg(feature = "dbus_api")]
use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions}; use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions};
use vmm::api::http::http_api_graceful_shutdown;
use vmm::api::ApiAction; use vmm::api::ApiAction;
use vmm::config; use vmm::config;
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@ -82,6 +83,8 @@ enum Error {
LogFileCreation(std::io::Error), LogFileCreation(std::io::Error),
#[error("Error setting up logger: {0}")] #[error("Error setting up logger: {0}")]
LoggerSetup(log::SetLoggerError), LoggerSetup(log::SetLoggerError),
#[error("Failed to gracefully shutdown http api: {0}")]
HttpApiShutdown(#[source] vmm::Error),
} }
struct Logger { struct Logger {
@ -760,6 +763,10 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
.map_err(Error::ThreadJoin)? .map_err(Error::ThreadJoin)?
.map_err(Error::VmmThread)?; .map_err(Error::VmmThread)?;
if let Some(api_handle) = vmm_thread_handle.http_api_handle {
http_api_graceful_shutdown(api_handle).map_err(Error::HttpApiShutdown)?
}
#[cfg(feature = "dbus_api")] #[cfg(feature = "dbus_api")]
if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs { if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs {
dbus_api_graceful_shutdown(chs); dbus_api_graceful_shutdown(chs);

View File

@ -16,7 +16,9 @@ use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::{Error as VmmError, Result}; use crate::{Error as VmmError, Result};
use core::fmt; use core::fmt;
use hypervisor::HypervisorType; use hypervisor::HypervisorType;
use micro_http::{Body, HttpServer, MediaType, Method, Request, Response, StatusCode, Version}; use micro_http::{
Body, HttpServer, MediaType, Method, Request, Response, ServerError, StatusCode, Version,
};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use seccompiler::{apply_filter, SeccompAction}; use seccompiler::{apply_filter, SeccompAction};
use serde_json::Error as SerdeError; use serde_json::Error as SerdeError;
@ -33,6 +35,8 @@ use vmm_sys_util::eventfd::EventFd;
pub mod http_endpoint; pub mod http_endpoint;
pub type HttpApiHandle = (thread::JoinHandle<Result<()>>, EventFd);
/// Errors associated with VMM management /// Errors associated with VMM management
#[derive(Debug)] #[derive(Debug)]
pub enum HttpError { pub enum HttpError {
@ -297,12 +301,19 @@ fn start_http_thread(
seccomp_action: &SeccompAction, seccomp_action: &SeccompAction,
exit_evt: EventFd, exit_evt: EventFd,
hypervisor_type: HypervisorType, hypervisor_type: HypervisorType,
) -> Result<thread::JoinHandle<Result<()>>> { ) -> Result<HttpApiHandle> {
// Retrieve seccomp filter for API thread // Retrieve seccomp filter for API thread
let api_seccomp_filter = get_seccomp_filter(seccomp_action, Thread::HttpApi, hypervisor_type) let api_seccomp_filter = get_seccomp_filter(seccomp_action, Thread::HttpApi, hypervisor_type)
.map_err(VmmError::CreateSeccompFilter)?; .map_err(VmmError::CreateSeccompFilter)?;
thread::Builder::new() let api_shutdown_fd = EventFd::new(libc::EFD_NONBLOCK).map_err(VmmError::EventFdCreate)?;
let api_shutdown_fd_clone = api_shutdown_fd.try_clone().unwrap();
server
.add_kill_switch(api_shutdown_fd_clone)
.map_err(VmmError::CreateApiServer)?;
let thread = thread::Builder::new()
.name("http-server".to_string()) .name("http-server".to_string())
.spawn(move || { .spawn(move || {
// Apply seccomp filter for API thread. // Apply seccomp filter for API thread.
@ -329,6 +340,10 @@ fn start_http_thread(
} }
} }
} }
Err(ServerError::ShutdownEvent) => {
server.flush_outgoing_writes();
return;
}
Err(e) => { Err(e) => {
error!( error!(
"HTTP server error on retrieving incoming request. Error: {}", "HTTP server error on retrieving incoming request. Error: {}",
@ -346,7 +361,9 @@ fn start_http_thread(
Ok(()) Ok(())
}) })
.map_err(VmmError::HttpThreadSpawn) .map_err(VmmError::HttpThreadSpawn)?;
Ok((thread, api_shutdown_fd))
} }
pub fn start_http_path_thread( pub fn start_http_path_thread(
@ -356,12 +373,13 @@ pub fn start_http_path_thread(
seccomp_action: &SeccompAction, seccomp_action: &SeccompAction,
exit_evt: EventFd, exit_evt: EventFd,
hypervisor_type: HypervisorType, hypervisor_type: HypervisorType,
) -> Result<thread::JoinHandle<Result<()>>> { ) -> Result<HttpApiHandle> {
let socket_path = PathBuf::from(path); let socket_path = PathBuf::from(path);
let socket_fd = UnixListener::bind(socket_path).map_err(VmmError::CreateApiServerSocket)?; let socket_fd = UnixListener::bind(socket_path).map_err(VmmError::CreateApiServerSocket)?;
// SAFETY: Valid FD just opened // SAFETY: Valid FD just opened
let server = unsafe { HttpServer::new_from_fd(socket_fd.into_raw_fd()) } let server = unsafe { HttpServer::new_from_fd(socket_fd.into_raw_fd()) }
.map_err(VmmError::CreateApiServer)?; .map_err(VmmError::CreateApiServer)?;
start_http_thread( start_http_thread(
server, server,
api_notifier, api_notifier,
@ -379,7 +397,7 @@ pub fn start_http_fd_thread(
seccomp_action: &SeccompAction, seccomp_action: &SeccompAction,
exit_evt: EventFd, exit_evt: EventFd,
hypervisor_type: HypervisorType, hypervisor_type: HypervisorType,
) -> Result<thread::JoinHandle<Result<()>>> { ) -> Result<HttpApiHandle> {
// SAFETY: Valid FD // SAFETY: Valid FD
let server = unsafe { HttpServer::new_from_fd(fd) }.map_err(VmmError::CreateApiServer)?; let server = unsafe { HttpServer::new_from_fd(fd) }.map_err(VmmError::CreateApiServer)?;
start_http_thread( start_http_thread(
@ -391,3 +409,10 @@ pub fn start_http_fd_thread(
hypervisor_type, hypervisor_type,
) )
} }
pub fn http_api_graceful_shutdown(http_handle: HttpApiHandle) -> Result<()> {
let (api_thread, api_shutdown_fd) = http_handle;
api_shutdown_fd.write(1).unwrap();
api_thread.join().map_err(VmmError::ThreadCleanup)?
}

View File

@ -27,6 +27,7 @@ use crate::vm::{Error as VmError, Vm, VmState};
use anyhow::anyhow; use anyhow::anyhow;
#[cfg(feature = "dbus_api")] #[cfg(feature = "dbus_api")]
use api::dbus::{DBusApiOptions, DBusApiShutdownChannels}; use api::dbus::{DBusApiOptions, DBusApiShutdownChannels};
use api::http::HttpApiHandle;
use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW}; use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW};
use memory_manager::MemoryManagerSnapshotData; use memory_manager::MemoryManagerSnapshotData;
use pci::PciBdf; use pci::PciBdf;
@ -455,25 +456,27 @@ pub fn start_vmm_thread(
None => None, None => None,
}; };
if let Some(http_path) = http_path { let http_api_handle = if let Some(http_path) = http_path {
api::start_http_path_thread( Some(api::start_http_path_thread(
http_path, http_path,
api_event_clone, api_event_clone,
api_sender, api_sender,
seccomp_action, seccomp_action,
exit_event, exit_event,
hypervisor_type, hypervisor_type,
)?; )?)
} else if let Some(http_fd) = http_fd { } else if let Some(http_fd) = http_fd {
api::start_http_fd_thread( Some(api::start_http_fd_thread(
http_fd, http_fd,
api_event_clone, api_event_clone,
api_sender, api_sender,
seccomp_action, seccomp_action,
exit_event, exit_event,
hypervisor_type, hypervisor_type,
)?; )?)
} } else {
None
};
#[cfg(feature = "guest_debug")] #[cfg(feature = "guest_debug")]
if let Some(debug_path) = debug_path { if let Some(debug_path) = debug_path {
@ -493,6 +496,7 @@ pub fn start_vmm_thread(
thread_handle: thread, thread_handle: thread,
#[cfg(feature = "dbus_api")] #[cfg(feature = "dbus_api")]
dbus_shutdown_chs, dbus_shutdown_chs,
http_api_handle,
}) })
} }
@ -523,6 +527,7 @@ pub struct VmmThreadHandle {
pub thread_handle: thread::JoinHandle<Result<()>>, pub thread_handle: thread::JoinHandle<Result<()>>,
#[cfg(feature = "dbus_api")] #[cfg(feature = "dbus_api")]
pub dbus_shutdown_chs: Option<DBusApiShutdownChannels>, pub dbus_shutdown_chs: Option<DBusApiShutdownChannels>,
pub http_api_handle: Option<HttpApiHandle>,
} }
pub struct Vmm { pub struct Vmm {

View File

@ -838,6 +838,7 @@ fn http_api_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError>
(libc::SYS_sched_yield, vec![]), (libc::SYS_sched_yield, vec![]),
(libc::SYS_sigaltstack, vec![]), (libc::SYS_sigaltstack, vec![]),
(libc::SYS_write, vec![]), (libc::SYS_write, vec![]),
(libc::SYS_rt_sigprocmask, vec![]),
]) ])
} }