Add support for QCow2 file format in web ui
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Pierre HUBERT 2025-05-22 18:41:04 +02:00
parent 53a8963fc4
commit 94ee8f8c78
4 changed files with 75 additions and 44 deletions

View File

@ -77,8 +77,8 @@ pub struct VMInfo {
pub vnc_access: bool,
/// Attach ISO file(s)
pub iso_files: Vec<String>,
/// 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<FileDisk>,
/// 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<FileDisk>,
/// Network cards
pub networks: Vec<Network>,
/// 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()

View File

@ -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<VMInfo> {
// 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;
});

View File

@ -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) => (
<DiskInfo
// eslint-disable-next-line react-x/no-array-index-key
key={num}
@ -46,7 +46,7 @@ export function VMDisksList(p: {
disk={d}
onChange={p.onChange}
removeFromList={() => {
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 : ""}`}
/>
</ListItem>
);
return (
<Paper elevation={3} style={{ margin: "10px", padding: "10px" }}>
<TextInput
editable={true}
label="Disk name"
size={ServerApi.Config.constraints.disk_name_size}
checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)}
value={p.disk.name}
onValueChange={(v) => {
p.disk.name = v ?? "";
p.onChange?.();
}}
/>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<TextInput
editable={true}
label="Disk name"
size={ServerApi.Config.constraints.disk_name_size}
checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)}
value={p.disk.name}
onValueChange={(v) => {
p.disk.name = v ?? "";
p.onChange?.();
}}
/>
<IconButton onClick={p.removeFromList}>
<DeleteIcon />
</IconButton>
</div>
<TextInput
editable={true}
@ -158,7 +163,21 @@ function DiskInfo(p: {
type="number"
/>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<SelectInput
editable={true}
label="Disk format"
options={[
{ label: "Raw file", value: "Raw" },
{ label: "QCow2", value: "QCow2" },
]}
value={p.disk.format}
onValueChange={(v) => {
p.disk.format = v as any;
p.onChange?.();
}}
/>
{p.disk.format === "Raw" && (
<SelectInput
editable={true}
label="File allocation type"
@ -168,15 +187,11 @@ function DiskInfo(p: {
]}
value={p.disk.alloc_type}
onValueChange={(v) => {
p.disk.alloc_type = v as any;
if (p.disk.format === "Raw") p.disk.alloc_type = v as any;
p.onChange?.();
}}
/>
<IconButton onClick={p.removeFromList}>
<DeleteIcon />
</IconButton>
</div>
)}
</Paper>
);
}

View File

@ -334,8 +334,8 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
function VMDetailsTabStorage(p: DetailsInnerProps): React.ReactElement {
return (
<Grid container spacing={2}>
{(p.editable || p.vm.disks.length > 0) && (
<EditSection title="Disks storage">
{(p.editable || p.vm.file_disks.length > 0) && (
<EditSection title="File disks storage">
<VMDisksList {...p} />
</EditSection>
)}