mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 08:49:26 +00:00
test(tier): cover zero reference proof matrix (#5100)
Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -6063,6 +6063,7 @@ mod tests {
|
|||||||
state: tokio::sync::Mutex<Option<(Vec<u8>, String)>>,
|
state: tokio::sync::Mutex<Option<(Vec<u8>, String)>>,
|
||||||
next_etag: AtomicUsize,
|
next_etag: AtomicUsize,
|
||||||
fail_put: AtomicBool,
|
fail_put: AtomicBool,
|
||||||
|
truncate_reference_page_without_marker: AtomicBool,
|
||||||
lock_manager: Arc<rustfs_lock::GlobalLockManager>,
|
lock_manager: Arc<rustfs_lock::GlobalLockManager>,
|
||||||
lock_requests: Mutex<Vec<(String, String)>>,
|
lock_requests: Mutex<Vec<(String, String)>>,
|
||||||
listed_versions: Mutex<Vec<ObjectInfo>>,
|
listed_versions: Mutex<Vec<ObjectInfo>>,
|
||||||
@@ -6074,6 +6075,7 @@ mod tests {
|
|||||||
state: tokio::sync::Mutex::new(None),
|
state: tokio::sync::Mutex::new(None),
|
||||||
next_etag: AtomicUsize::new(0),
|
next_etag: AtomicUsize::new(0),
|
||||||
fail_put: AtomicBool::new(false),
|
fail_put: AtomicBool::new(false),
|
||||||
|
truncate_reference_page_without_marker: AtomicBool::new(false),
|
||||||
lock_manager: Arc::new(rustfs_lock::GlobalLockManager::new()),
|
lock_manager: Arc::new(rustfs_lock::GlobalLockManager::new()),
|
||||||
lock_requests: Mutex::new(Vec::new()),
|
lock_requests: Mutex::new(Vec::new()),
|
||||||
listed_versions: Mutex::new(Vec::new()),
|
listed_versions: Mutex::new(Vec::new()),
|
||||||
@@ -6088,6 +6090,10 @@ mod tests {
|
|||||||
.expect("tier reference fixture should not poison")
|
.expect("tier reference fixture should not poison")
|
||||||
.push(object);
|
.push(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn omit_truncated_reference_marker(&self) {
|
||||||
|
self.truncate_reference_page_without_marker.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -6272,7 +6278,7 @@ mod tests {
|
|||||||
if is_truncated {
|
if is_truncated {
|
||||||
objects.truncate(limit);
|
objects.truncate(limit);
|
||||||
}
|
}
|
||||||
let (next_marker, next_version_idmarker) = if is_truncated {
|
let (mut next_marker, next_version_idmarker) = if is_truncated {
|
||||||
objects
|
objects
|
||||||
.last()
|
.last()
|
||||||
.map(|object| (Some(object.name.clone()), object.version_id.map(|version| version.to_string())))
|
.map(|object| (Some(object.name.clone()), object.version_id.map(|version| version.to_string())))
|
||||||
@@ -6280,6 +6286,9 @@ mod tests {
|
|||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
if is_truncated && self.truncate_reference_page_without_marker.load(Ordering::SeqCst) {
|
||||||
|
next_marker = None;
|
||||||
|
}
|
||||||
Ok(StorageListObjectVersionsInfo {
|
Ok(StorageListObjectVersionsInfo {
|
||||||
is_truncated,
|
is_truncated,
|
||||||
next_marker,
|
next_marker,
|
||||||
@@ -6447,6 +6456,118 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn zero_reference_proof_blocks_clear_before_config_save() {
|
||||||
|
let store = Arc::new(CasConfigStore::default());
|
||||||
|
let tier_a = build_rustfs_tier("COLD-A");
|
||||||
|
let tier_b = build_rustfs_tier("COLD-B");
|
||||||
|
let identity_b = tier_backend_identity(&tier_b).expect("test tier identity should encode");
|
||||||
|
let mut persisted = empty_mgr();
|
||||||
|
persisted.tiers.insert("COLD-A".to_string(), tier_a.clone_with_credentials());
|
||||||
|
persisted.tiers.insert("COLD-B".to_string(), tier_b.clone_with_credentials());
|
||||||
|
persisted
|
||||||
|
.save_tiering_config_if_current(store.clone(), None)
|
||||||
|
.await
|
||||||
|
.expect("reference proof fixture should persist");
|
||||||
|
store.add_listed_version(transitioned_tier_object("photos", "2026/b.jpg", "COLD-B", Some(identity_b)));
|
||||||
|
|
||||||
|
let manager = TierConfigMgr::new();
|
||||||
|
{
|
||||||
|
let mut guard = manager.write().await;
|
||||||
|
guard.tiers.insert("COLD-A".to_string(), tier_a);
|
||||||
|
guard.tiers.insert("COLD-B".to_string(), tier_b);
|
||||||
|
}
|
||||||
|
let err = TierConfigMgr::clear_and_save_with(&manager, store.clone(), true)
|
||||||
|
.await
|
||||||
|
.expect_err("authoritative object reference must block tier clear");
|
||||||
|
|
||||||
|
match err {
|
||||||
|
TierConfigUpdateError::Publish(err) => {
|
||||||
|
assert_eq!(err.code, ERR_TIER_BACKEND_IN_USE.code);
|
||||||
|
assert!(err.message.contains("photos/2026/b.jpg"), "{}", err.message);
|
||||||
|
}
|
||||||
|
other => panic!("reference proof failures must be surfaced as publish errors: {other:?}"),
|
||||||
|
}
|
||||||
|
let guard = manager.read().await;
|
||||||
|
assert!(guard.tiers.contains_key("COLD-A"));
|
||||||
|
assert!(guard.tiers.contains_key("COLD-B"));
|
||||||
|
drop(guard);
|
||||||
|
let reloaded = load_tier_config_for_update(store)
|
||||||
|
.await
|
||||||
|
.expect("blocked config should still reload")
|
||||||
|
.0;
|
||||||
|
assert!(reloaded.tiers.contains_key("COLD-A"));
|
||||||
|
assert!(reloaded.tiers.contains_key("COLD-B"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn zero_reference_proof_rebind_allows_only_new_destination_references() {
|
||||||
|
let current = build_rustfs_tier("COLD-A");
|
||||||
|
let current_identity = tier_backend_identity(¤t).expect("current identity should encode");
|
||||||
|
let mut replacement = build_rustfs_tier("COLD-A");
|
||||||
|
replacement.rustfs.as_mut().expect("replacement payload should exist").prefix = "new-prefix".to_string();
|
||||||
|
let replacement_identity = tier_backend_identity(&replacement).expect("replacement identity should encode");
|
||||||
|
assert_ne!(current_identity, replacement_identity);
|
||||||
|
let target = TierMutationIntentTarget {
|
||||||
|
tier_name: "COLD-A".to_string(),
|
||||||
|
old_backend_identity: Some(current_identity),
|
||||||
|
new_backend_identity: Some(replacement_identity),
|
||||||
|
};
|
||||||
|
|
||||||
|
let store = Arc::new(CasConfigStore::default());
|
||||||
|
store.add_listed_version(transitioned_tier_object(
|
||||||
|
"photos",
|
||||||
|
"2026/new-destination.jpg",
|
||||||
|
"COLD-A",
|
||||||
|
Some(replacement_identity),
|
||||||
|
));
|
||||||
|
ensure_no_authoritative_tier_object_references(store.clone(), std::slice::from_ref(&target))
|
||||||
|
.await
|
||||||
|
.expect("references already written with the new destination identity should not block rebind");
|
||||||
|
|
||||||
|
store.add_listed_version(transitioned_tier_object(
|
||||||
|
"photos",
|
||||||
|
"2026/old-destination.jpg",
|
||||||
|
"COLD-A",
|
||||||
|
Some(current_identity),
|
||||||
|
));
|
||||||
|
let err = ensure_no_authoritative_tier_object_references(store, &[target])
|
||||||
|
.await
|
||||||
|
.expect_err("references to the old destination identity must block rebind");
|
||||||
|
assert_eq!(err.code, ERR_TIER_BACKEND_IN_USE.code);
|
||||||
|
assert!(err.message.contains("photos/2026/old-destination.jpg"), "{}", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn zero_reference_proof_fails_closed_when_version_listing_marker_is_missing() {
|
||||||
|
let current = build_rustfs_tier("COLD-A");
|
||||||
|
let current_identity = tier_backend_identity(¤t).expect("current identity should encode");
|
||||||
|
let target = TierMutationIntentTarget {
|
||||||
|
tier_name: "COLD-A".to_string(),
|
||||||
|
old_backend_identity: Some(current_identity),
|
||||||
|
new_backend_identity: None,
|
||||||
|
};
|
||||||
|
let store = Arc::new(CasConfigStore::default());
|
||||||
|
for index in 0..=TIER_REFERENCE_PROOF_LIST_LIMIT {
|
||||||
|
store.add_listed_version(ObjectInfo {
|
||||||
|
bucket: "photos".to_string(),
|
||||||
|
name: format!("safe/{index:04}.jpg"),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
store.omit_truncated_reference_marker();
|
||||||
|
|
||||||
|
let err = ensure_no_authoritative_tier_object_references(store, &[target])
|
||||||
|
.await
|
||||||
|
.expect_err("truncated authoritative reference scan without a marker must fail closed");
|
||||||
|
assert_eq!(err.code, ERR_TIER_INVALID_CONFIG.code);
|
||||||
|
assert!(
|
||||||
|
err.message.contains("truncated without a next marker"),
|
||||||
|
"unexpected reference proof error: {}",
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_reference_proof_rebind_identity_boundaries_fail_closed() {
|
fn zero_reference_proof_rebind_identity_boundaries_fail_closed() {
|
||||||
let tier = build_rustfs_tier("COLD-A");
|
let tier = build_rustfs_tier("COLD-A");
|
||||||
|
|||||||
Reference in New Issue
Block a user