mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 08:49:26 +00:00
test(tier): cover mixed-version committed replay (#5119)
Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -5611,6 +5611,80 @@ mod tests {
|
|||||||
assert_eq!(lock_unpoisoned(&calls).as_slice(), &[format!("peer-a:commit:{mutation_id}:etag-new")]);
|
assert_eq!(lock_unpoisoned(&calls).as_slice(), &[format!("peer-a:commit:{mutation_id}:etag-new")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn committed_replay_mixed_version_peer_matrix_fails_before_local_publish() {
|
||||||
|
for (case, peer_error, expected_error) in [
|
||||||
|
(
|
||||||
|
"old_unimplemented",
|
||||||
|
"peer tier mutation commit RPC failed: status: Unimplemented, message: \"old peer has no tier mutation control service\"",
|
||||||
|
"old peer",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"deadline_exceeded",
|
||||||
|
"peer tier mutation commit RPC failed: status: DeadlineExceeded, message: \"peer tier mutation control timed out\"",
|
||||||
|
"timed out",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"unavailable",
|
||||||
|
"peer tier mutation commit RPC failed: status: Unavailable, message: \"peer tier mutation control unavailable\"",
|
||||||
|
"unavailable",
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let store = Arc::new(CasConfigStore::default());
|
||||||
|
let mut persisted = empty_mgr();
|
||||||
|
persisted.tiers.insert("COLD-A".to_string(), build_rustfs_tier("COLD-A"));
|
||||||
|
persisted
|
||||||
|
.save_tiering_config_if_current(store.clone(), None)
|
||||||
|
.await
|
||||||
|
.expect("tier config fixture should persist");
|
||||||
|
|
||||||
|
let mutation_id = uuid::Uuid::from_u128(match case {
|
||||||
|
"old_unimplemented" => 31,
|
||||||
|
"deadline_exceeded" => 32,
|
||||||
|
"unavailable" => 33,
|
||||||
|
_ => unreachable!("matrix cases are enumerated above"),
|
||||||
|
});
|
||||||
|
let intent = committed_remove_intent("COLD-A", mutation_id, "etag-new");
|
||||||
|
crate::services::tier::tier_mutation_intent::save_tier_mutation_intent_record(store.clone(), &intent)
|
||||||
|
.await
|
||||||
|
.expect("committed intent fixture should persist");
|
||||||
|
let writes_after_fixture = store.next_etag.load(Ordering::SeqCst);
|
||||||
|
let calls = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let handle = TierConfigMgr::new();
|
||||||
|
|
||||||
|
let err = TIER_MUTATION_TEST_COMMIT_PEERS
|
||||||
|
.scope(vec![FakeTierMutationCommitPeer::boxed("peer-a", calls.clone(), Err(peer_error))], async {
|
||||||
|
TierConfigMgr::reload_handle_with(&handle, store.clone()).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect_err("mixed-version peer failure must abort committed replay");
|
||||||
|
|
||||||
|
assert!(err.to_string().contains(expected_error), "{case} returned {err}");
|
||||||
|
assert_eq!(lock_unpoisoned(&calls).as_slice(), &[format!("peer-a:commit:{mutation_id}:etag-new")]);
|
||||||
|
assert!(
|
||||||
|
!handle.read().await.tiers.contains_key("COLD-A"),
|
||||||
|
"{case} must not publish the local tier config after peer replay fails"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
store.next_etag.load(Ordering::SeqCst),
|
||||||
|
writes_after_fixture,
|
||||||
|
"{case} must not perform a second tier-config CAS write after replay fails"
|
||||||
|
);
|
||||||
|
let reloaded = TierConfigMgr::load_tier_mutation_intents(store)
|
||||||
|
.await
|
||||||
|
.expect("intent scan should succeed after failed mixed-version replay");
|
||||||
|
assert!(
|
||||||
|
reloaded.iter().any(|intent| intent.mutation_id == mutation_id),
|
||||||
|
"{case} must keep the committed intent for retry"
|
||||||
|
);
|
||||||
|
let blocked = match TierConfigMgr::acquire_operation_lease(&handle, "COLD-A").await {
|
||||||
|
Ok(_) => panic!("{case} must keep committed replay blocking old tier operations"),
|
||||||
|
Err(err) => err,
|
||||||
|
};
|
||||||
|
assert!(blocked.message.contains("being replaced"), "{case} returned {blocked}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn committed_mutation_recovery_requires_every_peer_to_commit() {
|
async fn committed_mutation_recovery_requires_every_peer_to_commit() {
|
||||||
let mutation_id = uuid::Uuid::from_u128(17);
|
let mutation_id = uuid::Uuid::from_u128(17);
|
||||||
|
|||||||
@@ -2318,6 +2318,33 @@ mod tests {
|
|||||||
assert_eq!(error.code(), tonic::Code::InvalidArgument);
|
assert_eq!(error.code(), tonic::Code::InvalidArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tier_mutation_control_rejects_old_protocol_version_before_store_lookup() {
|
||||||
|
let service = make_tier_mutation_control_server_for_context(None);
|
||||||
|
let mutation_id = uuid::Uuid::new_v4();
|
||||||
|
let payload = Bytes::from_static(b"intent");
|
||||||
|
let mut request = Request::new(TierMutationPrepareRequest {
|
||||||
|
version: rustfs_protos::TIER_MUTATION_RPC_PROTOCOL_VERSION - 1,
|
||||||
|
mutation_id: mutation_id.to_string(),
|
||||||
|
canonical_payload: payload,
|
||||||
|
});
|
||||||
|
let body = rustfs_protos::canonical_tier_mutation_rpc_body(
|
||||||
|
request.get_ref().version,
|
||||||
|
rustfs_protos::TierMutationRpcPhase::Prepare,
|
||||||
|
mutation_id,
|
||||||
|
&request.get_ref().canonical_payload,
|
||||||
|
)
|
||||||
|
.expect("old-version request should encode for rejection test");
|
||||||
|
set_tonic_canonical_body_digest(&mut request, &body).expect("digest metadata should encode");
|
||||||
|
mark_v2_authenticated(&mut request);
|
||||||
|
|
||||||
|
let error = service
|
||||||
|
.prepare_tier_mutation(request)
|
||||||
|
.await
|
||||||
|
.expect_err("old tier mutation protocol version must fail closed");
|
||||||
|
assert_eq!(error.code(), tonic::Code::FailedPrecondition);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn tier_mutation_control_rejects_oversized_prepare_before_auth_and_store_lookup() {
|
async fn tier_mutation_control_rejects_oversized_prepare_before_auth_and_store_lookup() {
|
||||||
let service = make_tier_mutation_control_server_for_context(None);
|
let service = make_tier_mutation_control_server_for_context(None);
|
||||||
|
|||||||
Reference in New Issue
Block a user