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
 | 
					/// 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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user