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

veridian_kernel/services/csi/
controller.rs

1//! CSI Controller Service
2//!
3//! Provides volume lifecycle management including creation, deletion,
4//! publishing, and capacity tracking.
5
6#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9use core::sync::atomic::{AtomicU64, Ordering};
10
11// ---------------------------------------------------------------------------
12// Volume Types
13// ---------------------------------------------------------------------------
14
15/// Volume access type.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum AccessType {
18    /// Block device access.
19    Block,
20    /// Mounted filesystem access.
21    Mount,
22}
23
24/// Volume access mode.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum AccessMode {
27    /// Single node writer.
28    SingleNodeWriter,
29    /// Single node read-only.
30    SingleNodeReadOnly,
31    /// Multi-node read-only.
32    MultiNodeReadOnly,
33    /// Multi-node multi-writer.
34    MultiNodeMultiWriter,
35}
36
37/// Volume state in its lifecycle.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum VolumeState {
40    /// Volume is being created.
41    #[default]
42    Creating,
43    /// Volume is available for use.
44    Available,
45    /// Volume is currently in use.
46    InUse,
47    /// Volume is being deleted.
48    Deleting,
49}
50
51/// A storage volume.
52#[derive(Debug, Clone)]
53pub struct Volume {
54    /// Unique volume identifier.
55    pub id: u64,
56    /// Human-readable name.
57    pub name: String,
58    /// Capacity in bytes.
59    pub capacity_bytes: u64,
60    /// Access type (block or mount).
61    pub access_type: AccessType,
62    /// Filesystem type (e.g., "ext4", "xfs"). Empty for block.
63    pub fs_type: String,
64    /// Node this volume is published to (if any).
65    pub node_id: Option<String>,
66    /// Current state.
67    pub state: VolumeState,
68    /// Access mode.
69    pub access_mode: AccessMode,
70    /// Volume attributes.
71    pub attributes: BTreeMap<String, String>,
72    /// Tick when created.
73    pub created_tick: u64,
74}
75
76// ---------------------------------------------------------------------------
77// Controller Error
78// ---------------------------------------------------------------------------
79
80/// CSI controller error.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum ControllerError {
83    /// Volume not found.
84    VolumeNotFound(u64),
85    /// Volume already exists.
86    VolumeAlreadyExists(String),
87    /// Volume is in use and cannot be deleted.
88    VolumeInUse(u64),
89    /// Insufficient capacity.
90    InsufficientCapacity,
91    /// Invalid state for operation.
92    InvalidState { volume_id: u64, state: VolumeState },
93    /// Volume already published to a node.
94    AlreadyPublished(u64),
95    /// Volume not published.
96    NotPublished(u64),
97}
98
99// ---------------------------------------------------------------------------
100// Controller Service
101// ---------------------------------------------------------------------------
102
103/// Next volume ID generator.
104static NEXT_VOLUME_ID: AtomicU64 = AtomicU64::new(1);
105
106fn alloc_volume_id() -> u64 {
107    NEXT_VOLUME_ID.fetch_add(1, Ordering::Relaxed)
108}
109
110/// CSI ControllerService implementation.
111#[derive(Debug)]
112pub struct ControllerService {
113    /// Volumes keyed by ID.
114    volumes: BTreeMap<u64, Volume>,
115    /// Name to ID index.
116    name_index: BTreeMap<String, u64>,
117    /// Total capacity available (bytes).
118    total_capacity: u64,
119    /// Capacity currently used (bytes).
120    used_capacity: u64,
121}
122
123impl Default for ControllerService {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl ControllerService {
130    /// Default total capacity: 100 GB.
131    pub const DEFAULT_CAPACITY: u64 = 100 * 1024 * 1024 * 1024;
132
133    /// Create a new controller service.
134    pub fn new() -> Self {
135        ControllerService {
136            volumes: BTreeMap::new(),
137            name_index: BTreeMap::new(),
138            total_capacity: Self::DEFAULT_CAPACITY,
139            used_capacity: 0,
140        }
141    }
142
143    /// Create with custom capacity.
144    pub fn with_capacity(total_capacity: u64) -> Self {
145        ControllerService {
146            volumes: BTreeMap::new(),
147            name_index: BTreeMap::new(),
148            total_capacity,
149            used_capacity: 0,
150        }
151    }
152
153    /// Create a new volume.
154    pub fn create_volume(
155        &mut self,
156        name: String,
157        capacity_bytes: u64,
158        access_type: AccessType,
159        fs_type: String,
160        access_mode: AccessMode,
161        current_tick: u64,
162    ) -> Result<u64, ControllerError> {
163        // Check for duplicate name
164        if self.name_index.contains_key(&name) {
165            return Err(ControllerError::VolumeAlreadyExists(name));
166        }
167
168        // Check capacity
169        if self.used_capacity.saturating_add(capacity_bytes) > self.total_capacity {
170            return Err(ControllerError::InsufficientCapacity);
171        }
172
173        let id = alloc_volume_id();
174        let volume = Volume {
175            id,
176            name: name.clone(),
177            capacity_bytes,
178            access_type,
179            fs_type,
180            node_id: None,
181            state: VolumeState::Available,
182            access_mode,
183            attributes: BTreeMap::new(),
184            created_tick: current_tick,
185        };
186
187        self.used_capacity = self.used_capacity.saturating_add(capacity_bytes);
188        self.name_index.insert(name, id);
189        self.volumes.insert(id, volume);
190        Ok(id)
191    }
192
193    /// Delete a volume.
194    pub fn delete_volume(&mut self, volume_id: u64) -> Result<(), ControllerError> {
195        let volume = self
196            .volumes
197            .get(&volume_id)
198            .ok_or(ControllerError::VolumeNotFound(volume_id))?;
199
200        if volume.state == VolumeState::InUse {
201            return Err(ControllerError::VolumeInUse(volume_id));
202        }
203
204        let capacity = volume.capacity_bytes;
205        let name = volume.name.clone();
206
207        self.volumes.remove(&volume_id);
208        self.name_index.remove(&name);
209        self.used_capacity = self.used_capacity.saturating_sub(capacity);
210        Ok(())
211    }
212
213    /// Get remaining capacity.
214    pub fn get_capacity(&self) -> u64 {
215        self.total_capacity.saturating_sub(self.used_capacity)
216    }
217
218    /// Publish a volume to a node (make it available for node operations).
219    pub fn controller_publish(
220        &mut self,
221        volume_id: u64,
222        node_id: String,
223    ) -> Result<(), ControllerError> {
224        let volume = self
225            .volumes
226            .get_mut(&volume_id)
227            .ok_or(ControllerError::VolumeNotFound(volume_id))?;
228
229        if volume.state != VolumeState::Available {
230            return Err(ControllerError::InvalidState {
231                volume_id,
232                state: volume.state,
233            });
234        }
235
236        if volume.node_id.is_some() {
237            return Err(ControllerError::AlreadyPublished(volume_id));
238        }
239
240        volume.node_id = Some(node_id);
241        volume.state = VolumeState::InUse;
242        Ok(())
243    }
244
245    /// Unpublish a volume from a node.
246    pub fn controller_unpublish(&mut self, volume_id: u64) -> Result<(), ControllerError> {
247        let volume = self
248            .volumes
249            .get_mut(&volume_id)
250            .ok_or(ControllerError::VolumeNotFound(volume_id))?;
251
252        if volume.node_id.is_none() {
253            return Err(ControllerError::NotPublished(volume_id));
254        }
255
256        volume.node_id = None;
257        volume.state = VolumeState::Available;
258        Ok(())
259    }
260
261    /// List all volumes.
262    pub fn list_volumes(&self) -> Vec<&Volume> {
263        self.volumes.values().collect()
264    }
265
266    /// Get volume by ID.
267    pub fn get_volume(&self, volume_id: u64) -> Option<&Volume> {
268        self.volumes.get(&volume_id)
269    }
270
271    /// Get volume by name.
272    pub fn get_volume_by_name(&self, name: &str) -> Option<&Volume> {
273        self.name_index
274            .get(name)
275            .and_then(|id| self.volumes.get(id))
276    }
277
278    /// Validate volume capabilities.
279    pub fn validate_capabilities(
280        &self,
281        volume_id: u64,
282        access_mode: AccessMode,
283    ) -> Result<bool, ControllerError> {
284        let volume = self
285            .volumes
286            .get(&volume_id)
287            .ok_or(ControllerError::VolumeNotFound(volume_id))?;
288        Ok(volume.access_mode == access_mode)
289    }
290
291    /// Get the total number of volumes.
292    pub fn volume_count(&self) -> usize {
293        self.volumes.len()
294    }
295}
296
297// ---------------------------------------------------------------------------
298// Tests
299// ---------------------------------------------------------------------------
300
301#[cfg(test)]
302mod tests {
303    #[allow(unused_imports)]
304    use alloc::string::ToString;
305
306    use super::*;
307
308    fn make_service() -> ControllerService {
309        ControllerService::new()
310    }
311
312    #[test]
313    fn test_create_volume() {
314        let mut svc = make_service();
315        let id = svc
316            .create_volume(
317                String::from("data-vol"),
318                1024 * 1024 * 1024,
319                AccessType::Mount,
320                String::from("ext4"),
321                AccessMode::SingleNodeWriter,
322                100,
323            )
324            .unwrap();
325        assert!(id > 0);
326        let vol = svc.get_volume(id).unwrap();
327        assert_eq!(vol.name, "data-vol");
328        assert_eq!(vol.state, VolumeState::Available);
329    }
330
331    #[test]
332    fn test_create_duplicate_name() {
333        let mut svc = make_service();
334        svc.create_volume(
335            String::from("vol1"),
336            1024,
337            AccessType::Mount,
338            String::from("ext4"),
339            AccessMode::SingleNodeWriter,
340            100,
341        )
342        .unwrap();
343        let result = svc.create_volume(
344            String::from("vol1"),
345            1024,
346            AccessType::Mount,
347            String::from("ext4"),
348            AccessMode::SingleNodeWriter,
349            200,
350        );
351        assert!(result.is_err());
352    }
353
354    #[test]
355    fn test_delete_volume() {
356        let mut svc = make_service();
357        let id = svc
358            .create_volume(
359                String::from("vol1"),
360                1024,
361                AccessType::Block,
362                String::new(),
363                AccessMode::SingleNodeWriter,
364                100,
365            )
366            .unwrap();
367        svc.delete_volume(id).unwrap();
368        assert_eq!(svc.volume_count(), 0);
369    }
370
371    #[test]
372    fn test_delete_in_use_volume() {
373        let mut svc = make_service();
374        let id = svc
375            .create_volume(
376                String::from("vol1"),
377                1024,
378                AccessType::Mount,
379                String::from("ext4"),
380                AccessMode::SingleNodeWriter,
381                100,
382            )
383            .unwrap();
384        svc.controller_publish(id, String::from("node-1")).unwrap();
385        assert_eq!(svc.delete_volume(id), Err(ControllerError::VolumeInUse(id)));
386    }
387
388    #[test]
389    fn test_publish_unpublish() {
390        let mut svc = make_service();
391        let id = svc
392            .create_volume(
393                String::from("vol1"),
394                1024,
395                AccessType::Mount,
396                String::from("ext4"),
397                AccessMode::SingleNodeWriter,
398                100,
399            )
400            .unwrap();
401
402        svc.controller_publish(id, String::from("node-1")).unwrap();
403        let vol = svc.get_volume(id).unwrap();
404        assert_eq!(vol.state, VolumeState::InUse);
405
406        svc.controller_unpublish(id).unwrap();
407        let vol = svc.get_volume(id).unwrap();
408        assert_eq!(vol.state, VolumeState::Available);
409    }
410
411    #[test]
412    fn test_capacity_tracking() {
413        let mut svc = ControllerService::with_capacity(2048);
414        svc.create_volume(
415            String::from("v1"),
416            1024,
417            AccessType::Block,
418            String::new(),
419            AccessMode::SingleNodeWriter,
420            100,
421        )
422        .unwrap();
423        assert_eq!(svc.get_capacity(), 1024);
424
425        // Should fail: not enough capacity
426        let result = svc.create_volume(
427            String::from("v2"),
428            2048,
429            AccessType::Block,
430            String::new(),
431            AccessMode::SingleNodeWriter,
432            200,
433        );
434        assert_eq!(result, Err(ControllerError::InsufficientCapacity));
435    }
436
437    #[test]
438    fn test_list_volumes() {
439        let mut svc = make_service();
440        svc.create_volume(
441            String::from("v1"),
442            1024,
443            AccessType::Block,
444            String::new(),
445            AccessMode::SingleNodeWriter,
446            100,
447        )
448        .unwrap();
449        svc.create_volume(
450            String::from("v2"),
451            2048,
452            AccessType::Mount,
453            String::from("xfs"),
454            AccessMode::MultiNodeReadOnly,
455            200,
456        )
457        .unwrap();
458        assert_eq!(svc.list_volumes().len(), 2);
459    }
460
461    #[test]
462    fn test_validate_capabilities() {
463        let mut svc = make_service();
464        let id = svc
465            .create_volume(
466                String::from("v1"),
467                1024,
468                AccessType::Block,
469                String::new(),
470                AccessMode::SingleNodeWriter,
471                100,
472            )
473            .unwrap();
474        assert!(svc
475            .validate_capabilities(id, AccessMode::SingleNodeWriter)
476            .unwrap());
477        assert!(!svc
478            .validate_capabilities(id, AccessMode::MultiNodeReadOnly)
479            .unwrap());
480    }
481
482    #[test]
483    fn test_get_volume_by_name() {
484        let mut svc = make_service();
485        svc.create_volume(
486            String::from("my-vol"),
487            1024,
488            AccessType::Mount,
489            String::from("ext4"),
490            AccessMode::SingleNodeWriter,
491            100,
492        )
493        .unwrap();
494        assert!(svc.get_volume_by_name("my-vol").is_some());
495        assert!(svc.get_volume_by_name("other").is_none());
496    }
497
498    #[test]
499    fn test_unpublish_not_published() {
500        let mut svc = make_service();
501        let id = svc
502            .create_volume(
503                String::from("v1"),
504                1024,
505                AccessType::Block,
506                String::new(),
507                AccessMode::SingleNodeWriter,
508                100,
509            )
510            .unwrap();
511        assert_eq!(
512            svc.controller_unpublish(id),
513            Err(ControllerError::NotPublished(id))
514        );
515    }
516}