fakecloud_iam/
persistence.rs1use std::sync::Arc;
9
10use fakecloud_persistence::SnapshotStore;
11use tokio::sync::Mutex as AsyncMutex;
12
13use crate::state::{IamSnapshot, SharedIamState, IAM_SNAPSHOT_SCHEMA_VERSION};
14
15pub type IamSnapshotLock = Arc<AsyncMutex<()>>;
20
21pub fn new_snapshot_lock() -> IamSnapshotLock {
22 Arc::new(AsyncMutex::new(()))
23}
24
25pub async fn save_iam_snapshot(
31 state: &SharedIamState,
32 store: Option<Arc<dyn SnapshotStore>>,
33 lock: &IamSnapshotLock,
34) {
35 let Some(store) = store else {
36 return;
37 };
38 let _guard = lock.lock().await;
39 let snapshot = IamSnapshot {
40 schema_version: IAM_SNAPSHOT_SCHEMA_VERSION,
41 accounts: Some(state.read().clone()),
42 state: None,
43 };
44 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
45 let bytes = serde_json::to_vec(&snapshot)
46 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
47 store.save(&bytes)
48 })
49 .await;
50 match join {
51 Ok(Ok(())) => {}
52 Ok(Err(err)) => tracing::error!(%err, "failed to write iam snapshot"),
53 Err(err) => tracing::error!(%err, "iam snapshot task panicked"),
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::state::IamState;
61 use fakecloud_core::multi_account::MultiAccountState;
62 use fakecloud_persistence::DiskSnapshotStore;
63 use parking_lot::RwLock;
64
65 fn shared_state() -> SharedIamState {
66 let multi: MultiAccountState<IamState> =
67 MultiAccountState::new("123456789012", "us-east-1", "http://localhost:4566");
68 std::sync::Arc::new(RwLock::new(multi))
69 }
70
71 #[test]
72 fn new_snapshot_lock_returns_arc_mutex() {
73 let lock: IamSnapshotLock = new_snapshot_lock();
74 let _c = lock.clone();
75 }
76
77 #[tokio::test]
78 async fn save_snapshot_none_store_is_noop() {
79 let state = shared_state();
80 let lock = new_snapshot_lock();
81 save_iam_snapshot(&state, None, &lock).await;
82 }
83
84 #[tokio::test]
85 async fn save_snapshot_writes_bytes_to_disk_store() {
86 let state = shared_state();
87 let lock = new_snapshot_lock();
88 let tmp = tempfile::tempdir().unwrap();
89 let path = tmp.path().join("iam.json");
90 let store: std::sync::Arc<dyn SnapshotStore> =
91 std::sync::Arc::new(DiskSnapshotStore::new(path.clone()));
92 save_iam_snapshot(&state, Some(store), &lock).await;
93 let bytes = std::fs::read(&path).unwrap();
94 assert!(!bytes.is_empty());
95 let v: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
96 assert_eq!(v["schema_version"], IAM_SNAPSHOT_SCHEMA_VERSION);
97 }
98}