mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-06 12:09:12 +00:00
07833379b4
* test(e2e): add distributed 4x4 validation * test(e2e): prove operations overlap data movement --------- Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
150 lines
5.0 KiB
Rust
150 lines
5.0 KiB
Rust
// Copyright 2026 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use super::harness::{
|
|
DistCluster, DistLayout, TestResult, assert_object_bytes, get_object_bytes, put_object, unique_bucket, wait_until,
|
|
};
|
|
use crate::common::init_logging;
|
|
use aws_sdk_s3::primitives::ByteStream;
|
|
use aws_sdk_s3::types::{CompletedMultipartUpload, CompletedPart};
|
|
use std::time::Duration;
|
|
|
|
#[tokio::test]
|
|
async fn four_node_four_drive_multipart_and_cross_node_listing_agree() -> TestResult {
|
|
init_logging();
|
|
let dist = DistCluster::start(DistLayout::FourByFour).await?;
|
|
let bucket = unique_bucket("extra");
|
|
dist.create_bucket(&bucket).await?;
|
|
let client = dist.client(0)?;
|
|
|
|
let key = "multipart.bin";
|
|
let part1 = vec![0x41u8; 5 * 1024 * 1024];
|
|
let part2 = vec![0x42u8; 5 * 1024 * 1024];
|
|
let upload = client.create_multipart_upload().bucket(&bucket).key(key).send().await?;
|
|
let upload_id = upload.upload_id().ok_or("missing upload id")?.to_string();
|
|
|
|
let uploaded1 = client
|
|
.upload_part()
|
|
.bucket(&bucket)
|
|
.key(key)
|
|
.upload_id(&upload_id)
|
|
.part_number(1)
|
|
.body(ByteStream::from(part1.clone()))
|
|
.send()
|
|
.await?;
|
|
let uploaded2 = client
|
|
.upload_part()
|
|
.bucket(&bucket)
|
|
.key(key)
|
|
.upload_id(&upload_id)
|
|
.part_number(2)
|
|
.body(ByteStream::from(part2.clone()))
|
|
.send()
|
|
.await?;
|
|
|
|
client
|
|
.complete_multipart_upload()
|
|
.bucket(&bucket)
|
|
.key(key)
|
|
.upload_id(&upload_id)
|
|
.multipart_upload(
|
|
CompletedMultipartUpload::builder()
|
|
.parts(
|
|
CompletedPart::builder()
|
|
.part_number(1)
|
|
.e_tag(uploaded1.e_tag().unwrap_or_default())
|
|
.build(),
|
|
)
|
|
.parts(
|
|
CompletedPart::builder()
|
|
.part_number(2)
|
|
.e_tag(uploaded2.e_tag().unwrap_or_default())
|
|
.build(),
|
|
)
|
|
.build(),
|
|
)
|
|
.send()
|
|
.await?;
|
|
|
|
let mut expected = part1;
|
|
expected.extend_from_slice(&part2);
|
|
for node_idx in 0..dist.cluster.nodes.len() {
|
|
assert_object_bytes(&dist.client(node_idx)?, &bucket, key, &expected).await?;
|
|
}
|
|
|
|
put_object(&client, &bucket, "list/a", b"a".to_vec()).await?;
|
|
put_object(&dist.client(2)?, &bucket, "list/b", b"b".to_vec()).await?;
|
|
let mut seen = Vec::new();
|
|
for node_idx in 0..dist.cluster.nodes.len() {
|
|
let listed = dist
|
|
.client(node_idx)?
|
|
.list_objects_v2()
|
|
.bucket(&bucket)
|
|
.prefix("list/")
|
|
.send()
|
|
.await?;
|
|
let keys: Vec<String> = listed
|
|
.contents()
|
|
.iter()
|
|
.filter_map(|object| object.key().map(str::to_string))
|
|
.collect();
|
|
seen.push(keys);
|
|
}
|
|
for keys in &seen[1..] {
|
|
assert_eq!(&seen[0], keys, "list results diverged across nodes: {seen:?}");
|
|
}
|
|
|
|
let got = get_object_bytes(&dist.client(3)?, &bucket, "list/a").await?;
|
|
assert_eq!(got, b"a");
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn four_node_list_buckets_agree_across_all_nodes() -> TestResult {
|
|
init_logging();
|
|
let dist = DistCluster::start(DistLayout::FourByFour).await?;
|
|
let bucket = unique_bucket("listed");
|
|
dist.create_bucket(&bucket).await?;
|
|
put_object(&dist.client(0)?, &bucket, "seed.bin", b"seed".to_vec()).await?;
|
|
|
|
for node_idx in 0..dist.cluster.nodes.len() {
|
|
let client = dist.client(node_idx)?;
|
|
let name = bucket.clone();
|
|
wait_until(
|
|
Duration::from_secs(20),
|
|
|| {
|
|
let client = client.clone();
|
|
let name = name.clone();
|
|
async move {
|
|
let listed = client.list_buckets().send().await?;
|
|
Ok(listed.buckets().iter().any(|entry| entry.name() == Some(name.as_str())))
|
|
}
|
|
},
|
|
&format!("node {node_idx} lists {bucket}"),
|
|
)
|
|
.await?;
|
|
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(())
|
|
}
|