⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/services/csi/
snapshot.rs

1//! CSI Snapshot Service
2//!
3//! Provides COW snapshot creation, deletion, and listing for volumes.
4
5#![allow(dead_code)]
6
7use alloc::{collections::BTreeMap, string::String, vec::Vec};
8use core::sync::atomic::{AtomicU64, Ordering};
9
10// ---------------------------------------------------------------------------
11// Snapshot Types
12// ---------------------------------------------------------------------------
13
14/// A point-in-time snapshot of a volume.
15#[derive(Debug, Clone)]
16pub struct Snapshot {
17    /// Unique snapshot identifier.
18    pub id: u64,
19    /// Source volume ID.
20    pub source_volume_id: u64,
21    /// Snapshot size in bytes.
22    pub size_bytes: u64,
23    /// Tick when the snapshot was created.
24    pub created_tick: u64,
25    /// Whether the snapshot is ready to use.
26    pub ready: bool,
27    /// Human-readable name.
28    pub name: String,
29}
30
31/// Snapshot error.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum SnapshotError {
34    /// Snapshot not found.
35    NotFound(u64),
36    /// Source volume not found.
37    SourceNotFound(u64),
38    /// Snapshot already exists with this name.
39    AlreadyExists(String),
40    /// Snapshot is not ready.
41    NotReady(u64),
42}
43
44// ---------------------------------------------------------------------------
45// Snapshot Service
46// ---------------------------------------------------------------------------
47
48/// Next snapshot ID generator.
49static NEXT_SNAPSHOT_ID: AtomicU64 = AtomicU64::new(1);
50
51fn alloc_snapshot_id() -> u64 {
52    NEXT_SNAPSHOT_ID.fetch_add(1, Ordering::Relaxed)
53}
54
55/// CSI Snapshot Service implementation.
56#[derive(Debug)]
57pub struct SnapshotService {
58    /// Snapshots keyed by ID.
59    snapshots: BTreeMap<u64, Snapshot>,
60    /// Name to ID index.
61    name_index: BTreeMap<String, u64>,
62    /// Known volume IDs (for validation).
63    known_volumes: BTreeMap<u64, u64>, // volume_id -> capacity
64}
65
66impl Default for SnapshotService {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl SnapshotService {
73    /// Create a new snapshot service.
74    pub fn new() -> Self {
75        SnapshotService {
76            snapshots: BTreeMap::new(),
77            name_index: BTreeMap::new(),
78            known_volumes: BTreeMap::new(),
79        }
80    }
81
82    /// Register a known volume (for snapshot source validation).
83    pub fn register_volume(&mut self, volume_id: u64, capacity_bytes: u64) {
84        self.known_volumes.insert(volume_id, capacity_bytes);
85    }
86
87    /// Unregister a volume.
88    pub fn unregister_volume(&mut self, volume_id: u64) {
89        self.known_volumes.remove(&volume_id);
90    }
91
92    /// Create a COW snapshot of a volume.
93    pub fn create_snapshot(
94        &mut self,
95        name: String,
96        source_volume_id: u64,
97        current_tick: u64,
98    ) -> Result<u64, SnapshotError> {
99        // Check name uniqueness
100        if self.name_index.contains_key(&name) {
101            return Err(SnapshotError::AlreadyExists(name));
102        }
103
104        // Look up source volume capacity
105        let capacity = self
106            .known_volumes
107            .get(&source_volume_id)
108            .ok_or(SnapshotError::SourceNotFound(source_volume_id))?;
109
110        let id = alloc_snapshot_id();
111        let snapshot = Snapshot {
112            id,
113            source_volume_id,
114            size_bytes: *capacity,
115            created_tick: current_tick,
116            ready: true, // COW snapshots are instant
117            name: name.clone(),
118        };
119
120        self.name_index.insert(name, id);
121        self.snapshots.insert(id, snapshot);
122        Ok(id)
123    }
124
125    /// Delete a snapshot.
126    pub fn delete_snapshot(&mut self, snapshot_id: u64) -> Result<(), SnapshotError> {
127        let snapshot = self
128            .snapshots
129            .remove(&snapshot_id)
130            .ok_or(SnapshotError::NotFound(snapshot_id))?;
131        self.name_index.remove(&snapshot.name);
132        Ok(())
133    }
134
135    /// List all snapshots, optionally filtered by source volume.
136    pub fn list_snapshots(&self, source_filter: Option<u64>) -> Vec<&Snapshot> {
137        self.snapshots
138            .values()
139            .filter(|s| source_filter.is_none() || Some(s.source_volume_id) == source_filter)
140            .collect()
141    }
142
143    /// Get snapshot by ID.
144    pub fn get_snapshot(&self, snapshot_id: u64) -> Option<&Snapshot> {
145        self.snapshots.get(&snapshot_id)
146    }
147
148    /// Get snapshot by name.
149    pub fn get_snapshot_by_name(&self, name: &str) -> Option<&Snapshot> {
150        self.name_index
151            .get(name)
152            .and_then(|id| self.snapshots.get(id))
153    }
154
155    /// Get the total number of snapshots.
156    pub fn snapshot_count(&self) -> usize {
157        self.snapshots.len()
158    }
159}
160
161// ---------------------------------------------------------------------------
162// Tests
163// ---------------------------------------------------------------------------
164
165#[cfg(test)]
166mod tests {
167    #[allow(unused_imports)]
168    use alloc::string::ToString;
169
170    use super::*;
171
172    fn make_service() -> SnapshotService {
173        let mut svc = SnapshotService::new();
174        svc.register_volume(1, 1024 * 1024 * 1024);
175        svc.register_volume(2, 2048 * 1024 * 1024);
176        svc
177    }
178
179    #[test]
180    fn test_create_snapshot() {
181        let mut svc = make_service();
182        let id = svc.create_snapshot(String::from("snap-1"), 1, 100).unwrap();
183        let snap = svc.get_snapshot(id).unwrap();
184        assert_eq!(snap.source_volume_id, 1);
185        assert!(snap.ready);
186    }
187
188    #[test]
189    fn test_create_snapshot_unknown_volume() {
190        let mut svc = make_service();
191        assert_eq!(
192            svc.create_snapshot(String::from("snap"), 999, 100),
193            Err(SnapshotError::SourceNotFound(999))
194        );
195    }
196
197    #[test]
198    fn test_create_duplicate_name() {
199        let mut svc = make_service();
200        svc.create_snapshot(String::from("snap-1"), 1, 100).unwrap();
201        assert!(svc.create_snapshot(String::from("snap-1"), 2, 200).is_err());
202    }
203
204    #[test]
205    fn test_delete_snapshot() {
206        let mut svc = make_service();
207        let id = svc.create_snapshot(String::from("snap-1"), 1, 100).unwrap();
208        svc.delete_snapshot(id).unwrap();
209        assert_eq!(svc.snapshot_count(), 0);
210    }
211
212    #[test]
213    fn test_list_snapshots_filter() {
214        let mut svc = make_service();
215        svc.create_snapshot(String::from("s1"), 1, 100).unwrap();
216        svc.create_snapshot(String::from("s2"), 1, 200).unwrap();
217        svc.create_snapshot(String::from("s3"), 2, 300).unwrap();
218
219        let all = svc.list_snapshots(None);
220        assert_eq!(all.len(), 3);
221
222        let vol1 = svc.list_snapshots(Some(1));
223        assert_eq!(vol1.len(), 2);
224    }
225
226    #[test]
227    fn test_get_by_name() {
228        let mut svc = make_service();
229        svc.create_snapshot(String::from("my-snap"), 1, 100)
230            .unwrap();
231        assert!(svc.get_snapshot_by_name("my-snap").is_some());
232        assert!(svc.get_snapshot_by_name("other").is_none());
233    }
234}