@@ -12,6 +12,7 @@ use std::process::Command;
1212use crate :: {
1313 get_bck_command, get_test_image, run_bcvk, run_bcvk_nocapture, LIBVIRT_INTEGRATION_TEST_LABEL ,
1414} ;
15+ use bcvk:: xml_utils:: parse_xml_dom;
1516
1617/// Test libvirt list functionality (lists domains)
1718pub fn test_libvirt_list_functionality ( ) {
@@ -807,3 +808,158 @@ pub fn test_libvirt_error_handling() {
807808
808809 println ! ( "libvirt error handling validated" ) ;
809810}
811+
812+ /// Test transient VM functionality
813+ pub fn test_libvirt_transient_vm ( ) {
814+ let bck = get_bck_command ( ) . unwrap ( ) ;
815+ let test_image = get_test_image ( ) ;
816+
817+ // Generate unique domain name for this test
818+ let domain_name = format ! (
819+ "test-transient-{}" ,
820+ std:: time:: SystemTime :: now( )
821+ . duration_since( std:: time:: UNIX_EPOCH )
822+ . unwrap( )
823+ . as_secs( )
824+ ) ;
825+
826+ println ! ( "Testing transient VM with domain: {}" , domain_name) ;
827+
828+ // Cleanup any existing domain with this name
829+ cleanup_domain ( & domain_name) ;
830+
831+ // Create transient domain
832+ println ! ( "Creating transient libvirt domain..." ) ;
833+ let create_output = run_bcvk ( & [
834+ "libvirt" ,
835+ "run" ,
836+ "--name" ,
837+ & domain_name,
838+ "--label" ,
839+ LIBVIRT_INTEGRATION_TEST_LABEL ,
840+ "--transient" ,
841+ "--filesystem" ,
842+ "ext4" ,
843+ & test_image,
844+ ] )
845+ . expect ( "Failed to run libvirt run with --transient" ) ;
846+
847+ println ! ( "Create stdout: {}" , create_output. stdout) ;
848+ println ! ( "Create stderr: {}" , create_output. stderr) ;
849+
850+ if !create_output. success ( ) {
851+ cleanup_domain ( & domain_name) ;
852+ panic ! (
853+ "Failed to create transient domain: {}" ,
854+ create_output. stderr
855+ ) ;
856+ }
857+
858+ println ! ( "Successfully created transient domain: {}" , domain_name) ;
859+
860+ // Verify domain is transient using virsh dominfo
861+ println ! ( "Verifying domain is marked as transient..." ) ;
862+ let dominfo_output = Command :: new ( "virsh" )
863+ . args ( & [ "dominfo" , & domain_name] )
864+ . output ( )
865+ . expect ( "Failed to run virsh dominfo" ) ;
866+
867+ if !dominfo_output. status . success ( ) {
868+ cleanup_domain ( & domain_name) ;
869+ let stderr = String :: from_utf8_lossy ( & dominfo_output. stderr ) ;
870+ panic ! ( "Failed to get domain info: {}" , stderr) ;
871+ }
872+
873+ let dominfo = String :: from_utf8_lossy ( & dominfo_output. stdout ) ;
874+ println ! ( "Domain info:\n {}" , dominfo) ;
875+
876+ // Verify "Persistent: no" appears in dominfo
877+ assert ! (
878+ dominfo. contains( "Persistent:" ) && dominfo. contains( "no" ) ,
879+ "Domain should be marked as non-persistent (transient). dominfo: {}" ,
880+ dominfo
881+ ) ;
882+ println ! ( "✓ Domain is correctly marked as transient (Persistent: no)" ) ;
883+
884+ // Verify domain XML contains transient disk element
885+ println ! ( "Checking domain XML for transient disk configuration..." ) ;
886+ let dumpxml_output = Command :: new ( "virsh" )
887+ . args ( & [ "dumpxml" , & domain_name] )
888+ . output ( )
889+ . expect ( "Failed to dump domain XML" ) ;
890+
891+ let domain_xml = String :: from_utf8_lossy ( & dumpxml_output. stdout ) ;
892+
893+ // Parse the XML properly using our XML parser
894+ let xml_dom = parse_xml_dom ( & domain_xml) . expect ( "Failed to parse domain XML" ) ;
895+
896+ // Verify domain XML contains transient disk element
897+ let has_transient = xml_dom. find ( "transient" ) . is_some ( ) ;
898+ assert ! (
899+ has_transient,
900+ "Domain XML should contain transient disk element"
901+ ) ;
902+ println ! ( "✓ Domain XML contains transient disk element" ) ;
903+
904+ // Extract the base disk path from the domain XML using proper XML parsing
905+ let base_disk_path = xml_dom
906+ . find ( "source" )
907+ . and_then ( |source_node| source_node. attributes . get ( "file" ) )
908+ . map ( |s| s. to_string ( ) ) ;
909+
910+ println ! ( "Base disk path: {:?}" , base_disk_path) ;
911+
912+ // Stop the domain (this should make it disappear since it's transient)
913+ println ! ( "Stopping transient domain (should disappear)..." ) ;
914+ let destroy_output = Command :: new ( "virsh" )
915+ . args ( & [ "destroy" , & domain_name] )
916+ . output ( )
917+ . expect ( "Failed to run virsh destroy" ) ;
918+
919+ if !destroy_output. status . success ( ) {
920+ let stderr = String :: from_utf8_lossy ( & destroy_output. stderr ) ;
921+ panic ! ( "Failed to stop domain: {}" , stderr) ;
922+ }
923+
924+ // Poll for domain disappearance with timeout
925+ println ! ( "Verifying domain has disappeared..." ) ;
926+ let start_time = std:: time:: Instant :: now ( ) ;
927+ let timeout = std:: time:: Duration :: from_secs ( 10 ) ;
928+ let mut domain_disappeared = false ;
929+
930+ while start_time. elapsed ( ) < timeout {
931+ let list_output = Command :: new ( "virsh" )
932+ . args ( & [ "list" , "--all" , "--name" ] )
933+ . output ( )
934+ . expect ( "Failed to list domains" ) ;
935+
936+ let domain_list = String :: from_utf8_lossy ( & list_output. stdout ) ;
937+ if !domain_list. contains ( & domain_name) {
938+ domain_disappeared = true ;
939+ break ;
940+ }
941+
942+ // Wait briefly before checking again
943+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 200 ) ) ;
944+ }
945+
946+ assert ! (
947+ domain_disappeared,
948+ "Transient domain should have disappeared after shutdown within {} seconds" ,
949+ timeout. as_secs( )
950+ ) ;
951+ println ! ( "✓ Transient domain disappeared after shutdown" ) ;
952+
953+ // Verify base disk still exists (only the overlay was removed)
954+ if let Some ( ref disk_path) = base_disk_path {
955+ println ! ( "Verifying base disk still exists: {}" , disk_path) ;
956+ let disk_exists = std:: path:: Path :: new ( disk_path) . exists ( ) ;
957+ assert ! (
958+ disk_exists,
959+ "Base disk should still exist after transient domain shutdown"
960+ ) ;
961+ println ! ( "✓ Base disk still exists (not deleted)" ) ;
962+ }
963+
964+ println ! ( "✓ Transient VM test passed" ) ;
965+ }
0 commit comments