mirror of
https://github.com/containers/youki
synced 2024-05-17 21:16:30 +02:00
322 lines
11 KiB
Rust
322 lines
11 KiB
Rust
use std::{
|
|
fs::{self},
|
|
os::unix::fs::PermissionsExt,
|
|
};
|
|
|
|
use anyhow::{anyhow, bail, Result};
|
|
use nix::unistd::Pid;
|
|
use oci_spec::{FreezerState, LinuxResources};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use super::{
|
|
controller::Controller, controller_type::ControllerType, cpu::Cpu, cpuset::CpuSet,
|
|
freezer::Freezer, hugetlb::HugeTlb, io::Io, memory::Memory, pids::Pids,
|
|
};
|
|
use crate::common::{self, CgroupManager, PathBufExt};
|
|
use crate::stats::Stats;
|
|
|
|
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: PathBuf,
|
|
full_path: PathBuf,
|
|
}
|
|
|
|
/// 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<Self> {
|
|
// TODO: create the systemd unit using a dbus client.
|
|
let destructured_path = Self::destructure_cgroups_path(cgroups_path)?;
|
|
let cgroups_path = Self::construct_cgroups_path(destructured_path)?;
|
|
let full_path = root_path.join_safely(&cgroups_path)?;
|
|
|
|
Ok(SystemDCGroupManager {
|
|
root_path,
|
|
cgroups_path,
|
|
full_path,
|
|
})
|
|
}
|
|
|
|
fn destructure_cgroups_path(cgroups_path: PathBuf) -> Result<CgroupsPath> {
|
|
// 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::<Vec<&str>>();
|
|
parent = parts[0];
|
|
scope = parts[1];
|
|
name = parts[2];
|
|
}
|
|
|
|
Ok(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(cgroups_path: CgroupsPath) -> String {
|
|
// By default we create a scope unless specified explicitly.
|
|
if !cgroups_path.name.ends_with(".slice") {
|
|
return format!("{}-{}.scope", cgroups_path.scope, cgroups_path.name);
|
|
}
|
|
cgroups_path.name
|
|
}
|
|
|
|
// 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(slice: &str) -> Result<PathBuf> {
|
|
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 construct_cgroups_path(cgroups_path: CgroupsPath) -> Result<PathBuf> {
|
|
// 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 !cgroups_path.parent.is_empty() {
|
|
slice = Self::expand_slice(&cgroups_path.parent)?;
|
|
}
|
|
let unit_name = Self::get_unit_name(cgroups_path);
|
|
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 controllers: Vec<String> = 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 = self.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(self.full_path.join(CGROUP_PROCS), pid)
|
|
}
|
|
|
|
fn get_available_controllers<P: AsRef<Path>>(
|
|
&self,
|
|
cgroups_path: P,
|
|
) -> Result<Vec<ControllerType>> {
|
|
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: &[String]) -> Result<()> {
|
|
for controller in controllers {
|
|
common::write_cgroup_file_str(path.join(CGROUP_SUBTREE_CONTROL), controller)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl CgroupManager for SystemDCGroupManager {
|
|
fn add_task(&self, pid: Pid) -> Result<()> {
|
|
// Dont attach any pid to the cgroup if -1 is specified as a pid
|
|
if pid.as_raw() == -1 {
|
|
return Ok(());
|
|
}
|
|
|
|
self.create_unified_cgroup(pid)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn apply(&self, linux_resources: &LinuxResources) -> Result<()> {
|
|
for controller in CONTROLLER_TYPES {
|
|
match controller {
|
|
ControllerType::Cpu => Cpu::apply(linux_resources, &self.full_path)?,
|
|
ControllerType::CpuSet => CpuSet::apply(linux_resources, &self.full_path)?,
|
|
ControllerType::HugeTlb => HugeTlb::apply(linux_resources, &self.full_path)?,
|
|
ControllerType::Io => Io::apply(linux_resources, &self.full_path)?,
|
|
ControllerType::Memory => Memory::apply(linux_resources, &self.full_path)?,
|
|
ControllerType::Pids => Pids::apply(linux_resources, &self.full_path)?,
|
|
ControllerType::Freezer => Freezer::apply(linux_resources, &self.full_path)?,
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn remove(&self) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn freeze(&self, state: FreezerState) -> Result<()> {
|
|
let linux_resources = LinuxResources {
|
|
freezer: Some(state),
|
|
..Default::default()
|
|
};
|
|
Freezer::apply(&linux_resources, &self.full_path)
|
|
}
|
|
|
|
fn stats(&self) -> Result<Stats> {
|
|
Ok(Stats::default())
|
|
}
|
|
|
|
fn get_all_pids(&self) -> Result<Vec<Pid>> {
|
|
common::get_all_pids(&self.full_path)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn expand_slice_works() -> Result<()> {
|
|
assert_eq!(
|
|
SystemDCGroupManager::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 cgroups_path = SystemDCGroupManager::destructure_cgroups_path(PathBuf::from(
|
|
"test-a-b.slice:docker:foo",
|
|
))
|
|
.expect("");
|
|
|
|
assert_eq!(
|
|
SystemDCGroupManager::construct_cgroups_path(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 cgroups_path = SystemDCGroupManager::destructure_cgroups_path(PathBuf::from(
|
|
"machine.slice:libpod:foo",
|
|
))
|
|
.expect("");
|
|
|
|
assert_eq!(
|
|
SystemDCGroupManager::construct_cgroups_path(cgroups_path)?,
|
|
PathBuf::from("/machine.slice/libpod-foo.scope"),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn get_cgroups_path_works_with_scope() -> Result<()> {
|
|
let cgroups_path =
|
|
SystemDCGroupManager::destructure_cgroups_path(PathBuf::from(":docker:foo")).expect("");
|
|
|
|
assert_eq!(
|
|
SystemDCGroupManager::construct_cgroups_path(cgroups_path)?,
|
|
PathBuf::from("/machine.slice/docker-foo.scope"),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|