Generalize disk file creation logic
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 },
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								virtweb_backend/src/utils/file_size_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								virtweb_backend/src/utils/file_size_utils.rs
									
									
									
									
									
										Normal 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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user