1use std::{collections::hash_set::HashSet, fmt, path::PathBuf, str::FromStr};
6
7use crate::{
8 core::{DevId, Device, DeviceInfo, DmName, DmOptions, DmUuid, DM},
9 lineardev::{LinearDev, LinearDevTargetParams},
10 result::{DmError, DmResult, ErrorEnum},
11 shared::{
12 device_create, device_exists, device_match, get_status, get_status_line_fields,
13 make_unexpected_value_error, parse_device, parse_value, DmDevice, TargetLine, TargetParams,
14 TargetTable, TargetTypeBuf,
15 },
16 units::{DataBlocks, MetaBlocks, Sectors},
17};
18
19#[cfg(test)]
20use std::path::Path;
21
22#[cfg(test)]
23use crate::core::devnode_to_devno;
24
25const THINPOOL_TARGET_NAME: &str = "thin-pool";
26
27#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct ThinPoolTargetParams {
30 pub metadata_dev: Device,
32 pub data_dev: Device,
34 pub data_block_size: Sectors,
36 pub low_water_mark: DataBlocks,
38 pub feature_args: HashSet<String>,
40}
41
42impl ThinPoolTargetParams {
43 pub fn new(
45 metadata_dev: Device,
46 data_dev: Device,
47 data_block_size: Sectors,
48 low_water_mark: DataBlocks,
49 feature_args: Vec<String>,
50 ) -> ThinPoolTargetParams {
51 ThinPoolTargetParams {
52 metadata_dev,
53 data_dev,
54 data_block_size,
55 low_water_mark,
56 feature_args: feature_args.into_iter().collect::<HashSet<_>>(),
57 }
58 }
59}
60
61impl fmt::Display for ThinPoolTargetParams {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{} {}", THINPOOL_TARGET_NAME, self.param_str())
64 }
65}
66
67impl FromStr for ThinPoolTargetParams {
68 type Err = DmError;
69
70 fn from_str(s: &str) -> DmResult<ThinPoolTargetParams> {
71 let vals = s.split(' ').collect::<Vec<_>>();
72
73 if vals.len() < 5 {
74 let err_msg = format!(
75 "expected at least 5 values in params string \"{}\", found {}",
76 s,
77 vals.len()
78 );
79 return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
80 }
81
82 if vals[0] != THINPOOL_TARGET_NAME {
83 let err_msg = format!(
84 "Expected a thin-pool target entry but found target type {}",
85 vals[0]
86 );
87 return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
88 }
89
90 let metadata_dev = parse_device(vals[1], "metadata device for thinpool target")?;
91 let data_dev = parse_device(vals[2], "data device for thinpool target")?;
92
93 let data_block_size = Sectors(parse_value(vals[3], "data block size")?);
94 let low_water_mark = DataBlocks(parse_value(vals[4], "low water mark")?);
95
96 let feature_args = if vals.len() == 5 {
97 vec![]
98 } else {
99 vals[6..6 + parse_value::<usize>(vals[5], "number of feature args")?]
100 .iter()
101 .map(|x| (*x).to_string())
102 .collect()
103 };
104
105 Ok(ThinPoolTargetParams::new(
106 metadata_dev,
107 data_dev,
108 data_block_size,
109 low_water_mark,
110 feature_args,
111 ))
112 }
113}
114
115impl TargetParams for ThinPoolTargetParams {
116 fn param_str(&self) -> String {
117 let feature_args = if self.feature_args.is_empty() {
118 "0".to_owned()
119 } else {
120 format!(
121 "{} {}",
122 self.feature_args.len(),
123 self.feature_args
124 .iter()
125 .cloned()
126 .collect::<Vec<_>>()
127 .join(" ")
128 )
129 };
130
131 format!(
132 "{} {} {} {} {}",
133 self.metadata_dev,
134 self.data_dev,
135 *self.data_block_size,
136 *self.low_water_mark,
137 feature_args
138 )
139 }
140
141 fn target_type(&self) -> TargetTypeBuf {
142 TargetTypeBuf::new(THINPOOL_TARGET_NAME.into()).expect("THINPOOL_TARGET_NAME is valid")
143 }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct ThinPoolDevTargetTable {
149 pub table: TargetLine<ThinPoolTargetParams>,
151}
152
153impl ThinPoolDevTargetTable {
154 pub fn new(
156 start: Sectors,
157 length: Sectors,
158 params: ThinPoolTargetParams,
159 ) -> ThinPoolDevTargetTable {
160 ThinPoolDevTargetTable {
161 table: TargetLine::new(start, length, params),
162 }
163 }
164}
165
166impl fmt::Display for ThinPoolDevTargetTable {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 let table = &self.table;
169 writeln!(f, "{} {} {}", *table.start, *table.length, table.params)
170 }
171}
172
173impl TargetTable for ThinPoolDevTargetTable {
174 fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<ThinPoolDevTargetTable> {
175 if table.len() != 1 {
176 let err_msg = format!(
177 "ThinPoolDev table should have exactly one line, has {} lines",
178 table.len()
179 );
180 return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
181 }
182 let line = table.first().expect("table.len() == 1");
183 Ok(ThinPoolDevTargetTable::new(
184 Sectors(line.0),
185 Sectors(line.1),
186 format!("{} {}", line.2, line.3).parse::<ThinPoolTargetParams>()?,
187 ))
188 }
189
190 fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
191 to_raw_table_unique!(self)
192 }
193}
194
195#[derive(Debug)]
197pub struct ThinPoolDev {
198 dev_info: Box<DeviceInfo>,
199 meta_dev: LinearDev,
200 data_dev: LinearDev,
201 table: ThinPoolDevTargetTable,
202}
203
204impl DmDevice<ThinPoolDevTargetTable> for ThinPoolDev {
205 fn device(&self) -> Device {
206 device!(self)
207 }
208
209 fn devnode(&self) -> PathBuf {
210 devnode!(self)
211 }
212
213 fn equivalent_tables(
218 left: &ThinPoolDevTargetTable,
219 right: &ThinPoolDevTargetTable,
220 ) -> DmResult<bool> {
221 let left = &left.table;
222 let right = &right.table;
223
224 Ok(left.start == right.start
225 && left.length == right.length
226 && left.params.metadata_dev == right.params.metadata_dev
227 && left.params.data_dev == right.params.data_dev
228 && left.params.data_block_size == right.params.data_block_size)
229 }
230
231 fn name(&self) -> &DmName {
232 name!(self)
233 }
234
235 fn size(&self) -> Sectors {
236 self.data_dev.size()
237 }
238
239 fn table(&self) -> &ThinPoolDevTargetTable {
240 table!(self)
241 }
242
243 fn teardown(&mut self, dm: &DM) -> DmResult<()> {
244 dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?;
245 self.data_dev.teardown(dm)?;
246 self.meta_dev.teardown(dm)?;
247 Ok(())
248 }
249
250 fn uuid(&self) -> Option<&DmUuid> {
251 uuid!(self)
252 }
253}
254
255#[derive(Debug, Clone)]
256pub struct ThinPoolUsage {
259 pub used_meta: MetaBlocks,
261 pub total_meta: MetaBlocks,
263 pub used_data: DataBlocks,
265 pub total_data: DataBlocks,
267}
268
269#[derive(Clone, Copy, Debug, Eq, PartialEq)]
270pub enum ThinPoolStatusSummary {
273 Good,
275 ReadOnly,
277 OutOfSpace,
279}
280
281#[derive(Clone, Copy, Debug, Eq, PartialEq)]
283pub enum ThinPoolNoSpacePolicy {
284 Error,
286 Queue,
288}
289
290#[derive(Debug, Clone)]
292pub struct ThinPoolWorkingStatus {
293 pub transaction_id: u64,
295 pub usage: ThinPoolUsage,
297 pub held_metadata_root: Option<MetaBlocks>,
299 pub discard_passdown: bool,
301 pub no_space_policy: ThinPoolNoSpacePolicy,
303 pub summary: ThinPoolStatusSummary,
305 pub needs_check: bool,
307 pub meta_low_water: Option<u64>,
310}
311
312impl ThinPoolWorkingStatus {
313 #[allow(clippy::too_many_arguments)]
315 pub fn new(
316 transaction_id: u64,
317 usage: ThinPoolUsage,
318 held_metadata_root: Option<MetaBlocks>,
319 discard_passdown: bool,
320 no_space_policy: ThinPoolNoSpacePolicy,
321 summary: ThinPoolStatusSummary,
322 needs_check: bool,
323 meta_low_water: Option<u64>,
324 ) -> ThinPoolWorkingStatus {
325 ThinPoolWorkingStatus {
326 transaction_id,
327 usage,
328 held_metadata_root,
329 discard_passdown,
330 no_space_policy,
331 summary,
332 needs_check,
333 meta_low_water,
334 }
335 }
336}
337
338#[derive(Debug, Clone)]
339pub enum ThinPoolStatus {
341 Working(Box<ThinPoolWorkingStatus>),
343 Error,
345 Fail,
347}
348
349impl FromStr for ThinPoolStatus {
350 type Err = DmError;
351
352 fn from_str(status_line: &str) -> DmResult<ThinPoolStatus> {
353 if status_line.starts_with("Error") {
354 return Ok(ThinPoolStatus::Error);
355 }
356
357 if status_line.starts_with("Fail") {
358 return Ok(ThinPoolStatus::Fail);
359 }
360
361 let status_vals = get_status_line_fields(status_line, 8)?;
362
363 let transaction_id = parse_value(status_vals[0], "transaction id")?;
364
365 let usage = {
366 let meta_vals = status_vals[1].split('/').collect::<Vec<_>>();
367 let data_vals = status_vals[2].split('/').collect::<Vec<_>>();
368 ThinPoolUsage {
369 used_meta: MetaBlocks(parse_value(meta_vals[0], "used meta")?),
370 total_meta: MetaBlocks(parse_value(meta_vals[1], "total meta")?),
371 used_data: DataBlocks(parse_value(data_vals[0], "used data")?),
372 total_data: DataBlocks(parse_value(data_vals[1], "total data")?),
373 }
374 };
375
376 let held_metadata_root = match status_vals[3] {
377 "-" => None,
378 val => Some(MetaBlocks(parse_value(val, "held metadata root")?)),
379 };
380
381 let summary = match status_vals[4] {
382 "rw" => ThinPoolStatusSummary::Good,
383 "ro" => ThinPoolStatusSummary::ReadOnly,
384 "out_of_data_space" => ThinPoolStatusSummary::OutOfSpace,
385 val => {
386 return Err(make_unexpected_value_error(5, val, "summary"));
387 }
388 };
389
390 let discard_passdown = match status_vals[5] {
391 "discard_passdown" => true,
392 "no_discard_passdown" => false,
393 val => {
394 return Err(make_unexpected_value_error(6, val, "discard passdown"));
395 }
396 };
397
398 let no_space_policy = match status_vals[6] {
399 "error_if_no_space" => ThinPoolNoSpacePolicy::Error,
400 "queue_if_no_space" => ThinPoolNoSpacePolicy::Queue,
401 val => {
402 return Err(make_unexpected_value_error(7, val, "no space policy"));
403 }
404 };
405
406 let needs_check = match status_vals[7] {
407 "-" => false,
408 "needs_check" => true,
409 val => {
410 return Err(make_unexpected_value_error(8, val, "needs check"));
411 }
412 };
413
414 let meta_low_water = status_vals
415 .get(8)
416 .and_then(|v| parse_value(v, "meta low water").ok());
417
418 Ok(ThinPoolStatus::Working(Box::new(
419 ThinPoolWorkingStatus::new(
420 transaction_id,
421 usage,
422 held_metadata_root,
423 discard_passdown,
424 no_space_policy,
425 summary,
426 needs_check,
427 meta_low_water,
428 ),
429 )))
430 }
431}
432
433impl ThinPoolDev {
438 #[allow(clippy::too_many_arguments)]
443 pub fn new(
444 dm: &DM,
445 name: &DmName,
446 uuid: Option<&DmUuid>,
447 meta: LinearDev,
448 data: LinearDev,
449 data_block_size: Sectors,
450 low_water_mark: DataBlocks,
451 feature_args: Vec<String>,
452 ) -> DmResult<ThinPoolDev> {
453 if device_exists(dm, name)? {
454 let err_msg = format!("thinpooldev {name} already exists");
455 return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
456 }
457
458 let table =
459 ThinPoolDev::gen_table(&meta, &data, data_block_size, low_water_mark, feature_args);
460 let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?;
461
462 Ok(ThinPoolDev {
463 dev_info: Box::new(dev_info),
464 meta_dev: meta,
465 data_dev: data,
466 table,
467 })
468 }
469
470 pub fn meta_dev(&self) -> &LinearDev {
472 &self.meta_dev
473 }
474
475 pub fn data_dev(&self) -> &LinearDev {
477 &self.data_dev
478 }
479
480 pub fn data_block_size(&self) -> Sectors {
482 self.table.table.params.data_block_size
483 }
484
485 #[allow(clippy::too_many_arguments)]
492 pub fn setup(
493 dm: &DM,
494 name: &DmName,
495 uuid: Option<&DmUuid>,
496 meta: LinearDev,
497 data: LinearDev,
498 data_block_size: Sectors,
499 low_water_mark: DataBlocks,
500 feature_args: Vec<String>,
501 ) -> DmResult<ThinPoolDev> {
502 let table =
503 ThinPoolDev::gen_table(&meta, &data, data_block_size, low_water_mark, feature_args);
504 let dev = if device_exists(dm, name)? {
505 let dev_info = dm.device_info(&DevId::Name(name))?;
506 let dev = ThinPoolDev {
507 dev_info: Box::new(dev_info),
508 meta_dev: meta,
509 data_dev: data,
510 table,
511 };
512 device_match(dm, &dev, uuid)?;
513 dev
514 } else {
515 let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?;
516 ThinPoolDev {
517 dev_info: Box::new(dev_info),
518 meta_dev: meta,
519 data_dev: data,
520 table,
521 }
522 };
523 Ok(dev)
524 }
525
526 fn gen_table(
537 meta: &LinearDev,
538 data: &LinearDev,
539 data_block_size: Sectors,
540 low_water_mark: DataBlocks,
541 feature_args: Vec<String>,
542 ) -> ThinPoolDevTargetTable {
543 ThinPoolDevTargetTable::new(
544 Sectors::default(),
545 data.size(),
546 ThinPoolTargetParams::new(
547 meta.device(),
548 data.device(),
549 data_block_size,
550 low_water_mark,
551 feature_args,
552 ),
553 )
554 }
555
556 pub fn set_low_water_mark(&mut self, dm: &DM, low_water_mark: DataBlocks) -> DmResult<()> {
559 let mut new_table = self.table.clone();
560 new_table.table.params.low_water_mark = low_water_mark;
561
562 self.table_load(dm, &new_table, DmOptions::default())?;
563
564 self.table = new_table;
565 Ok(())
566 }
567
568 pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult<ThinPoolStatus> {
571 status!(self, dm, options)
572 }
573
574 pub fn set_meta_table(
581 &mut self,
582 dm: &DM,
583 table: Vec<TargetLine<LinearDevTargetParams>>,
584 ) -> DmResult<()> {
585 self.meta_dev.set_table(dm, table)?;
586 self.meta_dev.resume(dm)?;
587
588 self.table_load(dm, self.table(), DmOptions::default())?;
591
592 Ok(())
593 }
594
595 pub fn set_data_table(
602 &mut self,
603 dm: &DM,
604 table: Vec<TargetLine<LinearDevTargetParams>>,
605 ) -> DmResult<()> {
606 self.data_dev.set_table(dm, table)?;
607 self.data_dev.resume(dm)?;
608
609 let mut table = self.table.clone();
610 table.table.length = self.data_dev.size();
611 self.table_load(dm, &table, DmOptions::default())?;
612
613 self.table = table;
614
615 Ok(())
616 }
617
618 fn set_feature_arg(&mut self, feature_arg: &str, dm: &DM) -> DmResult<()> {
619 let mut table = self.table().clone();
620 if !table.table.params.feature_args.contains(feature_arg) {
621 table
622 .table
623 .params
624 .feature_args
625 .insert(feature_arg.to_string());
626
627 self.table_load(dm, &table, DmOptions::default())?;
628 self.table = table;
629
630 self.resume(dm)?;
631 }
632
633 Ok(())
634 }
635
636 fn unset_feature_arg(&mut self, feature_arg: &str, dm: &DM) -> DmResult<()> {
637 let mut table = self.table().clone();
638 if table.table.params.feature_args.contains(feature_arg) {
639 table.table.params.feature_args.remove(feature_arg);
640
641 self.table_load(dm, &table, DmOptions::default())?;
642 self.table = table;
643
644 self.resume(dm)?;
645 }
646
647 Ok(())
648 }
649
650 pub fn error_if_no_space(&mut self, dm: &DM) -> DmResult<()> {
658 self.set_feature_arg("error_if_no_space", dm)
659 }
660
661 pub fn queue_if_no_space(&mut self, dm: &DM) -> DmResult<()> {
669 self.unset_feature_arg("error_if_no_space", dm)
670 }
671
672 pub fn skip_block_zeroing(&mut self, dm: &DM) -> DmResult<()> {
679 self.set_feature_arg("skip_block_zeroing", dm)
680 }
681
682 pub fn require_block_zeroing(&mut self, dm: &DM) -> DmResult<()> {
689 self.unset_feature_arg("skip_block_zeroing", dm)
690 }
691
692 pub fn no_discard_passdown(&mut self, dm: &DM) -> DmResult<()> {
699 self.set_feature_arg("no_discard_passdown", dm)
700 }
701
702 pub fn discard_passdown(&mut self, dm: &DM) -> DmResult<()> {
709 self.unset_feature_arg("no_discard_passdown", dm)
710 }
711}
712
713#[cfg(test)]
714use std::fs::OpenOptions;
715
716#[cfg(test)]
717use crate::{
718 consts::IEC,
719 lineardev::LinearTargetParams,
720 testing::{blkdev_size, test_name},
721};
722
723#[cfg(test)]
725const MIN_DATA_BLOCK_SIZE: Sectors = Sectors(128); #[cfg(test)]
727#[allow(dead_code)]
728const MAX_DATA_BLOCK_SIZE: Sectors = Sectors(2 * IEC::Mi); #[cfg(test)]
730const MIN_RECOMMENDED_METADATA_SIZE: Sectors = Sectors(4 * IEC::Ki); #[cfg(test)]
732#[allow(dead_code)]
733const MAX_METADATA_SIZE: MetaBlocks = MetaBlocks(255 * ((1 << 14) - 64));
737
738#[cfg(test)]
739pub fn minimal_thinpool(dm: &DM, path: &Path) -> ThinPoolDev {
742 let dev_size = blkdev_size(&OpenOptions::new().read(true).open(path).unwrap()).sectors();
743 let dev = Device::from(devnode_to_devno(path).unwrap().unwrap());
744 let meta_params = LinearTargetParams::new(dev, Sectors(0));
745 let meta_table = vec![TargetLine::new(
746 Sectors(0),
747 MIN_RECOMMENDED_METADATA_SIZE,
748 LinearDevTargetParams::Linear(meta_params),
749 )];
750 let meta = LinearDev::setup(
751 dm,
752 &test_name("meta").expect("valid format"),
753 None,
754 meta_table,
755 )
756 .unwrap();
757
758 let data_params = LinearTargetParams::new(dev, MIN_RECOMMENDED_METADATA_SIZE);
759 let data_table = vec![TargetLine::new(
760 Sectors(0),
761 dev_size - MIN_RECOMMENDED_METADATA_SIZE,
762 LinearDevTargetParams::Linear(data_params),
763 )];
764 let data = LinearDev::setup(
765 dm,
766 &test_name("data").expect("valid format"),
767 None,
768 data_table,
769 )
770 .unwrap();
771
772 ThinPoolDev::new(
773 dm,
774 &test_name("pool").expect("valid format"),
775 None,
776 meta,
777 data,
778 MIN_DATA_BLOCK_SIZE,
779 DataBlocks(1),
780 vec![
781 "no_discard_passdown".to_owned(),
782 "skip_block_zeroing".to_owned(),
783 ],
784 )
785 .unwrap()
786}
787
788#[cfg(test)]
789mod tests {
790 use std::path::Path;
791
792 use crate::{
793 core::{errors::Error, DmFlags},
794 testing::{test_name, test_with_spec},
795 };
796
797 use super::*;
798
799 fn test_minimum_values(paths: &[&Path]) {
803 assert!(!paths.is_empty());
804
805 let dm = DM::new().unwrap();
806 let mut tp = minimal_thinpool(&dm, paths[0]);
807 match tp.status(&dm, DmOptions::default()).unwrap() {
808 ThinPoolStatus::Working(ref status)
809 if status.summary == ThinPoolStatusSummary::Good =>
810 {
811 assert!(!status.discard_passdown);
812 assert_eq!(status.held_metadata_root, None);
813
814 let usage = &status.usage;
815 assert!(usage.used_meta > MetaBlocks(0));
817 assert_eq!(usage.total_meta, tp.meta_dev().size().metablocks());
818 assert_eq!(usage.used_data, DataBlocks(0));
819 assert_eq!(
820 usage.total_data,
821 DataBlocks(tp.data_dev().size() / tp.data_block_size())
822 );
823 }
824 status => panic!("unexpected thinpool status: {status:?}"),
825 }
826
827 let table = ThinPoolDev::read_kernel_table(&dm, &DevId::Name(tp.name()))
828 .unwrap()
829 .table;
830 let params = &table.params;
831 assert_eq!(params.metadata_dev, tp.meta_dev().device());
832 assert_eq!(params.data_dev, tp.data_dev().device());
833
834 tp.teardown(&dm).unwrap();
835 }
836
837 #[test]
838 fn loop_test_basic() {
839 test_with_spec(1, test_minimum_values);
840 }
841
842 fn test_low_data_block_size(paths: &[&Path]) {
844 assert!(!paths.is_empty());
845 let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
846
847 let dm = DM::new().unwrap();
848
849 let meta_name = test_name("meta").expect("valid format");
850 let meta_params = LinearTargetParams::new(dev, Sectors(0));
851 let meta_table = vec![TargetLine::new(
852 Sectors(0),
853 MIN_RECOMMENDED_METADATA_SIZE,
854 LinearDevTargetParams::Linear(meta_params),
855 )];
856 let meta = LinearDev::setup(&dm, &meta_name, None, meta_table).unwrap();
857
858 let data_name = test_name("data").expect("valid format");
859 let data_params = LinearTargetParams::new(dev, MIN_RECOMMENDED_METADATA_SIZE);
860 let data_table = vec![TargetLine::new(
861 Sectors(0),
862 512u64 * MIN_DATA_BLOCK_SIZE,
863 LinearDevTargetParams::Linear(data_params),
864 )];
865 let data = LinearDev::setup(&dm, &data_name, None, data_table).unwrap();
866
867 assert_matches!(
868 ThinPoolDev::new(
869 &dm,
870 &test_name("pool").expect("valid format"),
871 None,
872 meta,
873 data,
874 MIN_DATA_BLOCK_SIZE / 2u64,
875 DataBlocks(1),
876 vec![
877 "no_discard_passdown".to_owned(),
878 "skip_block_zeroing".to_owned()
879 ],
880 ),
881 Err(DmError::Core(Error::Ioctl(_, _, _, _)))
882 );
883 dm.device_remove(&DevId::Name(&meta_name), DmOptions::default())
884 .unwrap();
885 dm.device_remove(&DevId::Name(&data_name), DmOptions::default())
886 .unwrap();
887 }
888
889 #[test]
890 fn loop_test_low_data_block_size() {
891 test_with_spec(1, test_low_data_block_size);
892 }
893
894 fn test_set_data(paths: &[&Path]) {
897 assert!(paths.len() > 1);
898
899 let dm = DM::new().unwrap();
900 let mut tp = minimal_thinpool(&dm, paths[0]);
901
902 let mut data_table = tp.data_dev.table().table.clone();
903 let data_size = tp.data_dev.size();
904
905 let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap());
906 let data_params = LinearTargetParams::new(dev2, Sectors(0));
907 data_table.push(TargetLine::new(
908 data_size,
909 data_size,
910 LinearDevTargetParams::Linear(data_params),
911 ));
912 tp.set_data_table(&dm, data_table).unwrap();
913 tp.resume(&dm).unwrap();
914
915 match tp.status(&dm, DmOptions::default()).unwrap() {
916 ThinPoolStatus::Working(ref status) => {
917 let usage = &status.usage;
918 assert_eq!(
919 *usage.total_data * tp.table().table.params.data_block_size,
920 2u8 * data_size
921 );
922 }
923 ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
924 ThinPoolStatus::Fail => panic!("thin pool should not have failed"),
925 }
926
927 tp.teardown(&dm).unwrap();
928 }
929
930 #[test]
931 fn loop_test_set_data() {
932 test_with_spec(2, test_set_data);
933 }
934
935 fn test_set_meta(paths: &[&Path]) {
938 assert!(paths.len() > 1);
939
940 let dm = DM::new().unwrap();
941 let mut tp = minimal_thinpool(&dm, paths[0]);
942
943 let mut meta_table = tp.meta_dev.table().table.clone();
944 let meta_size = tp.meta_dev.size();
945
946 let dev2 = Device::from(devnode_to_devno(paths[1]).unwrap().unwrap());
947 let meta_params = LinearTargetParams::new(dev2, Sectors(0));
948 meta_table.push(TargetLine::new(
949 meta_size,
950 meta_size,
951 LinearDevTargetParams::Linear(meta_params),
952 ));
953 tp.set_meta_table(&dm, meta_table).unwrap();
954 tp.resume(&dm).unwrap();
955
956 match tp.status(&dm, DmOptions::default()).unwrap() {
957 ThinPoolStatus::Working(ref status) => {
958 let usage = &status.usage;
959 assert_eq!(usage.total_meta.sectors(), 2u8 * meta_size);
960 }
961 ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
962 ThinPoolStatus::Fail => panic!("thin pool should not have failed"),
963 }
964
965 tp.teardown(&dm).unwrap();
966 }
967
968 #[test]
969 fn loop_test_set_meta() {
970 test_with_spec(2, test_set_meta);
971 }
972
973 fn test_suspend(paths: &[&Path]) {
975 assert!(!paths.is_empty());
976
977 let dm = DM::new().unwrap();
978 let mut tp = minimal_thinpool(&dm, paths[0]);
979 tp.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
980 .unwrap();
981 tp.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
982 .unwrap();
983 tp.resume(&dm).unwrap();
984 tp.resume(&dm).unwrap();
985 tp.teardown(&dm).unwrap();
986 }
987
988 #[test]
989 fn loop_test_suspend() {
990 test_with_spec(1, test_suspend);
991 }
992
993 fn test_status_noflush(paths: &[&Path]) {
994 assert!(!paths.is_empty());
995
996 let dm = DM::new().unwrap();
997 let tp = minimal_thinpool(&dm, paths[0]);
998
999 tp.status(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
1000 .unwrap();
1001 }
1002
1003 #[test]
1004 fn loop_test_status_noflush() {
1005 test_with_spec(1, test_status_noflush);
1006 }
1007
1008 #[test]
1009 fn test_thinpool_target_params_zero() {
1010 let result = "thin-pool 42:42 42:43 16 2 0"
1011 .parse::<ThinPoolTargetParams>()
1012 .unwrap();
1013 assert_eq!(result.feature_args, HashSet::new());
1014 }
1015
1016 #[test]
1017 fn test_thinpool_target_params_none() {
1018 let result = "thin-pool 42:42 42:43 16 2"
1019 .parse::<ThinPoolTargetParams>()
1020 .unwrap();
1021 assert_eq!(result.feature_args, HashSet::new());
1022 }
1023}