153 lines
5.5 KiB
Rust
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);
|
|
}
|