From 7ddaae397b5b5f2676f5c0f2ed89bef61253d45e Mon Sep 17 00:00:00 2001 From: abdullahnah92 <157595835+abdullahnah92@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:49:51 +0300 Subject: [PATCH] fix(site-repl): count replicated buckets as in-sync (base64 decode in status path) (#5022) fix(site-replication): count replicated buckets as in-sync in replicate status `mc admin replicate status` reported "0/N Buckets in sync" with a cross mark for every bucket even when replication was healthy and objects had propagated. build_sr_info stores each bucket's replication_config in the wire form as base64-encoded XML (raw_config_to_base64), but site_replication_config_mismatch XML-parsed that string directly without base64-decoding. The parse always failed, so every bucket with a replication config was reported as a config mismatch (replication_cfg_mismatch = true), which the status endpoint and mc render as out-of-sync. Decode the wire value (tolerant base64 decode, falling back to raw bytes) before XML-parsing. Adds a regression test that feeds the base64 wire form used in production; the existing tests only exercised raw XML, which masked the bug. Co-authored-by: Claude Opus 4.8 Co-authored-by: overtrue --- rustfs/src/admin/handlers/site_replication.rs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/rustfs/src/admin/handlers/site_replication.rs b/rustfs/src/admin/handlers/site_replication.rs index 9069a3315..713a5be26 100644 --- a/rustfs/src/admin/handlers/site_replication.rs +++ b/rustfs/src/admin/handlers/site_replication.rs @@ -3285,7 +3285,13 @@ fn site_replication_config_mismatch<'a>(values: impl Iterator(raw.as_bytes()) + // `raw` is the wire form produced by build_sr_info, i.e. base64-encoded XML + // (raw_config_to_base64). Decode it before XML-parsing — parsing the base64 text + // directly always fails, which would falsely report every replicated bucket as + // out-of-sync ("0/N Buckets in sync"). decode_bucket_meta_wire_value falls back to + // the raw bytes when the value is not base64, so plain-XML callers still work. + let xml = decode_bucket_meta_wire_value(raw); + deserialize::(&xml) .is_ok_and(|config| config.rules.len() == expected_rules && config.rules.iter().all(site_replication_rule_complete)) }); @@ -11003,6 +11009,45 @@ mod tests { ); } + // Status miscount regression: build_sr_info stores replication_config as base64-encoded XML + // (the wire form). site_replication_config_mismatch must decode it before XML-parsing; before + // the fix it parsed the base64 text directly, always failed, and reported every replicated + // bucket as out-of-sync ("0/N Buckets in sync"). This test feeds the real base64 wire form. + #[test] + fn test_site_replication_config_mismatch_accepts_base64_wire_form() { + let xml = { + let config = ReplicationConfiguration { + role: String::new(), + rules: vec![build_site_replication_rule( + "arn:rustfs:replication::dep-b:bucket", + 1, + "site-repl-dep-b", + )], + }; + String::from_utf8(serialize(&config).unwrap()).unwrap() + }; + let b64 = BASE64_STANDARD.encode(xml.as_bytes()); + + // Both sites present the complete config in base64 wire form → NOT a mismatch. + assert_eq!( + site_replication_config_mismatch(vec![Some(&b64), Some(&b64)].into_iter(), 2), + (2, false), + "base64-encoded complete configs on both sites must not be reported as a mismatch" + ); + // The tolerant decode keeps plain-XML callers working too. + assert_eq!( + site_replication_config_mismatch(vec![Some(&xml), Some(&xml)].into_iter(), 2), + (2, false), + "raw-XML wire form still parses via the base64 fallback" + ); + // A base64 config present on only one of two sites is still a mismatch. + assert_eq!( + site_replication_config_mismatch(vec![Some(&b64)].into_iter(), 2), + (1, true), + "config present on only one site is a mismatch regardless of encoding" + ); + } + // BUG1: peers persisted on add/join must carry a real sync_state (Enable), not Unknown, // so `mc admin replicate info` and the console show the correct state for healthy peers. #[test]