mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 15:04:37 +00:00
fix(storage): surface stat error on zero-size idx scrub, mirror to rust (#9612)
fix(storage): harden zero-size idx scrub and mirror to rust When a zero-size .idx is found, openIndex stats the backing .dat through v.DataBackend: wrap that GetStat failure with %w, fix the indices typo, and guard both openIndex and scrubVolumeData against a nil DataBackend (closed or remote-only volumes) instead of panicking. Add rust scrub tests for empty (superblock-only .dat, zero-size .idx) and healthy volumes, keeping the volume server in parity with the go zero-size scrub handling.
This commit is contained in:
@@ -3643,6 +3643,67 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scrub_empty_volume() {
|
||||
// Mirror of Go's TestScrubVolumeData "zero-size volume without index"
|
||||
// case (weed/storage/volume_checking_test.go): a freshly created /
|
||||
// pre-allocated volume has a superblock-only .dat and a zero-size .idx,
|
||||
// and must scrub clean instead of being flagged as corrupt.
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let dir = tmp.path().to_str().unwrap();
|
||||
let v = make_test_volume(dir);
|
||||
|
||||
// .dat holds only the superblock; .idx is empty.
|
||||
assert_eq!(v.dat_file_size().unwrap(), SUPER_BLOCK_SIZE as u64);
|
||||
|
||||
let (files_checked, broken) = v.scrub().unwrap();
|
||||
assert_eq!(files_checked, 0);
|
||||
assert!(
|
||||
broken.is_empty(),
|
||||
"empty volume should scrub clean, got {:?}",
|
||||
broken
|
||||
);
|
||||
|
||||
// The index-only mode must agree.
|
||||
let (idx_checked, idx_broken) = v.scrub_index().unwrap();
|
||||
assert_eq!(idx_checked, 0);
|
||||
assert!(
|
||||
idx_broken.is_empty(),
|
||||
"empty volume should scrub_index clean, got {:?}",
|
||||
idx_broken
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scrub_healthy_volume() {
|
||||
// Mirror of Go's TestScrubVolumeData "healthy volume" case: a volume
|
||||
// with live needles scrubs clean and the .dat size accounting matches.
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let dir = tmp.path().to_str().unwrap();
|
||||
let mut v = make_test_volume(dir);
|
||||
|
||||
for i in 1..=5 {
|
||||
let data = format!("needle data {}", i);
|
||||
let mut n = Needle {
|
||||
id: NeedleId(i),
|
||||
cookie: Cookie(i as u32),
|
||||
data: data.as_bytes().to_vec(),
|
||||
data_size: data.len() as u32,
|
||||
..Needle::default()
|
||||
};
|
||||
v.write_needle(&mut n, true).unwrap();
|
||||
}
|
||||
v.sync_to_disk().unwrap();
|
||||
|
||||
let (files_checked, broken) = v.scrub().unwrap();
|
||||
assert_eq!(files_checked, 5);
|
||||
assert!(
|
||||
broken.is_empty(),
|
||||
"healthy volume should scrub clean, got {:?}",
|
||||
broken
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_multiple_needles() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
|
||||
@@ -29,14 +29,18 @@ func (v *Volume) openIndex() (*os.File, int64, error) {
|
||||
}
|
||||
|
||||
if idxStat.Size() == 0 {
|
||||
if v.DataBackend == nil {
|
||||
idxFile.Close()
|
||||
return nil, 0, fmt.Errorf("volume %v has no data backend", v.Id)
|
||||
}
|
||||
volumeFileSize, _, err := v.DataBackend.GetStat()
|
||||
if err != nil {
|
||||
idxFile.Close()
|
||||
return nil, 0, fmt.Errorf("failed to stat storage for zero-size IDX volume %v", v.Id)
|
||||
return nil, 0, fmt.Errorf("failed to stat storage for zero-size IDX volume %v: %w", v.Id, err)
|
||||
}
|
||||
|
||||
// account for pre-allocated volumes (f.ex. after running "volume.grow") without data, as these
|
||||
// are allowed to have zero-size indeces.
|
||||
// are allowed to have zero-size indices.
|
||||
if volumeFileSize > int64(super_block.SuperBlockSize) {
|
||||
idxFile.Close()
|
||||
return nil, 0, fmt.Errorf("zero-size IDX file for volume %v with store size %d", v.Id, volumeFileSize)
|
||||
@@ -62,6 +66,10 @@ func (v *Volume) ScrubIndex() (int64, []error) {
|
||||
|
||||
// scrubVolumeData checks a volume content + index for issues.
|
||||
func (v *Volume) scrubVolumeData(idxFile *os.File, idxFileSize int64) (int64, []error) {
|
||||
if v.DataBackend == nil {
|
||||
return 0, []error{fmt.Errorf("volume %d has no data backend", v.Id)}
|
||||
}
|
||||
|
||||
// full scrubbing means also scrubbing the index
|
||||
var count int64
|
||||
_, errs := idx.CheckIndexFile(idxFile, idxFileSize, v.Version())
|
||||
|
||||
Reference in New Issue
Block a user