From 94ee8f8c78c1f8eb6b1282f76f55c3a6df022d1d Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 22 May 2025 18:41:04 +0200 Subject: [PATCH] Add support for QCow2 file format in web ui --- .../src/libvirt_rest_structures/vm.rs | 18 +++-- virtweb_frontend/src/api/VMApi.ts | 28 +++++--- .../src/widgets/forms/VMDisksList.tsx | 69 +++++++++++-------- .../src/widgets/vms/VMDetails.tsx | 4 +- 4 files changed, 75 insertions(+), 44 deletions(-) diff --git a/virtweb_backend/src/libvirt_rest_structures/vm.rs b/virtweb_backend/src/libvirt_rest_structures/vm.rs index 5f32d0d..76555af 100644 --- a/virtweb_backend/src/libvirt_rest_structures/vm.rs +++ b/virtweb_backend/src/libvirt_rest_structures/vm.rs @@ -77,8 +77,8 @@ pub struct VMInfo { pub vnc_access: bool, /// Attach ISO file(s) pub iso_files: Vec, - /// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest - pub disks: Vec, + /// File Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest + pub file_disks: Vec, /// Network cards pub networks: Vec, /// Add a TPM v2.0 module @@ -247,15 +247,21 @@ impl VMInfo { } // Check disks name for duplicates - for disk in &self.disks { - if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 { + for disk in &self.file_disks { + if self + .file_disks + .iter() + .filter(|d| d.name == disk.name) + .count() + > 1 + { return Err(StructureExtraction("Two different disks have the same name!").into()); } } // Apply disks configuration. Starting from now, the function should ideally never fail due to // bad user input - for disk in &self.disks { + for disk in &self.file_disks { disk.check_config()?; disk.apply_config(uuid)?; @@ -428,7 +434,7 @@ impl VMInfo { .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string()) .collect(), - disks: domain + file_disks: domain .devices .disks .iter() diff --git a/virtweb_frontend/src/api/VMApi.ts b/virtweb_frontend/src/api/VMApi.ts index d8bd614..11d498f 100644 --- a/virtweb_frontend/src/api/VMApi.ts +++ b/virtweb_frontend/src/api/VMApi.ts @@ -17,12 +17,11 @@ export type VMState = | "PowerManagementSuspended" | "Other"; -export type DiskAllocType = "Sparse" | "Fixed"; +export type VMFileDisk = BaseFileVMDisk & (RawVMDisk | QCow2Disk); -export interface VMDisk { +export interface BaseFileVMDisk { size: number; name: string; - alloc_type: DiskAllocType; delete: boolean; // application attribute @@ -30,6 +29,17 @@ export interface VMDisk { deleteType?: "keepfile" | "deletefile"; } +export type DiskAllocType = "Sparse" | "Fixed"; + +interface RawVMDisk { + format: "Raw"; + alloc_type: DiskAllocType; +} + +interface QCow2Disk { + format: "QCow2"; +} + export interface VMNetInterfaceFilterParams { name: string; value: string; @@ -70,7 +80,7 @@ interface VMInfoInterface { number_vcpu: number; vnc_access: boolean; iso_files: string[]; - disks: VMDisk[]; + file_disks: VMFileDisk[]; networks: VMNetInterface[]; tpm_module: boolean; } @@ -88,7 +98,7 @@ export class VMInfo implements VMInfoInterface { memory: number; vnc_access: boolean; iso_files: string[]; - disks: VMDisk[]; + file_disks: VMFileDisk[]; networks: VMNetInterface[]; tpm_module: boolean; @@ -105,7 +115,7 @@ export class VMInfo implements VMInfoInterface { this.memory = int.memory; this.vnc_access = int.vnc_access; this.iso_files = int.iso_files; - this.disks = int.disks; + this.file_disks = int.file_disks; this.networks = int.networks; this.tpm_module = int.tpm_module; } @@ -119,7 +129,7 @@ export class VMInfo implements VMInfoInterface { number_vcpu: 1, vnc_access: true, iso_files: [], - disks: [], + file_disks: [], networks: [], tpm_module: true, }); @@ -194,8 +204,8 @@ export class VMApi { */ static async UpdateSingle(vm: VMInfo): Promise { // Process disks list, looking for removal - vm.disks = vm.disks.filter((d) => d.deleteType !== "keepfile"); - vm.disks.forEach((d) => { + vm.file_disks = vm.file_disks.filter((d) => d.deleteType !== "keepfile"); + vm.file_disks.forEach((d) => { if (d.deleteType === "deletefile") d.delete = true; }); diff --git a/virtweb_frontend/src/widgets/forms/VMDisksList.tsx b/virtweb_frontend/src/widgets/forms/VMDisksList.tsx index a0e6d68..29c56c4 100644 --- a/virtweb_frontend/src/widgets/forms/VMDisksList.tsx +++ b/virtweb_frontend/src/widgets/forms/VMDisksList.tsx @@ -14,7 +14,7 @@ import { } from "@mui/material"; import { filesize } from "filesize"; import { ServerApi } from "../../api/ServerApi"; -import { VMDisk, VMInfo } from "../../api/VMApi"; +import { VMFileDisk, VMInfo } from "../../api/VMApi"; import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; import { SelectInput } from "./SelectInput"; import { TextInput } from "./TextInput"; @@ -25,11 +25,11 @@ export function VMDisksList(p: { editable: boolean; }): React.ReactElement { const addNewDisk = () => { - p.vm.disks.push({ - alloc_type: "Sparse", + p.vm.file_disks.push({ + format: "QCow2", size: 10000, delete: false, - name: `disk${p.vm.disks.length}`, + name: `disk${p.vm.file_disks.length}`, new: true, }); p.onChange?.(); @@ -38,7 +38,7 @@ export function VMDisksList(p: { return ( <> {/* disks list */} - {p.vm.disks.map((d, num) => ( + {p.vm.file_disks.map((d, num) => ( { - p.vm.disks.splice(num, 1); + p.vm.file_disks.splice(num, 1); p.onChange?.(); }} /> @@ -59,7 +59,7 @@ export function VMDisksList(p: { function DiskInfo(p: { editable: boolean; - disk: VMDisk; + disk: VMFileDisk; onChange?: () => void; removeFromList: () => void; }): React.ReactElement { @@ -126,25 +126,30 @@ function DiskInfo(p: { } secondary={`${filesize(p.disk.size * 1000 * 1000)} - ${ - p.disk.alloc_type - }`} + p.disk.format + }${p.disk.format == "Raw" ? " - " + p.disk.alloc_type : ""}`} /> ); return ( - /^[a-zA-Z0-9]+$/.test(v)} - value={p.disk.name} - onValueChange={(v) => { - p.disk.name = v ?? ""; - p.onChange?.(); - }} - /> +
+ /^[a-zA-Z0-9]+$/.test(v)} + value={p.disk.name} + onValueChange={(v) => { + p.disk.name = v ?? ""; + p.onChange?.(); + }} + /> + + + +
-
+ { + p.disk.format = v as any; + p.onChange?.(); + }} + /> + + {p.disk.format === "Raw" && ( { - p.disk.alloc_type = v as any; + if (p.disk.format === "Raw") p.disk.alloc_type = v as any; p.onChange?.(); }} /> - - - - -
+ )}
); } diff --git a/virtweb_frontend/src/widgets/vms/VMDetails.tsx b/virtweb_frontend/src/widgets/vms/VMDetails.tsx index 912885c..7959332 100644 --- a/virtweb_frontend/src/widgets/vms/VMDetails.tsx +++ b/virtweb_frontend/src/widgets/vms/VMDetails.tsx @@ -334,8 +334,8 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement { function VMDetailsTabStorage(p: DetailsInnerProps): React.ReactElement { return ( - {(p.editable || p.vm.disks.length > 0) && ( - + {(p.editable || p.vm.file_disks.length > 0) && ( + )}