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 <epicstyt@gmail.com>
Assisted-by: Claude:Opus-4.8
This commit is contained in:
Stepan Rabotkin
2026-07-31 11:47:22 +03:00
committed by Rob Bradford
parent bff57a8471
commit dcea656a72
3 changed files with 92 additions and 1 deletions

View File

@@ -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!(

View File

@@ -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:

View File

@@ -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")]