fix(ecstore): resolve erasure parity per pool (#4801) (#5015)

* fix(ecstore): add fallible erasure construction

(cherry picked from commit bd148b20f7)

* fix(ecstore): resolve storage parity per pool

(cherry picked from commit c05c2cb24b)

* fix(ecstore): keep carved per-pool parity core self-contained on main

Fixups so the cherry-picked fallible-erasure + per-pool-parity core builds standalone on current main without the excluded scope-creep commits:
- runtime/sources.rs: re-add backend_storage_class_parities (removed by the per-pool commit; its rebalance caller was updated in an unrelated reporting commit that was left out). Reimplemented over the snapshot API, behavior-identical.
- config/mod.rs: rename the storage-class publish test module (main independently added a mod tests, so the cherry-pick collided).
- rustfs storage_api.rs + startup_storage.rs: route the storage-class ENV consts through the startup storage facade and use a local const for the erasure-set-drive-count env name, satisfying the layer/facade guardrail (main's guardrail is stricter than when the core was authored).

* fix(ecstore): use struct-init in erasure test helper to satisfy clippy field_reassign_with_default

The cherry-picked fallible-erasure commit's `erasure_with_invalid_dimensions` test helper built `Erasure` via `default()` then reassigned fields, which trips `clippy::field_reassign_with_default` under `-D warnings` (only surfaced by `--all-targets`, which lints test code). #4977 fixed this in a later commit that was not part of the carved core. Use struct-init with `..Default::default()`, matching #4977's final form.

* fix(rustfs): gate the test-only storage-class ENV facade re-export behind cfg(test)

The ENV constants (INLINE_BLOCK_ENV/OPTIMIZE_ENV/RRS_ENV/STANDARD_ENV) re-exported through the startup storage facade are only consumed by a #[cfg(test)] test in startup_storage.rs, so in a non-test lib build the re-export is unused and trips -D unused-imports under clippy --all-targets. Gate it with #[cfg(test)], matching #4977's final form.

---------

Co-authored-by: cxymds <cxymds@gmail.com>
This commit is contained in:
Zhengchao An
2026-07-19 11:06:46 +08:00
committed by GitHub
parent 3ed682be42
commit 7f5873dac8
22 changed files with 1463 additions and 248 deletions
+27 -1
View File
@@ -141,7 +141,7 @@ pub enum DiskError {
ErasureReadQuorum,
#[error("io error {0}")]
Io(io::Error),
Io(#[source] io::Error),
#[error("source stalled")]
SourceStalled,
@@ -153,6 +153,12 @@ pub enum DiskError {
InvalidPath,
}
impl From<crate::erasure::coding::ErasureConstructionError> for DiskError {
fn from(error: crate::erasure::coding::ErasureConstructionError) -> Self {
Self::Io(error.into_io_error())
}
}
impl DiskError {
pub fn other<E>(error: E) -> Self
where
@@ -601,6 +607,26 @@ mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn other_preserves_erasure_construction_source_chain() {
use crate::erasure::coding::ErasureConstructionError;
use std::error::Error as _;
let error = DiskError::from(ErasureConstructionError::ModernEncoder {
source: reed_solomon_erasure::Error::TooManyShards,
});
let io_source = error.source().expect("DiskError::Io must expose its io::Error source");
assert!(io_source.is::<io::Error>());
let construction_source = io_source
.source()
.expect("io::Error must expose the erasure construction error");
assert!(construction_source.is::<ErasureConstructionError>());
let encoder_source = construction_source
.source()
.expect("construction error must expose the encoder error");
assert!(encoder_source.is::<reed_solomon_erasure::Error>());
}
#[test]
fn test_disk_error_variants() {
let errors = vec![
+29 -2
View File
@@ -5962,12 +5962,13 @@ impl DiskAPI for LocalDisk {
};
let erasure = &fi.erasure;
let codec_erasure = coding::Erasure::new_with_options(
let codec_erasure = coding::Erasure::try_new_with_options(
erasure.data_blocks,
erasure.parity_blocks,
erasure.block_size,
fi.uses_legacy_checksum,
);
)
.map_err(DiskError::from)?;
for (i, part) in fi.parts.iter().enumerate() {
let checksum_info = erasure.get_checksum_info(part.number);
let checksum_algo = if fi.uses_legacy_checksum && checksum_info.algorithm == HashAlgorithm::HighwayHash256S {
@@ -13600,6 +13601,32 @@ mod test {
assert!(!is_bitrot_verification_error(&io::Error::other("unrelated io failure")));
}
#[tokio::test]
async fn local_disk_verify_file_preserves_erasure_construction_error() {
use crate::erasure::coding::ErasureConstructionError;
use tempfile::tempdir;
let root_dir = tempdir().expect("temp dir should be created");
let endpoint = Endpoint::try_from(root_dir.path().to_string_lossy().as_ref()).expect("endpoint should parse");
let disk = LocalDisk::new(&endpoint, false).await.expect("local disk should be created");
let volume = "verify-volume";
ensure_test_volume(&disk, volume).await;
let mut file_info = FileInfo::new("invalid.bin", 2, 2);
file_info.erasure.block_size = 0;
let error = match disk.verify_file(volume, "invalid.bin", &file_info).await {
Ok(_) => panic!("invalid local-disk erasure metadata must be rejected"),
Err(error) => error,
};
assert!(error.to_string().contains("block_size must be greater than zero"));
let io_source = std::error::Error::source(&error).expect("DiskError::Io must expose its io::Error source");
let construction_source = io_source
.source()
.expect("io::Error must expose the erasure construction error");
assert!(construction_source.is::<ErasureConstructionError>());
}
#[tokio::test]
async fn local_disk_read_file_verifier_reports_bitrot_mismatch() {
use crate::erasure::coding::BitrotWriter;