@@ -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+
148232fn 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)
312444fn 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.\n Stderr:\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.\n Stderr:\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