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