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

veridian_kernel/services/cri/
runtime.rs

1//! Container Runtime Service
2//!
3//! Provides pod sandbox and container lifecycle management following
4//! the CRI RuntimeService specification.
5
6#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9use core::sync::atomic::{AtomicU64, Ordering};
10
11// ---------------------------------------------------------------------------
12// Pod Sandbox
13// ---------------------------------------------------------------------------
14
15/// Network configuration for a pod sandbox.
16#[derive(Debug, Clone, Default)]
17pub struct NetworkConfig {
18    /// Pod CIDR.
19    pub pod_cidr: String,
20    /// DNS server addresses.
21    pub dns_servers: Vec<String>,
22    /// DNS search domains.
23    pub dns_searches: Vec<String>,
24}
25
26/// Pod sandbox state.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum PodSandboxState {
29    /// Sandbox is ready to run containers.
30    Ready,
31    /// Sandbox is not ready (stopped or failed).
32    #[default]
33    NotReady,
34}
35
36/// A pod sandbox groups containers with shared namespaces and networking.
37#[derive(Debug, Clone)]
38pub struct PodSandbox {
39    /// Unique sandbox identifier.
40    pub id: u64,
41    /// Human-readable name.
42    pub name: String,
43    /// Kubernetes namespace.
44    pub namespace: String,
45    /// Current state.
46    pub state: PodSandboxState,
47    /// Network configuration.
48    pub network_config: NetworkConfig,
49    /// Tick when the sandbox was created.
50    pub created_tick: u64,
51    /// Labels (key-value metadata).
52    pub labels: BTreeMap<String, String>,
53    /// Annotations.
54    pub annotations: BTreeMap<String, String>,
55}
56
57// ---------------------------------------------------------------------------
58// Container
59// ---------------------------------------------------------------------------
60
61/// Container state.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63pub enum ContainerState {
64    /// Container has been created but not started.
65    #[default]
66    Created,
67    /// Container is actively running.
68    Running,
69    /// Container has exited.
70    Exited,
71    /// Container state is unknown.
72    Unknown,
73}
74
75impl ContainerState {
76    /// Check if a state transition is valid.
77    pub fn can_transition_to(self, target: ContainerState) -> bool {
78        matches!(
79            (self, target),
80            (ContainerState::Created, ContainerState::Running)
81                | (ContainerState::Running, ContainerState::Exited)
82                | (ContainerState::Created, ContainerState::Exited)
83                | (_, ContainerState::Unknown)
84        )
85    }
86}
87
88/// A container running within a pod sandbox.
89#[derive(Debug, Clone)]
90pub struct Container {
91    /// Unique container identifier.
92    pub id: u64,
93    /// Human-readable name.
94    pub name: String,
95    /// Container image reference.
96    pub image: String,
97    /// Current state.
98    pub state: ContainerState,
99    /// Pod sandbox this container belongs to.
100    pub pod_sandbox_id: u64,
101    /// Command to execute.
102    pub command: Vec<String>,
103    /// Command arguments.
104    pub args: Vec<String>,
105    /// Environment variables (key=value).
106    pub env: Vec<String>,
107    /// Exit code (set when state is Exited).
108    pub exit_code: i32,
109    /// Tick when started.
110    pub started_tick: u64,
111    /// Tick when finished.
112    pub finished_tick: u64,
113    /// Labels.
114    pub labels: BTreeMap<String, String>,
115}
116
117/// Container status information.
118#[derive(Debug, Clone)]
119pub struct ContainerStatus {
120    /// Container ID.
121    pub id: u64,
122    /// Container name.
123    pub name: String,
124    /// Current state.
125    pub state: ContainerState,
126    /// Image reference.
127    pub image: String,
128    /// Exit code.
129    pub exit_code: i32,
130    /// Start tick.
131    pub started_tick: u64,
132    /// Finish tick.
133    pub finished_tick: u64,
134}
135
136// ---------------------------------------------------------------------------
137// Runtime Service
138// ---------------------------------------------------------------------------
139
140/// CRI error type.
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub enum CriError {
143    /// Sandbox not found.
144    SandboxNotFound(u64),
145    /// Container not found.
146    ContainerNotFound(u64),
147    /// Invalid state transition.
148    InvalidStateTransition,
149    /// Sandbox is not ready.
150    SandboxNotReady(u64),
151    /// Duplicate ID.
152    AlreadyExists(u64),
153}
154
155/// Next unique ID generator.
156static NEXT_ID: AtomicU64 = AtomicU64::new(1);
157
158fn alloc_id() -> u64 {
159    NEXT_ID.fetch_add(1, Ordering::Relaxed)
160}
161
162/// CRI RuntimeService implementation.
163#[derive(Debug)]
164pub struct RuntimeService {
165    /// Active pod sandboxes.
166    sandboxes: BTreeMap<u64, PodSandbox>,
167    /// Active containers.
168    containers: BTreeMap<u64, Container>,
169}
170
171impl Default for RuntimeService {
172    fn default() -> Self {
173        Self::new()
174    }
175}
176
177impl RuntimeService {
178    /// Create a new runtime service.
179    pub fn new() -> Self {
180        RuntimeService {
181            sandboxes: BTreeMap::new(),
182            containers: BTreeMap::new(),
183        }
184    }
185
186    /// Create and start a pod sandbox.
187    pub fn run_pod_sandbox(
188        &mut self,
189        name: String,
190        namespace: String,
191        network_config: NetworkConfig,
192        current_tick: u64,
193    ) -> Result<u64, CriError> {
194        let id = alloc_id();
195        let sandbox = PodSandbox {
196            id,
197            name,
198            namespace,
199            state: PodSandboxState::Ready,
200            network_config,
201            created_tick: current_tick,
202            labels: BTreeMap::new(),
203            annotations: BTreeMap::new(),
204        };
205        self.sandboxes.insert(id, sandbox);
206        Ok(id)
207    }
208
209    /// Stop a running pod sandbox.
210    pub fn stop_pod_sandbox(&mut self, sandbox_id: u64) -> Result<(), CriError> {
211        let sandbox = self
212            .sandboxes
213            .get_mut(&sandbox_id)
214            .ok_or(CriError::SandboxNotFound(sandbox_id))?;
215        sandbox.state = PodSandboxState::NotReady;
216
217        // Stop all containers in this sandbox
218        for container in self.containers.values_mut() {
219            if container.pod_sandbox_id == sandbox_id && container.state == ContainerState::Running
220            {
221                container.state = ContainerState::Exited;
222                container.exit_code = -1;
223            }
224        }
225        Ok(())
226    }
227
228    /// Remove a stopped pod sandbox.
229    pub fn remove_pod_sandbox(&mut self, sandbox_id: u64) -> Result<(), CriError> {
230        if !self.sandboxes.contains_key(&sandbox_id) {
231            return Err(CriError::SandboxNotFound(sandbox_id));
232        }
233
234        // Remove all containers in this sandbox
235        self.containers
236            .retain(|_, c| c.pod_sandbox_id != sandbox_id);
237        self.sandboxes.remove(&sandbox_id);
238        Ok(())
239    }
240
241    /// Get pod sandbox status.
242    pub fn pod_sandbox_status(&self, sandbox_id: u64) -> Result<&PodSandbox, CriError> {
243        self.sandboxes
244            .get(&sandbox_id)
245            .ok_or(CriError::SandboxNotFound(sandbox_id))
246    }
247
248    /// List pod sandboxes, optionally filtered by state.
249    pub fn list_pod_sandboxes(&self, state_filter: Option<PodSandboxState>) -> Vec<&PodSandbox> {
250        self.sandboxes
251            .values()
252            .filter(|s| state_filter.is_none() || Some(s.state) == state_filter)
253            .collect()
254    }
255
256    /// Create a container within a pod sandbox.
257    pub fn create_container(
258        &mut self,
259        pod_sandbox_id: u64,
260        name: String,
261        image: String,
262        command: Vec<String>,
263        args: Vec<String>,
264        env: Vec<String>,
265    ) -> Result<u64, CriError> {
266        // Verify sandbox exists and is ready
267        let sandbox = self
268            .sandboxes
269            .get(&pod_sandbox_id)
270            .ok_or(CriError::SandboxNotFound(pod_sandbox_id))?;
271        if sandbox.state != PodSandboxState::Ready {
272            return Err(CriError::SandboxNotReady(pod_sandbox_id));
273        }
274
275        let id = alloc_id();
276        let container = Container {
277            id,
278            name,
279            image,
280            state: ContainerState::Created,
281            pod_sandbox_id,
282            command,
283            args,
284            env,
285            exit_code: 0,
286            started_tick: 0,
287            finished_tick: 0,
288            labels: BTreeMap::new(),
289        };
290        self.containers.insert(id, container);
291        Ok(id)
292    }
293
294    /// Start a created container.
295    pub fn start_container(
296        &mut self,
297        container_id: u64,
298        current_tick: u64,
299    ) -> Result<(), CriError> {
300        let container = self
301            .containers
302            .get_mut(&container_id)
303            .ok_or(CriError::ContainerNotFound(container_id))?;
304
305        if !container.state.can_transition_to(ContainerState::Running) {
306            return Err(CriError::InvalidStateTransition);
307        }
308
309        container.state = ContainerState::Running;
310        container.started_tick = current_tick;
311        Ok(())
312    }
313
314    /// Stop a running container.
315    pub fn stop_container(&mut self, container_id: u64, current_tick: u64) -> Result<(), CriError> {
316        let container = self
317            .containers
318            .get_mut(&container_id)
319            .ok_or(CriError::ContainerNotFound(container_id))?;
320
321        if !container.state.can_transition_to(ContainerState::Exited) {
322            return Err(CriError::InvalidStateTransition);
323        }
324
325        container.state = ContainerState::Exited;
326        container.exit_code = 0;
327        container.finished_tick = current_tick;
328        Ok(())
329    }
330
331    /// Remove a stopped container.
332    pub fn remove_container(&mut self, container_id: u64) -> Result<(), CriError> {
333        if !self.containers.contains_key(&container_id) {
334            return Err(CriError::ContainerNotFound(container_id));
335        }
336        self.containers.remove(&container_id);
337        Ok(())
338    }
339
340    /// Get container status.
341    pub fn container_status(&self, container_id: u64) -> Result<ContainerStatus, CriError> {
342        let c = self
343            .containers
344            .get(&container_id)
345            .ok_or(CriError::ContainerNotFound(container_id))?;
346
347        Ok(ContainerStatus {
348            id: c.id,
349            name: c.name.clone(),
350            state: c.state,
351            image: c.image.clone(),
352            exit_code: c.exit_code,
353            started_tick: c.started_tick,
354            finished_tick: c.finished_tick,
355        })
356    }
357
358    /// List containers, optionally filtered by state and/or sandbox.
359    pub fn list_containers(
360        &self,
361        state_filter: Option<ContainerState>,
362        sandbox_filter: Option<u64>,
363    ) -> Vec<&Container> {
364        self.containers
365            .values()
366            .filter(|c| state_filter.is_none() || Some(c.state) == state_filter)
367            .filter(|c| sandbox_filter.is_none() || Some(c.pod_sandbox_id) == sandbox_filter)
368            .collect()
369    }
370
371    /// Get the total number of sandboxes.
372    pub fn sandbox_count(&self) -> usize {
373        self.sandboxes.len()
374    }
375
376    /// Get the total number of containers.
377    pub fn container_count(&self) -> usize {
378        self.containers.len()
379    }
380}
381
382// ---------------------------------------------------------------------------
383// Tests
384// ---------------------------------------------------------------------------
385
386#[cfg(test)]
387mod tests {
388    #[allow(unused_imports)]
389    use alloc::string::ToString;
390    #[allow(unused_imports)]
391    use alloc::vec;
392
393    use super::*;
394
395    fn make_service() -> RuntimeService {
396        RuntimeService::new()
397    }
398
399    #[test]
400    fn test_run_pod_sandbox() {
401        let mut svc = make_service();
402        let id = svc
403            .run_pod_sandbox(
404                String::from("test-pod"),
405                String::from("default"),
406                NetworkConfig::default(),
407                100,
408            )
409            .unwrap();
410        assert!(id > 0);
411        let sandbox = svc.pod_sandbox_status(id).unwrap();
412        assert_eq!(sandbox.state, PodSandboxState::Ready);
413        assert_eq!(sandbox.name, "test-pod");
414    }
415
416    #[test]
417    fn test_stop_pod_sandbox() {
418        let mut svc = make_service();
419        let id = svc
420            .run_pod_sandbox(
421                String::from("pod1"),
422                String::from("default"),
423                NetworkConfig::default(),
424                100,
425            )
426            .unwrap();
427        svc.stop_pod_sandbox(id).unwrap();
428        let sandbox = svc.pod_sandbox_status(id).unwrap();
429        assert_eq!(sandbox.state, PodSandboxState::NotReady);
430    }
431
432    #[test]
433    fn test_remove_pod_sandbox() {
434        let mut svc = make_service();
435        let id = svc
436            .run_pod_sandbox(
437                String::from("pod1"),
438                String::from("default"),
439                NetworkConfig::default(),
440                100,
441            )
442            .unwrap();
443        svc.stop_pod_sandbox(id).unwrap();
444        svc.remove_pod_sandbox(id).unwrap();
445        assert_eq!(svc.sandbox_count(), 0);
446    }
447
448    #[test]
449    fn test_remove_nonexistent_sandbox() {
450        let mut svc = make_service();
451        assert_eq!(
452            svc.remove_pod_sandbox(999),
453            Err(CriError::SandboxNotFound(999))
454        );
455    }
456
457    #[test]
458    fn test_create_container() {
459        let mut svc = make_service();
460        let pod_id = svc
461            .run_pod_sandbox(
462                String::from("pod1"),
463                String::from("default"),
464                NetworkConfig::default(),
465                100,
466            )
467            .unwrap();
468        let cid = svc
469            .create_container(
470                pod_id,
471                String::from("nginx"),
472                String::from("nginx:latest"),
473                vec![String::from("/usr/sbin/nginx")],
474                vec![String::from("-g"), String::from("daemon off;")],
475                vec![String::from("PORT=80")],
476            )
477            .unwrap();
478        let status = svc.container_status(cid).unwrap();
479        assert_eq!(status.state, ContainerState::Created);
480    }
481
482    #[test]
483    fn test_start_and_stop_container() {
484        let mut svc = make_service();
485        let pod_id = svc
486            .run_pod_sandbox(
487                String::from("pod1"),
488                String::from("default"),
489                NetworkConfig::default(),
490                100,
491            )
492            .unwrap();
493        let cid = svc
494            .create_container(
495                pod_id,
496                String::from("app"),
497                String::from("app:v1"),
498                Vec::new(),
499                Vec::new(),
500                Vec::new(),
501            )
502            .unwrap();
503
504        svc.start_container(cid, 200).unwrap();
505        assert_eq!(
506            svc.container_status(cid).unwrap().state,
507            ContainerState::Running
508        );
509
510        svc.stop_container(cid, 300).unwrap();
511        let status = svc.container_status(cid).unwrap();
512        assert_eq!(status.state, ContainerState::Exited);
513        assert_eq!(status.exit_code, 0);
514    }
515
516    #[test]
517    fn test_container_invalid_transition() {
518        let mut svc = make_service();
519        let pod_id = svc
520            .run_pod_sandbox(
521                String::from("pod1"),
522                String::from("default"),
523                NetworkConfig::default(),
524                100,
525            )
526            .unwrap();
527        let cid = svc
528            .create_container(
529                pod_id,
530                String::from("app"),
531                String::from("app:v1"),
532                Vec::new(),
533                Vec::new(),
534                Vec::new(),
535            )
536            .unwrap();
537
538        svc.start_container(cid, 200).unwrap();
539        svc.stop_container(cid, 300).unwrap();
540        // Cannot start an exited container
541        assert_eq!(
542            svc.start_container(cid, 400),
543            Err(CriError::InvalidStateTransition)
544        );
545    }
546
547    #[test]
548    fn test_list_containers_filter() {
549        let mut svc = make_service();
550        let pod_id = svc
551            .run_pod_sandbox(
552                String::from("pod1"),
553                String::from("default"),
554                NetworkConfig::default(),
555                100,
556            )
557            .unwrap();
558        let c1 = svc
559            .create_container(
560                pod_id,
561                String::from("a"),
562                String::from("img:v1"),
563                Vec::new(),
564                Vec::new(),
565                Vec::new(),
566            )
567            .unwrap();
568        let _c2 = svc
569            .create_container(
570                pod_id,
571                String::from("b"),
572                String::from("img:v1"),
573                Vec::new(),
574                Vec::new(),
575                Vec::new(),
576            )
577            .unwrap();
578
579        svc.start_container(c1, 200).unwrap();
580
581        let running = svc.list_containers(Some(ContainerState::Running), None);
582        assert_eq!(running.len(), 1);
583        let created = svc.list_containers(Some(ContainerState::Created), None);
584        assert_eq!(created.len(), 1);
585        let all = svc.list_containers(None, None);
586        assert_eq!(all.len(), 2);
587    }
588
589    #[test]
590    fn test_stop_sandbox_stops_containers() {
591        let mut svc = make_service();
592        let pod_id = svc
593            .run_pod_sandbox(
594                String::from("pod1"),
595                String::from("default"),
596                NetworkConfig::default(),
597                100,
598            )
599            .unwrap();
600        let cid = svc
601            .create_container(
602                pod_id,
603                String::from("app"),
604                String::from("img:v1"),
605                Vec::new(),
606                Vec::new(),
607                Vec::new(),
608            )
609            .unwrap();
610        svc.start_container(cid, 200).unwrap();
611        svc.stop_pod_sandbox(pod_id).unwrap();
612
613        let status = svc.container_status(cid).unwrap();
614        assert_eq!(status.state, ContainerState::Exited);
615    }
616
617    #[test]
618    fn test_create_container_not_ready_sandbox() {
619        let mut svc = make_service();
620        let pod_id = svc
621            .run_pod_sandbox(
622                String::from("pod1"),
623                String::from("default"),
624                NetworkConfig::default(),
625                100,
626            )
627            .unwrap();
628        svc.stop_pod_sandbox(pod_id).unwrap();
629        assert_eq!(
630            svc.create_container(
631                pod_id,
632                String::from("a"),
633                String::from("img"),
634                Vec::new(),
635                Vec::new(),
636                Vec::new(),
637            ),
638            Err(CriError::SandboxNotReady(pod_id))
639        );
640    }
641
642    #[test]
643    fn test_remove_container() {
644        let mut svc = make_service();
645        let pod_id = svc
646            .run_pod_sandbox(
647                String::from("pod1"),
648                String::from("default"),
649                NetworkConfig::default(),
650                100,
651            )
652            .unwrap();
653        let cid = svc
654            .create_container(
655                pod_id,
656                String::from("app"),
657                String::from("img"),
658                Vec::new(),
659                Vec::new(),
660                Vec::new(),
661            )
662            .unwrap();
663        svc.remove_container(cid).unwrap();
664        assert_eq!(svc.container_count(), 0);
665    }
666}