Skip to content

Commit e732b88

Browse files
behoubarst0git
authored andcommitted
test: add global config end-to-end test
Signed-off-by: Kouame Behouba Manasse <[email protected]>
1 parent 56f0a62 commit e732b88

File tree

1 file changed

+251
-33
lines changed

1 file changed

+251
-33
lines changed

tests/e2e_criu.rs

Lines changed: 251 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,90 @@ fn setup(port: u16) -> Vec<TestProcess> {
145145
processes
146146
}
147147

148+
fn setup_with_global_config(port: u16) -> Vec<TestProcess> {
149+
println!("Setting up test environment with global config");
150+
151+
let make_status = Command::new("make")
152+
.current_dir("tests")
153+
.status()
154+
.expect("Failed to run `make` in tests directory");
155+
assert!(make_status.success(), "make command failed");
156+
157+
fs::create_dir_all("/etc/criu").expect("Failed to create /etc/criu directory");
158+
159+
// Create the global configuration file
160+
let global_config_path = "/etc/criu/criu-coordinator.json";
161+
let mut config_file =
162+
fs::File::create(global_config_path).expect("Failed to create global config file");
163+
let config_content = format!(
164+
r#"{{
165+
"address": "127.0.0.1",
166+
"port": "{port}",
167+
"log-file": "/tmp/criu-coordinator-global.log",
168+
"dependencies": {{
169+
"loop-1": ["loop-2", "loop-3"],
170+
"loop-2": ["loop-1", "loop-3"],
171+
"loop-3": ["loop-1", "loop-2"]
172+
}}
173+
}}"#
174+
);
175+
176+
config_file
177+
.write_all(config_content.as_bytes())
178+
.expect("Failed to write to global config file");
179+
println!("Created global config at {global_config_path}");
180+
181+
let mut processes = vec![];
182+
let p_names = ["loop-1", "loop-2", "loop-3"];
183+
184+
for name in p_names.iter() {
185+
let image_dir = env::temp_dir().join(format!(
186+
"criu-e2e-test-global-{}-{}",
187+
name,
188+
std::process::id()
189+
));
190+
let _ = fs::remove_dir_all(&image_dir);
191+
fs::create_dir_all(&image_dir).expect("Failed to create image directory");
192+
193+
let dependencies: Vec<String> = p_names
194+
.iter()
195+
.filter(|&p| p != name)
196+
.map(|&s| s.to_string())
197+
.collect();
198+
199+
let mut command = Command::new(format!("./tests/{name}"));
200+
command
201+
.stdout(Stdio::null())
202+
.stderr(Stdio::null())
203+
.stdin(Stdio::null());
204+
205+
unsafe {
206+
command.pre_exec(|| {
207+
if libc::setsid() == -1 {
208+
return Err(std::io::Error::last_os_error());
209+
}
210+
Ok(())
211+
});
212+
}
213+
214+
let child = command
215+
.spawn()
216+
.unwrap_or_else(|_| panic!("Failed to spawn process {}", name));
217+
let pid = child.id();
218+
219+
processes.push(TestProcess {
220+
id: name.to_string(),
221+
child: Some(child),
222+
pid,
223+
image_dir,
224+
_dependencies: dependencies,
225+
});
226+
println!("Spawned '{name}' with PID {pid}");
227+
}
228+
thread::sleep(Duration::from_millis(500));
229+
processes
230+
}
231+
148232
fn cleanup(server: &mut Child, processes: &mut [TestProcess]) {
149233
println!("\n--- Cleaning up test environment ---");
150234
let _ = server.kill();
@@ -172,6 +256,13 @@ fn cleanup(server: &mut Child, processes: &mut [TestProcess]) {
172256
let _ = fs::remove_dir_all(&p.image_dir);
173257
}
174258

259+
// Clean up global config file and directory
260+
let global_config_path = PathBuf::from("/etc/criu/criu-coordinator.json");
261+
if global_config_path.exists() {
262+
fs::remove_file(&global_config_path).expect("Failed to remove global config file");
263+
println!("Removed global config file.");
264+
}
265+
175266
let make_clean_status = Command::new("make")
176267
.arg("clean")
177268
.current_dir("tests")
@@ -307,6 +398,47 @@ fn setup_tcp_test(coordinator_port: u16, tcp_server_port: u16) -> Vec<TestProces
307398
processes
308399
}
309400

401+
fn check_tcp_connection(server_pid: u32, client_pid: u32, server_port: u16) -> bool {
402+
let output = match Command::new("ss").args(["-tpen"]).output() {
403+
Ok(out) => out,
404+
Err(e) => {
405+
println!("Failed to execute 'ss' command: {e}. Is it installed?");
406+
return false;
407+
}
408+
};
409+
410+
if !output.status.success() {
411+
println!("'ss' command failed with status: {}", output.status);
412+
return false;
413+
}
414+
415+
let stdout = String::from_utf8_lossy(&output.stdout);
416+
let server_pid_str = format!("pid={server_pid}");
417+
let client_pid_str = format!("pid={client_pid}");
418+
let server_port_str = format!(":{server_port}");
419+
420+
// We expect to find two entries for the connections for each process.
421+
let server_conn_found = stdout.lines().any(|line| {
422+
line.contains("ESTAB") && line.contains(&server_port_str) && line.contains(&server_pid_str)
423+
});
424+
425+
let client_conn_found = stdout.lines().any(|line| {
426+
line.contains("ESTAB") && line.contains(&server_port_str) && line.contains(&client_pid_str)
427+
});
428+
429+
if !server_conn_found || !client_conn_found {
430+
println!("Could not verify established TCP connection via 'ss'.");
431+
if !server_conn_found {
432+
println!("Did not find connection for server PID {server_pid} on port {server_port}");
433+
}
434+
if !client_conn_found {
435+
println!("Did not find connection for client PID {client_pid} to port {server_port}");
436+
}
437+
}
438+
439+
server_conn_found && client_conn_found
440+
}
441+
310442
#[test]
311443
#[ignore] // requires require root privileges (make test-e2e)
312444
fn e2e_dump_and_restore_with_criu() {
@@ -317,7 +449,7 @@ fn e2e_dump_and_restore_with_criu() {
317449

318450
assert!(is_criu_installed(), "CRIU command not found in PATH");
319451

320-
let coordinator_path = fs::canonicalize("target/debug/criu-coordinator")
452+
let coordinator_path = fs::canonicalize(CRIU_COORDINATOR_PATH)
321453
.expect("Could not find criu-coordinator binary. Run 'cargo build' first.")
322454
.to_str()
323455
.unwrap()
@@ -418,48 +550,134 @@ fn e2e_dump_and_restore_with_criu() {
418550
}
419551
}
420552

553+
#[test]
554+
#[ignore]
555+
fn e2e_dump_and_restore_with_global_config() {
556+
assert!(
557+
is_root(),
558+
"This test must be run with root privileges for 'criu' and to write to /etc."
559+
);
560+
assert!(is_criu_installed(), "CRIU command not found in PATH");
421561

422-
fn check_tcp_connection(server_pid: u32, client_pid: u32, server_port: u16) -> bool {
423-
let output = match Command::new("ss").args(["-tpen"]).output() {
424-
Ok(out) => out,
425-
Err(e) => {
426-
println!("Failed to execute 'ss' command: {e}. Is it installed?");
427-
return false;
428-
}
429-
};
562+
let coordinator_path = fs::canonicalize(CRIU_COORDINATOR_PATH)
563+
.expect("Could not find criu-coordinator binary. Run 'cargo build' first.")
564+
.to_str()
565+
.unwrap()
566+
.to_owned();
430567

431-
if !output.status.success() {
432-
println!("'ss' command failed with status: {}", output.status);
433-
return false;
434-
}
568+
let port = pick_port();
569+
let addr = format!("127.0.0.1:{port}");
570+
let server = spawn_server(port);
571+
assert!(
572+
server_ready(&addr, 20),
573+
"Server failed to start at {}",
574+
addr
575+
);
435576

436-
let stdout = String::from_utf8_lossy(&output.stdout);
437-
let server_pid_str = format!("pid={server_pid}");
438-
let client_pid_str = format!("pid={client_pid}");
439-
let server_port_str = format!(":{server_port}");
577+
let processes = setup_with_global_config(port);
578+
let mut _guard = TestGuard { server, processes };
440579

441-
// We expect to find two entries for the connections for each process.
442-
let server_conn_found = stdout.lines().any(|line| {
443-
line.contains("ESTAB") && line.contains(&server_port_str) && line.contains(&server_pid_str)
444-
});
580+
println!("\n--- Starting checkpoint phase (global config) ---");
581+
let mut dump_handles = vec![];
582+
for p in &_guard.processes {
583+
let coordinator_path_clone = coordinator_path.clone();
584+
let p_id = p.id.clone();
585+
let p_pid = p.pid;
586+
let p_image_dir = p.image_dir.clone();
587+
dump_handles.push(thread::spawn(move || {
588+
let out = Command::new("sudo")
589+
.args([
590+
"criu",
591+
"dump",
592+
"-t",
593+
&p_pid.to_string(),
594+
"-D",
595+
p_image_dir.to_str().unwrap(),
596+
"-j",
597+
"-v4",
598+
"--action-script",
599+
&coordinator_path_clone,
600+
])
601+
.output()
602+
.expect("failed to execute criu");
603+
(p_id, out)
604+
}));
605+
}
445606

446-
let client_conn_found = stdout.lines().any(|line| {
447-
line.contains("ESTAB") && line.contains(&server_port_str) && line.contains(&client_pid_str)
448-
});
607+
for handle in dump_handles {
608+
let (id, output) = handle.join().unwrap();
609+
let stderr = String::from_utf8_lossy(&output.stderr);
610+
assert!(
611+
output.status.success() && stderr.contains("Dumping finished successfully"),
612+
"CRIU failed for process '{}' with global config.\nStderr:\n{}",
613+
id,
614+
stderr
615+
);
616+
println!("Checkpoint successful for {id}");
617+
}
449618

450-
if !server_conn_found || !client_conn_found {
451-
println!("Could not verify established TCP connection via 'ss'.");
452-
if !server_conn_found {
453-
println!("Did not find connection for server PID {server_pid} on port {server_port}");
454-
}
455-
if !client_conn_found {
456-
println!("Did not find connection for client PID {client_pid} to port {server_port}");
619+
println!("\n--- REAPING checkpointed processes (global config) ---");
620+
for p in &mut _guard.processes {
621+
if let Some(mut child) = p.child.take() {
622+
match child.wait() {
623+
Ok(status) => println!(
624+
"Reaped process {} (PID {}) with exit status: {}",
625+
p.id, p.pid, status
626+
),
627+
Err(e) => eprintln!("Error reaping process {} (PID {}): {}", p.id, p.pid, e),
628+
}
457629
}
458630
}
459631

460-
server_conn_found && client_conn_found
461-
}
632+
thread::sleep(Duration::from_millis(500));
633+
println!("\n--- Starting restore phase (global config) ---");
634+
let mut restore_handles = vec![];
635+
for p in &_guard.processes {
636+
let coordinator_path_clone = coordinator_path.clone();
637+
let p_id = p.id.clone();
638+
let p_image_dir = p.image_dir.clone();
639+
restore_handles.push(thread::spawn(move || {
640+
let out = Command::new("sudo")
641+
.args([
642+
"criu",
643+
"restore",
644+
"-D",
645+
p_image_dir.to_str().unwrap(),
646+
"-d",
647+
"-v4",
648+
"--action-script",
649+
&coordinator_path_clone,
650+
])
651+
.output()
652+
.expect("failed to execute criu restore");
653+
(p_id, out)
654+
}));
655+
}
462656

657+
for handle in restore_handles {
658+
let (id, output) = handle.join().unwrap();
659+
let stderr = String::from_utf8_lossy(&output.stderr);
660+
assert!(
661+
output.status.success() && stderr.contains("Restore finished successfully"),
662+
"CRIU restore failed for process '{}' with global config.\nStderr:\n{}",
663+
id,
664+
stderr
665+
);
666+
println!("Restore successful for {id}");
667+
}
668+
669+
thread::sleep(Duration::from_millis(500));
670+
671+
println!("\n--- VERIFYING restored processes (global config) ---");
672+
for p in &_guard.processes {
673+
assert!(
674+
get_pid_by_name(&p.id).is_some(),
675+
"Process {} was not found running after restore.",
676+
p.id
677+
);
678+
println!("Verified process {} is running.", p.id);
679+
}
680+
}
463681

464682
#[test]
465683
#[ignore] // requires require root privileges (make test-e2e)

0 commit comments

Comments
 (0)