diff --git a/src/bin/stratisd-tools.rs b/src/bin/stratisd-tools.rs index e057929a57..3ab951c410 100644 --- a/src/bin/stratisd-tools.rs +++ b/src/bin/stratisd-tools.rs @@ -77,7 +77,7 @@ fn main() { match c.run(stripped_args) { Ok(()) => {} Err(e) => { - eprintln!("Error encountered: {e}"); + eprintln!("{e}"); process::exit(1); } } diff --git a/src/bin/utils/cmds.rs b/src/bin/utils/cmds.rs index 1c283013f0..a60b23ad6c 100644 --- a/src/bin/utils/cmds.rs +++ b/src/bin/utils/cmds.rs @@ -8,14 +8,20 @@ use std::{ str::FromStr, }; -use clap::{Arg, ArgAction, Command}; +use clap::{builder::PossibleValuesParser, Arg, ArgAction, Command}; #[cfg(feature = "systemd_compat")] use clap::builder::Str; use log::LevelFilter; +use strum::VariantNames; use devicemapper::Bytes; +use stratisd::engine::{ + IntegritySpec, IntegrityTagSpec, ValidatedIntegritySpec, DEFAULT_INTEGRITY_JOURNAL_SIZE, + DEFAULT_INTEGRITY_TAG_SPEC, +}; + use crate::utils::predict_usage; #[cfg(feature = "systemd_compat")] @@ -86,6 +92,31 @@ pool is encrypted, setting this option has no effect on the prediction."), .value_parser(clap::value_parser!(u128)) .help("Size of filesystem to be made for this pool. May be specified multiple times, one for each filesystem. Units are bytes. Must be at least 512 MiB and less than 4 PiB.") .next_line_help(true) + ) + .arg( + Arg::new("integrity_tag_spec") + .long("integrity-tag-spec") + .num_args(1) + .value_parser(PossibleValuesParser::new(IntegrityTagSpec::VARIANTS)) + .default_value(DEFAULT_INTEGRITY_TAG_SPEC.as_ref()) + .help("Integrity tag specification defining the size of the tag to store a checksum or other value for each block on a device.") + .next_line_help(true) + ) + .arg( + Arg::new("integrity_journal_size") + .long("integrity-journal-size") + .num_args(1) + .value_parser(clap::value_parser!(u64)) + .default_value(Box::leak((*DEFAULT_INTEGRITY_JOURNAL_SIZE).to_string().into_boxed_str()) as &'static str) + .help("Size of the integrity journal. Default is 128 MiB. Units are bytes.") + .next_line_help(true) + ) + .arg( + Arg::new("no_integrity_superblock") + .action(ArgAction::SetTrue) + .long("no-integrity-superblock") + .help("Do not allocate space for integrity superblock") + .next_line_help(true) ), Command::new("filesystem") .about("Predicts the space usage when creating a Stratis filesystem.") @@ -126,6 +157,24 @@ impl<'a> UtilCommand<'a> for StratisPredictUsage { sub_m .get_many::("filesystem-size") .map(|szs| szs.map(|n| Bytes(*n)).collect::>()), + ValidatedIntegritySpec::try_from(IntegritySpec { + journal_size: Some( + sub_m + .get_one::("integrity_journal_size") + .map(|n| Bytes::from(*n)) + .expect("default specified by parser"), + ), + tag_spec: Some( + sub_m + .get_one::("integrity_tag_spec") + .map(|sz| { + IntegrityTagSpec::try_from(sz.as_str()) + .expect("parser ensures valid value") + }) + .expect("default specified by parser"), + ), + allocate_superblock: Some(!sub_m.get_flag("no_integrity_superblock")), + })?, LevelFilter::from_str( matches .get_one::("log-level") diff --git a/src/bin/utils/predict_usage.rs b/src/bin/utils/predict_usage.rs index a7757fa4b5..268452757b 100644 --- a/src/bin/utils/predict_usage.rs +++ b/src/bin/utils/predict_usage.rs @@ -13,7 +13,9 @@ use serde_json::{json, Value}; use devicemapper::{Bytes, Sectors}; -use stratisd::engine::{crypt_metadata_size, integrity_meta_space, ThinPoolSizeParams, BDA}; +use stratisd::engine::{ + crypt_metadata_size, integrity_meta_space, ThinPoolSizeParams, ValidatedIntegritySpec, BDA, +}; // 2^FS_SIZE_START_POWER is the logical size of the smallest Stratis // filesystem for which usage data exists in FSSizeLookup::internal, i.e., @@ -161,14 +163,17 @@ pub fn predict_filesystem_usage( Ok(()) } -fn predict_pool_metadata_usage(device_sizes: Vec) -> Result> { +fn predict_pool_metadata_usage( + device_sizes: Vec, + integrity_spec: ValidatedIntegritySpec, +) -> Result> { let stratis_metadata_alloc = BDA::default().extended_size().sectors(); let stratis_avail_sizes = device_sizes .iter() .map(|&s| { info!("Total size of device: {:}", s); - let integrity_deduction = integrity_meta_space(s); + let integrity_deduction = integrity_meta_space(s, integrity_spec); info!( "Deduction for stratis metadata: {:}", stratis_metadata_alloc @@ -205,6 +210,7 @@ pub fn predict_pool_usage( overprovisioned: bool, device_sizes: Vec, filesystem_sizes: Option>, + integrity_spec: ValidatedIntegritySpec, log_level: LevelFilter, ) -> Result<(), Box> { Builder::new().filter(None, log_level).init(); @@ -216,7 +222,7 @@ pub fn predict_pool_usage( let device_sizes = device_sizes.iter().map(|s| s.sectors()).collect::>(); let total_size: Sectors = device_sizes.iter().cloned().sum(); - let non_metadata_size = predict_pool_metadata_usage(device_sizes)?; + let non_metadata_size = predict_pool_metadata_usage(device_sizes, integrity_spec)?; let size_params = ThinPoolSizeParams::new(non_metadata_size)?; let total_non_data = 2usize * size_params.meta_size() + size_params.mdv_size(); diff --git a/src/dbus_api/api/manager_3_0/methods.rs b/src/dbus_api/api/manager_3_0/methods.rs index 46a2d256e7..5b6ee29ab0 100644 --- a/src/dbus_api/api/manager_3_0/methods.rs +++ b/src/dbus_api/api/manager_3_0/methods.rs @@ -13,6 +13,7 @@ use futures::executor::block_on; use crate::{ dbus_api::{ + api::shared::EncryptionParams, blockdev::create_dbus_blockdev, consts, filesystem::create_dbus_filesystem, @@ -21,15 +22,13 @@ use crate::{ util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, engine::{ - CreateAction, DeleteAction, EncryptionInfo, EngineAction, KeyDescription, + CreateAction, DeleteAction, EncryptionInfo, EngineAction, IntegritySpec, KeyDescription, MappingCreateAction, MappingDeleteAction, PoolIdentifier, PoolUuid, SetUnlockAction, UnlockMethod, }, stratis::StratisError, }; -type EncryptionParams = (Option<(bool, String)>, Option<(bool, (String, String))>); - pub fn destroy_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let message: &Message = m.msg; let mut iter = message.iter_init(); @@ -329,6 +328,7 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { name, &devs.map(Path::new).collect::>(), EncryptionInfo::from_options((key_desc, clevis_info)).as_ref(), + IntegritySpec::default(), ))); match create_result { Ok(pool_uuid_action) => match pool_uuid_action { diff --git a/src/dbus_api/api/manager_3_5/methods.rs b/src/dbus_api/api/manager_3_5/methods.rs index 8cd3a05093..573358cb60 100644 --- a/src/dbus_api/api/manager_3_5/methods.rs +++ b/src/dbus_api/api/manager_3_5/methods.rs @@ -15,7 +15,7 @@ use crate::{ types::{DbusErrorEnum, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, - engine::{CreateAction, EncryptionInfo, KeyDescription, PoolIdentifier}, + engine::{CreateAction, EncryptionInfo, IntegritySpec, KeyDescription, PoolIdentifier}, stratis::StratisError, }; @@ -65,6 +65,7 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { name, &devs.map(Path::new).collect::>(), EncryptionInfo::from_options((key_desc, clevis_info)).as_ref(), + IntegritySpec::default(), ))); match create_result { Ok(pool_uuid_action) => match pool_uuid_action { diff --git a/src/dbus_api/api/manager_3_8/api.rs b/src/dbus_api/api/manager_3_8/api.rs index 627d7fd955..5b8e468599 100644 --- a/src/dbus_api/api/manager_3_8/api.rs +++ b/src/dbus_api/api/manager_3_8/api.rs @@ -6,7 +6,10 @@ use dbus_tree::{Access, EmitsChangedSignal, Factory, MTSync, Method, Property}; use crate::dbus_api::{ api::{ - manager_3_8::{methods::start_pool, props::get_stopped_pools}, + manager_3_8::{ + methods::{create_pool, start_pool}, + props::get_stopped_pools, + }, prop_conv::StoppedOrLockedPools, }, consts, @@ -31,6 +34,58 @@ pub fn start_pool_method(f: &Factory, TData>) -> Method, TData>) -> Method, TData> { + f.method("CreatePool", (), create_pool) + .in_arg(("name", "s")) + .in_arg(("devices", "as")) + // Optional key description of key in the kernel keyring + // b: true if the pool should be encrypted and able to be + // unlocked with a passphrase associated with this key description. + // s: key description + // + // Rust representation: (bool, String) + .in_arg(("key_desc", "(bs)")) + // Optional Clevis information for binding on initialization. + // b: true if the pool should be encrypted and able to be unlocked + // using Clevis. + // s: pin name + // s: JSON config for Clevis use + // + // Rust representation: (bool, (String, String)) + .in_arg(("clevis_info", "(b(ss))")) + // Optional journal size for integrity metadata reservation. + // b: true if the size should be specified. + // false if the default should be used. + // i: Integer representing journal size in bytes. + // + // Rust representation: (bool, u64) + .in_arg(("journal_size", "(bt)")) + // Optional tag size or specification for integrity metadata + // reservation. + // b: true if the size should be specified. + // false if the default should be used. + // s: Tag size specification. + // + // Rust representation: (bool, String) + .in_arg(("tag_spec", "(bs)")) + // Optionally specify whether to reserve space for integrity + // superblock. + // b: true if the second value is to be read, otherwise false. + // b: true if the superblock reservation is supposed to be done + // + // Rust representation: (bool, bool) + .in_arg(("allocate_superblock", "(bb)")) + // In order from left to right: + // b: true if a pool was created and object paths were returned + // o: Object path for Pool + // a(o): Array of object paths for block devices + // + // Rust representation: (bool, (dbus::Path, Vec)) + .out_arg(("result", "(b(oao))")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + pub fn stopped_pools_property(f: &Factory, TData>) -> Property, TData> { f.property::(consts::STOPPED_POOLS_PROP, ()) .access(Access::Read) diff --git a/src/dbus_api/api/manager_3_8/methods.rs b/src/dbus_api/api/manager_3_8/methods.rs index 5b7a40d943..c91d7eb211 100644 --- a/src/dbus_api/api/manager_3_8/methods.rs +++ b/src/dbus_api/api/manager_3_8/methods.rs @@ -2,19 +2,30 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use dbus::{arg::OwnedFd, Message, Path}; +use std::path::Path; + +use dbus::{ + arg::{Array, OwnedFd}, + Message, +}; use dbus_tree::{MTSync, MethodInfo, MethodResult}; use futures::executor::block_on; +use devicemapper::Bytes; + use crate::{ dbus_api::{ + api::shared::EncryptionParams, blockdev::create_dbus_blockdev, filesystem::create_dbus_filesystem, pool::create_dbus_pool, types::{DbusErrorEnum, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, - engine::{Name, PoolIdentifier, PoolUuid, StartAction, UnlockMethod}, + engine::{ + CreateAction, EncryptionInfo, IntegritySpec, IntegrityTagSpec, KeyDescription, Name, + PoolIdentifier, PoolUuid, StartAction, UnlockMethod, + }, stratis::StratisError, }; @@ -25,8 +36,12 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let dbus_context = m.tree.get_data(); let default_return: ( bool, - (Path<'static>, Vec>, Vec>), - ) = (false, (Path::default(), Vec::new(), Vec::new())); + ( + dbus::Path<'static>, + Vec>, + Vec>, + ), + ) = (false, (dbus::Path::default(), Vec::new(), Vec::new())); let return_message = message.method_return(); let id_str: &str = get_next_arg(&mut iter, 0)?; @@ -130,3 +145,119 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { OK_STRING.to_string(), )]) } + +pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let base_path = m.path.get_name(); + let message: &Message = m.msg; + let mut iter = message.iter_init(); + + let name: &str = get_next_arg(&mut iter, 0)?; + let devs: Array<'_, &str, _> = get_next_arg(&mut iter, 1)?; + let (key_desc_tuple, clevis_tuple): EncryptionParams = ( + Some(get_next_arg(&mut iter, 2)?), + Some(get_next_arg(&mut iter, 3)?), + ); + let journal_size_tuple: (bool, u64) = get_next_arg(&mut iter, 4)?; + let tag_spec_tuple: (bool, String) = get_next_arg(&mut iter, 5)?; + let allocate_superblock_tuple: (bool, bool) = get_next_arg(&mut iter, 6)?; + + let return_message = message.method_return(); + + let default_return: (bool, (dbus::Path<'static>, Vec>)) = + (false, (dbus::Path::default(), Vec::new())); + + let key_desc = match key_desc_tuple.and_then(tuple_to_option) { + Some(kds) => match KeyDescription::try_from(kds) { + Ok(kd) => Some(kd), + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }, + None => None, + }; + + let clevis_info = match clevis_tuple.and_then(tuple_to_option) { + Some((pin, json_string)) => match serde_json::from_str(json_string.as_str()) { + Ok(j) => Some((pin, j)), + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Serde(e)); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }, + None => None, + }; + + let journal_size = tuple_to_option(journal_size_tuple).map(Bytes::from); + let tag_spec = match tuple_to_option(tag_spec_tuple) + .map(|s| IntegrityTagSpec::try_from(s.as_str())) + .transpose() + { + Ok(s) => s, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Msg(format!( + "Failed to parse integrity tag specification: {e}" + ))); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let allocate_superblock = tuple_to_option(allocate_superblock_tuple); + + let dbus_context = m.tree.get_data(); + let create_result = handle_action!(block_on(dbus_context.engine.create_pool( + name, + &devs.map(Path::new).collect::>(), + EncryptionInfo::from_options((key_desc, clevis_info)).as_ref(), + IntegritySpec { + journal_size, + tag_spec, + allocate_superblock, + }, + ))); + match create_result { + Ok(pool_uuid_action) => match pool_uuid_action { + CreateAction::Created(uuid) => { + let guard = match block_on(dbus_context.engine.get_pool(PoolIdentifier::Uuid(uuid))) + { + Some(g) => g, + None => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Msg( + format!("Pool with UUID {uuid} was successfully started but appears to have been removed before it could be exposed on the D-Bus") + )); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let (pool_name, pool_uuid, pool) = guard.as_tuple(); + let pool_path = + create_dbus_pool(dbus_context, base_path.clone(), &pool_name, pool_uuid, pool); + let mut bd_paths = Vec::new(); + for (bd_uuid, tier, bd) in pool.blockdevs() { + bd_paths.push(create_dbus_blockdev( + dbus_context, + pool_path.clone(), + bd_uuid, + tier, + bd, + )); + } + + Ok(vec![return_message.append3( + (true, (pool_path, bd_paths)), + DbusErrorEnum::OK as u16, + OK_STRING.to_string(), + )]) + } + CreateAction::Identity => Ok(vec![return_message.append3( + default_return, + DbusErrorEnum::OK as u16, + OK_STRING.to_string(), + )]), + }, + Err(x) => { + let (rc, rs) = engine_to_dbus_err_tuple(&x); + Ok(vec![return_message.append3(default_return, rc, rs)]) + } + } +} diff --git a/src/dbus_api/api/manager_3_8/mod.rs b/src/dbus_api/api/manager_3_8/mod.rs index 48fc8b4d99..eb29edbbb5 100644 --- a/src/dbus_api/api/manager_3_8/mod.rs +++ b/src/dbus_api/api/manager_3_8/mod.rs @@ -6,4 +6,4 @@ mod api; mod methods; mod props; -pub use api::{start_pool_method, stopped_pools_property}; +pub use api::{create_pool_method, start_pool_method, stopped_pools_property}; diff --git a/src/dbus_api/api/mod.rs b/src/dbus_api/api/mod.rs index 84533c63bf..3775a53d88 100644 --- a/src/dbus_api/api/mod.rs +++ b/src/dbus_api/api/mod.rs @@ -152,7 +152,7 @@ pub fn get_base_tree<'a>( ) .add( f.interface(consts::MANAGER_INTERFACE_NAME_3_8, ()) - .add_m(manager_3_5::create_pool_method(&f)) + .add_m(manager_3_8::create_pool_method(&f)) .add_m(manager_3_0::set_key_method(&f)) .add_m(manager_3_0::unset_key_method(&f)) .add_m(manager_3_0::list_keys_method(&f)) diff --git a/src/dbus_api/api/shared.rs b/src/dbus_api/api/shared.rs index e2132b6d88..ec21bccc13 100644 --- a/src/dbus_api/api/shared.rs +++ b/src/dbus_api/api/shared.rs @@ -22,6 +22,8 @@ use crate::{ engine::{AllLockReadGuard, DevUuid, Engine, FilesystemUuid, Pool, PoolUuid, StratisUuid}, }; +pub type EncryptionParams = (Option<(bool, String)>, Option<(bool, (String, String))>); + pub fn get_managed_objects_method( f: &Factory, TData>, ) -> Method, TData> { diff --git a/src/engine/engine.rs b/src/engine/engine.rs index dc20fd76fa..1b192fedc0 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -21,11 +21,12 @@ use crate::{ structures::{AllLockReadGuard, AllLockWriteGuard, SomeLockReadGuard, SomeLockWriteGuard}, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, LockedPoolsInfo, - MappingCreateAction, MappingDeleteAction, Name, PoolDiff, PoolEncryptionInfo, - PoolIdentifier, PoolUuid, RegenAction, RenameAction, ReportType, SetCreateAction, - SetDeleteAction, SetUnlockAction, StartAction, StopAction, StoppedPoolsInfo, - StratFilesystemDiff, StratSigblockVersion, UdevEngineEvent, UnlockMethod, + EncryptionInfo, FilesystemUuid, GrowAction, IntegritySpec, Key, KeyDescription, + LockedPoolsInfo, MappingCreateAction, MappingDeleteAction, Name, PoolDiff, + PoolEncryptionInfo, PoolIdentifier, PoolUuid, RegenAction, RenameAction, ReportType, + SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, + StoppedPoolsInfo, StratFilesystemDiff, StratSigblockVersion, UdevEngineEvent, + UnlockMethod, }, }, stratis::StratisResult, @@ -381,6 +382,7 @@ pub trait Engine: Debug + Report + Send + Sync { name: &str, blockdev_paths: &[&Path], encryption_info: Option<&EncryptionInfo>, + integrity_spec: IntegritySpec, ) -> StratisResult>; /// Handle a libudev event. diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 17396d5fc2..df0a86c2fc 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -20,13 +20,14 @@ pub use self::{ structures::{AllLockReadGuard, ExclusiveGuard, SharedGuard, Table}, types::{ ActionAvailability, BlockDevTier, ClevisInfo, CreateAction, DeleteAction, DevUuid, Diff, - EncryptionInfo, EngineAction, FilesystemUuid, GrowAction, KeyDescription, Lockable, - LockedPoolInfo, LockedPoolsInfo, MappingCreateAction, MappingDeleteAction, - MaybeInconsistent, Name, PoolDiff, PoolEncryptionInfo, PoolIdentifier, PoolUuid, - PropChangeAction, RenameAction, ReportType, SetCreateAction, SetDeleteAction, + EncryptionInfo, EngineAction, FilesystemUuid, GrowAction, IntegritySpec, IntegrityTagSpec, + KeyDescription, Lockable, LockedPoolInfo, LockedPoolsInfo, MappingCreateAction, + MappingDeleteAction, MaybeInconsistent, Name, PoolDiff, PoolEncryptionInfo, PoolIdentifier, + PoolUuid, PropChangeAction, RenameAction, ReportType, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, StoppedPoolInfo, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, StratisUuid, - ThinPoolDiff, ToDisplay, UdevEngineEvent, UnlockMethod, + ThinPoolDiff, ToDisplay, UdevEngineEvent, UnlockMethod, ValidatedIntegritySpec, + DEFAULT_INTEGRITY_JOURNAL_SIZE, DEFAULT_INTEGRITY_TAG_SPEC, }, }; diff --git a/src/engine/sim_engine/engine.rs b/src/engine/sim_engine/engine.rs index 1761c2c7e9..c2cc44c595 100644 --- a/src/engine/sim_engine/engine.rs +++ b/src/engine/sim_engine/engine.rs @@ -25,9 +25,10 @@ use crate::{ }, types::{ CreateAction, DeleteAction, DevUuid, EncryptionInfo, Features, FilesystemUuid, - LockedPoolsInfo, Name, PoolDevice, PoolDiff, PoolIdentifier, PoolUuid, RenameAction, - ReportType, SetUnlockAction, StartAction, StopAction, StoppedPoolInfo, + IntegritySpec, LockedPoolsInfo, Name, PoolDevice, PoolDiff, PoolIdentifier, PoolUuid, + RenameAction, ReportType, SetUnlockAction, StartAction, StopAction, StoppedPoolInfo, StoppedPoolsInfo, StratFilesystemDiff, UdevEngineEvent, UnlockMethod, + ValidatedIntegritySpec, }, StratSigblockVersion, }, @@ -129,12 +130,15 @@ impl Engine for SimEngine { name: &str, blockdev_paths: &[&Path], encryption_info: Option<&EncryptionInfo>, + integrity_spec: IntegritySpec, ) -> StratisResult> { validate_name(name)?; let name = Name::new(name.to_owned()); validate_paths(blockdev_paths)?; + let integrity_spec = ValidatedIntegritySpec::try_from(integrity_spec)?; + if let Some(key_desc) = encryption_info.and_then(|ei| ei.key_description()) { if !self.key_handler.contains_key(key_desc) { return Err(StratisError::Msg(format!( @@ -156,7 +160,7 @@ impl Engine for SimEngine { let device_set: HashSet<_, RandomState> = HashSet::from_iter(blockdev_paths); let devices = device_set.into_iter().cloned().collect::>(); - let (pool_uuid, pool) = SimPool::new(&devices, encryption_info); + let (pool_uuid, pool) = SimPool::new(&devices, encryption_info, integrity_spec); self.pools.modify_all().await.insert( Name::new(name.to_owned()), @@ -440,6 +444,7 @@ mod tests { "name", strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -451,10 +456,15 @@ mod tests { /// Destroying a pool with devices should succeed fn destroy_pool_w_devices() { let engine = SimEngine::default(); - let uuid = test_async!(engine.create_pool("name", strs_to_paths!(["/s/d"]), None)) - .unwrap() - .changed() - .unwrap(); + let uuid = test_async!(engine.create_pool( + "name", + strs_to_paths!(["/s/d"]), + None, + IntegritySpec::default() + )) + .unwrap() + .changed() + .unwrap(); assert!(test_async!(engine.destroy_pool(uuid)).is_ok()); } @@ -463,10 +473,15 @@ mod tests { fn destroy_pool_w_filesystem() { let engine = SimEngine::default(); let pool_name = "pool_name"; - let uuid = test_async!(engine.create_pool(pool_name, strs_to_paths!(["/s/d"]), None)) - .unwrap() - .changed() - .unwrap(); + let uuid = test_async!(engine.create_pool( + pool_name, + strs_to_paths!(["/s/d"]), + None, + IntegritySpec::default() + )) + .unwrap() + .changed() + .unwrap(); { let mut pool = test_async!(engine.get_mut_pool(PoolIdentifier::Uuid(uuid))).unwrap(); pool.create_filesystems(pool_name, uuid, &[("test", None, None)]) @@ -482,9 +497,9 @@ mod tests { let name = "name"; let engine = SimEngine::default(); let devices = strs_to_paths!(["/s/d"]); - test_async!(engine.create_pool(name, devices, None)).unwrap(); + test_async!(engine.create_pool(name, devices, None, IntegritySpec::default())).unwrap(); assert_matches!( - test_async!(engine.create_pool(name, devices, None)), + test_async!(engine.create_pool(name, devices, None, IntegritySpec::default())), Ok(CreateAction::Identity) ); } @@ -494,11 +509,18 @@ mod tests { fn create_pool_name_collision_different_args() { let name = "name"; let engine = SimEngine::default(); - test_async!(engine.create_pool(name, strs_to_paths!(["/s/d"]), None)).unwrap(); + test_async!(engine.create_pool( + name, + strs_to_paths!(["/s/d"]), + None, + IntegritySpec::default() + )) + .unwrap(); assert!(test_async!(engine.create_pool( name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .is_err()); } @@ -509,15 +531,20 @@ mod tests { let path = "/s/d"; let engine = SimEngine::default(); assert_matches!( - test_async!(engine.create_pool("name", strs_to_paths!([path, path]), None)) - .unwrap() - .changed() - .map( - |uuid| test_async!(engine.get_pool(PoolIdentifier::Uuid(uuid))) - .unwrap() - .blockdevs() - .len() - ), + test_async!(engine.create_pool( + "name", + strs_to_paths!([path, path]), + None, + IntegritySpec::default() + )) + .unwrap() + .changed() + .map( + |uuid| test_async!(engine.get_pool(PoolIdentifier::Uuid(uuid))) + .unwrap() + .blockdevs() + .len() + ), Some(1) ); } @@ -541,6 +568,7 @@ mod tests { name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -559,6 +587,7 @@ mod tests { "old_name", strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -578,6 +607,7 @@ mod tests { "old_name", strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -586,6 +616,7 @@ mod tests { new_name, strs_to_paths!(["/dev/four", "/dev/five", "/dev/six"]), None, + IntegritySpec::default(), )) .unwrap(); assert!(test_async!(engine.rename_pool(uuid, new_name)).is_err()); @@ -600,6 +631,7 @@ mod tests { new_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap(); assert_matches!( diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index 3edc16d0f7..41ff7860a4 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -26,7 +26,7 @@ use crate::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, StratSigblockVersion, + SetDeleteAction, StratSigblockVersion, ValidatedIntegritySpec, }, PropChangeAction, }, @@ -41,6 +41,7 @@ pub struct SimPool { fs_limit: u64, enable_overprov: bool, encryption_info: Option, + integrity_spec: ValidatedIntegritySpec, } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -48,10 +49,15 @@ pub struct PoolSave { name: String, fs_limit: Option, enable_overprov: Option, + integrity_spec: Option, } impl SimPool { - pub fn new(paths: &[&Path], enc_info: Option<&EncryptionInfo>) -> (PoolUuid, SimPool) { + pub fn new( + paths: &[&Path], + enc_info: Option<&EncryptionInfo>, + integrity_spec: ValidatedIntegritySpec, + ) -> (PoolUuid, SimPool) { let devices: HashSet<_, RandomState> = HashSet::from_iter(paths); let device_pairs = devices.iter().map(|p| SimDev::new(p)); ( @@ -63,6 +69,7 @@ impl SimPool { fs_limit: 10, enable_overprov: true, encryption_info: enc_info.cloned(), + integrity_spec, }, ) } @@ -130,6 +137,7 @@ impl SimPool { name: name.to_owned(), enable_overprov: Some(self.enable_overprov), fs_limit: Some(self.fs_limit), + integrity_spec: Some(self.integrity_spec), } } } @@ -882,7 +890,7 @@ mod tests { use crate::engine::{ sim_engine::SimEngine, - types::{EngineAction, PoolIdentifier}, + types::{EngineAction, IntegritySpec, PoolIdentifier}, Engine, }; @@ -897,6 +905,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -917,6 +926,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -945,6 +955,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -976,6 +987,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -996,6 +1008,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1016,6 +1029,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1036,6 +1050,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1064,6 +1079,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1082,6 +1098,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1107,6 +1124,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1130,6 +1148,7 @@ mod tests { pool_name, strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() @@ -1157,6 +1176,7 @@ mod tests { "pool_name", strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), None, + IntegritySpec::default(), )) .unwrap() .changed() diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index 3034098fe0..ed10235457 100644 --- a/src/engine/strat_engine/backstore/backstore/v2.rs +++ b/src/engine/strat_engine/backstore/backstore/v2.rs @@ -34,7 +34,7 @@ use crate::{ }, types::{ ActionAvailability, BlockDevTier, DevUuid, EncryptionInfo, KeyDescription, PoolUuid, - SizedKeyMemory, UnlockMethod, + SizedKeyMemory, UnlockMethod, ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -437,12 +437,12 @@ impl Backstore { devices: UnownedDevices, mda_data_size: MDADataSize, encryption_info: Option<&EncryptionInfo>, + integrity_spec: ValidatedIntegritySpec, ) -> StratisResult { - let data_tier = DataTier::::new(BlockDevMgr::::initialize( - pool_uuid, - devices, - mda_data_size, - )?); + let data_tier = DataTier::::new( + BlockDevMgr::::initialize(pool_uuid, devices, mda_data_size)?, + integrity_spec, + ); let mut backstore = Backstore { data_tier, @@ -1170,13 +1170,16 @@ mod tests { use devicemapper::{CacheDevStatus, DataBlocks, DmOptions, IEC}; - use crate::engine::strat_engine::{ - backstore::devices::{ProcessedPathInfos, UnownedDevices}, - cmd, - crypt::crypt_metadata_size, - metadata::device_identifiers, - ns::{unshare_mount_namespace, MemoryFilesystem}, - tests::{crypt, loopbacked, real}, + use crate::engine::{ + strat_engine::{ + backstore::devices::{ProcessedPathInfos, UnownedDevices}, + cmd, + crypt::crypt_metadata_size, + metadata::device_identifiers, + ns::{unshare_mount_namespace, MemoryFilesystem}, + tests::{crypt, loopbacked, real}, + }, + types::ValidatedIntegritySpec, }; use super::*; @@ -1248,8 +1251,14 @@ mod tests { let initdatadevs = get_devices(initdatapaths).unwrap(); let initcachedevs = get_devices(initcachepaths).unwrap(); - let mut backstore = - Backstore::initialize(pool_uuid, initdatadevs, MDADataSize::default(), None).unwrap(); + let mut backstore = Backstore::initialize( + pool_uuid, + initdatadevs, + MDADataSize::default(), + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); invariant(&backstore); @@ -1340,8 +1349,14 @@ mod tests { let devices1 = get_devices(paths1).unwrap(); let devices2 = get_devices(paths2).unwrap(); - let mut backstore = - Backstore::initialize(pool_uuid, devices1, MDADataSize::default(), None).unwrap(); + let mut backstore = Backstore::initialize( + pool_uuid, + devices1, + MDADataSize::default(), + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); for path in paths1 { assert_eq!( @@ -1404,6 +1419,7 @@ mod tests { "tang".to_string(), json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), ))), + ValidatedIntegritySpec::default(), ) .unwrap(); backstore.alloc(pool_uuid, &[Sectors(512)]).unwrap(); @@ -1478,6 +1494,7 @@ mod tests { json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), ), )), + ValidatedIntegritySpec::default(), ).unwrap(); cmd::udev_settle().unwrap(); diff --git a/src/engine/strat_engine/backstore/blockdev/mod.rs b/src/engine/strat_engine/backstore/blockdev/mod.rs index 986a2a8a2b..9fa01c99dc 100644 --- a/src/engine/strat_engine/backstore/blockdev/mod.rs +++ b/src/engine/strat_engine/backstore/blockdev/mod.rs @@ -95,17 +95,6 @@ pub trait InternalBlockDev { /// * Otherwise, `Some(_)` fn calc_new_size(&self) -> StratisResult>; - /// Grow the block device if the underlying physical device has grown in size. - /// Return an error and leave the size as is if the device has shrunk. - /// Do nothing if the device is the same size as recorded in the metadata. - /// - /// This method does not need to block IO to the extended crypt device prior - /// to rollback because of per-pool locking. Growing the device will acquire - /// an exclusive lock on the pool and therefore the thin pool cannot be - /// extended to use the larger or unencrypted block device size until the - /// transaction has been completed successfully. - fn grow(&mut self) -> StratisResult; - /// Load the pool-level metadata for the given block device. fn load_state(&self) -> StratisResult, &DateTime)>>; diff --git a/src/engine/strat_engine/backstore/blockdev/v1.rs b/src/engine/strat_engine/backstore/blockdev/v1.rs index f5002cd952..dd628fca0f 100644 --- a/src/engine/strat_engine/backstore/blockdev/v1.rs +++ b/src/engine/strat_engine/backstore/blockdev/v1.rs @@ -336,72 +336,16 @@ impl StratBlockDev { } } - #[cfg(test)] - pub fn invariant(&self) { - assert!(self.total_size() == self.used.size()); - } -} - -impl InternalBlockDev for StratBlockDev { - fn uuid(&self) -> DevUuid { - self.bda.dev_uuid() - } - - fn device(&self) -> &Device { - &self.dev - } - - fn physical_path(&self) -> &Path { - self.devnode() - } - - fn blksizes(&self) -> StratSectorSizes { - self.blksizes - } - - fn metadata_version(&self) -> StratSigblockVersion { - self.bda.sigblock_version() - } - - fn total_size(&self) -> BlockdevSize { - self.bda.dev_size() - } - - fn available(&self) -> Sectors { - self.used.available() - } - - fn metadata_size(&self) -> Sectors { - self.bda.extended_size().sectors() - } - - fn max_stratis_metadata_size(&self) -> MDADataSize { - self.bda.max_data_size() - } - - fn in_use(&self) -> bool { - self.used.used() > self.metadata_size() - } - - fn alloc(&mut self, size: Sectors) -> PerDevSegments { - self.used.alloc_front(size) - } - - fn calc_new_size(&self) -> StratisResult> { - let s = Self::scan_blkdev_size( - self.physical_path(), - self.underlying_device.crypt_handle().is_some(), - )?; - if Some(s) == self.new_size - || (self.new_size.is_none() && s == self.bda.dev_size().sectors()) - { - Ok(None) - } else { - Ok(Some(s)) - } - } - - fn grow(&mut self) -> StratisResult { + /// Grow the block device if the underlying physical device has grown in size. + /// Return an error and leave the size as is if the device has shrunk. + /// Do nothing if the device is the same size as recorded in the metadata. + /// + /// This method does not need to block IO to the extended crypt device prior + /// to rollback because of per-pool locking. Growing the device will acquire + /// an exclusive lock on the pool and therefore the thin pool cannot be + /// extended to use the larger or unencrypted block device size until the + /// transaction has been completed successfully. + pub fn grow(&mut self) -> StratisResult { /// Precondition: size > h.blkdev_size fn needs_rollback(bd: &mut StratBlockDev, size: BlockdevSize) -> StratisResult<()> { let mut f = OpenOptions::new() @@ -472,6 +416,70 @@ impl InternalBlockDev for StratBlockDev { } } } + #[cfg(test)] + pub fn invariant(&self) { + assert!(self.total_size() == self.used.size()); + } +} + +impl InternalBlockDev for StratBlockDev { + fn uuid(&self) -> DevUuid { + self.bda.dev_uuid() + } + + fn device(&self) -> &Device { + &self.dev + } + + fn physical_path(&self) -> &Path { + self.devnode() + } + + fn blksizes(&self) -> StratSectorSizes { + self.blksizes + } + + fn metadata_version(&self) -> StratSigblockVersion { + self.bda.sigblock_version() + } + + fn total_size(&self) -> BlockdevSize { + self.bda.dev_size() + } + + fn available(&self) -> Sectors { + self.used.available() + } + + fn metadata_size(&self) -> Sectors { + self.bda.extended_size().sectors() + } + + fn max_stratis_metadata_size(&self) -> MDADataSize { + self.bda.max_data_size() + } + + fn in_use(&self) -> bool { + self.used.used() > self.metadata_size() + } + + fn alloc(&mut self, size: Sectors) -> PerDevSegments { + self.used.alloc_front(size) + } + + fn calc_new_size(&self) -> StratisResult> { + let s = Self::scan_blkdev_size( + self.physical_path(), + self.underlying_device.crypt_handle().is_some(), + )?; + if Some(s) == self.new_size + || (self.new_size.is_none() && s == self.bda.dev_size().sectors()) + { + Ok(None) + } else { + Ok(Some(s)) + } + } fn load_state(&self) -> StratisResult, &DateTime)>> { let mut f = OpenOptions::new() diff --git a/src/engine/strat_engine/backstore/blockdev/v2.rs b/src/engine/strat_engine/backstore/blockdev/v2.rs index 4c872b0cb6..6414863eb5 100644 --- a/src/engine/strat_engine/backstore/blockdev/v2.rs +++ b/src/engine/strat_engine/backstore/blockdev/v2.rs @@ -14,7 +14,7 @@ use std::{ use chrono::{DateTime, Utc}; use serde_json::Value; -use devicemapper::{Bytes, Device, Sectors, IEC}; +use devicemapper::{Bytes, Device, Sectors}; use crate::{ engine::{ @@ -35,7 +35,7 @@ use crate::{ }, types::{ Compare, DevUuid, DevicePath, Diff, PoolUuid, StateDiff, StratBlockDevDiff, - StratSigblockVersion, + StratSigblockVersion, ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -43,14 +43,28 @@ use crate::{ /// Return the amount of space required for integrity for a device of the given size. /// -/// This is a slight overestimation for the sake of simplicity. Because it uses the whole disk +/// This default is a slight overestimation for the sake of simplicity. Because it uses the whole disk /// size, once the integrity metadata size is calculated, the remaining data size is now smaller /// than the metadata region could support for integrity. /// The result is divisible by 8 sectors. -pub fn integrity_meta_space(total_space: Sectors) -> Sectors { - Bytes(4096).sectors() - + Bytes::from(64 * IEC::Mi).sectors() - + Bytes::from((*total_space * 32u64 + 4095) & !4095).sectors() +pub fn integrity_meta_space( + total_space: Sectors, + integrity_spec: ValidatedIntegritySpec, +) -> Sectors { + (if integrity_spec.allocate_superblock { + Bytes(4096) + } else { + Bytes(0) + }) + .sectors() + + integrity_spec.journal_size + + Bytes::from( + (*((total_space.bytes() / integrity_spec.block_size) + * integrity_spec.tag_spec.as_bytes_ceil()) + + 4095) + & !4095, + ) + .sectors() } #[derive(Debug)] @@ -206,6 +220,52 @@ impl StratBlockDev { } } + /// Grow the block device if the underlying physical device has grown in size. + /// Return an error and leave the size as is if the device has shrunk. + /// Do nothing if the device is the same size as recorded in the metadata. + /// + /// This will also extend integrity metadata reservations according to the new + /// size of the device. + pub fn grow(&mut self, integrity_spec: ValidatedIntegritySpec) -> StratisResult { + let size = BlockdevSize::new(Self::scan_blkdev_size(self.devnode())?); + let metadata_size = self.bda.dev_size(); + match size.cmp(&metadata_size) { + Ordering::Less => Err(StratisError::Msg( + "The underlying device appears to have shrunk; you may experience data loss" + .to_string(), + )), + Ordering::Equal => Ok(false), + Ordering::Greater => { + let mut f = OpenOptions::new() + .write(true) + .read(true) + .open(self.devnode())?; + let mut h = static_header(&mut f)?.ok_or_else(|| { + StratisError::Msg(format!( + "No static header found on device {}", + self.devnode().display() + )) + })?; + + h.blkdev_size = size; + let h = StaticHeader::write_header(&mut f, h, MetadataLocation::Both)?; + + self.bda.header = h; + self.used.increase_size(size.sectors()); + + let integrity_grow = integrity_meta_space(size.sectors(), integrity_spec) + - self + .integrity_meta_allocs + .iter() + .map(|(_, len)| *len) + .sum::(); + self.alloc_int_meta_back(integrity_grow); + + Ok(true) + } + } + } + #[cfg(test)] pub fn invariant(&self) { assert!(self.total_size() == self.used.size()); @@ -269,46 +329,6 @@ impl InternalBlockDev for StratBlockDev { } } - fn grow(&mut self) -> StratisResult { - let size = BlockdevSize::new(Self::scan_blkdev_size(self.devnode())?); - let metadata_size = self.bda.dev_size(); - match size.cmp(&metadata_size) { - Ordering::Less => Err(StratisError::Msg( - "The underlying device appears to have shrunk; you may experience data loss" - .to_string(), - )), - Ordering::Equal => Ok(false), - Ordering::Greater => { - let mut f = OpenOptions::new() - .write(true) - .read(true) - .open(self.devnode())?; - let mut h = static_header(&mut f)?.ok_or_else(|| { - StratisError::Msg(format!( - "No static header found on device {}", - self.devnode().display() - )) - })?; - - h.blkdev_size = size; - let h = StaticHeader::write_header(&mut f, h, MetadataLocation::Both)?; - - self.bda.header = h; - self.used.increase_size(size.sectors()); - - let integrity_grow = integrity_meta_space(size.sectors()) - - self - .integrity_meta_allocs - .iter() - .map(|(_, len)| *len) - .sum::(); - self.alloc_int_meta_back(integrity_grow); - - Ok(true) - } - } - } - fn load_state(&self) -> StratisResult, &DateTime)>> { let mut f = OpenOptions::new().read(true).open(&*self.devnode)?; match (self.bda.load_state(&mut f)?, self.bda.last_update_time()) { diff --git a/src/engine/strat_engine/backstore/blockdevmgr.rs b/src/engine/strat_engine/backstore/blockdevmgr.rs index 04525081c3..136371397b 100644 --- a/src/engine/strat_engine/backstore/blockdevmgr.rs +++ b/src/engine/strat_engine/backstore/blockdevmgr.rs @@ -29,7 +29,9 @@ use crate::{ serde_structs::{BaseBlockDevSave, Recordable}, shared::bds_to_bdas, }, - types::{DevUuid, EncryptionInfo, Name, PoolEncryptionInfo, PoolUuid}, + types::{ + DevUuid, EncryptionInfo, Name, PoolEncryptionInfo, PoolUuid, ValidatedIntegritySpec, + }, }, stratis::{StratisError, StratisResult}, }; @@ -165,6 +167,15 @@ impl BlockDevMgr { self.encryption_info().is_some() } + pub fn grow(&mut self, dev: DevUuid) -> StratisResult { + let bd = self + .block_devs + .iter_mut() + .find(|bd| bd.uuid() == dev) + .ok_or_else(|| StratisError::Msg(format!("Block device with UUID {dev} not found")))?; + bd.grow() + } + #[cfg(test)] fn invariant(&self) { let pool_uuids = self @@ -234,6 +245,19 @@ impl BlockDevMgr { Ok(bdev_uuids) } + pub fn grow( + &mut self, + dev: DevUuid, + integrity_spec: ValidatedIntegritySpec, + ) -> StratisResult { + let bd = self + .block_devs + .iter_mut() + .find(|bd| bd.uuid() == dev) + .ok_or_else(|| StratisError::Msg(format!("Block device with UUID {dev} not found")))?; + bd.grow(integrity_spec) + } + #[cfg(test)] fn invariant(&self) { let pool_uuids = self @@ -467,15 +491,6 @@ where self.block_devs.iter().map(|bd| bd.metadata_size()).sum() } - pub fn grow(&mut self, dev: DevUuid) -> StratisResult { - let bd = self - .block_devs - .iter_mut() - .find(|bd| bd.uuid() == dev) - .ok_or_else(|| StratisError::Msg(format!("Block device with UUID {dev} not found")))?; - bd.grow() - } - /// Tear down devicemapper devices for the block devices in this BlockDevMgr. pub fn teardown(&mut self) -> StratisResult<()> { let errs = self.block_devs.iter_mut().fold(Vec::new(), |mut errs, bd| { diff --git a/src/engine/strat_engine/backstore/data_tier.rs b/src/engine/strat_engine/backstore/data_tier.rs index 04fd6fc9b5..cb24d3e65d 100644 --- a/src/engine/strat_engine/backstore/data_tier.rs +++ b/src/engine/strat_engine/backstore/data_tier.rs @@ -27,7 +27,7 @@ use crate::{ }, types::BDARecordResult, }, - types::{BlockDevTier, DevUuid, Name, PoolUuid}, + types::{BlockDevTier, DevUuid, Name, PoolUuid, ValidatedIntegritySpec}, }, stratis::StratisResult, }; @@ -39,6 +39,8 @@ pub struct DataTier { pub(super) block_mgr: BlockDevMgr, /// The list of segments granted by block_mgr and used by dm_device pub(super) segments: AllocatedAbove, + /// Integrity spec + integrity_spec: Option, } impl DataTier { @@ -52,6 +54,7 @@ impl DataTier { DataTier { block_mgr, segments: AllocatedAbove { inner: vec![] }, + integrity_spec: None, } } @@ -97,6 +100,10 @@ impl DataTier { pub fn blockdevs_mut(&mut self) -> Vec<(DevUuid, &mut v1::StratBlockDev)> { self.block_mgr.blockdevs_mut() } + + pub fn grow(&mut self, dev: DevUuid) -> StratisResult { + self.block_mgr.grow(dev) + } } impl DataTier { @@ -105,16 +112,23 @@ impl DataTier { /// Initially 0 segments are allocated. /// /// WARNING: metadata changing event - pub fn new(mut block_mgr: BlockDevMgr) -> DataTier { + pub fn new( + mut block_mgr: BlockDevMgr, + integrity_spec: ValidatedIntegritySpec, + ) -> DataTier { for (_, bd) in block_mgr.blockdevs_mut() { // NOTE: over-allocates integrity metadata slightly. Some of the // total size of the device will not make use of the integrity // metadata. - bd.alloc_int_meta_back(integrity_meta_space(bd.total_size().sectors())); + bd.alloc_int_meta_back(integrity_meta_space( + bd.total_size().sectors(), + integrity_spec, + )); } DataTier { block_mgr, segments: AllocatedAbove { inner: vec![] }, + integrity_spec: Some(integrity_spec), } } @@ -142,10 +156,8 @@ impl DataTier { assert_eq!(bds.len(), uuids.len()); for bd in bds { bd.alloc_int_meta_back(integrity_meta_space( - // NOTE: Subtracting metadata size works here because the only metadata currently - // recorded in a newly created block device is the BDA. If this becomes untrue in - // the future, this code will no longer work. - bd.total_size().sectors() - bd.metadata_size(), + bd.total_size().sectors(), + self.integrity_spec.expect("Must be some in V2"), )); } Ok(uuids) @@ -179,6 +191,11 @@ impl DataTier { pub fn blockdevs_mut(&mut self) -> Vec<(DevUuid, &mut v2::StratBlockDev)> { self.block_mgr.blockdevs_mut() } + + pub fn grow(&mut self, dev: DevUuid) -> StratisResult { + self.block_mgr + .grow(dev, self.integrity_spec.expect("Must be Some in V2")) + } } impl DataTier @@ -207,6 +224,7 @@ where Ok(DataTier { block_mgr, segments, + integrity_spec: data_tier_save.integrity_spec, }) } @@ -265,10 +283,6 @@ where self.block_mgr.load_state() } - pub fn grow(&mut self, dev: DevUuid) -> StratisResult { - self.block_mgr.grow(dev) - } - /// Return the partition of the block devs that are in use and those /// that are not. pub fn partition_by_use(&self) -> BlockDevPartition<'_, B> { @@ -301,6 +315,7 @@ where allocs: vec![self.segments.record()], devs: self.block_mgr.record(), }, + integrity_spec: self.integrity_spec, } } } @@ -439,7 +454,10 @@ mod tests { ) .unwrap(); - let mut data_tier = DataTier::::new(mgr); + let mut data_tier = DataTier::::new( + mgr, + ValidatedIntegritySpec::default(), + ); data_tier.invariant(); // A data_tier w/ some devices but nothing allocated diff --git a/src/engine/strat_engine/engine.rs b/src/engine/strat_engine/engine.rs index ed7d5f40b3..1de9e4184f 100644 --- a/src/engine/strat_engine/engine.rs +++ b/src/engine/strat_engine/engine.rs @@ -37,9 +37,10 @@ use crate::{ SomeLockWriteGuard, Table, }, types::{ - CreateAction, DeleteAction, DevUuid, EncryptionInfo, FilesystemUuid, LockedPoolsInfo, - PoolDiff, PoolIdentifier, RenameAction, ReportType, SetUnlockAction, StartAction, - StopAction, StoppedPoolsInfo, StratFilesystemDiff, UdevEngineEvent, UnlockMethod, + CreateAction, DeleteAction, DevUuid, EncryptionInfo, FilesystemUuid, IntegritySpec, + LockedPoolsInfo, PoolDiff, PoolIdentifier, RenameAction, ReportType, SetUnlockAction, + StartAction, StopAction, StoppedPoolsInfo, StratFilesystemDiff, UdevEngineEvent, + UnlockMethod, ValidatedIntegritySpec, }, Engine, Name, Pool, PoolUuid, Report, }, @@ -494,9 +495,11 @@ impl Engine for StratEngine { name: &str, blockdev_paths: &[&Path], encryption_info: Option<&EncryptionInfo>, + integrity_spec: IntegritySpec, ) -> StratisResult> { validate_name(name)?; let name = Name::new(name.to_owned()); + let integrity_spec = ValidatedIntegritySpec::try_from(integrity_spec)?; validate_paths(blockdev_paths)?; @@ -558,6 +561,7 @@ impl Engine for StratEngine { &cloned_name, unowned_devices, cloned_enc_info.as_ref(), + integrity_spec, ) })??; pools.insert(Name::new(name.to_string()), pool_uuid, AnyPool::V2(pool)); @@ -911,7 +915,7 @@ mod test { let engine = StratEngine::initialize().unwrap(); let name1 = "name1"; - let uuid1 = test_async!(engine.create_pool(name1, paths, None)) + let uuid1 = test_async!(engine.create_pool(name1, paths, None, IntegritySpec::default())) .unwrap() .changed() .unwrap(); @@ -1034,13 +1038,13 @@ mod test { let engine = StratEngine::initialize().unwrap(); let name1 = "name1"; - let uuid1 = test_async!(engine.create_pool(name1, paths1, None)) + let uuid1 = test_async!(engine.create_pool(name1, paths1, None, IntegritySpec::default())) .unwrap() .changed() .unwrap(); let name2 = "name2"; - let uuid2 = test_async!(engine.create_pool(name2, paths2, None)) + let uuid2 = test_async!(engine.create_pool(name2, paths2, None, IntegritySpec::default())) .unwrap() .changed() .unwrap(); @@ -1500,7 +1504,7 @@ mod test { fn test_start_stop(paths: &[&Path]) { let engine = StratEngine::initialize().unwrap(); let name = "pool_name"; - let uuid = test_async!(engine.create_pool(name, paths, None)) + let uuid = test_async!(engine.create_pool(name, paths, None, IntegritySpec::default())) .unwrap() .changed() .unwrap(); diff --git a/src/engine/strat_engine/pool/inspection.rs b/src/engine/strat_engine/pool/inspection.rs index ab2bfd6911..0cbe203d34 100644 --- a/src/engine/strat_engine/pool/inspection.rs +++ b/src/engine/strat_engine/pool/inspection.rs @@ -10,7 +10,10 @@ use std::{ use devicemapper::Sectors; use crate::{ - engine::{strat_engine::serde_structs::PoolSave, types::DevUuid}, + engine::{ + strat_engine::serde_structs::PoolSave, + types::{DevUuid, IntegrityTagSpec, ValidatedIntegritySpec}, + }, stratis::{StratisError, StratisResult}, }; @@ -270,10 +273,31 @@ impl DataDevice { errors } - fn check(&self) -> Vec { + fn _check_integrity(&self, integrity_spec: Option) -> Vec { + if let Some(integrity_spec) = integrity_spec { + if !integrity_spec.allocate_superblock + && integrity_spec.journal_size == Sectors(0) + && integrity_spec.tag_spec == IntegrityTagSpec::B0 + && self.sum(&[DataDeviceUse::IntegrityMetadata]) > Sectors(0) + { + vec![ + format!( + "Integrity specification should resolve to 0 allocations for integrity, but data device has space allocated for integrity." + ) + ] + } else { + vec![] + } + } else { + vec![] + } + } + + fn check(&self, integrity_spec: Option) -> Vec { let mut errors = Vec::new(); errors.extend(check_overlap(&self.extents, self.offset())); errors.extend(self._check_integrity_meta_round()); + errors.extend(self._check_integrity(integrity_spec)); errors } } @@ -535,7 +559,9 @@ impl fmt::Display for FlexDevice { } // Calculate map of device UUIDs to data device representation from metadata. -fn data_devices(metadata: &PoolSave) -> StratisResult> { +fn data_devices( + metadata: &PoolSave, +) -> StratisResult<(HashMap, Option)> { let data_tier_metadata = &metadata.backstore.data_tier; let data_tier_devs = &data_tier_metadata.blockdev.devs; @@ -570,7 +596,7 @@ fn data_devices(metadata: &PoolSave) -> StratisResult, + integrity_spec: ValidatedIntegritySpec, ) -> StratisResult<(PoolUuid, StratPool)> { let pool_uuid = PoolUuid::new_v4(); // FIXME: Initializing with the minimum MDA size is not necessarily // enough. If there are enough devices specified, more space will be // required. - let mut backstore = - Backstore::initialize(pool_uuid, devices, MDADataSize::default(), encryption_info)?; + let mut backstore = Backstore::initialize( + pool_uuid, + devices, + MDADataSize::default(), + encryption_info, + integrity_spec, + )?; let thinpool = ThinPool::::new( pool_uuid, @@ -1257,7 +1263,7 @@ mod tests { tests::{loopbacked, real}, thinpool::ThinPoolStatusDigest, }, - types::{EngineAction, PoolIdentifier}, + types::{EngineAction, IntegritySpec, PoolIdentifier}, Engine, StratEngine, }; @@ -1300,7 +1306,13 @@ mod tests { stratis_devices.error_on_not_empty().unwrap(); let name = "stratis-test-pool"; - let (uuid, mut pool) = StratPool::initialize(name, unowned_devices2, None).unwrap(); + let (uuid, mut pool) = StratPool::initialize( + name, + unowned_devices2, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); invariant(&pool, name); let metadata1 = pool.record(name); @@ -1388,7 +1400,13 @@ mod tests { stratis_devices.error_on_not_empty().unwrap(); let name = "stratis-test-pool"; - let (uuid, mut pool) = StratPool::initialize(name, unowned_devices, None).unwrap(); + let (uuid, mut pool) = StratPool::initialize( + name, + unowned_devices, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); invariant(&pool, name); pool.init_cache(uuid, name, cache_path, true).unwrap(); @@ -1428,7 +1446,13 @@ mod tests { stratis_devices.error_on_not_empty().unwrap(); let name = "stratis-test-pool"; - let (pool_uuid, mut pool) = StratPool::initialize(name, unowned_devices1, None).unwrap(); + let (pool_uuid, mut pool) = StratPool::initialize( + name, + unowned_devices1, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); invariant(&pool, name); let fs_name = "stratis_test_filesystem"; @@ -1512,7 +1536,13 @@ mod tests { let (stratis_devices, unowned_devices) = devices.unpack(); stratis_devices.error_on_not_empty().unwrap(); - let (uuid, mut pool) = StratPool::initialize(name, unowned_devices, None).unwrap(); + let (uuid, mut pool) = StratPool::initialize( + name, + unowned_devices, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); invariant(&pool, name); assert_eq!(pool.action_avail, ActionAvailability::Full); @@ -1528,7 +1558,13 @@ mod tests { let (stratis_devices, unowned_devices) = devices.unpack(); stratis_devices.error_on_not_empty().unwrap(); - let (_, mut pool) = StratPool::initialize(name, unowned_devices, None).unwrap(); + let (_, mut pool) = StratPool::initialize( + name, + unowned_devices, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); invariant(&pool, name); assert_eq!(pool.action_avail, ActionAvailability::Full); @@ -1567,8 +1603,13 @@ mod tests { let (stratis_devices, unowned_devices) = devices.unpack(); stratis_devices.error_on_not_empty().unwrap(); - let (pool_uuid, mut pool) = - StratPool::initialize(pool_name, unowned_devices, None).unwrap(); + let (pool_uuid, mut pool) = StratPool::initialize( + pool_name, + unowned_devices, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); let (_, fs_uuid, _) = pool .create_filesystems( @@ -1678,10 +1719,11 @@ mod tests { fn test_grow_physical_pre_grow(paths: &[&Path]) { let pool_name = Name::new("pool".to_string()); let engine = StratEngine::initialize().unwrap(); - let pool_uuid = test_async!(engine.create_pool(&pool_name, paths, None)) - .unwrap() - .changed() - .unwrap(); + let pool_uuid = + test_async!(engine.create_pool(&pool_name, paths, None, IntegritySpec::default())) + .unwrap() + .changed() + .unwrap(); let mut guard = test_async!(engine.get_mut_pool(PoolIdentifier::Uuid(pool_uuid))).unwrap(); let (_, _, pool) = guard.as_mut_tuple(); diff --git a/src/engine/strat_engine/serde_structs.rs b/src/engine/strat_engine/serde_structs.rs index a9c907139d..478bbd0727 100644 --- a/src/engine/strat_engine/serde_structs.rs +++ b/src/engine/strat_engine/serde_structs.rs @@ -16,7 +16,7 @@ use serde::{Serialize, Serializer}; use devicemapper::{Sectors, ThinDevId}; -use crate::engine::types::{DevUuid, Features, FilesystemUuid}; +use crate::engine::types::{DevUuid, Features, FilesystemUuid, ValidatedIntegritySpec}; const MAXIMUM_STRING_SIZE: usize = 255; @@ -117,6 +117,8 @@ pub struct BackstoreSave { #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DataTierSave { pub blockdev: BlockDevSave, + #[serde(skip_serializing_if = "Option::is_none")] + pub integrity_spec: Option, } #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/src/engine/strat_engine/thinpool/thinpool.rs b/src/engine/strat_engine/thinpool/thinpool.rs index aea8df8a71..b7f2356986 100644 --- a/src/engine/strat_engine/thinpool/thinpool.rs +++ b/src/engine/strat_engine/thinpool/thinpool.rs @@ -2112,6 +2112,7 @@ mod tests { tests::{loopbacked, real}, writing::SyncAll, }, + types::ValidatedIntegritySpec, }; use super::*; @@ -3157,6 +3158,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let size = ThinPoolSizeParams::new(backstore.datatier_usable_size()).unwrap(); @@ -3259,6 +3261,7 @@ mod tests { first_devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -3394,6 +3397,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); warn!("Available: {}", backstore.available_in_backstore()); @@ -3519,6 +3523,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -3588,6 +3593,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -3667,6 +3673,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -3733,6 +3740,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -3794,6 +3802,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -3900,6 +3909,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( @@ -4038,6 +4048,7 @@ mod tests { devices, MDADataSize::default(), None, + ValidatedIntegritySpec::default(), ) .unwrap(); let mut pool = ThinPool::::new( diff --git a/src/engine/types/mod.rs b/src/engine/types/mod.rs index 287400c496..80e1f93e54 100644 --- a/src/engine/types/mod.rs +++ b/src/engine/types/mod.rs @@ -16,9 +16,11 @@ use std::{ use libudev::EventType; use serde::{Deserialize, Serialize}; use serde_json::Value; -use strum_macros::{self, EnumString, FromRepr, VariantNames}; +use strum_macros::{self, AsRefStr, EnumString, FromRepr, VariantNames}; use uuid::Uuid; +use devicemapper::{Bytes, Sectors, IEC}; + pub use crate::engine::{ engine::StateDiff, structures::Lockable, @@ -37,6 +39,10 @@ pub use crate::engine::{ }; use crate::stratis::{StratisError, StratisResult}; +pub const DEFAULT_INTEGRITY_JOURNAL_SIZE: Bytes = Bytes(128 * IEC::Mi as u128); +pub const DEFAULT_INTEGRITY_BLOCK_SIZE: Bytes = Bytes(4 * IEC::Ki as u128); +pub const DEFAULT_INTEGRITY_TAG_SPEC: IntegrityTagSpec = IntegrityTagSpec::B512; + mod actions; mod diff; mod keys; @@ -481,3 +487,97 @@ pub enum StratSigblockVersion { V1 = 1, V2 = 2, } + +/// A way to specify an integrity tag size. It is possible for the specification +/// to be non-numeric but translatable to some number of bits. +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + Hash, + Serialize, + Deserialize, + VariantNames, + EnumString, + AsRefStr, +)] +pub enum IntegrityTagSpec { + #[strum(serialize = "0b")] + #[serde(rename = "0b")] + B0, + #[strum(serialize = "32b")] + #[serde(rename = "32b")] + B32, + #[strum(serialize = "512b")] + #[serde(rename = "512b")] + B512, +} + +impl IntegrityTagSpec { + /// The smallest number of bytes containing the bits represented. + pub fn as_bytes_ceil(self) -> Bytes { + match self { + IntegrityTagSpec::B0 => Bytes(0), + IntegrityTagSpec::B32 => Bytes(4), + IntegrityTagSpec::B512 => Bytes(64), + } + } +} + +#[derive(Default)] +pub struct IntegritySpec { + pub tag_spec: Option, + pub journal_size: Option, + pub allocate_superblock: Option, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct ValidatedIntegritySpec { + pub tag_spec: IntegrityTagSpec, + pub journal_size: Sectors, + pub block_size: Bytes, + pub allocate_superblock: bool, +} + +impl Default for ValidatedIntegritySpec { + fn default() -> Self { + ValidatedIntegritySpec::try_from(IntegritySpec::default()).expect("default is valid") + } +} + +impl TryFrom for ValidatedIntegritySpec { + type Error = StratisError; + + fn try_from(spec: IntegritySpec) -> StratisResult { + let journal_size = match spec.journal_size { + Some(journal_size) => { + if journal_size % 4096u64 != Bytes(0) { + return Err(StratisError::Msg(format!( + "specified integrity journal size {journal_size} is not a multiple of 4096" + ))); + } else { + journal_size.sectors() + } + } + None => DEFAULT_INTEGRITY_JOURNAL_SIZE.sectors(), + }; + + Ok(ValidatedIntegritySpec { + journal_size, + tag_spec: spec.tag_spec.unwrap_or(DEFAULT_INTEGRITY_TAG_SPEC), + block_size: DEFAULT_INTEGRITY_BLOCK_SIZE, + allocate_superblock: spec.allocate_superblock.unwrap_or(true), + }) + } +} + +impl Display for ValidatedIntegritySpec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Allocate Superblock: {}", self.allocate_superblock)?; + writeln!(f, "Tag Specification: {}", self.tag_spec.as_ref())?; + writeln!(f, "Journal Size: {}", self.journal_size)?; + writeln!(f, "Block Size: {}", self.block_size) + } +} diff --git a/src/jsonrpc/server/pool.rs b/src/jsonrpc/server/pool.rs index 104c9459ae..3b1d584591 100644 --- a/src/jsonrpc/server/pool.rs +++ b/src/jsonrpc/server/pool.rs @@ -10,7 +10,7 @@ use tokio::task::block_in_place; use crate::{ engine::{ BlockDevTier, CreateAction, DeleteAction, EncryptionInfo, Engine, EngineAction, - KeyDescription, Name, PoolIdentifier, PoolUuid, RenameAction, UnlockMethod, + IntegritySpec, KeyDescription, Name, PoolIdentifier, PoolUuid, RenameAction, UnlockMethod, }, jsonrpc::interface::PoolListType, stratis::{StratisError, StratisResult}, @@ -45,7 +45,10 @@ pub async fn pool_create<'a>( enc_info: Option<&'a EncryptionInfo>, ) -> StratisResult { Ok( - match engine.create_pool(name, blockdev_paths, enc_info).await? { + match engine + .create_pool(name, blockdev_paths, enc_info, IntegritySpec::default()) + .await? + { CreateAction::Created(_) => true, CreateAction::Identity => false, }, diff --git a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py index 0f520d465e..9920f2e29d 100644 --- a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py +++ b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py @@ -13,6 +13,9 @@ + + + diff --git a/tests/client-dbus/tests/udev/_utils.py b/tests/client-dbus/tests/udev/_utils.py index 1fc9aa589e..40c4fcd935 100644 --- a/tests/client-dbus/tests/udev/_utils.py +++ b/tests/client-dbus/tests/udev/_utils.py @@ -150,6 +150,9 @@ def create_v2_pool(): "clevis_info": ( (False, ("", "")) if clevis_arg is None else (True, clevis_arg) ), + "journal_size": (False, 0), + "tag_spec": (False, ""), + "allocate_superblock": (False, False), }, )