Generalize disk file creation logic
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Pierre HUBERT 2025-05-29 08:03:42 +02:00
parent 7451f1b7b4
commit 20de618568
6 changed files with 103 additions and 58 deletions

View File

@ -1,3 +1,5 @@
use crate::utils::file_size_utils::FileSize;
/// Name of the cookie that contains session information /// Name of the cookie that contains session information
pub const SESSION_COOKIE_NAME: &str = "X-auth-token"; pub const SESSION_COOKIE_NAME: &str = "X-auth-token";
@ -47,10 +49,10 @@ pub const DISK_NAME_MIN_LEN: usize = 2;
pub const DISK_NAME_MAX_LEN: usize = 10; pub const DISK_NAME_MAX_LEN: usize = 10;
/// Disk size min (B) /// Disk size min (B)
pub const DISK_SIZE_MIN: usize = 100 * 1000 * 1000; pub const DISK_SIZE_MIN: FileSize = FileSize::from_mb(50);
/// Disk size max (B) /// Disk size max (B)
pub const DISK_SIZE_MAX: usize = 1000 * 1000 * 1000 * 1000 * 2; pub const DISK_SIZE_MAX: FileSize = FileSize::from_gb(20000);
/// Net nat entry comment max size /// Net nat entry comment max size
pub const NET_NAT_COMMENT_MAX_SIZE: usize = 250; pub const NET_NAT_COMMENT_MAX_SIZE: usize = 250;

View File

@ -87,8 +87,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
max: DISK_NAME_MAX_LEN, max: DISK_NAME_MAX_LEN,
}, },
disk_size: LenConstraints { disk_size: LenConstraints {
min: DISK_SIZE_MIN, min: DISK_SIZE_MIN.as_bytes(),
max: DISK_SIZE_MAX, max: DISK_SIZE_MAX.as_bytes(),
}, },
net_name_size: LenConstraints { min: 2, max: 50 }, net_name_size: LenConstraints { min: 2, max: 50 },

View File

@ -1,4 +1,5 @@
use crate::constants; use crate::constants;
use crate::utils::file_size_utils::FileSize;
use std::os::linux::fs::MetadataExt; use std::os::linux::fs::MetadataExt;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
@ -8,13 +9,15 @@ use std::time::UNIX_EPOCH;
enum DisksError { enum DisksError {
#[error("DiskParseError: {0}")] #[error("DiskParseError: {0}")]
Parse(&'static str), Parse(&'static str),
#[error("DiskCreateError")]
Create,
} }
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
#[serde(tag = "format")] #[serde(tag = "format")]
pub enum DiskFileFormat { pub enum DiskFileFormat {
Raw { is_sparse: bool }, Raw { is_sparse: bool },
QCow2 { virtual_size: usize }, QCow2 { virtual_size: FileSize },
CompressedRaw, CompressedRaw,
CompressedQCow2, CompressedQCow2,
} }
@ -22,7 +25,7 @@ pub enum DiskFileFormat {
/// Disk file information /// Disk file information
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct DiskFileInfo { pub struct DiskFileInfo {
pub file_size: usize, pub file_size: FileSize,
#[serde(flatten)] #[serde(flatten)]
pub format: DiskFileFormat, pub format: DiskFileFormat,
pub file_name: String, pub file_name: String,
@ -64,7 +67,7 @@ impl DiskFileInfo {
Ok(Self { Ok(Self {
name, name,
file_size: metadata.len() as usize, file_size: FileSize::from_bytes(metadata.len() as usize),
format, format,
file_name: file file_name: file
.file_name() .file_name()
@ -78,6 +81,50 @@ impl DiskFileInfo {
.as_secs(), .as_secs(),
}) })
} }
/// Create a new empty disk
pub fn create(file: &Path, format: DiskFileFormat, size: FileSize) -> anyhow::Result<()> {
// Prepare command to create file
let res = match format {
DiskFileFormat::Raw { is_sparse } => {
let mut cmd = Command::new("/usr/bin/dd");
cmd.arg("if=/dev/zero")
.arg(format!("of={}", file.to_string_lossy()))
.arg("bs=1M");
match is_sparse {
false => cmd.arg(format!("count={}", size.as_mb())),
true => cmd.arg(format!("seek={}", size.as_mb())).arg("count=0"),
};
cmd.output()?
}
DiskFileFormat::QCow2 { virtual_size } => {
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
cmd.arg("create")
.arg("-f")
.arg("qcow2")
.arg(file)
.arg(format!("{}M", virtual_size.as_mb()));
cmd.output()?
}
_ => anyhow::bail!("Cannot create disk file image of this format: {format:?}!"),
};
// Execute Linux command
if !res.status.success() {
log::error!(
"Failed to create disk! stderr={} stdout={}",
String::from_utf8_lossy(&res.stderr),
String::from_utf8_lossy(&res.stdout)
);
return Err(DisksError::Create.into());
}
Ok(())
}
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
@ -87,7 +134,7 @@ struct QCowInfoOutput {
} }
/// Get QCow2 virtual size /// Get QCow2 virtual size
fn qcow_virt_size(path: &Path) -> anyhow::Result<usize> { fn qcow_virt_size(path: &Path) -> anyhow::Result<FileSize> {
// Run qemu-img // Run qemu-img
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
cmd.args([ cmd.args([
@ -110,5 +157,5 @@ fn qcow_virt_size(path: &Path) -> anyhow::Result<usize> {
// Decode JSON // Decode JSON
let decoded: QCowInfoOutput = serde_json::from_str(&res_json)?; let decoded: QCowInfoOutput = serde_json::from_str(&res_json)?;
Ok(decoded.virtual_size) Ok(FileSize::from_bytes(decoded.virtual_size))
} }

View File

@ -0,0 +1,28 @@
#[derive(
serde::Serialize, serde::Deserialize, Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord,
)]
pub struct FileSize(usize);
impl FileSize {
pub const fn from_bytes(size: usize) -> Self {
Self(size)
}
pub const fn from_mb(mb: usize) -> Self {
Self(mb * 1000 * 1000)
}
pub const fn from_gb(gb: usize) -> Self {
Self(gb * 1000 * 1000 * 1000)
}
/// Get file size as bytes
pub fn as_bytes(&self) -> usize {
self.0
}
/// Get file size as megabytes
pub fn as_mb(&self) -> usize {
self.0 / (1000 * 1000)
}
}

View File

@ -1,5 +1,6 @@
pub mod exec_utils; pub mod exec_utils;
pub mod file_disks_utils; pub mod file_disks_utils;
pub mod file_size_utils;
pub mod files_utils; pub mod files_utils;
pub mod net_utils; pub mod net_utils;
pub mod rand_utils; pub mod rand_utils;

View File

@ -2,21 +2,19 @@ use crate::app_config::AppConfig;
use crate::constants; use crate::constants;
use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_lib_structures::XMLUuid;
use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo}; use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo};
use crate::utils::file_size_utils::FileSize;
use crate::utils::files_utils; use crate::utils::files_utils;
use lazy_regex::regex; use lazy_regex::regex;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
enum VMDisksError { enum VMDisksError {
#[error("DiskConfigError: {0}")] #[error("DiskConfigError: {0}")]
Config(&'static str), Config(&'static str),
#[error("DiskCreateError")]
Create,
} }
/// Type of disk allocation /// Type of disk allocation
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub enum VMDiskAllocType { pub enum VMDiskAllocType {
Fixed, Fixed,
Sparse, Sparse,
@ -38,7 +36,7 @@ pub struct VMFileDisk {
/// Disk name /// Disk name
pub name: String, pub name: String,
/// Disk size, in bytes /// Disk size, in bytes
pub size: usize, pub size: FileSize,
/// Disk format /// Disk format
#[serde(flatten)] #[serde(flatten)]
pub format: VMDiskFormat, pub format: VMDiskFormat,
@ -129,51 +127,20 @@ impl VMFileDisk {
return Ok(()); return Ok(());
} }
// Prepare command to create file // Create disk file
let res = match self.format { DiskFileInfo::create(
VMDiskFormat::Raw { alloc_type } => { &file,
let mut cmd = Command::new("/usr/bin/dd"); match self.format {
cmd.arg("if=/dev/zero") VMDiskFormat::Raw { alloc_type } => DiskFileFormat::Raw {
.arg(format!("of={}", file.to_string_lossy())) is_sparse: alloc_type == VMDiskAllocType::Sparse,
.arg("bs=1M"); },
VMDiskFormat::QCow2 => DiskFileFormat::QCow2 {
match alloc_type { virtual_size: self.size,
VMDiskAllocType::Fixed => cmd.arg(format!("count={}", self.size_mb())), },
VMDiskAllocType::Sparse => { },
cmd.arg(format!("seek={}", self.size_mb())).arg("count=0") self.size,
} )?;
};
cmd.output()?
}
VMDiskFormat::QCow2 => {
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
cmd.arg("create")
.arg("-f")
.arg("qcow2")
.arg(file)
.arg(format!("{}M", self.size_mb()));
cmd.output()?
}
};
// Execute Linux command
if !res.status.success() {
log::error!(
"Failed to create disk! stderr={} stdout={}",
String::from_utf8_lossy(&res.stderr),
String::from_utf8_lossy(&res.stdout)
);
return Err(VMDisksError::Create.into());
}
Ok(()) Ok(())
} }
/// Get the size of file disk in megabytes
pub fn size_mb(&self) -> usize {
self.size / (1000 * 1000)
}
} }