Files
sanctum/tests/live_crash_resilience_test.rs
T

153 lines
5.5 KiB
Rust

use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use bytes::Bytes;
use dav_server::{
davpath::DavPath,
fs::{DavFileSystem, OpenOptions},
};
use rand::RngCore;
use sanctum::{
crypto::{derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, FORMAT_VERSION},
storage::Database,
verify::verify_container,
vfs::SanctumFs,
};
/// Live-Crash- und Stresstest: Simuliert harten Verbindungsabbruch und Power-Cut
/// während intensiver paralleler Schreibvorgänge im VFS.
#[tokio::test]
async fn test_live_crash_and_recovery_stress() {
let temp_dir = std::env::temp_dir();
let container_path: PathBuf = temp_dir.join(format!("sanctum_live_stress_{}.sanctum", std::process::id()));
if container_path.exists() {
let _ = std::fs::remove_file(&container_path);
}
let password = "LiveStressPassword2026!";
let salt = generate_salt();
let kdf_params = KdfParams {
memory_cost: 1024,
time_cost: 1,
parallelism: 1,
};
let kek = derive_kek(password, &salt, &kdf_params).expect("KEK derivation");
let dek = generate_dek();
let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).expect("DEK wrapping");
// 1. Initialisierung des Containers
{
let db = Database::open(&container_path).expect("Open database");
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag)
.expect("Init schema");
db.checkpoint().expect("Initial Checkpoint");
}
// 2. Parallele Schreiblast mit SanctumFs erzeugen
let stop_signal = Arc::new(AtomicBool::new(false));
let db = Database::open(&container_path).expect("Open database for VFS");
let fs = SanctumFs::new(db, dek.clone(), FORMAT_VERSION);
let mut handles = Vec::new();
// Spawn 4 parallele Schreiber
for worker_id in 0..4 {
let fs_clone = fs.clone();
let stop_clone = stop_signal.clone();
let handle = tokio::spawn(async move {
let mut file_idx = 0;
while !stop_clone.load(Ordering::Relaxed) && file_idx < 10 {
let file_path_str = format!("/worker_{}_file_{}.dat", worker_id, file_idx);
let dav_path = DavPath::new(&file_path_str).unwrap();
let mut opts = OpenOptions::default();
opts.write = true;
opts.create = true;
opts.truncate = true;
// Datei anlegen
let mut file = match fs_clone.open(&dav_path, opts).await {
Ok(f) => f,
Err(_) => break,
};
// Mehrere 256-KB Blöcke schreiben (über mehrere Chunks hinweg)
let mut payload = vec![0u8; 256 * 1024];
rand::thread_rng().fill_bytes(&mut payload);
for _ in 0..6 {
if stop_clone.load(Ordering::Relaxed) {
break;
}
let _ = file.write_bytes(Bytes::copy_from_slice(&payload)).await;
}
let _ = file.flush().await;
file_idx += 1;
}
});
handles.push(handle);
}
// Lass die Worker 500ms unter Volllast schreiben
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
// 3. Simuliere abrupten Prozessabbruch (Hard Kill / Power Cut)
// Wir brechen die Worker hart ab (Cancel) und verwerfen das FS-Handle ohne sauberen Unmount
stop_signal.store(true, Ordering::SeqCst);
for h in handles {
h.abort(); // Simuliert Kill
}
// FS ohne Checkpoint/Drop-Finalisierung freigeben
drop(fs);
// 4. Recovery & Integritätsprüfung nach Crash
// Das System muss die SQLite WAL-Datei automatisch erkennen und verarbeiten
let verify_result = verify_container(&container_path, Some(&dek), false).expect("Verify post-crash");
assert!(
verify_result.is_healthy(),
"Container muss nach Crash vollkommen konsistent sein! Fehler: {:?}",
verify_result.errors
);
assert_eq!(verify_result.corrupted_chunks, 0, "Keine korrupten Chunks erlaubt");
// 5. Konsistentes Weiterarbeiten nach dem Absturz
let db_recovered = Database::open(&container_path).expect("Open database after crash");
let fs_recovered = SanctumFs::new(db_recovered, dek.clone(), FORMAT_VERSION);
// Neue Datei im wiederhergestellten Dateisystem anlegen und lesen
let recovery_test_path = DavPath::new("/post_crash_verification.txt").unwrap();
{
let mut opts = OpenOptions::default();
opts.write = true;
opts.create = true;
opts.truncate = true;
let mut file = fs_recovered
.open(&recovery_test_path, opts)
.await
.expect("Create post-crash file");
file.write_bytes(Bytes::from_static(b"Sanctum Crash Consistency Verified!"))
.await
.expect("Write post crash file");
file.flush().await.expect("Flush post crash file");
}
// Datei wieder einlesen
{
let mut opts = OpenOptions::default();
opts.read = true;
let mut file = fs_recovered
.open(&recovery_test_path, opts)
.await
.expect("Read post-crash file");
let bytes = file.read_bytes(1024).await.expect("Read bytes");
assert_eq!(&bytes[..], b"Sanctum Crash Consistency Verified!");
}
// Sauberes Aufräumen der Testdatei
drop(fs_recovered);
let _ = std::fs::remove_file(&container_path);
}