diff --git a/src/commands/create.rs b/src/commands/create.rs index da1f927a8..3fe7b3de6 100644 --- a/src/commands/create.rs +++ b/src/commands/create.rs @@ -33,23 +33,6 @@ pub struct Create { // it is running, it is just another process, and has attributes such as pid, file descriptors, etc. // associated with it like any other process. impl Create { - /// instant Create Command - pub fn new( - container_id: String, - pid_file: Option, - bundle: PathBuf, - console_socket: Option, - preserve_fds: i32, - ) -> Self { - Self { - pid_file, - bundle, - console_socket, - container_id, - preserve_fds, - } - } - /// Starts a new container process pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> { let syscall = create_syscall(); ContainerBuilder::new(self.container_id.clone(), syscall.as_ref()) @@ -59,6 +42,8 @@ impl Create { .with_preserved_fds(self.preserve_fds) .as_init(&self.bundle) .with_systemd(systemd_cgroup) - .build() + .build()?; + + Ok(()) } } diff --git a/src/commands/delete.rs b/src/commands/delete.rs index a0591b210..48d029c26 100644 --- a/src/commands/delete.rs +++ b/src/commands/delete.rs @@ -1,15 +1,7 @@ -use std::fs; -use std::path::PathBuf; - -use anyhow::{bail, Context, Result}; +use crate::commands::load_container; +use anyhow::{Context, Result}; use clap::Clap; -use nix::sys::signal::Signal; - -use crate::container::{Container, ContainerStatus}; -use crate::hooks; -use crate::utils; -use cgroups; -use nix::sys::signal as nix_signal; +use std::path::PathBuf; #[derive(Clap, Debug)] pub struct Delete { @@ -21,72 +13,11 @@ pub struct Delete { } impl Delete { - /// instant Delete Command - /// - /// This method is provided for those using `youki` as a library to enable the use of the - /// Delete command to remove containers. - pub fn new(container_id: String, force: bool) -> Self { - Self { - container_id, - force, - } - } - - pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> { + pub fn exec(&self, root_path: PathBuf) -> Result<()> { log::debug!("start deleting {}", self.container_id); - // state of container is stored in a directory named as container id inside - // root directory given in commandline options - let container_root = root_path.join(&self.container_id); - if !container_root.exists() { - bail!("{} doesn't exist.", self.container_id) - } - // load container state from json file, and check status of the container - // it might be possible that delete is invoked on a running container. - log::debug!("load the container from {:?}", container_root); - let mut container = Container::load(container_root)?.refresh_status()?; - if container.can_kill() && self.force { - let sig = Signal::SIGKILL; - log::debug!("kill signal {} to {}", sig, container.pid().unwrap()); - nix_signal::kill(container.pid().unwrap(), sig)?; - container = container.update_status(ContainerStatus::Stopped); - container.save()?; - } - log::debug!("container status: {:?}", container.status()); - if container.can_delete() { - if container.root.exists() { - let config_absolute_path = container.root.join("config.json"); - log::debug!("load spec from {:?}", config_absolute_path); - let spec = oci_spec::runtime::Spec::load(config_absolute_path)?; - log::debug!("spec: {:?}", spec); - - // remove the directory storing container state - log::debug!("remove dir {:?}", container.root); - fs::remove_dir_all(&container.root)?; - - let cgroups_path = utils::get_cgroup_path( - &spec.linux.context("no linux in spec")?.cgroups_path, - container.id(), - ); - - // remove the cgroup created for the container - // check https://man7.org/linux/man-pages/man7/cgroups.7.html - // creating and removing cgroups section for more information on cgroups - let cmanager = - cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?; - cmanager.remove()?; - - if let Some(hooks) = spec.hooks.as_ref() { - hooks::run_hooks(hooks.poststop.as_ref(), Some(&container)) - .with_context(|| "Failed to run post stop hooks")?; - } - } - std::process::exit(0) - } else { - bail!( - "{} could not be deleted because it was {:?}", - container.id(), - container.status() - ) - } + let mut container = load_container(root_path, self.container_id.as_str())?; + container + .delete(self.force) + .with_context(|| format!("failed to delete container {}", self.container_id)) } } diff --git a/src/commands/events.rs b/src/commands/events.rs index 80555393d..7e8687b32 100644 --- a/src/commands/events.rs +++ b/src/commands/events.rs @@ -1,11 +1,9 @@ -use crate::utils; -use cgroups::common; use clap::Clap; -use std::{path::PathBuf, thread, time::Duration}; +use std::path::PathBuf; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; -use crate::container::{Container, ContainerStatus}; +use crate::commands::load_container; #[derive(Clap, Debug)] pub struct Events { @@ -22,42 +20,9 @@ pub struct Events { impl Events { pub fn exec(&self, root_path: PathBuf) -> Result<()> { - let container_dir = root_path.join(&self.container_id); - if !container_dir.exists() { - log::debug!("{:?}", container_dir); - bail!("{} doesn't exist.", self.container_id) - } - - let container = Container::load(container_dir)?.refresh_status()?; - if !container.state.status.eq(&ContainerStatus::Running) { - bail!("{} is not in running state", self.container_id); - } - - let cgroups_path = utils::get_cgroup_path( - &container - .spec()? - .linux - .context("no linux in spec")? - .cgroups_path, - &self.container_id, - ); - let use_systemd = container - .systemd() - .context("Could not determine cgroup manager")?; - - let cgroup_manager = common::create_cgroup_manager(cgroups_path, use_systemd)?; - match self.stats { - true => { - let stats = cgroup_manager.stats()?; - println!("{}", serde_json::to_string_pretty(&stats)?); - } - false => loop { - let stats = cgroup_manager.stats()?; - println!("{}", serde_json::to_string_pretty(&stats)?); - thread::sleep(Duration::from_secs(self.interval as u64)); - }, - } - - Ok(()) + let mut container = load_container(root_path, &self.container_id)?; + container + .events(self.interval, self.stats) + .with_context(|| format!("failed to get events from container {}", self.container_id)) } } diff --git a/src/commands/kill.rs b/src/commands/kill.rs index 3dd88a420..ff91b4246 100644 --- a/src/commands/kill.rs +++ b/src/commands/kill.rs @@ -1,14 +1,10 @@ //! Contains functionality of kill container command -use std::{fs, path::PathBuf}; +use std::path::PathBuf; -use anyhow::{bail, Result}; +use anyhow::{Context, Result}; use clap::Clap; -use nix::sys::signal as nix_signal; -use crate::{ - container::{Container, ContainerStatus}, - signal::ToSignal, -}; +use crate::{commands::load_container, signal::ToSignal}; #[derive(Clap, Debug)] pub struct Kill { @@ -19,30 +15,11 @@ pub struct Kill { impl Kill { pub fn exec(&self, root_path: PathBuf) -> Result<()> { - // resolves relative paths, symbolic links etc. and get complete path - let root_path = fs::canonicalize(root_path)?; - // state of container is stored in a directory named as container id inside - // root directory given in commandline options - let container_root = root_path.join(&self.container_id); - if !container_root.exists() { - bail!("{} doesn't exist.", self.container_id) - } - - // load container state from json file, and check status of the container - // it might be possible that kill is invoked on a already stopped container etc. - let container = Container::load(container_root)?.refresh_status()?; - if container.can_kill() { - let sig = self.signal.to_signal()?; - log::debug!("kill signal {} to {}", sig, container.pid().unwrap()); - nix_signal::kill(container.pid().unwrap(), sig)?; - container.update_status(ContainerStatus::Stopped).save()?; - std::process::exit(0) - } else { - bail!( - "{} could not be killed because it was {:?}", - container.id(), - container.status() - ) - } + let mut container = load_container(root_path, &self.container_id)?; + let signal = self + .signal + .to_signal() + .with_context(|| format!("signal {} is unknown", self.signal))?; + container.kill(signal) } } diff --git a/src/commands/list.rs b/src/commands/list.rs index a6294575e..bbc4df732 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -30,7 +30,7 @@ impl List { continue; } - let container = Container::load(container_dir)?.refresh_status()?; + let container = Container::load(container_dir)?; let pid = if let Some(pid) = container.pid() { pid.to_string() } else { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index bcabba00c..eb545a900 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,8 @@ +use anyhow::{bail, Context, Result}; +use std::{fs, path::Path}; + +use crate::container::Container; + pub mod create; pub mod delete; pub mod events; @@ -12,3 +17,17 @@ pub mod run; pub mod spec_json; pub mod start; pub mod state; + +fn load_container>(root_path: P, container_id: &str) -> Result { + // resolves relative paths, symbolic links etc. and get complete path + let root_path = fs::canonicalize(&root_path) + .with_context(|| format!("failed to canonicalize {}", root_path.as_ref().display()))?; + // the state of the container is stored in a directory named after the container id + let container_root = root_path.join(container_id); + if !container_root.exists() { + bail!("{} does not exist.", container_id) + } + + Container::load(container_root) + .with_context(|| format!("could not load state for container {}", container_id)) +} diff --git a/src/commands/pause.rs b/src/commands/pause.rs index 51d1d48a9..464ee35ca 100644 --- a/src/commands/pause.rs +++ b/src/commands/pause.rs @@ -1,16 +1,10 @@ //! Contains functionality of pause container command -use std::fs::canonicalize; +use crate::commands::load_container; use std::path::PathBuf; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use clap::Clap; -use crate::container::Container; -use crate::container::ContainerStatus; -use crate::utils; -use cgroups; -use cgroups::common::FreezerState; - /// Structure to implement pause command #[derive(Clap, Debug)] pub struct Pause { @@ -24,41 +18,11 @@ pub struct Pause { // https://man7.org/linux/man-pages/man7/cgroups.7.html // https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt impl Pause { - /// Suspend the running container - pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> { + pub fn exec(&self, root_path: PathBuf) -> Result<()> { log::debug!("start pausing container {}", self.container_id); - let root_path = canonicalize(root_path)?; - let container_root = root_path.join(&self.container_id); - if !container_root.exists() { - bail!("{} doesn't exist.", self.container_id) - } - - // populate data in a container structure from its file - let container = Container::load(container_root)?.refresh_status()?; - // check if a container is pauseable : - // for example, a stopped container cannot be paused - if !container.can_pause() { - bail!( - "{} could not be paused because it was {:?}", - self.container_id, - container.status() - ); - } - - let spec = container.spec()?; - let cgroups_path = utils::get_cgroup_path( - &spec.linux.context("no linux in spec")?.cgroups_path, - &self.container_id, - ); - // create cgroup manager structure from the config at the path - let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?; - // freeze the container - cmanager.freeze(FreezerState::Frozen)?; - - log::debug!("saving paused status"); - container.update_status(ContainerStatus::Paused).save()?; - - log::debug!("container {} paused", self.container_id); - Ok(()) + let mut container = load_container(root_path, &self.container_id)?; + container + .pause() + .with_context(|| format!("failed to pause container {}", self.container_id)) } } diff --git a/src/commands/ps.rs b/src/commands/ps.rs index 210553da0..5dcc7c60f 100644 --- a/src/commands/ps.rs +++ b/src/commands/ps.rs @@ -22,7 +22,7 @@ impl Ps { if !container_root.exists() { bail!("{} doesn't exist.", self.container_id) } - let container = Container::load(container_root)?.refresh_status()?; + let container = Container::load(container_root)?; if container.root.exists() { let config_absolute_path = container.root.join("config.json"); log::debug!("load spec from {:?}", config_absolute_path); diff --git a/src/commands/resume.rs b/src/commands/resume.rs index 4a3fa7186..b872840bc 100644 --- a/src/commands/resume.rs +++ b/src/commands/resume.rs @@ -1,15 +1,10 @@ //! Contains functionality of resume container command -use std::fs::canonicalize; use std::path::PathBuf; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use clap::Clap; -use crate::container::Container; -use crate::container::ContainerStatus; -use crate::utils; -use cgroups; -use cgroups::common::FreezerState; +use crate::commands::load_container; /// Structure to implement resume command #[derive(Clap, Debug)] @@ -24,39 +19,11 @@ pub struct Resume { // https://man7.org/linux/man-pages/man7/cgroups.7.html // https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt impl Resume { - pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> { + pub fn exec(&self, root_path: PathBuf) -> Result<()> { log::debug!("start resuming container {}", self.container_id); - let root_path = canonicalize(root_path)?; - let container_root = root_path.join(&self.container_id); - if !container_root.exists() { - bail!("{} doesn't exist.", self.container_id) - } - - let container = Container::load(container_root)?.refresh_status()?; - // check if container can be resumed : - // for example, a running process cannot be resumed - if !container.can_resume() { - bail!( - "{} could not be resumed because it was {:?}", - self.container_id, - container.status() - ); - } - - let spec = container.spec()?; - let cgroups_path = utils::get_cgroup_path( - &spec.linux.context("no linux in spec")?.cgroups_path, - &self.container_id, - ); - // create cgroup manager structure from the config at the path - let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?; - // resume the frozen container - cmanager.freeze(FreezerState::Thawed)?; - - log::debug!("saving running status"); - container.update_status(ContainerStatus::Running).save()?; - - log::debug!("container {} resumed", self.container_id); - Ok(()) + let mut container = load_container(root_path, &self.container_id)?; + container + .resume() + .with_context(|| format!("failed to resume container {}", self.container_id)) } } diff --git a/src/commands/run.rs b/src/commands/run.rs index 17bb54946..683e6f8fa 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use crate::commands::create::Create; -use crate::commands::start::Start; -use anyhow::Result; +use crate::container::builder::ContainerBuilder; +use crate::syscall::syscall::create_syscall; +use anyhow::{Context, Result}; use clap::Clap; /// Create and start a container. /// a shortcut for create followed by start. @@ -28,17 +28,18 @@ pub struct Run { impl Run { pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> { - Create::new( - self.container_id.clone(), - self.pid_file.clone(), - self.bundle.clone(), - self.console_socket.clone(), - self.preserve_fds, - ) - .exec(root_path.clone(), systemd_cgroup)?; + let syscall = create_syscall(); + let mut container = ContainerBuilder::new(self.container_id.clone(), syscall.as_ref()) + .with_pid_file(self.pid_file.as_ref()) + .with_console_socket(self.console_socket.as_ref()) + .with_root_path(root_path) + .with_preserved_fds(self.preserve_fds) + .as_init(&self.bundle) + .with_systemd(systemd_cgroup) + .build()?; - Start::new(self.container_id.clone()).exec(root_path)?; - - Ok(()) + container + .start() + .with_context(|| format!("failed to start container {}", self.container_id)) } } diff --git a/src/commands/start.rs b/src/commands/start.rs index 75f393fa9..1da20729b 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -2,13 +2,10 @@ use std::path::PathBuf; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use clap::Clap; -use nix::unistd; -use crate::container::{Container, ContainerStatus}; -use crate::hooks; -use crate::notify_socket::{NotifySocket, NOTIFY_FILE}; +use crate::commands::load_container; #[derive(Clap, Debug)] pub struct Start { @@ -17,50 +14,10 @@ pub struct Start { } impl Start { - pub fn new(container_id: String) -> Self { - Self { container_id } - } - pub fn exec(&self, root_path: PathBuf) -> Result<()> { - let container_root = root_path.join(&self.container_id); - if !container_root.exists() { - bail!("{} doesn't exist.", self.container_id) - } - let container = Container::load(container_root)?.refresh_status()?; - if !container.can_start() { - let err_msg = format!( - "{} could not be started because it was {:?}", - container.id(), - container.status() - ); - log::error!("{}", err_msg); - bail!(err_msg); - } - - let spec_path = container.root.join("config.json"); - let spec = oci_spec::runtime::Spec::load(spec_path).context("failed to load spec")?; - if let Some(hooks) = spec.hooks.as_ref() { - // While prestart is marked as deprecated in the OCI spec, the docker and integration test still - // uses it. - #[allow(deprecated)] - hooks::run_hooks(hooks.prestart.as_ref(), Some(&container)) - .with_context(|| "Failed to run pre start hooks")?; - } - - unistd::chdir(container.root.as_os_str())?; - - let mut notify_socket = NotifySocket::new(&container.root.join(NOTIFY_FILE)); - notify_socket.notify_container_start()?; - let container = container.update_status(ContainerStatus::Running); - container.save()?; - - // Run post start hooks. It runs after the container process is started. - // It is called in the Runtime Namespace. - if let Some(hooks) = spec.hooks.as_ref() { - hooks::run_hooks(hooks.poststart.as_ref(), Some(&container)) - .with_context(|| "Failed to run post start hooks")?; - } - - Ok(()) + let mut container = load_container(root_path, &self.container_id)?; + container + .start() + .with_context(|| format!("failed to start container {}", self.container_id)) } } diff --git a/src/commands/state.rs b/src/commands/state.rs index 278e8598f..55fd4cf74 100644 --- a/src/commands/state.rs +++ b/src/commands/state.rs @@ -16,7 +16,7 @@ impl State { pub fn exec(&self, root_path: PathBuf) -> Result<()> { let root_path = fs::canonicalize(root_path)?; let container_root = root_path.join(&self.container_id); - let container = Container::load(container_root)?.refresh_status()?; + let container = Container::load(container_root)?; println!("{}", serde_json::to_string_pretty(&container.state)?); std::process::exit(0); } diff --git a/src/container/builder_impl.rs b/src/container/builder_impl.rs index a1ea85553..9e16c9b6c 100644 --- a/src/container/builder_impl.rs +++ b/src/container/builder_impl.rs @@ -175,10 +175,10 @@ impl<'a> ContainerBuilderImpl<'a> { fs::write(&pid_file, format!("{}", init_pid)).context("Failed to write pid file")?; } - if let Some(container) = &self.container { + if let Some(container) = &mut self.container { // update status and pid of the container process container - .update_status(ContainerStatus::Created) + .set_status(ContainerStatus::Created) .set_creator(nix::unistd::geteuid().as_raw()) .set_pid(init_pid.as_raw()) .save() diff --git a/src/container/container.rs b/src/container/container.rs index afcf76e1a..0a13febdc 100644 --- a/src/container/container.rs +++ b/src/container/container.rs @@ -53,49 +53,6 @@ impl Container { self.state.id.as_str() } - pub fn status(&self) -> ContainerStatus { - self.state.status - } - - pub fn refresh_status(&mut self) -> Result { - let new_status = match self.pid() { - Some(pid) => { - // Note that Process::new does not spawn a new process - // but instead creates a new Process structure, and fill - // it with information about the process with given pid - if let Ok(proc) = Process::new(pid.as_raw()) { - use procfs::process::ProcState; - match proc.stat.state().unwrap() { - ProcState::Zombie | ProcState::Dead => ContainerStatus::Stopped, - _ => match self.status() { - ContainerStatus::Creating - | ContainerStatus::Created - | ContainerStatus::Paused => self.status(), - _ => ContainerStatus::Running, - }, - } - } else { - ContainerStatus::Stopped - } - } - None => ContainerStatus::Stopped, - }; - Ok(self.update_status(new_status)) - } - - pub fn refresh_state(&self) -> Result { - let state = State::load(&self.root)?; - Ok(Self { - state, - root: self.root.clone(), - }) - } - - pub fn save(&self) -> Result<()> { - log::debug!("Save container status: {:?} in {:?}", self, self.root); - self.state.save(&self.root) - } - pub fn can_start(&self) -> bool { self.state.status.can_start() } @@ -120,29 +77,28 @@ impl Container { self.state.status.can_resume() } + pub fn bundle(&self) -> &PathBuf { + &self.state.bundle + } + + pub fn set_annotations(&mut self, annotations: Option>) -> &mut Self { + self.state.annotations = annotations; + self + } + pub fn pid(&self) -> Option { self.state.pid.map(Pid::from_raw) } - pub fn set_pid(&self, pid: i32) -> Self { - let mut new_state = self.state.clone(); - new_state.pid = Some(pid); - - Self { - state: new_state, - root: self.root.clone(), - } + pub fn set_pid(&mut self, pid: i32) -> &mut Self { + self.state.pid = Some(pid); + self } pub fn created(&self) -> Option> { self.state.created } - pub fn set_creator(mut self, uid: u32) -> Self { - self.state.creator = Some(uid); - self - } - pub fn creator(&self) -> Option { if let Some(uid) = self.state.creator { let command = create_syscall(); @@ -155,46 +111,84 @@ impl Container { None } - pub fn bundle(&self) -> &PathBuf { - &self.state.bundle + pub fn set_creator(&mut self, uid: u32) -> &mut Self { + self.state.creator = Some(uid); + self } - pub fn set_systemd(mut self, should_use: bool) -> Self { - self.state.use_systemd = Some(should_use); - self + pub fn systemd(&self) -> Option { + self.state.use_systemd } - pub fn set_annotations(mut self, annotations: Option>) -> Self { - self.state.annotations = annotations; + pub fn set_systemd(&mut self, should_use: bool) -> &mut Self { + self.state.use_systemd = Some(should_use); self } - pub fn systemd(&self) -> Option { - self.state.use_systemd + pub fn status(&self) -> ContainerStatus { + self.state.status } - pub fn update_status(&self, status: ContainerStatus) -> Self { + pub fn set_status(&mut self, status: ContainerStatus) -> &mut Self { let created = match (status, self.state.created) { (ContainerStatus::Created, None) => Some(Utc::now()), _ => self.state.created, }; - let mut new_state = self.state.clone(); - new_state.created = created; - new_state.status = status; + self.state.created = created; + self.state.status = status; - Self { - state: new_state, - root: self.root.clone(), - } + self + } + + pub fn refresh_status(&mut self) -> Result<()> { + let new_status = match self.pid() { + Some(pid) => { + // Note that Process::new does not spawn a new process + // but instead creates a new Process structure, and fill + // it with information about the process with given pid + if let Ok(proc) = Process::new(pid.as_raw()) { + use procfs::process::ProcState; + match proc.stat.state().unwrap() { + ProcState::Zombie | ProcState::Dead => ContainerStatus::Stopped, + _ => match self.status() { + ContainerStatus::Creating + | ContainerStatus::Created + | ContainerStatus::Paused => self.status(), + _ => ContainerStatus::Running, + }, + } + } else { + ContainerStatus::Stopped + } + } + None => ContainerStatus::Stopped, + }; + + self.set_status(new_status); + Ok(()) + } + + pub fn refresh_state(&mut self) -> Result<&mut Self> { + let state = State::load(&self.root)?; + self.state = state; + + Ok(self) } pub fn load(container_root: PathBuf) -> Result { let state = State::load(&container_root)?; - Ok(Self { + let mut container = Self { state, root: container_root, - }) + }; + container.refresh_status()?; + Ok(container) + } + + pub fn save(&self) -> Result<()> { + log::debug!("Save container status: {:?} in {:?}", self, self.root); + self.state.save(&self.root) } pub fn spec(&self) -> Result { @@ -213,8 +207,9 @@ mod tests { #[test] fn test_set_id() -> Result<()> { let dir = env::temp_dir(); - let container = Container::new("container_id", ContainerStatus::Created, None, &dir, &dir)?; - let container = container.set_pid(1); + let mut container = + Container::new("container_id", ContainerStatus::Created, None, &dir, &dir)?; + container.set_pid(1); assert_eq!(container.pid(), Some(Pid::from_raw(1))); Ok(()) } diff --git a/src/container/container_delete.rs b/src/container/container_delete.rs new file mode 100644 index 000000000..217a483aa --- /dev/null +++ b/src/container/container_delete.rs @@ -0,0 +1,81 @@ +use super::{Container, ContainerStatus}; +use crate::hooks; +use crate::utils; +use anyhow::{bail, Context, Result}; +use cgroups; +use nix::sys::signal; +use std::fs; + +impl Container { + /// Deletes the container + /// + /// # Example + /// + /// ```no_run + /// use youki::container::builder::ContainerBuilder; + /// use youki::syscall::syscall::create_syscall;; + /// + /// # fn main() -> anyhow::Result<()> { + /// let mut container = ContainerBuilder::new("74f1a4cb3801".to_owned(), create_syscall().as_ref()) + /// .as_init("/var/run/docker/bundle") + /// .build()?; + /// + /// container.delete(true)?; + /// # Ok(()) + /// # } + /// ``` + pub fn delete(&mut self, force: bool) -> Result<()> { + self.refresh_status() + .context("failed to refresh container status")?; + if self.can_kill() && force { + let sig = signal::Signal::SIGKILL; + log::debug!("kill signal {} to {}", sig, self.pid().unwrap()); + signal::kill(self.pid().unwrap(), sig)?; + self.set_status(ContainerStatus::Stopped).save()?; + } + log::debug!("container status: {:?}", self.status()); + if self.can_delete() { + if self.root.exists() { + let spec = self.spec().with_context(|| { + format!("failed to load runtime spec for container {}", self.id()) + })?; + log::debug!("spec: {:?}", spec); + + // remove the directory storing container state + log::debug!("remove dir {:?}", self.root); + fs::remove_dir_all(&self.root).with_context(|| { + format!("failed to remove container dir {}", self.root.display()) + })?; + + let cgroups_path = utils::get_cgroup_path( + &spec.linux.context("no linux in spec")?.cgroups_path, + self.id(), + ); + + // remove the cgroup created for the container + // check https://man7.org/linux/man-pages/man7/cgroups.7.html + // creating and removing cgroups section for more information on cgroups + let use_systemd = self + .systemd() + .context("container state does not contain cgroup manager")?; + let cmanager = cgroups::common::create_cgroup_manager(&cgroups_path, use_systemd) + .context("failed to create cgroup manager")?; + cmanager.remove().with_context(|| { + format!("failed to remove cgroup {}", cgroups_path.display()) + })?; + + if let Some(hooks) = spec.hooks.as_ref() { + hooks::run_hooks(hooks.poststop.as_ref(), Some(self)) + .with_context(|| "failed to run post stop hooks")?; + } + } + std::process::exit(0) + } else { + bail!( + "{} could not be deleted because it was {:?}", + self.id(), + self.status() + ) + } + } +} diff --git a/src/container/container_events.rs b/src/container/container_events.rs new file mode 100644 index 000000000..9933afc2b --- /dev/null +++ b/src/container/container_events.rs @@ -0,0 +1,56 @@ +use std::{thread, time::Duration}; + +use crate::utils; + +use super::{Container, ContainerStatus}; +use anyhow::{bail, Context, Result}; + +impl Container { + /// Displays container events + /// + /// # Example + /// + /// ```no_run + /// use youki::container::builder::ContainerBuilder; + /// use youki::syscall::syscall::create_syscall;; + /// + /// # fn main() -> anyhow::Result<()> { + /// let mut container = ContainerBuilder::new("74f1a4cb3801".to_owned(), create_syscall().as_ref()) + /// .as_init("/var/run/docker/bundle") + /// .build()?; + /// + /// container.events(5000, false)?; + /// # Ok(()) + /// # } + /// ``` + pub fn events(&mut self, interval: u32, stats: bool) -> Result<()> { + self.refresh_status() + .context("failed to refresh container status")?; + if !self.state.status.eq(&ContainerStatus::Running) { + bail!("{} is not in running state", self.id()); + } + + let cgroups_path = utils::get_cgroup_path( + &self.spec()?.linux.context("no linux in spec")?.cgroups_path, + self.id(), + ); + let use_systemd = self + .systemd() + .context("Could not determine cgroup manager")?; + + let cgroup_manager = cgroups::common::create_cgroup_manager(cgroups_path, use_systemd)?; + match stats { + true => { + let stats = cgroup_manager.stats()?; + println!("{}", serde_json::to_string_pretty(&stats)?); + } + false => loop { + let stats = cgroup_manager.stats()?; + println!("{}", serde_json::to_string_pretty(&stats)?); + thread::sleep(Duration::from_secs(interval as u64)); + }, + } + + Ok(()) + } +} diff --git a/src/container/container_kill.rs b/src/container/container_kill.rs new file mode 100644 index 000000000..9d88bdf58 --- /dev/null +++ b/src/container/container_kill.rs @@ -0,0 +1,40 @@ +use super::{Container, ContainerStatus}; +use anyhow::{bail, Context, Result}; +use nix::sys::signal::{self, Signal}; + +impl Container { + /// Sends the specified signal to the container init process + /// + /// # Example + /// + /// ```no_run + /// use youki::container::builder::ContainerBuilder; + /// use youki::syscall::syscall::create_syscall;; + /// use nix::sys::signal::Signal; + /// + /// # fn main() -> anyhow::Result<()> { + /// let mut container = ContainerBuilder::new("74f1a4cb3801".to_owned(), create_syscall().as_ref()) + /// .as_init("/var/run/docker/bundle") + /// .build()?; + /// + /// container.kill(Signal::SIGKILL)?; + /// # Ok(()) + /// # } + /// ``` + pub fn kill(&mut self, signal: Signal) -> Result<()> { + self.refresh_status() + .context("failed to refresh container status")?; + if self.can_kill() { + log::debug!("kill signal {} to {}", signal, self.pid().unwrap()); + signal::kill(self.pid().unwrap(), signal)?; + self.set_status(ContainerStatus::Stopped).save()?; + std::process::exit(0) + } else { + bail!( + "{} could not be killed because it was {:?}", + self.id(), + self.status() + ) + } + } +} diff --git a/src/container/container_pause.rs b/src/container/container_pause.rs new file mode 100644 index 000000000..74b0439f1 --- /dev/null +++ b/src/container/container_pause.rs @@ -0,0 +1,55 @@ +use crate::utils; + +use super::{Container, ContainerStatus}; +use anyhow::{bail, Context, Result}; +use cgroups::common::FreezerState; + +impl Container { + /// Suspends all processes within the container + /// + /// # Example + /// + /// ```no_run + /// use youki::container::builder::ContainerBuilder; + /// use youki::syscall::syscall::create_syscall;; + /// + /// # fn main() -> anyhow::Result<()> { + /// let mut container = ContainerBuilder::new("74f1a4cb3801".to_owned(), create_syscall().as_ref()) + /// .as_init("/var/run/docker/bundle") + /// .build()?; + /// + /// container.pause()?; + /// # Ok(()) + /// # } + /// ``` + pub fn pause(&mut self) -> Result<()> { + self.refresh_status() + .context("failed to refresh container status")?; + + if !self.can_pause() { + bail!( + "{} could not be paused because it was {:?}", + self.id(), + self.status() + ); + } + + let spec = self.spec()?; + let cgroups_path = utils::get_cgroup_path( + &spec.linux.context("no linux in spec")?.cgroups_path, + self.id(), + ); + + let use_systemd = self + .systemd() + .context("container state does not contain cgroup manager")?; + let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, use_systemd)?; + cmanager.freeze(FreezerState::Frozen)?; + + log::debug!("saving paused status"); + self.set_status(ContainerStatus::Paused).save()?; + + log::debug!("container {} paused", self.id()); + Ok(()) + } +} diff --git a/src/container/container_resume.rs b/src/container/container_resume.rs new file mode 100644 index 000000000..8d7570d91 --- /dev/null +++ b/src/container/container_resume.rs @@ -0,0 +1,59 @@ +use crate::utils; + +use super::{Container, ContainerStatus}; + +use anyhow::{bail, Context, Result}; +use cgroups::common::FreezerState; + +impl Container { + /// Resumes all processes within the container + /// + /// # Example + /// + /// ```no_run + /// use youki::container::builder::ContainerBuilder; + /// use youki::syscall::syscall::create_syscall;; + /// + /// # fn main() -> anyhow::Result<()> { + /// let mut container = ContainerBuilder::new("74f1a4cb3801".to_owned(), create_syscall().as_ref()) + /// .as_init("/var/run/docker/bundle") + /// .build()?; + /// + /// container.resume()?; + /// # Ok(()) + /// # } + /// ``` + pub fn resume(&mut self) -> Result<()> { + self.refresh_status() + .context("failed to refresh container status")?; + // check if container can be resumed : + // for example, a running process cannot be resumed + if !self.can_resume() { + bail!( + "{} could not be resumed because it was {:?}", + self.id(), + self.status() + ); + } + + let spec = self.spec()?; + let cgroups_path = utils::get_cgroup_path( + &spec.linux.context("no linux in spec")?.cgroups_path, + self.id(), + ); + + // create cgroup manager structure from the config at the path + let use_systemd = self + .systemd() + .context("container state does not contain cgroup manager")?; + let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, use_systemd)?; + // resume the frozen container + cmanager.freeze(FreezerState::Thawed)?; + + log::debug!("saving running status"); + self.set_status(ContainerStatus::Running).save()?; + + log::debug!("container {} resumed", self.id()); + Ok(()) + } +} diff --git a/src/container/container_start.rs b/src/container/container_start.rs new file mode 100644 index 000000000..e1074c62d --- /dev/null +++ b/src/container/container_start.rs @@ -0,0 +1,70 @@ +use crate::{ + hooks, + notify_socket::{NotifySocket, NOTIFY_FILE}, +}; + +use super::{Container, ContainerStatus}; +use anyhow::{bail, Context, Result}; +use nix::unistd; + +impl Container { + /// Starts a previously created container + /// + /// # Example + /// + /// ```no_run + /// use youki::container::builder::ContainerBuilder; + /// use youki::syscall::syscall::create_syscall;; + /// + /// # fn main() -> anyhow::Result<()> { + /// let mut container = ContainerBuilder::new("74f1a4cb3801".to_owned(), create_syscall().as_ref()) + /// .as_init("/var/run/docker/bundle") + /// .build()?; + /// + /// container.start(); + /// # Ok(()) + /// # } + /// ``` + pub fn start(&mut self) -> Result<()> { + self.refresh_status() + .context("failed to refresh container status")?; + + if !self.can_start() { + let err_msg = format!( + "{} could not be started because it was {:?}", + self.id(), + self.status() + ); + log::error!("{}", err_msg); + bail!(err_msg); + } + + let spec = self + .spec() + .with_context(|| format!("failed to load runtime spec for container {}", self.id()))?; + if let Some(hooks) = spec.hooks.as_ref() { + // While prestart is marked as deprecated in the OCI spec, the docker and integration test still + // uses it. + #[allow(deprecated)] + hooks::run_hooks(hooks.prestart.as_ref(), Some(self)) + .with_context(|| "failed to run pre start hooks")?; + } + + unistd::chdir(self.root.as_os_str())?; + + let mut notify_socket = NotifySocket::new(&self.root.join(NOTIFY_FILE)); + notify_socket.notify_container_start()?; + self.set_status(ContainerStatus::Running) + .save() + .with_context(|| format!("could not save state for container {}", self.id()))?; + + // Run post start hooks. It runs after the container process is started. + // It is called in the runtime namespace. + if let Some(hooks) = spec.hooks.as_ref() { + hooks::run_hooks(hooks.poststart.as_ref(), Some(self)) + .with_context(|| "failed to run post start hooks")?; + } + + Ok(()) + } +} diff --git a/src/container/init_builder.rs b/src/container/init_builder.rs index acb87ca37..5f927a461 100644 --- a/src/container/init_builder.rs +++ b/src/container/init_builder.rs @@ -38,13 +38,13 @@ impl<'a> InitContainerBuilder<'a> { } /// Creates a new container - pub fn build(self) -> Result<()> { + pub fn build(self) -> Result { let spec = self.load_spec()?; let container_dir = self.create_container_dir()?; self.save_spec(&spec, &container_dir)?; - let container_state = self - .create_container_state(&container_dir)? + let mut container = self.create_container_state(&container_dir)?; + container .set_systemd(self.use_systemd) .set_annotations(spec.annotations.clone()); @@ -77,12 +77,14 @@ impl<'a> InitContainerBuilder<'a> { rootfs, rootless, notify_path, - container: Some(container_state), + container: Some(container.clone()), preserve_fds: self.base.preserve_fds, }; builder_impl.create()?; - Ok(()) + container.refresh_state()?; + + Ok(container) } fn create_container_dir(&self) -> Result { diff --git a/src/container/mod.rs b/src/container/mod.rs index 04e21de25..2290d1088 100644 --- a/src/container/mod.rs +++ b/src/container/mod.rs @@ -8,6 +8,12 @@ pub mod builder; mod builder_impl; #[allow(clippy::module_inception)] mod container; +mod container_delete; +mod container_events; +mod container_kill; +mod container_pause; +mod container_resume; +mod container_start; pub mod init_builder; pub mod state; pub mod tenant_builder; diff --git a/src/container/state.rs b/src/container/state.rs index 4d2d1f527..3d64f8eb7 100644 --- a/src/container/state.rs +++ b/src/container/state.rs @@ -129,8 +129,8 @@ impl State { .append(false) .create(true) .truncate(true) - .open(state_file_path) - .expect("Unable to open"); + .open(&state_file_path) + .with_context(|| format!("failed to open {}", state_file_path.display()))?; serde_json::to_writer(&file, self)?; Ok(()) } diff --git a/src/container/tenant_builder.rs b/src/container/tenant_builder.rs index 42059d66a..1cfff85d6 100644 --- a/src/container/tenant_builder.rs +++ b/src/container/tenant_builder.rs @@ -145,7 +145,7 @@ impl<'a> TenantContainerBuilder<'a> { } fn load_container_state(&self, container_dir: PathBuf) -> Result { - let container = Container::load(container_dir)?.refresh_status()?; + let container = Container::load(container_dir)?; if !container.can_exec() { bail!( "Cannot exec as container is in state {}", diff --git a/src/main.rs b/src/main.rs index 64fdb47a2..b6f8ded93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,13 +101,13 @@ fn main() -> Result<()> { SubCommand::Run(run) => run.exec(root_path, systemd_cgroup), SubCommand::Exec(exec) => exec.exec(root_path), SubCommand::Kill(kill) => kill.exec(root_path), - SubCommand::Delete(delete) => delete.exec(root_path, systemd_cgroup), + SubCommand::Delete(delete) => delete.exec(root_path), SubCommand::State(state) => state.exec(root_path), SubCommand::Info(info) => info.exec(), SubCommand::List(list) => list.exec(root_path), SubCommand::Spec(spec) => spec.exec(), - SubCommand::Pause(pause) => pause.exec(root_path, systemd_cgroup), - SubCommand::Resume(resume) => resume.exec(root_path, systemd_cgroup), + SubCommand::Pause(pause) => pause.exec(root_path), + SubCommand::Resume(resume) => resume.exec(root_path), SubCommand::Events(events) => events.exec(root_path), SubCommand::Ps(ps) => ps.exec(root_path), }