mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-17 18:27:49 +00:00
fix(ecstore): map disk-representable StorageError variants in reverse conversion (#5980)
The StorageError -> DiskError conversion dropped seven variants with exact DiskError counterparts (FaultyRemoteDisk, DiskAccessDenied, DriveIsRoot, IsNotRegular, VolumeNotEmpty, VolumeAccessDenied, FileAccessDenied) into the DiskError::other fallback, degrading them to an opaque Io error. Two of them sit on the quorum ignore-lists in disk/error_reduce.rs, so a degraded instance would stop matching the ignore list and count toward the dominant error in reduce_errs. Also mirror the StorageError-side io::Error downdrill in From<io::Error> for DiskError: recover a StorageError boxed through From<StorageError> for io::Error instead of wrapping it as Io. Add a round-trip identity test over every DiskError variant (DiskError -> StorageError -> DiskError) and a boxed-StorageError recovery test.
This commit is contained in:
@@ -358,6 +358,13 @@ impl From<StorageError> for DiskError {
|
||||
StorageError::VolumeNotFound => DiskError::VolumeNotFound,
|
||||
StorageError::VolumeExists => DiskError::VolumeExists,
|
||||
StorageError::FileNameTooLong => DiskError::FileNameTooLong,
|
||||
StorageError::FaultyRemoteDisk => DiskError::FaultyRemoteDisk,
|
||||
StorageError::DiskAccessDenied => DiskError::DiskAccessDenied,
|
||||
StorageError::DriveIsRoot => DiskError::DriveIsRoot,
|
||||
StorageError::IsNotRegular => DiskError::IsNotRegular,
|
||||
StorageError::VolumeNotEmpty => DiskError::VolumeNotEmpty,
|
||||
StorageError::VolumeAccessDenied => DiskError::VolumeAccessDenied,
|
||||
StorageError::FileAccessDenied => DiskError::FileAccessDenied,
|
||||
_ => DiskError::other(val),
|
||||
}
|
||||
}
|
||||
@@ -1492,6 +1499,49 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// Every DiskError variant must survive DiskError -> StorageError -> DiskError
|
||||
// unchanged. A variant that degrades to `DiskError::Io` on the way back loses
|
||||
// its identity for quorum aggregation (`reduce_errs` classifies by variant
|
||||
// equality), so ignore-list entries such as FaultyRemoteDisk and
|
||||
// DiskAccessDenied would silently stop matching.
|
||||
#[test]
|
||||
fn test_disk_error_storage_error_round_trip_identity_all_variants() {
|
||||
// DiskError codes are contiguous from 0x01, so enumerating via from_u32
|
||||
// covers every variant and picks up newly appended ones automatically.
|
||||
let all_variants: Vec<DiskError> = (1u32..).map_while(DiskError::from_u32).collect();
|
||||
assert!(
|
||||
all_variants.len() >= 42,
|
||||
"DiskError variant enumeration shrank: got {}, expected at least 42",
|
||||
all_variants.len()
|
||||
);
|
||||
|
||||
for original in all_variants {
|
||||
let storage_error: StorageError = original.clone().into();
|
||||
let round_tripped: DiskError = storage_error.into();
|
||||
|
||||
assert_eq!(
|
||||
std::mem::discriminant(&original),
|
||||
std::mem::discriminant(&round_tripped),
|
||||
"round trip changed variant: {original:?} -> {round_tripped:?}"
|
||||
);
|
||||
assert_eq!(original, round_tripped, "round trip not identical for {original:?}");
|
||||
}
|
||||
|
||||
// Io is the only payload-carrying variant: a representative kind and
|
||||
// message must both survive the round trip.
|
||||
let io_original = DiskError::Io(IoError::new(ErrorKind::PermissionDenied, "denied"));
|
||||
let storage_error: StorageError = io_original.clone().into();
|
||||
let io_round_tripped: DiskError = storage_error.into();
|
||||
assert_eq!(io_original, io_round_tripped);
|
||||
match io_round_tripped {
|
||||
DiskError::Io(inner) => {
|
||||
assert_eq!(inner.kind(), ErrorKind::PermissionDenied);
|
||||
assert_eq!(inner.to_string(), "denied");
|
||||
}
|
||||
other => panic!("expected DiskError::Io, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_error_from_io_error() {
|
||||
// Test direct IO error conversion
|
||||
|
||||
Reference in New Issue
Block a user