Skip to content

test(contest): add cgroup v2 memory integration tests - #3725

Merged
saku3 merged 2 commits into
youki-dev:mainfrom
ARMeeru:fix/cgroup-v2-memory-integration-test
Sep 19, 2026
Merged

saku3 merged 2 commits into
youki-dev:mainfrom
ARMeeru:fix/cgroup-v2-memory-integration-test

Conversation

@ARMeeru

@ARMeeru ARMeeru commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds cgroup v2 integration tests for the memory resource on the unified hierarchy, as tests/cgroups/memory.rs, following the flat per-controller layout discussed in #3605 and rebased onto #3727 after the cgroup v1 test removal.

Covered:

  • memory.limit is written to memory.max
  • memory.reservation is written to memory.low
  • with a limit and a swap value both set, memory.max holds the limit and memory.swap.max holds the cgroup v1 to v2 converted swap - limit

Type of Change

  • Test updates

Testing

Ran the new group against a youki release build, three times consecutively, and against runc 1.3.4 as the reference runtime:

# against youki
1 / 3 : test_memory_limit_set : ok
2 / 3 : test_memory_reservation_set : ok
3 / 3 : test_memory_swap_set : ok

# against runc 1.3.4
1 / 3 : test_memory_limit_set : ok
2 / 3 : test_memory_reservation_set : ok
3 / 3 : test_memory_swap_set : ok

cargo clippy -p contest --all-targets -- -D warnings and cargo fmt -p contest -- --check are clean.

Related Issues

Refs #3605 (memory controller; the other controllers can follow separately)

Additional Context

memory.high and memory.swappiness are not used: neither is part of the cgroup v2 memory interface (swappiness does not exist there, and memory.high is a throttling watermark with no counterpart in the runtime spec).

@ARMeeru
ARMeeru force-pushed the fix/cgroup-v2-memory-integration-test branch 2 times, most recently from 9f80d81 to 9172973 Compare September 11, 2026 05:15
@ARMeeru
ARMeeru marked this pull request as ready for review September 11, 2026 05:23
@ARMeeru

ARMeeru commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Obsolete.

I believe this validate failure is intermittent and unrelated to the change.

cgroup_v2_memory passed in the same job, against runc:

1 / 3 : test_memory_limit_set : ok
2 / 3 : test_memory_reservation_set : ok
3 / 3 : test_memory_swap_set : ok

The test that failed is in the terminal group, terminal_exec_background_process_does_not_block. It exited 0 and printed the marker, but the assertion compares lines exactly:

missing exec completion marker: output="... process exited status=0\r\nEXEC_DONE\r\r\n"

saw_line (tests/contest/contest/src/tests/terminal/mod.rs:81) is stdout.lines().any(|line| line == marker), and str::lines() strips one \r from a \r\n, so a \r\r\n ending leaves EXEC_DONE\r rather than EXEC_DONE.

I also noticed this job has failed on other branches with different terminal tests (add-restore failed on checkpoint_and_restore_foreground_pty), while main passed it on 2026-09-10.

@saku3 saku3 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR! I've reviewed it. Could you please take a look at my comments?

let actual = data
.parse::<i64>()
.with_context(|| format!("failed to parse {data:?}"))?;
assert_result_eq!(actual, expected, "unexpected memory.max")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the actual and expected arguments are in the wrong order.

macro_rules! assert_result_eq {

})
}

fn check_memory_max(cgroup_name: &str, expected: i64) -> Result<()> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think check_memory_max, check_memory_low, and check_memory_swap_max can be combined into a single function.

assert_result_eq!(actual, expected, "unexpected memory.low")
}

fn check_memory_swap_max(cgroup_name: &str, expected: i64) -> Result<()> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check whether swap is enabled.

let have_swap = cgroup_path.join("memory.swap.max").exists();

Cover the memory resource on the unified hierarchy: memory.limit is
written to memory.max, memory.reservation to memory.low, and when a
limit and a swap value are both set, memory.swap.max holds the cgroup v1
to v2 converted swap - limit.

Refs youki-dev#3605

Signed-off-by: Asifur Rahaman Meeru <asifur.rahaman@meeru.dev>
@ARMeeru
ARMeeru force-pushed the fix/cgroup-v2-memory-integration-test branch from e69db88 to d23b5d8 Compare September 19, 2026 04:46
@ARMeeru

ARMeeru commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the PR! I've reviewed it. Could you please take a look at my comments?

Thanks for the review. All three points addressed:

  • Swapped the assert_result_eq! arguments to the macro's (expected, actual) order.
  • Merged check_memory_max / check_memory_low / check_memory_swap_max into one check_cgroup_file(cgroup_name, cgroup_file, expected).
  • Gated the swap test on swap support. Since the swap limit is applied at create time, checking memory.swap.max inside the test would be too late, so the test runs under a can_run_swap condition instead. It reads the cgroup of the contest process from /proc/self/cgroup and checks for the swap file there. A root-path check like the one cpu/v2.rs does for cpu.idle would always fail, since memory.swap.max only exists on non-root cgroups.

Ran the group against youki and runc 1.3.4, 3/3 with both. fmt and clippy are clean. Happy to make further changes if required.

@ARMeeru
ARMeeru requested a review from saku3 September 19, 2026 05:05
@saku3

saku3 commented Sep 19, 2026

Copy link
Copy Markdown
Member

I wonder why adding this test causes the mount_propagation tests to fail...

@ARMeeru

ARMeeru commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor Author

I wonder why adding this test causes the mount_propagation tests to fail...

I believe this failure is unrelated to this PR, though I see why it looks otherwise: this is the first CI run to execute the new mount_propagation tests. That group only landed on main on 09-18 via #3699, and no PR ran CI after that until this one. The same tests passed against runc in #3699's own run on 09-07, on the previous runner image (20260831.293.1), and the test code hasn't changed since.

On the new image (20260907.300.1) the group fails identically against both youki and runc, with the propagation flags missing from mountinfo altogether ("optional fields []"), which suggests the runner environment rather than either runtime or this PR. The validate job has been failing intermittently on other unrelated groups on this image too (io_priority on relative-blkio, checkpoint_restore on add-restore), and both recovered on re-run.

Re-running the failed jobs might settle it. Would you mind doing that to verify this?

Edit: Thanks for the rerun. I'm now a bit lost so I'll do some research and reach out to you.

@ARMeeru

ARMeeru commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

@saku3 You were right to suspect this, and I owe a correction: the runner image is not the cause. main's own validate run on 7cd4328 passed mount_propagation 8/8 on the same image, so the failure is specific to this PR, and I have now reproduced it locally.

contest runs the parallel test groups in chunks of CPU count, in alphabetical order. Adding cgroup_v2_memory shifts every later group by one slot, which puts mount_propagation in the same chunk as ns_itype. On main they run in different chunks, which is why main is green. Locally, contest -t mount_propagation ns_itype against runc fails exactly the three tests CI shows (private, rslave, slave), while mount_propagation alone, or paired with mounts_recursive or no_pivot, passes.

ns_itype creates a container with an empty namespace list, so it shares the host mount namespace. Neither runtime gates its rootfs preparation on having a mount namespace. runc's prepareRoot runs mount("", "/", "", MS_SLAVE|MS_REC) and rootfsParentMountPrivate, and youki's mount_to_rootfs does the same MS_REC|MS_SLAVE on /. In that container both land on the host's root and recursively strip every mount under it of its peer group. A mount with no remaining peers becomes private, not slave, so the copies inside mount_propagation's containers lose their master:N and show up as "optional fields []". The tests that assert inherited propagation (private, slave, rslave) fail, and the rest pass because their option either creates fresh state or ends at private regardless. The eight clean_mount EBUSY lines are the same cause: without propagation the hidden copy of sub under / is no longer unmounted along with the visible one, and remove_dir_all hits a mountpoint.

The memory tests are not the defect, but adding the group did expose two things. First, ns_itype mutates host mount state and so must not run in parallel with the mount tests, and set_nonparallel() on it fixes the scheduling. With that patch the same pair passes locally and the full chunk keeps mount_propagation 8/8. Second, both runtimes apply the rootfs propagation flags to the host tree when the spec omits the mount namespace, which looks like a bug in its own right. I can send the set_nonparallel change as a separate PR and open an issue for the runtime behaviour, or fold the first into this PR if you prefer.

@saku3

saku3 commented Sep 19, 2026

Copy link
Copy Markdown
Member

@ARMeeru
Thank you. I was able to reproduce the issue as well.

Could you also include a fix in this PR to apply set_nonparallel() to the ns_itype test?

The runtime behavior should be considered separately.

ns_itype creates a container without a mount namespace, so the runtime
applies its rootfs preparation (make-rslave) to the host mount tree.
That breaks the mount propagation tests when the groups run in parallel,
which the new cgroup_v2_memory group exposed by shifting the batch
boundaries.

Signed-off-by: Asifur Rahaman Meeru <asifur.rahaman@meeru.dev>
@ARMeeru

ARMeeru commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

@saku3 Done, set_nonparallel() on ns_itype is in as a second commit (82018c8), and the runtime behavior is filed separately as #3733.

CI is waiting on workflow approval again after the push.

@saku3 saku3 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@saku3
saku3 merged commit 82bd4b9 into youki-dev:main Sep 19, 2026
30 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants