Skip to main content

libcontainer/container/
container.rs

1use std::collections::HashMap;
2use std::ffi::OsString;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use chrono::{DateTime, Utc};
7use nix::unistd::Pid;
8use procfs::process::Process;
9
10use crate::config::YoukiConfig;
11use crate::container::{ContainerStatus, State};
12use crate::error::LibcontainerError;
13use crate::syscall::syscall::create_syscall;
14
15/// Structure representing the container data
16#[derive(Debug, Clone)]
17pub struct Container {
18    // State of the container
19    pub state: State,
20    // indicated the directory for the root path in the container
21    pub root: PathBuf,
22}
23
24impl Default for Container {
25    fn default() -> Self {
26        Self {
27            state: State::default(),
28            root: PathBuf::from("/run/youki"),
29        }
30    }
31}
32
33impl Container {
34    pub fn new(
35        container_id: &str,
36        status: ContainerStatus,
37        pid: Option<i32>,
38        bundle: &Path,
39        container_root: &Path,
40    ) -> Result<Self, LibcontainerError> {
41        let container_root = fs::canonicalize(container_root).map_err(|err| {
42            LibcontainerError::InvalidInput(format!(
43                "invalid container root {container_root:?}: {err:?}"
44            ))
45        })?;
46        let bundle = fs::canonicalize(bundle).map_err(|err| {
47            LibcontainerError::InvalidInput(format!("invalid bundle {bundle:?}: {err:?}"))
48        })?;
49        let state = State::new(container_id, status, pid, bundle);
50
51        Ok(Self {
52            state,
53            root: container_root,
54        })
55    }
56
57    pub fn id(&self) -> &str {
58        &self.state.id
59    }
60
61    pub fn can_start(&self) -> bool {
62        self.state.status.can_start()
63    }
64
65    pub fn can_kill(&self) -> bool {
66        self.state.status.can_kill()
67    }
68
69    pub fn can_delete(&self) -> bool {
70        self.state.status.can_delete()
71    }
72
73    pub fn can_exec(&self) -> bool {
74        self.state.status == ContainerStatus::Running
75    }
76
77    pub fn can_pause(&self) -> bool {
78        self.state.status.can_pause()
79    }
80
81    pub fn can_resume(&self) -> bool {
82        self.state.status.can_resume()
83    }
84
85    pub fn bundle(&self) -> &PathBuf {
86        &self.state.bundle
87    }
88
89    pub fn set_annotations(&mut self, annotations: Option<HashMap<String, String>>) -> &mut Self {
90        self.state.annotations = annotations;
91        self
92    }
93
94    pub fn pid(&self) -> Option<Pid> {
95        self.state.pid.map(Pid::from_raw)
96    }
97
98    pub fn set_pid(&mut self, pid: i32) -> &mut Self {
99        self.state.pid = Some(pid);
100        self
101    }
102
103    pub fn created(&self) -> Option<DateTime<Utc>> {
104        self.state.created
105    }
106
107    pub fn creator(&self) -> Option<OsString> {
108        if let Some(uid) = self.state.creator {
109            let command = create_syscall();
110            let user_name = command.get_pwuid(uid);
111            if let Some(user_name) = user_name {
112                return Some((*user_name).to_owned());
113            }
114        }
115
116        None
117    }
118
119    pub fn set_creator(&mut self, uid: u32) -> &mut Self {
120        self.state.creator = Some(uid);
121        self
122    }
123
124    pub fn systemd(&self) -> bool {
125        self.state.use_systemd
126    }
127
128    pub fn set_systemd(&mut self, should_use: bool) -> &mut Self {
129        self.state.use_systemd = should_use;
130        self
131    }
132
133    pub fn set_clean_up_intel_rdt_directory(&mut self, clean_up: bool) -> &mut Self {
134        self.state.clean_up_intel_rdt_subdirectory = Some(clean_up);
135        self
136    }
137
138    pub fn clean_up_intel_rdt_subdirectory(&self) -> Option<bool> {
139        self.state.clean_up_intel_rdt_subdirectory
140    }
141
142    pub fn status(&self) -> ContainerStatus {
143        self.state.status
144    }
145
146    pub fn set_status(&mut self, status: ContainerStatus) -> &mut Self {
147        let created = match (status, self.state.created) {
148            (ContainerStatus::Created, None) => Some(Utc::now()),
149            _ => self.state.created,
150        };
151
152        self.state.created = created;
153        self.state.status = status;
154
155        self
156    }
157
158    pub fn refresh_status(&mut self) -> Result<(), LibcontainerError> {
159        let new_status = match self.pid() {
160            Some(pid) => {
161                // Note that Process::new does not spawn a new process
162                // but instead creates a new Process structure, and fill
163                // it with information about the process with given pid
164                if let Ok(proc) = Process::new(pid.as_raw()) {
165                    use procfs::process::ProcState;
166
167                    match proc.stat()?.state()? {
168                        ProcState::Zombie | ProcState::Dead => ContainerStatus::Stopped,
169                        _ => match self.status() {
170                            ContainerStatus::Creating
171                            | ContainerStatus::Created
172                            | ContainerStatus::Paused => self.status(),
173                            _ => ContainerStatus::Running,
174                        },
175                    }
176                } else {
177                    ContainerStatus::Stopped
178                }
179            }
180            None => ContainerStatus::Stopped,
181        };
182
183        self.set_status(new_status);
184        Ok(())
185    }
186
187    pub fn refresh_state(&mut self) -> Result<&mut Self, LibcontainerError> {
188        let state = State::load(&self.root)?;
189        self.state = state;
190
191        Ok(self)
192    }
193
194    pub fn load(container_root: PathBuf) -> Result<Self, LibcontainerError> {
195        let state = State::load(&container_root)?;
196        let mut container = Self {
197            state,
198            root: container_root,
199        };
200        container.refresh_status()?;
201        Ok(container)
202    }
203
204    pub fn save(&self) -> Result<(), LibcontainerError> {
205        tracing::debug!("Save container status: {:?} in {:?}", self, self.root);
206        self.state.save(&self.root)?;
207
208        Ok(())
209    }
210
211    pub fn spec(&self) -> Result<YoukiConfig, LibcontainerError> {
212        let spec = YoukiConfig::load(&self.root)?;
213        Ok(spec)
214    }
215}
216
217/// Checkpoint parameter structure
218pub struct CheckpointOptions {
219    pub ext_unix_sk: bool,
220    pub file_locks: bool,
221    pub image_path: PathBuf,
222    pub leave_running: bool,
223    pub shell_job: bool,
224    pub tcp_established: bool,
225    pub work_path: Option<PathBuf>,
226    pub manage_cgroups_mode: rust_criu::CgMode,
227}
228
229#[cfg(test)]
230mod tests {
231    use anyhow::{Context, Result};
232    use serial_test::serial;
233
234    use super::*;
235
236    #[test]
237    fn test_get_set_pid() {
238        let mut container = Container::default();
239
240        assert_eq!(container.pid(), None);
241        container.set_pid(1);
242        assert_eq!(container.pid(), Some(Pid::from_raw(1)));
243    }
244
245    #[test]
246    fn test_basic_getter() -> Result<()> {
247        let mut container = Container::new(
248            "container_id",
249            ContainerStatus::Creating,
250            None,
251            &PathBuf::from("."),
252            &PathBuf::from("."),
253        )?;
254
255        // testing id
256        assert_eq!(container.id(), "container_id");
257        // testing bundle path
258        assert_eq!(
259            container.bundle(),
260            &fs::canonicalize(PathBuf::from(".")).unwrap()
261        );
262        // testing root path
263        assert_eq!(container.root, fs::canonicalize(PathBuf::from("."))?);
264        // testing created
265        assert_eq!(container.created(), None);
266        container.set_status(ContainerStatus::Created);
267        assert!(container.created().is_some());
268
269        Ok(())
270    }
271
272    #[test]
273    fn test_set_annotations() {
274        let mut container = Container::default();
275        assert_eq!(container.state.annotations, None);
276
277        let mut annotations = std::collections::HashMap::with_capacity(1);
278        annotations.insert(
279            "org.criu.config".to_string(),
280            "/etc/special-youki-criu-options".to_string(),
281        );
282        container.set_annotations(Some(annotations.clone()));
283        assert_eq!(container.state.annotations, Some(annotations));
284    }
285
286    #[test]
287    fn test_get_set_systemd() {
288        let mut container = Container::default();
289        assert!(!container.systemd());
290        container.set_systemd(true);
291        assert!(container.systemd());
292        container.set_systemd(false);
293        assert!(!container.systemd());
294    }
295
296    #[test]
297    fn test_get_set_creator() {
298        let mut container = Container::default();
299        assert_eq!(container.creator(), None);
300        container.set_creator(1000);
301        assert_eq!(container.creator(), Some(OsString::from("youki")));
302    }
303
304    #[test]
305    #[serial]
306    fn test_refresh_load_save_state() -> Result<()> {
307        let tmp_dir = tempfile::tempdir().unwrap();
308        let mut container_1 = Container::new(
309            "container_id_1",
310            ContainerStatus::Created,
311            None,
312            &PathBuf::from("."),
313            tmp_dir.path(),
314        )?;
315
316        container_1.save()?;
317        let container_2 = Container::load(tmp_dir.path().to_path_buf())?;
318        assert_eq!(container_1.state.id, container_2.state.id);
319        assert_eq!(container_2.state.status, ContainerStatus::Stopped);
320
321        container_1.state.id = "container_id_1_modified".to_string();
322        container_1.save()?;
323        container_1.refresh_state()?;
324        assert_eq!(container_1.state.id, "container_id_1_modified".to_string());
325
326        Ok(())
327    }
328
329    #[test]
330    #[serial]
331    fn test_get_spec() -> Result<()> {
332        let tmp_dir = tempfile::tempdir().unwrap();
333        use oci_spec::runtime::Spec;
334        let spec = Spec::default();
335        let config = YoukiConfig::from_spec(&spec, "123").context("convert spec to config")?;
336        config.save(tmp_dir.path()).context("save config")?;
337
338        let container = Container {
339            root: tmp_dir.path().to_path_buf(),
340            ..Default::default()
341        };
342        container.spec().context("get config")?;
343
344        Ok(())
345    }
346
347    #[test]
348    #[serial]
349    fn test_get_set_refresh_status() -> Result<()> {
350        // there already has a full and well-tested flow of status in state.rs
351        // so we just let the coverage run through those can_xxx functions.
352        let mut container = Container::default();
353        assert_eq!(container.status(), ContainerStatus::Creating);
354        assert!(!container.can_start());
355        assert!(!container.can_kill());
356        assert!(!container.can_delete());
357        assert!(!container.can_exec());
358        assert!(!container.can_pause());
359        assert!(!container.can_resume());
360
361        // no PID case
362        container.refresh_status()?;
363        assert_eq!(container.status(), ContainerStatus::Stopped);
364
365        // with PID case but PID not exists
366        container.set_pid(-1);
367        container.refresh_status()?;
368        assert_eq!(container.status(), ContainerStatus::Stopped);
369
370        // with PID case
371        container.set_pid(1);
372        container.set_status(ContainerStatus::Paused);
373        container.refresh_status()?;
374        assert_eq!(container.status(), ContainerStatus::Paused);
375        container.set_status(ContainerStatus::Running);
376        container.refresh_status()?;
377        assert_eq!(container.status(), ContainerStatus::Running);
378
379        Ok(())
380    }
381}