Can get the list of uploaded disk images
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:
parent
b55880b43c
commit
19ec9992be
@ -1,6 +1,7 @@
|
|||||||
use crate::app_config::AppConfig;
|
use crate::app_config::AppConfig;
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
|
use crate::utils::file_disks_utils::DiskFileInfo;
|
||||||
use crate::utils::files_utils;
|
use crate::utils::files_utils;
|
||||||
use actix_multipart::form::MultipartForm;
|
use actix_multipart::form::MultipartForm;
|
||||||
use actix_multipart::form::tempfile::TempFile;
|
use actix_multipart::form::tempfile::TempFile;
|
||||||
@ -55,3 +56,14 @@ pub async fn upload(MultipartForm(mut form): MultipartForm<UploadDiskImageForm>)
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok().json("Successfully uploaded disk image!"))
|
Ok(HttpResponse::Ok().json("Successfully uploaded disk image!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get disk images list
|
||||||
|
pub async fn get_list() -> HttpResult {
|
||||||
|
let mut list = vec![];
|
||||||
|
for entry in AppConfig::get().disk_images_storage_path().read_dir()? {
|
||||||
|
let entry = entry?;
|
||||||
|
list.push(DiskFileInfo::load_file(&entry.path())?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(list))
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cmp::max;
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_identity::IdentityMiddleware;
|
use actix_identity::IdentityMiddleware;
|
||||||
@ -14,6 +13,7 @@ use actix_web::middleware::Logger;
|
|||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{App, HttpServer, web};
|
use actix_web::{App, HttpServer, web};
|
||||||
use light_openid::basic_state_manager::BasicStateManager;
|
use light_openid::basic_state_manager::BasicStateManager;
|
||||||
|
use std::cmp::max;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use virtweb_backend::actors::libvirt_actor::LibVirtActor;
|
use virtweb_backend::actors::libvirt_actor::LibVirtActor;
|
||||||
use virtweb_backend::actors::vnc_tokens_actor::VNCTokensManager;
|
use virtweb_backend::actors::vnc_tokens_actor::VNCTokensManager;
|
||||||
@ -121,7 +121,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}))
|
}))
|
||||||
.app_data(conn.clone())
|
.app_data(conn.clone())
|
||||||
// Uploaded files
|
// Uploaded files
|
||||||
.app_data(MultipartFormConfig::default().total_limit(max(constants::DISK_IMAGE_MAX_SIZE,constants::ISO_MAX_SIZE)))
|
.app_data(
|
||||||
|
MultipartFormConfig::default()
|
||||||
|
.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(
|
||||||
@ -337,6 +340,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/api/disk_images/upload",
|
"/api/disk_images/upload",
|
||||||
web::post().to(disk_images_controller::upload),
|
web::post().to(disk_images_controller::upload),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/api/disk_images/list",
|
||||||
|
web::get().to(disk_images_controller::get_list),
|
||||||
|
)
|
||||||
// API tokens controller
|
// API tokens controller
|
||||||
.route(
|
.route(
|
||||||
"/api/token/create",
|
"/api/token/create",
|
||||||
|
@ -6,6 +6,7 @@ use lazy_regex::regex;
|
|||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum DisksError {
|
enum DisksError {
|
||||||
@ -52,35 +53,28 @@ impl FileDisk {
|
|||||||
pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
|
pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
|
||||||
let file = Path::new(path);
|
let file = Path::new(path);
|
||||||
|
|
||||||
if !file.is_file() {
|
let info = DiskFileInfo::load_file(file)?;
|
||||||
return Err(DisksError::Parse("Path is not a file!").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = file.metadata()?;
|
Ok(Self {
|
||||||
let name = file.file_stem().and_then(|s| s.to_str()).unwrap_or("disk");
|
name: info.name,
|
||||||
let ext = file.extension().and_then(|s| s.to_str()).unwrap_or("raw");
|
|
||||||
|
|
||||||
// Approximate raw file estimation
|
// Get only the virtual size of the file
|
||||||
let is_raw_sparse = metadata.len() / 512 >= metadata.st_blocks();
|
size: match info.format {
|
||||||
|
DiskFileFormat::Raw { .. } => info.file_size,
|
||||||
|
DiskFileFormat::QCow2 { virtual_size } => virtual_size,
|
||||||
|
_ => anyhow::bail!("Unsupported image format: {:?}", info.format),
|
||||||
|
},
|
||||||
|
|
||||||
let format = match ext {
|
format: match info.format {
|
||||||
"qcow2" => DiskFormat::QCow2,
|
DiskFileFormat::Raw { is_sparse } => DiskFormat::Raw {
|
||||||
"raw" => DiskFormat::Raw {
|
alloc_type: match is_sparse {
|
||||||
alloc_type: match is_raw_sparse {
|
|
||||||
true => DiskAllocType::Sparse,
|
true => DiskAllocType::Sparse,
|
||||||
false => DiskAllocType::Fixed,
|
false => DiskAllocType::Fixed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_ => anyhow::bail!("Unsupported disk extension: {ext}!"),
|
DiskFileFormat::QCow2 { .. } => DiskFormat::QCow2,
|
||||||
};
|
_ => anyhow::bail!("Unsupported image format: {:?}", info.format),
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
name: name.to_string(),
|
|
||||||
size: match format {
|
|
||||||
DiskFormat::Raw { .. } => metadata.len() as usize / (1000 * 1000),
|
|
||||||
DiskFormat::QCow2 => qcow_virt_size(path)? / (1000 * 1000),
|
|
||||||
},
|
},
|
||||||
format,
|
|
||||||
delete: false,
|
delete: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -180,6 +174,74 @@ impl FileDisk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub enum DiskFileFormat {
|
||||||
|
Raw { is_sparse: bool },
|
||||||
|
QCow2 { virtual_size: usize },
|
||||||
|
CompressedRaw,
|
||||||
|
CompressedQCow2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disk file information
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct DiskFileInfo {
|
||||||
|
file_size: usize,
|
||||||
|
format: DiskFileFormat,
|
||||||
|
file_name: String,
|
||||||
|
name: String,
|
||||||
|
created: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskFileInfo {
|
||||||
|
/// Get disk image file information
|
||||||
|
pub fn load_file(file: &Path) -> anyhow::Result<Self> {
|
||||||
|
if !file.is_file() {
|
||||||
|
return Err(DisksError::Parse("Path is not a file!").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file metadata
|
||||||
|
let metadata = file.metadata()?;
|
||||||
|
let mut name = file
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("disk")
|
||||||
|
.to_string();
|
||||||
|
let ext = file.extension().and_then(|s| s.to_str()).unwrap_or("raw");
|
||||||
|
|
||||||
|
// Determine file format
|
||||||
|
let format = match ext {
|
||||||
|
"qcow2" => DiskFileFormat::QCow2 {
|
||||||
|
virtual_size: qcow_virt_size(file)? / (1000 * 1000),
|
||||||
|
},
|
||||||
|
"raw" => DiskFileFormat::Raw {
|
||||||
|
is_sparse: metadata.len() / 512 >= metadata.st_blocks(),
|
||||||
|
},
|
||||||
|
"gz" if name.ends_with(".qcow2") => {
|
||||||
|
name = name.strip_suffix(".qcow2").unwrap_or(&name).to_string();
|
||||||
|
DiskFileFormat::CompressedQCow2
|
||||||
|
}
|
||||||
|
"gz" => DiskFileFormat::CompressedRaw,
|
||||||
|
_ => anyhow::bail!("Unsupported disk extension: {ext}!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
file_size: metadata.len() as usize / (1000 * 1000),
|
||||||
|
format,
|
||||||
|
file_name: file
|
||||||
|
.file_name()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string(),
|
||||||
|
created: metadata
|
||||||
|
.created()?
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct QCowInfoOutput {
|
struct QCowInfoOutput {
|
||||||
#[serde(rename = "virtual-size")]
|
#[serde(rename = "virtual-size")]
|
||||||
@ -187,10 +249,16 @@ struct QCowInfoOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get QCow2 virtual size
|
/// Get QCow2 virtual size
|
||||||
fn qcow_virt_size(path: &str) -> anyhow::Result<usize> {
|
fn qcow_virt_size(path: &Path) -> anyhow::Result<usize> {
|
||||||
// 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(["info", path, "--output", "json", "--force-share"]);
|
cmd.args([
|
||||||
|
"info",
|
||||||
|
path.to_str().unwrap_or(""),
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--force-share",
|
||||||
|
]);
|
||||||
let output = cmd.output()?;
|
let output = cmd.output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user