ecstore update ec/disk/error

This commit is contained in:
weisd
2025-06-04 14:26:46 +08:00
committed by weisd
parent 7fe0cc74d2
commit 9384b831ec
102 changed files with 18806 additions and 4864 deletions
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "rustfs-error"
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
protos.workspace = true
rmp.workspace = true
rmp-serde.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
time.workspace = true
tonic.workspace = true
uuid.workspace = true
[lints]
workspace = true
+27
View File
@@ -0,0 +1,27 @@
use crate::Error;
pub const CHECK_PART_UNKNOWN: usize = 0;
pub const CHECK_PART_SUCCESS: usize = 1;
pub const CHECK_PART_DISK_NOT_FOUND: usize = 2;
pub const CHECK_PART_VOLUME_NOT_FOUND: usize = 3;
pub const CHECK_PART_FILE_NOT_FOUND: usize = 4;
pub const CHECK_PART_FILE_CORRUPT: usize = 5;
pub fn conv_part_err_to_int(err: &Option<Error>) -> usize {
if let Some(err) = err {
match err {
Error::FileNotFound | Error::FileVersionNotFound => CHECK_PART_FILE_NOT_FOUND,
Error::FileCorrupt => CHECK_PART_FILE_CORRUPT,
Error::VolumeNotFound => CHECK_PART_VOLUME_NOT_FOUND,
Error::DiskNotFound => CHECK_PART_DISK_NOT_FOUND,
Error::Nil => CHECK_PART_SUCCESS,
_ => CHECK_PART_UNKNOWN,
}
} else {
CHECK_PART_SUCCESS
}
}
pub fn has_part_err(part_errs: &[usize]) -> bool {
part_errs.iter().any(|err| *err != CHECK_PART_SUCCESS)
}
+92
View File
@@ -0,0 +1,92 @@
use crate::Error;
pub fn to_file_error(io_err: std::io::Error) -> std::io::Error {
match io_err.kind() {
std::io::ErrorKind::NotFound => Error::FileNotFound.into(),
std::io::ErrorKind::PermissionDenied => Error::FileAccessDenied.into(),
std::io::ErrorKind::IsADirectory => Error::IsNotRegular.into(),
std::io::ErrorKind::NotADirectory => Error::FileAccessDenied.into(),
std::io::ErrorKind::DirectoryNotEmpty => Error::FileAccessDenied.into(),
std::io::ErrorKind::UnexpectedEof => Error::FaultyDisk.into(),
std::io::ErrorKind::TooManyLinks => Error::TooManyOpenFiles.into(),
std::io::ErrorKind::InvalidInput => Error::FileNotFound.into(),
std::io::ErrorKind::InvalidData => Error::FileCorrupt.into(),
std::io::ErrorKind::StorageFull => Error::DiskFull.into(),
_ => io_err,
}
}
pub fn to_volume_error(io_err: std::io::Error) -> std::io::Error {
match io_err.kind() {
std::io::ErrorKind::NotFound => Error::VolumeNotFound.into(),
std::io::ErrorKind::PermissionDenied => Error::DiskAccessDenied.into(),
std::io::ErrorKind::DirectoryNotEmpty => Error::VolumeNotEmpty.into(),
std::io::ErrorKind::NotADirectory => Error::IsNotRegular.into(),
std::io::ErrorKind::Other => {
let err = Error::from(io_err.to_string());
match err {
Error::FileNotFound => Error::VolumeNotFound.into(),
Error::FileAccessDenied => Error::DiskAccessDenied.into(),
_ => to_file_error(io_err),
}
}
_ => to_file_error(io_err),
}
}
pub fn to_disk_error(io_err: std::io::Error) -> std::io::Error {
match io_err.kind() {
std::io::ErrorKind::NotFound => Error::DiskNotFound.into(),
std::io::ErrorKind::PermissionDenied => Error::DiskAccessDenied.into(),
std::io::ErrorKind::Other => {
let err = Error::from(io_err.to_string());
match err {
Error::FileNotFound => Error::DiskNotFound.into(),
Error::VolumeNotFound => Error::DiskNotFound.into(),
Error::FileAccessDenied => Error::DiskAccessDenied.into(),
Error::VolumeAccessDenied => Error::DiskAccessDenied.into(),
_ => to_volume_error(io_err),
}
}
_ => to_volume_error(io_err),
}
}
// only errors from FileSystem operations
pub fn to_access_error(io_err: std::io::Error, per_err: Error) -> std::io::Error {
match io_err.kind() {
std::io::ErrorKind::PermissionDenied => per_err.into(),
std::io::ErrorKind::NotADirectory => per_err.into(),
std::io::ErrorKind::NotFound => Error::VolumeNotFound.into(),
std::io::ErrorKind::UnexpectedEof => Error::FaultyDisk.into(),
std::io::ErrorKind::Other => {
let err = Error::from(io_err.to_string());
match err {
Error::DiskAccessDenied => per_err.into(),
Error::FileAccessDenied => per_err.into(),
Error::FileNotFound => Error::VolumeNotFound.into(),
_ => to_volume_error(io_err),
}
}
_ => to_volume_error(io_err),
}
}
pub fn to_unformatted_disk_error(io_err: std::io::Error) -> std::io::Error {
match io_err.kind() {
std::io::ErrorKind::NotFound => Error::UnformattedDisk.into(),
std::io::ErrorKind::PermissionDenied => Error::DiskAccessDenied.into(),
std::io::ErrorKind::Other => {
let err = Error::from(io_err.to_string());
match err {
Error::FileNotFound => Error::UnformattedDisk.into(),
Error::DiskNotFound => Error::UnformattedDisk.into(),
Error::VolumeNotFound => Error::UnformattedDisk.into(),
Error::FileAccessDenied => Error::DiskAccessDenied.into(),
Error::DiskAccessDenied => Error::DiskAccessDenied.into(),
_ => Error::CorruptedBackend.into(),
}
}
_ => Error::CorruptedBackend.into(),
}
}
+586
View File
@@ -0,0 +1,586 @@
use std::hash::Hash;
use std::str::FromStr;
const ERROR_PREFIX: &str = "[RUSTFS error] ";
pub type Result<T> = core::result::Result<T, Error>;
#[derive(thiserror::Error, Default, Debug)]
pub enum Error {
#[default]
#[error("[RUSTFS error] Nil")]
Nil,
#[error("I/O error: {0}")]
IoError(std::io::Error),
#[error("[RUSTFS error] Erasure Read quorum not met")]
ErasureReadQuorum,
#[error("[RUSTFS error] Erasure Write quorum not met")]
ErasureWriteQuorum,
#[error("[RUSTFS error] Disk not found")]
DiskNotFound,
#[error("[RUSTFS error] Faulty disk")]
FaultyDisk,
#[error("[RUSTFS error] Faulty remote disk")]
FaultyRemoteDisk,
#[error("[RUSTFS error] Unsupported disk")]
UnsupportedDisk,
#[error("[RUSTFS error] Unformatted disk")]
UnformattedDisk,
#[error("[RUSTFS error] Corrupted backend")]
CorruptedBackend,
#[error("[RUSTFS error] Disk access denied")]
DiskAccessDenied,
#[error("[RUSTFS error] Disk ongoing request")]
DiskOngoingReq,
#[error("[RUSTFS error] Disk full")]
DiskFull,
#[error("[RUSTFS error] Volume not found")]
VolumeNotFound,
#[error("[RUSTFS error] Volume not empty")]
VolumeNotEmpty,
#[error("[RUSTFS error] Volume access denied")]
VolumeAccessDenied,
#[error("[RUSTFS error] Volume exists")]
VolumeExists,
#[error("[RUSTFS error] Disk not a directory")]
DiskNotDir,
#[error("[RUSTFS error] File not found")]
FileNotFound,
#[error("[RUSTFS error] File corrupt")]
FileCorrupt,
#[error("[RUSTFS error] File access denied")]
FileAccessDenied,
#[error("[RUSTFS error] Too many open files")]
TooManyOpenFiles,
#[error("[RUSTFS error] Is not a regular file")]
IsNotRegular,
#[error("[RUSTFS error] File version not found")]
FileVersionNotFound,
#[error("[RUSTFS error] Less data than expected")]
LessData,
#[error("[RUSTFS error] Short write")]
ShortWrite,
#[error("[RUSTFS error] Done for now")]
DoneForNow,
#[error("[RUSTFS error] Method not allowed")]
MethodNotAllowed,
#[error("[RUSTFS error] Inconsistent disk")]
InconsistentDisk,
#[error("[RUSTFS error] File name too long")]
FileNameTooLong,
#[error("[RUSTFS error] Scan ignore file contribution")]
ScanIgnoreFileContrib,
#[error("[RUSTFS error] Scan skip file")]
ScanSkipFile,
#[error("[RUSTFS error] Scan heal stop signaled")]
ScanHealStopSignal,
#[error("[RUSTFS error] Scan heal idle timeout")]
ScanHealIdleTimeout,
#[error("[RUSTFS error] Scan retry healing")]
ScanRetryHealing,
#[error("[RUSTFS error] {0}")]
Other(String),
}
// Generic From implementation removed to avoid conflicts with std::convert::From<T> for T
impl FromStr for Error {
type Err = Error;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
// Only strip prefix for non-IoError
let s = if s.starts_with("I/O error: ") {
s
} else {
s.strip_prefix(ERROR_PREFIX).unwrap_or(s)
};
match s {
"Nil" => Ok(Error::Nil),
"ErasureReadQuorum" => Ok(Error::ErasureReadQuorum),
"ErasureWriteQuorum" => Ok(Error::ErasureWriteQuorum),
"DiskNotFound" | "Disk not found" => Ok(Error::DiskNotFound),
"FaultyDisk" | "Faulty disk" => Ok(Error::FaultyDisk),
"FaultyRemoteDisk" | "Faulty remote disk" => Ok(Error::FaultyRemoteDisk),
"UnformattedDisk" | "Unformatted disk" => Ok(Error::UnformattedDisk),
"DiskAccessDenied" | "Disk access denied" => Ok(Error::DiskAccessDenied),
"DiskOngoingReq" | "Disk ongoing request" => Ok(Error::DiskOngoingReq),
"FileNotFound" | "File not found" => Ok(Error::FileNotFound),
"FileCorrupt" | "File corrupt" => Ok(Error::FileCorrupt),
"FileVersionNotFound" | "File version not found" => Ok(Error::FileVersionNotFound),
"LessData" | "Less data than expected" => Ok(Error::LessData),
"ShortWrite" | "Short write" => Ok(Error::ShortWrite),
"VolumeNotFound" | "Volume not found" => Ok(Error::VolumeNotFound),
"VolumeNotEmpty" | "Volume not empty" => Ok(Error::VolumeNotEmpty),
"VolumeExists" | "Volume exists" => Ok(Error::VolumeExists),
"VolumeAccessDenied" | "Volume access denied" => Ok(Error::VolumeAccessDenied),
"DiskNotDir" | "Disk not a directory" => Ok(Error::DiskNotDir),
"FileAccessDenied" | "File access denied" => Ok(Error::FileAccessDenied),
"TooManyOpenFiles" | "Too many open files" => Ok(Error::TooManyOpenFiles),
"IsNotRegular" | "Is not a regular file" => Ok(Error::IsNotRegular),
"CorruptedBackend" | "Corrupted backend" => Ok(Error::CorruptedBackend),
"UnsupportedDisk" | "Unsupported disk" => Ok(Error::UnsupportedDisk),
"InconsistentDisk" | "Inconsistent disk" => Ok(Error::InconsistentDisk),
"DiskFull" | "Disk full" => Ok(Error::DiskFull),
"FileNameTooLong" | "File name too long" => Ok(Error::FileNameTooLong),
"ScanIgnoreFileContrib" | "Scan ignore file contribution" => Ok(Error::ScanIgnoreFileContrib),
"ScanSkipFile" | "Scan skip file" => Ok(Error::ScanSkipFile),
"ScanHealStopSignal" | "Scan heal stop signaled" => Ok(Error::ScanHealStopSignal),
"ScanHealIdleTimeout" | "Scan heal idle timeout" => Ok(Error::ScanHealIdleTimeout),
"ScanRetryHealing" | "Scan retry healing" => Ok(Error::ScanRetryHealing),
s if s.starts_with("I/O error: ") => {
Ok(Error::IoError(std::io::Error::other(s.strip_prefix("I/O error: ").unwrap_or(""))))
}
"DoneForNow" | "Done for now" => Ok(Error::DoneForNow),
"MethodNotAllowed" | "Method not allowed" => Ok(Error::MethodNotAllowed),
str => Err(Error::IoError(std::io::Error::other(str.to_string()))),
}
}
}
impl From<Error> for std::io::Error {
fn from(err: Error) -> Self {
match err {
Error::IoError(e) => e,
e => std::io::Error::other(e),
}
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
match e.kind() {
// convert Error from string to Error
std::io::ErrorKind::Other => Error::from(e.to_string()),
_ => Error::IoError(e),
}
}
}
impl From<String> for Error {
fn from(s: String) -> Self {
Error::from_str(&s).unwrap_or(Error::IoError(std::io::Error::other(s)))
}
}
impl From<&str> for Error {
fn from(s: &str) -> Self {
Error::from_str(s).unwrap_or(Error::IoError(std::io::Error::other(s)))
}
}
// Common error type conversions for ? operator
impl From<std::num::ParseIntError> for Error {
fn from(e: std::num::ParseIntError) -> Self {
Error::Other(format!("Parse int error: {}", e))
}
}
impl From<std::num::ParseFloatError> for Error {
fn from(e: std::num::ParseFloatError) -> Self {
Error::Other(format!("Parse float error: {}", e))
}
}
impl From<std::str::Utf8Error> for Error {
fn from(e: std::str::Utf8Error) -> Self {
Error::Other(format!("UTF-8 error: {}", e))
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(e: std::string::FromUtf8Error) -> Self {
Error::Other(format!("UTF-8 conversion error: {}", e))
}
}
impl From<std::fmt::Error> for Error {
fn from(e: std::fmt::Error) -> Self {
Error::Other(format!("Format error: {}", e))
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for Error {
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
Error::Other(e.to_string())
}
}
impl From<time::error::ComponentRange> for Error {
fn from(e: time::error::ComponentRange) -> Self {
Error::Other(format!("Time component range error: {}", e))
}
}
impl From<rmp::decode::NumValueReadError<std::io::Error>> for Error {
fn from(e: rmp::decode::NumValueReadError<std::io::Error>) -> Self {
Error::Other(format!("NumValueReadError: {}", e))
}
}
impl From<rmp::encode::ValueWriteError> for Error {
fn from(e: rmp::encode::ValueWriteError) -> Self {
Error::Other(format!("ValueWriteError: {}", e))
}
}
impl From<rmp::decode::ValueReadError> for Error {
fn from(e: rmp::decode::ValueReadError) -> Self {
Error::Other(format!("ValueReadError: {}", e))
}
}
impl From<uuid::Error> for Error {
fn from(e: uuid::Error) -> Self {
Error::Other(format!("UUID error: {}", e))
}
}
impl From<rmp_serde::decode::Error> for Error {
fn from(e: rmp_serde::decode::Error) -> Self {
Error::Other(format!("rmp_serde::decode::Error: {}", e))
}
}
impl From<rmp_serde::encode::Error> for Error {
fn from(e: rmp_serde::encode::Error) -> Self {
Error::Other(format!("rmp_serde::encode::Error: {}", e))
}
}
impl From<serde::de::value::Error> for Error {
fn from(e: serde::de::value::Error) -> Self {
Error::Other(format!("serde::de::value::Error: {}", e))
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Other(format!("serde_json::Error: {}", e))
}
}
impl From<std::collections::TryReserveError> for Error {
fn from(e: std::collections::TryReserveError) -> Self {
Error::Other(format!("TryReserveError: {}", e))
}
}
impl From<tonic::Status> for Error {
fn from(e: tonic::Status) -> Self {
Error::Other(format!("tonic::Status: {}", e.message()))
}
}
impl From<protos::proto_gen::node_service::Error> for Error {
fn from(e: protos::proto_gen::node_service::Error) -> Self {
Error::from_str(&e.error_info).unwrap_or(Error::Other(format!("Proto_Error: {}", e.error_info)))
}
}
impl From<Error> for protos::proto_gen::node_service::Error {
fn from(val: Error) -> Self {
protos::proto_gen::node_service::Error {
code: 0,
error_info: val.to_string(),
}
}
}
impl Hash for Error {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Error::IoError(err) => {
err.kind().hash(state);
err.to_string().hash(state);
}
e => e.to_string().hash(state),
}
}
}
impl Clone for Error {
fn clone(&self) -> Self {
match self {
Error::IoError(err) => Error::IoError(std::io::Error::new(err.kind(), err.to_string())),
Error::ErasureReadQuorum => Error::ErasureReadQuorum,
Error::ErasureWriteQuorum => Error::ErasureWriteQuorum,
Error::DiskNotFound => Error::DiskNotFound,
Error::FaultyDisk => Error::FaultyDisk,
Error::FaultyRemoteDisk => Error::FaultyRemoteDisk,
Error::UnformattedDisk => Error::UnformattedDisk,
Error::DiskAccessDenied => Error::DiskAccessDenied,
Error::DiskOngoingReq => Error::DiskOngoingReq,
Error::FileNotFound => Error::FileNotFound,
Error::FileCorrupt => Error::FileCorrupt,
Error::FileVersionNotFound => Error::FileVersionNotFound,
Error::LessData => Error::LessData,
Error::ShortWrite => Error::ShortWrite,
Error::VolumeNotFound => Error::VolumeNotFound,
Error::VolumeNotEmpty => Error::VolumeNotEmpty,
Error::VolumeAccessDenied => Error::VolumeAccessDenied,
Error::VolumeExists => Error::VolumeExists,
Error::DiskNotDir => Error::DiskNotDir,
Error::FileAccessDenied => Error::FileAccessDenied,
Error::TooManyOpenFiles => Error::TooManyOpenFiles,
Error::IsNotRegular => Error::IsNotRegular,
Error::CorruptedBackend => Error::CorruptedBackend,
Error::UnsupportedDisk => Error::UnsupportedDisk,
Error::DiskFull => Error::DiskFull,
Error::Nil => Error::Nil,
Error::DoneForNow => Error::DoneForNow,
Error::MethodNotAllowed => Error::MethodNotAllowed,
Error::InconsistentDisk => Error::InconsistentDisk,
Error::FileNameTooLong => Error::FileNameTooLong,
Error::ScanIgnoreFileContrib => Error::ScanIgnoreFileContrib,
Error::ScanSkipFile => Error::ScanSkipFile,
Error::ScanHealStopSignal => Error::ScanHealStopSignal,
Error::ScanHealIdleTimeout => Error::ScanHealIdleTimeout,
Error::ScanRetryHealing => Error::ScanRetryHealing,
Error::Other(msg) => Error::Other(msg.clone()),
}
}
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Error::IoError(e1), Error::IoError(e2)) => e1.kind() == e2.kind() && e1.to_string() == e2.to_string(),
(Error::ErasureReadQuorum, Error::ErasureReadQuorum) => true,
(Error::ErasureWriteQuorum, Error::ErasureWriteQuorum) => true,
(Error::DiskNotFound, Error::DiskNotFound) => true,
(Error::FaultyDisk, Error::FaultyDisk) => true,
(Error::FaultyRemoteDisk, Error::FaultyRemoteDisk) => true,
(Error::UnformattedDisk, Error::UnformattedDisk) => true,
(Error::DiskAccessDenied, Error::DiskAccessDenied) => true,
(Error::DiskOngoingReq, Error::DiskOngoingReq) => true,
(Error::FileNotFound, Error::FileNotFound) => true,
(Error::FileCorrupt, Error::FileCorrupt) => true,
(Error::FileVersionNotFound, Error::FileVersionNotFound) => true,
(Error::LessData, Error::LessData) => true,
(Error::ShortWrite, Error::ShortWrite) => true,
(Error::VolumeNotFound, Error::VolumeNotFound) => true,
(Error::VolumeNotEmpty, Error::VolumeNotEmpty) => true,
(Error::VolumeAccessDenied, Error::VolumeAccessDenied) => true,
(Error::VolumeExists, Error::VolumeExists) => true,
(Error::DiskNotDir, Error::DiskNotDir) => true,
(Error::FileAccessDenied, Error::FileAccessDenied) => true,
(Error::TooManyOpenFiles, Error::TooManyOpenFiles) => true,
(Error::IsNotRegular, Error::IsNotRegular) => true,
(Error::CorruptedBackend, Error::CorruptedBackend) => true,
(Error::UnsupportedDisk, Error::UnsupportedDisk) => true,
(Error::DiskFull, Error::DiskFull) => true,
(Error::Nil, Error::Nil) => true,
(Error::DoneForNow, Error::DoneForNow) => true,
(Error::MethodNotAllowed, Error::MethodNotAllowed) => true,
(Error::InconsistentDisk, Error::InconsistentDisk) => true,
(Error::FileNameTooLong, Error::FileNameTooLong) => true,
(Error::ScanIgnoreFileContrib, Error::ScanIgnoreFileContrib) => true,
(Error::ScanSkipFile, Error::ScanSkipFile) => true,
(Error::ScanHealStopSignal, Error::ScanHealStopSignal) => true,
(Error::ScanHealIdleTimeout, Error::ScanHealIdleTimeout) => true,
(Error::ScanRetryHealing, Error::ScanRetryHealing) => true,
(Error::Other(s1), Error::Other(s2)) => s1 == s2,
_ => false,
}
}
}
impl Eq for Error {}
impl Error {
/// Create an error from a message string (for backward compatibility)
pub fn msg<S: Into<String>>(message: S) -> Self {
Error::Other(message.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io;
#[test]
fn test_display_and_debug() {
let e = Error::DiskNotFound;
assert_eq!(format!("{}", e), format!("{ERROR_PREFIX}Disk not found"));
assert_eq!(format!("{:?}", e), "DiskNotFound");
let io_err = Error::IoError(io::Error::other("fail"));
assert_eq!(format!("{}", io_err), "I/O error: fail");
}
#[test]
fn test_partial_eq_and_eq() {
assert_eq!(Error::DiskNotFound, Error::DiskNotFound);
assert_ne!(Error::DiskNotFound, Error::FaultyDisk);
let e1 = Error::IoError(io::Error::other("fail"));
let e2 = Error::IoError(io::Error::other("fail"));
assert_eq!(e1, e2);
let e3 = Error::IoError(io::Error::new(io::ErrorKind::NotFound, "fail"));
assert_ne!(e1, e3);
}
#[test]
fn test_clone() {
let e = Error::DiskAccessDenied;
let cloned = e.clone();
assert_eq!(e, cloned);
let io_err = Error::IoError(io::Error::other("fail"));
let cloned_io = io_err.clone();
assert_eq!(io_err, cloned_io);
}
#[test]
fn test_hash() {
let e1 = Error::DiskNotFound;
let e2 = Error::DiskNotFound;
let mut h1 = DefaultHasher::new();
let mut h2 = DefaultHasher::new();
e1.hash(&mut h1);
e2.hash(&mut h2);
assert_eq!(h1.finish(), h2.finish());
let io_err1 = Error::IoError(io::Error::other("fail"));
let io_err2 = Error::IoError(io::Error::other("fail"));
let mut h3 = DefaultHasher::new();
let mut h4 = DefaultHasher::new();
io_err1.hash(&mut h3);
io_err2.hash(&mut h4);
assert_eq!(h3.finish(), h4.finish());
}
#[test]
fn test_from_error_for_io_error() {
let e = Error::DiskNotFound;
let io_err: io::Error = e.into();
assert_eq!(io_err.kind(), io::ErrorKind::Other);
assert_eq!(io_err.to_string(), format!("{ERROR_PREFIX}Disk not found"));
assert_eq!(Error::from(io_err.to_string()), Error::DiskNotFound);
let orig = io::Error::other("fail");
let e2 = Error::IoError(orig.kind().into());
let io_err2: io::Error = e2.into();
assert_eq!(io_err2.kind(), io::ErrorKind::Other);
}
#[test]
fn test_from_io_error_for_error() {
let orig = io::Error::other("fail");
let e: Error = orig.into();
match e {
Error::IoError(ioe) => {
assert_eq!(ioe.kind(), io::ErrorKind::Other);
assert_eq!(ioe.to_string(), "fail");
}
_ => panic!("Expected IoError variant"),
}
}
#[test]
fn test_default() {
let e = Error::default();
assert_eq!(e, Error::Nil);
}
#[test]
fn test_from_str() {
use std::str::FromStr;
assert_eq!(Error::from_str("Nil"), Ok(Error::Nil));
assert_eq!(Error::from_str("DiskNotFound"), Ok(Error::DiskNotFound));
assert_eq!(Error::from_str("ErasureReadQuorum"), Ok(Error::ErasureReadQuorum));
assert_eq!(Error::from_str("I/O error: fail"), Ok(Error::IoError(io::Error::other("fail"))));
assert_eq!(Error::from_str(&format!("{ERROR_PREFIX}Disk not found")), Ok(Error::DiskNotFound));
assert_eq!(
Error::from_str("UnknownError"),
Err(Error::IoError(std::io::Error::other("UnknownError")))
);
}
#[test]
fn test_from_string() {
let e: Error = format!("{ERROR_PREFIX}Disk not found").parse().unwrap();
assert_eq!(e, Error::DiskNotFound);
let e2: Error = "I/O error: fail".to_string().parse().unwrap();
assert_eq!(e2, Error::IoError(std::io::Error::other("fail")));
}
#[test]
fn test_from_io_error() {
let e = Error::IoError(io::Error::other("fail"));
let io_err: io::Error = e.clone().into();
assert_eq!(io_err.to_string(), "fail");
let e2: Error = io::Error::other("fail").into();
assert_eq!(e2, Error::IoError(io::Error::other("fail")));
let result = Error::from(io::Error::other("fail"));
assert_eq!(result, Error::IoError(io::Error::other("fail")));
let io_err2: std::io::Error = Error::CorruptedBackend.into();
assert_eq!(io_err2.to_string(), "[RUSTFS error] Corrupted backend");
assert_eq!(Error::from(io_err2), Error::CorruptedBackend);
let io_err3: std::io::Error = Error::DiskNotFound.into();
assert_eq!(io_err3.to_string(), "[RUSTFS error] Disk not found");
assert_eq!(Error::from(io_err3), Error::DiskNotFound);
let io_err4: std::io::Error = Error::DiskAccessDenied.into();
assert_eq!(io_err4.to_string(), "[RUSTFS error] Disk access denied");
assert_eq!(Error::from(io_err4), Error::DiskAccessDenied);
}
#[test]
fn test_question_mark_operator() {
fn parse_number(s: &str) -> Result<i32> {
let num = s.parse::<i32>()?; // ParseIntError automatically converts to Error
Ok(num)
}
fn format_string() -> Result<String> {
use std::fmt::Write;
let mut s = String::new();
write!(&mut s, "test")?; // fmt::Error automatically converts to Error
Ok(s)
}
fn utf8_conversion() -> Result<String> {
let bytes = vec![0xFF, 0xFE]; // Invalid UTF-8
let s = String::from_utf8(bytes)?; // FromUtf8Error automatically converts to Error
Ok(s)
}
// Test successful case
assert_eq!(parse_number("42").unwrap(), 42);
// Test error conversion
let err = parse_number("not_a_number").unwrap_err();
assert!(matches!(err, Error::Other(_)));
assert!(err.to_string().contains("Parse int error"));
// Test format error conversion
assert_eq!(format_string().unwrap(), "test");
// Test UTF-8 error conversion
let err = utf8_conversion().unwrap_err();
assert!(matches!(err, Error::Other(_)));
assert!(err.to_string().contains("UTF-8 conversion error"));
}
}
+11
View File
@@ -0,0 +1,11 @@
use crate::Error;
pub static OBJECT_OP_IGNORED_ERRS: &[Error] = &[
Error::DiskNotFound,
Error::FaultyDisk,
Error::FaultyRemoteDisk,
Error::DiskAccessDenied,
Error::DiskOngoingReq,
Error::UnformattedDisk,
];
pub static BASE_IGNORED_ERRS: &[Error] = &[Error::DiskNotFound, Error::FaultyDisk, Error::FaultyRemoteDisk];
+14
View File
@@ -0,0 +1,14 @@
mod error;
pub use error::*;
mod reduce;
pub use reduce::*;
mod ignored;
pub use ignored::*;
mod convert;
pub use convert::*;
mod bitrot;
pub use bitrot::*;
+138
View File
@@ -0,0 +1,138 @@
use crate::error::Error;
pub fn reduce_write_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quorun: usize) -> Option<Error> {
reduce_quorum_errs(errors, ignored_errs, quorun, Error::ErasureWriteQuorum)
}
pub fn reduce_read_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quorun: usize) -> Option<Error> {
reduce_quorum_errs(errors, ignored_errs, quorun, Error::ErasureReadQuorum)
}
pub fn reduce_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quorun: usize, quorun_err: Error) -> Option<Error> {
let (max_count, err) = reduce_errs(errors, ignored_errs);
if max_count >= quorun {
err
} else {
Some(quorun_err)
}
}
pub fn reduce_errs(errors: &[Option<Error>], ignored_errs: &[Error]) -> (usize, Option<Error>) {
let err_counts =
errors
.iter()
.map(|e| e.as_ref().unwrap_or(&Error::Nil))
.fold(std::collections::HashMap::new(), |mut acc, e| {
if is_ignored_err(ignored_errs, e) {
return acc;
}
*acc.entry(e.clone()).or_insert(0) += 1;
acc
});
let (err, max_count) = err_counts
.into_iter()
.max_by(|(e1, c1), (e2, c2)| {
// Prefer Error::Nil if present in a tie
let count_cmp = c1.cmp(c2);
if count_cmp == std::cmp::Ordering::Equal {
match (e1, e2) {
(Error::Nil, _) => std::cmp::Ordering::Greater,
(_, Error::Nil) => std::cmp::Ordering::Less,
_ => format!("{e1:?}").cmp(&format!("{e2:?}")),
}
} else {
count_cmp
}
})
.unwrap_or((Error::Nil, 0));
(max_count, if err == Error::Nil { None } else { Some(err) })
}
pub fn is_ignored_err(ignored_errs: &[Error], err: &Error) -> bool {
ignored_errs.iter().any(|e| e == err)
}
pub fn count_errs(errors: &[Option<Error>], err: Error) -> usize {
errors
.iter()
.map(|e| if e.is_none() { &Error::Nil } else { e.as_ref().unwrap() })
.filter(|&e| e == &err)
.count()
}
#[cfg(test)]
mod tests {
use super::*;
fn err_io(msg: &str) -> Error {
Error::IoError(std::io::Error::other(msg))
}
#[test]
fn test_reduce_errs_basic() {
let e1 = err_io("a");
let e2 = err_io("b");
let errors = vec![Some(e1.clone()), Some(e1.clone()), Some(e2.clone()), None];
let ignored = vec![];
let (count, err) = reduce_errs(&errors, &ignored);
assert_eq!(count, 2);
assert_eq!(err, Some(e1));
}
#[test]
fn test_reduce_errs_ignored() {
let e1 = err_io("a");
let e2 = err_io("b");
let errors = vec![Some(e1.clone()), Some(e2.clone()), Some(e1.clone()), Some(e2.clone()), None];
let ignored = vec![e2.clone()];
let (count, err) = reduce_errs(&errors, &ignored);
assert_eq!(count, 2);
assert_eq!(err, Some(e1));
}
#[test]
fn test_reduce_quorum_errs() {
let e1 = err_io("a");
let e2 = err_io("b");
let errors = vec![Some(e1.clone()), Some(e1.clone()), Some(e2.clone()), None];
let ignored = vec![];
// quorum = 2, should return e1
let res = reduce_quorum_errs(&errors, &ignored, 2, Error::ErasureReadQuorum);
assert_eq!(res, Some(e1));
// quorum = 3, should return quorum error
let res = reduce_quorum_errs(&errors, &ignored, 3, Error::ErasureReadQuorum);
assert_eq!(res, Some(Error::ErasureReadQuorum));
}
#[test]
fn test_count_errs() {
let e1 = err_io("a");
let e2 = err_io("b");
let errors = vec![Some(e1.clone()), Some(e2.clone()), Some(e1.clone()), None];
assert_eq!(count_errs(&errors, e1.clone()), 2);
assert_eq!(count_errs(&errors, e2.clone()), 1);
}
#[test]
fn test_is_ignored_err() {
let e1 = err_io("a");
let e2 = err_io("b");
let ignored = vec![e1.clone()];
assert!(is_ignored_err(&ignored, &e1));
assert!(!is_ignored_err(&ignored, &e2));
}
#[test]
fn test_reduce_errs_nil_tiebreak() {
// Error::Nil and another error have the same count, should prefer Nil
let e1 = err_io("a");
let e2 = err_io("b");
let errors = vec![Some(e1.clone()), Some(e2.clone()), None, Some(e1.clone()), None]; // e1:1, Nil:1
let ignored = vec![];
let (count, err) = reduce_errs(&errors, &ignored);
assert_eq!(count, 2);
assert_eq!(err, None); // None means Error::Nil is preferred
}
}