1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! VM-backed end-to-end test for the
//! `#[ktstr_test(workload_root_cgroup = "/path")]` attribute.
//!
//! Locks in the framework-side workload-cgroup parent slot:
//!
//! * `KtstrTestEntry::workload_root_cgroup` flows through the
//! VM builder onto `KtstrVm::workload_root_cgroup`.
//! * The initramfs suffix stamps the path into
//! `/workload_root_cgroup` (see
//! `vmm::initramfs::SuffixParams::workload_root_cgroup`).
//! * The guest's `vmm::rust_init::create_workload_root_cgroup_from_file`
//! mkdir's `/sys/fs/cgroup{path}` BEFORE `start_scheduler`
//! fires and enables `+cpuset +cpu` on every ancestor's
//! `subtree_control`.
//! * The guest's
//! `test_support::args::resolve_cgroup_root` returns the same
//! path so every `ctx.cgroup_def(...)` lands under
//! `/sys/fs/cgroup{path}/...` instead of the legacy
//! `/sys/fs/cgroup/ktstr` default.
//!
//! Distinct from `cgroup_parent_multilevel.rs`, which covers the
//! per-scheduler `Scheduler::cgroup_parent` slot (where the
//! scheduler process is placed). The two concerns are independent
//! — this test pins only the workload-side path.
//!
//! The test boots a guest with `workload_root_cgroup =
//! "/ktstr-workload-root"` and asserts:
//! 1. `/sys/fs/cgroup/ktstr-workload-root` exists as a directory.
//! 2. Its `cgroup.subtree_control` advertises both `cpuset` and
//! `cpu`.
//! 3. A workload cgroup defined via `ctx.cgroup_def("cg_wlr_0")`
//! lands at `/sys/fs/cgroup/ktstr-workload-root/cg_wlr_0`
//! (proving `resolve_cgroup_root` honoured the file).
//!
//! Without the wiring, every step fails: the directory wouldn't
//! exist, the subtree_control would be missing controllers, and
//! the workload cgroup would fall back to
//! `/sys/fs/cgroup/ktstr/cg_wlr_0`.
use anyhow::Result;
use ktstr::assert::{AssertDetail, AssertResult, DetailKind};
use ktstr::ktstr_test;
use ktstr::scenario::Ctx;
use ktstr::scenario::ops::{HoldSpec, Step, execute_steps};
use ktstr::test_support::{Scheduler, SchedulerSpec};
use std::fs;
const KTSTR_SCHED: Scheduler =
Scheduler::named("ktstr_sched").binary(SchedulerSpec::Discover("scx-ktstr"));
#[ktstr_test(
scheduler = KTSTR_SCHED,
workload_root_cgroup = "/ktstr-workload-root",
llcs = 1,
cores = 1,
threads = 1,
memory_mib = 256,
duration_s = 3,
auto_repro = false,
)]
fn workload_root_cgroup_creates_dir_and_hosts_workload_cgroups(ctx: &Ctx) -> Result<AssertResult> {
// Run a tiny no-workload scenario so the host-side
// `vmm::rust_init::create_workload_root_cgroup_from_file` has
// fired by the time the assertions below run. The actual
// CgroupManager-managed workload cgroup creation is exercised
// implicitly by every other scenario test that uses
// `ctx.cgroup_def(...)`; this test only locks in the per-test
// workload-root pipeline (file write, file read, mkdir,
// subtree-controller walk).
let steps = vec![Step {
setup: vec![ctx.cgroup_def("cg_wlr_0")].into(),
ops: vec![],
hold: HoldSpec::FULL,
}];
let _ = execute_steps(ctx, steps)?;
// 1. Workload-root directory exists.
let root = "/sys/fs/cgroup/ktstr-workload-root";
if !std::path::Path::new(root).is_dir() {
return Ok(AssertResult::fail(AssertDetail::new(
DetailKind::Other,
format!(
"{root} does not exist. \
create_workload_root_cgroup_from_file should have \
mkdir'd it from /workload_root_cgroup before the \
scheduler started — the initramfs suffix writer or \
the guest reader is broken."
),
)));
}
// 2. Parent (the cgroup root)'s subtree_control advertises
// cpuset+cpu — proves `enable_subtree_controllers_to` ran for
// every ancestor between the leaf and the root. The leaf
// (workload_root_cgroup) itself is NOT written by the
// `create_workload_root_cgroup_from_file` walk (the walk
// mirrors `cgroup_parent_multilevel.rs`'s leaf-skipping
// contract); the CgroupManager populates the leaf's
// subtree_control later when it materialises the per-test
// child cgroup. Checking the parent's subtree_control here
// verifies the framework's own walk fired and is the only
// assertion guaranteed to be valid by the time `execute_steps`
// returns.
let parent_control_path = "/sys/fs/cgroup/cgroup.subtree_control";
let parent_contents = fs::read_to_string(parent_control_path)
.map_err(|e| anyhow::anyhow!("read {parent_control_path}: {e}"))?;
let tokens: std::collections::HashSet<&str> = parent_contents.split_whitespace().collect();
if !tokens.contains("cpuset") || !tokens.contains("cpu") {
return Ok(AssertResult::fail(AssertDetail::new(
DetailKind::Other,
format!(
"{parent_control_path} = {parent_contents:?}: missing \
one of cpuset/cpu. create_workload_root_cgroup_from_file \
should have called enable_subtree_controllers_to which \
walks from the leaf's parent up to /sys/fs/cgroup, \
writing `+cpuset +cpu` at each ancestor."
),
)));
}
Ok(AssertResult::pass())
}