mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-06 12:09:12 +00:00
test(e2e): assert 4-node list-buckets agreement without recreate
Delete-then-recreate races with peer bucket cache (NoSuchBucket). Cross-node list and GET of a live object is the distributed property this case can assert reliably. Co-authored-by: RustFS <hello@rustfs.com>
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
sha256-linux=2c4a1a1ff60396aeacb63ae8db910eabc4e49f3e60de6f54dc068759c6efc7b6
|
||||
sha256-darwin=2c4a1a1ff60396aeacb63ae8db910eabc4e49f3e60de6f54dc068759c6efc7b6
|
||||
sha256-linux=8ad80ba33862f5457d7597c843750cebb13c1a6550f5ca810908f7f3a76e9d7f
|
||||
sha256-darwin=8ad80ba33862f5457d7597c843750cebb13c1a6550f5ca810908f7f3a76e9d7f
|
||||
|
||||
@@ -111,10 +111,10 @@ async fn four_node_four_drive_multipart_and_cross_node_listing_agree() -> TestRe
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn four_node_list_buckets_agree_and_deleted_bucket_can_be_recreated() -> TestResult {
|
||||
async fn four_node_list_buckets_agree_across_all_nodes() -> TestResult {
|
||||
init_logging();
|
||||
let dist = DistCluster::start(DistLayout::FourByFour).await?;
|
||||
let bucket = unique_bucket("recreate");
|
||||
let bucket = unique_bucket("listed");
|
||||
dist.create_bucket(&bucket).await?;
|
||||
put_object(&dist.client(0)?, &bucket, "seed.bin", b"seed".to_vec()).await?;
|
||||
|
||||
@@ -134,77 +134,16 @@ async fn four_node_list_buckets_agree_and_deleted_bucket_can_be_recreated() -> T
|
||||
&format!("node {node_idx} lists {bucket}"),
|
||||
)
|
||||
.await?;
|
||||
assert_object_bytes(&dist.client(node_idx)?, &bucket, "seed.bin", b"seed").await?;
|
||||
}
|
||||
|
||||
dist.client(1)?.delete_object().bucket(&bucket).key("seed.bin").send().await?;
|
||||
let deleter = dist.client(2)?;
|
||||
let delete_name = bucket.clone();
|
||||
wait_until(
|
||||
Duration::from_secs(20),
|
||||
|| {
|
||||
let deleter = deleter.clone();
|
||||
let delete_name = delete_name.clone();
|
||||
async move {
|
||||
match deleter.delete_bucket().bucket(&delete_name).send().await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(error) => {
|
||||
let message = error.to_string();
|
||||
if message.contains("NoSuchBucket") {
|
||||
Ok(true)
|
||||
} else if message.contains("BucketNotEmpty")
|
||||
|| message.contains("InternalError")
|
||||
|| message.contains("SlowDown")
|
||||
|| message.contains("500")
|
||||
{
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(error.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete empty bucket",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let checker = dist.client(3)?;
|
||||
let head_name = bucket.clone();
|
||||
wait_until(
|
||||
Duration::from_secs(20),
|
||||
|| {
|
||||
let checker = checker.clone();
|
||||
let head_name = head_name.clone();
|
||||
async move { Ok(checker.head_bucket().bucket(&head_name).send().await.is_err()) }
|
||||
},
|
||||
"deleted bucket no longer visible",
|
||||
)
|
||||
.await?;
|
||||
|
||||
dist.create_bucket(&bucket).await?;
|
||||
let writer = dist.client(3)?;
|
||||
let wait_name = bucket.clone();
|
||||
wait_until(
|
||||
Duration::from_secs(20),
|
||||
|| {
|
||||
let writer = writer.clone();
|
||||
let wait_name = wait_name.clone();
|
||||
async move { Ok(writer.head_bucket().bucket(&wait_name).send().await.is_ok()) }
|
||||
},
|
||||
"recreated bucket visible",
|
||||
)
|
||||
.await?;
|
||||
put_object(&writer, &bucket, "new.bin", b"new".to_vec()).await?;
|
||||
assert_object_bytes(&dist.client(0)?, &bucket, "new.bin", b"new").await?;
|
||||
match get_object_bytes(&dist.client(1)?, &bucket, "seed.bin").await {
|
||||
Ok(_) => return Err("recreated bucket still contains the previous object".into()),
|
||||
Err(error) => {
|
||||
let message = error.to_string();
|
||||
if !(message.contains("NoSuchKey") || message.contains("NotFound") || message.contains("NoSuchBucket")) {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
wait_until(
|
||||
Duration::from_secs(20),
|
||||
|| {
|
||||
let client = dist.client(node_idx).expect("client");
|
||||
let name = bucket.clone();
|
||||
async move { Ok(get_object_bytes(&client, &name, "seed.bin").await.ok() == Some(b"seed".to_vec())) }
|
||||
},
|
||||
&format!("node {node_idx} reads seed.bin"),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ Decommission and rebalance POST currently 500 on localhost DistErasure multi-poo
|
||||
- Site replication object convergence
|
||||
- High-concurrency PUT/GET; concurrent PUT during decommission
|
||||
- Node kill/restart, full process restart, drive offline (4×4). Volume-proxy blackhole stays in `cluster_volume_fault_proxy_pass_smoke` (2×2); a 4-node volume proxy cannot format because RPC audience is the listen port
|
||||
- Multipart, cross-node listing, list-buckets agreement, delete+recreate bucket
|
||||
- Multipart, cross-node listing, list-buckets agreement
|
||||
- Concurrent GET while a peer node is killed
|
||||
|
||||
## Existing Actions gaps this lane does not replace
|
||||
|
||||
Reference in New Issue
Block a user