feat(scrub): dispatch EC CHECKSUM (mode 4) to checksum_scrub

Accept VolumeScrubMode.CHECKSUM=4 and route it to EcVolume::checksum_scrub,
accumulating blocks scanned + mismatched shards into the scrub response, plus the
CHECKSUM scrub-mode metric label. Read-only bitrot verification over local shards.

Claude-Session: https://claude.ai/code/session_015EE9Sc9EvNp8BCVva4RKdo
This commit is contained in:
Chris Lu
2026-06-30 19:26:04 -07:00
parent dfba9e1575
commit d810dbd03d
+32 -1
View File
@@ -37,6 +37,7 @@ fn scrub_mode_label(mode: i32) -> &'static str {
1 => "INDEX",
2 => "FULL",
3 => "LOCAL",
4 => "CHECKSUM",
_ => "UNKNOWN",
}
}
@@ -3911,7 +3912,7 @@ impl VolumeServer for VolumeGrpcService {
// Validate mode
let mode = req.mode;
match mode {
1 | 2 | 3 => {} // INDEX=1, FULL=2, LOCAL=3
1 | 2 | 3 | 4 => {} // INDEX=1, FULL=2, LOCAL=3, CHECKSUM=4
_ => {
return Err(Status::invalid_argument(format!(
"unsupported EC volume scrub mode {}",
@@ -4005,6 +4006,36 @@ impl VolumeServer for VolumeGrpcService {
}
}
}
4 => {
// CHECKSUM: verify each local shard's raw bytes against the
// bitrot checksum sidecar, exercising cold parity shards.
// Read-only. Mirrors Go's v.ChecksumScrub().
let (blocks_scanned, broken, errs, collection) = {
let store = self.state.store.read().unwrap();
let ecv = store.find_ec_volume(vid).ok_or_else(|| {
Status::not_found(format!("EC volume id {} not found", vid.0))
})?;
let collection = ecv.collection.clone();
let (blocks, broken, errs) = ecv.checksum_scrub();
(blocks, broken, errs, collection)
};
total_volumes += 1;
total_files += blocks_scanned;
if !errs.is_empty() || !broken.is_empty() {
broken_volume_ids.push(vid.0);
for b in broken {
broken_shard_infos.push(volume_server_pb::EcShardInfo {
volume_id: vid.0,
collection: collection.clone(),
shard_id: b,
..Default::default()
});
}
for msg in errs {
details.push(format!("ecvol {}: {}", vid.0, msg));
}
}
}
_ => unreachable!(), // validated above
}
}