Skip to content

Commit

Permalink
Merge pull request #391 from Furisto/pid-tests
Browse files Browse the repository at this point in the history
Cgroup v1 pid integration tests
  • Loading branch information
YJDoc2 authored Oct 19, 2021
2 parents 71e747f + e245fdd commit 9ed52d9
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 89 deletions.
70 changes: 70 additions & 0 deletions youki_integration_test/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions youki_integration_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
]

[dependencies]
procfs = "0.11.0"
uuid = "0.8"
rand = "0.8.0"
tar = "0.4"
Expand Down
2 changes: 2 additions & 0 deletions youki_integration_test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ This framework also has some test utils, meant to help doing common operations i
- delete_container : runs the runtime command with delete argument, with given id and with given bundle directory
- get_state : runs the runtime command with state argument, with given id and with given bundle directory
- test_outside_container : this is meant to mimic [validateOutsideContainer](https://github.com/opencontainers/runtime-tools/blob/59cdde06764be8d761db120664020f0415f36045/validation/util/test.go#L263) function of original tests.
- check_container_created: this checks if the container was created succesfully.
- test_result!: this is a macro, that allows you to convert from a Result<T,E> to a TestResult

Note that even though all of the above functions are provided, most of the time the only required function is test_outside_container, as it does all the work of setting up the bundle, creating and running the container, getting the state of the container, killing the container and then deleting the container.

Expand Down
5 changes: 5 additions & 0 deletions youki_integration_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use anyhow::Result;
use clap::Clap;
use std::path::PathBuf;
use test_framework::TestManager;
use tests::cgroups;

#[derive(Clap, Debug)]
#[clap(version = "0.0.1", author = "youki team")]
Expand Down Expand Up @@ -60,11 +61,15 @@ fn main() -> Result<()> {
let cc = ContainerCreate::new();
let huge_tlb = get_tlb_test();
let pidfile = get_pidfile_test();
let cgroup_v1_pids = cgroups::pids::get_test_group();

tm.add_test_group(&cl);
tm.add_test_group(&cc);
tm.add_test_group(&huge_tlb);
tm.add_test_group(&pidfile);
tm.add_test_group(&cgroup_v1_pids);

tm.add_cleanup(Box::new(cgroups::cleanup));

if let Some(tests) = opts.tests {
let tests_to_run = parse_tests(&tests);
Expand Down
35 changes: 35 additions & 0 deletions youki_integration_test/src/tests/cgroups/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::path::PathBuf;

use anyhow::{Context, Result};
use procfs::process::Process;
use std::fs;

pub mod pids;

pub fn cleanup() -> Result<()> {
for subsystem in list_subsystem_mount_points()? {
let runtime_test = subsystem.join("runtime-test");
if runtime_test.exists() {
fs::remove_dir(&runtime_test)
.with_context(|| format!("failed to delete {:?}", runtime_test))?;
}
}

Ok(())
}

pub fn list_subsystem_mount_points() -> Result<Vec<PathBuf>> {
Ok(Process::myself()
.context("failed to get self")?
.mountinfo()
.context("failed to get mountinfo")?
.into_iter()
.filter_map(|m| {
if m.fs_type == "cgroup" {
Some(m.mount_point)
} else {
None
}
})
.collect())
}
177 changes: 177 additions & 0 deletions youki_integration_test/src/tests/cgroups/pids.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use std::{
fs,
path::{Path, PathBuf},
};

use anyhow::{bail, Context, Result};
use oci_spec::runtime::{LinuxBuilder, LinuxPidsBuilder, LinuxResourcesBuilder, Spec, SpecBuilder};
use test_framework::{test_result, ConditionalTest, TestGroup, TestResult};

use crate::utils::{
test_outside_container,
test_utils::{check_container_created, CGROUP_ROOT},
};

// SPEC: The runtime spec does not specify what the behavior should be if the limit is
// zero or negative. We assume that the number of pids should be unlimited in this case.

fn create_spec(cgroup_name: &str, limit: i64) -> Result<Spec> {
let spec = SpecBuilder::default()
.linux(
LinuxBuilder::default()
.cgroups_path(Path::new("/runtime-test").join(cgroup_name))
.resources(
LinuxResourcesBuilder::default()
.pids(
LinuxPidsBuilder::default()
.limit(limit)
.build()
.context("failed to build pids spec")?,
)
.build()
.context("failed to build resource spec")?,
)
.build()
.context("failed to build linux spec")?,
)
.build()
.context("failed to build spec")?;

Ok(spec)
}

// Tests if a specified limit was successfully set
fn test_positive_limit() -> TestResult {
let cgroup_name = "test_positive_limit";
let limit = 50;
let spec = test_result!(create_spec(cgroup_name, limit));

test_outside_container(spec, &|data| {
test_result!(check_container_created(&data));
test_result!(check_pid_limit_set(cgroup_name, limit));
TestResult::Passed
})
}

// Tests if a specified limit of zero sets the pid limit to unlimited
fn test_zero_limit() -> TestResult {
let cgroup_name = "test_zero_limit";
let limit = 0;
let spec = test_result!(create_spec(cgroup_name, limit));

test_outside_container(spec, &|data| {
test_result!(check_container_created(&data));
test_result!(check_pids_are_unlimited(cgroup_name));
TestResult::Passed
})
}

// Tests if a specified negative limit sets the pid limit to unlimited
fn test_negative_limit() -> TestResult {
let cgroup_name = "test_negative_limit";
let limit = -1;
let spec = test_result!(create_spec(cgroup_name, limit));

test_outside_container(spec, &|data| {
test_result!(check_container_created(&data));
test_result!(check_pids_are_unlimited(cgroup_name));
TestResult::Passed
})
}

fn check_pid_limit_set(cgroup_name: &str, expected: i64) -> Result<()> {
let cgroup_path = PathBuf::from(CGROUP_ROOT)
.join("pids/runtime-test")
.join(cgroup_name)
.join("pids.max");
let content = fs::read_to_string(&cgroup_path)
.with_context(|| format!("failed to read {:?}", cgroup_path))?;
let trimmed = content.trim();

if trimmed.is_empty() {
bail!(
"expected {:?} to contain a pid limit of {}, but it was empty",
cgroup_path,
expected
);
}

if trimmed == "max" {
bail!(
"expected {:?} to contain a pid limit of {}, but no limit was set",
cgroup_path,
expected
);
}

let actual: i64 = trimmed
.parse()
.with_context(|| format!("could not parse {:?}", trimmed))?;
if expected != actual {
bail!(
"expected {:?} to contain a pid limit of {}, but the limit was {}",
cgroup_path,
expected,
actual
);
}

Ok(())
}

fn check_pids_are_unlimited(cgroup_name: &str) -> Result<()> {
let cgroup_path = PathBuf::from(CGROUP_ROOT)
.join("pids/runtime-test")
.join(cgroup_name)
.join("pids.max");
let content = fs::read_to_string(&cgroup_path)
.with_context(|| format!("failed to read {:?}", cgroup_path))?;
let trimmed = content.trim();

if trimmed.is_empty() {
bail!(
"expected {:?} to contain a pid limit of max, but it was empty",
cgroup_path
);
}

if trimmed != "max" {
bail!(
"expected {:?} to contain 'max' (unlimited), but the limit was {}",
cgroup_path,
trimmed
);
}

Ok(())
}

fn can_run() -> bool {
Path::new("/sys/fs/cgroup/pids").exists()
}

pub fn get_test_group<'a>() -> TestGroup<'a> {
let mut test_group = TestGroup::new("cgroup_v1_pids");
let positive_limit = ConditionalTest::new(
"positive_pid_limit",
Box::new(can_run),
Box::new(test_positive_limit),
);
let zero_limit = ConditionalTest::new(
"zero_pid_limit",
Box::new(can_run),
Box::new(test_zero_limit),
);
let negative_limit = ConditionalTest::new(
"negative_pid_limit",
Box::new(can_run),
Box::new(test_negative_limit),
);

test_group.add(vec![
Box::new(positive_limit),
Box::new(zero_limit),
Box::new(negative_limit),
]);
test_group
}
Loading

0 comments on commit 9ed52d9

Please sign in to comment.