1#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9use core::sync::atomic::{AtomicU64, Ordering};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum AccessType {
18 Block,
20 Mount,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum AccessMode {
27 SingleNodeWriter,
29 SingleNodeReadOnly,
31 MultiNodeReadOnly,
33 MultiNodeMultiWriter,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum VolumeState {
40 #[default]
42 Creating,
43 Available,
45 InUse,
47 Deleting,
49}
50
51#[derive(Debug, Clone)]
53pub struct Volume {
54 pub id: u64,
56 pub name: String,
58 pub capacity_bytes: u64,
60 pub access_type: AccessType,
62 pub fs_type: String,
64 pub node_id: Option<String>,
66 pub state: VolumeState,
68 pub access_mode: AccessMode,
70 pub attributes: BTreeMap<String, String>,
72 pub created_tick: u64,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum ControllerError {
83 VolumeNotFound(u64),
85 VolumeAlreadyExists(String),
87 VolumeInUse(u64),
89 InsufficientCapacity,
91 InvalidState { volume_id: u64, state: VolumeState },
93 AlreadyPublished(u64),
95 NotPublished(u64),
97}
98
99static 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#[derive(Debug)]
112pub struct ControllerService {
113 volumes: BTreeMap<u64, Volume>,
115 name_index: BTreeMap<String, u64>,
117 total_capacity: u64,
119 used_capacity: u64,
121}
122
123impl Default for ControllerService {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl ControllerService {
130 pub const DEFAULT_CAPACITY: u64 = 100 * 1024 * 1024 * 1024;
132
133 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 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 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 if self.name_index.contains_key(&name) {
165 return Err(ControllerError::VolumeAlreadyExists(name));
166 }
167
168 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 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 pub fn get_capacity(&self) -> u64 {
215 self.total_capacity.saturating_sub(self.used_capacity)
216 }
217
218 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 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 pub fn list_volumes(&self) -> Vec<&Volume> {
263 self.volumes.values().collect()
264 }
265
266 pub fn get_volume(&self, volume_id: u64) -> Option<&Volume> {
268 self.volumes.get(&volume_id)
269 }
270
271 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 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 pub fn volume_count(&self) -> usize {
293 self.volumes.len()
294 }
295}
296
297#[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 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}