From dcea656a722cab1b24c1d7c48fa2b12a7276f04c Mon Sep 17 00:00:00 2001 From: Stepan Rabotkin Date: Fri, 31 Jul 2026 11:47:22 +0300 Subject: [PATCH] vmm: api: Map config-validation errors to 400/409 (not 500) Map VmError::ConfigValidation in api_error_status_code() to client-error codes: 409 Conflict for a duplicate identifier or path, 400 Bad Request otherwise. Update the OpenAPI responses for the vm.add-* and vm.restore endpoints, fix the InvalidIdentifier message, and add unit tests. Signed-off-by: Stepan Rabotkin Assisted-by: Claude:Opus-4.8 --- vmm/src/api/http/mod.rs | 51 +++++++++++++++++++++++ vmm/src/api/openapi/cloud-hypervisor.yaml | 40 ++++++++++++++++++ vmm/src/config.rs | 2 +- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index ad2062be6..839a49379 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -33,6 +33,7 @@ use crate::api::{ VmPause, VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, }; +use crate::config::ValidationError; use crate::device_manager::DeviceManagerError; use crate::landlock::Landlock; use crate::seccomp_filters::{Thread, get_seccomp_filter}; @@ -94,6 +95,12 @@ fn api_error_status_code(error: &ApiError) -> StatusCode { | VmError::NoDeviceToRemove(_) | VmError::DeviceManager(DeviceManagerError::UnknownDeviceId(_)), ) => StatusCode::NotFound, + Some(VmError::ConfigValidation(e)) => match e { + ValidationError::IdentifierNotUnique(_) | ValidationError::DuplicateDevicePath(_) => { + StatusCode::Conflict + } + _ => StatusCode::BadRequest, + }, _ => StatusCode::InternalServerError, } } @@ -532,6 +539,50 @@ mod tests { ); } + #[test] + fn test_duplicate_identifier_or_path_maps_to_conflict() { + assert_eq!( + api_error_status_code(&ApiError::VmAddDisk(VmError::ConfigValidation( + ValidationError::IdentifierNotUnique("disk0".to_string()) + ))), + StatusCode::Conflict + ); + assert_eq!( + api_error_status_code(&ApiError::VmAddVdpa(VmError::ConfigValidation( + ValidationError::IdentifierNotUnique("vdpa0".to_string()) + ))), + StatusCode::Conflict + ); + assert_eq!( + api_error_status_code(&ApiError::VmAddDevice(VmError::ConfigValidation( + ValidationError::DuplicateDevicePath("/dev/foo".to_string()) + ))), + StatusCode::Conflict + ); + assert_eq!( + api_error_status_code(&ApiError::VmRestore(VmError::ConfigValidation( + ValidationError::IdentifierNotUnique("net0".to_string()) + ))), + StatusCode::Conflict + ); + } + + #[test] + fn test_invalid_config_maps_to_bad_request() { + assert_eq!( + api_error_status_code(&ApiError::VmAddDisk(VmError::ConfigValidation( + ValidationError::InvalidIdentifier("__disk0".to_string()) + ))), + StatusCode::BadRequest + ); + assert_eq!( + api_error_status_code(&ApiError::VmAddDisk(VmError::ConfigValidation( + ValidationError::BalloonLargerThanRam(1024, 512) + ))), + StatusCode::BadRequest + ); + } + #[test] fn test_other_errors_map_to_internal_server_error() { assert_eq!( diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index f7534a1f0..9f4307fe6 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -220,8 +220,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new device was successfully (cold) added to the VM instance. + 400: + description: The new device could not be added because the configuration is not valid. 404: description: The new device could not be added to the VM instance. + 409: + description: The new device could not be added because a device with the same identifier or path already exists. /vm.remove-device: put: @@ -258,8 +262,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new disk was successfully (cold) added to the VM instance. + 400: + description: The new disk could not be added because the configuration is not valid. 404: description: The new disk could not be added because the VM is not created. + 409: + description: The new disk could not be added because a device with the same identifier already exists. 500: description: The new disk could not be added to the VM instance. @@ -282,8 +290,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new device was successfully (cold) added to the VM instance. + 400: + description: The new device could not be added because the configuration is not valid. 404: description: The new device could not be added because the VM is not created. + 409: + description: The new device could not be added because a device with the same identifier already exists. 500: description: The new device could not be added to the VM instance. @@ -306,8 +318,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new device was successfully (cold) added to the VM instance. + 400: + description: The new device could not be added because the configuration is not valid. 404: description: The new device could not be added because the VM is not created. + 409: + description: The new device could not be added because a device with the same identifier already exists. 500: description: The new device could not be added to the VM instance. @@ -330,8 +346,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new device was successfully (cold) added to the VM instance. + 400: + description: The new device could not be added because the configuration is not valid. 404: description: The new device could not be added because the VM is not created. + 409: + description: The new device could not be added because a device with the same identifier already exists. 500: description: The new device could not be added to the VM instance. @@ -354,8 +374,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new device was successfully (cold) added to the VM instance. + 400: + description: The new device could not be added because the configuration is not valid. 404: description: The new device could not be added because the VM is not created. + 409: + description: The new device could not be added because a device with the same identifier already exists. 500: description: The new device could not be added to the VM instance. @@ -378,8 +402,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new device was successfully (cold) added to the VM instance. + 400: + description: The new device could not be added because the configuration is not valid. 404: description: The new device could not be added because the VM is not created. + 409: + description: The new device could not be added because a device with the same identifier already exists. 500: description: The new device could not be added to the VM instance. @@ -402,8 +430,12 @@ paths: $ref: "#/components/schemas/PciDeviceInfo" 204: description: The new vDPA device was successfully (cold) added to the VM instance. + 400: + description: The new vDPA device could not be added because the configuration is not valid. 404: description: The new vDPA device could not be added because the VM is not created. + 409: + description: The new vDPA device could not be added because a device with the same identifier already exists. 500: description: The new vDPA device could not be added to the VM instance. @@ -425,6 +457,10 @@ paths: description: The new device was successfully added to the VM instance. "204": description: The new device was successfully (cold) added to the VM instance. + "400": + description: The new device could not be added because the configuration is not valid. + "409": + description: The new device could not be added because a device with the same identifier already exists. "404": description: The new device could not be added to the VM instance. summary: Add a new userspace device to the VM @@ -485,8 +521,12 @@ paths: responses: 204: description: The VM instance was successfully restored. + 400: + description: The VM could not be restored because the restore configuration is not valid. 404: description: The VM instance could not be restored because it is already created. + 409: + description: The VM could not be restored because a device with the same identifier or path already exists. /vm.receive-migration: put: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 64b86a56d..abd67e95b 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -338,7 +338,7 @@ pub enum ValidationError { #[error("Identifier {0} is not unique")] IdentifierNotUnique(String), /// Invalid identifier - #[error("Identifier {0} is not invalid")] + #[error("Identifier {0} is not valid")] InvalidIdentifier(String), /// Placing the device behind a virtual IOMMU is not supported #[error("Device does not support being placed behind IOMMU")]