use std::{ffi::OsString, path::PathBuf};
use crate::zpool::{properties::ZpoolPropertiesWrite, vdev::CreateVdevRequest, CreateMode};
#[derive(Default, Builder, Debug, Clone, Getters, PartialEq, Eq)]
#[builder(setter(into))]
#[get = "pub"]
pub struct CreateZpoolRequest {
name: String,
#[builder(default)]
props: Option<ZpoolPropertiesWrite>,
#[builder(default)]
altroot: Option<PathBuf>,
#[builder(default)]
mount: Option<PathBuf>,
#[builder(default)]
create_mode: CreateMode,
#[builder(default)]
vdevs: Vec<CreateVdevRequest>,
#[builder(default)]
caches: Vec<PathBuf>,
#[builder(default)]
logs: Vec<CreateVdevRequest>,
#[builder(default)]
spares: Vec<PathBuf>,
}
impl CreateZpoolRequest {
pub fn builder() -> CreateZpoolRequestBuilder {
CreateZpoolRequestBuilder::default()
}
pub fn is_suitable_for_update(&self) -> bool {
let valid_vdevs = self.vdevs.iter().all(CreateVdevRequest::is_valid);
if !valid_vdevs {
return false;
}
let valid_logs = self.logs.iter().all(CreateVdevRequest::is_valid);
if !valid_logs {
return false;
}
true
}
pub fn is_suitable_for_create(&self) -> bool {
if self.vdevs.is_empty() {
return false;
}
self.is_suitable_for_update()
}
pub(crate) fn into_args(self) -> Vec<OsString> {
let mut ret: Vec<OsString> = Vec::with_capacity(13);
let vdevs = self
.vdevs
.into_iter()
.flat_map(CreateVdevRequest::into_args);
ret.extend(vdevs);
if !self.logs.is_empty() {
let log_vdevs = self.logs.into_iter().flat_map(CreateVdevRequest::into_args);
ret.push("log".into());
ret.extend(log_vdevs);
}
if !self.caches.is_empty() {
let caches = self.caches.into_iter().map(PathBuf::into_os_string);
ret.push("cache".into());
ret.extend(caches);
}
if !self.spares.is_empty() {
let spares = self.spares.into_iter().map(PathBuf::into_os_string);
ret.push("spare".into());
ret.extend(spares);
}
ret
}
}
impl CreateZpoolRequestBuilder {
pub fn vdev(&mut self, vdev: CreateVdevRequest) -> &mut CreateZpoolRequestBuilder {
match self.vdevs {
Some(ref mut vec) => vec.push(vdev),
None => {
self.vdevs = Some(Vec::new());
return self.vdev(vdev);
}
}
self
}
pub fn cache(&mut self, disk: PathBuf) -> &mut CreateZpoolRequestBuilder {
match self.caches {
Some(ref mut vec) => vec.push(disk),
None => {
self.caches = Some(Vec::new());
return self.cache(disk);
}
}
self
}
pub fn zil(&mut self, log: CreateVdevRequest) -> &mut CreateZpoolRequestBuilder {
match self.logs {
Some(ref mut vec) => vec.push(log),
None => {
self.logs = Some(Vec::with_capacity(1));
return self.zil(log);
}
}
self
}
pub fn spare(&mut self, disk: PathBuf) -> &mut CreateZpoolRequestBuilder {
match self.spares {
Some(ref mut vec) => vec.push(disk),
None => {
self.spares = Some(Vec::new());
return self.spare(disk);
}
}
self
}
}
#[cfg(test)]
mod test {
use std::{fs::File, path::PathBuf};
use tempdir::TempDir;
use super::*;
fn get_disks(num: usize, path: &PathBuf) -> Vec<PathBuf> {
(0..num).map(|_| path.clone()).collect()
}
fn args_from_slice(args: &[&str]) -> Vec<OsString> {
args.to_vec().into_iter().map(OsString::from).collect()
}
#[test]
fn test_validators() {
let tmp_dir = TempDir::new("zpool-tests").unwrap();
let file_path = tmp_dir.path().join("block-device");
let _valid_file = File::create(file_path.clone()).unwrap();
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdevs(vec![CreateVdevRequest::Mirror(get_disks(2, &file_path))])
.build()
.unwrap();
assert!(topo.is_suitable_for_create());
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdevs(vec![CreateVdevRequest::Mirror(get_disks(1, &file_path))])
.build()
.unwrap();
assert!(!topo.is_suitable_for_create());
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdevs(vec![CreateVdevRequest::Mirror(get_disks(2, &file_path))])
.caches(get_disks(2, &file_path))
.build()
.unwrap();
assert!(topo.is_suitable_for_create());
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.cache(file_path)
.build()
.unwrap();
assert!(topo.is_suitable_for_update());
assert!(!topo.is_suitable_for_create());
}
#[test]
fn test_builder() {
let result = CreateZpoolRequest::builder().build();
assert!(result.is_err());
}
#[test]
fn test_args() {
let tmp_dir = TempDir::new("zpool-tests").unwrap();
let file_path = tmp_dir.path().join("block-device");
let path = file_path.to_str().unwrap();
let _valid_file = File::create(file_path.clone()).unwrap();
let naked_vdev = CreateVdevRequest::SingleDisk(file_path.clone());
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.cache(file_path.clone())
.build()
.unwrap();
let result: Vec<OsString> = topo.into_args();
let expected = args_from_slice(&["cache", path]);
assert_eq!(expected, result);
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdev(naked_vdev.clone())
.vdev(naked_vdev.clone())
.zil(CreateVdevRequest::Mirror(get_disks(2, &file_path)))
.build()
.unwrap();
let result = topo.into_args();
let expected = args_from_slice(&[path, path, "log", "mirror", path, path]);
assert_eq!(expected, result);
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdev(CreateVdevRequest::RaidZ(get_disks(3, &file_path)))
.build()
.unwrap();
let result = topo.into_args();
let expected = args_from_slice(&["raidz", path, path, path]);
assert_eq!(expected, result);
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdev(CreateVdevRequest::RaidZ2(get_disks(5, &file_path)))
.build()
.unwrap();
let result = topo.into_args();
let expected = args_from_slice(&["raidz2", path, path, path, path, path]);
assert_eq!(expected, result);
let topo = CreateZpoolRequestBuilder::default()
.name("tank")
.vdev(CreateVdevRequest::RaidZ3(get_disks(8, &file_path)))
.build()
.unwrap();
let result = topo.into_args();
let expected = args_from_slice(&["raidz3", path, path, path, path, path, path, path, path]);
assert_eq!(expected, result);
}
}