From 92596da7fa523489f9471a88b56a18920f6c1f7d Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Mon, 6 Jul 2026 22:58:52 +0800 Subject: [PATCH] fix(filemeta): key round-tripped replication reset state by canonical header (backlog#799 B16) (#4325) fix(filemeta): key round-tripped replication reset state by canonical header (backlog#799 B19->B16) `get_internal_replication_state` stored the reset status under the bare ARN after stripping the internal prefix, while the write and lookup sides (`get_replication_state` / `ReplicationState::target_state`) use the full `target_reset_header(arn)` key. A `target_state` fallback masked the lookup miss, but the map stayed keyed inconsistently (bare on read, full on write), which can drop reset state across merge/reflatten cycles. Store the canonical `target_reset_header(arn)` key on read so the map is consistent everywhere; the lookup fallback becomes belt-and-suspenders rather than load-bearing. Adds a round-trip regression test. Refs backlog#799 (B16), tracked in rustfs/backlog#863. --- crates/filemeta/src/filemeta/version.rs | 34 ++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/filemeta/src/filemeta/version.rs b/crates/filemeta/src/filemeta/version.rs index b968a8e5e..6f5ff89d2 100644 --- a/crates/filemeta/src/filemeta/version.rs +++ b/crates/filemeta/src/filemeta/version.rs @@ -2326,7 +2326,14 @@ fn get_internal_replication_state(metadata: &HashMap) -> Option< _ => { if let Some(arn) = sub_key.strip_prefix("replication-reset-") { has = true; - rs.reset_statuses_map.insert(arn.to_string(), v.clone()); + // Store the canonical full-header key so the map matches + // the key `target_reset_header()` produces on the + // write/lookup side. Storing the bare ARN keyed the map + // inconsistently (bare on read, full on write), which + // could drop reset state across merge/reflatten cycles + // (backlog#799 B16). + rs.reset_statuses_map + .insert(crate::replication::target_reset_header(arn), v.clone()); } } } @@ -3645,4 +3652,29 @@ mod tests { assert_eq!(dm.version_id, Some(vid)); assert!(dm.mod_time.is_some()); } + + #[test] + fn get_internal_replication_state_keeps_canonical_reset_key() { + // The reset status is stored on disk under the full internal key; parsing + // must keep it keyed by `target_reset_header(arn)` (not the bare ARN) so + // `ReplicationState::target_state` finds it after a round trip + // (backlog#799 B16). + let arn = "arn:rustfs:replication:us-east-1:target:bucket"; + let ts = "2026-06-30T00:00:00Z;reset-1".to_string(); + let key = crate::replication::target_reset_header(arn); + let mut metadata = HashMap::new(); + metadata.insert(key.clone(), ts.clone()); + + let rs = get_internal_replication_state(&metadata).expect("reset state should parse"); + assert_eq!( + rs.reset_statuses_map.get(&key), + Some(&ts), + "map must be keyed by target_reset_header, not the bare ARN" + ); + assert_eq!( + rs.target_state(arn).resync_timestamp, + ts, + "lookup must find the round-tripped reset status" + ); + } }