use std::{collections::hash_set::HashSet, fmt, path::PathBuf, str::FromStr};
use crate::{
core::{DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM},
lineardev::{LinearDev, LinearDevTargetParams},
result::{DmError, DmResult, ErrorEnum},
shared::{
device_create, device_exists, device_match, get_status, get_status_line_fields,
make_unexpected_value_error, parse_device, parse_value, DmDevice, TargetLine, TargetParams,
TargetTable, TargetTypeBuf,
},
units::{DataBlocks, MetaBlocks, Sectors},
};
#[cfg(test)]
use std::path::Path;
#[cfg(test)]
use crate::core::devnode_to_devno;
const THINPOOL_TARGET_NAME: &str = "thin-pool";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ThinPoolTargetParams {
pub metadata_dev: Device,
pub data_dev: Device,
pub data_block_size: Sectors,
pub low_water_mark: DataBlocks,
pub feature_args: HashSet<String>,
}
impl ThinPoolTargetParams {
pub fn new(
metadata_dev: Device,
data_dev: Device,
data_block_size: Sectors,
low_water_mark: DataBlocks,
feature_args: Vec<String>,
) -> ThinPoolTargetParams {
ThinPoolTargetParams {
metadata_dev,
data_dev,
data_block_size,
low_water_mark,
feature_args: feature_args.into_iter().collect::<HashSet<_>>(),
}
}
}
impl fmt::Display for ThinPoolTargetParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", THINPOOL_TARGET_NAME, self.param_str())
}
}
impl FromStr for ThinPoolTargetParams {
type Err = DmError;
fn from_str(s: &str) -> DmResult<ThinPoolTargetParams> {
let vals = s.split(' ').collect::<Vec<_>>();
if vals.len() < 5 {
let err_msg = format!(
"expected at least 5 values in params string \"{}\", found {}",
s,
vals.len()
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
if vals[0] != THINPOOL_TARGET_NAME {
let err_msg = format!(
"Expected a thin-pool target entry but found target type {}",
vals[0]
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
let metadata_dev = parse_device(vals[1], "metadata device for thinpool target")?;
let data_dev = parse_device(vals[2], "data device for thinpool target")?;
let data_block_size = Sectors(parse_value(vals[3], "data block size")?);
let low_water_mark = DataBlocks(parse_value(vals[4], "low water mark")?);
let feature_args = if vals.len() == 5 {
vec![]
} else {
vals[6..6 + parse_value::<usize>(vals[5], "number of feature args")?]
.iter()
.map(|x| (*x).to_string())
.collect()
};
Ok(ThinPoolTargetParams::new(
metadata_dev,
data_dev,
data_block_size,
low_water_mark,
feature_args,
))
}
}
impl TargetParams for ThinPoolTargetParams {
fn param_str(&self) -> String {
let feature_args = if self.feature_args.is_empty() {
"0".to_owned()
} else {
format!(
"{} {}",
self.feature_args.len(),
self.feature_args
.iter()
.cloned()
.collect::<Vec<_>>()
.join(" ")
)
};
format!(
"{} {} {} {} {}",
self.metadata_dev,
self.data_dev,
*self.data_block_size,
*self.low_water_mark,
feature_args
)
}
fn target_type(&self) -> TargetTypeBuf {
TargetTypeBuf::new(THINPOOL_TARGET_NAME.into()).expect("THINPOOL_TARGET_NAME is valid")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ThinPoolDevTargetTable {
pub table: TargetLine<ThinPoolTargetParams>,
}
impl ThinPoolDevTargetTable {
pub fn new(
start: Sectors,
length: Sectors,
params: ThinPoolTargetParams,
) -> ThinPoolDevTargetTable {
ThinPoolDevTargetTable {
table: TargetLine::new(start, length, params),
}
}
}
impl fmt::Display for ThinPoolDevTargetTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let table = &self.table;
writeln!(f, "{} {} {}", *table.start, *table.length, table.params)
}
}
impl TargetTable for ThinPoolDevTargetTable {
fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<ThinPoolDevTargetTable> {
if table.len() != 1 {
let err_msg = format!(
"ThinPoolDev table should have exactly one line, has {} lines",
table.len()
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
let line = table.first().expect("table.len() == 1");
Ok(ThinPoolDevTargetTable::new(
Sectors(line.0),
Sectors(line.1),
format!("{} {}", line.2, line.3).parse::<ThinPoolTargetParams>()?,
))
}
fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
to_raw_table_unique!(self)
}
}
#[derive(Debug)]
pub struct ThinPoolDev {
dev_info: Box<DeviceInfo>,
meta_dev: LinearDev,
data_dev: LinearDev,
table: ThinPoolDevTargetTable,
}
impl DmDevice<ThinPoolDevTargetTable> for ThinPoolDev {
fn device(&self) -> Device {
device!(self)
}
fn devnode(&self) -> PathBuf {
devnode!(self)
}
fn equivalent_tables(
left: &ThinPoolDevTargetTable,
right: &ThinPoolDevTargetTable,
) -> DmResult<bool> {
let left = &left.table;
let right = &right.table;
Ok(left.start == right.start
&& left.length == right.length
&& left.params.metadata_dev == right.params.metadata_dev
&& left.params.data_dev == right.params.data_dev
&& left.params.data_block_size == right.params.data_block_size)
}
fn name(&self) -> &DmName {
name!(self)
}
fn size(&self) -> Sectors {
self.data_dev.size()
}
fn table(&self) -> &ThinPoolDevTargetTable {
table!(self)
}
fn teardown(&mut self, dm: &DM) -> DmResult<()> {
dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?;
self.data_dev.teardown(dm)?;
self.meta_dev.teardown(dm)?;
Ok(())
}
fn uuid(&self) -> Option<&DmUuid> {
uuid!(self)
}
}
#[derive(Debug, Clone)]
pub struct ThinPoolUsage {
pub used_meta: MetaBlocks,
pub total_meta: MetaBlocks,
pub used_data: DataBlocks,
pub total_data: DataBlocks,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ThinPoolStatusSummary {
Good,
ReadOnly,
OutOfSpace,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ThinPoolNoSpacePolicy {
Error,
Queue,
}
#[derive(Debug, Clone)]
pub struct ThinPoolWorkingStatus {
pub transaction_id: u64,
pub usage: ThinPoolUsage,
pub held_metadata_root: Option<MetaBlocks>,
pub discard_passdown: bool,
pub no_space_policy: ThinPoolNoSpacePolicy,
pub summary: ThinPoolStatusSummary,
pub needs_check: bool,
pub meta_low_water: Option<u64>,
}
impl ThinPoolWorkingStatus {
#[allow(clippy::too_many_arguments)]
pub fn new(
transaction_id: u64,
usage: ThinPoolUsage,
held_metadata_root: Option<MetaBlocks>,
discard_passdown: bool,
no_space_policy: ThinPoolNoSpacePolicy,
summary: ThinPoolStatusSummary,
needs_check: bool,
meta_low_water: Option<u64>,
) -> ThinPoolWorkingStatus {
ThinPoolWorkingStatus {
transaction_id,
usage,
held_metadata_root,
discard_passdown,
no_space_policy,
summary,
needs_check,
meta_low_water,
}
}
}
#[derive(Debug, Clone)]
pub enum ThinPoolStatus {
Working(Box<ThinPoolWorkingStatus>),
Error,
Fail,
}
impl FromStr for ThinPoolStatus {
type Err = DmError;
fn from_str(status_line: &str) -> DmResult<ThinPoolStatus> {
if status_line.starts_with("Error") {
return Ok(ThinPoolStatus::Error);
}
if status_line.starts_with("Fail") {
return Ok(ThinPoolStatus::Fail);
}
let status_vals = get_status_line_fields(status_line, 8)?;
let transaction_id = parse_value(status_vals[0], "transaction id")?;
let usage = {
let meta_vals = status_vals[1].split('/').collect::<Vec<_>>();
let data_vals = status_vals[2].split('/').collect::<Vec<_>>();
ThinPoolUsage {
used_meta: MetaBlocks(parse_value(meta_vals[0], "used meta")?),
total_meta: MetaBlocks(parse_value(meta_vals[1], "total meta")?),
used_data: DataBlocks(parse_value(data_vals[0], "used data")?),
total_data: DataBlocks(parse_value(data_vals[1], "total data")?),
}
};
let held_metadata_root = match status_vals[3] {
"-" => None,
val => Some(MetaBlocks(parse_value(val, "held metadata root")?)),
};
let summary = match status_vals[4] {
"rw" => ThinPoolStatusSummary::Good,
"ro" => ThinPoolStatusSummary::ReadOnly,
"out_of_data_space" => ThinPoolStatusSummary::OutOfSpace,
val => {
return Err(make_unexpected_value_error(5, val, "summary"));
}
};
let discard_passdown = match status_vals[5] {
"discard_passdown" => true,
"no_discard_passdown" => false,
val => {
return Err(make_unexpected_value_error(6, val, "discard passdown"));
}
};
let no_space_policy = match status_vals[6] {
"error_if_no_space" => ThinPoolNoSpacePolicy::Error,
"queue_if_no_space" => ThinPoolNoSpacePolicy::Queue,
val => {
return Err(make_unexpected_value_error(7, val, "no space policy"));
}
};
let needs_check = match status_vals[7] {
"-" => false,
"needs_check" => true,
val => {
return Err(make_unexpected_value_error(8, val, "needs checK"));
}
};
let meta_low_water = status_vals
.get(8)
.and_then(|v| parse_value(v, "meta low water").ok());
Ok(ThinPoolStatus::Working(Box::new(
ThinPoolWorkingStatus::new(
transaction_id,
usage,
held_metadata_root,
discard_passdown,
no_space_policy,
summary,
needs_check,
meta_low_water,
),
)))
}
}
impl ThinPoolDev {
#[allow(clippy::too_many_arguments)]
pub fn new(
dm: &DM,
name: &DmName,
uuid: Option<&DmUuid>,
meta: LinearDev,
data: LinearDev,
data_block_size: Sectors,
low_water_mark: DataBlocks,
feature_args: Vec<String>,
) -> DmResult<ThinPoolDev> {
if device_exists(dm, name)? {
let err_msg = format!("thinpooldev {name} already exists");
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
let table =
ThinPoolDev::gen_table(&meta, &data, data_block_size, low_water_mark, feature_args);
let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?;
Ok(ThinPoolDev {
dev_info: Box::new(dev_info),
meta_dev: meta,
data_dev: data,
table,
})
}
pub fn meta_dev(&self) -> &LinearDev {
&self.meta_dev
}
pub fn data_dev(&self) -> &LinearDev {
&self.data_dev
}
pub fn data_block_size(&self) -> Sectors {
self.table.table.params.data_block_size
}
#[allow(clippy::too_many_arguments)]
pub fn setup(
dm: &DM,
name: &DmName,
uuid: Option<&DmUuid>,
meta: LinearDev,
data: LinearDev,
data_block_size: Sectors,
low_water_mark: DataBlocks,
feature_args: Vec<String>,
) -> DmResult<ThinPoolDev> {
let table =
ThinPoolDev::gen_table(&meta, &data, data_block_size, low_water_mark, feature_args);
let dev = if device_exists(dm, name)? {
let dev_info = dm.device_info(&DevId::Name(name))?;
let dev = ThinPoolDev {
dev_info: Box::new(dev_info),
meta_dev: meta,
data_dev: data,
table,
};
device_match(dm, &dev, uuid)?;
dev
} else {
let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?;
ThinPoolDev {
dev_info: Box::new(dev_info),
meta_dev: meta,
data_dev: data,
table,
}
};
Ok(dev)
}
fn gen_table(
meta: &LinearDev,
data: &LinearDev,
data_block_size: Sectors,
low_water_mark: DataBlocks,
feature_args: Vec<String>,
) -> ThinPoolDevTargetTable {
ThinPoolDevTargetTable::new(
Sectors::default(),
data.size(),
ThinPoolTargetParams::new(
meta.device(),
data.device(),
data_block_size,
low_water_mark,
feature_args,
),
)
}
pub fn set_low_water_mark(&mut self, dm: &DM, low_water_mark: DataBlocks) -> DmResult<()> {
let mut new_table = self.table.clone();
new_table.table.params.low_water_mark = low_water_mark;
self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
self.table_load(dm, &new_table, DmOptions::default())?;
self.table = new_table;
Ok(())
}
pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult<ThinPoolStatus> {
status!(self, dm, options)
}
pub fn set_meta_table(
&mut self,
dm: &DM,
table: Vec<TargetLine<LinearDevTargetParams>>,
) -> DmResult<()> {
self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
self.meta_dev.set_table(dm, table)?;
self.meta_dev.resume(dm)?;
self.table_load(dm, self.table(), DmOptions::default())?;
Ok(())
}
pub fn set_data_table(
&mut self,
dm: &DM,
table: Vec<TargetLine<LinearDevTargetParams>>,
) -> DmResult<()> {
self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
self.data_dev.set_table(dm, table)?;
self.data_dev.resume(dm)?;
let mut table = self.table.clone();
table.table.length = self.data_dev.size();
self.table_load(dm, &table, DmOptions::default())?;
self.table = table;
Ok(())
}
fn set_feature_arg(&mut self, feature_arg: &str, dm: &DM) -> DmResult<()> {
let mut table = self.table().clone();
if !table.table.params.feature_args.contains(feature_arg) {
table
.table
.params
.feature_args
.insert(feature_arg.to_string());
self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
self.table_load(dm, &table, DmOptions::default())?;
self.table = table;
self.resume(dm)?;
}
Ok(())
}
fn unset_feature_arg(&mut self, feature_arg: &str, dm: &DM) -> DmResult<()> {
let mut table = self.table().clone();
if table.table.params.feature_args.contains(feature_arg) {
table.table.params.feature_args.remove(feature_arg);
self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
self.table_load(dm, &table, DmOptions::default())?;
self.table = table;
self.resume(dm)?;
}
Ok(())
}
pub fn error_if_no_space(&mut self, dm: &DM) -> DmResult<()> {
self.set_feature_arg("error_if_no_space", dm)
}
pub fn queue_if_no_space(&mut self, dm: &DM) -> DmResult<()> {
self.unset_feature_arg("error_if_no_space", dm)
}
pub fn skip_block_zeroing(&mut self, dm: &DM) -> DmResult<()> {
self.set_feature_arg("skip_block_zeroing", dm)
}
pub fn require_block_zeroing(&mut self, dm: &DM) -> DmResult<()> {
self.unset_feature_arg("skip_block_zeroing", dm)
}
pub fn no_discard_passdown(&mut self, dm: &DM) -> DmResult<()> {
self.set_feature_arg("no_discard_passdown", dm)
}
pub fn discard_passdown(&mut self, dm: &DM) -> DmResult<()> {
self.unset_feature_arg("no_discard_passdown", dm)
}
}
#[cfg(test)]
use std::fs::OpenOptions;
#[cfg(test)]
use crate::{
consts::IEC,
lineardev::LinearTargetParams,
testing::{blkdev_size, test_name},
};
#[cfg(test)]
const MIN_DATA_BLOCK_SIZE: Sectors = Sectors(128); #[cfg(test)]
#[allow(dead_code)]
const MAX_DATA_BLOCK_SIZE: Sectors = Sectors(2 * IEC::Mi); #[cfg(test)]
const MIN_RECOMMENDED_METADATA_SIZE: Sectors = Sectors(4 * IEC::Ki); #[cfg(test)]
#[allow(dead_code)]
const MAX_METADATA_SIZE: MetaBlocks = MetaBlocks(255 * ((1 << 14) - 64));
#[cfg(test)]
pub fn minimal_thinpool(dm: &DM, path: &Path) -> ThinPoolDev {
let dev_size = blkdev_size(&OpenOptions::new().read(true).open(path).unwrap()).sectors();
let dev = Device::from(devnode_to_devno(path).unwrap().unwrap());
let meta_params = LinearTargetParams::new(dev, Sectors(0));
let meta_table = vec![TargetLine::new(
Sectors(0),
MIN_RECOMMENDED_METADATA_SIZE,
LinearDevTargetParams::Linear(meta_params),
)];
let meta = LinearDev::setup(
dm,
&test_name("meta").expect("valid format"),
None,
meta_table,
)
.unwrap();
let data_params = LinearTargetParams::new(dev, MIN_RECOMMENDED_METADATA_SIZE);
let data_table = vec![TargetLine::new(
Sectors(0),
dev_size - MIN_RECOMMENDED_METADATA_SIZE,
LinearDevTargetParams::Linear(data_params),
)];
let data = LinearDev::setup(
dm,
&test_name("data").expect("valid format"),
None,
data_table,
)
.unwrap();
ThinPoolDev::new(
dm,
&test_name("pool").expect("valid format"),
None,
meta,
data,
MIN_DATA_BLOCK_SIZE,
DataBlocks(1),
vec![
"no_discard_passdown".to_owned(),
"skip_block_zeroing".to_owned(),
],
)
.unwrap()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::{
core::{errors::Error, DmFlags},
testing::{test_name, test_with_spec},
};
use super::*;
fn test_minimum_values(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status)
if status.summary == ThinPoolStatusSummary::Good =>
{
assert!(!status.discard_passdown);
assert_eq!(status.held_metadata_root, None);
let usage = &status.usage;
assert!(usage.used_meta > MetaBlocks(0));
assert_eq!(usage.total_meta, tp.meta_dev().size().metablocks());
assert_eq!(usage.used_data, DataBlocks(0));
assert_eq!(
usage.total_data,
DataBlocks(tp.data_dev().size() / tp.data_block_size())
);
}
status => panic!("unexpected thinpool status: {status:?}"),
}
let table = ThinPoolDev::read_kernel_table(&dm, &DevId::Name(tp.name()))
.unwrap()
.table;
let params = &table.params;
assert_eq!(params.metadata_dev, tp.meta_dev().device());
assert_eq!(params.data_dev, tp.data_dev().device());
tp.teardown(&dm).unwrap();
}
#[test]
fn loop_test_basic() {
test_with_spec(1, test_minimum_values);
}
fn test_low_data_block_size(paths: &[&Path]) {
assert!(!paths.is_empty());
let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
let dm = DM::new().unwrap();
let meta_name = test_name("meta").expect("valid format");
let meta_params = LinearTargetParams::new(dev, Sectors(0));
let meta_table = vec![TargetLine::new(
Sectors(0),
MIN_RECOMMENDED_METADATA_SIZE,
LinearDevTargetParams::Linear(meta_params),
)];
let meta = LinearDev::setup(&dm, &meta_name, None, meta_table).unwrap();
let data_name = test_name("data").expect("valid format");
let data_params = LinearTargetParams::new(dev, MIN_RECOMMENDED_METADATA_SIZE);
let data_table = vec![TargetLine::new(
Sectors(0),
512u64 * MIN_DATA_BLOCK_SIZE,
LinearDevTargetParams::Linear(data_params),
)];
let data = LinearDev::setup(&dm, &data_name, None, data_table).unwrap();
assert_matches!(
ThinPoolDev::new(
&dm,
&test_name("pool").expect("valid format"),
None,
meta,
data,
MIN_DATA_BLOCK_SIZE / 2u64,
DataBlocks(1),
vec![
"no_discard_passdown".to_owned(),
"skip_block_zeroing".to_owned()
],
),
Err(DmError::Core(Error::Ioctl(_, _, _, _)))
);
dm.device_remove(&DevId::Name(&meta_name), DmOptions::default())
.unwrap();
dm.device_remove(&DevId::Name(&data_name), DmOptions::default())
.unwrap();
}
#[test]
fn loop_test_low_data_block_size() {
test_with_spec(1, test_low_data_block_size);
}
fn test_set_data(paths: &[&Path]) {
assert!(paths.len() > 1);
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let mut data_table = tp.data_dev.table().table.clone();
let data_size = tp.data_dev.size();
let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap());
let data_params = LinearTargetParams::new(dev2, Sectors(0));
data_table.push(TargetLine::new(
data_size,
data_size,
LinearDevTargetParams::Linear(data_params),
));
tp.set_data_table(&dm, data_table).unwrap();
tp.resume(&dm).unwrap();
match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => {
let usage = &status.usage;
assert_eq!(
*usage.total_data * tp.table().table.params.data_block_size,
2u8 * data_size
);
}
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("thin pool should not have failed"),
}
tp.teardown(&dm).unwrap();
}
#[test]
fn loop_test_set_data() {
test_with_spec(2, test_set_data);
}
fn test_set_meta(paths: &[&Path]) {
assert!(paths.len() > 1);
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let mut meta_table = tp.meta_dev.table().table.clone();
let meta_size = tp.meta_dev.size();
let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap());
let meta_params = LinearTargetParams::new(dev2, Sectors(0));
meta_table.push(TargetLine::new(
meta_size,
meta_size,
LinearDevTargetParams::Linear(meta_params),
));
tp.set_meta_table(&dm, meta_table).unwrap();
tp.resume(&dm).unwrap();
match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => {
let usage = &status.usage;
assert_eq!(usage.total_meta.sectors(), 2u8 * meta_size);
}
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("thin pool should not have failed"),
}
tp.teardown(&dm).unwrap();
}
#[test]
fn loop_test_set_meta() {
test_with_spec(2, test_set_meta);
}
fn test_suspend(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
tp.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
.unwrap();
tp.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
.unwrap();
tp.resume(&dm).unwrap();
tp.resume(&dm).unwrap();
tp.teardown(&dm).unwrap();
}
#[test]
fn loop_test_suspend() {
test_with_spec(1, test_suspend);
}
fn test_status_noflush(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let tp = minimal_thinpool(&dm, paths[0]);
tp.status(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
.unwrap();
}
#[test]
fn loop_test_status_noflush() {
test_with_spec(1, test_status_noflush);
}
#[test]
fn test_thinpool_target_params_zero() {
let result = "thin-pool 42:42 42:43 16 2 0"
.parse::<ThinPoolTargetParams>()
.unwrap();
assert_eq!(result.feature_args, HashSet::new());
}
#[test]
fn test_thinpool_target_params_none() {
let result = "thin-pool 42:42 42:43 16 2"
.parse::<ThinPoolTargetParams>()
.unwrap();
assert_eq!(result.feature_args, HashSet::new());
}
}