From 940302a82585f2a18fc870f33dec73c7c52315cf Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 9 Jun 2025 17:42:36 +0200 Subject: [PATCH] Can resize disk image when adding a new disk image to a VM --- virtweb_backend/src/utils/file_disks_utils.rs | 34 ++++++++++++ .../src/utils/vm_file_disks_utils.rs | 54 ++++++++++++------- virtweb_frontend/src/api/VMApi.ts | 3 ++ .../src/widgets/forms/VMDisksList.tsx | 50 +++++++++++------ 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/virtweb_backend/src/utils/file_disks_utils.rs b/virtweb_backend/src/utils/file_disks_utils.rs index 5a5e126..ee9786d 100644 --- a/virtweb_backend/src/utils/file_disks_utils.rs +++ b/virtweb_backend/src/utils/file_disks_utils.rs @@ -394,6 +394,40 @@ impl DiskFileInfo { Ok(()) } + + /// Get disk virtual size, if available + pub fn virtual_size(&self) -> Option { + match self.format { + DiskFileFormat::Raw { .. } => Some(self.file_size), + DiskFileFormat::QCow2 { virtual_size } => Some(virtual_size), + _ => None, + } + } + + /// Resize disk + pub fn resize(&self, new_size: FileSize) -> anyhow::Result<()> { + let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); + cmd.arg("resize") + .arg("-f") + .arg(match self.format { + DiskFileFormat::QCow2 { .. } => "qcow2", + DiskFileFormat::Raw { .. } => "raw", + f => anyhow::bail!("Unsupported disk format for resize: {f:?}"), + }) + .arg(&self.file_path) + .arg(new_size.as_bytes().to_string()); + + let output = cmd.output()?; + if !output.status.success() { + anyhow::bail!( + "{} info failed, status: {}, stderr: {}", + constants::PROGRAM_QEMU_IMAGE, + output.status, + String::from_utf8_lossy(&output.stderr) + ); + } + Ok(()) + } } #[derive(serde::Deserialize)] diff --git a/virtweb_backend/src/utils/vm_file_disks_utils.rs b/virtweb_backend/src/utils/vm_file_disks_utils.rs index aff55b7..84603f8 100644 --- a/virtweb_backend/src/utils/vm_file_disks_utils.rs +++ b/virtweb_backend/src/utils/vm_file_disks_utils.rs @@ -44,6 +44,9 @@ pub struct VMFileDisk { /// When creating a new disk, specify the disk image template to use #[serde(skip_serializing_if = "Option::is_none")] pub from_image: Option, + /// Set this variable to true to resize disk image + #[serde(skip_serializing_if = "Option::is_none")] + pub resize: Option, /// Set this variable to true to delete the disk pub delete: bool, } @@ -78,6 +81,7 @@ impl VMFileDisk { delete: false, from_image: None, + resize: None, }) } @@ -144,28 +148,40 @@ impl VMFileDisk { if file.exists() { log::debug!("File {file:?} does not exists, so it was not touched"); - return Ok(()); + } + // Create disk if required + else { + // Determine file format + let format = match self.format { + VMDiskFormat::Raw { is_sparse } => DiskFileFormat::Raw { is_sparse }, + VMDiskFormat::QCow2 => DiskFileFormat::QCow2 { + virtual_size: self.size, + }, + }; + + // Create / Restore disk file + match &self.from_image { + // Create disk file + None => { + DiskFileInfo::create(&file, format, self.size)?; + } + + // Restore disk image template + Some(disk_img) => { + let src_file = + DiskFileInfo::load_file(&AppConfig::get().disk_images_file_path(disk_img))?; + src_file.convert(&file, format)?; + } + } } - let format = match self.format { - VMDiskFormat::Raw { is_sparse } => DiskFileFormat::Raw { is_sparse }, - VMDiskFormat::QCow2 => DiskFileFormat::QCow2 { - virtual_size: self.size, - }, - }; + // Resize disk file if requested + if self.resize == Some(true) { + let disk = DiskFileInfo::load_file(&file)?; - // Create / Restore disk file - match &self.from_image { - // Create disk file - None => { - DiskFileInfo::create(&file, format, self.size)?; - } - - // Restore disk image template - Some(disk_img) => { - let src_file = - DiskFileInfo::load_file(&AppConfig::get().disk_images_file_path(disk_img))?; - src_file.convert(&file, format)?; + // Can only increase disk size + if let Err(e) = disk.resize(self.size) { + log::error!("Failed to resize disk file {}: {e:?}", self.name); } } diff --git a/virtweb_frontend/src/api/VMApi.ts b/virtweb_frontend/src/api/VMApi.ts index 2284aed..5df788f 100644 --- a/virtweb_frontend/src/api/VMApi.ts +++ b/virtweb_frontend/src/api/VMApi.ts @@ -31,6 +31,9 @@ export interface BaseFileVMDisk { // For new disk only from_image?: string; + // Resize disk image after clone + resize?: boolean; + // application attributes new?: boolean; deleteType?: "keepfile" | "deletefile"; diff --git a/virtweb_frontend/src/widgets/forms/VMDisksList.tsx b/virtweb_frontend/src/widgets/forms/VMDisksList.tsx index 9744dcd..4b69923 100644 --- a/virtweb_frontend/src/widgets/forms/VMDisksList.tsx +++ b/virtweb_frontend/src/widgets/forms/VMDisksList.tsx @@ -218,24 +218,40 @@ function DiskInfo(p: { /> )} - { - p.disk.size = Number(v ?? "0") * 1000 * 1000 * 1000; - p.onChange?.(); - }} - type="number" - disabled={!!p.disk.from_image} - /> + {/* Resize disk image */} + {!!p.disk.from_image && ( + { + p.disk.resize = v; + p.onChange?.(); + }} + /> + )} + {/* Disk size */} + {(!p.disk.from_image || p.disk.resize === true) && ( + { + p.disk.size = Number(v ?? "0") * 1000 * 1000 * 1000; + p.onChange?.(); + }} + type="number" + /> + )} + + {/* Disk image selection */}