Skip to content

Commit 9315147

Browse files
committed
WIP tests: Rewrite the TSI test and also test dgram and unix sockets
Signed-off-by: Matej Hrica <[email protected]>
1 parent d1e569d commit 9315147

File tree

12 files changed

+748
-330
lines changed

12 files changed

+748
-330
lines changed

tests/guest-agent/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ fn run_guest_agent(test_name: &str) -> anyhow::Result<()> {
88
.into_iter()
99
.find(|t| t.name() == test_name)
1010
.context("No such test!")?;
11-
let TestCase { test, name: _, requires_namespace: _ } = test_case;
11+
let TestCase {
12+
test,
13+
name: _,
14+
requires_namespace: _,
15+
} = test_case;
1216
test.in_guest();
1317
Ok(())
1418
}

tests/runner/src/main.rs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn get_test(name: &str) -> anyhow::Result<Box<dyn Test>> {
2525
.map(|t| t.test)
2626
}
2727

28-
fn start_vm(test_setup: TestSetup) -> anyhow::Result<()> {
28+
fn start_vm(mut test_setup: TestSetup) -> anyhow::Result<()> {
2929
// Raise soft fd limit up to the hard limit
3030
let (_soft_limit, hard_limit) =
3131
getrlimit(Resource::RLIMIT_NOFILE).context("getrlimit RLIMIT_NOFILE")?;
@@ -40,6 +40,8 @@ fn start_vm(test_setup: TestSetup) -> anyhow::Result<()> {
4040
.map(|t| t.requires_namespace)
4141
.unwrap_or(false);
4242

43+
test_setup.requires_namespace = requires_namespace;
44+
4345
if requires_namespace {
4446
setup_namespace_and_run(test_setup)?;
4547
} else {
@@ -52,7 +54,7 @@ fn start_vm(test_setup: TestSetup) -> anyhow::Result<()> {
5254

5355
fn setup_namespace_and_run(test_setup: TestSetup) -> anyhow::Result<()> {
5456
use nix::sched::{unshare, CloneFlags};
55-
use nix::unistd::{fork, Gid, Uid, ForkResult};
57+
use nix::unistd::{fork, ForkResult, Gid, Uid};
5658
use std::fs;
5759

5860
// Get our current uid/gid before entering the namespace
@@ -65,17 +67,14 @@ fn setup_namespace_and_run(test_setup: TestSetup) -> anyhow::Result<()> {
6567

6668
// Set up uid_map to map our uid to root (0) in the namespace
6769
let uid_map = format!("0 {} 1", uid);
68-
fs::write("/proc/self/uid_map", uid_map)
69-
.context("Failed to write uid_map")?;
70+
fs::write("/proc/self/uid_map", uid_map).context("Failed to write uid_map")?;
7071

7172
// Disable setgroups (required before writing gid_map as non-root)
72-
fs::write("/proc/self/setgroups", "deny")
73-
.context("Failed to write setgroups")?;
73+
fs::write("/proc/self/setgroups", "deny").context("Failed to write setgroups")?;
7474

7575
// Set up gid_map to map our gid to root (0) in the namespace
7676
let gid_map = format!("0 {} 1", gid);
77-
fs::write("/proc/self/gid_map", gid_map)
78-
.context("Failed to write gid_map")?;
77+
fs::write("/proc/self/gid_map", gid_map).context("Failed to write gid_map")?;
7978

8079
// Fork so the child becomes PID 1 in the new PID namespace
8180
// This is necessary to be able to mount procfs
@@ -92,7 +91,47 @@ fn setup_namespace_and_run(test_setup: TestSetup) -> anyhow::Result<()> {
9291
}
9392
}
9493
ForkResult::Child => {
94+
use nix::mount::{mount, MsFlags};
95+
use std::fs::create_dir;
96+
9597
// Child continues - we are now PID 1 in the PID namespace
98+
// Set up the root directory structure (but don't chroot yet - that happens after krun loads libraries)
99+
let root_dir = test_setup.tmp_dir.join("root");
100+
create_dir(&root_dir).context("Failed to create root directory")?;
101+
102+
// Create necessary directories
103+
create_dir(root_dir.join("tmp")).context("Failed to create tmp directory")?;
104+
create_dir(root_dir.join("dev")).context("Failed to create dev directory")?;
105+
create_dir(root_dir.join("proc")).context("Failed to create proc directory")?;
106+
create_dir(root_dir.join("sys")).context("Failed to create sys directory")?;
107+
108+
// Copy guest agent
109+
let guest_agent_path = env::var_os("KRUN_TEST_GUEST_AGENT_PATH")
110+
.context("KRUN_TEST_GUEST_AGENT_PATH env variable not set")?;
111+
fs::copy(&guest_agent_path, root_dir.join("guest-agent"))
112+
.context("Failed to copy guest agent")?;
113+
114+
// Make mounts private so they don't affect parent namespace
115+
mount(
116+
None::<&str>,
117+
"/",
118+
None::<&str>,
119+
MsFlags::MS_REC | MsFlags::MS_PRIVATE,
120+
None::<&str>,
121+
)
122+
.context("Failed to make / private")?;
123+
124+
// Bind mount /dev
125+
mount(
126+
Some("/dev"),
127+
root_dir.join("dev").as_path(),
128+
None::<&str>,
129+
MsFlags::MS_BIND | MsFlags::MS_REC,
130+
None::<&str>,
131+
)
132+
.context("Failed to bind mount /dev")?;
133+
134+
// The test's start_vm will handle chroot after loading libraries
96135
let test = get_test(&test_setup.test_case)?;
97136
test.start_vm(test_setup.clone())
98137
.with_context(|| format!("testcase: {test_setup:?}"))?;
@@ -305,7 +344,11 @@ fn main() -> anyhow::Result<()> {
305344
let command = cli.command.unwrap_or_default();
306345

307346
match command {
308-
CliCommand::StartVm { test_case, tmp_dir } => start_vm(TestSetup { test_case, tmp_dir }),
347+
CliCommand::StartVm { test_case, tmp_dir } => start_vm(TestSetup {
348+
test_case,
349+
tmp_dir,
350+
requires_namespace: false, // Will be set by start_vm based on test case
351+
}),
309352
CliCommand::Test {
310353
test_case,
311354
base_dir,

tests/test_cases/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ name = "test_cases"
1212
[dependencies]
1313
krun-sys = { path = "../../krun-sys", optional = true }
1414
macros = { path = "../macros" }
15-
nix = { version = "0.29.0", features = ["socket", "sched", "user", "mount"] }
15+
nix = { version = "0.29.0", features = ["socket", "sched", "user", "mount", "fs"] }
1616
anyhow = "1.0.95"
1717
tempdir = "0.3.7"

tests/test_cases/src/common.rs

Lines changed: 40 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
//! Common utilities used by multiple test
1+
//! Common utilities used by multiple tests
22
33
use anyhow::Context;
44
use std::ffi::CString;
5-
use std::fs;
65
use std::fs::create_dir;
76
use std::os::unix::ffi::OsStrExt;
87
use std::path::Path;
@@ -11,125 +10,57 @@ use std::ptr::null;
1110
use crate::{krun_call, TestSetup};
1211
use krun_sys::*;
1312

14-
use nix::unistd::{chroot, chdir};
15-
use std::path::PathBuf;
16-
1713
fn copy_guest_agent(dir: &Path) -> anyhow::Result<()> {
1814
let path = std::env::var_os("KRUN_TEST_GUEST_AGENT_PATH")
1915
.context("KRUN_TEST_GUEST_AGENT_PATH env variable not set")?;
2016

2117
let output_path = dir.join("guest-agent");
22-
fs::copy(path, output_path).context("Failed to copy executable into vm")?;
18+
std::fs::copy(path, output_path).context("Failed to copy executable into vm")?;
2319
Ok(())
2420
}
2521

26-
/// Common part of most test. This setups an empty root filesystem, copies the guest agent there
27-
/// and runs the guest agent in the VM.
28-
/// Note that some tests might want to use a different root file system (perhaps a qcow image),
29-
/// in which case the test can implement the equivalent functionality itself, or better if there
30-
/// are more test doing that, add another utility method in this file.
22+
/// Common setup for most tests. Sets up the root filesystem and runs the guest agent in the VM.
3123
///
32-
/// The returned object is used for deleting the temporary files.
33-
pub fn setup_fs_and_enter(ctx: u32, test_setup: TestSetup) -> anyhow::Result<()> {
34-
let root_dir = test_setup.tmp_dir.join("root");
35-
create_dir(&root_dir).context("Failed to create root directory")?;
36-
37-
let path_str = CString::new(root_dir.as_os_str().as_bytes()).context("CString::new")?;
38-
copy_guest_agent(&root_dir)?;
39-
unsafe {
40-
krun_call!(krun_set_root(ctx, path_str.as_ptr()))?;
41-
krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?;
42-
let test_case_cstr = CString::new(test_setup.test_case).context("CString::new")?;
43-
let argv = [test_case_cstr.as_ptr(), null()];
44-
//let envp = [c"RUST_BACKTRACE=1".as_ptr(), null()];
45-
let envp = [null()];
46-
krun_call!(krun_set_exec(
47-
ctx,
48-
c"/guest-agent".as_ptr(),
49-
argv.as_ptr(),
50-
envp.as_ptr(),
51-
))?;
52-
krun_call!(krun_start_enter(ctx))?;
53-
}
54-
unreachable!()
55-
}
56-
57-
/// Like setup_fs_and_enter, but changes the host process's root to the guest's root
58-
/// before entering the VM. This is needed for Unix domain socket TSI tests where the
59-
/// host process needs to access socket paths in the guest filesystem.
24+
/// If `requires_namespace` is true, the runner has already created the root directory structure
25+
/// with /dev, /tmp, /sys, guest-agent. After krun_create_ctx loads libraries, we chroot there.
6026
///
61-
/// This function:
62-
/// 1. Creates a new user namespace and mount namespace (unshare CLONE_NEWUSER | CLONE_NEWNS)
63-
/// 2. Sets up uid/gid mappings to become root in the namespace
64-
/// 3. Changes root to the guest's root directory (chroot)
65-
/// 4. Then calls krun_start_enter
66-
///
67-
/// The before_enter callback is called after chroot but before krun_start_enter, allowing
68-
/// setup of host-side resources (like Unix domain socket servers) that need to be accessible
69-
/// at the same paths as the guest will use.
70-
///
71-
/// Note: This uses rootless namespaces (user namespaces) so it doesn't require root.
72-
pub fn setup_fs_and_enter_with_namespace<F>(
73-
ctx: u32,
74-
test_setup: TestSetup,
75-
before_enter: F,
76-
) -> anyhow::Result<()>
77-
where
78-
F: FnOnce() -> anyhow::Result<()>,
79-
{
80-
let root_dir = test_setup.tmp_dir.join("root");
81-
create_dir(&root_dir).context("Failed to create root directory")?;
82-
83-
// Create necessary directories in the guest root
84-
create_dir(root_dir.join("tmp")).context("Failed to create tmp directory")?;
85-
create_dir(root_dir.join("dev")).context("Failed to create dev directory")?;
86-
create_dir(root_dir.join("proc")).context("Failed to create proc directory")?;
87-
create_dir(root_dir.join("sys")).context("Failed to create sys directory")?;
88-
89-
copy_guest_agent(&root_dir)?;
90-
91-
// The runner has already set up the namespace for us (user+mount+pid)
92-
// We are now root in the user namespace and PID 1 in the PID namespace
93-
// Make our mounts private so they don't affect the parent namespace
94-
use nix::mount::{mount, MsFlags};
95-
mount(
96-
None::<&str>,
97-
"/",
98-
None::<&str>,
99-
MsFlags::MS_REC | MsFlags::MS_PRIVATE,
100-
None::<&str>,
101-
).context("Failed to make / private")?;
102-
103-
// Bind mount /dev into the guest root so /dev/kvm is accessible
104-
// (we're root in the namespace now)
105-
mount(
106-
Some("/dev"),
107-
root_dir.join("dev").as_path(),
108-
None::<&str>,
109-
MsFlags::MS_BIND | MsFlags::MS_REC,
110-
None::<&str>,
111-
).context("Failed to bind mount /dev")?;
112-
113-
// Now we can chroot
114-
let root_path = PathBuf::from(&root_dir);
115-
chroot(&root_path).context("Failed to chroot to guest root")?;
116-
chdir("/").context("Failed to chdir to /")?;
117-
118-
// Mount procfs after chroot with standard proc mount flags
119-
mount(
120-
Some("proc"),
121-
"/proc",
122-
Some("proc"),
123-
MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC,
124-
None::<&str>,
125-
).context("Failed to mount procfs")?;
126-
127-
// Call the before_enter callback to set up host-side resources
128-
before_enter().context("before_enter callback failed")?;
27+
/// If `requires_namespace` is false, this function creates a root directory, copies the
28+
/// guest agent there, and sets it as the VM root.
29+
pub fn setup_fs_and_enter(ctx: u32, test_setup: TestSetup) -> anyhow::Result<()> {
30+
let root_path = if test_setup.requires_namespace {
31+
// Runner set up the root dir structure, now we chroot after libraries are loaded
32+
use nix::mount::{mount, MsFlags};
33+
use nix::unistd::{chdir, chroot};
34+
35+
let root_dir = test_setup.tmp_dir.join("root");
36+
37+
// Chroot into the prepared root
38+
chroot(&root_dir).context("Failed to chroot")?;
39+
chdir("/").context("Failed to chdir to /")?;
40+
41+
// Mount procfs after chroot
42+
mount(
43+
Some("proc"),
44+
"/proc",
45+
Some("proc"),
46+
MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC,
47+
None::<&str>,
48+
)
49+
.context("Failed to mount procfs")?;
50+
51+
CString::new("/").context("CString::new")?
52+
} else {
53+
// Create root directory and copy guest agent
54+
let root_dir = test_setup.tmp_dir.join("root");
55+
create_dir(&root_dir).context("Failed to create root directory")?;
56+
// Create /tmp for tests that use Unix sockets
57+
let _ = create_dir(root_dir.join("tmp"));
58+
copy_guest_agent(&root_dir)?;
59+
CString::new(root_dir.as_os_str().as_bytes()).context("CString::new")?
60+
};
12961

130-
let path_str = CString::new("/").context("CString::new")?;
13162
unsafe {
132-
krun_call!(krun_set_root(ctx, path_str.as_ptr()))?;
63+
krun_call!(krun_set_root(ctx, root_path.as_ptr()))?;
13364
krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?;
13465
let test_case_cstr = CString::new(test_setup.test_case).context("CString::new")?;
13566
let argv = [test_case_cstr.as_ptr(), null()];

0 commit comments

Comments
 (0)