Can backup vm disks as images

This commit is contained in:
Pierre HUBERT 2025-05-30 10:55:40 +02:00
parent 83df7e1b20
commit 6b6fef5ccc
6 changed files with 84 additions and 28 deletions

View File

@ -1,6 +1,8 @@
use crate::app_config::AppConfig;
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::files_utils;
use actix_files::NamedFile;
@ -107,6 +109,61 @@ pub async fn convert(
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) {
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!"));
}
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()
.disk_images_storage_path()
.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

View File

@ -356,6 +356,10 @@ async fn main() -> std::io::Result<()> {
"/api/disk_images/{filename}",
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
.route(
"/api/token/create",

View File

@ -93,7 +93,7 @@ impl VMFileDisk {
Ok(())
}
/// Get disk path
/// Get disk path on file system
pub fn disk_path(&self, id: XMLUuid) -> PathBuf {
let domain_dir = AppConfig::get().vm_storage_path(id);
let file_name = match self.format {

View File

@ -1,4 +1,5 @@
import { APIClient } from "./ApiClient";
import { VMFileDisk, VMInfo } from "./VMApi";
export type DiskImageFormat =
| { 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
*/

View File

@ -5,7 +5,6 @@
*/
import { APIClient } from "./ApiClient";
import { DiskImageFormat } from "./DiskImageApi";
export type VMState =
| "NoState"
@ -385,20 +384,4 @@ export class VMApi {
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 },
});
}
}

View File

@ -9,7 +9,7 @@ import {
import React from "react";
import { DiskImage, DiskImageApi, DiskImageFormat } from "../api/DiskImageApi";
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 { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
@ -60,7 +60,8 @@ export function ConvertDiskImageDialog(
);
// 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);
p.onFinished();