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
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;
/// 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)
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
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,
},
disk_size: LenConstraints {
min: DISK_SIZE_MIN,
max: DISK_SIZE_MAX,
min: DISK_SIZE_MIN.as_bytes(),
max: DISK_SIZE_MAX.as_bytes(),
},
net_name_size: LenConstraints { min: 2, max: 50 },

View File

@ -1,4 +1,5 @@
use crate::constants;
use crate::utils::file_size_utils::FileSize;
use std::os::linux::fs::MetadataExt;
use std::path::Path;
use std::process::Command;
@ -8,13 +9,15 @@ use std::time::UNIX_EPOCH;
enum DisksError {
#[error("DiskParseError: {0}")]
Parse(&'static str),
#[error("DiskCreateError")]
Create,
}
#[derive(Debug, serde::Serialize)]
#[serde(tag = "format")]
pub enum DiskFileFormat {
Raw { is_sparse: bool },
QCow2 { virtual_size: usize },
QCow2 { virtual_size: FileSize },
CompressedRaw,
CompressedQCow2,
}
@ -22,7 +25,7 @@ pub enum DiskFileFormat {
/// Disk file information
#[derive(serde::Serialize)]
pub struct DiskFileInfo {
pub file_size: usize,
pub file_size: FileSize,
#[serde(flatten)]
pub format: DiskFileFormat,
pub file_name: String,
@ -64,7 +67,7 @@ impl DiskFileInfo {
Ok(Self {
name,
file_size: metadata.len() as usize,
file_size: FileSize::from_bytes(metadata.len() as usize),
format,
file_name: file
.file_name()
@ -78,6 +81,50 @@ impl DiskFileInfo {
.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)]
@ -87,7 +134,7 @@ struct QCowInfoOutput {
}
/// 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
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
cmd.args([
@ -110,5 +157,5 @@ fn qcow_virt_size(path: &Path) -> anyhow::Result<usize> {
// Decode 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 file_disks_utils;
pub mod file_size_utils;
pub mod files_utils;
pub mod net_utils;
pub mod rand_utils;

View File

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