libvirt volume refactor
This commit is contained in:
parent
f2c5d1073d
commit
4b6f469a58
10 changed files with 304 additions and 64 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -1590,6 +1590,15 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
|
@ -1863,6 +1872,7 @@ dependencies = [
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
|
|
|
@ -42,6 +42,7 @@ pub struct Config {
|
||||||
pub rpc: RPCConfig,
|
pub rpc: RPCConfig,
|
||||||
pub log_level: String,
|
pub log_level: String,
|
||||||
pub db_path: PathBuf,
|
pub db_path: PathBuf,
|
||||||
|
pub qemu_img_path: Option<PathBuf>,
|
||||||
pub libvirt_uri: String,
|
pub libvirt_uri: String,
|
||||||
pub storage: StorageConfig,
|
pub storage: StorageConfig,
|
||||||
pub dns: DNSConfig,
|
pub dns: DNSConfig,
|
||||||
|
@ -51,6 +52,7 @@ impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
log_level: "WARN".to_owned(),
|
log_level: "WARN".to_owned(),
|
||||||
|
qemu_img_path: None,
|
||||||
rpc: RPCConfig {
|
rpc: RPCConfig {
|
||||||
socket_path: PathBuf::from("/var/run/nazrin/nzrd.sock"),
|
socket_path: PathBuf::from("/var/run/nazrin/nzrd.sock"),
|
||||||
admin_group: None,
|
admin_group: None,
|
||||||
|
|
|
@ -31,10 +31,10 @@ pub struct NewInstanceArgs {
|
||||||
/// Memory to assign, in MiB
|
/// Memory to assign, in MiB
|
||||||
#[arg(short, long, default_value_t = 1024)]
|
#[arg(short, long, default_value_t = 1024)]
|
||||||
mem: u32,
|
mem: u32,
|
||||||
/// Primary HDD size, in MiB
|
/// Primary HDD size, in GiB
|
||||||
#[arg(short, long, default_value_t = 20)]
|
#[arg(short, long, default_value_t = 20)]
|
||||||
primary_size: u32,
|
primary_size: u32,
|
||||||
/// Secndary HDD size, in MiB
|
/// Secndary HDD size, in GiB
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
secondary_size: Option<u32>,
|
secondary_size: Option<u32>,
|
||||||
/// File containing a list of SSH keys to use
|
/// File containing a list of SSH keys to use
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tarpc = { version = "0.31", features = ["tokio1", "unix", "serde-transport", "serde-transport-bincode"] }
|
tarpc = { version = "0.31", features = ["tokio1", "unix", "serde-transport", "serde-transport-bincode"] }
|
||||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "process"] }
|
||||||
tokio-serde = { version = "0.8.0", features = ["bincode"] }
|
tokio-serde = { version = "0.8.0", features = ["bincode"] }
|
||||||
sled = "0.34.7"
|
sled = "0.34.7"
|
||||||
# virt = "0.2.11"
|
# virt = "0.2.11"
|
||||||
|
@ -34,9 +34,9 @@ trust-dns-server = "0.22.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
syslog = "6.0.1"
|
syslog = "6.0.1"
|
||||||
nix = "0.26.1"
|
nix = "0.26.1"
|
||||||
|
tempdir = "0.3.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3.7"
|
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -90,7 +90,7 @@ pub async fn new_instance(
|
||||||
|
|
||||||
// and upload it to a vol
|
// and upload it to a vol
|
||||||
let vol_data = Volume::new(&args.name, VolType::Raw, datasize!(1440 KiB));
|
let vol_data = Volume::new(&args.name, VolType::Raw, datasize!(1440 KiB));
|
||||||
let mut cidata_vol = VirtVolume::create_xml(&ctx.virt.pools.cidata, vol_data, 0)?;
|
let mut cidata_vol = VirtVolume::create_xml(&ctx.virt.pools.cidata, vol_data, 0).await?;
|
||||||
|
|
||||||
let cistream = Stream::new(&cidata_vol.get_connect()?, 0)?;
|
let cistream = Stream::new(&cidata_vol.get_connect()?, 0)?;
|
||||||
if let Err(er) = cidata_vol.upload(&cistream, 0, datasize!(1440 KiB).into(), 0) {
|
if let Err(er) = cidata_vol.upload(&cistream, 0, datasize!(1440 KiB).into(), 0) {
|
||||||
|
@ -120,6 +120,7 @@ pub async fn new_instance(
|
||||||
&args.name,
|
&args.name,
|
||||||
datasize!((args.disk_sizes.0) GiB),
|
datasize!((args.disk_sizes.0) GiB),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.map_err(|er| cmd_error!("Failed to clone base image: {}", er))?;
|
.map_err(|er| cmd_error!("Failed to clone base image: {}", er))?;
|
||||||
|
|
||||||
// and, if it exists: the second volume
|
// and, if it exists: the second volume
|
||||||
|
@ -130,11 +131,7 @@ pub async fn new_instance(
|
||||||
ctx.virt.pools.secondary.xml.vol_type(),
|
ctx.virt.pools.secondary.xml.vol_type(),
|
||||||
datasize!(sec_size GiB),
|
datasize!(sec_size GiB),
|
||||||
);
|
);
|
||||||
Some(VirtVolume::create_xml(
|
Some(VirtVolume::create_xml(&ctx.virt.pools.secondary, voldata, 0).await?)
|
||||||
&ctx.virt.pools.secondary,
|
|
||||||
voldata,
|
|
||||||
0,
|
|
||||||
)?)
|
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
@ -163,6 +160,7 @@ pub async fn new_instance(
|
||||||
.disk_device(|dsk| {
|
.disk_device(|dsk| {
|
||||||
dsk.volume_source(pri_name, &pri_vol.name)
|
dsk.volume_source(pri_name, &pri_vol.name)
|
||||||
.target("vda", "virtio")
|
.target("vda", "virtio")
|
||||||
|
.qcow2()
|
||||||
.boot_order(1)
|
.boot_order(1)
|
||||||
})
|
})
|
||||||
.disk_device(|fda| {
|
.disk_device(|fda| {
|
||||||
|
@ -183,6 +181,7 @@ pub async fn new_instance(
|
||||||
Some(vol) => instdata.disk_device(|dsk| {
|
Some(vol) => instdata.disk_device(|dsk| {
|
||||||
dsk.volume_source(sec_name, &vol.name)
|
dsk.volume_source(sec_name, &vol.name)
|
||||||
.target("vdb", "virtio")
|
.target("vdb", "virtio")
|
||||||
|
.qcow2()
|
||||||
}),
|
}),
|
||||||
None => instdata,
|
None => instdata,
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,6 +208,14 @@ impl DiskBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn qcow2(mut self) -> Self {
|
||||||
|
self.disk.driver = Some(DiskDriver {
|
||||||
|
name: "qemu".to_owned(),
|
||||||
|
r#type: Some("qcow2".to_owned()),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the source for a block device
|
/// Set the source for a block device
|
||||||
pub fn block_source(mut self, dev: &str) -> Self {
|
pub fn block_source(mut self, dev: &str) -> Self {
|
||||||
self.disk.r#type = DiskType::Block;
|
self.disk.r#type = DiskType::Block;
|
||||||
|
|
|
@ -239,6 +239,7 @@ pub enum IfaceType {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct NetDevice {
|
pub struct NetDevice {
|
||||||
#[serde(rename = "@type")]
|
#[serde(rename = "@type")]
|
||||||
|
@ -381,7 +382,7 @@ impl Default for OsData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
pub enum SizeUnit {
|
pub enum SizeUnit {
|
||||||
#[serde(rename = "bytes")]
|
#[serde(rename = "bytes")]
|
||||||
Bytes,
|
Bytes,
|
||||||
|
@ -409,6 +410,15 @@ impl std::fmt::Display for SizeUnit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for SizeUnit {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct SizeInfo {
|
pub struct SizeInfo {
|
||||||
#[serde(rename = "@unit")]
|
#[serde(rename = "@unit")]
|
||||||
|
@ -437,6 +447,17 @@ impl std::fmt::Display for SizeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SizeInfo {
|
||||||
|
pub fn qemu_style(&self) -> String {
|
||||||
|
if self.unit == SizeUnit::Bytes {
|
||||||
|
self.amount.to_string()
|
||||||
|
} else {
|
||||||
|
let unit_str = self.unit.to_string();
|
||||||
|
format!("{}{}", self.amount, unit_str.chars().next().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct CpuTopology {
|
pub struct CpuTopology {
|
||||||
#[serde(rename = "@sockets")]
|
#[serde(rename = "@sockets")]
|
||||||
|
@ -472,6 +493,7 @@ pub struct Volume {
|
||||||
pub target: Option<VolTarget>,
|
pub target: Option<VolTarget>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum VolType {
|
pub enum VolType {
|
||||||
Qcow2,
|
Qcow2,
|
||||||
Block,
|
Block,
|
||||||
|
@ -513,23 +535,39 @@ impl Volume {
|
||||||
key: None,
|
key: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vol_type(&self) -> Option<VolType> {
|
||||||
|
if let Some(target) = &self.target {
|
||||||
|
if let Some(format) = &target.format {
|
||||||
|
let t = match format.r#type.as_str() {
|
||||||
|
"qcow2" => VolType::Qcow2,
|
||||||
|
"raw" => VolType::Raw,
|
||||||
|
"block" => VolType::Block,
|
||||||
|
_ => VolType::Invalid,
|
||||||
|
};
|
||||||
|
return Some(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct VolTarget {
|
pub struct VolTarget {
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
format: Option<TargetFormat>,
|
pub format: Option<TargetFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct TargetFormat {
|
pub struct TargetFormat {
|
||||||
#[serde(rename = "@type")]
|
#[serde(rename = "@type")]
|
||||||
r#type: String,
|
pub r#type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
|
// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename = "pool")]
|
#[serde(rename = "pool")]
|
||||||
pub struct Pool {
|
pub struct Pool {
|
||||||
|
|
150
nzrd/src/img.rs
Normal file
150
nzrd/src/img.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
ops::Deref,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::ctrl::virtxml::SizeInfo;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ImgError {
|
||||||
|
message: String,
|
||||||
|
command_output: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ImgError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Some(output) = &self.command_output {
|
||||||
|
write!(f, "{}\n output from command: {}", self.message, output)
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for ImgError {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
message: format!("IO Error: {}", value),
|
||||||
|
command_output: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ImgError {}
|
||||||
|
|
||||||
|
impl ImgError {
|
||||||
|
fn new<S>(message: S) -> Self
|
||||||
|
where
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
message: message.as_ref().to_owned(),
|
||||||
|
command_output: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_output<S>(message: S, output: S) -> Self
|
||||||
|
where
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
message: message.as_ref().to_owned(),
|
||||||
|
command_output: Some(output.as_ref().to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locates where qemu-img is on the server system
|
||||||
|
fn qemu_img_path() -> Result<PathBuf, ImgError> {
|
||||||
|
if let Ok(path_var) = std::env::var("PATH") {
|
||||||
|
for path in path_var.split(':') {
|
||||||
|
let qemu_img = Path::new(path).join("qemu-img");
|
||||||
|
if qemu_img.exists() {
|
||||||
|
return Ok(qemu_img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ImgError::new("couldn't find qemu-img in PATH; you may want to define where qemu-img is in nazrin.conf"))
|
||||||
|
} else {
|
||||||
|
Err(ImgError::new(
|
||||||
|
"can't read PATH; you may want to define where qemu-img is in nazrin.conf",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
|
||||||
|
|
||||||
|
pub struct Qcow2Img {
|
||||||
|
_img_dir: TempDir,
|
||||||
|
img_file: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Qcow2Img {
|
||||||
|
type Target = File;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.img_file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_err(err: std::io::Error) -> ImgError {
|
||||||
|
ImgError::new(format!("failed to invoke qemu-img: {}", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_qemu_img<F, Fut>(func: F) -> Result<Qcow2Img, ImgError>
|
||||||
|
where
|
||||||
|
F: FnOnce(Command, &Path) -> Fut,
|
||||||
|
Fut: Future<Output = std::io::Result<std::process::Output>>,
|
||||||
|
{
|
||||||
|
let qi_path = qemu_img_path()?;
|
||||||
|
let img_dir = TempDir::new("nazrin")?;
|
||||||
|
let img_path = img_dir.path().join("img");
|
||||||
|
|
||||||
|
let qi_out = func(Command::new(&qi_path), &img_path)
|
||||||
|
.await
|
||||||
|
.map_err(cmd_err)?;
|
||||||
|
|
||||||
|
if !qi_out.status.success() {
|
||||||
|
Err(ImgError::with_output(
|
||||||
|
"qemu-img failed",
|
||||||
|
&String::from_utf8_lossy(&qi_out.stderr),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(Qcow2Img {
|
||||||
|
_img_dir: img_dir,
|
||||||
|
img_file: File::open(&img_path)
|
||||||
|
.expect("Couldn't open image file after qemu-img wrote to it"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_qcow2(size: SizeInfo) -> Result<Qcow2Img, ImgError> {
|
||||||
|
run_qemu_img(|mut cmd, img_path| {
|
||||||
|
cmd.arg("create")
|
||||||
|
.arg("-f")
|
||||||
|
.arg("qcow2")
|
||||||
|
.arg(img_path)
|
||||||
|
.arg(size.qemu_style())
|
||||||
|
.output()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clone_qcow2<P>(from: P, size: SizeInfo) -> Result<Qcow2Img, ImgError>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
run_qemu_img(|mut cmd, img_path| {
|
||||||
|
std::fs::copy(&from, img_path).expect("Copy failed");
|
||||||
|
|
||||||
|
cmd.arg("resize")
|
||||||
|
.arg(img_path)
|
||||||
|
.arg(size.qemu_style())
|
||||||
|
.output()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ mod cmd;
|
||||||
mod ctrl;
|
mod ctrl;
|
||||||
mod ctx;
|
mod ctx;
|
||||||
mod dns;
|
mod dns;
|
||||||
|
mod img;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
mod rpc;
|
mod rpc;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
134
nzrd/src/virt.rs
134
nzrd/src/virt.rs
|
@ -2,6 +2,8 @@ use std::io::{prelude::*, BufReader};
|
||||||
use std::{fmt::Display, ops::Deref};
|
use std::{fmt::Display, ops::Deref};
|
||||||
use virt::{storage_pool::StoragePool, storage_vol::StorageVol, stream::Stream};
|
use virt::{storage_pool::StoragePool, storage_vol::StorageVol, stream::Stream};
|
||||||
|
|
||||||
|
use crate::ctrl::virtxml::VolType;
|
||||||
|
use crate::img;
|
||||||
use crate::{
|
use crate::{
|
||||||
ctrl::virtxml::{Pool, SizeInfo, Volume},
|
ctrl::virtxml::{Pool, SizeInfo, Volume},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
@ -16,15 +18,86 @@ pub struct VirtVolume {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtVolume {
|
impl VirtVolume {
|
||||||
pub fn create_xml(
|
fn upload_img(from: &std::fs::File, to: Stream) -> Result<(), PoolError> {
|
||||||
|
let buf_cap: u64 = datasize!(4 MiB).into();
|
||||||
|
|
||||||
|
let mut reader = BufReader::with_capacity(buf_cap as usize, from);
|
||||||
|
loop {
|
||||||
|
let read_bytes = {
|
||||||
|
// read from the source file...
|
||||||
|
let data = match reader.fill_buf() {
|
||||||
|
Ok(buf) => buf,
|
||||||
|
Err(er) => {
|
||||||
|
if let Err(er) = to.abort() {
|
||||||
|
warn!("Stream abort failed: {}", er);
|
||||||
|
}
|
||||||
|
return Err(PoolError::FileError(er));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if data.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("pulled {} bytes", data.len());
|
||||||
|
|
||||||
|
// ... and then send upstream
|
||||||
|
let mut send_idx = 0;
|
||||||
|
while send_idx < data.len() {
|
||||||
|
match to.send(&data[send_idx..]) {
|
||||||
|
Ok(sz) => {
|
||||||
|
send_idx += sz;
|
||||||
|
}
|
||||||
|
Err(er) => {
|
||||||
|
if let Err(er) = to.abort() {
|
||||||
|
warn!("Stream abort failed: {}", er);
|
||||||
|
}
|
||||||
|
return Err(PoolError::UploadError(er));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.len()
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("consuming {} bytes", read_bytes);
|
||||||
|
reader.consume(read_bytes);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_xml(
|
||||||
pool: &StoragePool,
|
pool: &StoragePool,
|
||||||
xmldata: Volume,
|
xmldata: Volume,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let xml = quick_xml::se::to_string(&xmldata)?;
|
let xml = quick_xml::se::to_string(&xmldata)?;
|
||||||
|
|
||||||
|
let svol = StorageVol::create_xml(pool, &xml, flags)?;
|
||||||
|
|
||||||
|
if xmldata.vol_type() == Some(VolType::Qcow2) {
|
||||||
|
let size = xmldata.capacity.unwrap().clone();
|
||||||
|
let src_img = img::create_qcow2(size).await?;
|
||||||
|
|
||||||
|
let stream = match Stream::new(&svol.get_connect().map_err(PoolError::VirtError)?, 0) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(er) => {
|
||||||
|
svol.delete(0).ok();
|
||||||
|
return Err(Box::new(er));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let img_size = src_img.metadata().unwrap().len();
|
||||||
|
|
||||||
|
if let Err(er) = svol.upload(&stream, 0, img_size, 0) {
|
||||||
|
svol.delete(0).ok();
|
||||||
|
return Err(Box::new(PoolError::CantUpload(er)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::upload_img(&*src_img, stream)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: StorageVol::create_xml(pool, &xml, flags)?,
|
inner: svol,
|
||||||
persist: false,
|
persist: false,
|
||||||
name: xmldata.name,
|
name: xmldata.name,
|
||||||
})
|
})
|
||||||
|
@ -42,7 +115,7 @@ impl VirtVolume {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_vol(
|
pub async fn clone_vol(
|
||||||
&mut self,
|
&mut self,
|
||||||
pool: &VirtPool,
|
pool: &VirtPool,
|
||||||
vol_name: &str,
|
vol_name: &str,
|
||||||
|
@ -52,7 +125,9 @@ impl VirtVolume {
|
||||||
|
|
||||||
let src_path = self.get_path().map_err(PoolError::NoPath)?;
|
let src_path = self.get_path().map_err(PoolError::NoPath)?;
|
||||||
|
|
||||||
let src_fd = std::fs::File::open(src_path).map_err(PoolError::FileError)?;
|
let src_img = img::clone_qcow2(src_path, size.clone())
|
||||||
|
.await
|
||||||
|
.map_err(PoolError::QemuError)?;
|
||||||
|
|
||||||
let newvol = Volume::new(vol_name, pool.xml.vol_type(), size);
|
let newvol = Volume::new(vol_name, pool.xml.vol_type(), size);
|
||||||
let newxml_str = quick_xml::se::to_string(&newvol).map_err(PoolError::SerdeError)?;
|
let newxml_str = quick_xml::se::to_string(&newvol).map_err(PoolError::SerdeError)?;
|
||||||
|
@ -95,59 +170,14 @@ impl VirtVolume {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let img_size = src_fd.metadata().unwrap().len();
|
let img_size = src_img.metadata().unwrap().len();
|
||||||
|
|
||||||
if let Err(er) = cloned.upload(&stream, 0, img_size, 0) {
|
if let Err(er) = cloned.upload(&stream, 0, img_size, 0) {
|
||||||
cloned.delete(0).ok();
|
cloned.delete(0).ok();
|
||||||
return Err(PoolError::CantUpload(er));
|
return Err(PoolError::CantUpload(er));
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf_cap: u64 = datasize!(4 MiB).into();
|
Self::upload_img(&*src_img, stream)?;
|
||||||
|
|
||||||
let mut reader = BufReader::with_capacity(buf_cap as usize, src_fd);
|
|
||||||
loop {
|
|
||||||
let read_bytes = {
|
|
||||||
// read from the source file...
|
|
||||||
let data = match reader.fill_buf() {
|
|
||||||
Ok(buf) => buf,
|
|
||||||
Err(er) => {
|
|
||||||
if let Err(er) = stream.abort() {
|
|
||||||
warn!("Stream abort failed: {}", er);
|
|
||||||
}
|
|
||||||
if let Err(er) = cloned.delete(0) {
|
|
||||||
warn!("Couldn't delete destination volume: {}", er);
|
|
||||||
}
|
|
||||||
return Err(PoolError::FileError(er));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if data.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... and then send upstream
|
|
||||||
let mut send_idx = 0;
|
|
||||||
while send_idx < data.len() {
|
|
||||||
match stream.send(&data[send_idx..]) {
|
|
||||||
Ok(sz) => {
|
|
||||||
send_idx += sz;
|
|
||||||
}
|
|
||||||
Err(er) => {
|
|
||||||
if let Err(er) = stream.abort() {
|
|
||||||
warn!("Stream abort failed: {}", er);
|
|
||||||
}
|
|
||||||
if let Err(er) = cloned.delete(0) {
|
|
||||||
warn!("Couldn't delete destination volume: {}", er);
|
|
||||||
}
|
|
||||||
return Err(PoolError::UploadError(er));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.len()
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.consume(read_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: cloned,
|
inner: cloned,
|
||||||
|
@ -181,6 +211,7 @@ pub enum PoolError {
|
||||||
FileError(std::io::Error),
|
FileError(std::io::Error),
|
||||||
CantUpload(virt::error::Error),
|
CantUpload(virt::error::Error),
|
||||||
UploadError(virt::error::Error),
|
UploadError(virt::error::Error),
|
||||||
|
QemuError(img::ImgError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for PoolError {
|
impl Display for PoolError {
|
||||||
|
@ -192,6 +223,7 @@ impl Display for PoolError {
|
||||||
Self::FileError(er) => er.fmt(f),
|
Self::FileError(er) => er.fmt(f),
|
||||||
Self::CantUpload(er) => write!(f, "Unable to start upload to image: {}", er),
|
Self::CantUpload(er) => write!(f, "Unable to start upload to image: {}", er),
|
||||||
Self::UploadError(er) => write!(f, "Failed to upload image: {}", er),
|
Self::UploadError(er) => write!(f, "Failed to upload image: {}", er),
|
||||||
|
Self::QemuError(er) => er.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue