mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-11 23:56:53 +00:00
3dabac4a09
* perf(observability): avoid cgroup path allocation Co-Authored-By: heihutu <heihutu@gmail.com> * test(e2e): satisfy regression test clippy Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
203 lines
7.1 KiB
Rust
203 lines
7.1 KiB
Rust
// Copyright 2024 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/licenses/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.
|
|
|
|
//! Regression tests for distributed cluster startup and quorum.
|
|
//!
|
|
//! Covers the recurring pattern where multi-node clusters fail to start due to
|
|
//! lock quorum issues, DNS resolution delays, or erasure quorum deadlocks.
|
|
//! This has regressed 7+ times.
|
|
//!
|
|
//! ## Regression Issues
|
|
//!
|
|
//! - rustfs#5416: RustFS cannot cold-start with 2/3 quorum when Pod DNS missing
|
|
//! - rustfs#2945: Distributed mode fails on K8s: erasure quorum deadlock
|
|
//! - rustfs#2794: distributed deployment does not become ready
|
|
//! - rustfs#2601: fresh pod immediately enters FaultyDisk state
|
|
//! - rustfs#4040: Distributed startup can fail lock quorum before AppContext initializes
|
|
//! - rustfs#5655: fix(ecstore): bootstrap fresh four-node clusters reliably
|
|
//! - rustfs#4954: S3/health endpoint unavailability after multi-pool scale-up
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::common::{RustFSTestClusterEnvironment, init_logging};
|
|
use aws_sdk_s3::primitives::ByteStream;
|
|
use serial_test::serial;
|
|
use std::error::Error;
|
|
use tokio::time::{Duration, sleep};
|
|
use tracing::info;
|
|
|
|
type TestResult = Result<(), Box<dyn Error + Send + Sync>>;
|
|
|
|
/// RT-10: Verify 4-node cluster starts successfully and all nodes are ready.
|
|
///
|
|
/// Regression pattern: distributed startup fails with quorum deadlock or
|
|
/// lock acquisition timeout (rustfs#2945, rustfs#5655).
|
|
///
|
|
/// Steps:
|
|
/// 1. Create a 4-node cluster
|
|
/// 2. Start all nodes simultaneously
|
|
/// 3. Verify all nodes report healthy
|
|
/// 4. Verify S3 operations work through any node
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_four_node_cluster_startup_and_health() -> TestResult {
|
|
init_logging();
|
|
info!("RT-10: 4-node cluster startup and health");
|
|
|
|
let mut cluster = RustFSTestClusterEnvironment::new(4).await.expect("create 4-node cluster");
|
|
|
|
cluster.start().await.expect("start 4-node cluster");
|
|
|
|
// Create a bucket and verify it's accessible from all nodes
|
|
cluster
|
|
.create_test_bucket("rt10-startup")
|
|
.await
|
|
.expect("create bucket on cluster");
|
|
|
|
let clients = cluster.create_all_clients().expect("create per-node clients");
|
|
|
|
// Verify S3 operations work from every node
|
|
for (i, client) in clients.iter().enumerate() {
|
|
client
|
|
.put_object()
|
|
.bucket("rt10-startup")
|
|
.key(format!("from-node-{i}.txt"))
|
|
.body(ByteStream::from_static(b"hello from node"))
|
|
.send()
|
|
.await
|
|
.unwrap_or_else(|e| panic!("PUT from node {i} failed: {e}"));
|
|
}
|
|
|
|
// Verify all objects are visible from node 0
|
|
let list = clients[0]
|
|
.list_objects_v2()
|
|
.bucket("rt10-startup")
|
|
.send()
|
|
.await
|
|
.expect("list objects from node 0");
|
|
|
|
assert_eq!(
|
|
list.contents().len(),
|
|
4,
|
|
"RT-10 FAIL: expected 4 objects (one per node), found {}",
|
|
list.contents().len()
|
|
);
|
|
|
|
info!("RT-10 PASS: 4-node cluster starts and serves S3 from all nodes");
|
|
Ok(())
|
|
}
|
|
|
|
/// RT-10b: Verify cluster handles node restart gracefully.
|
|
///
|
|
/// Regression pattern: after a node restart, it cannot rejoin the cluster
|
|
/// or enters a faulty state (rustfs#2601).
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_cluster_survives_node_restart() -> TestResult {
|
|
init_logging();
|
|
info!("RT-10b: cluster survives node restart");
|
|
|
|
let mut cluster = RustFSTestClusterEnvironment::new(4).await.expect("create 4-node cluster");
|
|
|
|
cluster.start().await.expect("start cluster");
|
|
|
|
cluster.create_test_bucket("rt10b-restart").await.expect("create bucket");
|
|
|
|
// Write data
|
|
let clients = cluster.create_all_clients()?;
|
|
clients[0]
|
|
.put_object()
|
|
.bucket("rt10b-restart")
|
|
.key("before-restart.txt")
|
|
.body(ByteStream::from_static(b"persistent data"))
|
|
.send()
|
|
.await
|
|
.expect("put object before restart");
|
|
|
|
// Stop node 3
|
|
cluster.stop_node(3).expect("stop node 3");
|
|
sleep(Duration::from_secs(2)).await;
|
|
|
|
// Verify cluster still works with 3/4 nodes (quorum)
|
|
clients[0]
|
|
.put_object()
|
|
.bucket("rt10b-restart")
|
|
.key("during-offline.txt")
|
|
.body(ByteStream::from_static(b"written while node 3 down"))
|
|
.send()
|
|
.await
|
|
.expect("PUT should succeed with 3/4 nodes");
|
|
|
|
// Restart node 3
|
|
cluster.start_node(3).await.expect("restart node 3");
|
|
|
|
// Wait for node to rejoin
|
|
sleep(Duration::from_secs(3)).await;
|
|
|
|
// Verify the restarted node can serve reads
|
|
let list = clients[3]
|
|
.list_objects_v2()
|
|
.bucket("rt10b-restart")
|
|
.send()
|
|
.await
|
|
.expect("list from restarted node");
|
|
|
|
assert!(
|
|
list.contents().len() >= 2,
|
|
"RT-10b FAIL: restarted node sees {} objects, expected >= 2",
|
|
list.contents().len()
|
|
);
|
|
|
|
info!("RT-10b PASS: cluster survives and recovers from node restart");
|
|
Ok(())
|
|
}
|
|
|
|
/// RT-10c: Verify bucket creation persists across all nodes.
|
|
///
|
|
/// Regression pattern: bucket metadata is not replicated to all nodes,
|
|
/// causing NoSuchBucket errors on some nodes (rustfs#3191).
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_bucket_visible_from_all_nodes() -> TestResult {
|
|
init_logging();
|
|
info!("RT-10c: bucket visible from all nodes");
|
|
|
|
let mut cluster = RustFSTestClusterEnvironment::new(4).await.expect("create 4-node cluster");
|
|
|
|
cluster.start().await.expect("start cluster");
|
|
|
|
cluster
|
|
.create_test_bucket("rt10c-bucket-visibility")
|
|
.await
|
|
.expect("create bucket");
|
|
|
|
let clients = cluster.create_all_clients()?;
|
|
|
|
// Verify the bucket is visible from every node
|
|
for (i, client) in clients.iter().enumerate() {
|
|
let resp = client
|
|
.list_objects_v2()
|
|
.bucket("rt10c-bucket-visibility")
|
|
.send()
|
|
.await
|
|
.unwrap_or_else(|e| panic!("list from node {i} failed (NoSuchBucket?): {e}"));
|
|
|
|
assert!(resp.contents().is_empty(), "RT-10c: fresh bucket should be empty on node {i}");
|
|
}
|
|
|
|
info!("RT-10c PASS: bucket visible from all 4 nodes");
|
|
Ok(())
|
|
}
|
|
}
|