diff --git a/src/bin/stratis-min/stratis-min.rs b/src/bin/stratis-min/stratis-min.rs index 050eee8fe9..fa13a63d25 100644 --- a/src/bin/stratis-min/stratis-min.rs +++ b/src/bin/stratis-min/stratis-min.rs @@ -181,6 +181,9 @@ fn parse_args() -> Command { .arg(Arg::new("pool_name").required(true)) .arg(Arg::new("fs_name").required(true)) .arg(Arg::new("new_fs_name").required(true)), + Command::new("origin") + .arg(Arg::new("pool_name").required(true)) + .arg(Arg::new("fs_name").required(true)), ]), Command::new("report"), ]) @@ -576,6 +579,19 @@ fn main() -> Result<(), String> { .to_owned(), )?; Ok(()) + } else if let Some(args) = subcommand.subcommand_matches("origin") { + filesystem::filesystem_origin( + args.get_one::("pool_name") + .expect("required") + .to_owned(), + args.get_one::("fs_name") + .expect("required") + .to_owned(), + ) + .map(|origin| { + println!("{}", origin); + })?; + Ok(()) } else { filesystem::filesystem_list()?; Ok(()) diff --git a/src/dbus_api/consts.rs b/src/dbus_api/consts.rs index ee28acf9be..6fe299ef21 100644 --- a/src/dbus_api/consts.rs +++ b/src/dbus_api/consts.rs @@ -65,6 +65,7 @@ pub const FILESYSTEM_POOL_PROP: &str = "Pool"; pub const FILESYSTEM_CREATED_PROP: &str = "Created"; pub const FILESYSTEM_SIZE_PROP: &str = "Size"; pub const FILESYSTEM_SIZE_LIMIT_PROP: &str = "SizeLimit"; +pub const FILESYSTEM_ORIGIN_PROP: &str = "Origin"; pub const BLOCKDEV_INTERFACE_NAME_3_0: &str = "org.storage.stratis3.blockdev.r0"; pub const BLOCKDEV_INTERFACE_NAME_3_1: &str = "org.storage.stratis3.blockdev.r1"; diff --git a/src/dbus_api/filesystem/filesystem_3_7/api.rs b/src/dbus_api/filesystem/filesystem_3_7/api.rs new file mode 100644 index 0000000000..b8864240e9 --- /dev/null +++ b/src/dbus_api/filesystem/filesystem_3_7/api.rs @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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_tree::{Access, EmitsChangedSignal, Factory, MTSync, Property}; + +use crate::dbus_api::{consts, filesystem::filesystem_3_7::props::get_fs_origin, types::TData}; + +pub fn origin_property(f: &Factory, TData>) -> Property, TData> { + f.property::<(bool, String), _>(consts::FILESYSTEM_ORIGIN_PROP, ()) + .access(Access::Read) + .emits_changed(EmitsChangedSignal::True) + .on_get(get_fs_origin) +} diff --git a/src/dbus_api/filesystem/filesystem_3_7/mod.rs b/src/dbus_api/filesystem/filesystem_3_7/mod.rs new file mode 100644 index 0000000000..4028a40d67 --- /dev/null +++ b/src/dbus_api/filesystem/filesystem_3_7/mod.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/. + +mod api; +mod props; + +pub use api::origin_property; diff --git a/src/dbus_api/filesystem/filesystem_3_7/props.rs b/src/dbus_api/filesystem/filesystem_3_7/props.rs new file mode 100644 index 0000000000..5fa35ee585 --- /dev/null +++ b/src/dbus_api/filesystem/filesystem_3_7/props.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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::IterAppend; +use dbus_tree::{MTSync, MethodErr, PropInfo}; + +use crate::dbus_api::{ + filesystem::shared::{self, get_filesystem_property}, + types::TData, +}; + +pub fn get_fs_origin( + i: &mut IterAppend<'_>, + p: &PropInfo<'_, MTSync, TData>, +) -> Result<(), MethodErr> { + get_filesystem_property(i, p, |(_, _, f)| Ok(shared::fs_origin_prop(f))) +} diff --git a/src/dbus_api/filesystem/mod.rs b/src/dbus_api/filesystem/mod.rs index a09427817b..095a436cea 100644 --- a/src/dbus_api/filesystem/mod.rs +++ b/src/dbus_api/filesystem/mod.rs @@ -15,6 +15,7 @@ use crate::{ mod filesystem_3_0; mod filesystem_3_6; +mod filesystem_3_7; pub mod prop_conv; mod shared; @@ -124,7 +125,8 @@ pub fn create_dbus_filesystem<'a>( .add_p(filesystem_3_0::created_property(&f)) .add_p(filesystem_3_0::size_property(&f)) .add_p(filesystem_3_0::used_property(&f)) - .add_p(filesystem_3_6::size_limit_property(&f)), + .add_p(filesystem_3_6::size_limit_property(&f)) + .add_p(filesystem_3_7::origin_property(&f)), ); let path = object_path.get_name().to_owned(); @@ -214,7 +216,8 @@ pub fn get_fs_properties( consts::FILESYSTEM_CREATED_PROP => shared::fs_created_prop(fs), consts::FILESYSTEM_SIZE_PROP => shared::fs_size_prop(fs), consts::FILESYSTEM_USED_PROP => shared::fs_used_prop(fs), - consts::FILESYSTEM_SIZE_LIMIT_PROP => shared::fs_size_limit_prop(fs) + consts::FILESYSTEM_SIZE_LIMIT_PROP => shared::fs_size_limit_prop(fs), + consts::FILESYSTEM_ORIGIN_PROP => shared::fs_origin_prop(fs) } } } diff --git a/src/dbus_api/filesystem/prop_conv.rs b/src/dbus_api/filesystem/prop_conv.rs index e64b458426..07b0f0e56d 100644 --- a/src/dbus_api/filesystem/prop_conv.rs +++ b/src/dbus_api/filesystem/prop_conv.rs @@ -4,7 +4,7 @@ use devicemapper::{Bytes, Sectors}; -use crate::dbus_api::util::option_to_tuple; +use crate::{dbus_api::util::option_to_tuple, engine::FilesystemUuid}; /// Generate D-Bus representation of filesystem size property. #[inline] @@ -23,3 +23,9 @@ pub fn fs_used_to_prop(used: Option) -> (bool, String) { pub fn fs_size_limit_to_prop(limit: Option) -> (bool, String) { option_to_tuple(limit.map(|u| (*u.bytes()).to_string()), String::new()) } + +/// Generate D-Bus representation of filesystem origin property. +#[inline] +pub fn fs_origin_to_prop(origin: Option) -> (bool, String) { + option_to_tuple(origin.map(|u| (uuid_to_string!(u))), String::new()) +} diff --git a/src/dbus_api/filesystem/shared.rs b/src/dbus_api/filesystem/shared.rs index f2520ecffb..8efd2d72df 100644 --- a/src/dbus_api/filesystem/shared.rs +++ b/src/dbus_api/filesystem/shared.rs @@ -202,3 +202,8 @@ pub fn fs_size_prop(fs: &dyn Filesystem) -> String { pub fn fs_used_prop(fs: &dyn Filesystem) -> (bool, String) { prop_conv::fs_used_to_prop(fs.used().ok()) } + +/// Generate D-Bus representation of origin property. +pub fn fs_origin_prop(fs: &dyn Filesystem) -> (bool, String) { + prop_conv::fs_origin_to_prop(fs.origin()) +} diff --git a/src/dbus_api/pool/mod.rs b/src/dbus_api/pool/mod.rs index 6c989db2ed..a00981371c 100644 --- a/src/dbus_api/pool/mod.rs +++ b/src/dbus_api/pool/mod.rs @@ -18,6 +18,7 @@ mod pool_3_1; mod pool_3_3; mod pool_3_5; mod pool_3_6; +mod pool_3_7; pub mod prop_conv; mod shared; @@ -245,7 +246,7 @@ pub fn create_dbus_pool<'a>( .add( f.interface(consts::POOL_INTERFACE_NAME_3_7, ()) .add_m(pool_3_6::create_filesystems_method(&f)) - .add_m(pool_3_0::destroy_filesystems_method(&f)) + .add_m(pool_3_7::destroy_filesystems_method(&f)) .add_m(pool_3_0::snapshot_filesystem_method(&f)) .add_m(pool_3_0::add_blockdevs_method(&f)) .add_m(pool_3_0::bind_clevis_method(&f)) diff --git a/src/dbus_api/pool/pool_3_0/methods.rs b/src/dbus_api/pool/pool_3_0/methods.rs index 3a4214335a..38d676ad75 100644 --- a/src/dbus_api/pool/pool_3_0/methods.rs +++ b/src/dbus_api/pool/pool_3_0/methods.rs @@ -177,7 +177,7 @@ pub fn destroy_filesystems(m: &MethodInfo<'_, MTSync, TData>) -> MethodRe Ok(uuids) => { // Only get changed values here as non-existent filesystems will have been filtered out // before calling destroy_filesystems - let uuid_vec: Vec = if let Some(ref changed_uuids) = uuids.changed() { + let uuid_vec: Vec = if let Some((ref changed_uuids, _)) = uuids.changed() { for uuid in changed_uuids { let op = filesystem_map .get(uuid) diff --git a/src/dbus_api/pool/pool_3_7/api.rs b/src/dbus_api/pool/pool_3_7/api.rs new file mode 100644 index 0000000000..178ca35a3a --- /dev/null +++ b/src/dbus_api/pool/pool_3_7/api.rs @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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_tree::{Factory, MTSync, Method}; + +use crate::dbus_api::{pool::pool_3_7::methods::destroy_filesystems, types::TData}; + +pub fn destroy_filesystems_method( + f: &Factory, TData>, +) -> Method, TData> { + f.method("DestroyFilesystems", (), destroy_filesystems) + .in_arg(("filesystems", "ao")) + // b: true if filesystems were destroyed + // as: Array of UUIDs of destroyed filesystems + // + // Rust representation: (bool, Vec) + .out_arg(("results", "(bas)")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} diff --git a/src/dbus_api/pool/pool_3_7/methods.rs b/src/dbus_api/pool/pool_3_7/methods.rs new file mode 100644 index 0000000000..87f24fa776 --- /dev/null +++ b/src/dbus_api/pool/pool_3_7/methods.rs @@ -0,0 +1,108 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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 std::collections::HashMap; + +use dbus::{arg::Array, Message}; +use dbus_tree::{MTSync, MethodInfo, MethodResult}; + +use crate::{ + dbus_api::{ + consts::filesystem_interface_list, + types::{DbusErrorEnum, TData, OK_STRING}, + util::{engine_to_dbus_err_tuple, get_next_arg}, + }, + engine::{EngineAction, FilesystemUuid, StratisUuid}, +}; + +pub fn destroy_filesystems(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + let mut iter = message.iter_init(); + + let filesystems: Array<'_, dbus::Path<'static>, _> = get_next_arg(&mut iter, 0)?; + + let dbus_context = m.tree.get_data(); + let object_path = m.path.get_name(); + let return_message = message.method_return(); + let default_return: (bool, Vec) = (false, Vec::new()); + + let pool_path = m + .tree + .get(object_path) + .expect("implicit argument must be in tree"); + let pool_uuid = typed_uuid!( + get_data!(pool_path; default_return; return_message).uuid; + Pool; + default_return; + return_message + ); + + let mut guard = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let (pool_name, _, pool) = guard.as_mut_tuple(); + + let mut filesystem_map: HashMap> = HashMap::new(); + for path in filesystems { + if let Some((u, path)) = m.tree.get(&path).and_then(|op| { + op.get_data() + .as_ref() + .map(|d| (&d.uuid, op.get_name().clone())) + }) { + let uuid = *typed_uuid!(u; Fs; default_return; return_message); + filesystem_map.insert(uuid, path); + } + } + + let result = handle_action!( + pool.destroy_filesystems( + &pool_name, + &filesystem_map.keys().cloned().collect::>(), + ), + dbus_context, + pool_path.get_name() + ); + let msg = match result { + Ok(uuids) => { + // Only get changed values here as non-existent filesystems will have been filtered out + // before calling destroy_filesystems + let uuid_vec: Vec = + if let Some((ref changed_uuids, ref updated_uuids)) = uuids.changed() { + for uuid in changed_uuids { + let op = filesystem_map + .get(uuid) + .expect("'uuids' is a subset of filesystem_map.keys()"); + dbus_context.push_remove(op, filesystem_interface_list()); + } + + for sn_op in m.tree.iter().filter(|op| { + op.get_data() + .as_ref() + .map(|data| match data.uuid { + StratisUuid::Fs(uuid) => updated_uuids.contains(&uuid), + _ => false, + }) + .unwrap_or(false) + }) { + dbus_context.push_filesystem_origin_change(sn_op.get_name()); + } + + changed_uuids + .iter() + .map(|uuid| uuid_to_string!(uuid)) + .collect() + } else { + Vec::new() + }; + return_message.append3( + (true, uuid_vec), + DbusErrorEnum::OK as u16, + OK_STRING.to_string(), + ) + } + Err(err) => { + let (rc, rs) = engine_to_dbus_err_tuple(&err); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} diff --git a/src/dbus_api/pool/pool_3_7/mod.rs b/src/dbus_api/pool/pool_3_7/mod.rs new file mode 100644 index 0000000000..6e784a7ac7 --- /dev/null +++ b/src/dbus_api/pool/pool_3_7/mod.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/. + +mod api; +mod methods; + +pub use api::destroy_filesystems_method; diff --git a/src/dbus_api/tree.rs b/src/dbus_api/tree.rs index 2255d8df51..b44cd502d1 100644 --- a/src/dbus_api/tree.rs +++ b/src/dbus_api/tree.rs @@ -30,7 +30,9 @@ use crate::{ blockdev_user_info_to_prop, }, consts, - filesystem::prop_conv::{fs_size_limit_to_prop, fs_size_to_prop, fs_used_to_prop}, + filesystem::prop_conv::{ + fs_origin_to_prop, fs_size_limit_to_prop, fs_size_to_prop, fs_used_to_prop, + }, pool::prop_conv::{ avail_actions_to_prop, clevis_info_to_prop, key_desc_to_prop, pool_alloc_to_prop, pool_size_to_prop, pool_used_to_prop, @@ -201,6 +203,25 @@ impl DbusTreeHandler { } } + fn handle_fs_origin_change(&self, item: Path<'static>) { + let origin_prop = fs_origin_to_prop(None); + if self + .property_changed_invalidated_signal( + &item, + prop_hashmap! { + consts::FILESYSTEM_INTERFACE_NAME_3_7 => { + vec![], + consts::FILESYSTEM_ORIGIN_PROP.to_string() => + box_variant!(origin_prop.clone()) + } + }, + ) + .is_err() + { + warn!("Signal on filesystem origin change was not sent to the D-Bus client"); + } + } + /// Handle a pool name change in the engine. fn handle_pool_name_change( &self, @@ -1195,6 +1216,10 @@ impl DbusTreeHandler { self.handle_fs_name_change(item, new_name); Ok(true) } + DbusAction::FsOriginChange(item) => { + self.handle_fs_origin_change(item); + Ok(true) + } DbusAction::PoolNameChange(item, new_name) => { if let Some(read_lock) = poll_exit_and_future(self.should_exit.recv(), self.tree.read())? diff --git a/src/dbus_api/types.rs b/src/dbus_api/types.rs index 256834a99f..af345c152c 100644 --- a/src/dbus_api/types.rs +++ b/src/dbus_api/types.rs @@ -106,6 +106,7 @@ pub enum DbusAction { StoppedPoolsChange(StoppedPoolsInfo), BlockdevUserInfoChange(Path<'static>, Option), BlockdevTotalPhysicalSizeChange(Path<'static>, Sectors), + FsOriginChange(Path<'static>), FsSizeLimitChange(Path<'static>, Option), FsBackgroundChange( FilesystemUuid, @@ -487,6 +488,16 @@ impl DbusContext { ) } } + + /// Send changed signal for changed filesystem origin property. + pub fn push_filesystem_origin_change(&self, path: &Path<'static>) { + if let Err(e) = self.sender.send(DbusAction::FsOriginChange(path.clone())) { + warn!( + "Filesystem origin change event could not be sent to the processing thread; no signal will be sent out for the filesystem origin state change: {}", + e, + ) + } + } } #[derive(Debug)] diff --git a/src/engine/engine.rs b/src/engine/engine.rs index c6702c7f11..f875ef4698 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -93,6 +93,9 @@ pub trait Filesystem: Debug { /// Get filesystem size limit. fn size_limit(&self) -> Option; + + /// Get filesystem snapshot origin. + fn origin(&self) -> Option; } pub trait BlockDev: Debug { @@ -207,7 +210,7 @@ pub trait Pool: Debug + Send + Sync { &mut self, pool_name: &str, fs_uuids: &[FilesystemUuid], - ) -> StratisResult>; + ) -> StratisResult>; /// Rename filesystem /// Rename pool with uuid to new_name. diff --git a/src/engine/sim_engine/filesystem.rs b/src/engine/sim_engine/filesystem.rs index 9309075181..5198f234bf 100644 --- a/src/engine/sim_engine/filesystem.rs +++ b/src/engine/sim_engine/filesystem.rs @@ -10,7 +10,7 @@ use serde_json::{Map, Value}; use devicemapper::{Bytes, Sectors}; use crate::{ - engine::Filesystem, + engine::{types::FilesystemUuid, Filesystem}, stratis::{StratisError, StratisResult}, }; @@ -20,10 +20,15 @@ pub struct SimFilesystem { created: DateTime, size: Sectors, size_limit: Option, + origin: Option, } impl SimFilesystem { - pub fn new(size: Sectors, size_limit: Option) -> StratisResult { + pub fn new( + size: Sectors, + size_limit: Option, + origin: Option, + ) -> StratisResult { if let Some(limit) = size_limit { if limit < size { return Err(StratisError::Msg(format!( @@ -36,6 +41,7 @@ impl SimFilesystem { created: Utc::now(), size, size_limit, + origin, }) } @@ -61,6 +67,12 @@ impl SimFilesystem { } } } + + pub fn unset_origin(&mut self) -> bool { + let changed = self.origin.is_some(); + self.origin = None; + changed + } } impl Filesystem for SimFilesystem { @@ -89,6 +101,10 @@ impl Filesystem for SimFilesystem { fn size_limit(&self) -> Option { self.size_limit } + + fn origin(&self) -> Option { + self.origin + } } impl<'a> Into for &'a SimFilesystem { @@ -112,6 +128,15 @@ impl<'a> Into for &'a SimFilesystem { .unwrap_or_else(|| "Not set".to_string()), ), ); + json.insert( + "origin".to_string(), + Value::from( + self.origin + .as_ref() + .map(|v| v.to_string()) + .unwrap_or_else(|| "Not set".to_string()), + ), + ); Value::from(json) } } diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index d6b7dda5c9..ae1a655381 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -118,6 +118,13 @@ impl SimPool { Ok(()) } } + + fn filesystems_mut(&mut self) -> Vec<(Name, FilesystemUuid, &mut SimFilesystem)> { + self.filesystems + .iter_mut() + .map(|(name, uuid, x)| (name.clone(), *uuid, x)) + .collect() + } } // Precondition: SimDev::into() always returns a value that matches Value::Object(_). @@ -243,7 +250,7 @@ impl Pool for SimPool { for (name, (size, size_limit)) in spec_map { if !self.filesystems.contains_name(name) { let uuid = FilesystemUuid::new_v4(); - let new_filesystem = SimFilesystem::new(size, size_limit)?; + let new_filesystem = SimFilesystem::new(size, size_limit, None)?; self.filesystems .insert(Name::new((name).to_owned()), uuid, new_filesystem); result.push((name, uuid, size)); @@ -476,14 +483,33 @@ impl Pool for SimPool { &mut self, _pool_name: &str, fs_uuids: &[FilesystemUuid], - ) -> StratisResult> { + ) -> StratisResult> { let mut removed = Vec::new(); for &uuid in fs_uuids { if self.filesystems.remove_by_uuid(uuid).is_some() { removed.push(uuid); } } - Ok(SetDeleteAction::new(removed)) + + let updated_origins: Vec = self + .filesystems_mut() + .iter_mut() + .filter_map(|(_, u, fs)| { + fs.origin().and_then(|x| { + if removed.contains(&x) { + if fs.unset_origin() { + Some(*u) + } else { + None + } + } else { + None + } + }) + }) + .collect(); + + Ok(SetDeleteAction::new(removed, updated_origins)) } fn rename_filesystem( @@ -533,7 +559,11 @@ impl Pool for SimPool { return Ok(CreateAction::Identity); } } - SimFilesystem::new(filesystem.size(), filesystem.size_limit())? + SimFilesystem::new( + filesystem.size(), + filesystem.size_limit(), + Some(origin_uuid), + )? } None => { return Err(StratisError::Msg(origin_uuid.to_string())); @@ -884,7 +914,7 @@ mod tests { .unwrap(); let fs_uuid = fs_results[0].1; assert!(match pool.destroy_filesystems(pool_name, &[fs_uuid]) { - Ok(filesystems) => filesystems == SetDeleteAction::new(vec![fs_uuid]), + Ok(filesystems) => filesystems == SetDeleteAction::new(vec![fs_uuid], vec![]), _ => false, }); } diff --git a/src/engine/strat_engine/pool.rs b/src/engine/strat_engine/pool.rs index 4a8d1ee930..bfdbfb1a0c 100644 --- a/src/engine/strat_engine/pool.rs +++ b/src/engine/strat_engine/pool.rs @@ -1003,7 +1003,7 @@ impl Pool for StratPool { &mut self, pool_name: &str, fs_uuids: &[FilesystemUuid], - ) -> StratisResult> { + ) -> StratisResult> { let mut removed = Vec::new(); for &uuid in fs_uuids { if let Some(uuid) = self.thin_pool.destroy_filesystem(pool_name, uuid)? { @@ -1011,7 +1011,29 @@ impl Pool for StratPool { } } - Ok(SetDeleteAction::new(removed)) + let snapshots: Vec = self + .thin_pool + .filesystems() + .iter() + .filter_map(|(_, u, fs)| { + fs.origin() + .and_then(|x| if removed.contains(&x) { Some(*u) } else { None }) + }) + .collect(); + + let mut updated_origins = vec![]; + for sn_uuid in snapshots { + if let Err(err) = self.thin_pool.unset_fs_origin(sn_uuid) { + warn!( + "Failed to write null origin to metadata for filesystem with UUID {}: {}", + sn_uuid, err + ); + } else { + updated_origins.push(sn_uuid); + } + } + + Ok(SetDeleteAction::new(removed, updated_origins)) } #[pool_mutating_action("NoRequests")] diff --git a/src/engine/strat_engine/serde_structs.rs b/src/engine/strat_engine/serde_structs.rs index f77693509a..085945bc45 100644 --- a/src/engine/strat_engine/serde_structs.rs +++ b/src/engine/strat_engine/serde_structs.rs @@ -119,4 +119,6 @@ pub struct FilesystemSave { pub created: u64, // Unix timestamp #[serde(skip_serializing_if = "Option::is_none")] pub fs_size_limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub origin: Option, } diff --git a/src/engine/strat_engine/thinpool/filesystem.rs b/src/engine/strat_engine/thinpool/filesystem.rs index a93fd9dab5..3b55f63922 100644 --- a/src/engine/strat_engine/thinpool/filesystem.rs +++ b/src/engine/strat_engine/thinpool/filesystem.rs @@ -51,6 +51,7 @@ pub struct StratFilesystem { created: DateTime, used: Option, size_limit: Option, + origin: Option, } fn init_used(thin_dev: &ThinDev) -> Option { @@ -114,6 +115,7 @@ impl StratFilesystem { thin_dev, created: Utc::now(), size_limit, + origin: None, }, )) } @@ -140,6 +142,7 @@ impl StratFilesystem { thin_dev, created, size_limit: fssave.fs_size_limit, + origin: fssave.origin, }) } @@ -200,6 +203,7 @@ impl StratFilesystem { snapshot_fs_name: &Name, snapshot_fs_uuid: FilesystemUuid, snapshot_thin_id: ThinDevId, + origin_uuid: FilesystemUuid, ) -> StratisResult { match self.thin_dev.snapshot( get_dm(), @@ -245,6 +249,7 @@ impl StratFilesystem { thin_dev, created: Utc::now(), size_limit: self.size_limit, + origin: Some(origin_uuid), }) } Err(e) => Err(StratisError::Msg(format!( @@ -366,6 +371,7 @@ impl StratFilesystem { size: self.thin_dev.size(), created: self.created.timestamp() as u64, fs_size_limit: self.size_limit, + origin: self.origin, } } @@ -420,6 +426,12 @@ impl StratFilesystem { pub fn thindev_size(&self) -> Sectors { self.thin_dev.size() } + + pub fn unset_origin(&mut self) -> bool { + let changed = self.origin.is_some(); + self.origin = None; + changed + } } impl Filesystem for StratFilesystem { @@ -459,6 +471,10 @@ impl Filesystem for StratFilesystem { fn size_limit(&self) -> Option { self.size_limit } + + fn origin(&self) -> Option { + self.origin + } } /// Represents the state of the Stratis filesystem at a given moment in time. @@ -545,6 +561,15 @@ impl<'a> Into for &'a StratFilesystem { .unwrap_or_else(|| "Not set".to_string()), ), ); + json.insert( + "origin".to_string(), + Value::from( + self.origin + .as_ref() + .map(|v| v.to_string()) + .unwrap_or_else(|| "Not set".to_string()), + ), + ); Value::from(json) } } diff --git a/src/engine/strat_engine/thinpool/thinpool.rs b/src/engine/strat_engine/thinpool/thinpool.rs index d79d3baaee..80b30c8044 100644 --- a/src/engine/strat_engine/thinpool/thinpool.rs +++ b/src/engine/strat_engine/thinpool/thinpool.rs @@ -1253,6 +1253,7 @@ impl ThinPool { &fs_name, snapshot_fs_uuid, snapshot_id, + origin_uuid, )?, None => { return Err(StratisError::Msg( @@ -1576,6 +1577,22 @@ impl ThinPool { } Ok(changed) } + + pub fn unset_fs_origin(&mut self, fs_uuid: FilesystemUuid) -> StratisResult { + let changed = { + let (_, fs) = self.get_mut_filesystem_by_uuid(fs_uuid).ok_or_else(|| { + StratisError::Msg(format!("No filesystem with UUID {fs_uuid} found")) + })?; + fs.unset_origin() + }; + if changed { + let (name, fs) = self + .get_filesystem_by_uuid(fs_uuid) + .expect("Looked up above."); + self.mdv.save_fs(&name, fs_uuid, fs)?; + } + Ok(changed) + } } impl<'a> Into for &'a ThinPool { diff --git a/src/engine/types/actions.rs b/src/engine/types/actions.rs index 02e3dc9683..ca8088404a 100644 --- a/src/engine/types/actions.rs +++ b/src/engine/types/actions.rs @@ -575,38 +575,44 @@ impl Display for DeleteAction { } /// An action which may delete multiple things. +/// This action may also cause other values to require updating. #[derive(Debug, PartialEq, Eq)] -pub struct SetDeleteAction { +pub struct SetDeleteAction { changed: Vec, + updated: Vec, } -impl SetDeleteAction { - pub fn new(changed: Vec) -> Self { - SetDeleteAction { changed } +impl SetDeleteAction { + pub fn new(changed: Vec, updated: Vec) -> Self { + assert!(!changed.is_empty() || updated.is_empty()); + SetDeleteAction { changed, updated } } pub fn empty() -> Self { - SetDeleteAction { changed: vec![] } + SetDeleteAction { + changed: vec![], + updated: vec![], + } } } -impl EngineAction for SetDeleteAction { - type Return = Vec; +impl EngineAction for SetDeleteAction { + type Return = (Vec, Vec); fn is_changed(&self) -> bool { !self.changed.is_empty() } - fn changed(self) -> Option> { + fn changed(self) -> Option<(Vec, Vec)> { if self.changed.is_empty() { None } else { - Some(self.changed) + Some((self.changed, self.updated)) } } } -impl Display for SetDeleteAction { +impl Display for SetDeleteAction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.changed.is_empty() { write!( diff --git a/src/jsonrpc/client/filesystem.rs b/src/jsonrpc/client/filesystem.rs index 5ac3721d9b..080396117f 100644 --- a/src/jsonrpc/client/filesystem.rs +++ b/src/jsonrpc/client/filesystem.rs @@ -2,7 +2,10 @@ // 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 crate::{jsonrpc::client::utils::to_suffix_repr, stratis::StratisResult}; +use crate::{ + jsonrpc::client::utils::to_suffix_repr, + stratis::{StratisError, StratisResult}, +}; // stratis-min filesystem create pub fn filesystem_create(pool_name: String, filesystem_name: String) -> StratisResult<()> { @@ -46,3 +49,13 @@ pub fn filesystem_rename( ) -> StratisResult<()> { do_request_standard!(FsRename, pool_name, filesystem_name, new_filesystem_name) } + +// stratis-min filesystem origin +pub fn filesystem_origin(pool_name: String, filesystem_name: String) -> StratisResult { + let (origin, rc, rs) = do_request!(FsOrigin, pool_name, filesystem_name); + if rc != 0 { + Err(StratisError::Msg(rs)) + } else { + Ok(origin.unwrap_or_else(|| "None".to_string())) + } +} diff --git a/src/jsonrpc/interface.rs b/src/jsonrpc/interface.rs index d1349bf330..e9744e6da0 100644 --- a/src/jsonrpc/interface.rs +++ b/src/jsonrpc/interface.rs @@ -58,6 +58,7 @@ pub enum StratisParamType { FsCreate(String, String), FsDestroy(String, String), FsRename(String, String, String), + FsOrigin(String, String), FsList, Report, } @@ -98,5 +99,6 @@ pub enum StratisRet { FsList(FsListType), FsDestroy((bool, u16, String)), FsRename((bool, u16, String)), + FsOrigin((Option, u16, String)), Report(Value), } diff --git a/src/jsonrpc/server/filesystem.rs b/src/jsonrpc/server/filesystem.rs index 8254c18bbe..691ebabcf4 100644 --- a/src/jsonrpc/server/filesystem.rs +++ b/src/jsonrpc/server/filesystem.rs @@ -94,3 +94,19 @@ pub async fn filesystem_rename<'a>( .is_changed()) }) } + +// stratis-min filesystem origin +pub async fn filesystem_origin<'a>( + engine: Arc, + pool_name: &'a str, + fs_name: &'a str, +) -> StratisResult> { + let pool = engine + .get_pool(PoolIdentifier::Name(Name::new(pool_name.to_owned()))) + .await + .ok_or_else(|| StratisError::Msg(format!("No pool named {pool_name} found")))?; + let (_, fs) = pool + .get_filesystem_by_name(&Name::new(fs_name.to_string())) + .ok_or_else(|| StratisError::Msg(format!("No filesystem named {fs_name} found")))?; + Ok(fs.origin().map(|u| u.as_simple().to_string())) +} diff --git a/src/jsonrpc/server/server.rs b/src/jsonrpc/server/server.rs index c13ff90f90..34dfedda93 100644 --- a/src/jsonrpc/server/server.rs +++ b/src/jsonrpc/server/server.rs @@ -239,6 +239,13 @@ impl StratisParams { false, ))) } + StratisParamType::FsOrigin(pool_name, fs_name) => { + expects_fd!(self.fd_opt, false); + Ok(StratisRet::FsOrigin(stratis_result_to_return( + filesystem::filesystem_origin(engine, &pool_name, &fs_name).await, + None, + ))) + } StratisParamType::Report => { expects_fd!(self.fd_opt, false); Ok(StratisRet::Report(report::report(engine).await)) diff --git a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py index 688c6ab707..79955dec31 100644 --- a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py +++ b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py @@ -124,6 +124,7 @@ +