libs/sys-util: add utilities to manipulate cgroup

Add utilities to manipulate cgroup, currently only v1 is supported.

Signed-off-by: Liu Jiang <gerry@linux.alibaba.com>
Signed-off-by: He Rongguang <herongguang@linux.alibaba.com>
Signed-off-by: Jiahuan Chao <jhchao@linux.alibaba.com>
Signed-off-by: Qingyuan Hou <qingyuan.hou@linux.alibaba.com>
Signed-off-by: Quanwei Zhou <quanweiZhou@linux.alibaba.com>
Signed-off-by: Tim Zhang <tim@hyper.sh>
This commit is contained in:
Liu Jiang 2021-12-09 16:22:22 +08:00 committed by Fupan Li
parent ccd03e2cae
commit 87887026f6
6 changed files with 912 additions and 1 deletions

97
src/libs/Cargo.lock generated
View File

@ -80,6 +80,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cgroups-rs"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b827f9d9f6c2fff719d25f5d44cbc8d2ef6df1ef00d055c5c14d5dc25529579"
dependencies = [
"libc",
"log",
"nix 0.23.1",
"regex",
]
[[package]]
name = "chrono"
version = "0.4.19"
@ -89,6 +101,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
@ -338,12 +351,17 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
name = "kata-sys-util"
version = "0.1.0"
dependencies = [
"cgroups-rs",
"chrono",
"common-path",
"fail",
"kata-types",
"lazy_static",
"libc",
"nix 0.23.1",
"num_cpus",
"once_cell",
"serial_test",
"slog",
"slog-scope",
"tempfile",
@ -379,6 +397,16 @@ version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "lock_api"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.16"
@ -524,6 +552,31 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "petgraph"
version = "0.5.1"
@ -741,6 +794,12 @@ dependencies = [
"tempfile",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.136"
@ -772,6 +831,28 @@ dependencies = [
"serde",
]
[[package]]
name = "serial_test"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d"
dependencies = [
"lazy_static",
"parking_lot",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.6"
@ -819,6 +900,12 @@ dependencies = [
"slog",
]
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.4"
@ -889,6 +976,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "tokio"
version = "1.17.0"

View File

@ -11,11 +11,14 @@ license = "Apache-2.0"
edition = "2018"
[dependencies]
cgroups = { package = "cgroups-rs", version = "0.2.7" }
chrono = "0.4.0"
common-path = "=1.0.0"
fail = "0.5.0"
lazy_static = "1.4.0"
libc = "0.2.100"
nix = "0.23.0"
once_cell = "1.9.0"
slog = "2.5.2"
slog-scope = "4.4.0"
thiserror = "1.0.30"
@ -23,4 +26,6 @@ thiserror = "1.0.30"
kata-types = { path = "../kata-types" }
[dev-dependencies]
num_cpus = "1.13.1"
serial_test = "0.5.1"
tempfile = "3.2.0"

View File

@ -0,0 +1,735 @@
// Copyright (c) 2019-2021 Alibaba Cloud
// Copyright (c) 2019-2021 Ant Group
//
// SPDX-License-Identifier: Apache-2.0
//
use std::ops::Deref;
use std::path::{Component, Path, PathBuf};
use std::sync::Mutex;
use cgroups::{Cgroup, CgroupPid, Controllers, Hierarchy, Subsystem};
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use crate::sl;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Can not add tgid {0} to cgroup, {1:?}")]
AddTgid(u64, #[source] cgroups::error::Error),
#[error("failed to apply resources to cgroup: {0:?}")]
ApplyResource(#[source] cgroups::error::Error),
#[error("failed to delete cgroup after {0} retries")]
DeleteCgroup(u64),
#[error("Invalid cgroup path {0}")]
InvalidCgroupPath(String),
}
pub type Result<T> = std::result::Result<T, Error>;
lazy_static! {
/// Disable cgroup v1 subsystems.
pub static ref DISABLED_HIERARCHIES: Mutex<Vec<cgroups::Controllers>> = Mutex::new(Vec::new());
}
/// Update the disabled cgroup subsystems.
///
/// Some cgroup controllers may be disabled by runtime configuration file. The sandbox may call
/// this method to disable those cgroup controllers once.
pub fn update_disabled_cgroup_list(hierarchies: &[String]) {
let mut disabled_hierarchies = DISABLED_HIERARCHIES.lock().unwrap();
disabled_hierarchies.clear();
for hierarchy in hierarchies {
//disabled_hierarchies.push(hie.clone());
match hierarchy.as_str() {
"blkio" => disabled_hierarchies.push(cgroups::Controllers::BlkIo),
"cpu" => disabled_hierarchies.push(cgroups::Controllers::Cpu),
"cpuset" => disabled_hierarchies.push(cgroups::Controllers::CpuSet),
"cpuacct" => disabled_hierarchies.push(cgroups::Controllers::CpuAcct),
"devices" => disabled_hierarchies.push(cgroups::Controllers::Devices),
"freezer" => disabled_hierarchies.push(cgroups::Controllers::Freezer),
"hugetlb" => disabled_hierarchies.push(cgroups::Controllers::HugeTlb),
"memory" => disabled_hierarchies.push(cgroups::Controllers::Mem),
"net_cls" => disabled_hierarchies.push(cgroups::Controllers::NetCls),
"net_prio" => disabled_hierarchies.push(cgroups::Controllers::NetPrio),
"perf_event" => disabled_hierarchies.push(cgroups::Controllers::PerfEvent),
"pids" => disabled_hierarchies.push(cgroups::Controllers::Pids),
"systemd" => disabled_hierarchies.push(cgroups::Controllers::Systemd),
_ => warn!(sl!(), "unknown cgroup controller {}", hierarchy),
}
}
debug!(
sl!(),
"disable cgroup list {:?} from {:?}", disabled_hierarchies, hierarchies
);
}
/// Filter out disabled cgroup subsystems.
pub fn filter_disabled_cgroup(controllers: &mut Vec<Controllers>) {
let disabled_hierarchies = DISABLED_HIERARCHIES.lock().unwrap();
controllers.retain(|x| !disabled_hierarchies.contains(x));
}
#[derive(Copy, Clone, Debug)]
pub enum PidType {
/// Add pid to `tasks`
Tasks,
/// Add pid to `cgroup.procs`
CgroupProcs,
}
/// Get the singleton instance for cgroup v1 hierarchy object.
pub fn get_cgroup_hierarchies() -> &'static cgroups::hierarchies::V1 {
static GLOBAL: Lazy<cgroups::hierarchies::V1> = Lazy::new(cgroups::hierarchies::V1::new);
GLOBAL.deref()
}
// Prepend a kata specific string to oci cgroup path to form a different cgroup path, thus cAdvisor
// couldn't find kata containers cgroup path on host to prevent it from grabbing the stats data.
const CGROUP_KATA_PREFIX: &str = "kata";
/// Convert to a Kata specific cgroup path.
pub fn gen_kata_cgroup_path(path: &str) -> PathBuf {
// Be careful to trim off the possible '/' prefix. Joining an absolute path to a `Path` object
// will replace the old `Path` instead of concat.
Path::new(CGROUP_KATA_PREFIX).join(path.trim_start_matches('/'))
}
/// Convert to a cgroup path for K8S sandbox.
pub fn gen_sandbox_cgroup_path(path: &str) -> PathBuf {
PathBuf::from(path)
}
/// A customized cgroup v1 hierarchy object with configurable filters for supported subsystems.
#[derive(Debug)]
pub struct V1Customized {
mount_point: PathBuf,
controllers: Vec<Controllers>,
}
impl V1Customized {
/// Create a new instance of [`V1Customized`].
///
/// The `controllers` configures the subsystems to enable.
///
/// Note :
/// 1. When enabling both blkio and memory cgroups, blkio cgroup must be enabled before memory
/// cgroup due to a limitation in writeback control of blkio cgroup.
/// 2. cpu, cpuset, cpuacct should be adjacent to each other.
pub fn new(controllers: Vec<Controllers>) -> Self {
let mount_point = get_cgroup_hierarchies().root();
V1Customized {
mount_point,
controllers,
}
}
}
impl Hierarchy for V1Customized {
fn subsystems(&self) -> Vec<Subsystem> {
let subsystems = get_cgroup_hierarchies().subsystems();
subsystems
.into_iter()
.filter(|sub| {
self.controllers
.contains(&sub.to_controller().control_type())
})
.collect::<Vec<_>>()
}
fn root(&self) -> PathBuf {
self.mount_point.clone()
}
fn root_control_group(&self) -> Cgroup {
Cgroup::load(Box::new(V1Customized::new(self.controllers.clone())), "")
}
fn v2(&self) -> bool {
false
}
}
/// An boxed cgroup hierarchy object.
pub type BoxedHierarchyObject = Box<dyn Hierarchy>;
/// Create a cgroup hierarchy object with all subsystems disabled.
pub fn get_empty_hierarchy() -> BoxedHierarchyObject {
Box::new(V1Customized::new(vec![]))
}
/// Create a cgroup hierarchy object for pod sandbox.
pub fn get_sandbox_hierarchy(no_mem: bool) -> BoxedHierarchyObject {
let mut controllers = vec![
cgroups::Controllers::BlkIo,
cgroups::Controllers::Cpu,
cgroups::Controllers::CpuSet,
cgroups::Controllers::CpuAcct,
cgroups::Controllers::PerfEvent,
];
if !no_mem {
controllers.push(cgroups::Controllers::Mem);
}
filter_disabled_cgroup(&mut controllers);
Box::new(V1Customized::new(controllers))
}
/// Create a cgroup hierarchy object with mem subsystem.
///
/// Note: the mem subsystem may have been disabled, so it will get filtered out.
pub fn get_mem_hierarchy() -> BoxedHierarchyObject {
let mut controllers = vec![cgroups::Controllers::Mem];
filter_disabled_cgroup(&mut controllers);
Box::new(V1Customized::new(controllers))
}
/// Create a cgroup hierarchy object with CPU related subsystems.
///
/// Note: the mem subsystem may have been disabled, so it will get filtered out.
pub fn get_cpu_hierarchy() -> BoxedHierarchyObject {
let mut controllers = vec![
cgroups::Controllers::Cpu,
cgroups::Controllers::CpuSet,
cgroups::Controllers::CpuAcct,
];
filter_disabled_cgroup(&mut controllers);
Box::new(V1Customized::new(controllers))
}
/// Get cgroup hierarchy object from `path`.
pub fn get_hierarchy_by_path(path: &str) -> Result<BoxedHierarchyObject> {
let v1 = get_cgroup_hierarchies().clone();
let valid_path = valid_cgroup_path(path)?;
let cg = cgroups::Cgroup::load(Box::new(v1), valid_path.as_str());
let mut hierarchy = vec![];
for subsys in cg.subsystems() {
let controller = subsys.to_controller();
if controller.exists() {
hierarchy.push(controller.control_type());
}
}
Ok(Box::new(V1Customized::new(hierarchy)))
}
/// Create or load a cgroup object from a path.
pub fn create_or_load_cgroup(path: &str) -> Result<Cgroup> {
let hie = Box::new(get_cgroup_hierarchies().clone());
create_or_load_cgroup_with_hier(hie, path)
}
/// Create or load a cgroup v1 object from a path, with a given hierarchy object.
pub fn create_or_load_cgroup_with_hier(hie: BoxedHierarchyObject, path: &str) -> Result<Cgroup> {
let valid_path = valid_cgroup_path(path)?;
if is_cgroup_exist(valid_path.as_str()) {
Ok(cgroups::Cgroup::load(hie, valid_path.as_str()))
} else {
Ok(cgroups::Cgroup::new(hie, valid_path.as_str()))
}
}
/// Check whether `path` hosts a cgroup hierarchy directory.
pub fn is_cgroup_exist(path: &str) -> bool {
let valid_path = match valid_cgroup_path(path) {
Ok(v) => v,
Err(e) => {
warn!(sl!(), "{}", e);
return false;
}
};
let v1 = get_cgroup_hierarchies().clone();
let cg = cgroups::Cgroup::load(Box::new(v1), valid_path.as_str());
for subsys in cg.subsystems() {
if subsys.to_controller().exists() {
debug!(sl!(), "cgroup {} exist", path);
return true;
}
}
false
}
// Validate the cgroup path is a relative path, do not include ".", "..".
fn valid_cgroup_path(path: &str) -> Result<String> {
let path = path.trim_start_matches('/').to_string();
for comp in Path::new(&path).components() {
if !matches!(comp, Component::Normal(_)) {
return Err(Error::InvalidCgroupPath(path.to_string()));
}
}
Ok(path)
}
/// Remove all task from cgroup and delete the cgroup.
pub fn force_delete_cgroup(cg: cgroups::Cgroup) -> Result<()> {
delete_cgroup_with_retry(cg, |cg: &Cgroup| {
// if task exist need to delete first.
for cg_pid in cg.tasks() {
warn!(sl!(), "Delete cgroup task pid {}", cg_pid.pid);
cg.remove_task(cg_pid);
}
})
}
/// Try to delete a cgroup, call the `do_process` handler at each iteration.
pub fn delete_cgroup_with_retry<F>(cg: Cgroup, mut do_process: F) -> Result<()>
where
F: FnMut(&Cgroup),
{
// sleep DURATION
const SLEEP_MILLISECS: u64 = 10;
const RETRY_COUNT: u64 = 200;
// In case of deletion failure caused by "Resource busy", sleep DURATION and retry RETRY times.
for index in 0..RETRY_COUNT {
do_process(&cg);
if cg.delete().is_ok() {
if index > 0 {
info!(
sl!(),
"cgroup delete cgroup cost {} ms, retry {} times",
index * SLEEP_MILLISECS,
index,
);
}
return Ok(());
}
std::thread::sleep(std::time::Duration::from_millis(SLEEP_MILLISECS))
}
Err(Error::DeleteCgroup(RETRY_COUNT))
}
/// Move the process `pid` into the cgroup `to`.
pub fn move_tgid(pid: u64, to: &Cgroup) -> Result<()> {
info!(sl!(), "try to move tid {:?}", pid);
to.add_task_by_tgid(CgroupPid::from(pid))
.map_err(|e| Error::AddTgid(pid, e))
}
/// Move all processes tasks from `from` to `to`.
pub fn move_cgroup_task(from: &Cgroup, to: &Cgroup) -> Result<()> {
info!(sl!(), "try to move tasks {:?}", from.tasks());
for cg_pid in from.tasks() {
from.remove_task(CgroupPid::from(cg_pid.pid));
// TODO: enhance cgroups to implement Copy for CgroupPid
// https://github.com/kata-containers/cgroups-rs/issues/70
let pid = cg_pid.pid;
to.add_task(cg_pid).map_err(|e| Error::AddTgid(pid, e))?;
}
Ok(())
}
/// Associate a group of tasks with a cgroup, and optionally configure resources for the cgroup.
pub fn update_cgroup_task_resources(
hierarchy: BoxedHierarchyObject,
path: &str,
pids: &[u64],
pid_type: PidType,
resources: Option<&cgroups::Resources>,
) -> Result<()> {
if hierarchy.subsystems().is_empty() {
return Ok(());
}
fail::fail_point!("update_cgroup_task_resources", |_| { () });
let cg = create_or_load_cgroup_with_hier(hierarchy, path)?;
for pid in pids {
let result = match pid_type {
PidType::Tasks => cg.add_task(CgroupPid { pid: *pid }),
PidType::CgroupProcs => cg.add_task_by_tgid(CgroupPid { pid: *pid }),
};
if let Err(err) = result {
return Err(Error::AddTgid(*pid, err));
}
}
if let Some(res) = resources {
cg.apply(res).map_err(Error::ApplyResource)?;
}
debug!(
sl!(),
"update {:?} {:?} resources {:?} for cgroup {}", pid_type, pids, resources, path
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use cgroups::Controllers;
use serial_test::serial;
use std::sync::atomic::{AtomicUsize, Ordering};
static GLOBAL_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn gen_test_path() -> String {
let pid = nix::unistd::getpid().as_raw();
let index = GLOBAL_COUNTER.fetch_add(1, Ordering::SeqCst);
let path = format!("kata-tests-{}-{}", pid, index);
println!("test path {}", path);
path
}
fn get_hierarchy(controllers: Vec<Controllers>) -> Box<V1Customized> {
Box::new(V1Customized::new(controllers))
}
#[test]
fn test_v1_customized_cgroup() {
update_disabled_cgroup_list(&[]);
let c = V1Customized::new(vec![]);
assert_eq!(c.subsystems().len(), 0);
assert!(!c.v2());
let c = V1Customized::new(vec![Controllers::Cpu, Controllers::CpuSet]);
assert_eq!(c.subsystems().len(), 2);
assert!(!c.v2());
}
#[test]
#[serial]
fn test_filter_disabled_cgroup() {
update_disabled_cgroup_list(&[]);
assert_eq!(DISABLED_HIERARCHIES.lock().unwrap().len(), 0);
let disabeld = ["perf_event".to_string()];
update_disabled_cgroup_list(&disabeld);
assert_eq!(DISABLED_HIERARCHIES.lock().unwrap().len(), 1);
assert_eq!(
DISABLED_HIERARCHIES.lock().unwrap()[0],
Controllers::PerfEvent
);
let mut subsystems = vec![Controllers::BlkIo, Controllers::PerfEvent, Controllers::Cpu];
filter_disabled_cgroup(&mut subsystems);
assert_eq!(subsystems.len(), 2);
assert_eq!(subsystems[0], Controllers::BlkIo);
assert_eq!(subsystems[1], Controllers::Cpu);
let disabeld = ["cpu".to_string(), "cpuset".to_string()];
update_disabled_cgroup_list(&disabeld);
assert_eq!(DISABLED_HIERARCHIES.lock().unwrap().len(), 2);
let mut subsystems = vec![Controllers::BlkIo, Controllers::PerfEvent, Controllers::Cpu];
filter_disabled_cgroup(&mut subsystems);
assert_eq!(subsystems.len(), 2);
assert_eq!(subsystems[0], Controllers::BlkIo);
assert_eq!(subsystems[1], Controllers::PerfEvent);
update_disabled_cgroup_list(&[]);
}
#[test]
fn test_create_empty_hierarchy() {
update_disabled_cgroup_list(&[]);
let controller = get_empty_hierarchy();
assert_eq!(controller.subsystems().len(), 0);
assert!(!controller.root_control_group().v2());
}
#[test]
#[serial]
fn test_create_sandbox_hierarchy() {
update_disabled_cgroup_list(&[]);
let controller = get_sandbox_hierarchy(true);
assert_eq!(controller.subsystems().len(), 5);
assert!(!controller.root_control_group().v2());
let controller = get_sandbox_hierarchy(false);
assert_eq!(controller.subsystems().len(), 6);
assert!(!controller.root_control_group().v2());
}
#[test]
#[serial]
fn test_get_hierarchy() {
update_disabled_cgroup_list(&[]);
let controller = get_mem_hierarchy();
assert!(!controller.v2());
assert_eq!(controller.subsystems().len(), 1);
let controller = get_cpu_hierarchy();
assert!(!controller.v2());
assert_eq!(controller.subsystems().len(), 3);
}
#[test]
#[serial]
fn test_create_cgroup_default() {
update_disabled_cgroup_list(&[]);
// test need root permission
if !nix::unistd::getuid().is_root() {
println!("test need root permission");
return;
}
let v1 = Box::new(cgroups::hierarchies::V1::new());
let test_path = gen_test_path();
let cg_path = test_path.as_str();
assert!(!is_cgroup_exist(cg_path));
// new cgroup
let cg = cgroups::Cgroup::new(v1, cg_path);
assert!(is_cgroup_exist(cg_path));
// add task
let _ = cg.add_task(cgroups::CgroupPid {
pid: nix::unistd::getpid().as_raw() as u64,
});
// delete cgroup
force_delete_cgroup(cg).unwrap();
assert!(!is_cgroup_exist(cg_path));
}
#[test]
#[serial]
fn test_create_cgroup_cpus() {
update_disabled_cgroup_list(&[]);
// test need root permission
if !nix::unistd::getuid().is_root() {
println!("test need root permission");
return;
}
if num_cpus::get() <= 1 {
println!("The unit test is only supported on SMP systems.");
return;
}
let test_path = gen_test_path();
let cg_path = test_path.as_str();
assert!(!is_cgroup_exist(cg_path));
// new cgroup
let cgroup = create_or_load_cgroup(cg_path).unwrap();
let cpus: &cgroups::cpuset::CpuSetController = cgroup.controller_of().unwrap();
cpus.set_cpus("0-1").unwrap();
assert!(is_cgroup_exist(cg_path));
// current cgroup
let current_cgroup = create_or_load_cgroup(cg_path).unwrap();
let current_cpus: &cgroups::cpuset::CpuSetController =
current_cgroup.controller_of().unwrap();
// check value
assert_eq!(cpus.cpuset().cpus, current_cpus.cpuset().cpus);
// delete cgroup
force_delete_cgroup(cgroup).unwrap();
assert!(!is_cgroup_exist(cg_path));
}
#[test]
#[serial]
fn test_create_cgroup_with_parent() {
update_disabled_cgroup_list(&[]);
// test need root permission
if !nix::unistd::getuid().is_root() {
println!("test need root permission");
return;
}
if num_cpus::get() <= 1 {
println!("The unit test is only supported on SMP systems.");
return;
}
let test_path = gen_test_path();
let cg_path = test_path.as_str();
assert!(!is_cgroup_exist(cg_path));
// new cgroup
let cg = create_or_load_cgroup(cg_path).unwrap();
let cpus: &cgroups::cpuset::CpuSetController = cg.controller_of().unwrap();
cpus.set_cpus("0-1").unwrap();
assert!(is_cgroup_exist(cg_path));
// new cgroup 1
let cg_test_path_1 = format!("{}/vcpu0", test_path);
let cg_path_1 = cg_test_path_1.as_str();
let cg1 = create_or_load_cgroup(cg_path_1).unwrap();
let cpus1: &cgroups::cpuset::CpuSetController = cg1.controller_of().unwrap();
cpus1.set_cpus("0").unwrap();
assert!(is_cgroup_exist(cg_path_1));
// new cgroup 2
let cg_test_path_2 = format!("{}/vcpu1", test_path);
let cg_path_2 = cg_test_path_2.as_str();
// new cgroup
let cg2 = create_or_load_cgroup(cg_path_2).unwrap();
let cpus2: &cgroups::cpuset::CpuSetController = cg2.controller_of().unwrap();
cpus2.set_cpus("1").unwrap();
assert!(is_cgroup_exist(cg_path_2));
// must delete sub dir first
force_delete_cgroup(cg1).unwrap();
assert!(!is_cgroup_exist(cg_path_1));
force_delete_cgroup(cg2).unwrap();
assert!(!is_cgroup_exist(cg_path_2));
force_delete_cgroup(cg).unwrap();
assert!(!is_cgroup_exist(cg_path));
}
fn assert_customize_path_exist(path: &str, current_subsystems: &[Subsystem], expect: bool) {
println!("assert customize path {} exist expect {}", path, expect);
let v1 = Box::new(cgroups::hierarchies::V1::new());
let v1_cg = Cgroup::load(v1, path);
let v1_subsystems = v1_cg.subsystems();
for v1_sub in v1_subsystems {
let check_expect = || -> bool {
for current_sub in current_subsystems {
if v1_sub.to_controller().control_type()
== current_sub.to_controller().control_type()
{
return expect;
}
}
false
}();
assert_eq!(
check_expect,
v1_sub.to_controller().exists(),
"failed to check path {:?} subsystem {:?}",
path,
v1_sub
)
}
}
fn clean_cgroup_v1(path: &str) {
let v1 = Box::new(cgroups::hierarchies::V1::new());
let cg = Cgroup::load(v1.clone(), path);
delete_cgroup_with_retry(cg, |_: &Cgroup| {}).unwrap();
let check_cg = Cgroup::load(v1, path);
assert_customize_path_exist(path, check_cg.subsystems(), false);
}
#[test]
#[serial]
fn test_customize_hierarchies() {
update_disabled_cgroup_list(&[]);
// test need root permission
if !nix::unistd::getuid().is_root() {
println!("test need root permission");
return;
}
let cg_path_1 = "test_customize_hierarchies1";
let cg_path_2 = "test_customize_hierarchies2";
// clean
clean_cgroup_v1(cg_path_1);
clean_cgroup_v1(cg_path_2);
// check customized cgroup
// With some kernels, Cpu and CpuAcct are combined into one directory, so enable both
// to ease test code.
let controllers_1 = vec![Controllers::Cpu, Controllers::CpuAcct];
let controllers_2 = vec![Controllers::Cpu, Controllers::CpuSet, Controllers::CpuAcct];
let cg_1 = Cgroup::new(get_hierarchy(controllers_1.clone()), cg_path_1);
let cg_2 = Cgroup::new(get_hierarchy(controllers_2.clone()), cg_path_2);
assert_customize_path_exist(cg_path_1, cg_1.subsystems(), true);
assert_customize_path_exist(cg_path_2, cg_2.subsystems(), true);
// delete
let _ = cg_1.delete();
let _ = cg_2.delete();
// check after delete
let check_cg_1 = Cgroup::load(get_hierarchy(controllers_1), cg_path_1);
let check_cg_2 = Cgroup::load(get_hierarchy(controllers_2), cg_path_2);
assert_customize_path_exist(cg_path_1, check_cg_1.subsystems(), false);
assert_customize_path_exist(cg_path_2, check_cg_2.subsystems(), false);
}
#[test]
#[serial]
fn test_task_move() {
update_disabled_cgroup_list(&[]);
// test need root permission
if !nix::unistd::getuid().is_root() {
println!("test need root permission");
return;
}
let cg_path_1 = "test_task_move_before";
let cg_path_2 = "test_task_move_after";
// clean
clean_cgroup_v1(cg_path_1);
clean_cgroup_v1(cg_path_2);
// With some kernels, Cpu and CpuAcct are combined into one directory, so enable both
// to ease test code.
let controllers = vec![Controllers::Cpu, Controllers::CpuAcct];
let cg_1 = Cgroup::new(get_hierarchy(controllers.clone()), cg_path_1);
let cg_2 = Cgroup::new(get_hierarchy(controllers.clone()), cg_path_2);
assert_customize_path_exist(cg_path_1, cg_1.subsystems(), true);
assert_customize_path_exist(cg_path_2, cg_2.subsystems(), true);
// add task
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let _ = cg_1.add_task(CgroupPid::from(pid)).unwrap();
let mut cg_task_1 = cg_1.tasks();
let mut cg_task_2 = cg_2.tasks();
assert_eq!(1, cg_task_1.len());
assert_eq!(0, cg_task_2.len());
// move task
let _ = cg_2.add_task(CgroupPid::from(pid)).unwrap();
cg_task_1 = cg_1.tasks();
cg_task_2 = cg_2.tasks();
assert_eq!(0, cg_task_1.len());
assert_eq!(1, cg_task_2.len());
cg_2.remove_task(CgroupPid::from(pid));
// delete
cg_1.delete().unwrap();
// delete cg_2 with retry because of possible unknown failed
// caused by "Resource busy", we do the same in the production
// code, so it makes sense in the test.
delete_cgroup_with_retry(cg_2, |_| {}).unwrap();
// check after delete
let check_cg_1 = Cgroup::load(get_hierarchy(controllers.clone()), cg_path_1);
let check_cg_2 = Cgroup::load(get_hierarchy(controllers), cg_path_2);
assert_customize_path_exist(cg_path_1, check_cg_1.subsystems(), false);
assert_customize_path_exist(cg_path_2, check_cg_2.subsystems(), false);
}
#[test]
fn test_gen_kata_cgroup_path() {
assert_eq!(
&gen_kata_cgroup_path("sandbox1/container2"),
Path::new("kata/sandbox1/container2")
);
assert_eq!(
&gen_kata_cgroup_path("/sandbox1/container2"),
Path::new("kata/sandbox1/container2")
);
assert_eq!(
&gen_kata_cgroup_path("/sandbox1:container2"),
Path::new("kata/sandbox1:container2")
);
}
}

View File

@ -6,6 +6,7 @@
#[macro_use]
extern crate slog;
pub mod cgroup;
pub mod fs;
pub mod mount;

View File

@ -0,0 +1,70 @@
// Copyright (c) 2022 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0
//
/// A list of CPU IDs.
#[derive(Debug)]
pub struct CpuSet(Vec<u32>);
impl CpuSet {
/// Create a new instance of `CpuSet`.
pub fn new() -> Self {
CpuSet(vec![])
}
/// Add new CPUs into the set.
pub fn extend(&mut self, cpus: &[u32]) {
self.0.extend_from_slice(cpus);
self.0.sort_unstable();
self.0.dedup();
}
/// Returns true if the CPU set contains elements.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Get number of elements in the CPU set.
pub fn len(&self) -> usize {
self.0.len()
}
}
impl From<Vec<u32>> for CpuSet {
fn from(mut cpus: Vec<u32>) -> Self {
cpus.sort_unstable();
cpus.dedup();
CpuSet(cpus)
}
}
/// Test whether two CPU sets are equal.
impl PartialEq for CpuSet {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_cpu_list_equal() {
let cpuset1 = CpuSet::from(vec![1, 2, 3]);
let cpuset2 = CpuSet::from(vec![3, 2, 1]);
let cpuset3 = CpuSet::from(vec![]);
let cpuset4 = CpuSet::from(vec![3, 2, 4]);
let cpuset5 = CpuSet::from(vec![1, 2, 3, 3, 2, 1]);
assert_eq!(cpuset1.len(), 3);
assert!(cpuset3.is_empty());
assert_eq!(cpuset5.len(), 3);
assert_eq!(cpuset1, cpuset2);
assert_eq!(cpuset1, cpuset5);
assert_ne!(cpuset1, cpuset3);
assert_ne!(cpuset1, cpuset4);
}
}

View File

@ -11,7 +11,7 @@ extern crate slog;
#[macro_use]
extern crate serde;
/// Constants and data types annotations.
/// Constants and data types related to annotations.
pub mod annotations;
/// Kata configuration information from configuration file.
@ -20,6 +20,9 @@ pub mod config;
/// Constants and data types related to container.
pub mod container;
/// Constants and data types related to CPU.
pub mod cpu;
/// Constants and data types related to Kubernetes/kubelet.
pub mod k8s;