feat(rpc): advertise cross-pool fence capability (#5809)

* feat(rpc): advertise cross-pool fence capability

* refactor(rpc): narrow fence capability surface

* fix(rpc): state fence compatibility removal condition
This commit is contained in:
cxymds
2026-08-08 05:43:26 +08:00
committed by GitHub
parent ce7ca4cbb8
commit 8201a74f7f
3 changed files with 160 additions and 8 deletions
+44 -5
View File
@@ -172,6 +172,7 @@ pub const HEAL_CONTROL_PROTOCOL_VERSION: u32 = 3;
pub const DYNAMIC_CONFIG_PROTOCOL_VERSION: u32 = 1;
pub const HEAL_CONTROL_CAPABILITY_PROBE_PREFIX: &[u8] = b"rustfs-heal-control-capability-v3\0";
pub const REMOTE_VERSION_STATE_CAPABILITY_PROBE_PREFIX: &[u8] = b"rustfs-tier-remote-version-state-capability-v1\0";
pub const CROSS_POOL_FENCE_CAPABILITY_PROBE_PREFIX: &[u8] = b"rustfs-cross-pool-fence-capability-v1\0";
pub const TIER_MUTATION_RPC_MAX_PREPARE_PAYLOAD_SIZE: usize = 64 * 1024;
pub const TIER_MUTATION_RPC_MAX_COMMIT_PAYLOAD_SIZE: usize = 1024;
pub const TIER_MUTATION_RPC_MAX_MESSAGE_SIZE: usize = TIER_MUTATION_RPC_MAX_PREPARE_PAYLOAD_SIZE + 4096;
@@ -210,6 +211,11 @@ pub fn is_remote_version_state_capability_probe(command: &[u8]) -> bool {
&& command.starts_with(REMOTE_VERSION_STATE_CAPABILITY_PROBE_PREFIX)
}
pub fn is_cross_pool_fence_capability_probe(command: &[u8]) -> bool {
command.len() == CROSS_POOL_FENCE_CAPABILITY_PROBE_PREFIX.len() + 16
&& command.starts_with(CROSS_POOL_FENCE_CAPABILITY_PROBE_PREFIX)
}
pub fn encode_remote_version_state_capability(
topology_member: &str,
process_epoch: &[u8; 16],
@@ -241,6 +247,18 @@ pub fn decode_remote_version_state_capability(result: &[u8]) -> Result<(&str, &[
Ok((topology_member, process_epoch))
}
pub fn encode_cross_pool_fence_capability(
supported_version: u32,
topology_member: &str,
process_epoch: &[u8; 16],
) -> Result<Vec<u8>, std::num::TryFromIntError> {
let identity = encode_remote_version_state_capability(topology_member, process_epoch)?;
let mut result = Vec::with_capacity(4 + identity.len());
result.extend_from_slice(&supported_version.to_be_bytes());
result.extend_from_slice(&identity);
Ok(result)
}
/// Builds the stable byte representation authenticated for a heal-control request.
///
/// This deliberately does not reuse protobuf encoding: mixed-version peers may
@@ -1700,11 +1718,12 @@ mod scanner_activity_tests {
#[cfg(test)]
mod heal_control_tests {
use super::{
HEAL_CONTROL_CAPABILITY_PROBE_PREFIX, HEAL_CONTROL_PROTOCOL_VERSION, REMOTE_VERSION_STATE_CAPABILITY_PROBE_PREFIX,
canonical_heal_control_capability_ack, canonical_heal_control_request_body, canonical_heal_control_response_body,
decode_remote_version_state_capability, encode_remote_version_state_capability, heal_control_capability_probe,
heal_control_coordinator_epoch, heal_control_execution_timeout, heal_control_execution_timeout_for,
internode_rpc_timeout, is_heal_control_capability_probe, is_remote_version_state_capability_probe,
CROSS_POOL_FENCE_CAPABILITY_PROBE_PREFIX, HEAL_CONTROL_CAPABILITY_PROBE_PREFIX, HEAL_CONTROL_PROTOCOL_VERSION,
REMOTE_VERSION_STATE_CAPABILITY_PROBE_PREFIX, canonical_heal_control_capability_ack, canonical_heal_control_request_body,
canonical_heal_control_response_body, decode_remote_version_state_capability, encode_cross_pool_fence_capability,
encode_remote_version_state_capability, heal_control_capability_probe, heal_control_coordinator_epoch,
heal_control_execution_timeout, heal_control_execution_timeout_for, internode_rpc_timeout,
is_cross_pool_fence_capability_probe, is_heal_control_capability_probe, is_remote_version_state_capability_probe,
normalize_internode_rpc_timeout, remote_version_state_capability_probe,
};
use crate::heal_control;
@@ -1785,6 +1804,26 @@ mod heal_control_tests {
assert!(decode_remote_version_state_capability(&invalid_utf8).is_err());
}
#[test]
fn cross_pool_fence_capability_binds_version_member_and_epoch() {
assert_eq!(CROSS_POOL_FENCE_CAPABILITY_PROBE_PREFIX, b"rustfs-cross-pool-fence-capability-v1\0");
let mut probe = b"rustfs-cross-pool-fence-capability-v1\0".to_vec();
probe.extend_from_slice(&[7; 16]);
assert!(is_cross_pool_fence_capability_probe(&probe));
assert!(!is_cross_pool_fence_capability_probe(CROSS_POOL_FENCE_CAPABILITY_PROBE_PREFIX));
let mut wrong_prefix = probe.clone();
wrong_prefix[0] ^= 1;
assert!(!is_cross_pool_fence_capability_probe(&wrong_prefix));
let encoded = encode_cross_pool_fence_capability(0x0102_0304, "node-a:9000", &[7; 16])
.expect("small capability response should encode");
let mut expected_response = vec![1, 2, 3, 4];
expected_response.extend_from_slice(&11_u64.to_be_bytes());
expected_response.extend_from_slice(b"node-a:9000");
expected_response.extend_from_slice(&[7; 16]);
assert_eq!(encoded, expected_response);
}
#[test]
fn canonical_response_binds_request_and_result() {
let baseline = canonical_heal_control_response_body(2, "abcdef", b"query", b"result").unwrap();