tools: disk: rustfmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
a1c906cb02
commit
af6fdb9d0d
|
@ -1,13 +1,13 @@
|
|||
use std::collections::HashSet;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use anyhow::{Error};
|
||||
use serde_json::Value;
|
||||
use anyhow::Error;
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::LsblkInfo;
|
||||
|
||||
lazy_static!{
|
||||
lazy_static! {
|
||||
static ref LVM_UUIDS: HashSet<&'static str> = {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("e6d6d379-f507-44c2-a23c-238f2a3df928");
|
||||
|
@ -18,14 +18,18 @@ lazy_static!{
|
|||
/// Get set of devices used by LVM (pvs).
|
||||
///
|
||||
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
||||
pub fn get_lvm_devices(
|
||||
lsblk_info: &[LsblkInfo],
|
||||
) -> Result<HashSet<u64>, Error> {
|
||||
|
||||
pub fn get_lvm_devices(lsblk_info: &[LsblkInfo]) -> Result<HashSet<u64>, Error> {
|
||||
const PVS_BIN_PATH: &str = "pvs";
|
||||
|
||||
let mut command = std::process::Command::new(PVS_BIN_PATH);
|
||||
command.args(&["--reportformat", "json", "--noheadings", "--readonly", "-o", "pv_name"]);
|
||||
command.args(&[
|
||||
"--reportformat",
|
||||
"json",
|
||||
"--noheadings",
|
||||
"--readonly",
|
||||
"-o",
|
||||
"pv_name",
|
||||
]);
|
||||
|
||||
let output = proxmox_sys::command::run_command(command, None)?;
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ use once_cell::sync::OnceCell;
|
|||
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
|
||||
use proxmox_sys::error::io_err_other;
|
||||
use proxmox_sys::linux::procfs::{MountInfo, mountinfo::Device};
|
||||
use proxmox_sys::{io_bail, io_format_err};
|
||||
use proxmox_schema::api;
|
||||
use proxmox_sys::error::io_err_other;
|
||||
use proxmox_sys::linux::procfs::{mountinfo::Device, MountInfo};
|
||||
use proxmox_sys::{io_bail, io_format_err};
|
||||
|
||||
use pbs_api_types::{BLOCKDEVICE_NAME_REGEX, StorageStatus};
|
||||
use pbs_api_types::{StorageStatus, BLOCKDEVICE_NAME_REGEX};
|
||||
|
||||
mod zfs;
|
||||
pub use zfs::*;
|
||||
|
@ -32,7 +32,7 @@ pub use lvm::*;
|
|||
mod smart;
|
||||
pub use smart::*;
|
||||
|
||||
lazy_static::lazy_static!{
|
||||
lazy_static::lazy_static! {
|
||||
static ref ISCSI_PATH_REGEX: regex::Regex =
|
||||
regex::Regex::new(r"host[^/]*/session[^/]*").unwrap();
|
||||
}
|
||||
|
@ -153,7 +153,6 @@ impl DiskManage {
|
|||
&self,
|
||||
path: &std::path::Path,
|
||||
) -> Result<Option<(String, Device, Option<OsString>)>, Error> {
|
||||
|
||||
let stat = nix::sys::stat::stat(path)?;
|
||||
let device = Device::from_dev_t(stat.st_dev);
|
||||
|
||||
|
@ -161,7 +160,11 @@ impl DiskManage {
|
|||
|
||||
for (_id, entry) in self.mount_info()? {
|
||||
if entry.root == root_path && entry.device == device {
|
||||
return Ok(Some((entry.fs_type.clone(), entry.device, entry.mount_source.clone())));
|
||||
return Ok(Some((
|
||||
entry.fs_type.clone(),
|
||||
entry.device,
|
||||
entry.mount_source.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,7 +303,7 @@ impl Disk {
|
|||
/// Get the disk's size in bytes.
|
||||
pub fn size(&self) -> io::Result<u64> {
|
||||
Ok(*self.info.size.get_or_try_init(|| {
|
||||
self.read_sys_u64("size")?.map(|s| s*512).ok_or_else(|| {
|
||||
self.read_sys_u64("size")?.map(|s| s * 512).ok_or_else(|| {
|
||||
io_format_err!(
|
||||
"failed to get disk size from {:?}",
|
||||
self.syspath().join("size"),
|
||||
|
@ -444,19 +447,19 @@ impl Disk {
|
|||
/// another kernel driver like the device mapper.
|
||||
pub fn has_holders(&self) -> io::Result<bool> {
|
||||
Ok(*self
|
||||
.info
|
||||
.has_holders
|
||||
.get_or_try_init(|| -> io::Result<bool> {
|
||||
let mut subdir = self.syspath().to_owned();
|
||||
subdir.push("holders");
|
||||
for entry in std::fs::read_dir(subdir)? {
|
||||
match entry?.file_name().as_bytes() {
|
||||
b"." | b".." => (),
|
||||
_ => return Ok(true),
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
})?)
|
||||
.info
|
||||
.has_holders
|
||||
.get_or_try_init(|| -> io::Result<bool> {
|
||||
let mut subdir = self.syspath().to_owned();
|
||||
subdir.push("holders");
|
||||
for entry in std::fs::read_dir(subdir)? {
|
||||
match entry?.file_name().as_bytes() {
|
||||
b"." | b".." => (),
|
||||
_ => return Ok(true),
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
})?)
|
||||
}
|
||||
|
||||
/// Check if this disk is mounted.
|
||||
|
@ -473,26 +476,28 @@ impl Disk {
|
|||
pub fn read_stat(&self) -> std::io::Result<Option<BlockDevStat>> {
|
||||
if let Some(stat) = self.read_sys(Path::new("stat"))? {
|
||||
let stat = unsafe { std::str::from_utf8_unchecked(&stat) };
|
||||
let stat: Vec<u64> = stat.split_ascii_whitespace().map(|s| {
|
||||
u64::from_str_radix(s, 10).unwrap_or(0)
|
||||
}).collect();
|
||||
let stat: Vec<u64> = stat
|
||||
.split_ascii_whitespace()
|
||||
.map(|s| u64::from_str_radix(s, 10).unwrap_or(0))
|
||||
.collect();
|
||||
|
||||
if stat.len() < 15 { return Ok(None); }
|
||||
if stat.len() < 15 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
return Ok(Some(BlockDevStat {
|
||||
read_ios: stat[0],
|
||||
read_sectors: stat[2],
|
||||
write_ios: stat[4] + stat[11], // write + discard
|
||||
write_ios: stat[4] + stat[11], // write + discard
|
||||
write_sectors: stat[6] + stat[13], // write + discard
|
||||
io_ticks: stat[10],
|
||||
}));
|
||||
}));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// List device partitions
|
||||
pub fn partitions(&self) -> Result<HashMap<u64, Disk>, Error> {
|
||||
|
||||
let sys_path = self.syspath();
|
||||
let device = self.sysname().to_string_lossy().to_string();
|
||||
|
||||
|
@ -505,7 +510,9 @@ impl Disk {
|
|||
Err(_) => continue, // skip non utf8 entries
|
||||
};
|
||||
|
||||
if !name.starts_with(&device) { continue; }
|
||||
if !name.starts_with(&device) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut part_path = sys_path.to_owned();
|
||||
part_path.push(name);
|
||||
|
@ -523,7 +530,6 @@ impl Disk {
|
|||
|
||||
/// Returns disk usage information (total, used, avail)
|
||||
pub fn disk_usage(path: &std::path::Path) -> Result<StorageStatus, Error> {
|
||||
|
||||
let mut stat: libc::statfs64 = unsafe { std::mem::zeroed() };
|
||||
|
||||
use nix::NixPath;
|
||||
|
@ -533,16 +539,16 @@ pub fn disk_usage(path: &std::path::Path) -> Result<StorageStatus, Error> {
|
|||
|
||||
let bsize = stat.f_bsize as u64;
|
||||
|
||||
Ok(StorageStatus{
|
||||
total: stat.f_blocks*bsize,
|
||||
used: (stat.f_blocks-stat.f_bfree)*bsize,
|
||||
avail: stat.f_bavail*bsize,
|
||||
Ok(StorageStatus {
|
||||
total: stat.f_blocks * bsize,
|
||||
used: (stat.f_blocks - stat.f_bfree) * bsize,
|
||||
avail: stat.f_bavail * bsize,
|
||||
})
|
||||
}
|
||||
|
||||
#[api()]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// This is just a rough estimate for a "type" of disk.
|
||||
pub enum DiskType {
|
||||
/// We know nothing.
|
||||
|
@ -570,7 +576,6 @@ pub struct BlockDevStat {
|
|||
|
||||
/// Use lsblk to read partition type uuids and file system types.
|
||||
pub fn get_lsblk_info() -> Result<Vec<LsblkInfo>, Error> {
|
||||
|
||||
let mut command = std::process::Command::new("lsblk");
|
||||
command.args(&["--json", "-o", "path,parttype,fstype"]);
|
||||
|
||||
|
@ -584,10 +589,7 @@ pub fn get_lsblk_info() -> Result<Vec<LsblkInfo>, Error> {
|
|||
/// Get set of devices with a file system label.
|
||||
///
|
||||
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
||||
fn get_file_system_devices(
|
||||
lsblk_info: &[LsblkInfo],
|
||||
) -> Result<HashSet<u64>, Error> {
|
||||
|
||||
fn get_file_system_devices(lsblk_info: &[LsblkInfo]) -> Result<HashSet<u64>, Error> {
|
||||
let mut device_set: HashSet<u64> = HashSet::new();
|
||||
|
||||
for info in lsblk_info.iter() {
|
||||
|
@ -602,7 +604,7 @@ fn get_file_system_devices(
|
|||
|
||||
#[api()]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DiskUsageType {
|
||||
/// Disk is not used (as far we can tell)
|
||||
Unused,
|
||||
|
@ -634,7 +636,7 @@ pub enum DiskUsageType {
|
|||
}
|
||||
)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// Information about how a Disk is used
|
||||
pub struct DiskUsageInfo {
|
||||
/// Disk name (/sys/block/<name>)
|
||||
|
@ -668,7 +670,6 @@ fn scan_partitions(
|
|||
zfs_devices: &HashSet<u64>,
|
||||
device: &str,
|
||||
) -> Result<DiskUsageType, Error> {
|
||||
|
||||
let mut sys_path = std::path::PathBuf::from("/sys/block");
|
||||
sys_path.push(device);
|
||||
|
||||
|
@ -686,7 +687,9 @@ fn scan_partitions(
|
|||
Ok(name) => name,
|
||||
Err(_) => continue, // skip non utf8 entries
|
||||
};
|
||||
if !name.starts_with(device) { continue; }
|
||||
if !name.starts_with(device) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found_partitions = true;
|
||||
|
||||
|
@ -709,9 +712,9 @@ fn scan_partitions(
|
|||
found_dm = true;
|
||||
}
|
||||
|
||||
if zfs_devices.contains(&devnum) {
|
||||
if zfs_devices.contains(&devnum) {
|
||||
found_zfs = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found_mountpoints {
|
||||
|
@ -729,12 +732,8 @@ fn scan_partitions(
|
|||
Ok(used)
|
||||
}
|
||||
|
||||
|
||||
/// Get disk usage information for a single disk
|
||||
pub fn get_disk_usage_info(
|
||||
disk: &str,
|
||||
no_smart: bool,
|
||||
) -> Result<DiskUsageInfo, Error> {
|
||||
pub fn get_disk_usage_info(disk: &str, no_smart: bool) -> Result<DiskUsageInfo, Error> {
|
||||
let mut filter = Vec::new();
|
||||
filter.push(disk.to_string());
|
||||
let mut map = get_disks(Some(filter), no_smart)?;
|
||||
|
@ -752,15 +751,15 @@ pub fn get_disks(
|
|||
// do no include data from smartctl
|
||||
no_smart: bool,
|
||||
) -> Result<HashMap<String, DiskUsageInfo>, Error> {
|
||||
|
||||
let disk_manager = DiskManage::new();
|
||||
|
||||
let lsblk_info = get_lsblk_info()?;
|
||||
|
||||
let zfs_devices = zfs_devices(&lsblk_info, None).or_else(|err| -> Result<HashSet<u64>, Error> {
|
||||
eprintln!("error getting zfs devices: {}", err);
|
||||
Ok(HashSet::new())
|
||||
})?;
|
||||
let zfs_devices =
|
||||
zfs_devices(&lsblk_info, None).or_else(|err| -> Result<HashSet<u64>, Error> {
|
||||
eprintln!("error getting zfs devices: {}", err);
|
||||
Ok(HashSet::new())
|
||||
})?;
|
||||
|
||||
let lvm_devices = get_lvm_devices(&lsblk_info)?;
|
||||
|
||||
|
@ -770,20 +769,25 @@ pub fn get_disks(
|
|||
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for item in proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX)? {
|
||||
for item in proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX)?
|
||||
{
|
||||
let item = item?;
|
||||
|
||||
let name = item.file_name().to_str().unwrap().to_string();
|
||||
|
||||
if let Some(ref disks) = disks {
|
||||
if !disks.contains(&name) { continue; }
|
||||
if !disks.contains(&name) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let sys_path = format!("/sys/block/{}", name);
|
||||
|
||||
if let Ok(target) = std::fs::read_link(&sys_path) {
|
||||
if let Some(target) = target.to_str() {
|
||||
if ISCSI_PATH_REGEX.is_match(target) { continue; } // skip iSCSI devices
|
||||
if ISCSI_PATH_REGEX.is_match(target) {
|
||||
continue;
|
||||
} // skip iSCSI devices
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -809,7 +813,7 @@ pub fn get_disks(
|
|||
|
||||
match disk.is_mounted() {
|
||||
Ok(true) => usage = DiskUsageType::Mounted,
|
||||
Ok(false) => {},
|
||||
Ok(false) => {}
|
||||
Err(_) => continue, // skip devices with undetectable mount status
|
||||
}
|
||||
|
||||
|
@ -817,17 +821,20 @@ pub fn get_disks(
|
|||
usage = DiskUsageType::ZFS;
|
||||
}
|
||||
|
||||
let vendor = disk.vendor().unwrap_or(None).
|
||||
map(|s| s.to_string_lossy().trim().to_string());
|
||||
let vendor = disk
|
||||
.vendor()
|
||||
.unwrap_or(None)
|
||||
.map(|s| s.to_string_lossy().trim().to_string());
|
||||
|
||||
let model = disk.model().map(|s| s.to_string_lossy().into_owned());
|
||||
|
||||
let serial = disk.serial().map(|s| s.to_string_lossy().into_owned());
|
||||
|
||||
let devpath = disk.device_path().map(|p| p.to_owned())
|
||||
let devpath = disk
|
||||
.device_path()
|
||||
.map(|p| p.to_owned())
|
||||
.map(|p| p.to_string_lossy().to_string());
|
||||
|
||||
|
||||
let wwn = disk.wwn().map(|s| s.to_string_lossy().into_owned());
|
||||
|
||||
if usage != DiskUsageType::Mounted {
|
||||
|
@ -836,7 +843,7 @@ pub fn get_disks(
|
|||
if part_usage != DiskUsageType::Unused {
|
||||
usage = part_usage;
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(_) => continue, // skip devices if scan_partitions fail
|
||||
};
|
||||
}
|
||||
|
@ -849,7 +856,7 @@ pub fn get_disks(
|
|||
usage = DiskUsageType::DeviceMapper;
|
||||
}
|
||||
|
||||
let mut status = SmartStatus::Unknown;
|
||||
let mut status = SmartStatus::Unknown;
|
||||
let mut wearout = None;
|
||||
|
||||
if !no_smart {
|
||||
|
@ -861,8 +868,15 @@ pub fn get_disks(
|
|||
|
||||
let info = DiskUsageInfo {
|
||||
name: name.clone(),
|
||||
vendor, model, serial, devpath, size, wwn, disk_type,
|
||||
status, wearout,
|
||||
vendor,
|
||||
model,
|
||||
serial,
|
||||
devpath,
|
||||
size,
|
||||
wwn,
|
||||
disk_type,
|
||||
status,
|
||||
wearout,
|
||||
used: usage,
|
||||
gpt: disk.has_gpt(),
|
||||
rpm: disk.ata_rotation_rate_rpm(),
|
||||
|
@ -876,7 +890,6 @@ pub fn get_disks(
|
|||
|
||||
/// Try to reload the partition table
|
||||
pub fn reread_partition_table(disk: &Disk) -> Result<(), Error> {
|
||||
|
||||
let disk_path = match disk.device_path() {
|
||||
Some(path) => path,
|
||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||
|
@ -893,7 +906,6 @@ pub fn reread_partition_table(disk: &Disk) -> Result<(), Error> {
|
|||
|
||||
/// Initialize disk by writing a GPT partition table
|
||||
pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Error> {
|
||||
|
||||
let disk_path = match disk.device_path() {
|
||||
Some(path) => path,
|
||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||
|
@ -912,7 +924,6 @@ pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Erro
|
|||
|
||||
/// Create a single linux partition using the whole available space
|
||||
pub fn create_single_linux_partition(disk: &Disk) -> Result<Disk, Error> {
|
||||
|
||||
let disk_path = match disk.device_path() {
|
||||
Some(path) => path,
|
||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||
|
@ -934,7 +945,7 @@ pub fn create_single_linux_partition(disk: &Disk) -> Result<Disk, Error> {
|
|||
|
||||
#[api()]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FileSystemType {
|
||||
/// Linux Ext4
|
||||
Ext4,
|
||||
|
@ -963,7 +974,6 @@ impl std::str::FromStr for FileSystemType {
|
|||
|
||||
/// Create a file system on a disk or disk partition
|
||||
pub fn create_file_system(disk: &Disk, fs_type: FileSystemType) -> Result<(), Error> {
|
||||
|
||||
let disk_path = match disk.device_path() {
|
||||
Some(path) => path,
|
||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||
|
@ -982,21 +992,21 @@ pub fn create_file_system(disk: &Disk, fs_type: FileSystemType) -> Result<(), Er
|
|||
|
||||
/// Block device name completion helper
|
||||
pub fn complete_disk_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
||||
let dir = match proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX) {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => return vec![],
|
||||
};
|
||||
let dir =
|
||||
match proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX) {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => return vec![],
|
||||
};
|
||||
|
||||
dir.flatten().map(|item| {
|
||||
item.file_name().to_str().unwrap().to_string()
|
||||
}).collect()
|
||||
dir.flatten()
|
||||
.map(|item| item.file_name().to_str().unwrap().to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Read the FS UUID (parse blkid output)
|
||||
///
|
||||
/// Note: Calling blkid is more reliable than using the udev ID_FS_UUID property.
|
||||
pub fn get_fs_uuid(disk: &Disk) -> Result<String, Error> {
|
||||
|
||||
let disk_path = match disk.device_path() {
|
||||
Some(path) => path,
|
||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use anyhow::{bail, Error};
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
use anyhow::{bail, Error};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use proxmox_schema::api;
|
||||
|
||||
#[api()]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// SMART status
|
||||
pub enum SmartStatus {
|
||||
/// Smart tests passed - everything is OK
|
||||
|
@ -29,23 +29,22 @@ pub struct SmartAttribute {
|
|||
value: String,
|
||||
// the rest of the values is available for ATA type
|
||||
/// ATA Attribute ID
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<u64>,
|
||||
/// ATA Flags
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
flags: Option<String>,
|
||||
/// ATA normalized value (0..100)
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
normalized: Option<f64>,
|
||||
/// ATA worst
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
worst: Option<f64>,
|
||||
/// ATA threshold
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
threshold: Option<f64>,
|
||||
}
|
||||
|
||||
|
||||
#[api(
|
||||
properties: {
|
||||
status: {
|
||||
|
@ -74,16 +73,14 @@ pub struct SmartData {
|
|||
}
|
||||
|
||||
/// Read smartctl data for a disk (/dev/XXX).
|
||||
pub fn get_smart_data(
|
||||
disk: &super::Disk,
|
||||
health_only: bool,
|
||||
) -> Result<SmartData, Error> {
|
||||
|
||||
pub fn get_smart_data(disk: &super::Disk, health_only: bool) -> Result<SmartData, Error> {
|
||||
const SMARTCTL_BIN_PATH: &str = "smartctl";
|
||||
|
||||
let mut command = std::process::Command::new(SMARTCTL_BIN_PATH);
|
||||
command.arg("-H");
|
||||
if !health_only { command.args(&["-A", "-j"]); }
|
||||
if !health_only {
|
||||
command.args(&["-A", "-j"]);
|
||||
}
|
||||
|
||||
let disk_path = match disk.device_path() {
|
||||
Some(path) => path,
|
||||
|
@ -91,9 +88,12 @@ pub fn get_smart_data(
|
|||
};
|
||||
command.arg(disk_path);
|
||||
|
||||
let output = proxmox_sys::command::run_command(command, Some(|exitcode|
|
||||
(exitcode & 0b0111) == 0 // only bits 0-2 are fatal errors
|
||||
))?;
|
||||
let output = proxmox_sys::command::run_command(
|
||||
command,
|
||||
Some(
|
||||
|exitcode| (exitcode & 0b0111) == 0, // only bits 0-2 are fatal errors
|
||||
),
|
||||
)?;
|
||||
|
||||
let output: serde_json::Value = output.parse()?;
|
||||
|
||||
|
@ -196,8 +196,11 @@ pub fn get_smart_data(
|
|||
Some(false) => SmartStatus::Failed,
|
||||
};
|
||||
|
||||
|
||||
Ok(SmartData { status, wearout, attributes })
|
||||
Ok(SmartData {
|
||||
status,
|
||||
wearout,
|
||||
attributes,
|
||||
})
|
||||
}
|
||||
|
||||
static WEAROUT_FIELD_ORDER: &[&'static str] = &[
|
||||
|
@ -213,11 +216,10 @@ static WEAROUT_FIELD_ORDER: &[&'static str] = &[
|
|||
"Lifetime_Remaining",
|
||||
"Percent_Life_Remaining",
|
||||
"Percent_Lifetime_Used",
|
||||
"Perc_Rated_Life_Used"
|
||||
"Perc_Rated_Life_Used",
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
static ref WEAROUT_FIELD_NAMES: HashSet<&'static str> = {
|
||||
WEAROUT_FIELD_ORDER.iter().cloned().collect()
|
||||
};
|
||||
static ref WEAROUT_FIELD_NAMES: HashSet<&'static str> =
|
||||
WEAROUT_FIELD_ORDER.iter().cloned().collect();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
use std::collections::HashSet;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
|
@ -10,7 +10,7 @@ use proxmox_schema::const_regex;
|
|||
|
||||
use super::*;
|
||||
|
||||
lazy_static!{
|
||||
lazy_static! {
|
||||
static ref ZFS_UUIDS: HashSet<&'static str> = {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("6a898cc3-1dd2-11b2-99a6-080020736631"); // apple
|
||||
|
@ -29,14 +29,15 @@ fn get_pool_from_dataset(dataset: &str) -> &str {
|
|||
|
||||
/// Returns kernel IO-stats for zfs pools
|
||||
pub fn zfs_pool_stats(pool: &OsStr) -> Result<Option<BlockDevStat>, Error> {
|
||||
|
||||
let mut path = PathBuf::from("/proc/spl/kstat/zfs");
|
||||
path.push(pool);
|
||||
path.push("io");
|
||||
|
||||
let text = match proxmox_sys::fs::file_read_optional_string(&path)? {
|
||||
Some(text) => text,
|
||||
None => { return Ok(None); }
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let lines: Vec<&str> = text.lines().collect();
|
||||
|
@ -50,15 +51,16 @@ pub fn zfs_pool_stats(pool: &OsStr) -> Result<Option<BlockDevStat>, Error> {
|
|||
// Note: w -> wait (wtime -> wait time)
|
||||
// Note: r -> run (rtime -> run time)
|
||||
// All times are nanoseconds
|
||||
let stat: Vec<u64> = lines[2].split_ascii_whitespace().map(|s| {
|
||||
u64::from_str_radix(s, 10).unwrap_or(0)
|
||||
}).collect();
|
||||
let stat: Vec<u64> = lines[2]
|
||||
.split_ascii_whitespace()
|
||||
.map(|s| u64::from_str_radix(s, 10).unwrap_or(0))
|
||||
.collect();
|
||||
|
||||
let ticks = (stat[4] + stat[7])/1_000_000; // convert to milisec
|
||||
let ticks = (stat[4] + stat[7]) / 1_000_000; // convert to milisec
|
||||
|
||||
let stat = BlockDevStat {
|
||||
read_sectors: stat[0]>>9,
|
||||
write_sectors: stat[1]>>9,
|
||||
read_sectors: stat[0] >> 9,
|
||||
write_sectors: stat[1] >> 9,
|
||||
read_ios: stat[2],
|
||||
write_ios: stat[3],
|
||||
io_ticks: ticks,
|
||||
|
@ -70,11 +72,7 @@ pub fn zfs_pool_stats(pool: &OsStr) -> Result<Option<BlockDevStat>, Error> {
|
|||
/// Get set of devices used by zfs (or a specific zfs pool)
|
||||
///
|
||||
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
||||
pub fn zfs_devices(
|
||||
lsblk_info: &[LsblkInfo],
|
||||
pool: Option<String>,
|
||||
) -> Result<HashSet<u64>, Error> {
|
||||
|
||||
pub fn zfs_devices(lsblk_info: &[LsblkInfo], pool: Option<String>) -> Result<HashSet<u64>, Error> {
|
||||
let list = zpool_list(pool, true)?;
|
||||
|
||||
let mut device_set = HashSet::new();
|
||||
|
@ -162,7 +160,6 @@ fn parse_objset_stat(pool: &str, objset_id: &str) -> Result<(String, BlockDevSta
|
|||
Ok((dataset_name, stat))
|
||||
}
|
||||
|
||||
|
||||
fn get_mapping(dataset: &str) -> Option<(String, String)> {
|
||||
ZFS_DATASET_OBJSET_MAP
|
||||
.lock()
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
use anyhow::{bail, Error};
|
||||
|
||||
use pbs_tools::nom::{
|
||||
multispace0, multispace1, notspace1, IResult,
|
||||
};
|
||||
use pbs_tools::nom::{multispace0, multispace1, notspace1, IResult};
|
||||
|
||||
use nom::{
|
||||
bytes::complete::{take_while1, take_till, take_till1},
|
||||
combinator::{map_res, all_consuming, recognize, opt},
|
||||
bytes::complete::{take_till, take_till1, take_while1},
|
||||
character::complete::{char, digit1, line_ending},
|
||||
combinator::{all_consuming, map_res, opt, recognize},
|
||||
multi::many0,
|
||||
sequence::{preceded, tuple},
|
||||
character::complete::{digit1, char, line_ending},
|
||||
multi::{many0},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -29,7 +27,6 @@ pub struct ZFSPoolInfo {
|
|||
pub devices: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
fn parse_optional_u64(i: &str) -> IResult<&str, Option<u64>> {
|
||||
if let Some(rest) = i.strip_prefix('-') {
|
||||
Ok((rest, None))
|
||||
|
@ -61,44 +58,49 @@ fn parse_pool_device(i: &str) -> IResult<&str, String> {
|
|||
fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||
// name, size, allocated, free, checkpoint, expandsize, fragmentation, capacity, dedupratio, health, altroot.
|
||||
|
||||
let (i, (text, size, alloc, free, _, _,
|
||||
frag, _, dedup, health,
|
||||
_altroot, _eol)) = tuple((
|
||||
let (i, (text, size, alloc, free, _, _, frag, _, dedup, health, _altroot, _eol)) = tuple((
|
||||
take_while1(|c| char::is_alphanumeric(c) || c == '-' || c == ':' || c == '_' || c == '.'), // name
|
||||
preceded(multispace1, parse_optional_u64), // size
|
||||
preceded(multispace1, parse_optional_u64), // allocated
|
||||
preceded(multispace1, parse_optional_u64), // free
|
||||
preceded(multispace1, notspace1), // checkpoint
|
||||
preceded(multispace1, notspace1), // expandsize
|
||||
preceded(multispace1, notspace1), // checkpoint
|
||||
preceded(multispace1, notspace1), // expandsize
|
||||
preceded(multispace1, parse_optional_u64), // fragmentation
|
||||
preceded(multispace1, notspace1), // capacity
|
||||
preceded(multispace1, notspace1), // capacity
|
||||
preceded(multispace1, parse_optional_f64), // dedup
|
||||
preceded(multispace1, notspace1), // health
|
||||
opt(preceded(multispace1, notspace1)), // optional altroot
|
||||
preceded(multispace1, notspace1), // health
|
||||
opt(preceded(multispace1, notspace1)), // optional altroot
|
||||
line_ending,
|
||||
))(i)?;
|
||||
|
||||
let status = if let (Some(size), Some(alloc), Some(free), Some(frag), Some(dedup)) = (size, alloc, free, frag, dedup) {
|
||||
let status = if let (Some(size), Some(alloc), Some(free), Some(frag), Some(dedup)) =
|
||||
(size, alloc, free, frag, dedup)
|
||||
{
|
||||
ZFSPoolInfo {
|
||||
name: text.into(),
|
||||
health: health.into(),
|
||||
usage: Some(ZFSPoolUsage { size, alloc, free, frag, dedup }),
|
||||
usage: Some(ZFSPoolUsage {
|
||||
size,
|
||||
alloc,
|
||||
free,
|
||||
frag,
|
||||
dedup,
|
||||
}),
|
||||
devices: Vec::new(),
|
||||
}
|
||||
} else {
|
||||
ZFSPoolInfo {
|
||||
name: text.into(),
|
||||
health: health.into(),
|
||||
usage: None,
|
||||
devices: Vec::new(),
|
||||
}
|
||||
ZFSPoolInfo {
|
||||
name: text.into(),
|
||||
health: health.into(),
|
||||
usage: None,
|
||||
devices: Vec::new(),
|
||||
}
|
||||
};
|
||||
|
||||
Ok((i, status))
|
||||
}
|
||||
|
||||
fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||
|
||||
let (i, mut stat) = parse_zpool_list_header(i)?;
|
||||
let (i, devices) = many0(parse_pool_device)(i)?;
|
||||
|
||||
|
@ -117,9 +119,11 @@ fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
|||
/// the zpool list output format is not really defined...
|
||||
fn parse_zpool_list(i: &str) -> Result<Vec<ZFSPoolInfo>, Error> {
|
||||
match all_consuming(many0(parse_zpool_list_item))(i) {
|
||||
Err(nom::Err::Error(err)) |
|
||||
Err(nom::Err::Failure(err)) => {
|
||||
bail!("unable to parse zfs list output - {}", nom::error::convert_error(i, err));
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
bail!(
|
||||
"unable to parse zfs list output - {}",
|
||||
nom::error::convert_error(i, err)
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("unable to parse zfs list output - {}", err);
|
||||
|
@ -133,7 +137,6 @@ fn parse_zpool_list(i: &str) -> Result<Vec<ZFSPoolInfo>, Error> {
|
|||
/// Devices are only included when run with verbose flags
|
||||
/// set. Without, device lists are empty.
|
||||
pub fn zpool_list(pool: Option<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo>, Error> {
|
||||
|
||||
// Note: zpools list verbose output can include entries for 'special', 'cache' and 'logs'
|
||||
// and maybe other things.
|
||||
|
||||
|
@ -143,9 +146,13 @@ pub fn zpool_list(pool: Option<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo
|
|||
// Note: We do not use -o to define output properties, because zpool command ignores
|
||||
// that completely for special vdevs and devices
|
||||
|
||||
if verbose { command.arg("-v"); }
|
||||
if verbose {
|
||||
command.arg("-v");
|
||||
}
|
||||
|
||||
if let Some(pool) = pool { command.arg(pool); }
|
||||
if let Some(pool) = pool {
|
||||
command.arg(pool);
|
||||
}
|
||||
|
||||
let output = proxmox_sys::command::run_command(command, None)?;
|
||||
|
||||
|
@ -154,7 +161,6 @@ pub fn zpool_list(pool: Option<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo
|
|||
|
||||
#[test]
|
||||
fn test_zfs_parse_list() -> Result<(), Error> {
|
||||
|
||||
let output = "";
|
||||
|
||||
let data = parse_zpool_list(output)?;
|
||||
|
@ -164,19 +170,18 @@ fn test_zfs_parse_list() -> Result<(), Error> {
|
|||
|
||||
let output = "btest 427349245952 405504 427348840448 - - 0 0 1.00 ONLINE -\n";
|
||||
let data = parse_zpool_list(output)?;
|
||||
let expect = vec![
|
||||
ZFSPoolInfo {
|
||||
name: "btest".to_string(),
|
||||
health: "ONLINE".to_string(),
|
||||
devices: Vec::new(),
|
||||
usage: Some(ZFSPoolUsage {
|
||||
size: 427349245952,
|
||||
alloc: 405504,
|
||||
free: 427348840448,
|
||||
dedup: 1.0,
|
||||
frag: 0,
|
||||
}),
|
||||
}];
|
||||
let expect = vec![ZFSPoolInfo {
|
||||
name: "btest".to_string(),
|
||||
health: "ONLINE".to_string(),
|
||||
devices: Vec::new(),
|
||||
usage: Some(ZFSPoolUsage {
|
||||
size: 427349245952,
|
||||
alloc: 405504,
|
||||
free: 427348840448,
|
||||
dedup: 1.0,
|
||||
frag: 0,
|
||||
}),
|
||||
}];
|
||||
|
||||
assert_eq!(data, expect);
|
||||
|
||||
|
@ -195,10 +200,12 @@ logs
|
|||
ZFSPoolInfo {
|
||||
name: String::from("rpool"),
|
||||
health: String::from("ONLINE"),
|
||||
devices: vec![String::from("/dev/disk/by-id/ata-Crucial_CT500MX200SSD1_154210EB4078-part3")],
|
||||
devices: vec![String::from(
|
||||
"/dev/disk/by-id/ata-Crucial_CT500MX200SSD1_154210EB4078-part3",
|
||||
)],
|
||||
usage: Some(ZFSPoolUsage {
|
||||
size: 535260299264,
|
||||
alloc:402852388864 ,
|
||||
alloc: 402852388864,
|
||||
free: 132407910400,
|
||||
dedup: 1.0,
|
||||
frag: 22,
|
||||
|
@ -249,7 +256,7 @@ logs - - - - - - - - -
|
|||
String::from("/dev/sda2"),
|
||||
String::from("/dev/sda3"),
|
||||
String::from("/dev/sda4"),
|
||||
]
|
||||
],
|
||||
},
|
||||
ZFSPoolInfo {
|
||||
name: String::from("logs"),
|
||||
|
@ -268,22 +275,18 @@ b.test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE -
|
|||
";
|
||||
|
||||
let data = parse_zpool_list(output)?;
|
||||
let expect = vec![
|
||||
ZFSPoolInfo {
|
||||
name: String::from("b.test"),
|
||||
health: String::from("ONLINE"),
|
||||
usage: Some(ZFSPoolUsage {
|
||||
size: 427349245952,
|
||||
alloc: 761856,
|
||||
free: 427348484096,
|
||||
dedup: 1.0,
|
||||
frag: 0,
|
||||
}),
|
||||
devices: vec![
|
||||
String::from("/dev/sda1"),
|
||||
]
|
||||
},
|
||||
];
|
||||
let expect = vec![ZFSPoolInfo {
|
||||
name: String::from("b.test"),
|
||||
health: String::from("ONLINE"),
|
||||
usage: Some(ZFSPoolUsage {
|
||||
size: 427349245952,
|
||||
alloc: 761856,
|
||||
free: 427348484096,
|
||||
dedup: 1.0,
|
||||
frag: 0,
|
||||
}),
|
||||
devices: vec![String::from("/dev/sda1")],
|
||||
}];
|
||||
|
||||
assert_eq!(data, expect);
|
||||
|
||||
|
|
|
@ -5,32 +5,31 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{Map, Value};
|
||||
|
||||
use pbs_tools::nom::{
|
||||
parse_complete, parse_error, parse_failure,
|
||||
multispace0, multispace1, notspace1, parse_u64, IResult,
|
||||
multispace0, multispace1, notspace1, parse_complete, parse_error, parse_failure, parse_u64,
|
||||
IResult,
|
||||
};
|
||||
|
||||
use nom::{
|
||||
bytes::complete::{tag, take_while, take_while1},
|
||||
combinator::{opt},
|
||||
sequence::{preceded},
|
||||
character::complete::{line_ending},
|
||||
multi::{many0,many1},
|
||||
character::complete::line_ending,
|
||||
combinator::opt,
|
||||
multi::{many0, many1},
|
||||
sequence::preceded,
|
||||
};
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ZFSPoolVDevState {
|
||||
pub name: String,
|
||||
pub lvl: u64,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<String>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub read: Option<u64>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub write: Option<u64>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cksum: Option<u64>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub msg: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -39,7 +38,6 @@ fn expand_tab_length(input: &str) -> usize {
|
|||
}
|
||||
|
||||
fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
||||
|
||||
let (n, indent) = multispace0(i)?;
|
||||
|
||||
let indent_len = expand_tab_length(indent);
|
||||
|
@ -49,11 +47,12 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
|||
}
|
||||
let i = n;
|
||||
|
||||
let indent_level = (indent_len as u64)/2;
|
||||
let indent_level = (indent_len as u64) / 2;
|
||||
|
||||
let (i, vdev_name) = notspace1(i)?;
|
||||
let (i, vdev_name) = notspace1(i)?;
|
||||
|
||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // special device
|
||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) {
|
||||
// special device
|
||||
let vdev = ZFSPoolVDevState {
|
||||
name: vdev_name.to_string(),
|
||||
lvl: indent_level,
|
||||
|
@ -67,7 +66,8 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
|||
}
|
||||
|
||||
let (i, state) = preceded(multispace1, notspace1)(i)?;
|
||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // spares
|
||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) {
|
||||
// spares
|
||||
let vdev = ZFSPoolVDevState {
|
||||
name: vdev_name.to_string(),
|
||||
lvl: indent_level,
|
||||
|
@ -100,7 +100,6 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
|||
}
|
||||
|
||||
fn parse_zpool_status_tree(i: &str) -> IResult<&str, Vec<ZFSPoolVDevState>> {
|
||||
|
||||
// skip header
|
||||
let (i, _) = tag("NAME")(i)?;
|
||||
let (i, _) = multispace1(i)?;
|
||||
|
@ -130,8 +129,10 @@ fn space_indented_line(indent: usize) -> impl Fn(&str) -> IResult<&str, &str> {
|
|||
break;
|
||||
}
|
||||
n = &n[1..];
|
||||
if len >= indent { break; }
|
||||
};
|
||||
if len >= indent {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if len != indent {
|
||||
return Err(parse_error(i, "not correctly indented"));
|
||||
}
|
||||
|
@ -144,7 +145,9 @@ fn parse_zpool_status_field(i: &str) -> IResult<&str, (String, String)> {
|
|||
let (i, prefix) = take_while1(|c| c != ':')(i)?;
|
||||
let (i, _) = tag(":")(i)?;
|
||||
let (i, mut value) = take_while(|c| c != '\n')(i)?;
|
||||
if value.starts_with(' ') { value = &value[1..]; }
|
||||
if value.starts_with(' ') {
|
||||
value = &value[1..];
|
||||
}
|
||||
|
||||
let (mut i, _) = line_ending(i)?;
|
||||
|
||||
|
@ -169,7 +172,9 @@ fn parse_zpool_status_field(i: &str) -> IResult<&str, (String, String)> {
|
|||
if let Some(cont) = cont {
|
||||
let (n, _) = line_ending(n)?;
|
||||
i = n;
|
||||
if !value.is_empty() { value.push('\n'); }
|
||||
if !value.is_empty() {
|
||||
value.push('\n');
|
||||
}
|
||||
value.push_str(cont);
|
||||
} else {
|
||||
if field == "config" {
|
||||
|
@ -233,7 +238,9 @@ where
|
|||
while vdev_level < cur.level {
|
||||
cur.children_of_parent.push(Value::Object(cur.node));
|
||||
let mut parent = stack.pop().unwrap();
|
||||
parent.node.insert("children".to_string(), Value::Array(cur.children_of_parent));
|
||||
parent
|
||||
.node
|
||||
.insert("children".to_string(), Value::Array(cur.children_of_parent));
|
||||
parent.node.insert("leaf".to_string(), Value::Bool(false));
|
||||
cur = parent;
|
||||
|
||||
|
@ -252,16 +259,17 @@ where
|
|||
});
|
||||
} else {
|
||||
// same indentation level, add to children of the previous level:
|
||||
cur.children_of_parent.push(Value::Object(
|
||||
replace(&mut cur.node, node),
|
||||
));
|
||||
cur.children_of_parent
|
||||
.push(Value::Object(replace(&mut cur.node, node)));
|
||||
}
|
||||
}
|
||||
|
||||
while !stack.is_empty() {
|
||||
cur.children_of_parent.push(Value::Object(cur.node));
|
||||
let mut parent = stack.pop().unwrap();
|
||||
parent.node.insert("children".to_string(), Value::Array(cur.children_of_parent));
|
||||
parent
|
||||
.node
|
||||
.insert("children".to_string(), Value::Array(cur.children_of_parent));
|
||||
parent.node.insert("leaf".to_string(), Value::Bool(false));
|
||||
cur = parent;
|
||||
}
|
||||
|
@ -281,6 +289,7 @@ fn test_vdev_list_to_tree() {
|
|||
msg: None,
|
||||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
let input = vec![
|
||||
//ZFSPoolVDevState { name: "root".to_string(), lvl: 0, ..DEFAULT },
|
||||
ZFSPoolVDevState { name: "vdev1".to_string(), lvl: 1, ..DEFAULT },
|
||||
|
@ -351,16 +360,14 @@ fn test_vdev_list_to_tree() {
|
|||
}],\
|
||||
\"leaf\":false\
|
||||
}";
|
||||
let expected: Value = serde_json::from_str(EXPECTED)
|
||||
.expect("failed to parse expected json value");
|
||||
let expected: Value =
|
||||
serde_json::from_str(EXPECTED).expect("failed to parse expected json value");
|
||||
|
||||
let tree = vdev_list_to_tree(&input)
|
||||
.expect("failed to turn valid vdev list into a tree");
|
||||
let tree = vdev_list_to_tree(&input).expect("failed to turn valid vdev list into a tree");
|
||||
assert_eq!(tree, expected);
|
||||
}
|
||||
|
||||
pub fn zpool_status(pool: &str) -> Result<Vec<(String, String)>, Error> {
|
||||
|
||||
let mut command = std::process::Command::new("zpool");
|
||||
command.args(&["status", "-p", "-P", pool]);
|
||||
|
||||
|
@ -390,7 +397,6 @@ fn test_parse(output: &str) -> Result<(), Error> {
|
|||
|
||||
#[test]
|
||||
fn test_zpool_status_parser() -> Result<(), Error> {
|
||||
|
||||
let output = r###" pool: tank
|
||||
state: DEGRADED
|
||||
status: One or more devices could not be opened. Sufficient replicas exist for
|
||||
|
@ -418,7 +424,6 @@ errors: No known data errors
|
|||
|
||||
#[test]
|
||||
fn test_zpool_status_parser2() -> Result<(), Error> {
|
||||
|
||||
// Note: this input create TABS
|
||||
let output = r###" pool: btest
|
||||
state: ONLINE
|
||||
|
@ -443,7 +448,6 @@ errors: No known data errors
|
|||
|
||||
#[test]
|
||||
fn test_zpool_status_parser3() -> Result<(), Error> {
|
||||
|
||||
let output = r###" pool: bt-est
|
||||
state: ONLINE
|
||||
scan: none requested
|
||||
|
@ -468,7 +472,6 @@ errors: No known data errors
|
|||
|
||||
#[test]
|
||||
fn test_zpool_status_parser_spares() -> Result<(), Error> {
|
||||
|
||||
let output = r###" pool: tank
|
||||
state: ONLINE
|
||||
scan: none requested
|
||||
|
|
Loading…
Reference in New Issue