diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 68e805cd2..69650e881 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@v2 - run: rustup component add clippy + - run: sudo apt-get -y update + - run: sudo apt-get install -y pkg-config libsystemd-dev libdbus-glib-1-dev - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -30,6 +32,8 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: stable + - run: sudo apt-get -y update + - run: sudo apt-get install -y pkg-config libsystemd-dev libdbus-glib-1-dev - run: cargo install cargo-when - name: Build run: ./build.sh diff --git a/Cargo.lock b/Cargo.lock index a9a81ed7c..6c5b12f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "build-env" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" + [[package]] name = "byteorder" version = "1.4.3" @@ -69,6 +75,12 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -126,7 +138,27 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", +] + +[[package]] +name = "cstr-argument" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bd4e8067c20c7c3a4dea759ef91d4b18418ddb5bd8837ef6e2f2f93ca7ccbb" +dependencies = [ + "cfg-if 0.1.10", + "memchr", +] + +[[package]] +name = "dbus" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f597e08dfa79b593f23bbfc7840b23b2c5aa2e3a98d8e68b67b5b9ff800dc0db" +dependencies = [ + "libc", + "libdbus-sys", ] [[package]] @@ -166,12 +198,39 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crc32fast", "libc", "miniz_oxide", ] +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" + [[package]] name = "futures" version = "0.3.15" @@ -279,7 +338,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -330,7 +389,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -351,6 +410,26 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" +[[package]] +name = "libdbus-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libsystemd-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e03fd580bcecda68dcdcd5297085ade6a3dc552cd8b030d2b94a9b089ef7ab8" +dependencies = [ + "build-env", + "libc", + "pkg-config", +] + [[package]] name = "lock_api" version = "0.4.4" @@ -366,7 +445,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -424,7 +503,7 @@ checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", ] @@ -436,7 +515,7 @@ checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "memoffset", ] @@ -520,7 +599,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", @@ -540,6 +619,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + [[package]] name = "prctl" version = "1.0.0" @@ -768,6 +853,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "systemd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f722cabda922e471742300045f56dbaa53fafbb4520fca304e51258019bfe91d" +dependencies = [ + "cstr-argument", + "foreign-types", + "libc", + "libsystemd-sys", + "log", + "memchr", + "utf8-cstr", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -834,6 +934,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "utf8-cstr" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628" + [[package]] name = "vec_map" version = "0.8.2" @@ -891,6 +997,7 @@ dependencies = [ "caps", "chrono", "clap", + "dbus", "futures", "libc", "log", @@ -905,4 +1012,5 @@ dependencies = [ "serde", "serde_json", "serial_test", + "systemd", ] diff --git a/Cargo.toml b/Cargo.toml index cfec9e42b..afba5c345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ once_cell = "1.6.0" futures = { version = "0.3", features = ["thread-pool"] } regex = "1.5" oci_spec = { version = "0.1.0", path = "./oci_spec" } +systemd = { version = "0.8", default-features = false } +dbus = "0.9.2" [dev-dependencies] oci_spec = { version = "0.1.0", path = "./oci_spec", features = ["proptests"] } diff --git a/README.md b/README.md index 374219f7a..3efcd974f 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,29 @@ For other platforms, please use the devcontainer that we prepared. - Rust(See [here](https://www.rust-lang.org/tools/install)) - Docker(See [here](https://docs.docker.com/engine/install)) -## Building +## Dependencies +```sh +$ cargo install cargo-when +``` + +### Debian, Ubuntu and related distributions +```sh +$ sudo dnf install \ + pkg-config \ + libsystemd-dev \ + libdbus-glib-1-dev +``` + +### Fedora, Centos, RHEL and related distributions ```sh -$ cargo install cargo-when # installs prerequisite for building youki +$ sudo dnf install \ + pkg-config \ + systemd-dev \ + dbus-devel ``` +## Build ```sh $ git clone git@github.com:containers/youki.git $ cd youki diff --git a/src/cgroups/common.rs b/src/cgroups/common.rs index 56f3b1a57..26f626805 100644 --- a/src/cgroups/common.rs +++ b/src/cgroups/common.rs @@ -6,11 +6,11 @@ use std::{ path::{Path, PathBuf}, }; - use anyhow::{bail, Context, Result}; use nix::unistd::Pid; use oci_spec::LinuxResources; use procfs::process::Process; +use systemd::daemon::booted; use crate::cgroups::v1; use crate::cgroups::v2; @@ -91,7 +91,10 @@ pub fn get_supported_cgroup_fs() -> Result> { Ok(cgroups) } -pub fn create_cgroup_manager>(cgroup_path: P) -> Result> { +pub fn create_cgroup_manager>( + cgroup_path: P, + systemd_cgroup: bool, +) -> Result> { let cgroup_mount = Process::myself()? .mountinfo()? .into_iter() @@ -109,6 +112,16 @@ pub fn create_cgroup_manager>(cgroup_path: P) -> Result { log::info!("cgroup manager V2 will be used"); + if systemd_cgroup { + if !booted()? { + bail!("systemd cgroup flag passed, but systemd support for managing cgroups is not available"); + } + log::info!("systemd cgroup manager will be used"); + return Ok(Box::new(v2::SystemDCGroupManager::new( + cgroup2.mount_point, + cgroup_path.into(), + )?)); + } Ok(Box::new(v2::manager::Manager::new( cgroup2.mount_point, cgroup_path.into(), @@ -119,6 +132,16 @@ pub fn create_cgroup_manager>(cgroup_path: P) -> Result { log::info!("cgroup manager V2 will be used"); + if systemd_cgroup { + if !booted()? { + bail!("systemd cgroup flag passed, but systemd support for managing cgroups is not available"); + } + log::info!("systemd cgroup manager will be used"); + return Ok(Box::new(v2::SystemDCGroupManager::new( + cgroup2.mount_point, + cgroup_path.into(), + )?)); + } Ok(Box::new(v2::manager::Manager::new( cgroup2.mount_point, cgroup_path.into(), diff --git a/src/cgroups/test.rs b/src/cgroups/test.rs index ffad8b3ed..57ad71ef8 100644 --- a/src/cgroups/test.rs +++ b/src/cgroups/test.rs @@ -2,14 +2,13 @@ use anyhow::Result; use std::{ - io::Write, + io::Write, path::{Path, PathBuf}, }; use oci_spec::LinuxCpu; -use crate::utils::{create_temp_dir, TempDir}; - +use crate::utils::{create_temp_dir, TempDir}; pub fn setup(testname: &str, cgroup_file: &str) -> (TempDir, PathBuf) { let tmp = create_temp_dir(testname).expect("create temp directory for test"); diff --git a/src/cgroups/v1/freezer.rs b/src/cgroups/v1/freezer.rs index fbe02192b..5ea995e93 100644 --- a/src/cgroups/v1/freezer.rs +++ b/src/cgroups/v1/freezer.rs @@ -117,7 +117,7 @@ impl Freezer { mod tests { use super::*; use crate::cgroups::test::set_fixture; - use crate::utils::create_temp_dir; + use crate::utils::create_temp_dir; use oci_spec::FreezerState; #[test] diff --git a/src/cgroups/v1/hugetlb.rs b/src/cgroups/v1/hugetlb.rs index d36b68516..ad1a2604b 100644 --- a/src/cgroups/v1/hugetlb.rs +++ b/src/cgroups/v1/hugetlb.rs @@ -59,7 +59,7 @@ impl Hugetlb { mod tests { use super::*; use crate::cgroups::test::set_fixture; - use crate::utils::create_temp_dir; + use crate::utils::create_temp_dir; use oci_spec::LinuxHugepageLimit; use std::fs::read_to_string; diff --git a/src/cgroups/v1/memory.rs b/src/cgroups/v1/memory.rs index 8f4cb6b0c..f00a7f450 100644 --- a/src/cgroups/v1/memory.rs +++ b/src/cgroups/v1/memory.rs @@ -240,7 +240,7 @@ impl Memory { mod tests { use super::*; use crate::cgroups::test::set_fixture; - use crate::utils::create_temp_dir; + use crate::utils::create_temp_dir; use oci_spec::LinuxMemory; #[test] diff --git a/src/cgroups/v1/network_classifier.rs b/src/cgroups/v1/network_classifier.rs index d7bf1f4ee..88da25653 100644 --- a/src/cgroups/v1/network_classifier.rs +++ b/src/cgroups/v1/network_classifier.rs @@ -36,9 +36,9 @@ impl NetworkClassifier { #[cfg(test)] mod tests { + use super::*; use crate::cgroups::test::set_fixture; use crate::utils::create_temp_dir; - use super::*; #[test] fn test_apply_network_classifier() { diff --git a/src/cgroups/v1/pids.rs b/src/cgroups/v1/pids.rs index 577090778..e41153db1 100644 --- a/src/cgroups/v1/pids.rs +++ b/src/cgroups/v1/pids.rs @@ -46,9 +46,9 @@ impl Pids { #[cfg(test)] mod tests { + use super::*; use crate::cgroups::test::set_fixture; use crate::utils::create_temp_dir; - use super::*; use oci_spec::LinuxPids; #[test] diff --git a/src/cgroups/v2/mod.rs b/src/cgroups/v2/mod.rs index cea672f02..a3c10f481 100644 --- a/src/cgroups/v2/mod.rs +++ b/src/cgroups/v2/mod.rs @@ -7,4 +7,6 @@ mod io; pub mod manager; mod memory; mod pids; +pub mod systemd_manager; pub mod util; +pub use systemd_manager::SystemDCGroupManager; diff --git a/src/cgroups/v2/systemd_manager.rs b/src/cgroups/v2/systemd_manager.rs new file mode 100644 index 000000000..6e01e3050 --- /dev/null +++ b/src/cgroups/v2/systemd_manager.rs @@ -0,0 +1,304 @@ +use std::{ + fs::{self}, + os::unix::fs::PermissionsExt, +}; + +use anyhow::{anyhow, bail, Result}; +use nix::unistd::Pid; +use oci_spec::LinuxResources; +use std::path::{Path, PathBuf}; + +use super::{cpu::Cpu, cpuset::CpuSet, hugetlb::HugeTlb, io::Io, memory::Memory, pids::Pids}; +use crate::cgroups::common; +use crate::cgroups::common::CgroupManager; +use crate::cgroups::v2::controller::Controller; +use crate::cgroups::v2::controller_type::ControllerType; +use crate::utils::PathBufExt; + +const CGROUP_PROCS: &str = "cgroup.procs"; +const CGROUP_CONTROLLERS: &str = "cgroup.controllers"; +const CGROUP_SUBTREE_CONTROL: &str = "cgroup.subtree_control"; + +// v2 systemd only supports cpu, io, memory and pids. +const CONTROLLER_TYPES: &[ControllerType] = &[ + ControllerType::Cpu, + ControllerType::Io, + ControllerType::Memory, + ControllerType::Pids, +]; + +/// SystemDCGroupManager is a driver for managing cgroups via systemd. +pub struct SystemDCGroupManager { + root_path: PathBuf, + cgroups_path: CgroupsPath, +} + +/// Represents the systemd cgroups path: +/// It should be of the form [slice]:[scope_prefix]:[name]. +/// The slice is the "parent" and should be expanded properly, +/// see expand_slice below. +struct CgroupsPath { + parent: String, + scope: String, + name: String, +} + +impl SystemDCGroupManager { + pub fn new(root_path: PathBuf, cgroups_path: PathBuf) -> Result { + // cgroups path may never be empty as it is defaulted to `/youki` + // see 'get_cgroup_path' under utils.rs. + // if cgroups_path was provided it should be of the form [slice]:[scope_prefix]:[name], + // for example: "system.slice:docker:1234". + let mut parent = ""; + let scope; + let name; + if cgroups_path.starts_with("/youki") { + scope = "youki"; + name = cgroups_path + .strip_prefix("/youki/")? + .to_str() + .ok_or_else(|| anyhow!("Failed to parse cgroupsPath field."))?; + } else { + let parts = cgroups_path + .to_str() + .ok_or_else(|| anyhow!("Failed to parse cgroupsPath field."))? + .split(':') + .collect::>(); + parent = parts[0]; + scope = parts[1]; + name = parts[2]; + } + + // TODO: create the systemd unit using a dbus client. + + Ok(SystemDCGroupManager { + root_path, + cgroups_path: CgroupsPath { + parent: parent.to_owned(), + scope: scope.to_owned(), + name: name.to_owned(), + }, + }) + } + + /// get_unit_name returns the unit (scope) name from the path provided by the user + /// for example: foo:docker:bar returns in '/docker-bar.scope' + fn get_unit_name(&self) -> String { + // By default we create a scope unless specified explicitly. + if !self.cgroups_path.name.ends_with(".slice") { + return format!( + "{}-{}.scope", + self.cgroups_path.scope, self.cgroups_path.name + ); + } + self.cgroups_path.name.clone() + } + + // systemd represents slice hierarchy using `-`, so we need to follow suit when + // generating the path of slice. For example, 'test-a-b.slice' becomes + // '/test.slice/test-a.slice/test-a-b.slice'. + fn expand_slice(&self, slice: &str) -> Result { + let suffix = ".slice"; + if slice.len() <= suffix.len() || !slice.ends_with(suffix) { + bail!("invalid slice name: {}", slice); + } + if slice.contains('/') { + bail!("invalid slice name: {}", slice); + } + let mut path = "".to_owned(); + let mut prefix = "".to_owned(); + let slice_name = slice.trim_end_matches(suffix); + // if input was -.slice, we should just return root now + if slice_name == "-" { + return Ok(Path::new("/").to_path_buf()); + } + for component in slice_name.split('-') { + if component.is_empty() { + anyhow!("Invalid slice name: {}", slice); + } + // Append the component to the path and to the prefix. + path = format!("{}/{}{}{}", path, prefix, component, suffix); + prefix = format!("{}{}-", prefix, component); + } + Ok(Path::new(&path).to_path_buf()) + } + + // get_cgroups_path generates a cgroups path from the one provided by the user via cgroupsPath. + // an example of the final path: "/machine.slice/docker-foo.scope" + fn get_cgroups_path(&self) -> Result { + // the root slice is under 'machine.slice'. + let mut slice = Path::new("/machine.slice").to_path_buf(); + // if the user provided a '.slice' (as in a branch of a tree) + // we need to "unpack it". + if !self.cgroups_path.parent.is_empty() { + slice = self.expand_slice(&self.cgroups_path.parent)?; + } + let unit_name = self.get_unit_name(); + let cgroups_path = slice.join(unit_name); + Ok(cgroups_path) + } + + /// create_unified_cgroup verifies sure that *each level* in the downward path from the root cgroup + /// down to the cgroup_path provided by the user is a valid cgroup hierarchy, + /// containing the attached controllers and that it contains the container pid. + fn create_unified_cgroup(&self, pid: Pid) -> Result { + let cgroups_path = self.get_cgroups_path()?; + let full_path = self.root_path.join_absolute_path(&cgroups_path)?; + let controllers: Vec = self + .get_available_controllers(&self.root_path)? + .into_iter() + .map(|c| format!("{}{}", "+", c.to_string())) + .collect(); + + // Write the controllers to the root_path. + Self::write_controllers(&self.root_path, &controllers)?; + + let mut current_path = self.root_path.clone(); + let mut components = cgroups_path.components().skip(1).peekable(); + // Verify that *each level* in the downward path from the root cgroup + // down to the cgroup_path provided by the user is a valid cgroup hierarchy. + // containing the attached controllers. + while let Some(component) = components.next() { + current_path = current_path.join(component); + if !current_path.exists() { + fs::create_dir(¤t_path)?; + fs::metadata(¤t_path)?.permissions().set_mode(0o755); + } + + // last component cannot have subtree_control enabled due to internal process constraint + // if this were set, writing to the cgroups.procs file will fail with Erno 16 (device or resource busy) + if components.peek().is_some() { + Self::write_controllers(¤t_path, &controllers)?; + } + } + + common::write_cgroup_file(full_path.join(CGROUP_PROCS), &pid)?; + Ok(full_path) + } + + fn get_available_controllers>( + &self, + cgroups_path: P, + ) -> Result> { + let controllers_path = self.root_path.join(cgroups_path).join(CGROUP_CONTROLLERS); + if !controllers_path.exists() { + bail!( + "cannot get available controllers. {:?} does not exist", + controllers_path + ) + } + + let mut controllers = Vec::new(); + for controller in fs::read_to_string(&controllers_path)?.split_whitespace() { + match controller { + "cpu" => controllers.push(ControllerType::Cpu), + "io" => controllers.push(ControllerType::Io), + "memory" => controllers.push(ControllerType::Memory), + "pids" => controllers.push(ControllerType::Pids), + _ => continue, + } + } + + Ok(controllers) + } + + fn write_controllers(path: &Path, controllers: &Vec) -> Result<()> { + for controller in controllers { + common::write_cgroup_file_str(path.join(CGROUP_SUBTREE_CONTROL), controller)?; + } + + Ok(()) + } +} + +impl CgroupManager for SystemDCGroupManager { + fn apply(&self, linux_resources: &LinuxResources, pid: Pid) -> Result<()> { + // Dont attach any pid to the cgroup if -1 is specified as a pid + if pid.as_raw() == -1 { + return Ok(()); + } + let full_cgroup_path = self.create_unified_cgroup(pid)?; + + for controller in CONTROLLER_TYPES { + match controller { + ControllerType::Cpu => Cpu::apply(linux_resources, &full_cgroup_path)?, + ControllerType::CpuSet => CpuSet::apply(linux_resources, &full_cgroup_path)?, + ControllerType::HugeTlb => HugeTlb::apply(linux_resources, &&full_cgroup_path)?, + ControllerType::Io => Io::apply(linux_resources, &&full_cgroup_path)?, + ControllerType::Memory => Memory::apply(linux_resources, &full_cgroup_path)?, + ControllerType::Pids => Pids::apply(linux_resources, &&full_cgroup_path)?, + } + } + + Ok(()) + } + + fn remove(&self) -> Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expand_slice_works() -> Result<()> { + let manager = SystemDCGroupManager::new( + PathBuf::from("/sys/fs/cgroup"), + PathBuf::from("test-a-b.slice:docker:foo"), + )?; + + assert_eq!( + manager.expand_slice("test-a-b.slice")?, + PathBuf::from("/test.slice/test-a.slice/test-a-b.slice"), + ); + + Ok(()) + } + + #[test] + fn get_cgroups_path_works_with_a_complex_slice() -> Result<()> { + let manager = SystemDCGroupManager::new( + PathBuf::from("/sys/fs/cgroup"), + PathBuf::from("test-a-b.slice:docker:foo"), + )?; + + assert_eq!( + manager.get_cgroups_path()?, + PathBuf::from("/test.slice/test-a.slice/test-a-b.slice/docker-foo.scope"), + ); + + Ok(()) + } + + #[test] + fn get_cgroups_path_works_with_a_simple_slice() -> Result<()> { + let manager = SystemDCGroupManager::new( + PathBuf::from("/sys/fs/cgroup"), + PathBuf::from("machine.slice:libpod:foo"), + )?; + + assert_eq!( + manager.get_cgroups_path()?, + PathBuf::from("/machine.slice/libpod-foo.scope"), + ); + + Ok(()) + } + + #[test] + fn get_cgroups_path_works_with_scope() -> Result<()> { + let manager = SystemDCGroupManager::new( + PathBuf::from("/sys/fs/cgroup"), + PathBuf::from(":docker:foo"), + )?; + + assert_eq!( + manager.get_cgroups_path()?, + PathBuf::from("/machine.slice/docker-foo.scope"), + ); + + Ok(()) + } +} diff --git a/src/create.rs b/src/create.rs index b24a73700..90b941139 100644 --- a/src/create.rs +++ b/src/create.rs @@ -46,7 +46,12 @@ pub struct Create { // associated with it like any other process. impl Create { /// Starts a new container process - pub fn exec(&self, root_path: PathBuf, command: impl Command) -> Result<()> { + pub fn exec( + &self, + root_path: PathBuf, + systemd_cgroup: bool, + command: impl Command, + ) -> Result<()> { // create a directory for the container to store state etc. // if already present, return error let bundle_canonicalized = fs::canonicalize(&self.bundle) @@ -102,6 +107,7 @@ impl Create { rootfs, spec, csocketfd, + systemd_cgroup, container, command, )?; @@ -121,6 +127,7 @@ fn run_container>( rootfs: PathBuf, spec: oci_spec::Spec, csocketfd: Option, + systemd_cgroup: bool, container: Container, command: impl Command, ) -> Result { @@ -149,7 +156,7 @@ fn run_container>( }; let cgroups_path = utils::get_cgroup_path(&linux.cgroups_path, container.id()); - let cmanager = cgroups::common::create_cgroup_manager(&cgroups_path)?; + let cmanager = cgroups::common::create_cgroup_manager(&cgroups_path, systemd_cgroup)?; // first fork, which creates process, which will later create actual container process match fork::fork_first(pid_file, rootless, linux, &container, cmanager)? { diff --git a/src/dbus/client.rs b/src/dbus/client.rs new file mode 100644 index 000000000..b0dc4afef --- /dev/null +++ b/src/dbus/client.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use dbus::blocking::Connection; +use std::time::Duration; +use std::vec::Vec; + +/// Client is a wrapper providing higher level API and abatraction around dbus. +/// For more information see https://www.freedesktop.org/wiki/Software/systemd/dbus/ +pub struct Client { + conn: Connection, +} + +impl Client { + pub fn new() -> Result { + let conn = Connection::new_session()?; + Ok(Client { conn }) + } + + /// start_unit starts a specific unit under systemd. See https://www.freedesktop.org/wiki/Software/systemd/dbus + /// for more details. + pub fn start_unit(&self, unit_name: &str, _properties: Vec<&str>) -> Result<()> { + let proxy = self.conn.with_proxy( + "org.freedesktop.systemd1.Manager", + "/", + Duration::from_millis(5000), + ); + let (_job_id,): (i32,) = proxy.method_call( + "org.freedesktop.systemd1.Manager", + "StartTransientUnit", + (unit_name, "replace"), + )?; + Ok(()) + } +} diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs new file mode 100644 index 000000000..e99ee79b9 --- /dev/null +++ b/src/dbus/mod.rs @@ -0,0 +1,2 @@ +mod client; +pub use client::Client; diff --git a/src/lib.rs b/src/lib.rs index 3bb33ce15..b8509f676 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod cgroups; pub mod command; pub mod container; pub mod create; +pub mod dbus; pub mod info; pub mod logger; pub mod namespaces; diff --git a/src/main.rs b/src/main.rs index 4df5f55d2..6dd777ea1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,9 @@ struct Opts { log: Option, #[clap(long)] log_format: Option, + /// Enable systemd cgroup manager, rather then use the cgroupfs directly. + #[clap(short, long)] + systemd_cgroup: bool, /// command to actually manage container #[clap(subcommand)] subcmd: SubCommand, @@ -47,6 +50,9 @@ pub struct Kill { #[derive(Clap, Debug)] pub struct Delete { container_id: String, + // forces deletion of the container. + #[clap(short, long)] + force: bool, } #[derive(Clap, Debug)] @@ -88,8 +94,10 @@ fn main() -> Result<()> { }; fs::create_dir_all(&root_path)?; + let systemd_cgroup = opts.systemd_cgroup; + match opts.subcmd { - SubCommand::Create(create) => create.exec(root_path, LinuxCommand), + SubCommand::Create(create) => create.exec(root_path, systemd_cgroup, LinuxCommand), SubCommand::Start(start) => start.exec(root_path), SubCommand::Kill(kill) => { // resolves relative paths, symbolic links etc. and get complete path @@ -151,7 +159,8 @@ fn main() -> Result<()> { // 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)?; + let cmanager = + cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?; cmanager.remove()?; } std::process::exit(0) diff --git a/src/tty.rs b/src/tty.rs index 40407cc2d..10b5de92b 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -72,17 +72,16 @@ pub fn setup_console(console_fd: FileDescriptor) -> Result<()> { #[cfg(test)] mod tests { use super::*; - + use std::env; use std::fs::{self, File}; use std::os::unix::net::UnixListener; use std::path::PathBuf; use serial_test::serial; - + use crate::utils::{create_temp_dir, TempDir}; - fn setup(testname: &str) -> Result<(TempDir, PathBuf, PathBuf)> { let testdir = create_temp_dir(testname)?; let rundir_path = Path::join(&testdir, "run"); @@ -93,11 +92,10 @@ mod tests { Ok((testdir, rundir_path, socket_path)) } - #[test] #[serial] fn test_setup_console_socket() { - let init = setup("test_setup_console_socket"); + let init = setup("test_setup_console_socket"); assert!(init.is_ok()); let (testdir, rundir_path, socket_path) = init.unwrap(); let lis = UnixListener::bind(Path::join(&testdir, "console-socket")); @@ -110,7 +108,7 @@ mod tests { #[test] #[serial] fn test_setup_console_socket_empty() { - let init = setup("test_setup_console_socket_empty"); + let init = setup("test_setup_console_socket_empty"); assert!(init.is_ok()); let (_testdir, rundir_path, socket_path) = init.unwrap(); let fd = setup_console_socket(&rundir_path, &socket_path); @@ -121,7 +119,7 @@ mod tests { #[test] #[serial] fn test_setup_console_socket_invalid() { - let init = setup("test_setup_console_socket_invalid"); + let init = setup("test_setup_console_socket_invalid"); assert!(init.is_ok()); let (testdir, rundir_path, socket_path) = init.unwrap(); let _socket = File::create(Path::join(&testdir, "console-socket")); @@ -133,7 +131,7 @@ mod tests { #[test] #[serial] fn test_setup_console() { - let init = setup("test_setup_console"); + let init = setup("test_setup_console"); assert!(init.is_ok()); let (testdir, rundir_path, socket_path) = init.unwrap(); let lis = UnixListener::bind(Path::join(&testdir, "console-socket")); @@ -141,6 +139,5 @@ mod tests { let fd = setup_console_socket(&&rundir_path, &socket_path); let status = setup_console(fd.unwrap()); assert!(status.is_ok()); - } + } } -