@@ -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 } ;
@@ -653,6 +655,111 @@ impl App {
653655 }
654656 Ok ( ( ) )
655657 }
658+
659+ pub ( crate ) async fn backup_disk ( & self , id : & str , level : & str ) -> Result < ( ) > {
660+ let work_dir = self . work_dir ( id) ;
661+
662+ // Determine backup level based on the backup_type
663+ let backup_level = match level {
664+ "full" => "full" ,
665+ "incremental" => "inc" ,
666+ _ => bail ! ( "Invalid backup level: {level}" ) ,
667+ } ;
668+
669+ // Get the VM directory path as a string
670+ let backup_dir = work_dir. path ( ) . join ( "backups" ) ;
671+ let qmp_socket = work_dir. qmp_socket ( ) . to_string_lossy ( ) . to_string ( ) ;
672+
673+ // Create backup directory if it doesn't exist
674+ tokio:: fs:: create_dir_all ( & backup_dir)
675+ . await
676+ . context ( "Failed to create backup directory" ) ?;
677+
678+ // Run the qmpbackup command in a blocking thread pool since it takes seconds to complete
679+ tokio:: task:: spawn_blocking ( move || {
680+ let output = std:: process:: Command :: new ( "qmpbackup" )
681+ . arg ( "--socket" )
682+ . arg ( qmp_socket)
683+ . arg ( "backup" )
684+ . arg ( "-i" )
685+ . arg ( "hd1" )
686+ . arg ( "--no-subdir" )
687+ . arg ( "-t" )
688+ . arg ( & backup_dir)
689+ . arg ( "-T" )
690+ . arg ( "-l" )
691+ . arg ( backup_level)
692+ . output ( ) ;
693+
694+ match output {
695+ Ok ( output) => {
696+ if !output. status . success ( ) {
697+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
698+ Err ( anyhow:: anyhow!( "qmpbackup command failed: {}" , stderr) )
699+ } else {
700+ Ok ( ( ) )
701+ }
702+ }
703+ Err ( e) => Err ( anyhow:: anyhow!(
704+ "Failed to execute qmpbackup command: {}" ,
705+ e
706+ ) ) ,
707+ }
708+ } )
709+ . await
710+ . context ( "Failed to execute backup task" ) ?
711+ }
712+
713+ pub ( crate ) async fn list_backups ( & self , id : & str ) -> Result < Vec < BackupInfo > > {
714+ let work_dir = self . work_dir ( id) ;
715+ let backup_dir = work_dir. path ( ) . join ( "backups" ) ;
716+
717+ // Create backup directory if it doesn't exist
718+ if !backup_dir. exists ( ) {
719+ return Ok ( Vec :: new ( ) ) ;
720+ }
721+
722+ // List backup files in the directory
723+ let mut backups = Vec :: new ( ) ;
724+
725+ // Read directory entries in a blocking task
726+ let backup_dir_clone = backup_dir. clone ( ) ;
727+ let entries =
728+ std:: fs:: read_dir ( backup_dir_clone) . context ( "Failed to read backup directory" ) ?;
729+ // Process each entry
730+ for entry in entries {
731+ let path = match entry {
732+ Ok ( entry) => entry. path ( ) ,
733+ Err ( e) => {
734+ warn ! ( "Failed to read directory entry: {e:?}" ) ;
735+ continue ;
736+ }
737+ } ;
738+ // Skip if not a file
739+ if !path. is_file ( ) {
740+ continue ;
741+ }
742+
743+ // Get file name
744+ let file_name = match path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
745+ Some ( name) => name. to_string ( ) ,
746+ None => continue ,
747+ } ;
748+
749+ if !file_name. ends_with ( ".img" ) {
750+ continue ;
751+ }
752+
753+ backups. push ( BackupInfo {
754+ filename : file_name,
755+ size : path
756+ . metadata ( )
757+ . context ( "Failed to get file metadata" ) ?
758+ . len ( ) ,
759+ } ) ;
760+ }
761+ Ok ( backups)
762+ }
656763}
657764
658765fn paginate < T > ( items : Vec < T > , page : u32 , page_size : u32 ) -> impl Iterator < Item = T > {
0 commit comments