Compare commits
2 Commits
d5fbc24c96
...
a18310e04a
Author | SHA1 | Date | |
---|---|---|---|
a18310e04a | |||
dd7f9176fa |
@ -27,20 +27,20 @@ pub const ALLOWED_ISO_MIME_TYPES: [&str; 4] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/// ISO max size
|
/// ISO max size
|
||||||
pub const ISO_MAX_SIZE: usize = 10 * 1000 * 1000 * 1000;
|
pub const ISO_MAX_SIZE: FileSize = FileSize::from_gb(10);
|
||||||
|
|
||||||
/// Allowed uploaded disk images formats
|
/// Allowed uploaded disk images formats
|
||||||
pub const ALLOWED_DISK_IMAGES_MIME_TYPES: [&str; 2] =
|
pub const ALLOWED_DISK_IMAGES_MIME_TYPES: [&str; 2] =
|
||||||
["application/x-qemu-disk", "application/gzip"];
|
["application/x-qemu-disk", "application/gzip"];
|
||||||
|
|
||||||
/// Disk image max size
|
/// Disk image max size
|
||||||
pub const DISK_IMAGE_MAX_SIZE: usize = 10 * 1000 * 1000 * 1000 * 1000;
|
pub const DISK_IMAGE_MAX_SIZE: FileSize = FileSize::from_gb(10 * 1000);
|
||||||
|
|
||||||
/// Min VM memory size (MB)
|
/// Min VM memory size
|
||||||
pub const MIN_VM_MEMORY: usize = 100;
|
pub const MIN_VM_MEMORY: FileSize = FileSize::from_mb(100);
|
||||||
|
|
||||||
/// Max VM memory size (MB)
|
/// Max VM memory size
|
||||||
pub const MAX_VM_MEMORY: usize = 64000;
|
pub const MAX_VM_MEMORY: FileSize = FileSize::from_gb(64);
|
||||||
|
|
||||||
/// Disk name min length
|
/// Disk name min length
|
||||||
pub const DISK_NAME_MIN_LEN: usize = 2;
|
pub const DISK_NAME_MIN_LEN: usize = 2;
|
||||||
|
@ -24,7 +24,7 @@ pub async fn upload(MultipartForm(mut form): MultipartForm<UploadDiskImageForm>)
|
|||||||
let file = form.files.remove(0);
|
let file = form.files.remove(0);
|
||||||
|
|
||||||
// Check uploaded file size
|
// Check uploaded file size
|
||||||
if file.size > constants::DISK_IMAGE_MAX_SIZE {
|
if file.size > constants::DISK_IMAGE_MAX_SIZE.as_bytes() {
|
||||||
return Ok(HttpResponse::BadRequest().json("Disk image max size exceeded!"));
|
return Ok(HttpResponse::BadRequest().json("Disk image max size exceeded!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ pub async fn upload_file(MultipartForm(mut form): MultipartForm<UploadIsoForm>)
|
|||||||
|
|
||||||
let file = form.files.remove(0);
|
let file = form.files.remove(0);
|
||||||
|
|
||||||
if file.size > constants::ISO_MAX_SIZE {
|
if file.size > constants::ISO_MAX_SIZE.as_bytes() {
|
||||||
log::error!("Uploaded ISO file is too large!");
|
log::error!("Uploaded ISO file is too large!");
|
||||||
return Ok(HttpResponse::BadRequest().json("File is too large!"));
|
return Ok(HttpResponse::BadRequest().json("File is too large!"));
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ pub async fn upload_from_url(req: web::Json<DownloadFromURLReq>) -> HttpResult {
|
|||||||
let response = reqwest::get(&req.url).await?;
|
let response = reqwest::get(&req.url).await?;
|
||||||
|
|
||||||
if let Some(len) = response.content_length() {
|
if let Some(len) = response.content_length() {
|
||||||
if len > constants::ISO_MAX_SIZE as u64 {
|
if len > constants::ISO_MAX_SIZE.as_bytes() as u64 {
|
||||||
return Ok(HttpResponse::BadRequest().json("File is too large!"));
|
return Ok(HttpResponse::BadRequest().json("File is too large!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
builtin_nwfilter_rules: &constants::BUILTIN_NETWORK_FILTER_RULES,
|
builtin_nwfilter_rules: &constants::BUILTIN_NETWORK_FILTER_RULES,
|
||||||
nwfilter_chains: &constants::NETWORK_CHAINS,
|
nwfilter_chains: &constants::NETWORK_CHAINS,
|
||||||
constraints: ServerConstraints {
|
constraints: ServerConstraints {
|
||||||
iso_max_size: constants::ISO_MAX_SIZE,
|
iso_max_size: constants::ISO_MAX_SIZE.as_bytes(),
|
||||||
disk_image_max_size: constants::DISK_IMAGE_MAX_SIZE,
|
disk_image_max_size: constants::DISK_IMAGE_MAX_SIZE.as_bytes(),
|
||||||
|
|
||||||
vnc_token_duration: VNC_TOKEN_LIFETIME,
|
vnc_token_duration: VNC_TOKEN_LIFETIME,
|
||||||
|
|
||||||
@ -80,8 +80,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
vm_title_size: LenConstraints { min: 0, max: 50 },
|
vm_title_size: LenConstraints { min: 0, max: 50 },
|
||||||
group_id_size: LenConstraints { min: 3, max: 50 },
|
group_id_size: LenConstraints { min: 3, max: 50 },
|
||||||
memory_size: LenConstraints {
|
memory_size: LenConstraints {
|
||||||
min: constants::MIN_VM_MEMORY,
|
min: constants::MIN_VM_MEMORY.as_bytes(),
|
||||||
max: constants::MAX_VM_MEMORY,
|
max: constants::MAX_VM_MEMORY.as_bytes(),
|
||||||
},
|
},
|
||||||
disk_name_size: LenConstraints {
|
disk_name_size: LenConstraints {
|
||||||
min: DISK_NAME_MIN_LEN,
|
min: DISK_NAME_MIN_LEN,
|
||||||
|
@ -4,8 +4,8 @@ use crate::libvirt_lib_structures::XMLUuid;
|
|||||||
use crate::libvirt_lib_structures::domain::*;
|
use crate::libvirt_lib_structures::domain::*;
|
||||||
use crate::libvirt_rest_structures::LibVirtStructError;
|
use crate::libvirt_rest_structures::LibVirtStructError;
|
||||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
||||||
|
use crate::utils::file_size_utils::FileSize;
|
||||||
use crate::utils::files_utils;
|
use crate::utils::files_utils;
|
||||||
use crate::utils::files_utils::convert_size_unit_to_mb;
|
|
||||||
use crate::utils::vm_file_disks_utils::{VMDiskFormat, VMFileDisk};
|
use crate::utils::vm_file_disks_utils::{VMDiskFormat, VMFileDisk};
|
||||||
use lazy_regex::regex;
|
use lazy_regex::regex;
|
||||||
use num::Integer;
|
use num::Integer;
|
||||||
@ -70,8 +70,8 @@ pub struct VMInfo {
|
|||||||
pub group: Option<VMGroupId>,
|
pub group: Option<VMGroupId>,
|
||||||
pub boot_type: BootType,
|
pub boot_type: BootType,
|
||||||
pub architecture: VMArchitecture,
|
pub architecture: VMArchitecture,
|
||||||
/// VM allocated memory, in megabytes
|
/// VM allocated RAM memory
|
||||||
pub memory: usize,
|
pub memory: FileSize,
|
||||||
/// Number of vCPU for the VM
|
/// Number of vCPU for the VM
|
||||||
pub number_vcpu: usize,
|
pub number_vcpu: usize,
|
||||||
/// Enable VNC access through admin console
|
/// Enable VNC access through admin console
|
||||||
@ -380,7 +380,7 @@ impl VMInfo {
|
|||||||
|
|
||||||
memory: DomainMemoryXML {
|
memory: DomainMemoryXML {
|
||||||
unit: "MB".to_string(),
|
unit: "MB".to_string(),
|
||||||
memory: self.memory,
|
memory: self.memory.as_mb(),
|
||||||
},
|
},
|
||||||
|
|
||||||
vcpu: DomainVCPUXML {
|
vcpu: DomainVCPUXML {
|
||||||
@ -452,7 +452,7 @@ impl VMInfo {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
number_vcpu: domain.vcpu.body,
|
number_vcpu: domain.vcpu.body,
|
||||||
memory: convert_size_unit_to_mb(&domain.memory.unit, domain.memory.memory)?,
|
memory: FileSize::from_size_unit(&domain.memory.unit, domain.memory.memory)?,
|
||||||
vnc_access: domain.devices.graphics.is_some(),
|
vnc_access: domain.devices.graphics.is_some(),
|
||||||
iso_files: domain
|
iso_files: domain
|
||||||
.devices
|
.devices
|
||||||
|
@ -122,10 +122,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}))
|
}))
|
||||||
.app_data(conn.clone())
|
.app_data(conn.clone())
|
||||||
// Uploaded files
|
// Uploaded files
|
||||||
.app_data(
|
.app_data(MultipartFormConfig::default().total_limit(
|
||||||
MultipartFormConfig::default()
|
max(constants::DISK_IMAGE_MAX_SIZE, constants::ISO_MAX_SIZE).as_bytes(),
|
||||||
.total_limit(max(constants::DISK_IMAGE_MAX_SIZE, constants::ISO_MAX_SIZE)),
|
))
|
||||||
)
|
|
||||||
.app_data(TempFileConfig::default().directory(&AppConfig::get().temp_dir))
|
.app_data(TempFileConfig::default().directory(&AppConfig::get().temp_dir))
|
||||||
// Server controller
|
// Server controller
|
||||||
.route(
|
.route(
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
use std::ops::Mul;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
enum FilesSizeUtilsError {
|
||||||
|
#[error("UnitConvertError: {0}")]
|
||||||
|
UnitConvert(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds a data size, convertible in any form
|
||||||
#[derive(
|
#[derive(
|
||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
serde::Deserialize,
|
serde::Deserialize,
|
||||||
@ -25,6 +34,30 @@ impl FileSize {
|
|||||||
Self(gb * 1000 * 1000 * 1000)
|
Self(gb * 1000 * 1000 * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert size unit to MB
|
||||||
|
pub fn from_size_unit(unit: &str, value: usize) -> anyhow::Result<Self> {
|
||||||
|
let fact = match unit {
|
||||||
|
"bytes" | "b" => 1f64,
|
||||||
|
"KB" => 1000f64,
|
||||||
|
"MB" => 1000f64 * 1000f64,
|
||||||
|
"GB" => 1000f64 * 1000f64 * 1000f64,
|
||||||
|
"TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,
|
||||||
|
|
||||||
|
"k" | "KiB" => 1024f64,
|
||||||
|
"M" | "MiB" => 1024f64 * 1024f64,
|
||||||
|
"G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
|
||||||
|
"T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
FilesSizeUtilsError::UnitConvert(format!("Unknown size unit: {unit}")).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self((value as f64).mul(fact) as usize))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get file size as bytes
|
/// Get file size as bytes
|
||||||
pub fn as_bytes(&self) -> usize {
|
pub fn as_bytes(&self) -> usize {
|
||||||
self.0
|
self.0
|
||||||
@ -35,3 +68,24 @@ impl FileSize {
|
|||||||
self.0 / (1000 * 1000)
|
self.0 / (1000 * 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::utils::file_size_utils::FileSize;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_units_mb() {
|
||||||
|
assert_eq!(FileSize::from_size_unit("MB", 1).unwrap().as_mb(), 1);
|
||||||
|
assert_eq!(FileSize::from_size_unit("MB", 1000).unwrap().as_mb(), 1000);
|
||||||
|
assert_eq!(
|
||||||
|
FileSize::from_size_unit("GB", 1000).unwrap().as_mb(),
|
||||||
|
1000 * 1000
|
||||||
|
);
|
||||||
|
assert_eq!(FileSize::from_size_unit("GB", 1).unwrap().as_mb(), 1000);
|
||||||
|
assert_eq!(FileSize::from_size_unit("GiB", 3).unwrap().as_mb(), 3221);
|
||||||
|
assert_eq!(
|
||||||
|
FileSize::from_size_unit("KiB", 488281).unwrap().as_mb(),
|
||||||
|
499
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
use std::ops::{Div, Mul};
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
enum FilesUtilsError {
|
|
||||||
#[error("UnitConvertError: {0}")]
|
|
||||||
UnitConvert(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
const INVALID_CHARS: [&str; 19] = [
|
const INVALID_CHARS: [&str; 19] = [
|
||||||
"@", "\\", "/", ":", ",", "<", ">", "%", "'", "\"", "?", "{", "}", "$", "*", "|", ";", "=",
|
"@", "\\", "/", ":", ",", "<", ">", "%", "'", "\"", "?", "{", "}", "$", "*", "|", ";", "=",
|
||||||
"\t",
|
"\t",
|
||||||
@ -35,31 +28,9 @@ pub fn set_file_permission<P: AsRef<Path>>(path: P, mode: u32) -> anyhow::Result
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert size unit to MB
|
|
||||||
pub fn convert_size_unit_to_mb(unit: &str, value: usize) -> anyhow::Result<usize> {
|
|
||||||
let fact = match unit {
|
|
||||||
"bytes" | "b" => 1f64,
|
|
||||||
"KB" => 1000f64,
|
|
||||||
"MB" => 1000f64 * 1000f64,
|
|
||||||
"GB" => 1000f64 * 1000f64 * 1000f64,
|
|
||||||
"TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,
|
|
||||||
|
|
||||||
"k" | "KiB" => 1024f64,
|
|
||||||
"M" | "MiB" => 1024f64 * 1024f64,
|
|
||||||
"G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
|
|
||||||
"T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
return Err(FilesUtilsError::UnitConvert(format!("Unknown size unit: {unit}")).into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((value as f64).mul(fact.div((1000 * 1000) as f64)).ceil() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::utils::files_utils::{check_file_name, convert_size_unit_to_mb};
|
use crate::utils::files_utils::check_file_name;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_file_name() {
|
fn empty_file_name() {
|
||||||
@ -85,14 +56,4 @@ mod test {
|
|||||||
fn valid_file_name() {
|
fn valid_file_name() {
|
||||||
assert!(check_file_name("test.iso"));
|
assert!(check_file_name("test.iso"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn convert_units_mb() {
|
|
||||||
assert_eq!(convert_size_unit_to_mb("MB", 1).unwrap(), 1);
|
|
||||||
assert_eq!(convert_size_unit_to_mb("MB", 1000).unwrap(), 1000);
|
|
||||||
assert_eq!(convert_size_unit_to_mb("GB", 1000).unwrap(), 1000 * 1000);
|
|
||||||
assert_eq!(convert_size_unit_to_mb("GB", 1).unwrap(), 1000);
|
|
||||||
assert_eq!(convert_size_unit_to_mb("GiB", 3).unwrap(), 3222);
|
|
||||||
assert_eq!(convert_size_unit_to_mb("KiB", 488281).unwrap(), 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ function VMListWidget(p: {
|
|||||||
{row.name}
|
{row.name}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{row.description ?? ""}</TableCell>
|
<TableCell>{row.description ?? ""}</TableCell>
|
||||||
<TableCell>{vmMemoryToHuman(row.memory)}</TableCell>
|
<TableCell>{filesize(row.memory)}</TableCell>
|
||||||
<TableCell>{row.number_vcpu}</TableCell>
|
<TableCell>{row.number_vcpu}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<VMStatusWidget
|
<VMStatusWidget
|
||||||
@ -183,13 +183,13 @@ function VMListWidget(p: {
|
|||||||
<TableCell></TableCell>
|
<TableCell></TableCell>
|
||||||
<TableCell></TableCell>
|
<TableCell></TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{vmMemoryToHuman(
|
{filesize(
|
||||||
p.list
|
p.list
|
||||||
.filter((v) => runningVMs.has(v.name))
|
.filter((v) => runningVMs.has(v.name))
|
||||||
.reduce((s, v) => s + v.memory, 0)
|
.reduce((s, v) => s + v.memory, 0)
|
||||||
)}
|
)}
|
||||||
{" / "}
|
{" / "}
|
||||||
{vmMemoryToHuman(p.list.reduce((s, v) => s + v.memory, 0))}
|
{filesize(p.list.reduce((s, v) => s + v.memory, 0))}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{p.list
|
{p.list
|
||||||
@ -206,7 +206,3 @@ function VMListWidget(p: {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmMemoryToHuman(size: number): string {
|
|
||||||
return filesize(size * 1000 * 1000);
|
|
||||||
}
|
|
||||||
|
@ -279,14 +279,16 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
|
|||||||
label="Memory (MB)"
|
label="Memory (MB)"
|
||||||
editable={p.editable}
|
editable={p.editable}
|
||||||
type="number"
|
type="number"
|
||||||
value={p.vm.memory.toString()}
|
value={Math.floor(p.vm.memory / (1000 * 1000)).toString()}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
p.vm.memory = Number(v ?? "0");
|
p.vm.memory = Number(v ?? "0") * 1000 * 1000;
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
checkValue={(v) =>
|
checkValue={(v) =>
|
||||||
Number(v) > ServerApi.Config.constraints.memory_size.min &&
|
Number(v) >
|
||||||
Number(v) < ServerApi.Config.constraints.memory_size.max
|
ServerApi.Config.constraints.memory_size.min / (1000 * 1000) &&
|
||||||
|
Number(v) <
|
||||||
|
ServerApi.Config.constraints.memory_size.max / (1000 * 1000)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user