mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 21:26:28 +00:00
fix(ecstore): align Deep verifier shard geometry (#4656)
This commit is contained in:
@@ -32,7 +32,7 @@ use crate::disk::{
|
|||||||
os,
|
os,
|
||||||
os::{check_path_length, is_empty_dir, is_root_disk, rename_all, rename_all_ignore_missing_source},
|
os::{check_path_length, is_empty_dir, is_root_disk, rename_all, rename_all_ignore_missing_source},
|
||||||
};
|
};
|
||||||
use crate::erasure::coding::bitrot_verify;
|
use crate::erasure::coding::{self, bitrot_verify};
|
||||||
use crate::runtime::sources as runtime_sources;
|
use crate::runtime::sources as runtime_sources;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use metrics::counter;
|
use metrics::counter;
|
||||||
@@ -5285,6 +5285,12 @@ impl DiskAPI for LocalDisk {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let erasure = &fi.erasure;
|
let erasure = &fi.erasure;
|
||||||
|
let codec_erasure = coding::Erasure::new_with_options(
|
||||||
|
erasure.data_blocks,
|
||||||
|
erasure.parity_blocks,
|
||||||
|
erasure.block_size,
|
||||||
|
fi.uses_legacy_checksum,
|
||||||
|
);
|
||||||
for (i, part) in fi.parts.iter().enumerate() {
|
for (i, part) in fi.parts.iter().enumerate() {
|
||||||
let checksum_info = erasure.get_checksum_info(part.number);
|
let checksum_info = erasure.get_checksum_info(part.number);
|
||||||
let checksum_algo =
|
let checksum_algo =
|
||||||
@@ -5305,9 +5311,9 @@ impl DiskAPI for LocalDisk {
|
|||||||
let err = self
|
let err = self
|
||||||
.bitrot_verify(
|
.bitrot_verify(
|
||||||
&part_path,
|
&part_path,
|
||||||
erasure.shard_file_size(part.size as i64) as usize,
|
codec_erasure.shard_file_size(part.size as i64) as usize,
|
||||||
checksum_algo,
|
checksum_algo,
|
||||||
erasure.shard_size(),
|
codec_erasure.shard_size(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err();
|
.err();
|
||||||
@@ -12352,6 +12358,69 @@ mod test {
|
|||||||
assert_eq!(result.results, vec![CHECK_PART_FILE_CORRUPT]);
|
assert_eq!(result.results, vec![CHECK_PART_FILE_CORRUPT]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn local_disk_verify_file_matches_legacy_and_nonlegacy_shard_geometry() {
|
||||||
|
use crate::erasure::coding::{BitrotWriter, Erasure};
|
||||||
|
use rustfs_filemeta::ChecksumInfo;
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (object, payload, uses_legacy_checksum) in [
|
||||||
|
("nonlegacy.bin", Bytes::from_static(b"sharddata"), false),
|
||||||
|
("legacy.bin", Bytes::from_static(b"legacydata"), true),
|
||||||
|
] {
|
||||||
|
let data_dir = Uuid::new_v4();
|
||||||
|
let part_number = 1;
|
||||||
|
let codec_erasure = Erasure::new_with_options(2, 2, 16, uses_legacy_checksum);
|
||||||
|
let mut file_info = FileInfo::new(object, 2, 2);
|
||||||
|
file_info.volume = volume.to_string();
|
||||||
|
file_info.name = object.to_string();
|
||||||
|
file_info.size = 17;
|
||||||
|
file_info.data_dir = Some(data_dir);
|
||||||
|
file_info.uses_legacy_checksum = uses_legacy_checksum;
|
||||||
|
file_info.erasure.block_size = 16;
|
||||||
|
file_info.erasure.index = 1;
|
||||||
|
file_info.erasure.checksums = vec![ChecksumInfo {
|
||||||
|
part_number,
|
||||||
|
algorithm: HashAlgorithm::HighwayHash256S,
|
||||||
|
hash: Bytes::new(),
|
||||||
|
}];
|
||||||
|
file_info.parts = vec![ObjectPartInfo {
|
||||||
|
number: part_number,
|
||||||
|
size: 17,
|
||||||
|
actual_size: 17,
|
||||||
|
..Default::default()
|
||||||
|
}];
|
||||||
|
|
||||||
|
let checksum_algo = if uses_legacy_checksum {
|
||||||
|
HashAlgorithm::HighwayHash256SLegacy
|
||||||
|
} else {
|
||||||
|
HashAlgorithm::HighwayHash256S
|
||||||
|
};
|
||||||
|
let mut writer = BitrotWriter::new(std::io::Cursor::new(Vec::new()), codec_erasure.shard_size(), checksum_algo);
|
||||||
|
writer.write(&payload[..8]).await.expect("first shard block should encode");
|
||||||
|
writer.write(&payload[8..]).await.expect("final shard block should encode");
|
||||||
|
writer.shutdown().await.expect("bitrot writer should flush test payload");
|
||||||
|
|
||||||
|
let part_path = path_join_buf(&[object, &data_dir.to_string(), &format!("part.{part_number}")]);
|
||||||
|
disk.write_all(volume, &part_path, Bytes::from(writer.into_inner().into_inner()))
|
||||||
|
.await
|
||||||
|
.expect("encoded part should be written");
|
||||||
|
|
||||||
|
let result = disk
|
||||||
|
.verify_file(volume, object, &file_info)
|
||||||
|
.await
|
||||||
|
.expect("verify_file should return per-part status");
|
||||||
|
|
||||||
|
assert_eq!(result.results, vec![CHECK_PART_SUCCESS]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----- HP-6: O_DIRECT shard write -----
|
// ----- HP-6: O_DIRECT shard write -----
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -565,4 +565,43 @@ mod serial_tests {
|
|||||||
"version must remain byte-identical after heal"
|
"version must remain byte-identical after heal"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
|
#[serial]
|
||||||
|
async fn deep_heal_keeps_present_ec2_plus_2_shards_healthy() {
|
||||||
|
disable_dangling_grace();
|
||||||
|
let (disk_paths, ecstore, heal_storage) = setup_test_env_n(4).await;
|
||||||
|
let bucket = "b1044-deep-verify";
|
||||||
|
let object = "obj.bin";
|
||||||
|
create_versioned_bucket(&ecstore, bucket).await;
|
||||||
|
|
||||||
|
let data_v1 = versioned_test_data(12);
|
||||||
|
let v1 = put_versioned(&ecstore, bucket, object, &data_v1).await;
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(object_dir(&disk_paths[3], bucket, object)).expect("wipe object on disk3");
|
||||||
|
for disk in &disk_paths[..3] {
|
||||||
|
assert_eq!(count_part_files(&object_dir(disk, bucket, object)), 1, "intact shard must remain present");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_result, error) = heal_storage
|
||||||
|
.heal_object(bucket, object, Some(&v1), &deep_heal_opts())
|
||||||
|
.await
|
||||||
|
.expect("deep heal_object call must not itself error");
|
||||||
|
assert!(error.is_none(), "Deep heal must retain the three intact EC2+2 shards: {error:?}");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
xl_meta_path(&object_dir(&disk_paths[3], bucket, object)).exists(),
|
||||||
|
"Deep heal must restore xl.meta on the missing disk"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
count_part_files(&object_dir(&disk_paths[3], bucket, object)),
|
||||||
|
1,
|
||||||
|
"Deep heal must restore the missing shard"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
read_version(&ecstore, bucket, object, &v1).await,
|
||||||
|
data_v1,
|
||||||
|
"Deep-healed EC2+2 object must remain byte-identical"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user