From d93a2b857a43c1e12b926154e73b867af4bc4863 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Thu, 19 Oct 2023 18:12:24 +0200 Subject: [PATCH] Managed to insert an ISO file --- virtweb_backend/src/libvirt_lib_structures.rs | 73 +++++++++++++++++++ .../src/libvirt_rest_structures.rs | 57 ++++++++++++++- virtweb_frontend/src/api/VMApi.ts | 3 + .../src/widgets/forms/SelectInput.tsx | 2 +- .../src/widgets/vms/VMDetails.tsx | 51 ++++++++++++- 5 files changed, 180 insertions(+), 6 deletions(-) diff --git a/virtweb_backend/src/libvirt_lib_structures.rs b/virtweb_backend/src/libvirt_lib_structures.rs index a64eb4f..357d486 100644 --- a/virtweb_backend/src/libvirt_lib_structures.rs +++ b/virtweb_backend/src/libvirt_lib_structures.rs @@ -60,8 +60,13 @@ pub struct ACPIXML {} #[derive(serde::Serialize, serde::Deserialize)] #[serde(rename = "devices")] pub struct DevicesXML { + /// Graphics (used for VNC #[serde(skip_serializing_if = "Option::is_none")] pub graphics: Option, + + /// Disks (used for storage) + #[serde(default, rename = "disk")] + pub disks: Option, // TODO : change to vec } /// Screen information @@ -74,6 +79,74 @@ pub struct GraphicsXML { pub socket: String, } +/// Disk information +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "disk")] +pub struct DiskXML { + #[serde(rename(serialize = "@type"))] + pub r#type: String, + #[serde(rename(serialize = "@device"))] + pub r#device: String, + + pub driver: DiskDriverXML, + pub source: DiskSourceXML, + pub target: DiskTargetXML, + pub readonly: DiskReadOnlyXML, + pub boot: DiskBootXML, + pub address: DiskAddressXML, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "driver")] +pub struct DiskDriverXML { + #[serde(rename(serialize = "@name"))] + pub name: String, + #[serde(rename(serialize = "@type"))] + pub r#type: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "source")] +pub struct DiskSourceXML { + #[serde(rename(serialize = "@file"))] + pub file: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "target")] +pub struct DiskTargetXML { + #[serde(rename(serialize = "@dev"))] + pub dev: String, + #[serde(rename(serialize = "@bus"))] + pub bus: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "readonly")] +pub struct DiskReadOnlyXML {} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "boot")] +pub struct DiskBootXML { + #[serde(rename(serialize = "@order"))] + pub order: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(rename = "address")] +pub struct DiskAddressXML { + #[serde(rename(serialize = "@type"))] + pub r#type: String, + #[serde(rename(serialize = "@controller"))] + pub r#controller: String, + #[serde(rename(serialize = "@bus"))] + pub r#bus: String, + #[serde(rename(serialize = "@target"))] + pub r#target: String, + #[serde(rename(serialize = "@unit"))] + pub r#unit: String, +} + /// Domain RAM information #[derive(serde::Serialize, serde::Deserialize)] #[serde(rename = "memory")] diff --git a/virtweb_backend/src/libvirt_rest_structures.rs b/virtweb_backend/src/libvirt_rest_structures.rs index 5f26929..e14b310 100644 --- a/virtweb_backend/src/libvirt_rest_structures.rs +++ b/virtweb_backend/src/libvirt_rest_structures.rs @@ -1,10 +1,12 @@ use crate::app_config::AppConfig; use crate::constants; use crate::libvirt_lib_structures::{ - DevicesXML, DomainMemoryXML, DomainXML, DomainXMLUuid, FeaturesXML, GraphicsXML, OSLoaderXML, - OSTypeXML, ACPIXML, OSXML, + DevicesXML, DiskAddressXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, + DiskTargetXML, DiskXML, DomainMemoryXML, DomainXML, DomainXMLUuid, FeaturesXML, GraphicsXML, + OSLoaderXML, OSTypeXML, ACPIXML, OSXML, }; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; +use crate::utils::files_utils; use lazy_regex::regex; use std::ops::{Div, Mul}; @@ -70,8 +72,9 @@ pub struct VMInfo { pub memory: usize, /// Enable VNC access through admin console pub vnc_access: bool, + /// Attach an ISO file + pub iso_file: Option, // TODO : storage - // TODO : iso // TODO : autostart // TODO : network interface } @@ -105,6 +108,47 @@ impl VMInfo { return Err(StructureExtraction("VM memory is invalid!").into()); } + let mut disks = vec![]; + + if let Some(iso_file) = &self.iso_file { + if !files_utils::check_file_name(iso_file) { + return Err(StructureExtraction("ISO filename is invalid!").into()); + } + + let path = AppConfig::get().iso_storage_path().join(iso_file); + + if !path.exists() { + return Err(StructureExtraction("Specified ISO file does not exists!").into()); + } + + disks.push(DiskXML { + r#type: "file".to_string(), + device: "cdrom".to_string(), + driver: DiskDriverXML { + name: "qemu".to_string(), + r#type: "raw".to_string(), + }, + source: DiskSourceXML { + file: path.to_string_lossy().to_string(), + }, + target: DiskTargetXML { + dev: "hdc".to_string(), + bus: "ide".to_string(), + }, + readonly: DiskReadOnlyXML {}, + boot: DiskBootXML { + order: "1".to_string(), + }, + address: DiskAddressXML { + r#type: "drive".to_string(), + controller: "0".to_string(), + bus: "1".to_string(), + target: "0".to_string(), + unit: "0".to_string(), + }, + }) + } + let vnc_graphics = match self.vnc_access { true => Some(GraphicsXML { r#type: "vnc".to_string(), @@ -147,6 +191,7 @@ impl VMInfo { devices: DevicesXML { graphics: vnc_graphics, + disks: disks.into_iter().next(), }, memory: DomainMemoryXML { @@ -187,6 +232,12 @@ impl VMInfo { }, memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?, vnc_access: domain.devices.graphics.is_some(), + iso_file: domain + .devices + .disks + .iter() + .find(|d| d.device == "cdrom") + .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string()), }) } } diff --git a/virtweb_frontend/src/api/VMApi.ts b/virtweb_frontend/src/api/VMApi.ts index 8475ddd..2cfe001 100644 --- a/virtweb_frontend/src/api/VMApi.ts +++ b/virtweb_frontend/src/api/VMApi.ts @@ -27,6 +27,7 @@ interface VMInfoInterface { architecture: "i686" | "x86_64"; memory: number; vnc_access: boolean; + iso_file?: string; } export class VMInfo implements VMInfoInterface { @@ -39,6 +40,7 @@ export class VMInfo implements VMInfoInterface { architecture: "i686" | "x86_64"; memory: number; vnc_access: boolean; + iso_file?: string; constructor(int: VMInfoInterface) { this.name = int.name; @@ -50,6 +52,7 @@ export class VMInfo implements VMInfoInterface { this.architecture = int.architecture; this.memory = int.memory; this.vnc_access = int.vnc_access; + this.iso_file = int.iso_file; } static NewEmpty(): VMInfo { diff --git a/virtweb_frontend/src/widgets/forms/SelectInput.tsx b/virtweb_frontend/src/widgets/forms/SelectInput.tsx index fc02b2c..8cbca78 100644 --- a/virtweb_frontend/src/widgets/forms/SelectInput.tsx +++ b/virtweb_frontend/src/widgets/forms/SelectInput.tsx @@ -2,7 +2,7 @@ import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; import { TextInput } from "./TextInput"; export interface SelectOption { - value: string; + value?: string; label: string; } diff --git a/virtweb_frontend/src/widgets/vms/VMDetails.tsx b/virtweb_frontend/src/widgets/vms/VMDetails.tsx index e1df427..b33e4a1 100644 --- a/virtweb_frontend/src/widgets/vms/VMDetails.tsx +++ b/virtweb_frontend/src/widgets/vms/VMDetails.tsx @@ -7,13 +7,38 @@ import { CheckboxInput } from "../forms/CheckboxInput"; import { SelectInput } from "../forms/SelectInput"; import { TextInput } from "../forms/TextInput"; import { VMScreenshot } from "./VMScreenshot"; +import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; +import { AsyncWidget } from "../AsyncWidget"; +import React from "react"; +import { filesize } from "filesize"; -export function VMDetails(p: { +interface DetailsProps { vm: VMInfo; editable: boolean; onChange?: () => void; screenshot?: boolean; -}): React.ReactElement { +} + +export function VMDetails(p: DetailsProps): React.ReactElement { + const [list, setList] = React.useState(); + + const load = async () => { + setList(await IsoFilesApi.GetList()); + }; + + return ( + } + /> + ); +} + +function VMDetailsInner( + p: DetailsProps & { iso: IsoFile[] } +): React.ReactElement { return ( { @@ -128,6 +153,28 @@ export function VMDetails(p: { }} /> + + {/* Storage section */} + + { + p.vm.iso_file = v; + p.onChange?.(); + }} + options={[ + { label: "None", value: undefined }, + ...p.iso.map((i) => { + return { + label: `${i.filename} ${filesize(i.size)}`, + value: i.filename, + }; + }), + ]} + /> + ); }