Files
rustfs/crates/ecstore/src/error.rs
T
2026-06-10 09:03:29 +08:00

1444 lines
64 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::bucket::error::BucketMetadataError;
use crate::disk::error::DiskError;
use rustfs_storage_api::StorageErrorCode;
use rustfs_utils::path::decode_dir_object;
use s3s::{S3Error, S3ErrorCode};
pub type Error = StorageError;
pub type Result<T> = core::result::Result<T, Error>;
/// Storage layer error type covering disk, volume, bucket, object, multipart,
/// erasure-coding, and operational error conditions.
///
/// Variants are organized by domain:
/// - **Disk / Volume** low-level storage I/O
/// - **Bucket** bucket-level operations
/// - **Object** object-level operations (including versioning)
/// - **Multipart** multipart upload lifecycle
/// - **Erasure / Quorum** erasure coding and quorum failures
/// - **Operational** decommission, healing, rate-limiting, etc.
/// - **Generic** I/O, locks, and catch-all errors
#[derive(Debug, thiserror::Error)]
pub enum StorageError {
// ── Disk / Volume ────────────────────────────────────────────────
#[error("Faulty disk")]
FaultyDisk,
#[error("Faulty remote disk")]
FaultyRemoteDisk,
#[error("Disk full")]
DiskFull,
#[error("Disk not found")]
DiskNotFound,
#[error("Disk access denied")]
DiskAccessDenied,
#[error("Drive is root")]
DriveIsRoot,
#[error("Unformatted disk")]
UnformattedDisk,
#[error("Corrupted format")]
CorruptedFormat,
#[error("Corrupted backend")]
CorruptedBackend,
#[error("Too many open files")]
TooManyOpenFiles,
#[error("Volume not found")]
VolumeNotFound,
#[error("Volume exists")]
VolumeExists,
#[error("Volume not empty")]
VolumeNotEmpty,
#[error("Volume access denied")]
VolumeAccessDenied,
#[error("File not found")]
FileNotFound,
#[error("File version not found")]
FileVersionNotFound,
#[error("File name too long")]
FileNameTooLong,
#[error("File access denied")]
FileAccessDenied,
#[error("File is corrupted")]
FileCorrupt,
#[error("Not a regular file")]
IsNotRegular,
// ── Bucket ───────────────────────────────────────────────────────
#[error("Bucket not found: {0}")]
BucketNotFound(String),
#[error("Bucket exists: {0}")]
BucketExists(String),
#[error("Bucket not empty: {0}")]
BucketNotEmpty(String),
#[error("Bucket name invalid: {0}")]
BucketNameInvalid(String),
// ── Object ───────────────────────────────────────────────────────
#[error("Object not found: {0}/{1}")]
ObjectNotFound(String, String),
#[error("Object name invalid: {0}/{1}")]
ObjectNameInvalid(String, String),
#[error("Object name too long: {0}/{1}")]
ObjectNameTooLong(String, String),
#[error("Object name contains forward slash as prefix: {0}/{1}")]
ObjectNamePrefixAsSlash(String, String),
#[error("Object exists on :{0} as directory {1}")]
ObjectExistsAsDirectory(String, String),
#[error("Version not found: {0}/{1}-{2}")]
VersionNotFound(String, String, String),
#[error("Invalid version id: {0}/{1}-{2}")]
InvalidVersionID(String, String, String),
#[error("invalid data movement operation, source and destination pool are the same for : {0}/{1}-{2}")]
DataMovementOverwriteErr(String, String, String),
#[error("Prefix access is denied:{0}/{1}")]
PrefixAccessDenied(String, String),
// ── Multipart ────────────────────────────────────────────────────
#[error("Invalid upload id: {0}/{1}-{2}")]
InvalidUploadID(String, String, String),
#[error("Invalid UploadID KeyCombination: {0}/{1}")]
InvalidUploadIDKeyCombination(String, String),
#[error("Malformed UploadID: {0}")]
MalformedUploadID(String),
#[error("Specified part could not be found. PartNumber {0}, Expected {1}, got {2}")]
InvalidPart(usize, String, String),
#[error("Invalid part number: {0}")]
InvalidPartNumber(usize),
#[error("Your proposed upload is smaller than the minimum allowed size. Part {0} size {1} is less than minimum {2}")]
EntityTooSmall(usize, i64, i64),
// ── Erasure / Quorum ─────────────────────────────────────────────
#[error("erasure read quorum")]
ErasureReadQuorum,
#[error("erasure write quorum")]
ErasureWriteQuorum,
#[error("Storage resources are insufficient for the read operation: {0}/{1}")]
InsufficientReadQuorum(String, String),
#[error("Storage resources are insufficient for the write operation: {0}/{1}")]
InsufficientWriteQuorum(String, String),
#[error("not first disk")]
NotFirstDisk,
#[error("first disk wait")]
FirstDiskWait,
// ── Operational ──────────────────────────────────────────────────
#[error("Storage reached its minimum free drive threshold.")]
StorageFull,
#[error("Please reduce your request rate")]
SlowDown,
#[error("Decommission not started")]
DecommissionNotStarted,
#[error("Decommission already running")]
DecommissionAlreadyRunning,
#[error("Rebalance already running")]
RebalanceAlreadyRunning,
#[error("Operation canceled")]
OperationCanceled,
#[error("No heal required")]
NoHealRequired,
#[error("DoneForNow")]
DoneForNow,
#[error("Config not found")]
ConfigNotFound,
#[error("Precondition failed")]
PreconditionFailed,
#[error("Not modified")]
NotModified,
#[error("Invalid range specified: {0}")]
InvalidRangeSpec(String),
#[error("Namespace lock quorum unavailable for {mode} lock on {bucket}/{object}: required {required}, achieved {achieved}")]
NamespaceLockQuorumUnavailable {
mode: &'static str,
bucket: String,
object: String,
required: usize,
achieved: usize,
},
// ── Generic ──────────────────────────────────────────────────────
#[error("Unexpected error")]
Unexpected,
#[error("not implemented")]
NotImplemented,
#[error("Invalid arguments provided for {0}/{1}-{2}")]
InvalidArgument(String, String, String),
#[error("method not allowed")]
MethodNotAllowed,
#[error("Io error: {0}")]
Io(std::io::Error),
#[error("Lock error: {0}")]
Lock(#[from] rustfs_lock::LockError),
}
impl StorageError {
pub fn other<E>(error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
StorageError::Io(std::io::Error::other(error))
}
pub fn is_not_found(&self) -> bool {
matches!(
self,
StorageError::FileNotFound
| StorageError::ObjectNotFound(_, _)
| StorageError::FileVersionNotFound
| StorageError::VersionNotFound(_, _, _)
| StorageError::VolumeNotFound
| StorageError::DiskNotFound
| StorageError::BucketNotFound(_)
)
}
pub fn is_quorum_error(&self) -> bool {
matches!(
self,
StorageError::ErasureReadQuorum
| StorageError::ErasureWriteQuorum
| StorageError::InsufficientReadQuorum(_, _)
| StorageError::InsufficientWriteQuorum(_, _)
| StorageError::NamespaceLockQuorumUnavailable { .. }
)
}
}
impl From<DiskError> for StorageError {
fn from(e: DiskError) -> Self {
match e {
DiskError::Io(io_error) => StorageError::Io(io_error),
// DiskError::MaxVersionsExceeded => todo!(),
DiskError::Unexpected => StorageError::Unexpected,
DiskError::CorruptedFormat => StorageError::CorruptedFormat,
DiskError::CorruptedBackend => StorageError::CorruptedBackend,
DiskError::UnformattedDisk => StorageError::UnformattedDisk,
// DiskError::InconsistentDisk => StorageError::InconsistentDisk,
// DiskError::UnsupportedDisk => StorageError::UnsupportedDisk,
DiskError::DiskFull => StorageError::DiskFull,
// DiskError::DiskNotDir => StorageError::DiskNotDir,
DiskError::DiskNotFound => StorageError::DiskNotFound,
// DiskError::DiskOngoingReq => StorageError::DiskOngoingReq,
DiskError::DriveIsRoot => StorageError::DriveIsRoot,
DiskError::FaultyRemoteDisk => StorageError::FaultyRemoteDisk,
DiskError::FaultyDisk => StorageError::FaultyDisk,
DiskError::DiskAccessDenied => StorageError::DiskAccessDenied,
DiskError::FileNotFound => StorageError::FileNotFound,
DiskError::FileVersionNotFound => StorageError::FileVersionNotFound,
DiskError::TooManyOpenFiles => StorageError::TooManyOpenFiles,
DiskError::FileNameTooLong => StorageError::FileNameTooLong,
DiskError::VolumeExists => StorageError::VolumeExists,
DiskError::IsNotRegular => StorageError::IsNotRegular,
// DiskError::PathNotFound => StorageError::PathNotFound,
DiskError::VolumeNotFound => StorageError::VolumeNotFound,
DiskError::VolumeNotEmpty => StorageError::VolumeNotEmpty,
DiskError::VolumeAccessDenied => StorageError::VolumeAccessDenied,
DiskError::FileAccessDenied => StorageError::FileAccessDenied,
DiskError::FileCorrupt => StorageError::FileCorrupt,
// DiskError::BitrotHashAlgoInvalid => StorageError::BitrotHashAlgoInvalid,
// DiskError::CrossDeviceLink => StorageError::CrossDeviceLink,
// DiskError::LessData => StorageError::LessData,
// DiskError::MoreData => StorageError::MoreData,
// DiskError::OutdatedXLMeta => StorageError::OutdatedXLMeta,
// DiskError::PartMissingOrCorrupt => StorageError::PartMissingOrCorrupt,
DiskError::NoHealRequired => StorageError::NoHealRequired,
DiskError::MethodNotAllowed => StorageError::MethodNotAllowed,
DiskError::ErasureReadQuorum => StorageError::ErasureReadQuorum,
DiskError::ErasureWriteQuorum => StorageError::ErasureWriteQuorum,
_ => StorageError::Io(std::io::Error::other(e)),
}
}
}
impl From<StorageError> for DiskError {
fn from(val: StorageError) -> Self {
match val {
StorageError::Io(io_error) => io_error.into(),
StorageError::Unexpected => DiskError::Unexpected,
StorageError::FileNotFound => DiskError::FileNotFound,
StorageError::FileVersionNotFound => DiskError::FileVersionNotFound,
StorageError::FileCorrupt => DiskError::FileCorrupt,
StorageError::MethodNotAllowed => DiskError::MethodNotAllowed,
StorageError::StorageFull => DiskError::DiskFull,
StorageError::SlowDown => DiskError::TooManyOpenFiles,
StorageError::ErasureReadQuorum => DiskError::ErasureReadQuorum,
StorageError::ErasureWriteQuorum => DiskError::ErasureWriteQuorum,
StorageError::TooManyOpenFiles => DiskError::TooManyOpenFiles,
StorageError::NoHealRequired => DiskError::NoHealRequired,
StorageError::CorruptedFormat => DiskError::CorruptedFormat,
StorageError::CorruptedBackend => DiskError::CorruptedBackend,
StorageError::UnformattedDisk => DiskError::UnformattedDisk,
StorageError::DiskNotFound => DiskError::DiskNotFound,
StorageError::FaultyDisk => DiskError::FaultyDisk,
StorageError::DiskFull => DiskError::DiskFull,
StorageError::VolumeNotFound => DiskError::VolumeNotFound,
StorageError::VolumeExists => DiskError::VolumeExists,
StorageError::FileNameTooLong => DiskError::FileNameTooLong,
_ => DiskError::other(val),
}
}
}
impl From<BucketMetadataError> for Error {
fn from(e: BucketMetadataError) -> Self {
match e {
BucketMetadataError::TaggingNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketPolicyNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketObjectLockConfigNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketLifecycleNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketSSEConfigNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketQuotaConfigNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketReplicationConfigNotFound => Error::ConfigNotFound,
BucketMetadataError::BucketRemoteTargetNotFound => Error::ConfigNotFound,
_ => Error::other(e),
}
}
}
impl From<std::io::Error> for StorageError {
fn from(e: std::io::Error) -> Self {
match e.downcast::<StorageError>() {
Ok(storage_error) => storage_error,
Err(io_error) => match io_error.downcast::<DiskError>() {
Ok(disk_error) => disk_error.into(),
Err(io_error) => StorageError::Io(io_error),
},
}
}
}
impl From<StorageError> for std::io::Error {
fn from(e: StorageError) -> Self {
match e {
StorageError::Io(io_error) => io_error,
e => std::io::Error::other(e),
}
}
}
impl From<rustfs_filemeta::Error> for StorageError {
fn from(e: rustfs_filemeta::Error) -> Self {
match e {
rustfs_filemeta::Error::DoneForNow => StorageError::DoneForNow,
rustfs_filemeta::Error::MethodNotAllowed => StorageError::MethodNotAllowed,
rustfs_filemeta::Error::VolumeNotFound => StorageError::VolumeNotFound,
rustfs_filemeta::Error::FileNotFound => StorageError::FileNotFound,
rustfs_filemeta::Error::FileVersionNotFound => StorageError::FileVersionNotFound,
rustfs_filemeta::Error::FileCorrupt => StorageError::FileCorrupt,
rustfs_filemeta::Error::Unexpected => StorageError::Unexpected,
rustfs_filemeta::Error::Io(io_error) => io_error.into(),
_ => StorageError::Io(std::io::Error::other(e)),
}
}
}
impl From<StorageError> for rustfs_filemeta::Error {
fn from(val: StorageError) -> Self {
match val {
StorageError::Unexpected => rustfs_filemeta::Error::Unexpected,
StorageError::FileNotFound => rustfs_filemeta::Error::FileNotFound,
StorageError::FileVersionNotFound => rustfs_filemeta::Error::FileVersionNotFound,
StorageError::FileCorrupt => rustfs_filemeta::Error::FileCorrupt,
StorageError::DoneForNow => rustfs_filemeta::Error::DoneForNow,
StorageError::MethodNotAllowed => rustfs_filemeta::Error::MethodNotAllowed,
StorageError::VolumeNotFound => rustfs_filemeta::Error::VolumeNotFound,
StorageError::Io(io_error) => io_error.into(),
_ => rustfs_filemeta::Error::other(val),
}
}
}
impl PartialEq for StorageError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(StorageError::Io(e1), StorageError::Io(e2)) => e1.kind() == e2.kind() && e1.to_string() == e2.to_string(),
(e1, e2) => e1.to_u32() == e2.to_u32(),
}
}
}
impl Clone for StorageError {
fn clone(&self) -> Self {
match self {
StorageError::Io(e) => StorageError::Io(std::io::Error::new(e.kind(), e.to_string())),
StorageError::FaultyDisk => StorageError::FaultyDisk,
StorageError::DiskFull => StorageError::DiskFull,
StorageError::VolumeNotFound => StorageError::VolumeNotFound,
StorageError::VolumeExists => StorageError::VolumeExists,
StorageError::FileNotFound => StorageError::FileNotFound,
StorageError::FileVersionNotFound => StorageError::FileVersionNotFound,
StorageError::FileNameTooLong => StorageError::FileNameTooLong,
StorageError::FileAccessDenied => StorageError::FileAccessDenied,
StorageError::FileCorrupt => StorageError::FileCorrupt,
StorageError::IsNotRegular => StorageError::IsNotRegular,
StorageError::VolumeNotEmpty => StorageError::VolumeNotEmpty,
StorageError::VolumeAccessDenied => StorageError::VolumeAccessDenied,
StorageError::CorruptedFormat => StorageError::CorruptedFormat,
StorageError::CorruptedBackend => StorageError::CorruptedBackend,
StorageError::UnformattedDisk => StorageError::UnformattedDisk,
StorageError::DiskNotFound => StorageError::DiskNotFound,
StorageError::DriveIsRoot => StorageError::DriveIsRoot,
StorageError::FaultyRemoteDisk => StorageError::FaultyRemoteDisk,
StorageError::DiskAccessDenied => StorageError::DiskAccessDenied,
StorageError::Unexpected => StorageError::Unexpected,
StorageError::ConfigNotFound => StorageError::ConfigNotFound,
StorageError::NotImplemented => StorageError::NotImplemented,
StorageError::InvalidArgument(a, b, c) => StorageError::InvalidArgument(a.clone(), b.clone(), c.clone()),
StorageError::MethodNotAllowed => StorageError::MethodNotAllowed,
StorageError::BucketNotFound(a) => StorageError::BucketNotFound(a.clone()),
StorageError::BucketNotEmpty(a) => StorageError::BucketNotEmpty(a.clone()),
StorageError::BucketNameInvalid(a) => StorageError::BucketNameInvalid(a.clone()),
StorageError::ObjectNameInvalid(a, b) => StorageError::ObjectNameInvalid(a.clone(), b.clone()),
StorageError::BucketExists(a) => StorageError::BucketExists(a.clone()),
StorageError::StorageFull => StorageError::StorageFull,
StorageError::SlowDown => StorageError::SlowDown,
StorageError::PrefixAccessDenied(a, b) => StorageError::PrefixAccessDenied(a.clone(), b.clone()),
StorageError::InvalidUploadIDKeyCombination(a, b) => {
StorageError::InvalidUploadIDKeyCombination(a.clone(), b.clone())
}
StorageError::MalformedUploadID(a) => StorageError::MalformedUploadID(a.clone()),
StorageError::ObjectNameTooLong(a, b) => StorageError::ObjectNameTooLong(a.clone(), b.clone()),
StorageError::ObjectNamePrefixAsSlash(a, b) => StorageError::ObjectNamePrefixAsSlash(a.clone(), b.clone()),
StorageError::ObjectNotFound(a, b) => StorageError::ObjectNotFound(a.clone(), b.clone()),
StorageError::VersionNotFound(a, b, c) => StorageError::VersionNotFound(a.clone(), b.clone(), c.clone()),
StorageError::InvalidUploadID(a, b, c) => StorageError::InvalidUploadID(a.clone(), b.clone(), c.clone()),
StorageError::InvalidVersionID(a, b, c) => StorageError::InvalidVersionID(a.clone(), b.clone(), c.clone()),
StorageError::DataMovementOverwriteErr(a, b, c) => {
StorageError::DataMovementOverwriteErr(a.clone(), b.clone(), c.clone())
}
StorageError::ObjectExistsAsDirectory(a, b) => StorageError::ObjectExistsAsDirectory(a.clone(), b.clone()),
// StorageError::InsufficientReadQuorum => StorageError::InsufficientReadQuorum,
// StorageError::InsufficientWriteQuorum => StorageError::InsufficientWriteQuorum,
StorageError::DecommissionNotStarted => StorageError::DecommissionNotStarted,
StorageError::InvalidPart(a, b, c) => StorageError::InvalidPart(*a, b.clone(), c.clone()),
StorageError::EntityTooSmall(a, b, c) => StorageError::EntityTooSmall(*a, *b, *c),
StorageError::DoneForNow => StorageError::DoneForNow,
StorageError::DecommissionAlreadyRunning => StorageError::DecommissionAlreadyRunning,
StorageError::RebalanceAlreadyRunning => StorageError::RebalanceAlreadyRunning,
StorageError::OperationCanceled => StorageError::OperationCanceled,
StorageError::ErasureReadQuorum => StorageError::ErasureReadQuorum,
StorageError::ErasureWriteQuorum => StorageError::ErasureWriteQuorum,
StorageError::NotFirstDisk => StorageError::NotFirstDisk,
StorageError::FirstDiskWait => StorageError::FirstDiskWait,
StorageError::TooManyOpenFiles => StorageError::TooManyOpenFiles,
StorageError::NoHealRequired => StorageError::NoHealRequired,
StorageError::Lock(e) => StorageError::Lock(e.clone()),
StorageError::InsufficientReadQuorum(a, b) => StorageError::InsufficientReadQuorum(a.clone(), b.clone()),
StorageError::InsufficientWriteQuorum(a, b) => StorageError::InsufficientWriteQuorum(a.clone(), b.clone()),
StorageError::PreconditionFailed => StorageError::PreconditionFailed,
StorageError::NotModified => StorageError::NotModified,
StorageError::InvalidPartNumber(a) => StorageError::InvalidPartNumber(*a),
StorageError::InvalidRangeSpec(a) => StorageError::InvalidRangeSpec(a.clone()),
StorageError::NamespaceLockQuorumUnavailable {
mode,
bucket,
object,
required,
achieved,
} => StorageError::NamespaceLockQuorumUnavailable {
mode,
bucket: bucket.clone(),
object: object.clone(),
required: *required,
achieved: *achieved,
},
}
}
}
impl StorageError {
fn code(&self) -> StorageErrorCode {
match self {
StorageError::Io(_) => StorageErrorCode::Io,
StorageError::FaultyDisk => StorageErrorCode::FaultyDisk,
StorageError::DiskFull => StorageErrorCode::DiskFull,
StorageError::VolumeNotFound => StorageErrorCode::VolumeNotFound,
StorageError::VolumeExists => StorageErrorCode::VolumeExists,
StorageError::FileNotFound => StorageErrorCode::FileNotFound,
StorageError::FileVersionNotFound => StorageErrorCode::FileVersionNotFound,
StorageError::FileNameTooLong => StorageErrorCode::FileNameTooLong,
StorageError::FileAccessDenied => StorageErrorCode::FileAccessDenied,
StorageError::FileCorrupt => StorageErrorCode::FileCorrupt,
StorageError::IsNotRegular => StorageErrorCode::IsNotRegular,
StorageError::VolumeNotEmpty => StorageErrorCode::VolumeNotEmpty,
StorageError::VolumeAccessDenied => StorageErrorCode::VolumeAccessDenied,
StorageError::CorruptedFormat => StorageErrorCode::CorruptedFormat,
StorageError::CorruptedBackend => StorageErrorCode::CorruptedBackend,
StorageError::UnformattedDisk => StorageErrorCode::UnformattedDisk,
StorageError::DiskNotFound => StorageErrorCode::DiskNotFound,
StorageError::DriveIsRoot => StorageErrorCode::DriveIsRoot,
StorageError::FaultyRemoteDisk => StorageErrorCode::FaultyRemoteDisk,
StorageError::DiskAccessDenied => StorageErrorCode::DiskAccessDenied,
StorageError::Unexpected => StorageErrorCode::Unexpected,
StorageError::NotImplemented => StorageErrorCode::NotImplemented,
StorageError::InvalidArgument(_, _, _) => StorageErrorCode::InvalidArgument,
StorageError::MethodNotAllowed => StorageErrorCode::MethodNotAllowed,
StorageError::BucketNotFound(_) => StorageErrorCode::BucketNotFound,
StorageError::BucketNotEmpty(_) => StorageErrorCode::BucketNotEmpty,
StorageError::BucketNameInvalid(_) => StorageErrorCode::BucketNameInvalid,
StorageError::ObjectNameInvalid(_, _) => StorageErrorCode::ObjectNameInvalid,
StorageError::BucketExists(_) => StorageErrorCode::BucketExists,
StorageError::StorageFull => StorageErrorCode::StorageFull,
StorageError::SlowDown => StorageErrorCode::SlowDown,
StorageError::PrefixAccessDenied(_, _) => StorageErrorCode::PrefixAccessDenied,
StorageError::InvalidUploadIDKeyCombination(_, _) => StorageErrorCode::InvalidUploadIDKeyCombination,
StorageError::MalformedUploadID(_) => StorageErrorCode::MalformedUploadID,
StorageError::ObjectNameTooLong(_, _) => StorageErrorCode::ObjectNameTooLong,
StorageError::ObjectNamePrefixAsSlash(_, _) => StorageErrorCode::ObjectNamePrefixAsSlash,
StorageError::ObjectNotFound(_, _) => StorageErrorCode::ObjectNotFound,
StorageError::VersionNotFound(_, _, _) => StorageErrorCode::VersionNotFound,
StorageError::InvalidUploadID(_, _, _) => StorageErrorCode::InvalidUploadID,
StorageError::InvalidVersionID(_, _, _) => StorageErrorCode::InvalidVersionID,
StorageError::DataMovementOverwriteErr(_, _, _) => StorageErrorCode::DataMovementOverwriteErr,
StorageError::ObjectExistsAsDirectory(_, _) => StorageErrorCode::ObjectExistsAsDirectory,
StorageError::DecommissionNotStarted => StorageErrorCode::DecommissionNotStarted,
StorageError::InvalidPart(_, _, _) => StorageErrorCode::InvalidPart,
StorageError::DoneForNow => StorageErrorCode::DoneForNow,
StorageError::DecommissionAlreadyRunning => StorageErrorCode::DecommissionAlreadyRunning,
StorageError::RebalanceAlreadyRunning => StorageErrorCode::RebalanceAlreadyRunning,
StorageError::OperationCanceled => StorageErrorCode::OperationCanceled,
StorageError::ErasureReadQuorum => StorageErrorCode::ErasureReadQuorum,
StorageError::ErasureWriteQuorum => StorageErrorCode::ErasureWriteQuorum,
StorageError::NotFirstDisk => StorageErrorCode::NotFirstDisk,
StorageError::FirstDiskWait => StorageErrorCode::FirstDiskWait,
StorageError::ConfigNotFound => StorageErrorCode::ConfigNotFound,
StorageError::TooManyOpenFiles => StorageErrorCode::TooManyOpenFiles,
StorageError::NoHealRequired => StorageErrorCode::NoHealRequired,
StorageError::Lock(_) => StorageErrorCode::Lock,
StorageError::InsufficientReadQuorum(_, _) => StorageErrorCode::InsufficientReadQuorum,
StorageError::InsufficientWriteQuorum(_, _) => StorageErrorCode::InsufficientWriteQuorum,
StorageError::PreconditionFailed => StorageErrorCode::PreconditionFailed,
StorageError::EntityTooSmall(_, _, _) => StorageErrorCode::EntityTooSmall,
StorageError::InvalidRangeSpec(_) => StorageErrorCode::InvalidRangeSpec,
StorageError::NotModified => StorageErrorCode::NotModified,
StorageError::InvalidPartNumber(_) => StorageErrorCode::InvalidPartNumber,
StorageError::NamespaceLockQuorumUnavailable { .. } => StorageErrorCode::NamespaceLockQuorumUnavailable,
}
}
pub fn to_u32(&self) -> u32 {
self.code().as_u32()
}
pub fn from_u32(error: u32) -> Option<Self> {
match StorageErrorCode::from_u32(error)? {
StorageErrorCode::Io => Some(StorageError::Io(std::io::Error::other("Io error"))),
StorageErrorCode::FaultyDisk => Some(StorageError::FaultyDisk),
StorageErrorCode::DiskFull => Some(StorageError::DiskFull),
StorageErrorCode::VolumeNotFound => Some(StorageError::VolumeNotFound),
StorageErrorCode::VolumeExists => Some(StorageError::VolumeExists),
StorageErrorCode::FileNotFound => Some(StorageError::FileNotFound),
StorageErrorCode::FileVersionNotFound => Some(StorageError::FileVersionNotFound),
StorageErrorCode::FileNameTooLong => Some(StorageError::FileNameTooLong),
StorageErrorCode::FileAccessDenied => Some(StorageError::FileAccessDenied),
StorageErrorCode::FileCorrupt => Some(StorageError::FileCorrupt),
StorageErrorCode::IsNotRegular => Some(StorageError::IsNotRegular),
StorageErrorCode::VolumeNotEmpty => Some(StorageError::VolumeNotEmpty),
StorageErrorCode::VolumeAccessDenied => Some(StorageError::VolumeAccessDenied),
StorageErrorCode::CorruptedFormat => Some(StorageError::CorruptedFormat),
StorageErrorCode::CorruptedBackend => Some(StorageError::CorruptedBackend),
StorageErrorCode::UnformattedDisk => Some(StorageError::UnformattedDisk),
StorageErrorCode::DiskNotFound => Some(StorageError::DiskNotFound),
StorageErrorCode::DriveIsRoot => Some(StorageError::DriveIsRoot),
StorageErrorCode::FaultyRemoteDisk => Some(StorageError::FaultyRemoteDisk),
StorageErrorCode::DiskAccessDenied => Some(StorageError::DiskAccessDenied),
StorageErrorCode::Unexpected => Some(StorageError::Unexpected),
StorageErrorCode::NotImplemented => Some(StorageError::NotImplemented),
StorageErrorCode::InvalidArgument => {
Some(StorageError::InvalidArgument(Default::default(), Default::default(), Default::default()))
}
StorageErrorCode::MethodNotAllowed => Some(StorageError::MethodNotAllowed),
StorageErrorCode::BucketNotFound => Some(StorageError::BucketNotFound(Default::default())),
StorageErrorCode::BucketNotEmpty => Some(StorageError::BucketNotEmpty(Default::default())),
StorageErrorCode::BucketNameInvalid => Some(StorageError::BucketNameInvalid(Default::default())),
StorageErrorCode::ObjectNameInvalid => Some(StorageError::ObjectNameInvalid(Default::default(), Default::default())),
StorageErrorCode::BucketExists => Some(StorageError::BucketExists(Default::default())),
StorageErrorCode::StorageFull => Some(StorageError::StorageFull),
StorageErrorCode::SlowDown => Some(StorageError::SlowDown),
StorageErrorCode::PrefixAccessDenied => {
Some(StorageError::PrefixAccessDenied(Default::default(), Default::default()))
}
StorageErrorCode::InvalidUploadIDKeyCombination => {
Some(StorageError::InvalidUploadIDKeyCombination(Default::default(), Default::default()))
}
StorageErrorCode::MalformedUploadID => Some(StorageError::MalformedUploadID(Default::default())),
StorageErrorCode::ObjectNameTooLong => Some(StorageError::ObjectNameTooLong(Default::default(), Default::default())),
StorageErrorCode::ObjectNamePrefixAsSlash => {
Some(StorageError::ObjectNamePrefixAsSlash(Default::default(), Default::default()))
}
StorageErrorCode::ObjectNotFound => Some(StorageError::ObjectNotFound(Default::default(), Default::default())),
StorageErrorCode::VersionNotFound => {
Some(StorageError::VersionNotFound(Default::default(), Default::default(), Default::default()))
}
StorageErrorCode::InvalidUploadID => {
Some(StorageError::InvalidUploadID(Default::default(), Default::default(), Default::default()))
}
StorageErrorCode::InvalidVersionID => {
Some(StorageError::InvalidVersionID(Default::default(), Default::default(), Default::default()))
}
StorageErrorCode::DataMovementOverwriteErr => Some(StorageError::DataMovementOverwriteErr(
Default::default(),
Default::default(),
Default::default(),
)),
StorageErrorCode::ObjectExistsAsDirectory => {
Some(StorageError::ObjectExistsAsDirectory(Default::default(), Default::default()))
}
StorageErrorCode::DecommissionNotStarted => Some(StorageError::DecommissionNotStarted),
StorageErrorCode::InvalidPart => {
Some(StorageError::InvalidPart(Default::default(), Default::default(), Default::default()))
}
StorageErrorCode::DoneForNow => Some(StorageError::DoneForNow),
StorageErrorCode::DecommissionAlreadyRunning => Some(StorageError::DecommissionAlreadyRunning),
StorageErrorCode::RebalanceAlreadyRunning => Some(StorageError::RebalanceAlreadyRunning),
StorageErrorCode::OperationCanceled => Some(StorageError::OperationCanceled),
StorageErrorCode::ErasureReadQuorum => Some(StorageError::ErasureReadQuorum),
StorageErrorCode::ErasureWriteQuorum => Some(StorageError::ErasureWriteQuorum),
StorageErrorCode::NotFirstDisk => Some(StorageError::NotFirstDisk),
StorageErrorCode::FirstDiskWait => Some(StorageError::FirstDiskWait),
StorageErrorCode::ConfigNotFound => Some(StorageError::ConfigNotFound),
StorageErrorCode::TooManyOpenFiles => Some(StorageError::TooManyOpenFiles),
StorageErrorCode::NoHealRequired => Some(StorageError::NoHealRequired),
StorageErrorCode::Lock => {
Some(StorageError::Lock(rustfs_lock::LockError::internal("Generic lock error".to_string())))
}
StorageErrorCode::InsufficientReadQuorum => {
Some(StorageError::InsufficientReadQuorum(Default::default(), Default::default()))
}
StorageErrorCode::InsufficientWriteQuorum => {
Some(StorageError::InsufficientWriteQuorum(Default::default(), Default::default()))
}
StorageErrorCode::PreconditionFailed => Some(StorageError::PreconditionFailed),
StorageErrorCode::EntityTooSmall => {
Some(StorageError::EntityTooSmall(Default::default(), Default::default(), Default::default()))
}
StorageErrorCode::InvalidRangeSpec => Some(StorageError::InvalidRangeSpec(Default::default())),
StorageErrorCode::NotModified => Some(StorageError::NotModified),
StorageErrorCode::InvalidPartNumber => Some(StorageError::InvalidPartNumber(Default::default())),
StorageErrorCode::NamespaceLockQuorumUnavailable => Some(StorageError::NamespaceLockQuorumUnavailable {
mode: "write",
bucket: Default::default(),
object: Default::default(),
required: Default::default(),
achieved: Default::default(),
}),
}
}
}
impl From<tokio::task::JoinError> for StorageError {
fn from(e: tokio::task::JoinError) -> Self {
StorageError::other(e)
}
}
impl From<serde_json::Error> for StorageError {
fn from(e: serde_json::Error) -> Self {
StorageError::other(e)
}
}
impl From<rmp_serde::encode::Error> for Error {
fn from(e: rmp_serde::encode::Error) -> Self {
Error::other(e)
}
}
impl From<rmp::encode::ValueWriteError> for Error {
fn from(e: rmp::encode::ValueWriteError) -> Self {
Error::other(e)
}
}
impl From<rmp::decode::ValueReadError> for Error {
fn from(e: rmp::decode::ValueReadError) -> Self {
Error::other(e)
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(e: std::string::FromUtf8Error) -> Self {
Error::other(e)
}
}
impl From<rmp::decode::NumValueReadError> for Error {
fn from(e: rmp::decode::NumValueReadError) -> Self {
Error::other(e)
}
}
impl From<rmp_serde::decode::Error> for Error {
fn from(e: rmp_serde::decode::Error) -> Self {
Error::other(e)
}
}
impl From<s3s::xml::SerError> for Error {
fn from(e: s3s::xml::SerError) -> Self {
Error::other(e)
}
}
impl From<s3s::xml::DeError> for Error {
fn from(e: s3s::xml::DeError) -> Self {
Error::other(e)
}
}
impl From<tonic::Status> for Error {
fn from(e: tonic::Status) -> Self {
Error::other(e.to_string())
}
}
impl From<uuid::Error> for Error {
fn from(e: uuid::Error) -> Self {
Error::other(e)
}
}
impl From<time::error::ComponentRange> for Error {
fn from(e: time::error::ComponentRange) -> Self {
Error::other(e)
}
}
pub fn is_err_object_not_found(err: &Error) -> bool {
matches!(err, &Error::FileNotFound) || matches!(err, &Error::ObjectNotFound(_, _))
}
pub fn is_err_version_not_found(err: &Error) -> bool {
matches!(err, &Error::FileVersionNotFound) || matches!(err, &Error::VersionNotFound(_, _, _))
}
pub fn is_err_bucket_exists(err: &Error) -> bool {
matches!(err, &StorageError::BucketExists(_))
}
pub fn is_err_read_quorum(err: &Error) -> bool {
matches!(err, &StorageError::ErasureReadQuorum)
}
pub fn classify_system_path_failure_reason(err: &Error) -> &'static str {
match err {
StorageError::ConfigNotFound => "config_not_found",
StorageError::ErasureReadQuorum | StorageError::InsufficientReadQuorum(_, _) => "read_quorum",
StorageError::Io(io_err) => match io_err.kind() {
std::io::ErrorKind::TimedOut => "timeout",
_ => "io",
},
_ => "other",
}
}
pub fn is_err_invalid_upload_id(err: &Error) -> bool {
matches!(err, &StorageError::InvalidUploadID(_, _, _))
}
pub fn is_err_bucket_not_found(err: &Error) -> bool {
matches!(err, &StorageError::VolumeNotFound)
| matches!(err, &StorageError::DiskNotFound)
| matches!(err, &StorageError::BucketNotFound(_))
}
pub fn is_err_data_movement_overwrite(err: &Error) -> bool {
matches!(err, &StorageError::DataMovementOverwriteErr(_, _, _))
}
pub fn is_err_decommission_running(err: &Error) -> bool {
matches!(err, &StorageError::DecommissionAlreadyRunning)
}
pub fn is_err_rebalance_running(err: &Error) -> bool {
matches!(err, &StorageError::RebalanceAlreadyRunning)
}
pub fn is_err_operation_canceled(err: &Error) -> bool {
matches!(err, &StorageError::OperationCanceled)
}
pub fn is_err_not_initialized(err: &Error) -> bool {
err.to_string().contains("errServerNotInitialized") || err.to_string().contains("ServerNotInitialized")
}
pub fn is_err_io(err: &Error) -> bool {
matches!(err, &StorageError::Io(_))
}
pub fn is_all_not_found(errs: &[Option<Error>]) -> bool {
for err in errs.iter() {
if let Some(err) = err {
if is_err_object_not_found(err) || is_err_version_not_found(err) || is_err_bucket_not_found(err) {
continue;
}
return false;
}
return false;
}
!errs.is_empty()
}
pub fn is_all_volume_not_found(errs: &[Option<Error>]) -> bool {
for err in errs.iter() {
if let Some(err) = err {
if is_err_bucket_not_found(err) {
continue;
}
return false;
}
return false;
}
!errs.is_empty()
}
pub fn to_object_err(err: Error, params: Vec<&str>) -> Error {
match err {
StorageError::DiskFull => StorageError::StorageFull,
StorageError::FileNotFound => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::ObjectNotFound(bucket, object)
}
StorageError::FileVersionNotFound => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
let version = params.get(2).cloned().unwrap_or_default().to_owned();
StorageError::VersionNotFound(bucket, object, version)
}
StorageError::TooManyOpenFiles => StorageError::SlowDown,
StorageError::FileNameTooLong => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::ObjectNameInvalid(bucket, object)
}
StorageError::VolumeExists => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
StorageError::BucketExists(bucket)
}
StorageError::IsNotRegular => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::ObjectExistsAsDirectory(bucket, object)
}
StorageError::VolumeNotFound => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
StorageError::BucketNotFound(bucket)
}
StorageError::VolumeNotEmpty => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
StorageError::BucketNotEmpty(bucket)
}
StorageError::FileAccessDenied => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::PrefixAccessDenied(bucket, object)
}
StorageError::ErasureReadQuorum => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::InsufficientReadQuorum(bucket, object)
}
StorageError::ErasureWriteQuorum => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::InsufficientWriteQuorum(bucket, object)
}
_ => err,
}
}
pub fn is_network_or_host_down(err: &str, _expect_timeouts: bool) -> bool {
err.contains("Connection closed by foreign host")
|| err.contains("TLS handshake timeout")
|| err.contains("i/o timeout")
|| err.contains("connection timed out")
|| err.contains("connection reset by peer")
|| err.contains("broken pipe")
|| err.to_lowercase().contains("503 service unavailable")
|| err.contains("use of closed network connection")
|| err.contains("An existing connection was forcibly closed by the remote host")
|| err.contains("client error (Connect)")
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct GenericError {
pub bucket: String,
pub object: String,
pub version_id: String,
//pub err: Error,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum ObjectApiError {
#[error("Operation timed out")]
OperationTimedOut,
#[error("etag of the object has changed")]
InvalidETag,
#[error("BackendDown")]
BackendDown(String),
#[error("Unsupported headers in Metadata")]
UnsupportedMetadata,
#[error("Method not allowed: {}/{}", .0.bucket, .0.object)]
MethodNotAllowed(GenericError),
#[error("The operation is not valid for the current state of the object {}/{}({})", .0.bucket, .0.object, .0.version_id)]
InvalidObjectState(GenericError),
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("{}", .message)]
pub struct ErrorResponse {
pub code: S3ErrorCode,
pub message: String,
pub key: Option<String>,
pub bucket_name: Option<String>,
pub region: Option<String>,
pub request_id: Option<String>,
pub host_id: String,
}
pub fn error_resp_to_object_err(err: ErrorResponse, params: Vec<&str>) -> std::io::Error {
let mut bucket = "";
let mut object = "";
let mut version_id = "";
if !params.is_empty() {
bucket = params[0];
}
if params.len() >= 2 {
object = params[1];
}
if params.len() >= 3 {
version_id = params[2];
}
if is_network_or_host_down(&err.to_string(), false) {
return std::io::Error::other(ObjectApiError::BackendDown(format!("{err}")));
}
let err_ = std::io::Error::other(err.to_string());
let r_err = err;
let err;
let bucket = bucket.to_string();
let object = object.to_string();
let version_id = version_id.to_string();
match r_err.code {
S3ErrorCode::BucketNotEmpty => {
err = std::io::Error::other(StorageError::BucketNotEmpty("".to_string()).to_string());
}
S3ErrorCode::InvalidBucketName => {
err = std::io::Error::other(StorageError::BucketNameInvalid(bucket));
}
S3ErrorCode::InvalidPart => {
err = std::io::Error::other(StorageError::InvalidPart(0, bucket, object /* , version_id */));
}
S3ErrorCode::NoSuchBucket => {
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
}
S3ErrorCode::NoSuchKey => {
if !object.is_empty() {
err = std::io::Error::other(StorageError::ObjectNotFound(bucket, object));
} else {
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
}
}
S3ErrorCode::NoSuchVersion => {
if !object.is_empty() {
err = std::io::Error::other(StorageError::ObjectNotFound(bucket, object)); //, version_id);
} else {
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
}
}
S3ErrorCode::AccessDenied => {
err = std::io::Error::other(StorageError::PrefixAccessDenied(bucket, object));
}
S3ErrorCode::NoSuchUpload => {
err = std::io::Error::other(StorageError::InvalidUploadID(bucket, object, version_id));
}
_ => {
err = err_;
}
}
err
}
pub fn storage_to_object_err(err: Error, params: Vec<&str>) -> S3Error {
let storage_err = &err;
let mut bucket: String = "".to_string();
let mut object: String = "".to_string();
if !params.is_empty() {
bucket = params[0].to_string();
}
if params.len() >= 2 {
object = decode_dir_object(params[1]);
}
match storage_err {
StorageError::MethodNotAllowed => S3Error::with_message(
S3ErrorCode::MethodNotAllowed,
ObjectApiError::MethodNotAllowed(GenericError {
bucket,
object,
..Default::default()
})
.to_string(),
),
_ => s3s::S3Error::with_message(S3ErrorCode::Custom("err".into()), err.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error as IoError, ErrorKind};
#[test]
fn test_storage_error_to_u32() {
// Test Io error uses 0x01
let io_error = StorageError::Io(IoError::other("test"));
assert_eq!(io_error.to_u32(), 0x01);
// Test other errors have correct codes
assert_eq!(StorageError::FaultyDisk.to_u32(), 0x02);
assert_eq!(StorageError::DiskFull.to_u32(), 0x03);
assert_eq!(StorageError::VolumeNotFound.to_u32(), 0x04);
assert_eq!(StorageError::VolumeExists.to_u32(), 0x05);
assert_eq!(StorageError::FileNotFound.to_u32(), 0x06);
assert_eq!(StorageError::DecommissionAlreadyRunning.to_u32(), 0x30);
assert_eq!(StorageError::RebalanceAlreadyRunning.to_u32(), 0x40);
assert_eq!(StorageError::OperationCanceled.to_u32(), 0x41);
assert_eq!(
StorageError::NamespaceLockQuorumUnavailable {
mode: "write",
bucket: "bucket".into(),
object: "object".into(),
required: 3,
achieved: 2,
}
.to_u32(),
0x42
);
}
#[test]
fn test_storage_error_from_u32() {
// Test Io error conversion
assert!(matches!(StorageError::from_u32(0x01), Some(StorageError::Io(_))));
// Test other error conversions
assert!(matches!(StorageError::from_u32(0x02), Some(StorageError::FaultyDisk)));
assert!(matches!(StorageError::from_u32(0x03), Some(StorageError::DiskFull)));
assert!(matches!(StorageError::from_u32(0x04), Some(StorageError::VolumeNotFound)));
assert!(matches!(StorageError::from_u32(0x30), Some(StorageError::DecommissionAlreadyRunning)));
assert!(matches!(StorageError::from_u32(0x40), Some(StorageError::RebalanceAlreadyRunning)));
assert!(matches!(StorageError::from_u32(0x41), Some(StorageError::OperationCanceled)));
assert!(matches!(
StorageError::from_u32(0x42),
Some(StorageError::NamespaceLockQuorumUnavailable { .. })
));
// Test invalid code returns None
assert!(StorageError::from_u32(0xFF).is_none());
}
#[test]
fn test_storage_error_code_contract_matches_storage_api() {
assert_eq!(StorageError::DiskFull.to_u32(), StorageErrorCode::DiskFull.as_u32());
assert_eq!(
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()).to_u32(),
StorageErrorCode::ObjectNotFound.as_u32()
);
assert!(matches!(
StorageError::from_u32(StorageErrorCode::ErasureReadQuorum.as_u32()),
Some(StorageError::ErasureReadQuorum)
));
assert!(matches!(
StorageError::from_u32(StorageErrorCode::NamespaceLockQuorumUnavailable.as_u32()),
Some(StorageError::NamespaceLockQuorumUnavailable { .. })
));
assert!(StorageError::from_u32(0x2B).is_none());
assert!(StorageError::from_u32(0x2C).is_none());
}
#[test]
fn test_storage_error_partial_eq() {
// Test IO error comparison
let io1 = StorageError::Io(IoError::new(ErrorKind::NotFound, "file not found"));
let io2 = StorageError::Io(IoError::new(ErrorKind::NotFound, "file not found"));
let io3 = StorageError::Io(IoError::new(ErrorKind::PermissionDenied, "access denied"));
assert_eq!(io1, io2);
assert_ne!(io1, io3);
// Test non-IO error comparison
let bucket1 = StorageError::BucketExists("test".to_string());
let bucket2 = StorageError::BucketExists("different".to_string());
assert_eq!(bucket1, bucket2); // Same error type, different parameters
let disk_error = StorageError::DiskFull;
assert_ne!(bucket1, disk_error);
}
#[test]
fn test_error_running_state_helpers() {
assert!(is_err_decommission_running(&StorageError::DecommissionAlreadyRunning));
assert!(!is_err_decommission_running(&StorageError::RebalanceAlreadyRunning));
assert!(is_err_rebalance_running(&StorageError::RebalanceAlreadyRunning));
assert!(!is_err_rebalance_running(&StorageError::DecommissionAlreadyRunning));
assert!(is_err_operation_canceled(&StorageError::OperationCanceled));
assert!(!is_err_operation_canceled(&StorageError::RebalanceAlreadyRunning));
assert!(is_err_not_initialized(&StorageError::other("errServerNotInitialized")));
assert!(is_err_not_initialized(&StorageError::other("ServerNotInitialized")));
assert!(!is_err_not_initialized(&StorageError::DecommissionAlreadyRunning));
}
#[test]
fn test_classify_system_path_failure_reason() {
assert_eq!(classify_system_path_failure_reason(&StorageError::ConfigNotFound), "config_not_found");
assert_eq!(classify_system_path_failure_reason(&StorageError::ErasureReadQuorum), "read_quorum");
assert_eq!(
classify_system_path_failure_reason(&StorageError::InsufficientReadQuorum(
"bucket".to_string(),
"object".to_string()
)),
"read_quorum"
);
assert_eq!(
classify_system_path_failure_reason(&StorageError::Io(IoError::new(ErrorKind::TimedOut, "probe"))),
"timeout"
);
assert_eq!(
classify_system_path_failure_reason(&StorageError::Io(IoError::new(ErrorKind::PermissionDenied, "probe"))),
"io"
);
assert_eq!(classify_system_path_failure_reason(&StorageError::DiskFull), "other");
}
#[test]
fn test_storage_error_from_disk_error() {
// Test conversion from DiskError
let disk_io = DiskError::Io(IoError::other("disk io error"));
let storage_error: StorageError = disk_io.into();
assert!(matches!(storage_error, StorageError::Io(_)));
let disk_full = DiskError::DiskFull;
let storage_error: StorageError = disk_full.into();
assert_eq!(storage_error, StorageError::DiskFull);
let file_not_found = DiskError::FileNotFound;
let storage_error: StorageError = file_not_found.into();
assert_eq!(storage_error, StorageError::FileNotFound);
}
#[test]
fn test_storage_error_from_io_error() {
// Test direct IO error conversion
let io_error = IoError::new(ErrorKind::NotFound, "test error");
let storage_error: StorageError = io_error.into();
assert!(matches!(storage_error, StorageError::Io(_)));
// Test IO error containing DiskError
let disk_error = DiskError::DiskFull;
let io_with_disk_error = IoError::other(disk_error);
let storage_error: StorageError = io_with_disk_error.into();
assert_eq!(storage_error, StorageError::DiskFull);
// Test IO error containing StorageError
let original_storage_error = StorageError::BucketNotFound("test".to_string());
let io_with_storage_error = IoError::other(original_storage_error.clone());
let recovered_storage_error: StorageError = io_with_storage_error.into();
assert_eq!(recovered_storage_error, original_storage_error);
}
#[test]
fn test_storage_error_to_io_error() {
// Test conversion to IO error
let storage_error = StorageError::DiskFull;
let io_error: IoError = storage_error.into();
assert_eq!(io_error.kind(), ErrorKind::Other);
// Test IO error round trip
let original_io = IoError::new(ErrorKind::PermissionDenied, "access denied");
let storage_error = StorageError::Io(original_io);
let converted_io: IoError = storage_error.into();
assert_eq!(converted_io.kind(), ErrorKind::PermissionDenied);
}
#[test]
fn test_bucket_and_object_errors() {
let bucket_not_found = StorageError::BucketNotFound("mybucket".to_string());
let object_not_found = StorageError::ObjectNotFound("mybucket".to_string(), "myobject".to_string());
let version_not_found = StorageError::VersionNotFound("mybucket".to_string(), "myobject".to_string(), "v1".to_string());
// Test different error codes
assert_ne!(bucket_not_found.to_u32(), object_not_found.to_u32());
assert_ne!(object_not_found.to_u32(), version_not_found.to_u32());
// Test error messages contain expected information
assert!(bucket_not_found.to_string().contains("mybucket"));
assert!(object_not_found.to_string().contains("mybucket"));
assert!(object_not_found.to_string().contains("myobject"));
assert!(version_not_found.to_string().contains("v1"));
}
#[test]
fn test_upload_id_errors() {
let invalid_upload = StorageError::InvalidUploadID("bucket".to_string(), "object".to_string(), "uploadid".to_string());
let malformed_upload = StorageError::MalformedUploadID("badid".to_string());
assert_ne!(invalid_upload.to_u32(), malformed_upload.to_u32());
assert!(invalid_upload.to_string().contains("uploadid"));
assert!(malformed_upload.to_string().contains("badid"));
}
#[test]
fn test_round_trip_conversion() {
// Test that to_u32 and from_u32 are consistent for all variants
let test_errors = vec![
StorageError::FaultyDisk,
StorageError::DiskFull,
StorageError::VolumeNotFound,
StorageError::BucketExists("test".to_string()),
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()),
StorageError::DecommissionAlreadyRunning,
StorageError::RebalanceAlreadyRunning,
StorageError::OperationCanceled,
];
for original_error in test_errors {
let code = original_error.to_u32();
if let Some(recovered_error) = StorageError::from_u32(code) {
// For errors with parameters, we only check the variant type
assert_eq!(std::mem::discriminant(&original_error), std::mem::discriminant(&recovered_error));
} else {
panic!("Failed to recover error from code: {code:#x}");
}
}
}
#[test]
fn test_storage_error_io_roundtrip() {
// Test StorageError -> std::io::Error -> StorageError roundtrip conversion
let original_storage_errors = vec![
StorageError::FileNotFound,
StorageError::VolumeNotFound,
StorageError::DiskFull,
StorageError::FileCorrupt,
StorageError::MethodNotAllowed,
StorageError::BucketExists("test-bucket".to_string()),
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()),
];
for original_error in original_storage_errors {
// Convert to io::Error and back
let io_error: std::io::Error = original_error.clone().into();
let recovered_error: StorageError = io_error.into();
// Check that conversion preserves the essential error information
match &original_error {
StorageError::Io(_) => {
// Io errors should maintain their inner structure
assert!(matches!(recovered_error, StorageError::Io(_)));
}
_ => {
// Other errors should be recoverable via downcast or match to equivalent type
assert_eq!(original_error.to_u32(), recovered_error.to_u32());
}
}
}
}
#[test]
fn test_io_error_with_storage_error_inside() {
// Test that io::Error containing StorageError can be properly converted back
let original_storage_error = StorageError::FileNotFound;
let io_with_storage_error = std::io::Error::other(original_storage_error.clone());
// Convert io::Error back to StorageError
let recovered_storage_error: StorageError = io_with_storage_error.into();
assert_eq!(original_storage_error, recovered_storage_error);
}
#[test]
fn test_io_error_with_disk_error_inside() {
// Test io::Error containing DiskError -> StorageError conversion
let original_disk_error = DiskError::FileNotFound;
let io_with_disk_error = std::io::Error::other(original_disk_error);
// Convert io::Error to StorageError
let storage_error: StorageError = io_with_disk_error.into();
assert_eq!(storage_error, StorageError::FileNotFound);
}
#[test]
fn test_nested_error_conversion_chain() {
// Test complex conversion chain: DiskError -> StorageError -> io::Error -> StorageError
let original_disk_error = DiskError::DiskFull;
let storage_error1: StorageError = original_disk_error.into();
let io_error: std::io::Error = storage_error1.into();
let storage_error2: StorageError = io_error.into();
assert_eq!(storage_error2, StorageError::DiskFull);
}
#[test]
fn test_storage_error_different_io_kinds() {
use std::io::ErrorKind;
let test_cases = vec![
(ErrorKind::NotFound, "not found"),
(ErrorKind::PermissionDenied, "permission denied"),
(ErrorKind::ConnectionRefused, "connection refused"),
(ErrorKind::TimedOut, "timed out"),
(ErrorKind::InvalidInput, "invalid input"),
(ErrorKind::BrokenPipe, "broken pipe"),
];
for (kind, message) in test_cases {
let io_error = std::io::Error::new(kind, message);
let storage_error: StorageError = io_error.into();
// Should become StorageError::Io with the same kind and message
match storage_error {
StorageError::Io(inner_io) => {
assert_eq!(inner_io.kind(), kind);
assert!(inner_io.to_string().contains(message));
}
_ => panic!("Expected StorageError::Io variant for kind: {kind:?}"),
}
}
}
#[test]
fn test_storage_error_to_io_error_preserves_information() {
let test_cases = vec![
StorageError::FileNotFound,
StorageError::VolumeNotFound,
StorageError::DiskFull,
StorageError::FileCorrupt,
StorageError::MethodNotAllowed,
StorageError::StorageFull,
StorageError::SlowDown,
StorageError::BucketExists("test-bucket".to_string()),
];
for storage_error in test_cases {
let io_error: std::io::Error = storage_error.clone().into();
// Error message should be preserved
assert!(io_error.to_string().contains(&storage_error.to_string()));
// Should be able to downcast back to StorageError
let recovered_error = io_error.downcast::<StorageError>();
assert!(recovered_error.is_ok());
assert_eq!(recovered_error.unwrap(), storage_error);
}
}
#[test]
fn test_storage_error_io_variant_preservation() {
// Test StorageError::Io variant preserves original io::Error
let original_io = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof");
let storage_error = StorageError::Io(original_io);
let converted_io: std::io::Error = storage_error.into();
assert_eq!(converted_io.kind(), std::io::ErrorKind::UnexpectedEof);
assert!(converted_io.to_string().contains("unexpected eof"));
}
#[test]
fn test_from_filemeta_error_conversions() {
// Test conversions from rustfs_filemeta::Error
use rustfs_filemeta::Error as FilemetaError;
let filemeta_errors = vec![
(FilemetaError::FileNotFound, StorageError::FileNotFound),
(FilemetaError::FileVersionNotFound, StorageError::FileVersionNotFound),
(FilemetaError::FileCorrupt, StorageError::FileCorrupt),
(FilemetaError::MethodNotAllowed, StorageError::MethodNotAllowed),
(FilemetaError::VolumeNotFound, StorageError::VolumeNotFound),
(FilemetaError::DoneForNow, StorageError::DoneForNow),
(FilemetaError::Unexpected, StorageError::Unexpected),
];
for (filemeta_error, expected_storage_error) in filemeta_errors {
let converted_storage_error: StorageError = filemeta_error.into();
assert_eq!(converted_storage_error, expected_storage_error);
// Test reverse conversion
let converted_back: rustfs_filemeta::Error = converted_storage_error.into();
assert_eq!(converted_back, expected_storage_error.into());
}
}
#[test]
fn test_error_message_consistency() {
let storage_errors = vec![
StorageError::BucketNotFound("test-bucket".to_string()),
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()),
StorageError::VersionNotFound("bucket".to_string(), "object".to_string(), "v1".to_string()),
StorageError::InvalidUploadID("bucket".to_string(), "object".to_string(), "upload123".to_string()),
];
for storage_error in storage_errors {
let original_message = storage_error.to_string();
let io_error: std::io::Error = storage_error.clone().into();
// The io::Error should contain the original error message or info
assert!(io_error.to_string().contains(&original_message));
}
}
#[test]
fn test_error_equality_after_conversion() {
let storage_errors = vec![
StorageError::FileNotFound,
StorageError::VolumeNotFound,
StorageError::DiskFull,
StorageError::MethodNotAllowed,
];
for original_error in storage_errors {
// Test that equality is preserved through conversion
let io_error: std::io::Error = original_error.clone().into();
let recovered_error: StorageError = io_error.into();
assert_eq!(original_error, recovered_error);
}
}
}