use std::{path::PathBuf, str::FromStr};
use pest::iterators::{Pair, Pairs};
use crate::{parsers::Rule,
zpool::{vdev::{ErrorStatistics, Vdev, VdevType},
CreateZpoolRequest, Disk, Health}};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Reason {
Other(String),
}
#[derive(Getters, Builder, Debug, Eq, PartialEq, Clone)]
#[builder(setter(into))]
#[get = "pub"]
pub struct Zpool {
name: String,
#[builder(default)]
id: Option<u64>,
health: Health,
vdevs: Vec<Vdev>,
#[builder(default)]
caches: Vec<Disk>,
#[builder(default)]
logs: Vec<Vdev>,
#[builder(default)]
spares: Vec<Disk>,
#[builder(default)]
action: Option<String>,
#[builder(default)]
errors: Option<String>,
#[builder(default)]
reason: Option<Reason>,
#[builder(default)]
error_statistics: ErrorStatistics,
}
impl Zpool {
pub fn builder() -> ZpoolBuilder { ZpoolBuilder::default() }
#[allow(clippy::option_unwrap_used, clippy::wildcard_enum_match_arm)]
pub(crate) fn from_pest_pair(pair: Pair<'_, Rule>) -> Zpool {
debug_assert!(pair.as_rule() == Rule::zpool);
let pairs = pair.into_inner();
let mut zpool = ZpoolBuilder::default();
for pair in pairs {
match pair.as_rule() {
Rule::pool_name => {
zpool.name(get_string_from_pair(pair));
},
Rule::pool_id => {
zpool.id(Some(get_u64_from_pair(pair)));
},
Rule::state => {
zpool.health(get_health_from_pair(pair));
},
Rule::action => {
zpool.action(Some(get_string_from_pair(pair)));
},
Rule::errors => {
zpool.errors(get_error_from_pair(pair));
},
Rule::vdevs => {
zpool.vdevs(get_vdevs_from_pair(pair));
},
Rule::pool_line => {
set_stats_and_reason_from_pool_line(pair, &mut zpool);
},
Rule::logs => {
zpool.logs(get_logs_from_pair(pair));
},
Rule::caches => {
zpool.caches(get_caches_from_pair(pair));
},
Rule::spares => {
zpool.spares(get_spares_from_pair(pair));
},
Rule::config | Rule::status | Rule::see | Rule::pool_headers => {},
Rule::scan_line => {},
_ => unreachable!(),
}
}
zpool.build().expect("Can't build zpool out of pair. Please report at: https://github.com/Inner-Heaven/libzetta-rs")
}
}
impl PartialEq<CreateZpoolRequest> for Zpool {
fn eq(&self, other: &CreateZpoolRequest) -> bool {
&self.logs == other.logs()
&& &self.name == other.name()
&& &self.caches == other.caches()
&& &self.vdevs == other.vdevs()
&& &self.spares == other.spares()
}
}
impl PartialEq<Zpool> for CreateZpoolRequest {
fn eq(&self, other: &Zpool) -> bool { other == self }
}
#[inline]
#[allow(clippy::option_unwrap_used, clippy::result_unwrap_used, clippy::wildcard_enum_match_arm)]
fn get_error_statistics_from_pair(pair: Pair<'_, Rule>) -> ErrorStatistics {
debug_assert_eq!(Rule::error_statistics, pair.as_rule());
let mut inner = pair.into_inner();
ErrorStatistics {
read: inner.next().unwrap().as_span().as_str().parse().unwrap_or(std::u64::MAX),
write: inner.next().unwrap().as_span().as_str().parse().unwrap_or(std::u64::MAX),
checksum: inner.next().unwrap().as_span().as_str().parse().unwrap_or(std::u64::MAX),
}
}
#[inline]
#[allow(clippy::option_unwrap_used, clippy::wildcard_enum_match_arm)]
fn set_stats_and_reason_from_pool_line(pool_line: Pair<'_, Rule>, zpool: &mut ZpoolBuilder) {
debug_assert_eq!(pool_line.as_rule(), Rule::pool_line);
for pair in pool_line.into_inner() {
match pair.as_rule() {
Rule::reason => {
zpool.reason(Some(Reason::Other(String::from(pair.as_span().as_str()))));
},
Rule::error_statistics => {
zpool.error_statistics(get_error_statistics_from_pair(pair));
},
_ => { },
};
}
}
#[inline]
fn get_vdev_type(raid_name: Pair<'_, Rule>) -> VdevType {
let raid_enum = raid_name.into_inner().next().expect("Failed to parse raid_enum");
debug_assert!(raid_enum.as_rule() == Rule::raid_enum);
VdevType::from_str(raid_enum.as_str()).expect("Failed to parse raid type")
}
#[inline]
fn get_path_from_path(path: Option<Pair<'_, Rule>>) -> PathBuf {
let path = path.expect("Missing path from disk line");
debug_assert!(path.as_rule() == Rule::path);
PathBuf::from(path.as_span().as_str())
}
#[inline]
fn get_health_from_health(health: Option<Pair<'_, Rule>>) -> Health {
let health = health.expect("Missing health from disk line");
debug_assert!(health.as_rule() == Rule::state_enum);
Health::try_from_str(Some(health.as_span().as_str())).expect("Failed to parse Health")
}
#[inline]
fn get_disk_from_disk_line(disk_line: Pair<'_, Rule>) -> Disk {
debug_assert!(disk_line.as_rule() == Rule::disk_line);
let mut inner = disk_line.into_inner();
let path = get_path_from_path(inner.next());
let health = get_health_from_health(inner.next());
let (error_statics, reason) = get_stats_and_reason_from_pairs(inner);
Disk::builder()
.path(path)
.health(health)
.error_statistics(error_statics)
.reason(reason)
.build()
.expect("Failed to build disk")
}
#[inline]
#[allow(clippy::option_unwrap_used, clippy::wildcard_enum_match_arm)]
fn get_stats_and_reason_from_pairs(pairs: Pairs<'_, Rule>) -> (ErrorStatistics, Option<Reason>) {
let mut stats = None;
let mut reason = None;
for pair in pairs {
match pair.as_rule() {
Rule::error_statistics => stats = Some(get_error_statistics_from_pair(pair)),
Rule::reason => reason = Some(Reason::Other(String::from(pair.as_span().as_str()))),
_ => {
unreachable!();
},
}
}
(stats.unwrap_or_default(), reason)
}
#[inline]
#[allow(clippy::option_unwrap_used, clippy::wildcard_enum_match_arm)]
fn get_vdevs_from_pair(pair: Pair<'_, Rule>) -> Vec<Vdev> {
debug_assert!(pair.as_rule() == Rule::vdevs);
pair.into_inner()
.map(|vdev| match vdev.as_rule() {
Rule::naked_vdev => {
let disk_line = vdev.into_inner().next().unwrap();
let disk = get_disk_from_disk_line(disk_line);
Vdev::builder()
.kind(VdevType::SingleDisk)
.health(disk.health().clone())
.reason(None)
.disks(vec![disk])
.build()
.expect("Failed to build Vdev")
},
Rule::raided_vdev => {
let mut inner = vdev.into_inner();
let raid_line = inner.next().unwrap();
debug_assert!(raid_line.as_rule() == Rule::raid_line);
let mut raid_line = raid_line.into_inner();
let raid_name = raid_line.next().unwrap();
let health = get_health_from_health(raid_line.next());
let (error_statics, reason) = get_stats_and_reason_from_pairs(raid_line);
Vdev::builder()
.kind(get_vdev_type(raid_name))
.health(health)
.disks(inner.map(get_disk_from_disk_line).collect())
.error_statistics(error_statics)
.reason(reason)
.build()
.expect("Failed to build vdev")
},
_ => {
unreachable!();
},
})
.collect()
}
#[inline]
fn get_health_from_pair(pair: Pair<'_, Rule>) -> Health {
let health = get_string_from_pair(pair);
Health::try_from_str(Some(&health)).expect("Failed to unwrap health")
}
#[inline]
fn get_u64_from_pair(pair: Pair<'_, Rule>) -> u64 {
get_value_from_pair(pair).as_str().parse().expect("Failed to unwrap u64")
}
#[inline]
fn get_string_from_pair(pair: Pair<'_, Rule>) -> String {
String::from(get_value_from_pair(pair).as_str())
}
#[inline]
fn get_value_from_pair(pair: Pair<'_, Rule>) -> Pair<'_, Rule> {
let mut pairs = pair.into_inner();
pairs.next().expect("Failed to unwrap value")
}
#[inline]
#[allow(clippy::option_unwrap_used, clippy::wildcard_enum_match_arm)]
fn get_error_from_pair(pair: Pair<'_, Rule>) -> Option<String> {
let mut pairs = pair.into_inner();
let error_pair = pairs.next().expect("Failed to unwrap error");
match error_pair.as_rule() {
Rule::no_errors => None,
_ => Some(String::from(error_pair.as_str())),
}
}
#[inline]
fn get_logs_from_pair(pair: Pair<'_, Rule>) -> Vec<Vdev> {
debug_assert!(pair.as_rule() == Rule::logs);
if let Some(vdevs) = pair.into_inner().next() {
get_vdevs_from_pair(vdevs)
} else {
Vec::new()
}
}
#[inline]
fn get_caches_from_pair(pair: Pair<'_, Rule>) -> Vec<Disk> {
debug_assert!(pair.as_rule() == Rule::caches);
pair.into_inner().map(get_disk_from_disk_line).collect()
}
#[inline]
fn get_spares_from_pair(pair: Pair<'_, Rule>) -> Vec<Disk> {
debug_assert!(pair.as_rule() == Rule::spares);
pair.into_inner().map(get_disk_from_disk_line).collect()
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use crate::zpool::{CreateVdevRequest, Disk, Health, Vdev, VdevType};
use super::{CreateZpoolRequest, Zpool};
#[test]
fn test_eq_zpool() {
let request = CreateZpoolRequest::builder()
.name("wat")
.zil(CreateVdevRequest::SingleDisk(PathBuf::from("hd0")))
.cache(PathBuf::from("hd1"))
.build()
.unwrap();
let zpool = Zpool::builder()
.name("wat")
.health(Health::Online)
.caches(vec![Disk::builder().path("hd1").health(Health::Online).build().unwrap()])
.logs(vec![Vdev::builder()
.kind(VdevType::SingleDisk)
.health(Health::Online)
.disks(vec![Disk::builder().path("hd0").health(Health::Online).build().unwrap()])
.build()
.unwrap()])
.vdevs(vec![])
.build()
.unwrap();
assert_eq!(request, zpool);
}
#[test]
fn test_ne_zpool() {
let request = CreateZpoolRequest::builder()
.name("wat")
.zil(CreateVdevRequest::SingleDisk(PathBuf::from("hd0")))
.build()
.unwrap();
let zpool =
Zpool::builder().name("wat").health(Health::Online).vdevs(vec![]).build().unwrap();
assert_ne!(request, zpool);
}
}