1#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9use core::sync::atomic::{AtomicU64, Ordering};
10
11#[derive(Debug, Clone, Default)]
17pub struct NetworkConfig {
18 pub pod_cidr: String,
20 pub dns_servers: Vec<String>,
22 pub dns_searches: Vec<String>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum PodSandboxState {
29 Ready,
31 #[default]
33 NotReady,
34}
35
36#[derive(Debug, Clone)]
38pub struct PodSandbox {
39 pub id: u64,
41 pub name: String,
43 pub namespace: String,
45 pub state: PodSandboxState,
47 pub network_config: NetworkConfig,
49 pub created_tick: u64,
51 pub labels: BTreeMap<String, String>,
53 pub annotations: BTreeMap<String, String>,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63pub enum ContainerState {
64 #[default]
66 Created,
67 Running,
69 Exited,
71 Unknown,
73}
74
75impl ContainerState {
76 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#[derive(Debug, Clone)]
90pub struct Container {
91 pub id: u64,
93 pub name: String,
95 pub image: String,
97 pub state: ContainerState,
99 pub pod_sandbox_id: u64,
101 pub command: Vec<String>,
103 pub args: Vec<String>,
105 pub env: Vec<String>,
107 pub exit_code: i32,
109 pub started_tick: u64,
111 pub finished_tick: u64,
113 pub labels: BTreeMap<String, String>,
115}
116
117#[derive(Debug, Clone)]
119pub struct ContainerStatus {
120 pub id: u64,
122 pub name: String,
124 pub state: ContainerState,
126 pub image: String,
128 pub exit_code: i32,
130 pub started_tick: u64,
132 pub finished_tick: u64,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
142pub enum CriError {
143 SandboxNotFound(u64),
145 ContainerNotFound(u64),
147 InvalidStateTransition,
149 SandboxNotReady(u64),
151 AlreadyExists(u64),
153}
154
155static NEXT_ID: AtomicU64 = AtomicU64::new(1);
157
158fn alloc_id() -> u64 {
159 NEXT_ID.fetch_add(1, Ordering::Relaxed)
160}
161
162#[derive(Debug)]
164pub struct RuntimeService {
165 sandboxes: BTreeMap<u64, PodSandbox>,
167 containers: BTreeMap<u64, Container>,
169}
170
171impl Default for RuntimeService {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177impl RuntimeService {
178 pub fn new() -> Self {
180 RuntimeService {
181 sandboxes: BTreeMap::new(),
182 containers: BTreeMap::new(),
183 }
184 }
185
186 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 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 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 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 self.containers
236 .retain(|_, c| c.pod_sandbox_id != sandbox_id);
237 self.sandboxes.remove(&sandbox_id);
238 Ok(())
239 }
240
241 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 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 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 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 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 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 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 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 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 pub fn sandbox_count(&self) -> usize {
373 self.sandboxes.len()
374 }
375
376 pub fn container_count(&self) -> usize {
378 self.containers.len()
379 }
380}
381
382#[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 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}