@@ -6,7 +6,9 @@ use dstack_kms_rpc::kms_client::KmsClient;
66use dstack_types:: shared_filenames:: {
77 compat_v3, APP_COMPOSE , ENCRYPTED_ENV , INSTANCE_INFO , SYS_CONFIG , USER_CONFIG ,
88} ;
9- use dstack_vmm_rpc:: { self as pb, GpuInfo , StatusRequest , StatusResponse , VmConfiguration } ;
9+ use dstack_vmm_rpc:: {
10+ self as pb, BackupInfo , GpuInfo , StatusRequest , StatusResponse , VmConfiguration ,
11+ } ;
1012use fs_err as fs;
1113use guest_api:: client:: DefaultClient as GuestClient ;
1214use id_pool:: IdPool ;
@@ -18,7 +20,7 @@ use std::net::IpAddr;
1820use std:: path:: { Path , PathBuf } ;
1921use std:: sync:: { Arc , Mutex , MutexGuard } ;
2022use supervisor_client:: SupervisorClient ;
21- use tracing:: { error, info} ;
23+ use tracing:: { error, info, warn } ;
2224
2325pub use image:: { Image , ImageInfo } ;
2426pub use qemu:: { VmConfig , VmWorkDir } ;
@@ -647,6 +649,111 @@ impl App {
647649 }
648650 Ok ( ( ) )
649651 }
652+
653+ pub ( crate ) async fn backup_disk ( & self , id : & str , level : & str ) -> Result < ( ) > {
654+ let work_dir = self . work_dir ( id) ;
655+
656+ // Determine backup level based on the backup_type
657+ let backup_level = match level {
658+ "full" => "full" ,
659+ "incremental" => "inc" ,
660+ _ => bail ! ( "Invalid backup level: {level}" ) ,
661+ } ;
662+
663+ // Get the VM directory path as a string
664+ let backup_dir = work_dir. path ( ) . join ( "backups" ) ;
665+ let qmp_socket = work_dir. qmp_socket ( ) . to_string_lossy ( ) . to_string ( ) ;
666+
667+ // Create backup directory if it doesn't exist
668+ tokio:: fs:: create_dir_all ( & backup_dir)
669+ . await
670+ . context ( "Failed to create backup directory" ) ?;
671+
672+ // Run the qmpbackup command in a blocking thread pool since it takes seconds to complete
673+ tokio:: task:: spawn_blocking ( move || {
674+ let output = std:: process:: Command :: new ( "qmpbackup" )
675+ . arg ( "--socket" )
676+ . arg ( qmp_socket)
677+ . arg ( "backup" )
678+ . arg ( "-i" )
679+ . arg ( "hd1" )
680+ . arg ( "--no-subdir" )
681+ . arg ( "-t" )
682+ . arg ( & backup_dir)
683+ . arg ( "-T" )
684+ . arg ( "-l" )
685+ . arg ( backup_level)
686+ . output ( ) ;
687+
688+ match output {
689+ Ok ( output) => {
690+ if !output. status . success ( ) {
691+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
692+ Err ( anyhow:: anyhow!( "qmpbackup command failed: {}" , stderr) )
693+ } else {
694+ Ok ( ( ) )
695+ }
696+ }
697+ Err ( e) => Err ( anyhow:: anyhow!(
698+ "Failed to execute qmpbackup command: {}" ,
699+ e
700+ ) ) ,
701+ }
702+ } )
703+ . await
704+ . context ( "Failed to execute backup task" ) ?
705+ }
706+
707+ pub ( crate ) async fn list_backups ( & self , id : & str ) -> Result < Vec < BackupInfo > > {
708+ let work_dir = self . work_dir ( id) ;
709+ let backup_dir = work_dir. path ( ) . join ( "backups" ) ;
710+
711+ // Create backup directory if it doesn't exist
712+ if !backup_dir. exists ( ) {
713+ return Ok ( Vec :: new ( ) ) ;
714+ }
715+
716+ // List backup files in the directory
717+ let mut backups = Vec :: new ( ) ;
718+
719+ // Read directory entries in a blocking task
720+ let backup_dir_clone = backup_dir. clone ( ) ;
721+ let entries =
722+ std:: fs:: read_dir ( backup_dir_clone) . context ( "Failed to read backup directory" ) ?;
723+ // Process each entry
724+ for entry in entries {
725+ let path = match entry {
726+ Ok ( entry) => entry. path ( ) ,
727+ Err ( e) => {
728+ warn ! ( "Failed to read directory entry: {e:?}" ) ;
729+ continue ;
730+ }
731+ } ;
732+ // Skip if not a file
733+ if !path. is_file ( ) {
734+ continue ;
735+ }
736+
737+ // Get file name
738+ let file_name = match path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
739+ Some ( name) => name. to_string ( ) ,
740+ None => continue ,
741+ } ;
742+
743+ if !file_name. ends_with ( ".img" ) {
744+ continue ;
745+ }
746+
747+ backups. push ( BackupInfo {
748+ filename : file_name,
749+ size : path
750+ . metadata ( )
751+ . context ( "Failed to get file metadata" ) ?
752+ . len ( ) ,
753+ } ) ;
754+ }
755+ Ok ( backups)
756+ }
650757}
651758
652759fn paginate < T > ( items : Vec < T > , page : u32 , page_size : u32 ) -> impl Iterator < Item = T > {
0 commit comments