1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-05-09 00:56:14 +02:00
youki/crates/container/src/container/state.rs

233 lines
6.9 KiB
Rust

//! Information about status and state of the container
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::path::PathBuf;
use std::{fs::File, path::Path};
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Indicates status of the container
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum ContainerStatus {
// The container is being created
Creating,
// The runtime has finished the create operation
Created,
// The container process has executed the user-specified program but has not exited
Running,
// The container process has exited
Stopped,
// The container process has paused
Paused,
}
impl Default for ContainerStatus {
fn default() -> Self {
ContainerStatus::Creating
}
}
impl ContainerStatus {
pub fn can_start(&self) -> bool {
matches!(self, ContainerStatus::Created)
}
pub fn can_kill(&self) -> bool {
use ContainerStatus::*;
match self {
Creating | Stopped => false,
Created | Running | Paused => true,
}
}
pub fn can_delete(&self) -> bool {
matches!(self, ContainerStatus::Stopped)
}
pub fn can_pause(&self) -> bool {
matches!(self, ContainerStatus::Running)
}
pub fn can_resume(&self) -> bool {
matches!(self, ContainerStatus::Paused)
}
}
impl Display for ContainerStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let print = match *self {
Self::Creating => "Creating",
Self::Created => "Created",
Self::Running => "Running",
Self::Stopped => "Stopped",
Self::Paused => "Paused",
};
write!(f, "{}", print)
}
}
/// Stores the state information of the container
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct State {
// Version is the version of the specification that is supported.
pub oci_version: String,
// ID is the container ID
pub id: String,
// Status is the runtime status of the container.
pub status: ContainerStatus,
// Pid is the process ID for the container process.
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<i32>,
// Bundle is the path to the container's bundle directory.
pub bundle: PathBuf,
// Annotations are key values associated with the container.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<HashMap<String, String>>,
// Creation time of the container
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<DateTime<Utc>>,
// User that created the container
#[serde(skip_serializing_if = "Option::is_none")]
pub creator: Option<u32>,
// Specifies if systemd should be used to manage cgroups
pub use_systemd: Option<bool>,
}
impl State {
const STATE_FILE_PATH: &'static str = "state.json";
pub fn new(
container_id: &str,
status: ContainerStatus,
pid: Option<i32>,
bundle: PathBuf,
) -> Self {
Self {
oci_version: "v1.0.2".to_string(),
id: container_id.to_string(),
status,
pid,
bundle,
annotations: Some(HashMap::default()),
created: None,
creator: None,
use_systemd: None,
}
}
pub fn save(&self, container_root: &Path) -> Result<()> {
let state_file_path = Self::file_path(container_root);
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.append(false)
.create(true)
.truncate(true)
.open(&state_file_path)
.with_context(|| format!("failed to open {}", state_file_path.display()))?;
serde_json::to_writer(&file, self)?;
Ok(())
}
pub fn load(container_root: &Path) -> Result<Self> {
let state_file_path = Self::file_path(container_root);
let file = File::open(&state_file_path).with_context(|| {
format!("failed to open container state file {:?}", state_file_path)
})?;
let state: Self = serde_json::from_reader(&file)?;
Ok(state)
}
/// Returns the path to the state JSON file for the provided `container_root`.
///
/// ```
/// # use std::path::Path;
/// # use youki::container::State;
///
/// let container_root = Path::new("/var/run/containers/container");
/// let state_file = State::file_path(&container_root);
/// assert_eq!(state_file.to_str(), Some("/var/run/containers/container/state.json"));
/// ```
pub fn file_path(container_root: &Path) -> PathBuf {
container_root.join(Self::STATE_FILE_PATH)
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct ContainerProcessState {
// Version is the version of the specification that is supported.
pub oci_version: String,
// Fds is a string array containing the names of the file descriptors passed.
// The index of the name in this array corresponds to index of the file
// descriptor in the `SCM_RIGHTS` array.
pub fds: Vec<String>,
// Pid is the process ID as seen by the runtime.
pub pid: i32,
// Opaque metadata.
pub metadata: String,
// State of the container.
pub state: State,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creating_status() {
let cstatus = ContainerStatus::default();
assert!(!cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(!cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_create_status() {
let cstatus = ContainerStatus::Created;
assert!(cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_running_status() {
let cstatus = ContainerStatus::Running;
assert!(!cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(cstatus.can_kill());
assert!(cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_stopped_status() {
let cstatus = ContainerStatus::Stopped;
assert!(!cstatus.can_start());
assert!(cstatus.can_delete());
assert!(!cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_paused_status() {
let cstatus = ContainerStatus::Paused;
assert!(!cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(cstatus.can_resume());
}
}