Can backup vm disks as images
This commit is contained in:
		@@ -1,6 +1,8 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::controllers::HttpResult;
 | 
					use crate::controllers::{HttpResult, LibVirtReq};
 | 
				
			||||||
 | 
					use crate::libvirt_lib_structures::XMLUuid;
 | 
				
			||||||
 | 
					use crate::libvirt_rest_structures::vm::VMInfo;
 | 
				
			||||||
use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo};
 | 
					use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo};
 | 
				
			||||||
use crate::utils::files_utils;
 | 
					use crate::utils::files_utils;
 | 
				
			||||||
use actix_files::NamedFile;
 | 
					use actix_files::NamedFile;
 | 
				
			||||||
@@ -107,6 +109,61 @@ pub async fn convert(
 | 
				
			|||||||
        return Ok(HttpResponse::BadRequest().json("Invalid src file name!"));
 | 
					        return Ok(HttpResponse::BadRequest().json("Invalid src file name!"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let src_file_path = AppConfig::get()
 | 
				
			||||||
 | 
					        .disk_images_storage_path()
 | 
				
			||||||
 | 
					        .join(&p.filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let src = DiskFileInfo::load_file(&src_file_path)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    handle_convert_request(src, &req).await
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct BackupVMDiskPath {
 | 
				
			||||||
 | 
					    uid: XMLUuid,
 | 
				
			||||||
 | 
					    diskid: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Perform disk backup
 | 
				
			||||||
 | 
					pub async fn backup_disk(
 | 
				
			||||||
 | 
					    client: LibVirtReq,
 | 
				
			||||||
 | 
					    path: web::Path<BackupVMDiskPath>,
 | 
				
			||||||
 | 
					    req: web::Json<ConvertDiskImageRequest>,
 | 
				
			||||||
 | 
					) -> HttpResult {
 | 
				
			||||||
 | 
					    // Get the VM information
 | 
				
			||||||
 | 
					    let info = match client.get_single_domain(path.uid).await {
 | 
				
			||||||
 | 
					        Ok(i) => i,
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
 | 
					            log::error!("Failed to get domain info! {e}");
 | 
				
			||||||
 | 
					            return Ok(HttpResponse::InternalServerError().json(e.to_string()));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let vm = VMInfo::from_domain(info)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Load disk information
 | 
				
			||||||
 | 
					    let Some(disk) = vm
 | 
				
			||||||
 | 
					        .file_disks
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .find(|disk| disk.name == path.diskid)
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::NotFound()
 | 
				
			||||||
 | 
					            .json(format!("Disk {} not found for vm {}", path.diskid, vm.name)));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let src_path = disk.disk_path(vm.uuid.expect("Missing VM uuid!"));
 | 
				
			||||||
 | 
					    let src_disk = DiskFileInfo::load_file(&src_path)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Perform conversion
 | 
				
			||||||
 | 
					    handle_convert_request(src_disk, &req).await
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Generic controller code that performs image conversion to create a disk image file
 | 
				
			||||||
 | 
					pub async fn handle_convert_request(
 | 
				
			||||||
 | 
					    src: DiskFileInfo,
 | 
				
			||||||
 | 
					    req: &ConvertDiskImageRequest,
 | 
				
			||||||
 | 
					) -> HttpResult {
 | 
				
			||||||
 | 
					    // Check destination file
 | 
				
			||||||
    if !files_utils::check_file_name(&req.dest_file_name) {
 | 
					    if !files_utils::check_file_name(&req.dest_file_name) {
 | 
				
			||||||
        return Ok(HttpResponse::BadRequest().json("Invalid destination file name!"));
 | 
					        return Ok(HttpResponse::BadRequest().json("Invalid destination file name!"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -119,12 +176,6 @@ pub async fn convert(
 | 
				
			|||||||
        return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!"));
 | 
					        return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let src_file_path = AppConfig::get()
 | 
					 | 
				
			||||||
        .disk_images_storage_path()
 | 
					 | 
				
			||||||
        .join(&p.filename);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let src = DiskFileInfo::load_file(&src_file_path)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let dst_file_path = AppConfig::get()
 | 
					    let dst_file_path = AppConfig::get()
 | 
				
			||||||
        .disk_images_storage_path()
 | 
					        .disk_images_storage_path()
 | 
				
			||||||
        .join(&req.dest_file_name);
 | 
					        .join(&req.dest_file_name);
 | 
				
			||||||
@@ -141,7 +192,7 @@ pub async fn convert(
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(src))
 | 
					    Ok(HttpResponse::Accepted().json("Successfully converted disk file"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Delete a disk image
 | 
					/// Delete a disk image
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -356,6 +356,10 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
                "/api/disk_images/{filename}",
 | 
					                "/api/disk_images/{filename}",
 | 
				
			||||||
                web::delete().to(disk_images_controller::delete),
 | 
					                web::delete().to(disk_images_controller::delete),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/vm/{uid}/disk/{diskid}/backup",
 | 
				
			||||||
 | 
					                web::post().to(disk_images_controller::backup_disk),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // API tokens controller
 | 
					            // API tokens controller
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/api/token/create",
 | 
					                "/api/token/create",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,7 +93,7 @@ impl VMFileDisk {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get disk path
 | 
					    /// Get disk path on file system
 | 
				
			||||||
    pub fn disk_path(&self, id: XMLUuid) -> PathBuf {
 | 
					    pub fn disk_path(&self, id: XMLUuid) -> PathBuf {
 | 
				
			||||||
        let domain_dir = AppConfig::get().vm_storage_path(id);
 | 
					        let domain_dir = AppConfig::get().vm_storage_path(id);
 | 
				
			||||||
        let file_name = match self.format {
 | 
					        let file_name = match self.format {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { APIClient } from "./ApiClient";
 | 
					import { APIClient } from "./ApiClient";
 | 
				
			||||||
 | 
					import { VMFileDisk, VMInfo } from "./VMApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type DiskImageFormat =
 | 
					export type DiskImageFormat =
 | 
				
			||||||
  | { format: "Raw"; is_sparse: boolean }
 | 
					  | { format: "Raw"; is_sparse: boolean }
 | 
				
			||||||
@@ -77,6 +78,22 @@ export class DiskImageApi {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Backup VM disk into image disks library
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async BackupVMDisk(
 | 
				
			||||||
 | 
					    vm: VMInfo,
 | 
				
			||||||
 | 
					    disk: VMFileDisk,
 | 
				
			||||||
 | 
					    dest_file_name: string,
 | 
				
			||||||
 | 
					    format: DiskImageFormat
 | 
				
			||||||
 | 
					  ): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      uri: `/vm/${vm.uuid}/disk/${disk.name}/backup`,
 | 
				
			||||||
 | 
					      method: "POST",
 | 
				
			||||||
 | 
					      jsonData: { ...format, dest_file_name },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Delete disk image file
 | 
					   * Delete disk image file
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { APIClient } from "./ApiClient";
 | 
					import { APIClient } from "./ApiClient";
 | 
				
			||||||
import { DiskImageFormat } from "./DiskImageApi";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type VMState =
 | 
					export type VMState =
 | 
				
			||||||
  | "NoState"
 | 
					  | "NoState"
 | 
				
			||||||
@@ -385,20 +384,4 @@ export class VMApi {
 | 
				
			|||||||
      encodeURIComponent(token)
 | 
					      encodeURIComponent(token)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Backup VM disk
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  static async BackupDisk(
 | 
					 | 
				
			||||||
    vm: VMInfo,
 | 
					 | 
				
			||||||
    disk: VMFileDisk,
 | 
					 | 
				
			||||||
    file_name: string,
 | 
					 | 
				
			||||||
    format: DiskImageFormat
 | 
					 | 
				
			||||||
  ): Promise<void> {
 | 
					 | 
				
			||||||
    await APIClient.exec({
 | 
					 | 
				
			||||||
      uri: `/vm/${vm.uuid}/disk/${disk.name}/backup`,
 | 
					 | 
				
			||||||
      method: "POST",
 | 
					 | 
				
			||||||
      jsonData: { ...format, file_name },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ import {
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { DiskImage, DiskImageApi, DiskImageFormat } from "../api/DiskImageApi";
 | 
					import { DiskImage, DiskImageApi, DiskImageFormat } from "../api/DiskImageApi";
 | 
				
			||||||
import { ServerApi } from "../api/ServerApi";
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
import { VMApi, VMFileDisk, VMInfo } from "../api/VMApi";
 | 
					import { VMFileDisk, VMInfo } from "../api/VMApi";
 | 
				
			||||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
				
			||||||
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
 | 
					import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
 | 
				
			||||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
				
			||||||
@@ -60,7 +60,8 @@ export function ConvertDiskImageDialog(
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Perform the conversion / backup operation
 | 
					      // Perform the conversion / backup operation
 | 
				
			||||||
      if (p.backup) await VMApi.BackupDisk(p.vm, p.disk, filename, format);
 | 
					      if (p.backup)
 | 
				
			||||||
 | 
					        await DiskImageApi.BackupVMDisk(p.vm, p.disk, filename, format);
 | 
				
			||||||
      else await DiskImageApi.Convert(p.image, filename, format);
 | 
					      else await DiskImageApi.Convert(p.image, filename, format);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      p.onFinished();
 | 
					      p.onFinished();
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user