stratisd/engine/strat_engine/backstore/
blockdevmgr.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5// Code to handle a collection of block devices.
6
7use std::collections::HashMap;
8#[cfg(test)]
9use std::collections::HashSet;
10
11use chrono::{DateTime, Duration, Utc};
12use rand::{rng, seq::IteratorRandom};
13
14use devicemapper::{Bytes, Device, Sectors};
15
16use crate::{
17    engine::{
18        shared::gather_encryption_info,
19        strat_engine::{
20            backstore::{
21                blockdev::{v1, v2, InternalBlockDev},
22                devices::{
23                    initialize_devices, initialize_devices_legacy, wipe_blockdevs, UnownedDevices,
24                },
25                shared::{BlkDevSegment, Segment},
26            },
27            crypt::handle::v1::CryptHandle,
28            metadata::{MDADataSize, BDA},
29            serde_structs::{BaseBlockDevSave, Recordable},
30            shared::bds_to_bdas,
31        },
32        types::{
33            DevUuid, InputEncryptionInfo, Name, PoolEncryptionInfo, PoolUuid,
34            ValidatedIntegritySpec,
35        },
36    },
37    stratis::{StratisError, StratisResult},
38};
39
40const MAX_NUM_TO_WRITE: usize = 10;
41
42#[derive(Debug)]
43struct TimeStamp {
44    inner: Option<DateTime<Utc>>,
45}
46
47impl From<Option<DateTime<Utc>>> for TimeStamp {
48    fn from(time: Option<DateTime<Utc>>) -> Self {
49        TimeStamp { inner: time }
50    }
51}
52
53impl TimeStamp {
54    // Get the best next time stamp; at least now() and at least one
55    // nanosecond more than the previous.
56    fn next(&self) -> DateTime<Utc> {
57        let current_time = Utc::now();
58        if Some(current_time) <= self.inner {
59            self.inner
60                .expect("self.inner >= Some(current_time")
61                .checked_add_signed(Duration::nanoseconds(1))
62                .expect("self.inner << maximum representable DateTime")
63        } else {
64            current_time
65        }
66    }
67
68    // Set the time stamp to a new value.
69    fn set(&mut self, time: DateTime<Utc>) {
70        self.inner = Some(time);
71    }
72}
73
74#[derive(Debug)]
75pub struct BlockDevMgr<B> {
76    /// All the block devices that belong to this block dev manager.
77    block_devs: Vec<B>,
78    /// The most recent time that variable length metadata was saved to the
79    /// devices managed by this block dev manager.
80    last_update_time: TimeStamp,
81}
82
83impl BlockDevMgr<v1::StratBlockDev> {
84    /// Initialize a new StratBlockDevMgr with specified pool and devices.
85    pub fn initialize(
86        pool_name: Name,
87        pool_uuid: PoolUuid,
88        devices: UnownedDevices,
89        mda_data_size: MDADataSize,
90        encryption_info: Option<&InputEncryptionInfo>,
91        sector_size: Option<u32>,
92    ) -> StratisResult<BlockDevMgr<v1::StratBlockDev>> {
93        Ok(BlockDevMgr::new(
94            initialize_devices_legacy(
95                devices,
96                pool_name,
97                pool_uuid,
98                mda_data_size,
99                encryption_info,
100                sector_size,
101            )?,
102            None,
103        ))
104    }
105
106    /// Add paths to self.
107    /// Return the uuids of all blockdevs corresponding to paths that were
108    /// added.
109    pub fn add(
110        &mut self,
111        pool_name: Name,
112        pool_uuid: PoolUuid,
113        devices: UnownedDevices,
114        sector_size: Option<u32>,
115    ) -> StratisResult<Vec<DevUuid>> {
116        let this_pool_uuid = self.block_devs.first().map(|bd| bd.pool_uuid());
117        if this_pool_uuid.is_some() && this_pool_uuid != Some(pool_uuid) {
118            return Err(StratisError::Msg(
119                format!("block devices being managed have pool UUID {} but new devices are to be added with pool UUID {}",
120                        this_pool_uuid.expect("guarded by if-expression"),
121                        pool_uuid)
122            ));
123        }
124
125        let encryption_info = pool_enc_to_enc!(self.encryption_info());
126        if let Some(ref ei) = encryption_info {
127            if !CryptHandle::can_unlock(
128                self.block_devs
129                    .first()
130                    .expect("Must have at least one blockdev")
131                    .physical_path(),
132                ei.single_key_description().is_some(),
133                ei.single_clevis_info().is_some(),
134            ) {
135                return Err(StratisError::Msg(
136                    "Either the key in the kernel keyring, Clevis, or both could not be used to perform encryption operations on the devices in the pool; check that the appropriate key in the keyring is set and that the Clevis key storage method is available depending on your provided unlock methods".to_string(),
137                ));
138            }
139        }
140
141        // FIXME: This is a bug. If new devices are added to a pool, and the
142        // variable length metadata requires more than the minimum allocated,
143        // then the necessary amount must be provided or the data can not be
144        // saved.
145        let bds = initialize_devices_legacy(
146            devices,
147            pool_name,
148            pool_uuid,
149            MDADataSize::default(),
150            encryption_info.map(InputEncryptionInfo::from).as_ref(),
151            sector_size,
152        )?;
153        let bdev_uuids = bds.iter().map(|bd| bd.uuid()).collect();
154        self.block_devs.extend(bds);
155        Ok(bdev_uuids)
156    }
157
158    /// Get the encryption information for a whole pool.
159    pub fn encryption_info(&self) -> Option<PoolEncryptionInfo> {
160        gather_encryption_info(
161            self.block_devs.len(),
162            self.block_devs.iter().map(|bd| bd.encryption_info()),
163        )
164        .expect("Cannot create a pool out of both encrypted and unencrypted devices")
165    }
166
167    pub fn is_encrypted(&self) -> bool {
168        self.encryption_info().is_some()
169    }
170
171    pub fn grow(&mut self, dev: DevUuid) -> StratisResult<bool> {
172        let bd = self
173            .block_devs
174            .iter_mut()
175            .find(|bd| bd.uuid() == dev)
176            .ok_or_else(|| StratisError::Msg(format!("Block device with UUID {dev} not found")))?;
177        bd.grow()
178    }
179
180    #[cfg(test)]
181    fn invariant(&self) {
182        let pool_uuids = self
183            .block_devs
184            .iter()
185            .map(|bd| bd.pool_uuid())
186            .collect::<HashSet<_>>();
187        assert!(pool_uuids.len() == 1);
188
189        let encryption_infos = self
190            .block_devs
191            .iter()
192            .filter_map(|bd| bd.encryption_info())
193            .collect::<Vec<_>>();
194        if encryption_infos.is_empty() {
195            assert_eq!(self.encryption_info(), None);
196        } else {
197            assert_eq!(encryption_infos.len(), self.block_devs.len());
198
199            let info_set = encryption_infos.iter().collect::<HashSet<_>>();
200            assert!(info_set.len() == 1);
201        }
202
203        for bd in self.block_devs.iter() {
204            bd.invariant();
205        }
206    }
207}
208
209impl BlockDevMgr<v2::StratBlockDev> {
210    /// Initialize a new StratBlockDevMgr with specified pool and devices.
211    pub fn initialize(
212        pool_uuid: PoolUuid,
213        devices: UnownedDevices,
214        mda_data_size: MDADataSize,
215    ) -> StratisResult<BlockDevMgr<v2::StratBlockDev>> {
216        Ok(BlockDevMgr::new(
217            initialize_devices(devices, pool_uuid, mda_data_size)?,
218            None,
219        ))
220    }
221
222    /// Add paths to self.
223    /// Return the uuids of all blockdevs corresponding to paths that were
224    /// added.
225    pub fn add(
226        &mut self,
227        pool_uuid: PoolUuid,
228        devices: UnownedDevices,
229    ) -> StratisResult<Vec<DevUuid>> {
230        let this_pool_uuid = self.block_devs.first().map(|bd| bd.pool_uuid());
231        if this_pool_uuid.is_some() && this_pool_uuid != Some(pool_uuid) {
232            return Err(StratisError::Msg(
233                format!("block devices being managed have pool UUID {} but new devices are to be added with pool UUID {}",
234                        this_pool_uuid.expect("guarded by if-expression"),
235                        pool_uuid)
236            ));
237        }
238
239        // FIXME: This is a bug. If new devices are added to a pool, and the
240        // variable length metadata requires more than the minimum allocated,
241        // then the necessary amount must be provided or the data can not be
242        // saved.
243        let bds = initialize_devices(devices, pool_uuid, MDADataSize::default())?;
244        let bdev_uuids = bds.iter().map(|bd| bd.uuid()).collect();
245        self.block_devs.extend(bds);
246        Ok(bdev_uuids)
247    }
248
249    pub fn grow(
250        &mut self,
251        dev: DevUuid,
252        integrity_spec: ValidatedIntegritySpec,
253    ) -> StratisResult<bool> {
254        let bd = self
255            .block_devs
256            .iter_mut()
257            .find(|bd| bd.uuid() == dev)
258            .ok_or_else(|| StratisError::Msg(format!("Block device with UUID {dev} not found")))?;
259        bd.grow(integrity_spec)
260    }
261
262    #[cfg(test)]
263    fn invariant(&self) {
264        let pool_uuids = self
265            .block_devs
266            .iter()
267            .map(|bd| bd.pool_uuid())
268            .collect::<HashSet<_>>();
269        assert!(pool_uuids.len() == 1);
270
271        for bd in self.block_devs.iter() {
272            bd.invariant();
273        }
274    }
275}
276
277impl<B> BlockDevMgr<B>
278where
279    B: InternalBlockDev,
280{
281    /// Make a struct that represents an existing BlockDevMgr.
282    pub fn new(block_devs: Vec<B>, last_update_time: Option<DateTime<Utc>>) -> BlockDevMgr<B> {
283        BlockDevMgr {
284            block_devs,
285            last_update_time: last_update_time.into(),
286        }
287    }
288
289    /// Convert the BlockDevMgr into a collection of BDAs.
290    pub fn into_bdas(self) -> HashMap<DevUuid, BDA> {
291        bds_to_bdas(self.block_devs)
292    }
293
294    /// Get a hashmap that maps UUIDs to Devices.
295    pub fn uuid_to_devno(&self) -> HashMap<DevUuid, Device> {
296        self.block_devs
297            .iter()
298            .map(|bd| (bd.uuid(), *bd.device()))
299            .collect()
300    }
301
302    pub fn destroy_all(&mut self) -> StratisResult<()> {
303        wipe_blockdevs(&mut self.block_devs)
304    }
305
306    /// Drain the BlockDevMgr block devices into a collection of block devices.
307    pub fn drain_bds(&mut self) -> Vec<B> {
308        self.block_devs.drain(..).collect::<Vec<_>>()
309    }
310
311    /// Get references to managed blockdevs.
312    pub fn blockdevs(&self) -> Vec<(DevUuid, &B)> {
313        self.block_devs.iter().map(|bd| (bd.uuid(), bd)).collect()
314    }
315
316    pub fn blockdevs_mut(&mut self) -> Vec<(DevUuid, &mut B)> {
317        self.block_devs
318            .iter_mut()
319            .map(|bd| (bd.uuid(), bd))
320            .collect()
321    }
322
323    pub fn get_blockdev_by_uuid(&self, uuid: DevUuid) -> Option<&B> {
324        self.block_devs.iter().find(|bd| bd.uuid() == uuid)
325    }
326
327    pub fn get_mut_blockdev_by_uuid(&mut self, uuid: DevUuid) -> Option<&mut B> {
328        self.block_devs.iter_mut().find(|bd| bd.uuid() == uuid)
329    }
330
331    /// Remove the specified block devs and erase their metadata.
332    ///
333    /// Precondition: It is the responsibility of the caller to ensure that
334    /// none of the blockdevs are in use, that is, have had any space allocated
335    /// from them for upper layers.
336    ///
337    /// If a specified blockdev is not found, returns an error and does nothing.
338    ///
339    /// NOTE: This method traverses the block_devs Vec from the rear to the
340    /// front, looking for blockdevs to remove. This is algorithmically
341    /// inefficient, unless it is assumed that the blockdevs specified are very
342    /// near the end of the Vec, which is expected to be the case. In that case,
343    /// the algorithm is O(n).
344    pub(super) fn remove_blockdevs(&mut self, uuids: &[DevUuid]) -> StratisResult<()> {
345        let mut removed = Vec::new();
346        for uuid in uuids {
347            let mut found = false;
348            let blockdevs_last_index = self.block_devs.len() - 1;
349            for i in 0..blockdevs_last_index {
350                let index = blockdevs_last_index - i;
351                if self.block_devs[index].uuid() == *uuid {
352                    removed.push(self.block_devs.swap_remove(index));
353                    found = true;
354                    break;
355                }
356            }
357            if !found {
358                return Err(StratisError::Msg(format!(
359                    "Blockdev corresponding to UUID: {uuid} not found."
360                )));
361            }
362        }
363        wipe_blockdevs(&mut removed)?;
364        Ok(())
365    }
366
367    /// Allocate space according to sizes vector request.
368    /// Return the segments allocated for each request, or None if it was
369    /// not possible to satisfy the request.
370    /// This method is atomic, it either allocates all requested or allocates
371    /// nothing.
372    pub fn alloc(&mut self, sizes: &[Sectors]) -> Option<Vec<Vec<BlkDevSegment>>> {
373        let total_needed: Sectors = sizes.iter().cloned().sum();
374        if self.avail_space() < total_needed {
375            return None;
376        }
377
378        let mut lists = Vec::new();
379
380        for &needed in sizes.iter() {
381            let mut alloc = Sectors(0);
382            let mut segs = Vec::new();
383            // TODO: Consider greater efficiency for allocation generally.
384            // Over time, the blockdevs at the start will be exhausted. It
385            // might be a good idea to keep an auxiliary structure, so that
386            // only blockdevs with some space left to allocate are accessed.
387            // In the context of this major inefficiency that ensues over time
388            // the obvious but more minor inefficiency of this inner loop is
389            // not worth worrying about.
390            for bd in &mut self.block_devs {
391                if alloc == needed {
392                    break;
393                }
394
395                let r_segs = bd.alloc(needed - alloc);
396                let blkdev_segs = r_segs.iter().map(|(&start, &length)| {
397                    BlkDevSegment::new(bd.uuid(), Segment::new(*bd.device(), start, length))
398                });
399                segs.extend(blkdev_segs);
400                alloc += r_segs.sum();
401            }
402            assert_eq!(alloc, needed);
403            lists.push(segs);
404        }
405
406        Some(lists)
407    }
408
409    /// Write the given data to all blockdevs marking with current time.
410    /// Return an error if data was not written to any blockdev.
411    /// Omit blockdevs which do not have sufficient space in BDA to accommodate
412    /// metadata. If current time is not more recent than previously written
413    /// time, use a time that is one nanosecond greater than that previously
414    /// written. Randomly select no more than MAX_NUM_TO_WRITE blockdevs to
415    /// write to.
416    pub fn save_state(&mut self, metadata: &[u8]) -> StratisResult<()> {
417        let stamp_time = self.last_update_time.next();
418
419        let data_size = Bytes::from(metadata.len());
420        let candidates = self
421            .block_devs
422            .iter_mut()
423            .filter(|b| b.max_stratis_metadata_size().bytes() >= data_size);
424
425        debug!("Writing {data_size} of pool level metadata to devices in pool");
426
427        // TODO: consider making selection not entirely random, i.e, ensuring
428        // distribution of metadata over different paths.
429        let saved = candidates
430            .choose_multiple(&mut rng(), MAX_NUM_TO_WRITE)
431            .iter_mut()
432            .fold(false, |acc, b| {
433                acc | b.save_state(&stamp_time, metadata).is_ok()
434            });
435
436        if saved {
437            self.last_update_time.set(stamp_time);
438            Ok(())
439        } else {
440            let err_msg = "Failed to save metadata to even one device in pool";
441            Err(StratisError::Msg(err_msg.into()))
442        }
443    }
444
445    /// Load the most recently written metadata from the block devices.
446    /// Returns an error if there is some metadata on a device but it could
447    /// not be loaded.
448    pub fn load_state(&self) -> StratisResult<Vec<u8>> {
449        let (mut last_updated, mut result) = (None, None);
450        for metadata in self.block_devs.iter().map(|dev| dev.load_state()) {
451            match metadata {
452                Ok(Some((metadata, updated))) => {
453                    if Some(updated) > last_updated {
454                        (last_updated, result) = (Some(updated), Some(metadata));
455                    }
456                }
457                Ok(None) => {}
458                Err(err) => return Err(err),
459            }
460        }
461
462        result.ok_or_else(|| {
463            StratisError::Msg(
464                "No pool-level metadata could be obtained for the specified pool".into(),
465            )
466        })
467    }
468
469    // SIZE methods
470
471    /// The number of sectors not allocated for any purpose.
472    pub fn avail_space(&self) -> Sectors {
473        self.block_devs.iter().map(|bd| bd.available()).sum()
474    }
475
476    /// The current size of all the blockdevs.
477    /// self.size() > self.avail_space() because some sectors are certainly
478    /// allocated for Stratis metadata
479    pub fn size(&self) -> Sectors {
480        self.block_devs
481            .iter()
482            .map(|b| b.total_size().sectors())
483            .sum()
484    }
485
486    /// The number of sectors given over to Stratis metadata
487    /// self.allocated_size() - self.metadata_size() >= self.avail_space()
488    pub fn metadata_size(&self) -> Sectors {
489        self.block_devs.iter().map(|bd| bd.metadata_size()).sum()
490    }
491
492    /// Tear down devicemapper devices for the block devices in this BlockDevMgr.
493    pub fn teardown(&mut self) -> StratisResult<()> {
494        let errs = self.block_devs.iter_mut().fold(Vec::new(), |mut errs, bd| {
495            if let Err(e) = bd.teardown() {
496                errs.push(e);
497            }
498            errs
499        });
500
501        if errs.is_empty() {
502            Ok(())
503        } else {
504            Err(StratisError::BestEffortError("Failed to remove devicemapper devices for some or all physical devices in the pool".to_string(), errs))
505        }
506    }
507}
508
509impl<B> Recordable<Vec<BaseBlockDevSave>> for BlockDevMgr<B>
510where
511    B: Recordable<BaseBlockDevSave>,
512{
513    fn record(&self) -> Vec<BaseBlockDevSave> {
514        self.block_devs.iter().map(|bd| bd.record()).collect()
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use std::path::Path;
521
522    use crate::engine::{
523        strat_engine::{
524            backstore::{
525                blockdev,
526                devices::{ProcessedPathInfos, UnownedDevices},
527            },
528            cmd,
529            tests::{crypt, loopbacked, real},
530        },
531        types::KeyDescription,
532    };
533
534    use super::*;
535
536    fn get_devices(paths: &[&Path]) -> StratisResult<UnownedDevices> {
537        ProcessedPathInfos::try_from(paths)
538            .map(|ps| ps.unpack())
539            .and_then(|(sds, uds)| sds.error_on_not_empty().map(|_| uds))
540    }
541
542    mod v1 {
543        use super::*;
544
545        /// Verify that initially,
546        /// size() - metadata_size() = avail_space().
547        /// After 2 Sectors have been allocated, that amount must also be included
548        /// in balance.
549        fn test_blockdevmgr_used(paths: &[&Path]) {
550            let pool_uuid = PoolUuid::new_v4();
551            let pool_name = Name::new("pool_name".to_string());
552            let devices = get_devices(paths).unwrap();
553            let mut mgr = BlockDevMgr::<blockdev::v1::StratBlockDev>::initialize(
554                pool_name,
555                pool_uuid,
556                devices,
557                MDADataSize::default(),
558                None,
559                None,
560            )
561            .unwrap();
562            assert_eq!(mgr.avail_space() + mgr.metadata_size(), mgr.size());
563
564            let allocated = Sectors(2);
565            mgr.alloc(&[allocated]).unwrap();
566            assert_eq!(
567                mgr.avail_space() + allocated + mgr.metadata_size(),
568                mgr.size()
569            );
570        }
571
572        #[test]
573        fn loop_test_blockdevmgr_used() {
574            loopbacked::test_with_spec(
575                &loopbacked::DeviceLimits::Range(1, 3, None),
576                test_blockdevmgr_used,
577            );
578        }
579
580        #[test]
581        fn real_test_blockdevmgr_used() {
582            real::test_with_spec(
583                &real::DeviceLimits::AtLeast(1, None, None),
584                test_blockdevmgr_used,
585            );
586        }
587
588        /// Test that the `BlockDevMgr` will add devices if the same key
589        /// is used to encrypted the existing devices and the added devices.
590        fn test_blockdevmgr_same_key(paths: &[&Path]) {
591            fn test_with_key(paths: &[&Path], key_desc: &KeyDescription) {
592                let pool_uuid = PoolUuid::new_v4();
593
594                let devices1 = get_devices(&paths[..2]).unwrap();
595                let devices2 = get_devices(&paths[2..3]).unwrap();
596
597                let pool_name = Name::new("pool_name".to_string());
598                let mut bdm = BlockDevMgr::<blockdev::v1::StratBlockDev>::initialize(
599                    pool_name.clone(),
600                    pool_uuid,
601                    devices1,
602                    MDADataSize::default(),
603                    InputEncryptionInfo::new_legacy(Some(key_desc.clone()), None).as_ref(),
604                    None,
605                )
606                .unwrap();
607
608                if bdm.add(pool_name, pool_uuid, devices2, None).is_err() {
609                    panic!(
610                        "Adding a blockdev with the same key to an encrypted pool should succeed"
611                    )
612                }
613            }
614
615            crypt::insert_and_cleanup_key(paths, test_with_key);
616        }
617
618        #[test]
619        fn loop_test_blockdevmgr_same_key() {
620            loopbacked::test_with_spec(
621                &loopbacked::DeviceLimits::Exactly(3, None),
622                test_blockdevmgr_same_key,
623            );
624        }
625
626        #[test]
627        fn real_test_blockdevmgr_same_key() {
628            real::test_with_spec(
629                &real::DeviceLimits::Exactly(3, None, None),
630                test_blockdevmgr_same_key,
631            );
632        }
633
634        /// Test that the `BlockDevMgr` will not add devices if a different key
635        /// is present in the keyring than was used to encrypted the existing
636        /// devices.
637        fn test_blockdevmgr_changed_key(paths: &[&Path]) {
638            fn test_with_key(paths: &[&Path], key_desc: &KeyDescription) {
639                let pool_uuid = PoolUuid::new_v4();
640
641                let devices1 = get_devices(&paths[..2]).unwrap();
642                let devices2 = get_devices(&paths[2..3]).unwrap();
643
644                let pool_name = Name::new("pool_name".to_string());
645                let mut bdm = BlockDevMgr::<blockdev::v1::StratBlockDev>::initialize(
646                    pool_name.clone(),
647                    pool_uuid,
648                    devices1,
649                    MDADataSize::default(),
650                    InputEncryptionInfo::new_legacy(Some(key_desc.clone()), None).as_ref(),
651                    None,
652                )
653                .unwrap();
654
655                crypt::change_key(key_desc);
656
657                if bdm.add(pool_name, pool_uuid, devices2, None).is_ok() {
658                    panic!("Adding a blockdev with a new key to an encrypted pool should fail")
659                }
660            }
661
662            crypt::insert_and_cleanup_key(paths, test_with_key);
663        }
664
665        #[test]
666        fn loop_test_blockdevmgr_changed_key() {
667            loopbacked::test_with_spec(
668                &loopbacked::DeviceLimits::Exactly(3, None),
669                test_blockdevmgr_changed_key,
670            );
671        }
672
673        #[test]
674        fn real_test_blockdevmgr_changed_key() {
675            real::test_with_spec(
676                &real::DeviceLimits::Exactly(3, None, None),
677                test_blockdevmgr_changed_key,
678            );
679        }
680
681        /// Verify that it is impossible to steal blockdevs from another Stratis
682        /// pool.
683        /// 1. Initialize devices with pool uuid.
684        /// 2. Initializing again with different uuid must fail.
685        /// 3. Adding the devices must succeed, because they already belong.
686        fn test_initialization_add_stratis(paths: &[&Path]) {
687            assert!(paths.len() > 1);
688            let (paths1, paths2) = paths.split_at(paths.len() / 2);
689
690            let uuid = PoolUuid::new_v4();
691            let uuid2 = PoolUuid::new_v4();
692            let pool_name1 = Name::new("pool_name1".to_string());
693            let pool_name2 = Name::new("pool_name2".to_string());
694
695            let bd_mgr = BlockDevMgr::<blockdev::v1::StratBlockDev>::initialize(
696                pool_name1,
697                uuid,
698                get_devices(paths1).unwrap(),
699                MDADataSize::default(),
700                None,
701                None,
702            )
703            .unwrap();
704            cmd::udev_settle().unwrap();
705
706            assert_matches!(get_devices(paths1), Err(_));
707
708            assert!(ProcessedPathInfos::try_from(paths1)
709                .unwrap()
710                .unpack()
711                .0
712                .partition(uuid2)
713                .0
714                .is_empty());
715
716            assert!(!ProcessedPathInfos::try_from(paths1)
717                .unwrap()
718                .unpack()
719                .0
720                .partition(uuid)
721                .0
722                .is_empty());
723
724            BlockDevMgr::<blockdev::v1::StratBlockDev>::initialize(
725                pool_name2,
726                uuid,
727                get_devices(paths2).unwrap(),
728                MDADataSize::default(),
729                None,
730                None,
731            )
732            .unwrap();
733
734            cmd::udev_settle().unwrap();
735
736            assert!(!ProcessedPathInfos::try_from(paths2)
737                .unwrap()
738                .unpack()
739                .0
740                .partition(uuid)
741                .0
742                .is_empty());
743
744            bd_mgr.invariant()
745        }
746
747        #[test]
748        fn loop_test_initialization_stratis() {
749            loopbacked::test_with_spec(
750                &loopbacked::DeviceLimits::Range(2, 3, None),
751                test_initialization_add_stratis,
752            );
753        }
754
755        #[test]
756        fn real_test_initialization_stratis() {
757            real::test_with_spec(
758                &real::DeviceLimits::AtLeast(2, None, None),
759                test_initialization_add_stratis,
760            );
761        }
762    }
763
764    mod v2 {
765        use super::*;
766
767        /// Verify that initially,
768        /// size() - metadata_size() = avail_space().
769        /// After 2 Sectors have been allocated, that amount must also be included
770        /// in balance.
771        fn test_blockdevmgr_used(paths: &[&Path]) {
772            let pool_uuid = PoolUuid::new_v4();
773            let devices = get_devices(paths).unwrap();
774            let mut mgr = BlockDevMgr::<blockdev::v2::StratBlockDev>::initialize(
775                pool_uuid,
776                devices,
777                MDADataSize::default(),
778            )
779            .unwrap();
780            assert_eq!(mgr.avail_space() + mgr.metadata_size(), mgr.size());
781
782            let allocated = Sectors(2);
783            mgr.alloc(&[allocated]).unwrap();
784            assert_eq!(
785                mgr.avail_space() + allocated + mgr.metadata_size(),
786                mgr.size()
787            );
788        }
789
790        #[test]
791        fn loop_test_blockdevmgr_used() {
792            loopbacked::test_with_spec(
793                &loopbacked::DeviceLimits::Range(1, 3, None),
794                test_blockdevmgr_used,
795            );
796        }
797
798        #[test]
799        fn real_test_blockdevmgr_used() {
800            real::test_with_spec(
801                &real::DeviceLimits::AtLeast(1, None, None),
802                test_blockdevmgr_used,
803            );
804        }
805
806        /// Verify that it is impossible to steal blockdevs from another Stratis
807        /// pool.
808        /// 1. Initialize devices with pool uuid.
809        /// 2. Initializing again with different uuid must fail.
810        /// 3. Adding the devices must succeed, because they already belong.
811        fn test_initialization_add_stratis(paths: &[&Path]) {
812            assert!(paths.len() > 1);
813            let (paths1, paths2) = paths.split_at(paths.len() / 2);
814
815            let uuid = PoolUuid::new_v4();
816            let uuid2 = PoolUuid::new_v4();
817
818            let bd_mgr = BlockDevMgr::<blockdev::v2::StratBlockDev>::initialize(
819                uuid,
820                get_devices(paths1).unwrap(),
821                MDADataSize::default(),
822            )
823            .unwrap();
824            cmd::udev_settle().unwrap();
825
826            assert_matches!(get_devices(paths1), Err(_));
827
828            assert!(ProcessedPathInfos::try_from(paths1)
829                .unwrap()
830                .unpack()
831                .0
832                .partition(uuid2)
833                .0
834                .is_empty());
835
836            assert!(!ProcessedPathInfos::try_from(paths1)
837                .unwrap()
838                .unpack()
839                .0
840                .partition(uuid)
841                .0
842                .is_empty());
843
844            BlockDevMgr::<blockdev::v2::StratBlockDev>::initialize(
845                uuid,
846                get_devices(paths2).unwrap(),
847                MDADataSize::default(),
848            )
849            .unwrap();
850
851            cmd::udev_settle().unwrap();
852
853            assert!(!ProcessedPathInfos::try_from(paths2)
854                .unwrap()
855                .unpack()
856                .0
857                .partition(uuid)
858                .0
859                .is_empty());
860
861            bd_mgr.invariant()
862        }
863
864        #[test]
865        fn loop_test_initialization_stratis() {
866            loopbacked::test_with_spec(
867                &loopbacked::DeviceLimits::Range(2, 3, None),
868                test_initialization_add_stratis,
869            );
870        }
871
872        #[test]
873        fn real_test_initialization_stratis() {
874            real::test_with_spec(
875                &real::DeviceLimits::AtLeast(2, None, None),
876                test_initialization_add_stratis,
877            );
878        }
879    }
880}