vmm: use trait objects for API actions

Uses of the old ApiRequest enum conflated two different concerns:
identifying an API request endpoint, and storing data for an API
request.  This led to ApiRequest values being passed around with junk
data just to communicate a request type, which forced all API request
body types to implement Default, which in some cases doesn't make any
sense — what's the "default" path for a vhost-user socket?  The
nonsensical Default values have led to tests relying on being able to
use nonsensical data, which is an impediment to adding better
validation for these types.

Rather than having API request types be represented by an enum, which
has to carry associated body data everywhere it's used, it makes more
sense to represent API request types as trait objects.  These can have
an associated type for the type of the request body, and this makes it
possible to pass API request types and data around as siblings in a
type-safe way without forcing them into a single value even where it
doesn't make sense.  Trait objects also give us dynamic dispatch,
which lets us get rid of several large match blocks.

To keep it possible to fuzz the HTTP API, all the Vmm methods called
by the HTTP API are pulled out into a trait, so the fuzzer can provide
its own stub implementation of the VMM.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
This commit is contained in:
Alyssa Ross 2024-01-05 15:08:53 +01:00 committed by Rob Bradford
parent 6aa7afbb6f
commit 4ca18c082e
9 changed files with 2041 additions and 1409 deletions

1
fuzz/Cargo.lock generated
View File

@ -252,6 +252,7 @@ dependencies = [
"virtio-queue",
"vm-device",
"vm-memory",
"vm-migration",
"vm-virtio",
"vmm",
"vmm-sys-util 0.11.2",

View File

@ -27,6 +27,7 @@ virtio-queue = "0.10.0"
vmm = { path = "../vmm" }
vmm-sys-util = "0.11.2"
vm-memory = "0.13.1"
vm-migration = { path = "../vm-migration" }
vm-device = { path = "../vm-device" }
vm-virtio = { path = "../vm-virtio" }

View File

@ -7,9 +7,18 @@ use libfuzzer_sys::fuzz_target;
use micro_http::Request;
use once_cell::sync::Lazy;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, Mutex};
use std::thread;
use vmm::api::{http::*, ApiRequest, ApiResponsePayload};
use vm_migration::MigratableError;
use vmm::api::{
http::*, ApiRequest, RequestHandler, VmInfoResponse, VmReceiveMigrationData,
VmSendMigrationData, VmmPingResponse,
};
use vmm::config::RestoreConfig;
use vmm::vm::{Error as VmError, VmState};
use vmm::vm_config::*;
use vmm::{EpollContext, EpollDispatch};
use vmm_sys_util::eventfd::EventFd;
@ -69,6 +78,197 @@ fn generate_request(bytes: &[u8]) -> Option<Request> {
Request::try_from(&request, None).ok()
}
struct StubApiRequestHandler;
impl RequestHandler for StubApiRequestHandler {
fn vm_create(&mut self, _: Arc<Mutex<VmConfig>>) -> Result<(), VmError> {
Ok(())
}
fn vm_boot(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_pause(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_resume(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_snapshot(&mut self, _: &str) -> Result<(), VmError> {
Ok(())
}
fn vm_restore(&mut self, _: RestoreConfig) -> Result<(), VmError> {
Ok(())
}
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
fn vm_coredump(&mut self, _: &str) -> Result<(), VmError> {
Ok(())
}
fn vm_shutdown(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_reboot(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_info(&self) -> Result<VmInfoResponse, VmError> {
Ok(VmInfoResponse {
config: Arc::new(Mutex::new(VmConfig {
cpus: CpusConfig {
boot_vcpus: 1,
max_vcpus: 1,
topology: None,
kvm_hyperv: false,
max_phys_bits: 46,
affinity: None,
features: CpuFeatures::default(),
},
memory: MemoryConfig {
size: 536_870_912,
mergeable: false,
hotplug_method: HotplugMethod::Acpi,
hotplug_size: None,
hotplugged_size: None,
shared: false,
hugepages: false,
hugepage_size: None,
prefault: false,
zones: None,
thp: true,
},
payload: Some(PayloadConfig {
kernel: Some(PathBuf::from("/path/to/kernel")),
..Default::default()
}),
rate_limit_groups: None,
disks: None,
net: None,
rng: RngConfig {
src: PathBuf::from("/dev/urandom"),
iommu: false,
},
balloon: None,
fs: None,
pmem: None,
serial: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Null,
iommu: false,
socket: None,
},
console: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Tty,
iommu: false,
socket: None,
},
devices: None,
user_devices: None,
vdpa: None,
vsock: None,
pvpanic: false,
iommu: false,
#[cfg(target_arch = "x86_64")]
sgx_epc: None,
numa: None,
watchdog: false,
#[cfg(feature = "guest_debug")]
gdb: false,
platform: None,
tpm: None,
preserved_fds: None,
})),
state: VmState::Running,
memory_actual_size: 0,
device_tree: None,
})
}
fn vmm_ping(&self) -> VmmPingResponse {
VmmPingResponse {
build_version: String::new(),
version: String::new(),
pid: 0,
features: Vec::new(),
}
}
fn vm_delete(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vmm_shutdown(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_resize(&mut self, _: Option<u8>, _: Option<u64>, _: Option<u64>) -> Result<(), VmError> {
Ok(())
}
fn vm_resize_zone(&mut self, _: String, _: u64) -> Result<(), VmError> {
Ok(())
}
fn vm_add_device(&mut self, _: DeviceConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_add_user_device(&mut self, _: UserDeviceConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_remove_device(&mut self, _: String) -> Result<(), VmError> {
Ok(())
}
fn vm_add_disk(&mut self, _: DiskConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_add_fs(&mut self, _: FsConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_add_pmem(&mut self, _: PmemConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_add_net(&mut self, _: NetConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_add_vdpa(&mut self, _: VdpaConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_add_vsock(&mut self, _: VsockConfig) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_counters(&mut self) -> Result<Option<Vec<u8>>, VmError> {
Ok(None)
}
fn vm_power_button(&mut self) -> Result<(), VmError> {
Ok(())
}
fn vm_receive_migration(&mut self, _: VmReceiveMigrationData) -> Result<(), MigratableError> {
Ok(())
}
fn vm_send_migration(&mut self, _: VmSendMigrationData) -> Result<(), MigratableError> {
Ok(())
}
}
fn http_receiver_stub(exit_evt: EventFd, api_evt: EventFd, api_receiver: Receiver<ApiRequest>) {
let mut epoll = EpollContext::new().unwrap();
epoll.add_event(&exit_evt, EpollDispatch::Exit).unwrap();
@ -98,89 +298,7 @@ fn http_receiver_stub(exit_evt: EventFd, api_evt: EventFd, api_receiver: Receive
EpollDispatch::Api => {
for _ in 0..api_evt.read().unwrap() {
let api_request = api_receiver.recv().unwrap();
match api_request {
ApiRequest::VmCreate(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmDelete(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmBoot(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmShutdown(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmReboot(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmInfo(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmmPing(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmPause(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmResume(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmSnapshot(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmRestore(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmmShutdown(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmResize(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmResizeZone(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddDevice(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddUserDevice(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmRemoveDevice(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddDisk(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddFs(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddPmem(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddNet(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddVdpa(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmAddVsock(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmCounters(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmReceiveMigration(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmSendMigration(_, sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
ApiRequest::VmPowerButton(sender) => {
sender.send(Ok(ApiResponsePayload::Empty)).unwrap();
}
}
api_request(&mut StubApiRequestHandler).unwrap();
}
}
_ => {

View File

@ -20,6 +20,7 @@ use std::sync::{Arc, Mutex};
use thiserror::Error;
#[cfg(feature = "dbus_api")]
use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions};
use vmm::api::ApiAction;
use vmm::config;
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::signal::block_signal;
@ -695,22 +696,24 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
// Create and boot the VM based off the VM config we just built.
let sender = api_request_sender.clone();
vmm::api::vm_create(
api_evt.try_clone().unwrap(),
api_request_sender,
Arc::new(Mutex::new(vm_config)),
)
.map_err(Error::VmCreate)?;
vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?;
vmm::api::VmCreate
.send(
api_evt.try_clone().unwrap(),
api_request_sender,
Arc::new(Mutex::new(vm_config)),
)
.map_err(Error::VmCreate)?;
vmm::api::VmBoot
.send(api_evt.try_clone().unwrap(), sender, ())
.map_err(Error::VmBoot)?;
} else if let Some(restore_params) = cmd_arguments.get_one::<String>("restore") {
vmm::api::vm_restore(
api_evt.try_clone().unwrap(),
api_request_sender,
Arc::new(
vmm::api::VmRestore
.send(
api_evt.try_clone().unwrap(),
api_request_sender,
config::RestoreConfig::parse(restore_params).map_err(Error::ParsingRestore)?,
),
)
.map_err(Error::VmRestore)?;
)
.map_err(Error::VmRestore)?;
}
Ok(())

View File

@ -2,7 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
//
use super::{ApiRequest, VmAction};
use super::{ApiAction, ApiRequest};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
use crate::api::VmCoredump;
use crate::api::{
AddDisk, Body, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa,
VmAddVsock, VmBoot, VmCounters, VmCreate, VmDelete, VmInfo, VmPause, VmPowerButton, VmReboot,
VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
VmSendMigration, VmShutdown, VmSnapshot, VmmPing, VmmShutdown,
};
use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::{Error as VmmError, Result as VmmResult};
use crate::{NetConfig, VmConfig};
@ -78,11 +86,15 @@ impl DBusApi {
.map_err(|err| fdo::Error::IOError(format!("{err:?}")))
}
async fn vm_action(&self, action: VmAction) -> Result<Optional<String>> {
async fn vm_action<Action: ApiAction<ResponseBody = Option<Body>>>(
&self,
action: &'static Action,
body: Action::RequestBody,
) -> Result<Optional<String>> {
let api_sender = self.clone_api_sender().await;
let api_notifier = self.clone_api_notifier()?;
let result = blocking::unblock(move || super::vm_action(api_notifier, api_sender, action))
let result = blocking::unblock(move || action.send(api_notifier, api_sender, body))
.await
.map_err(api_error)?
// We're using `from_utf8_lossy` here to not deal with the
@ -99,7 +111,7 @@ impl DBusApi {
let api_sender = self.clone_api_sender().await;
let api_notifier = self.clone_api_notifier()?;
let result = blocking::unblock(move || super::vmm_ping(api_notifier, api_sender))
let result = blocking::unblock(move || VmmPing.send(api_notifier, api_sender, ()))
.await
.map_err(api_error)?;
serde_json::to_string(&result).map_err(api_error)
@ -109,26 +121,24 @@ impl DBusApi {
let api_sender = self.clone_api_sender().await;
let api_notifier = self.clone_api_notifier()?;
blocking::unblock(move || super::vmm_shutdown(api_notifier, api_sender))
blocking::unblock(move || VmmShutdown.send(api_notifier, api_sender, ()))
.await
.map_err(api_error)
}
async fn vm_add_device(&self, device_config: String) -> Result<Optional<String>> {
let device_config = serde_json::from_str(&device_config).map_err(api_error)?;
self.vm_action(VmAction::AddDevice(Arc::new(device_config)))
.await
self.vm_action(&VmAddDevice, device_config).await
}
async fn vm_add_disk(&self, disk_config: String) -> Result<Optional<String>> {
let disk_config = serde_json::from_str(&disk_config).map_err(api_error)?;
self.vm_action(VmAction::AddDisk(Arc::new(disk_config)))
.await
self.vm_action(&AddDisk, disk_config).await
}
async fn vm_add_fs(&self, fs_config: String) -> Result<Optional<String>> {
let fs_config = serde_json::from_str(&fs_config).map_err(api_error)?;
self.vm_action(VmAction::AddFs(Arc::new(fs_config))).await
self.vm_action(&VmAddFs, fs_config).await
}
async fn vm_add_net(&self, net_config: String) -> Result<Optional<String>> {
@ -137,35 +147,31 @@ impl DBusApi {
warn!("Ignoring FDs sent via the D-Bus request body");
net_config.fds = None;
}
self.vm_action(VmAction::AddNet(Arc::new(net_config))).await
self.vm_action(&VmAddNet, net_config).await
}
async fn vm_add_pmem(&self, pmem_config: String) -> Result<Optional<String>> {
let pmem_config = serde_json::from_str(&pmem_config).map_err(api_error)?;
self.vm_action(VmAction::AddPmem(Arc::new(pmem_config)))
.await
self.vm_action(&VmAddPmem, pmem_config).await
}
async fn vm_add_user_device(&self, vm_add_user_device: String) -> Result<Optional<String>> {
let vm_add_user_device = serde_json::from_str(&vm_add_user_device).map_err(api_error)?;
self.vm_action(VmAction::AddUserDevice(Arc::new(vm_add_user_device)))
.await
self.vm_action(&VmAddUserDevice, vm_add_user_device).await
}
async fn vm_add_vdpa(&self, vdpa_config: String) -> Result<Optional<String>> {
let vdpa_config = serde_json::from_str(&vdpa_config).map_err(api_error)?;
self.vm_action(VmAction::AddVdpa(Arc::new(vdpa_config)))
.await
self.vm_action(&VmAddVdpa, vdpa_config).await
}
async fn vm_add_vsock(&self, vsock_config: String) -> Result<Optional<String>> {
let vsock_config = serde_json::from_str(&vsock_config).map_err(api_error)?;
self.vm_action(VmAction::AddVsock(Arc::new(vsock_config)))
.await
self.vm_action(&VmAddVsock, vsock_config).await
}
async fn vm_boot(&self) -> Result<()> {
self.vm_action(VmAction::Boot).await.map(|_| ())
self.vm_action(&VmBoot, ()).await.map(|_| ())
}
#[allow(unused_variables)]
@ -176,7 +182,7 @@ impl DBusApi {
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
{
let vm_coredump_data = serde_json::from_str(&vm_coredump_data).map_err(api_error)?;
self.vm_action(VmAction::Coredump(Arc::new(vm_coredump_data)))
self.vm_action(&VmCoredump, vm_coredump_data)
.await
.map(|_| ())
}
@ -188,7 +194,7 @@ impl DBusApi {
}
async fn vm_counters(&self) -> Result<Optional<String>> {
self.vm_action(VmAction::Counters).await
self.vm_action(&VmCounters, ()).await
}
async fn vm_create(&self, vm_config: String) -> Result<()> {
@ -207,7 +213,7 @@ impl DBusApi {
}
blocking::unblock(move || {
super::vm_create(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
VmCreate.send(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
})
.await
.map_err(api_error)?;
@ -216,85 +222,81 @@ impl DBusApi {
}
async fn vm_delete(&self) -> Result<()> {
self.vm_action(VmAction::Delete).await.map(|_| ())
self.vm_action(&VmDelete, ()).await.map(|_| ())
}
async fn vm_info(&self) -> Result<String> {
let api_sender = self.clone_api_sender().await;
let api_notifier = self.clone_api_notifier()?;
let result = blocking::unblock(move || super::vm_info(api_notifier, api_sender))
let result = blocking::unblock(move || VmInfo.send(api_notifier, api_sender, ()))
.await
.map_err(api_error)?;
serde_json::to_string(&result).map_err(api_error)
}
async fn vm_pause(&self) -> Result<()> {
self.vm_action(VmAction::Pause).await.map(|_| ())
self.vm_action(&VmPause, ()).await.map(|_| ())
}
async fn vm_power_button(&self) -> Result<()> {
self.vm_action(VmAction::PowerButton).await.map(|_| ())
self.vm_action(&VmPowerButton, ()).await.map(|_| ())
}
async fn vm_reboot(&self) -> Result<()> {
self.vm_action(VmAction::Reboot).await.map(|_| ())
self.vm_action(&VmReboot, ()).await.map(|_| ())
}
async fn vm_remove_device(&self, vm_remove_device: String) -> Result<()> {
let vm_remove_device = serde_json::from_str(&vm_remove_device).map_err(api_error)?;
self.vm_action(VmAction::RemoveDevice(Arc::new(vm_remove_device)))
self.vm_action(&VmRemoveDevice, vm_remove_device)
.await
.map(|_| ())
}
async fn vm_resize(&self, vm_resize: String) -> Result<()> {
let vm_resize = serde_json::from_str(&vm_resize).map_err(api_error)?;
self.vm_action(VmAction::Resize(Arc::new(vm_resize)))
.await
.map(|_| ())
self.vm_action(&VmResize, vm_resize).await.map(|_| ())
}
async fn vm_resize_zone(&self, vm_resize_zone: String) -> Result<()> {
let vm_resize_zone = serde_json::from_str(&vm_resize_zone).map_err(api_error)?;
self.vm_action(VmAction::ResizeZone(Arc::new(vm_resize_zone)))
self.vm_action(&VmResizeZone, vm_resize_zone)
.await
.map(|_| ())
}
async fn vm_restore(&self, restore_config: String) -> Result<()> {
let restore_config = serde_json::from_str(&restore_config).map_err(api_error)?;
self.vm_action(VmAction::Restore(Arc::new(restore_config)))
.await
.map(|_| ())
self.vm_action(&VmRestore, restore_config).await.map(|_| ())
}
async fn vm_receive_migration(&self, receive_migration_data: String) -> Result<()> {
let receive_migration_data =
serde_json::from_str(&receive_migration_data).map_err(api_error)?;
self.vm_action(VmAction::ReceiveMigration(Arc::new(receive_migration_data)))
self.vm_action(&VmReceiveMigration, receive_migration_data)
.await
.map(|_| ())
}
async fn vm_send_migration(&self, send_migration_data: String) -> Result<()> {
let send_migration_data = serde_json::from_str(&send_migration_data).map_err(api_error)?;
self.vm_action(VmAction::SendMigration(Arc::new(send_migration_data)))
self.vm_action(&VmSendMigration, send_migration_data)
.await
.map(|_| ())
}
async fn vm_resume(&self) -> Result<()> {
self.vm_action(VmAction::Resume).await.map(|_| ())
self.vm_action(&VmResume, ()).await.map(|_| ())
}
async fn vm_shutdown(&self) -> Result<()> {
self.vm_action(VmAction::Shutdown).await.map(|_| ())
self.vm_action(&VmShutdown, ()).await.map(|_| ())
}
async fn vm_snapshot(&self, vm_snapshot_config: String) -> Result<()> {
let vm_snapshot_config = serde_json::from_str(&vm_snapshot_config).map_err(api_error)?;
self.vm_action(VmAction::Snapshot(Arc::new(vm_snapshot_config)))
self.vm_action(&VmSnapshot, vm_snapshot_config)
.await
.map(|_| ())
}

View File

@ -1,17 +1,17 @@
// Copyright © 2019 Intel Corporation
// Copyright 2024 Alyssa Ross <hi@alyssa.is>
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::api::http::{error_response, EndpointHandler, HttpError};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
use crate::api::vm_coredump;
use crate::api::VmCoredump;
use crate::api::{
vm_add_device, vm_add_disk, vm_add_fs, vm_add_net, vm_add_pmem, vm_add_user_device,
vm_add_vdpa, vm_add_vsock, vm_boot, vm_counters, vm_create, vm_delete, vm_info, vm_pause,
vm_power_button, vm_reboot, vm_receive_migration, vm_remove_device, vm_resize, vm_resize_zone,
vm_restore, vm_resume, vm_send_migration, vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown,
ApiRequest, VmAction, VmConfig,
AddDisk, ApiAction, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmPause, VmPowerButton,
VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
VmSendMigration, VmShutdown, VmSnapshot,
};
use crate::config::NetConfig;
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
@ -52,8 +52,8 @@ impl EndpointHandler for VmCreate {
}
}
// Call vm_create()
match vm_create(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
match crate::api::VmCreate
.send(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
.map_err(HttpError::ApiError)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
@ -70,13 +70,162 @@ impl EndpointHandler for VmCreate {
}
}
pub trait GetHandler {
fn handle_request(
&'static self,
_api_notifier: EventFd,
_api_sender: Sender<ApiRequest>,
) -> std::result::Result<Option<Body>, HttpError> {
Err(HttpError::BadRequest)
}
}
pub trait PutHandler {
fn handle_request(
&'static self,
_api_notifier: EventFd,
_api_sender: Sender<ApiRequest>,
_body: &Option<Body>,
_files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
Err(HttpError::BadRequest)
}
}
pub trait HttpVmAction: GetHandler + PutHandler + Sync {}
impl<T: GetHandler + PutHandler + Sync> HttpVmAction for T {}
macro_rules! vm_action_get_handler {
($action:ty) => {
impl GetHandler for $action {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> std::result::Result<Option<Body>, HttpError> {
self.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
}
}
impl PutHandler for $action {}
};
}
macro_rules! vm_action_put_handler {
($action:ty) => {
impl PutHandler for $action {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
_files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
if body.is_some() {
Err(HttpError::BadRequest)
} else {
self.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
}
}
}
impl GetHandler for $action {}
};
}
macro_rules! vm_action_put_handler_body {
($action:ty) => {
impl PutHandler for $action {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
_files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
if let Some(body) = body {
self.send(
api_notifier,
api_sender,
serde_json::from_slice(body.raw())?,
)
.map_err(HttpError::ApiError)
} else {
Err(HttpError::BadRequest)
}
}
}
impl GetHandler for $action {}
};
}
vm_action_get_handler!(VmCounters);
vm_action_put_handler!(VmBoot);
vm_action_put_handler!(VmDelete);
vm_action_put_handler!(VmShutdown);
vm_action_put_handler!(VmReboot);
vm_action_put_handler!(VmPause);
vm_action_put_handler!(VmResume);
vm_action_put_handler!(VmPowerButton);
vm_action_put_handler_body!(VmAddDevice);
vm_action_put_handler_body!(AddDisk);
vm_action_put_handler_body!(VmAddFs);
vm_action_put_handler_body!(VmAddPmem);
vm_action_put_handler_body!(VmAddVdpa);
vm_action_put_handler_body!(VmAddVsock);
vm_action_put_handler_body!(VmAddUserDevice);
vm_action_put_handler_body!(VmRemoveDevice);
vm_action_put_handler_body!(VmResize);
vm_action_put_handler_body!(VmResizeZone);
vm_action_put_handler_body!(VmRestore);
vm_action_put_handler_body!(VmSnapshot);
vm_action_put_handler_body!(VmReceiveMigration);
vm_action_put_handler_body!(VmSendMigration);
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
vm_action_put_handler_body!(VmCoredump);
impl PutHandler for VmAddNet {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
mut files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
if let Some(body) = body {
let mut net_cfg: NetConfig = serde_json::from_slice(body.raw())?;
if net_cfg.fds.is_some() {
warn!("Ignoring FDs sent via the HTTP request body");
net_cfg.fds = None;
}
if !files.is_empty() {
let fds = files.drain(..).map(|f| f.into_raw_fd()).collect();
net_cfg.fds = Some(fds);
}
self.send(api_notifier, api_sender, net_cfg)
.map_err(HttpError::ApiError)
} else {
Err(HttpError::BadRequest)
}
}
}
impl GetHandler for VmAddNet {}
// Common handler for boot, shutdown and reboot
pub struct VmActionHandler {
action: VmAction,
action: &'static dyn HttpVmAction,
}
impl VmActionHandler {
pub fn new(action: VmAction) -> Self {
pub fn new(action: &'static dyn HttpVmAction) -> Self {
VmActionHandler { action }
}
}
@ -87,117 +236,9 @@ impl EndpointHandler for VmActionHandler {
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
mut files: Vec<File>,
files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
use VmAction::*;
if let Some(body) = body {
match self.action {
AddDevice(_) => vm_add_device(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddDisk(_) => vm_add_disk(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddFs(_) => vm_add_fs(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddPmem(_) => vm_add_pmem(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddNet(_) => {
let mut net_cfg: NetConfig = serde_json::from_slice(body.raw())?;
if net_cfg.fds.is_some() {
warn!("Ignoring FDs sent via the HTTP request body");
net_cfg.fds = None;
}
// Update network config with optional files that might have
// been sent through control message.
if !files.is_empty() {
let fds = files.drain(..).map(|f| f.into_raw_fd()).collect();
net_cfg.fds = Some(fds);
}
vm_add_net(api_notifier, api_sender, Arc::new(net_cfg))
}
AddVdpa(_) => vm_add_vdpa(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddVsock(_) => vm_add_vsock(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddUserDevice(_) => vm_add_user_device(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
RemoveDevice(_) => vm_remove_device(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
Resize(_) => vm_resize(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
ResizeZone(_) => vm_resize_zone(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
Restore(_) => vm_restore(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
Snapshot(_) => vm_snapshot(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
Coredump(_) => vm_coredump(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
ReceiveMigration(_) => vm_receive_migration(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
SendMigration(_) => vm_send_migration(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
_ => return Err(HttpError::BadRequest),
}
} else {
match self.action {
Boot => vm_boot(api_notifier, api_sender),
Delete => vm_delete(api_notifier, api_sender),
Shutdown => vm_shutdown(api_notifier, api_sender),
Reboot => vm_reboot(api_notifier, api_sender),
Pause => vm_pause(api_notifier, api_sender),
Resume => vm_resume(api_notifier, api_sender),
PowerButton => vm_power_button(api_notifier, api_sender),
_ => return Err(HttpError::BadRequest),
}
}
.map_err(HttpError::ApiError)
PutHandler::handle_request(self.action, api_notifier, api_sender, body, files)
}
fn get_handler(
@ -206,11 +247,7 @@ impl EndpointHandler for VmActionHandler {
api_sender: Sender<ApiRequest>,
_body: &Option<Body>,
) -> std::result::Result<Option<Body>, HttpError> {
use VmAction::*;
match self.action {
Counters => vm_counters(api_notifier, api_sender).map_err(HttpError::ApiError),
_ => Err(HttpError::BadRequest),
}
GetHandler::handle_request(self.action, api_notifier, api_sender)
}
}
@ -225,7 +262,10 @@ impl EndpointHandler for VmInfo {
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Get => match vm_info(api_notifier, api_sender).map_err(HttpError::ApiError) {
Method::Get => match crate::api::VmInfo
.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
{
Ok(info) => {
let mut response = Response::new(Version::Http11, StatusCode::OK);
let info_serialized = serde_json::to_string(&info).unwrap();
@ -251,7 +291,10 @@ impl EndpointHandler for VmmPing {
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Get => match vmm_ping(api_notifier, api_sender).map_err(HttpError::ApiError) {
Method::Get => match crate::api::VmmPing
.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
{
Ok(pong) => {
let mut response = Response::new(Version::Http11, StatusCode::OK);
let info_serialized = serde_json::to_string(&pong).unwrap();
@ -279,7 +322,10 @@ impl EndpointHandler for VmmShutdown {
) -> Response {
match req.method() {
Method::Put => {
match vmm_shutdown(api_notifier, api_sender).map_err(HttpError::ApiError) {
match crate::api::VmmShutdown
.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
{
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
Err(e) => error_response(e, StatusCode::InternalServerError),
}

View File

@ -4,7 +4,14 @@
//
use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdown};
use crate::api::{ApiError, ApiRequest, VmAction};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
use crate::api::VmCoredump;
use crate::api::{
AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmPause, VmPowerButton, VmReboot,
VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
VmSendMigration, VmShutdown, VmSnapshot,
};
use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::{Error as VmmError, Result};
use hypervisor::HypervisorType;
@ -19,7 +26,6 @@ use std::os::unix::net::UnixListener;
use std::panic::AssertUnwindSafe;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::thread;
use vmm_sys_util::eventfd::EventFd;
@ -141,109 +147,103 @@ pub static HTTP_ROUTES: Lazy<HttpRoutes> = Lazy::new(|| {
r.routes.insert(
endpoint!("/vm.add-device"),
Box::new(VmActionHandler::new(VmAction::AddDevice(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddDevice)),
);
r.routes.insert(
endpoint!("/vm.add-user-device"),
Box::new(VmActionHandler::new(
VmAction::AddUserDevice(Arc::default()),
)),
Box::new(VmActionHandler::new(&VmAddUserDevice)),
);
r.routes.insert(
endpoint!("/vm.add-disk"),
Box::new(VmActionHandler::new(VmAction::AddDisk(Arc::default()))),
Box::new(VmActionHandler::new(&AddDisk)),
);
r.routes.insert(
endpoint!("/vm.add-fs"),
Box::new(VmActionHandler::new(VmAction::AddFs(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddFs)),
);
r.routes.insert(
endpoint!("/vm.add-net"),
Box::new(VmActionHandler::new(VmAction::AddNet(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddNet)),
);
r.routes.insert(
endpoint!("/vm.add-pmem"),
Box::new(VmActionHandler::new(VmAction::AddPmem(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddPmem)),
);
r.routes.insert(
endpoint!("/vm.add-vdpa"),
Box::new(VmActionHandler::new(VmAction::AddVdpa(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddVdpa)),
);
r.routes.insert(
endpoint!("/vm.add-vsock"),
Box::new(VmActionHandler::new(VmAction::AddVsock(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddVsock)),
);
r.routes.insert(
endpoint!("/vm.boot"),
Box::new(VmActionHandler::new(VmAction::Boot)),
Box::new(VmActionHandler::new(&VmBoot)),
);
r.routes.insert(
endpoint!("/vm.counters"),
Box::new(VmActionHandler::new(VmAction::Counters)),
Box::new(VmActionHandler::new(&VmCounters)),
);
r.routes
.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
r.routes.insert(
endpoint!("/vm.delete"),
Box::new(VmActionHandler::new(VmAction::Delete)),
Box::new(VmActionHandler::new(&VmDelete)),
);
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
r.routes.insert(
endpoint!("/vm.pause"),
Box::new(VmActionHandler::new(VmAction::Pause)),
Box::new(VmActionHandler::new(&VmPause)),
);
r.routes.insert(
endpoint!("/vm.power-button"),
Box::new(VmActionHandler::new(VmAction::PowerButton)),
Box::new(VmActionHandler::new(&VmPowerButton)),
);
r.routes.insert(
endpoint!("/vm.reboot"),
Box::new(VmActionHandler::new(VmAction::Reboot)),
Box::new(VmActionHandler::new(&VmReboot)),
);
r.routes.insert(
endpoint!("/vm.receive-migration"),
Box::new(VmActionHandler::new(VmAction::ReceiveMigration(
Arc::default(),
))),
Box::new(VmActionHandler::new(&VmReceiveMigration)),
);
r.routes.insert(
endpoint!("/vm.remove-device"),
Box::new(VmActionHandler::new(VmAction::RemoveDevice(Arc::default()))),
Box::new(VmActionHandler::new(&VmRemoveDevice)),
);
r.routes.insert(
endpoint!("/vm.resize"),
Box::new(VmActionHandler::new(VmAction::Resize(Arc::default()))),
Box::new(VmActionHandler::new(&VmResize)),
);
r.routes.insert(
endpoint!("/vm.resize-zone"),
Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))),
Box::new(VmActionHandler::new(&VmResizeZone)),
);
r.routes.insert(
endpoint!("/vm.restore"),
Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))),
Box::new(VmActionHandler::new(&VmRestore)),
);
r.routes.insert(
endpoint!("/vm.resume"),
Box::new(VmActionHandler::new(VmAction::Resume)),
Box::new(VmActionHandler::new(&VmResume)),
);
r.routes.insert(
endpoint!("/vm.send-migration"),
Box::new(VmActionHandler::new(
VmAction::SendMigration(Arc::default()),
)),
Box::new(VmActionHandler::new(&VmSendMigration)),
);
r.routes.insert(
endpoint!("/vm.shutdown"),
Box::new(VmActionHandler::new(VmAction::Shutdown)),
Box::new(VmActionHandler::new(&VmShutdown)),
);
r.routes.insert(
endpoint!("/vm.snapshot"),
Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))),
Box::new(VmActionHandler::new(&VmSnapshot)),
);
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
r.routes.insert(
endpoint!("/vm.coredump"),
Box::new(VmActionHandler::new(VmAction::Coredump(Arc::default()))),
Box::new(VmActionHandler::new(&VmCoredump)),
);
r.routes
.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff