mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-20 19:42:17 +00:00
fix(ecstore): invalidate wiped disk id cache (#3251)
This commit is contained in:
@@ -60,6 +60,125 @@ mod tests {
|
||||
assert_eq!(body.as_ref(), expected, "object body changed for {key}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_auto_heal_rebuilds_runtime_wiped_disk_without_restart() {
|
||||
init_logging();
|
||||
info!("Issue #1533: auto heal should rebuild a runtime-wiped disk in a 4-disk single-node erasure set without restart");
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await.expect("Failed to create test environment");
|
||||
let root = PathBuf::from(env.temp_dir.clone());
|
||||
let disk0 = root.join("disk0");
|
||||
let disk1 = root.join("disk1");
|
||||
let disk2 = root.join("disk2");
|
||||
let disk3 = root.join("disk3");
|
||||
for disk in [&disk0, &disk1, &disk2, &disk3] {
|
||||
std::fs::create_dir_all(disk).expect("disk directory should be created");
|
||||
}
|
||||
|
||||
env.temp_dir = disk3.to_string_lossy().to_string();
|
||||
let disk0_arg = disk0.to_string_lossy().to_string();
|
||||
let disk1_arg = disk1.to_string_lossy().to_string();
|
||||
let disk2_arg = disk2.to_string_lossy().to_string();
|
||||
env.start_rustfs_server_with_env(
|
||||
vec![disk0_arg.as_str(), disk1_arg.as_str(), disk2_arg.as_str()],
|
||||
&[
|
||||
("RUSTFS_UNSAFE_BYPASS_DISK_CHECK", "true"),
|
||||
("RUSTFS_HEAL_INTERVAL_SECS", "10"),
|
||||
],
|
||||
)
|
||||
.await
|
||||
.expect("Failed to start 4-disk RustFS");
|
||||
env.temp_dir = root.to_string_lossy().to_string();
|
||||
|
||||
let client = env.create_s3_client();
|
||||
let bucket = "heal-runtime-wiped-disk";
|
||||
let heal_timeout_secs = std::env::var("RUSTFS_AUTO_HEAL_RUNTIME_WIPE_TIMEOUT_SECS")
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
.unwrap_or(45);
|
||||
|
||||
let objects: Vec<(String, Vec<u8>, &'static str)> = vec![
|
||||
(
|
||||
"runtime/alpha.txt".to_string(),
|
||||
b"alpha payload for runtime wipe heal".to_vec(),
|
||||
"text/plain; charset=utf-8",
|
||||
),
|
||||
("runtime/beta.bin".to_string(), (0..=127).collect::<Vec<u8>>(), "application/octet-stream"),
|
||||
(
|
||||
"runtime/dir/emoji-free-name.json".to_string(),
|
||||
br#"{"status":"runtime-heal"}"#.to_vec(),
|
||||
"application/json",
|
||||
),
|
||||
(
|
||||
"runtime/dir/gamma.txt".to_string(),
|
||||
b"gamma payload for runtime wipe heal".to_vec(),
|
||||
"text/plain; charset=utf-8",
|
||||
),
|
||||
];
|
||||
|
||||
client
|
||||
.create_bucket()
|
||||
.bucket(bucket)
|
||||
.send()
|
||||
.await
|
||||
.expect("bucket create should succeed");
|
||||
for (key, body, content_type) in &objects {
|
||||
client
|
||||
.put_object()
|
||||
.bucket(bucket)
|
||||
.key(key)
|
||||
.content_type(*content_type)
|
||||
.body(ByteStream::from(body.clone()))
|
||||
.send()
|
||||
.await
|
||||
.expect("PUT should succeed");
|
||||
}
|
||||
|
||||
for (key, body, _) in &objects {
|
||||
assert_object_body(&env, bucket, key, body).await;
|
||||
assert!(
|
||||
object_metadata_exists_on_disk(&disk0, bucket, key),
|
||||
"disk0 should contain xl.meta before runtime wipe for {key}"
|
||||
);
|
||||
}
|
||||
|
||||
std::fs::remove_dir_all(&disk0).expect("disk0 wipe should succeed while server is running");
|
||||
std::fs::create_dir_all(&disk0).expect("disk0 should be recreated empty while server is running");
|
||||
assert!(!has_file_under(&disk0), "disk0 must be empty immediately after runtime wipe");
|
||||
|
||||
let mut remaining_rebuild_keys: HashSet<String> = objects.iter().map(|(key, _, _)| key.clone()).collect();
|
||||
for _ in 0..heal_timeout_secs {
|
||||
for (key, body, _) in &objects {
|
||||
assert_object_body(&env, bucket, key, body).await;
|
||||
}
|
||||
|
||||
if !remaining_rebuild_keys.is_empty() {
|
||||
let mut rebuilt = Vec::new();
|
||||
for key in &remaining_rebuild_keys {
|
||||
if object_metadata_exists_on_disk(&disk0, bucket, key) {
|
||||
rebuilt.push(key.clone());
|
||||
}
|
||||
}
|
||||
for key in rebuilt {
|
||||
let _ = remaining_rebuild_keys.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
if remaining_rebuild_keys.is_empty() {
|
||||
assert!(
|
||||
disk0.join(".rustfs.sys").join("format.json").is_file(),
|
||||
"runtime-wiped disk should have format.json restored by auto heal"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
panic!("auto heal did not rebuild all files on the runtime-wiped disk within timeout");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_admin_deep_heal_rebuilds_cleared_disk_in_single_node_erasure_set() {
|
||||
|
||||
Reference in New Issue
Block a user