From 0e58741a09ebe8d662bbb11d60e6c2528d1d5a86 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 27 Feb 2020 14:00:46 +0100 Subject: [PATCH] vmm: api: Introduce new "add-device" HTTP endpoint This commit introduces the new command "add-device" that will let a user hotplug a VFIO PCI device to an already running VM. Signed-off-by: Sebastien Boeuf --- vmm/src/api/http.rs | 3 +- vmm/src/api/http_endpoint.rs | 48 ++++++++++++++++++++++- vmm/src/api/mod.rs | 31 ++++++++++++++- vmm/src/api/openapi/cloud-hypervisor.yaml | 22 +++++++++++ vmm/src/device_manager.rs | 4 ++ vmm/src/lib.rs | 20 ++++++++++ vmm/src/vm.rs | 10 +++++ 7 files changed, 134 insertions(+), 4 deletions(-) diff --git a/vmm/src/api/http.rs b/vmm/src/api/http.rs index 37de6dcff..d59e7b776 100644 --- a/vmm/src/api/http.rs +++ b/vmm/src/api/http.rs @@ -4,7 +4,7 @@ // use crate::api::http_endpoint::{ - VmActionHandler, VmCreate, VmInfo, VmResize, VmmPing, VmmShutdown, + VmActionHandler, VmAddDevice, VmCreate, VmInfo, VmResize, VmmPing, VmmShutdown, }; use crate::api::{ApiRequest, VmAction}; use crate::{Error, Result}; @@ -62,6 +62,7 @@ lazy_static! { r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {})); r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {})); r.routes.insert(endpoint!("/vm.resize"), Box::new(VmResize {})); + r.routes.insert(endpoint!("/vm.add-device"), Box::new(VmAddDevice {})); r }; diff --git a/vmm/src/api/http_endpoint.rs b/vmm/src/api/http_endpoint.rs index 615f16a30..ec79a08fb 100644 --- a/vmm/src/api/http_endpoint.rs +++ b/vmm/src/api/http_endpoint.rs @@ -5,8 +5,9 @@ use crate::api::http::EndpointHandler; use crate::api::{ - vm_boot, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_resize, vm_resume, vm_shutdown, - vmm_ping, vmm_shutdown, ApiError, ApiRequest, ApiResult, VmAction, VmConfig, VmResizeData, + vm_add_device, vm_boot, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_resize, + vm_resume, vm_shutdown, vmm_ping, vmm_shutdown, ApiError, ApiRequest, ApiResult, VmAction, + VmAddDeviceData, VmConfig, VmResizeData, }; use micro_http::{Body, Method, Request, Response, StatusCode, Version}; use serde_json::Error as SerdeError; @@ -47,6 +48,9 @@ pub enum HttpError { /// Could not resize a VM VmResize(ApiError), + /// Could not add a device to a VM + VmAddDevice(ApiError), + /// Could not shut the VMM down VmmShutdown(ApiError), @@ -261,3 +265,43 @@ impl EndpointHandler for VmResize { } } } + +// /api/v1/vm.add-device handler +pub struct VmAddDevice {} + +impl EndpointHandler for VmAddDevice { + fn handle_request( + &self, + req: &Request, + api_notifier: EventFd, + api_sender: Sender, + ) -> Response { + match req.method() { + Method::Put => { + match &req.body { + Some(body) => { + // Deserialize into a VmAddDeviceData + let vm_add_device_data: VmAddDeviceData = + match serde_json::from_slice(body.raw()) + .map_err(HttpError::SerdeJsonDeserialize) + { + Ok(config) => config, + Err(e) => return error_response(e, StatusCode::BadRequest), + }; + + // Call vm_add_device() + match vm_add_device(api_notifier, api_sender, Arc::new(vm_add_device_data)) + .map_err(HttpError::VmAddDevice) + { + Ok(_) => Response::new(Version::Http11, StatusCode::NoContent), + Err(e) => error_response(e, StatusCode::InternalServerError), + } + } + + None => Response::new(Version::Http11, StatusCode::BadRequest), + } + } + _ => Response::new(Version::Http11, StatusCode::BadRequest), + } + } +} diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 98f1c4f23..9e2a0cbba 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -99,6 +99,9 @@ pub enum ApiError { /// The VM could not be resized VmResize(VmError), + + /// The device could not be added to the VM. + VmAddDevice(VmError), } pub type ApiResult = std::result::Result; @@ -119,6 +122,11 @@ pub struct VmResizeData { pub desired_ram: Option, } +#[derive(Clone, Deserialize, Serialize)] +pub struct VmAddDeviceData { + pub path: String, +} + pub enum ApiResponsePayload { /// No data is sent on the channel. Empty, @@ -179,8 +187,11 @@ pub enum ApiRequest { /// VMM process. VmmShutdown(Sender), - /// Resize the VMM + /// Resize the VM. VmResize(Arc, Sender), + + /// Add a device to the VM. + VmAddDevice(Arc, Sender), } pub fn vm_create( @@ -333,3 +344,21 @@ pub fn vm_resize( Ok(()) } + +pub fn vm_add_device( + api_evt: EventFd, + api_sender: Sender, + data: Arc, +) -> ApiResult<()> { + let (response_sender, response_receiver) = channel(); + + // Send the VM add-device request. + api_sender + .send(ApiRequest::VmAddDevice(data, response_sender)) + .map_err(ApiError::RequestSend)?; + api_evt.write(1).map_err(ApiError::EventFdWrite)?; + + response_receiver.recv().map_err(ApiError::ResponseRecv)??; + + Ok(()) +} diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index b04a9ce39..fdbc08867 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -139,6 +139,22 @@ paths: 404: description: The VM instance could not be resized because it is not created. + /vm.add-device: + put: + summary: Add a new device to the VM + requestBody: + description: The path of the new device + content: + application/json: + schema: + $ref: '#/components/schemas/VmAddDevice' + required: true + responses: + 204: + description: The new device was successfully added to the VM instance. + 404: + description: The new device could not be added to the VM instance. + components: schemas: @@ -427,3 +443,9 @@ components: type: integer desired_ram: type: integer + + VmAddDevice: + type: object + properties: + path: + type: string diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 90459a6a3..a9beb6ad3 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1585,6 +1585,10 @@ impl DeviceManager { #[cfg(not(feature = "acpi"))] return Ok(()); } + + pub fn add_device(&self, _path: String) -> DeviceManagerResult<()> { + Ok(()) + } } #[cfg(feature = "acpi")] diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 28fa28283..02bdc0e59 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -372,6 +372,19 @@ impl Vmm { } } + fn vm_add_device(&mut self, path: String) -> result::Result<(), VmError> { + if let Some(ref mut vm) = self.vm { + if let Err(e) = vm.add_device(path) { + error!("Error when adding new device to the VM: {:?}", e); + Err(e) + } else { + Ok(()) + } + } else { + Err(VmError::VmNotRunning) + } + } + fn control_loop(&mut self, api_receiver: Arc>) -> Result<()> { const EPOLL_EVENTS_LEN: usize = 100; @@ -527,6 +540,13 @@ impl Vmm { .map(|_| ApiResponsePayload::Empty); sender.send(response).map_err(Error::ApiResponseSend)?; } + ApiRequest::VmAddDevice(add_device_data, sender) => { + let response = self + .vm_add_device(add_device_data.path.clone()) + .map_err(ApiError::VmAddDevice) + .map(|_| ApiResponsePayload::Empty); + sender.send(response).map_err(Error::ApiResponseSend)?; + } } } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 6df079b5a..329cc085f 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -540,6 +540,16 @@ impl Vm { Ok(()) } + pub fn add_device(&mut self, path: String) -> Result<()> { + self.devices + .lock() + .unwrap() + .add_device(path) + .map_err(Error::DeviceManager)?; + + Ok(()) + } + fn os_signal_handler(signals: Signals, console_input_clone: Arc, on_tty: bool) { for signal in signals.forever() { match signal {