fix: 修复 MSD ISO/FLASH 挂载识别错误;完善错误提示

This commit is contained in:
mofeng-git
2026-07-26 22:59:27 +08:00
parent 27c8da9a75
commit 376dc97134
24 changed files with 1703 additions and 522 deletions

View File

@@ -1,5 +1,236 @@
use serde::Serialize;
use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MsdErrorCode {
MsdUnavailable,
MsdOperationInProgress,
MsdOperationFailed,
MsdInvalidRequest,
MsdResourceNotFound,
MsdResourceAlreadyExists,
MsdMediaSlotsFull,
MsdMediaAlreadyMounted,
MsdMediaInUse,
MsdImageTooLarge,
MsdInvalidUrl,
MsdRemoteDownloadFailed,
MsdDownloadIncomplete,
MsdDriveNotInitialized,
MsdDriveConnected,
MsdDriveFilesystemUnsupported,
MsdDriveSizeInvalid,
MsdStorageSpaceUnavailable,
MsdStorageFull,
MsdStorageReadOnly,
MsdStoragePermissionDenied,
MsdMediumRemovalPrevented,
MsdDisconnectFailed,
}
impl MsdErrorCode {
pub const ALL: [Self; 23] = [
Self::MsdUnavailable,
Self::MsdOperationInProgress,
Self::MsdOperationFailed,
Self::MsdInvalidRequest,
Self::MsdResourceNotFound,
Self::MsdResourceAlreadyExists,
Self::MsdMediaSlotsFull,
Self::MsdMediaAlreadyMounted,
Self::MsdMediaInUse,
Self::MsdImageTooLarge,
Self::MsdInvalidUrl,
Self::MsdRemoteDownloadFailed,
Self::MsdDownloadIncomplete,
Self::MsdDriveNotInitialized,
Self::MsdDriveConnected,
Self::MsdDriveFilesystemUnsupported,
Self::MsdDriveSizeInvalid,
Self::MsdStorageSpaceUnavailable,
Self::MsdStorageFull,
Self::MsdStorageReadOnly,
Self::MsdStoragePermissionDenied,
Self::MsdMediumRemovalPrevented,
Self::MsdDisconnectFailed,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::MsdUnavailable => "MSD_UNAVAILABLE",
Self::MsdOperationInProgress => "MSD_OPERATION_IN_PROGRESS",
Self::MsdOperationFailed => "MSD_OPERATION_FAILED",
Self::MsdInvalidRequest => "MSD_INVALID_REQUEST",
Self::MsdResourceNotFound => "MSD_RESOURCE_NOT_FOUND",
Self::MsdResourceAlreadyExists => "MSD_RESOURCE_ALREADY_EXISTS",
Self::MsdMediaSlotsFull => "MSD_MEDIA_SLOTS_FULL",
Self::MsdMediaAlreadyMounted => "MSD_MEDIA_ALREADY_MOUNTED",
Self::MsdMediaInUse => "MSD_MEDIA_IN_USE",
Self::MsdImageTooLarge => "MSD_IMAGE_TOO_LARGE",
Self::MsdInvalidUrl => "MSD_INVALID_URL",
Self::MsdRemoteDownloadFailed => "MSD_REMOTE_DOWNLOAD_FAILED",
Self::MsdDownloadIncomplete => "MSD_DOWNLOAD_INCOMPLETE",
Self::MsdDriveNotInitialized => "MSD_DRIVE_NOT_INITIALIZED",
Self::MsdDriveConnected => "MSD_DRIVE_CONNECTED",
Self::MsdDriveFilesystemUnsupported => "MSD_DRIVE_FILESYSTEM_UNSUPPORTED",
Self::MsdDriveSizeInvalid => "MSD_DRIVE_SIZE_INVALID",
Self::MsdStorageSpaceUnavailable => "MSD_STORAGE_SPACE_UNAVAILABLE",
Self::MsdStorageFull => "MSD_STORAGE_FULL",
Self::MsdStorageReadOnly => "MSD_STORAGE_READ_ONLY",
Self::MsdStoragePermissionDenied => "MSD_STORAGE_PERMISSION_DENIED",
Self::MsdMediumRemovalPrevented => "MSD_MEDIUM_REMOVAL_PREVENTED",
Self::MsdDisconnectFailed => "MSD_DISCONNECT_FAILED",
}
}
pub const fn message(self) -> &'static str {
match self {
Self::MsdUnavailable => "Virtual media service is unavailable.",
Self::MsdOperationInProgress => "Another virtual media operation is in progress.",
Self::MsdOperationFailed => "The virtual media operation failed.",
Self::MsdInvalidRequest => "The virtual media request is invalid.",
Self::MsdResourceNotFound => "The requested virtual media resource was not found.",
Self::MsdResourceAlreadyExists => "The virtual media resource already exists.",
Self::MsdMediaSlotsFull => "All virtual media slots are in use.",
Self::MsdMediaAlreadyMounted => "The virtual medium is already mounted.",
Self::MsdMediaInUse => "The virtual medium is currently in use.",
Self::MsdImageTooLarge => "The virtual media image is too large.",
Self::MsdInvalidUrl => "The download URL is invalid.",
Self::MsdRemoteDownloadFailed => "The remote image download failed.",
Self::MsdDownloadIncomplete => "The remote image download was incomplete.",
Self::MsdDriveNotInitialized => "The virtual drive is not initialized.",
Self::MsdDriveConnected => "The virtual drive is connected to the controlled computer.",
Self::MsdDriveFilesystemUnsupported => "The virtual drive filesystem is unsupported.",
Self::MsdDriveSizeInvalid => "The virtual drive size is invalid.",
Self::MsdStorageSpaceUnavailable => {
"Available virtual media storage space could not be determined."
}
Self::MsdStorageFull => "Virtual media storage does not have enough free space.",
Self::MsdStorageReadOnly => "Virtual media storage is read-only.",
Self::MsdStoragePermissionDenied => {
"Permission to access virtual media storage was denied."
}
Self::MsdMediumRemovalPrevented => {
"The controlled computer prevented removal of the virtual medium."
}
Self::MsdDisconnectFailed => "The virtual medium could not be disconnected.",
}
}
pub const fn redfish_key(self) -> &'static str {
match self {
Self::MsdUnavailable => "MsdUnavailable",
Self::MsdOperationInProgress => "MsdOperationInProgress",
Self::MsdOperationFailed => "MsdOperationFailed",
Self::MsdInvalidRequest => "MsdInvalidRequest",
Self::MsdResourceNotFound => "MsdResourceNotFound",
Self::MsdResourceAlreadyExists => "MsdResourceAlreadyExists",
Self::MsdMediaSlotsFull => "MsdMediaSlotsFull",
Self::MsdMediaAlreadyMounted => "MsdMediaAlreadyMounted",
Self::MsdMediaInUse => "MsdMediaInUse",
Self::MsdImageTooLarge => "MsdImageTooLarge",
Self::MsdInvalidUrl => "MsdInvalidUrl",
Self::MsdRemoteDownloadFailed => "MsdRemoteDownloadFailed",
Self::MsdDownloadIncomplete => "MsdDownloadIncomplete",
Self::MsdDriveNotInitialized => "MsdDriveNotInitialized",
Self::MsdDriveConnected => "MsdDriveConnected",
Self::MsdDriveFilesystemUnsupported => "MsdDriveFilesystemUnsupported",
Self::MsdDriveSizeInvalid => "MsdDriveSizeInvalid",
Self::MsdStorageSpaceUnavailable => "MsdStorageSpaceUnavailable",
Self::MsdStorageFull => "MsdStorageFull",
Self::MsdStorageReadOnly => "MsdStorageReadOnly",
Self::MsdStoragePermissionDenied => "MsdStoragePermissionDenied",
Self::MsdMediumRemovalPrevented => "MsdMediumRemovalPrevented",
Self::MsdDisconnectFailed => "MsdDisconnectFailed",
}
}
pub const fn severity(self) -> &'static str {
match self {
Self::MsdUnavailable | Self::MsdOperationFailed | Self::MsdDisconnectFailed => {
"Critical"
}
_ => "Warning",
}
}
pub const fn resolution(self) -> &'static str {
match self {
Self::MsdUnavailable => "Enable or restore the virtual media service, then retry.",
Self::MsdOperationInProgress => {
"Wait for the current virtual media operation to finish, then retry."
}
Self::MsdResourceNotFound | Self::MsdDriveNotInitialized => {
"Verify that the requested virtual media resource exists, then retry."
}
Self::MsdResourceAlreadyExists => {
"Use a different resource name or remove the existing resource, then retry."
}
Self::MsdMediaSlotsFull => "Eject an inserted virtual medium, then retry.",
Self::MsdMediaAlreadyMounted => {
"Eject the existing virtual medium before mounting it again."
}
Self::MsdMediaInUse | Self::MsdDriveConnected | Self::MsdMediumRemovalPrevented => {
"Eject or unmount the virtual medium on the controlled computer, then retry."
}
Self::MsdImageTooLarge | Self::MsdDriveSizeInvalid => {
"Use a supported image or virtual drive size, then retry."
}
Self::MsdInvalidUrl | Self::MsdInvalidRequest => "Correct the request and retry.",
Self::MsdRemoteDownloadFailed | Self::MsdDownloadIncomplete => {
"Verify the remote server and network connection, then retry."
}
Self::MsdDriveFilesystemUnsupported => {
"Reinitialize the virtual drive with a supported filesystem, then retry."
}
Self::MsdStorageSpaceUnavailable => {
"Verify that virtual media storage is available, then retry."
}
Self::MsdStorageFull => {
"Free space in virtual media storage or select a smaller image, then retry."
}
Self::MsdStorageReadOnly => "Make virtual media storage writable, then retry.",
Self::MsdStoragePermissionDenied => {
"Correct virtual media storage permissions, then retry."
}
Self::MsdOperationFailed | Self::MsdDisconnectFailed => {
"Retry the operation. If the problem persists, check the One-KVM system logs."
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MsdError {
code: MsdErrorCode,
}
impl MsdError {
pub const fn new(code: MsdErrorCode) -> Self {
Self { code }
}
pub const fn code(self) -> MsdErrorCode {
self.code
}
}
impl fmt::Display for MsdError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.code.message())
}
}
impl std::error::Error for MsdError {}
impl From<MsdErrorCode> for AppError {
fn from(code: MsdErrorCode) -> Self {
Self::Msd(MsdError::new(code))
}
}
#[derive(Error, Debug)]
pub enum AppError {
#[error("Authentication failed: {0}")]
@@ -26,6 +257,9 @@ pub enum AppError {
#[error("Internal error: {0}")]
Internal(String),
#[error(transparent)]
Msd(#[from] MsdError),
#[error("Configuration error: {0}")]
Config(String),
@@ -66,3 +300,135 @@ impl From<sqlx::Error> for AppError {
AppError::Persistence(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::MsdErrorCode::*;
#[test]
fn msd_codes_and_messages_are_stable() {
let cases = [
(
MsdUnavailable,
"MSD_UNAVAILABLE",
"Virtual media service is unavailable.",
),
(
MsdOperationInProgress,
"MSD_OPERATION_IN_PROGRESS",
"Another virtual media operation is in progress.",
),
(
MsdOperationFailed,
"MSD_OPERATION_FAILED",
"The virtual media operation failed.",
),
(
MsdInvalidRequest,
"MSD_INVALID_REQUEST",
"The virtual media request is invalid.",
),
(
MsdResourceNotFound,
"MSD_RESOURCE_NOT_FOUND",
"The requested virtual media resource was not found.",
),
(
MsdResourceAlreadyExists,
"MSD_RESOURCE_ALREADY_EXISTS",
"The virtual media resource already exists.",
),
(
MsdMediaSlotsFull,
"MSD_MEDIA_SLOTS_FULL",
"All virtual media slots are in use.",
),
(
MsdMediaAlreadyMounted,
"MSD_MEDIA_ALREADY_MOUNTED",
"The virtual medium is already mounted.",
),
(
MsdMediaInUse,
"MSD_MEDIA_IN_USE",
"The virtual medium is currently in use.",
),
(
MsdImageTooLarge,
"MSD_IMAGE_TOO_LARGE",
"The virtual media image is too large.",
),
(
MsdInvalidUrl,
"MSD_INVALID_URL",
"The download URL is invalid.",
),
(
MsdRemoteDownloadFailed,
"MSD_REMOTE_DOWNLOAD_FAILED",
"The remote image download failed.",
),
(
MsdDownloadIncomplete,
"MSD_DOWNLOAD_INCOMPLETE",
"The remote image download was incomplete.",
),
(
MsdDriveNotInitialized,
"MSD_DRIVE_NOT_INITIALIZED",
"The virtual drive is not initialized.",
),
(
MsdDriveConnected,
"MSD_DRIVE_CONNECTED",
"The virtual drive is connected to the controlled computer.",
),
(
MsdDriveFilesystemUnsupported,
"MSD_DRIVE_FILESYSTEM_UNSUPPORTED",
"The virtual drive filesystem is unsupported.",
),
(
MsdDriveSizeInvalid,
"MSD_DRIVE_SIZE_INVALID",
"The virtual drive size is invalid.",
),
(
MsdStorageSpaceUnavailable,
"MSD_STORAGE_SPACE_UNAVAILABLE",
"Available virtual media storage space could not be determined.",
),
(
MsdStorageFull,
"MSD_STORAGE_FULL",
"Virtual media storage does not have enough free space.",
),
(
MsdStorageReadOnly,
"MSD_STORAGE_READ_ONLY",
"Virtual media storage is read-only.",
),
(
MsdStoragePermissionDenied,
"MSD_STORAGE_PERMISSION_DENIED",
"Permission to access virtual media storage was denied.",
),
(
MsdMediumRemovalPrevented,
"MSD_MEDIUM_REMOVAL_PREVENTED",
"The controlled computer prevented removal of the virtual medium.",
),
(
MsdDisconnectFailed,
"MSD_DISCONNECT_FAILED",
"The virtual medium could not be disconnected.",
),
];
assert_eq!(cases.len(), super::MsdErrorCode::ALL.len());
for (code, expected_code, expected_message) in cases {
assert_eq!(code.as_str(), expected_code);
assert_eq!(code.message(), expected_message);
}
}
}