diff --git a/Cargo.lock b/Cargo.lock index 1911a11550..f7239523e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1633,7 +1633,7 @@ dependencies = [ [[package]] name = "stratisd" -version = "3.9.2" +version = "3.10.0" dependencies = [ "assert_cmd", "assert_matches", diff --git a/Cargo.toml b/Cargo.toml index 041f332eea..26775e623b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stratisd" -version = "3.9.2" +version = "3.10.0" authors.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/src/dbus/blockdev/blockdev_3_10/mod.rs b/src/dbus/blockdev/blockdev_3_10/mod.rs new file mode 100644 index 0000000000..92a5f5bfc7 --- /dev/null +++ b/src/dbus/blockdev/blockdev_3_10/mod.rs @@ -0,0 +1,182 @@ +// 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::sync::Arc; + +use tokio::sync::RwLock; +use zbus::{ + self, + fdo::Error, + interface, + zvariant::{ObjectPath, OwnedObjectPath}, + Connection, +}; + +use crate::dbus::blockdev::blockdev_3_0::{ + devnode_prop, hardware_info_prop, init_time_prop, physical_path_prop, pool_prop, tier_prop, + total_physical_size_prop, user_info_prop, +}; +use crate::{ + dbus::{ + blockdev::shared::{blockdev_prop, set_blockdev_prop}, + manager::Manager, + }, + engine::{DevUuid, Engine, Lockable, PoolUuid}, + stratis::StratisResult, +}; + +use crate::dbus::blockdev::blockdev_3_3::{ + new_physical_size_prop, send_user_info_signal_on_change, set_user_info_prop, +}; + +pub struct BlockdevR10 { + engine: Arc, + connection: Arc, + manager: Lockable>>, + parent_uuid: PoolUuid, + uuid: DevUuid, +} + +impl BlockdevR10 { + fn new( + engine: Arc, + connection: Arc, + manager: Lockable>>, + parent_uuid: PoolUuid, + uuid: DevUuid, + ) -> Self { + BlockdevR10 { + engine, + connection, + manager, + parent_uuid, + uuid, + } + } + + pub async fn register( + engine: Arc, + connection: &Arc, + manager: &Lockable>>, + path: ObjectPath<'_>, + parent_uuid: PoolUuid, + uuid: DevUuid, + ) -> StratisResult<()> { + let blockdev = Self::new( + engine, + Arc::clone(connection), + manager.clone(), + parent_uuid, + uuid, + ); + + connection.object_server().at(path, blockdev).await?; + Ok(()) + } + + pub async fn unregister( + connection: &Arc, + path: ObjectPath<'_>, + ) -> StratisResult<()> { + connection + .object_server() + .remove::(path) + .await?; + Ok(()) + } +} + +#[interface(name = "org.storage.stratis3.blockdev.r10", introspection_docs = false)] +impl BlockdevR10 { + #[zbus(property(emits_changed_signal = "const"))] + async fn devnode(&self) -> Result { + blockdev_prop(&self.engine, self.parent_uuid, self.uuid, devnode_prop).await + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn hardware_info(&self) -> Result<(bool, String), Error> { + blockdev_prop( + &self.engine, + self.parent_uuid, + self.uuid, + hardware_info_prop, + ) + .await + } + + #[zbus(property)] + async fn user_info(&self) -> Result<(bool, String), Error> { + blockdev_prop(&self.engine, self.parent_uuid, self.uuid, user_info_prop).await + } + + #[zbus(property)] + async fn set_user_info(&self, value: (bool, String)) -> Result<(), zbus::Error> { + set_blockdev_prop( + &self.engine, + &self.connection, + &self.manager, + self.parent_uuid, + self.uuid, + value, + set_user_info_prop, + send_user_info_signal_on_change, + ) + .await + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn initialization_time(&self) -> Result { + blockdev_prop(&self.engine, self.parent_uuid, self.uuid, init_time_prop) + .await + .and_then(|r| r) + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn pool(&self) -> Result { + pool_prop(self.manager.read().await, self.parent_uuid) + } + + #[zbus(property(emits_changed_signal = "const"))] + fn uuid(&self) -> String { + self.uuid.simple().to_string() + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn tier(&self) -> Result { + blockdev_prop(&self.engine, self.parent_uuid, self.uuid, tier_prop).await + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn physical_path(&self) -> Result { + blockdev_prop( + &self.engine, + self.parent_uuid, + self.uuid, + physical_path_prop, + ) + .await + } + + #[zbus(property)] + async fn total_physical_size(&self) -> Result { + blockdev_prop( + &self.engine, + self.parent_uuid, + self.uuid, + total_physical_size_prop, + ) + .await + } + + #[zbus(property)] + async fn new_physical_size(&self) -> Result<(bool, String), Error> { + blockdev_prop( + &self.engine, + self.parent_uuid, + self.uuid, + new_physical_size_prop, + ) + .await + } +} diff --git a/src/dbus/blockdev/mod.rs b/src/dbus/blockdev/mod.rs index a2910afc78..d8d45e23f0 100644 --- a/src/dbus/blockdev/mod.rs +++ b/src/dbus/blockdev/mod.rs @@ -19,6 +19,7 @@ use crate::{ mod blockdev_3_0; mod blockdev_3_1; +mod blockdev_3_10; mod blockdev_3_2; mod blockdev_3_3; mod blockdev_3_4; @@ -31,6 +32,7 @@ mod shared; pub use blockdev_3_0::BlockdevR0; pub use blockdev_3_1::BlockdevR1; +pub use blockdev_3_10::BlockdevR10; pub use blockdev_3_2::BlockdevR2; pub use blockdev_3_3::BlockdevR3; pub use blockdev_3_4::BlockdevR4; @@ -173,6 +175,18 @@ pub async fn register_blockdev<'a>( { warn!("Failed to register interface blockdev.r9 for pool with UUID {pool_uuid}: {e}"); }; + if let Err(e) = BlockdevR10::register( + engine.clone(), + connection, + manager, + path.clone(), + pool_uuid, + dev_uuid, + ) + .await + { + warn!("Failed to register interface blockdev.r10 for pool with UUID {pool_uuid}: {e}"); + }; manager.write().await.add_blockdev(&path, dev_uuid)?; Ok(path) } @@ -192,6 +206,7 @@ pub async fn unregister_blockdev( BlockdevR7::unregister(connection, path.clone()).await?; BlockdevR8::unregister(connection, path.clone()).await?; BlockdevR9::unregister(connection, path.clone()).await?; + BlockdevR10::unregister(connection, path.clone()).await?; let mut lock = manager.write().await; let uuid = lock diff --git a/src/dbus/filesystem/filesystem_3_10/mod.rs b/src/dbus/filesystem/filesystem_3_10/mod.rs new file mode 100644 index 0000000000..650be1c947 --- /dev/null +++ b/src/dbus/filesystem/filesystem_3_10/mod.rs @@ -0,0 +1,197 @@ +// 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::sync::Arc; + +use tokio::sync::RwLock; +use zbus::{ + fdo::Error, + interface, + zvariant::{ObjectPath, OwnedObjectPath}, + Connection, +}; + +use crate::{ + dbus::{ + filesystem::{ + filesystem_3_0::{ + created_prop, devnode_prop, name_prop, pool_prop, set_name_method, size_prop, + used_prop, + }, + filesystem_3_6::{ + send_size_limit_signal_on_change, set_size_limit_prop, size_limit_prop, + }, + filesystem_3_7::{ + merge_scheduled_prop, origin_prop, send_merge_scheduled_signal_on_change, + set_merge_scheduled_prop, + }, + shared::{filesystem_prop, set_filesystem_prop}, + }, + manager::Manager, + }, + engine::{Engine, FilesystemUuid, Lockable, Name, PoolUuid}, + stratis::StratisResult, +}; + +pub struct FilesystemR10 { + engine: Arc, + connection: Arc, + manager: Lockable>>, + parent_uuid: PoolUuid, + uuid: FilesystemUuid, +} + +impl FilesystemR10 { + fn new( + engine: Arc, + connection: Arc, + manager: Lockable>>, + parent_uuid: PoolUuid, + uuid: FilesystemUuid, + ) -> Self { + FilesystemR10 { + engine, + connection, + manager, + parent_uuid, + uuid, + } + } + + pub async fn register( + engine: Arc, + connection: &Arc, + manager: &Lockable>>, + path: ObjectPath<'_>, + parent_uuid: PoolUuid, + uuid: FilesystemUuid, + ) -> StratisResult<()> { + let filesystem = Self::new( + engine, + Arc::clone(connection), + manager.clone(), + parent_uuid, + uuid, + ); + + connection.object_server().at(path, filesystem).await?; + Ok(()) + } + + pub async fn unregister( + connection: &Arc, + path: ObjectPath<'_>, + ) -> StratisResult<()> { + connection + .object_server() + .remove::(path) + .await?; + Ok(()) + } +} + +#[interface( + name = "org.storage.stratis3.filesystem.r10", + introspection_docs = false +)] +impl FilesystemR10 { + #[zbus(out_args("result", "return_code", "return_string"))] + async fn set_name(&self, name: &str) -> ((bool, String), u16, String) { + set_name_method( + &self.engine, + &self.connection, + &self.manager, + self.parent_uuid, + self.uuid, + name, + ) + .await + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn created(&self) -> Result { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, created_prop).await + } + + #[zbus(property(emits_changed_signal = "invalidates"))] + async fn devnode(&self) -> Result { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, devnode_prop).await + } + + #[zbus(property)] + async fn merge_scheduled(&self) -> Result { + filesystem_prop( + &self.engine, + self.parent_uuid, + self.uuid, + merge_scheduled_prop, + ) + .await + } + + #[zbus(property)] + async fn set_merge_scheduled(&self, value: bool) -> Result<(), Error> { + set_filesystem_prop( + &self.engine, + &self.connection, + &self.manager, + self.parent_uuid, + self.uuid, + value, + set_merge_scheduled_prop, + send_merge_scheduled_signal_on_change, + ) + .await + } + + #[zbus(property)] + async fn name(&self) -> Result { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, name_prop).await + } + + #[zbus(property)] + async fn origin(&self) -> Result<(bool, String), Error> { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, origin_prop).await + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn pool(&self) -> Result { + pool_prop(self.manager.read().await, self.parent_uuid) + } + + #[zbus(property)] + async fn size(&self) -> Result { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, size_prop).await + } + + #[zbus(property)] + async fn size_limit(&self) -> Result<(bool, String), Error> { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, size_limit_prop).await + } + + #[zbus(property)] + async fn set_size_limit(&self, value: (bool, String)) -> Result<(), Error> { + set_filesystem_prop( + &self.engine, + &self.connection, + &self.manager, + self.parent_uuid, + self.uuid, + value, + set_size_limit_prop, + send_size_limit_signal_on_change, + ) + .await + } + + #[zbus(property)] + async fn used(&self) -> Result<(bool, String), Error> { + filesystem_prop(&self.engine, self.parent_uuid, self.uuid, used_prop).await + } + + #[zbus(property(emits_changed_signal = "const"))] + fn uuid(&self) -> String { + self.uuid.simple().to_string() + } +} diff --git a/src/dbus/filesystem/mod.rs b/src/dbus/filesystem/mod.rs index 8de092b666..f5db5cb0e9 100644 --- a/src/dbus/filesystem/mod.rs +++ b/src/dbus/filesystem/mod.rs @@ -18,6 +18,7 @@ use crate::{ mod filesystem_3_0; mod filesystem_3_1; +mod filesystem_3_10; mod filesystem_3_2; mod filesystem_3_3; mod filesystem_3_4; @@ -30,6 +31,7 @@ mod shared; pub use filesystem_3_0::FilesystemR0; pub use filesystem_3_1::FilesystemR1; +pub use filesystem_3_10::FilesystemR10; pub use filesystem_3_2::FilesystemR2; pub use filesystem_3_3::FilesystemR3; pub use filesystem_3_4::FilesystemR4; @@ -145,6 +147,15 @@ pub async fn register_filesystem<'a>( uuid, ) .await?; + FilesystemR10::register( + engine.clone(), + connection, + manager, + path.clone(), + pool_uuid, + uuid, + ) + .await?; Ok(path) } @@ -173,6 +184,7 @@ pub async fn unregister_filesystem( FilesystemR7::unregister(connection, path.clone()).await?; FilesystemR8::unregister(connection, path.clone()).await?; FilesystemR9::unregister(connection, path.clone()).await?; + FilesystemR10::unregister(connection, path.clone()).await?; Ok(uuid) } diff --git a/src/dbus/manager/manager_3_10/mod.rs b/src/dbus/manager/manager_3_10/mod.rs new file mode 100644 index 0000000000..695ce1dacb --- /dev/null +++ b/src/dbus/manager/manager_3_10/mod.rs @@ -0,0 +1,187 @@ +// 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::{ + path::PathBuf, + sync::{atomic::AtomicU64, Arc}, +}; + +use tokio::sync::RwLock; +use zbus::{ + interface, + zvariant::{Fd, ObjectPath, OwnedObjectPath}, + Connection, Result, +}; + +use crate::{ + dbus::{ + consts, + manager::Manager, + manager::{ + manager_3_0::{ + destroy_pool_method, engine_state_report_method, list_keys_method, set_key_method, + unset_key_method, version_prop, + }, + manager_3_2::refresh_state_method, + manager_3_6::stop_pool_method, + manager_3_8::{create_pool_method, stopped_pools_prop}, + manager_3_9::start_pool_method, + }, + types, + }, + engine::{Engine, KeyDescription, Lockable, StoppedPoolsInfo}, +}; + +pub struct ManagerR10 { + connection: Arc, + engine: Arc, + manager: Lockable>>, + counter: Arc, +} + +impl ManagerR10 { + pub fn new( + engine: Arc, + connection: Arc, + manager: Lockable>>, + counter: Arc, + ) -> Self { + ManagerR10 { + connection, + engine, + manager, + counter, + } + } + + pub async fn register( + engine: &Arc, + connection: &Arc, + manager: &Lockable>>, + counter: &Arc, + ) -> Result<()> { + let manager = Self::new( + Arc::clone(engine), + Arc::clone(connection), + manager.clone(), + Arc::clone(counter), + ); + connection + .object_server() + .at(consts::STRATIS_BASE_PATH, manager) + .await?; + Ok(()) + } +} + +#[interface(name = "org.storage.stratis3.Manager.r10")] +impl ManagerR10 { + #[zbus(property(emits_changed_signal = "const"))] + #[allow(clippy::unused_self)] + fn version(&self) -> &str { + version_prop() + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn stopped_pools(&self) -> types::ManagerR8 { + stopped_pools_prop(&self.engine).await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn list_keys(&self) -> (Vec, u16, String) { + list_keys_method(&self.engine).await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn set_key( + &self, + key_desc: KeyDescription, + key_fd: Fd<'_>, + ) -> ((bool, bool), u16, String) { + set_key_method(&self.engine, &key_desc, key_fd).await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn unset_key(&self, key_desc: KeyDescription) -> (bool, u16, String) { + unset_key_method(&self.engine, &key_desc).await + } + + #[allow(clippy::too_many_arguments)] + #[zbus(out_args("result", "return_code", "return_string"))] + async fn create_pool( + &self, + name: &str, + devices: Vec, + key_desc: Vec<((bool, u32), KeyDescription)>, + clevis_info: Vec<((bool, u32), &str, &str)>, + journal_size: (bool, u64), + tag_spec: (bool, &str), + allocate_superblock: (bool, bool), + ) -> ((bool, (OwnedObjectPath, Vec)), u16, String) { + create_pool_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + name, + devices, + key_desc, + clevis_info, + journal_size, + tag_spec, + allocate_superblock, + ) + .await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn destroy_pool(&self, pool: ObjectPath<'_>) -> ((bool, String), u16, String) { + destroy_pool_method(&self.engine, &self.connection, &self.manager, pool).await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn start_pool( + &self, + id: &str, + id_type: &str, + unlock_method: (bool, (bool, u32)), + key_fd: (bool, Fd<'_>), + remove_cache: bool, + ) -> ( + ( + bool, + (OwnedObjectPath, Vec, Vec), + ), + u16, + String, + ) { + start_pool_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + id, + id_type, + unlock_method, + key_fd, + remove_cache, + ) + .await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn stop_pool(&self, id: &str, id_type: &str) -> ((bool, String), u16, String) { + stop_pool_method(&self.engine, &self.connection, &self.manager, id, id_type).await + } + + #[zbus(out_args("return_code", "return_string"))] + async fn refresh_state(&self) -> (u16, String) { + refresh_state_method(&self.engine).await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + fn engine_state_report(&self) -> (String, u16, String) { + engine_state_report_method(&self.engine) + } +} diff --git a/src/dbus/manager/mod.rs b/src/dbus/manager/mod.rs index 864260ece0..cc760292d2 100644 --- a/src/dbus/manager/mod.rs +++ b/src/dbus/manager/mod.rs @@ -22,6 +22,7 @@ use crate::{ mod manager_3_0; mod manager_3_1; +mod manager_3_10; mod manager_3_2; mod manager_3_3; mod manager_3_4; @@ -32,6 +33,7 @@ mod manager_3_8; mod manager_3_9; mod report_3_0; mod report_3_1; +mod report_3_10; mod report_3_2; mod report_3_3; mod report_3_4; @@ -43,6 +45,7 @@ mod report_3_9; pub use manager_3_0::ManagerR0; pub use manager_3_1::ManagerR1; +pub use manager_3_10::ManagerR10; pub use manager_3_2::ManagerR2; pub use manager_3_3::ManagerR3; pub use manager_3_4::ManagerR4; @@ -53,6 +56,7 @@ pub use manager_3_8::ManagerR8; pub use manager_3_9::ManagerR9; pub use report_3_0::ReportR0; pub use report_3_1::ReportR1; +pub use report_3_10::ReportR10; pub use report_3_2::ReportR2; pub use report_3_3::ReportR3; pub use report_3_4::ReportR4; @@ -264,6 +268,9 @@ pub async fn register_manager( if let Err(e) = ManagerR9::register(engine, connection, manager, counter).await { warn!("Failed to register interface Manager.r9: {e}"); } + if let Err(e) = ManagerR10::register(engine, connection, manager, counter).await { + warn!("Failed to register interface Manager.r10: {e}"); + } if let Err(e) = ReportR0::register(engine, connection).await { warn!("Failed to register interface Report.r0: {e}"); } @@ -294,6 +301,9 @@ pub async fn register_manager( if let Err(e) = ReportR9::register(engine, connection).await { warn!("Failed to register interface Report.r9: {e}"); } + if let Err(e) = ReportR10::register(engine, connection).await { + warn!("Failed to register interface Report.r10: {e}"); + } if let Err(e) = connection .object_server() .at(STRATIS_BASE_PATH, ObjectManager) diff --git a/src/dbus/manager/report_3_10/mod.rs b/src/dbus/manager/report_3_10/mod.rs new file mode 100644 index 0000000000..7747e45dab --- /dev/null +++ b/src/dbus/manager/report_3_10/mod.rs @@ -0,0 +1,39 @@ +// 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::sync::Arc; + +use zbus::{interface, Connection, Result}; + +use crate::{ + dbus::{consts, manager::report_3_0::get_report_method}, + engine::Engine, +}; + +pub struct ReportR10 { + engine: Arc, +} + +impl ReportR10 { + pub fn new(engine: Arc) -> Self { + ReportR10 { engine } + } + + pub async fn register(engine: &Arc, connection: &Arc) -> Result<()> { + let report = Self::new(Arc::clone(engine)); + connection + .object_server() + .at(consts::STRATIS_BASE_PATH, report) + .await?; + Ok(()) + } +} + +#[interface(name = "org.storage.stratis3.Report.r10", introspection_docs = false)] +impl ReportR10 { + #[zbus(out_args("result", "return_code", "return_string"))] + fn get_report(&self, name: &str) -> (String, u16, String) { + get_report_method(&self.engine, name) + } +} diff --git a/src/dbus/pool/mod.rs b/src/dbus/pool/mod.rs index 7aca1cd58b..1993b5b3fb 100644 --- a/src/dbus/pool/mod.rs +++ b/src/dbus/pool/mod.rs @@ -21,6 +21,7 @@ use crate::{ mod pool_3_0; mod pool_3_1; +mod pool_3_10; mod pool_3_2; mod pool_3_3; mod pool_3_4; @@ -33,6 +34,7 @@ mod shared; pub use pool_3_0::PoolR0; pub use pool_3_1::PoolR1; +pub use pool_3_10::PoolR10; pub use pool_3_2::PoolR2; pub use pool_3_3::PoolR3; pub use pool_3_4::PoolR4; @@ -177,6 +179,18 @@ pub async fn register_pool<'a>( { warn!("Failed to register interface pool.r9 for pool with UUID {pool_uuid}: {e}"); } + if let Err(e) = PoolR10::register( + engine, + connection, + manager, + counter, + path.clone(), + pool_uuid, + ) + .await + { + warn!("Failed to register interface pool.r10 for pool with UUID {pool_uuid}: {e}"); + } manager.write().await.add_pool(&path, pool_uuid)?; @@ -277,6 +291,9 @@ pub async fn unregister_pool( if let Err(e) = PoolR9::unregister(connection, path.clone()).await { warn!("Failed to deregister interface pool.r9 for path {path}: {e}"); } + if let Err(e) = PoolR10::unregister(connection, path.clone()).await { + warn!("Failed to deregister interface pool.r10 for path {path}: {e}"); + } Ok(uuid) } diff --git a/src/dbus/pool/pool_3_10/methods.rs b/src/dbus/pool/pool_3_10/methods.rs new file mode 100644 index 0000000000..e3e458ad22 --- /dev/null +++ b/src/dbus/pool/pool_3_10/methods.rs @@ -0,0 +1,97 @@ +// 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::sync::Arc; + +use tokio::sync::RwLock; +use zbus::Connection; + +use crate::{ + dbus::{ + blockdev::unregister_blockdev, + consts::OK_STRING, + manager::Manager, + types::DbusErrorEnum, + util::{engine_to_dbus_err_tuple, send_has_cache_signal}, + }, + engine::{Engine, EngineAction, Lockable, PoolIdentifier, PoolUuid}, + stratis::StratisError, +}; + +pub async fn remove_cache_method( + engine: &Arc, + connection: &Arc, + manager: &Lockable>>, + pool_uuid: PoolUuid, +) -> ((bool, Vec), u16, String) { + let default_return = (false, Vec::default()); + + let guard_res = engine + .get_mut_pool(PoolIdentifier::Uuid(pool_uuid)) + .await + .ok_or_else(|| StratisError::Msg(format!("No pool associated with uuid {pool_uuid}"))); + let conn_clone = Arc::clone(connection); + let man_clone = manager.clone(); + match tokio::task::spawn_blocking(move || { + let mut guard = guard_res?; + let (name, _, pool) = guard.as_mut_tuple(); + handle_action!( + pool.remove_cache(pool_uuid, name.to_string().as_str()), + conn_clone, + man_clone, + pool_uuid + ) + }) + .await + { + Ok(Ok(action)) => match action.changed() { + Some((dev_uuids, _)) => { + match manager.read().await.pool_get_path(&pool_uuid) { + Some(p) => { + send_has_cache_signal(connection, p).await; + } + None => { + warn!("No object path associated with pool UUID {pool_uuid}; failed to send pool has cache change signals"); + } + }; + + let mut removed_uuids = Vec::new(); + for dev_uuid in dev_uuids { + let opt = manager.read().await.blockdev_get_path(&dev_uuid).cloned(); + match opt { + Some(p) => { + if let Err(e) = + unregister_blockdev(connection, manager, &p.as_ref()).await + { + warn!("Unable to unregister object path for blockdev with UUID {dev_uuid} belonging to pool {pool_uuid} on the D-Bus: {e}"); + } + } + None => { + warn!("No path found to unregister for removed cache blockdev with UUID {dev_uuid}"); + } + } + removed_uuids.push(dev_uuid.simple().to_string()); + } + ( + (true, removed_uuids), + DbusErrorEnum::OK as u16, + OK_STRING.to_string(), + ) + } + None => ( + default_return, + DbusErrorEnum::OK as u16, + OK_STRING.to_string(), + ), + }, + Ok(Err(e)) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + (default_return, rc, rs) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::from(e)); + (default_return, rc, rs) + } + } +} diff --git a/src/dbus/pool/pool_3_10/mod.rs b/src/dbus/pool/pool_3_10/mod.rs new file mode 100644 index 0000000000..582a9ee7c9 --- /dev/null +++ b/src/dbus/pool/pool_3_10/mod.rs @@ -0,0 +1,485 @@ +// 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::{ + path::PathBuf, + sync::{atomic::AtomicU64, Arc}, +}; + +use tokio::sync::RwLock; +use zbus::{ + fdo::Error, + interface, + zvariant::{ObjectPath, OwnedObjectPath, Value}, + Connection, +}; + +mod methods; + +pub use methods::remove_cache_method; + +use crate::{ + dbus::{ + manager::Manager, + pool::{ + pool_3_0::{ + add_cache_devs_method, add_data_devs_method, allocated_prop, + avail_actions_property, destroy_filesystems_method, encrypted_prop, + has_cache_property, name_prop, set_name_method, size_prop, + snapshot_filesystem_method, used_prop, + }, + pool_3_1::{ + enable_overprovisioning_prop, fs_limit_prop, no_alloc_space_prop, + send_enable_overprovisioning_signal_on_change, send_fs_limit_signal_on_change, + set_enable_overprovisioning_prop, set_fs_limit_prop, + }, + pool_3_3::grow_physical_device_method, + pool_3_5::init_cache_method, + pool_3_6::create_filesystems_method, + pool_3_7::{filesystem_metadata_method, metadata_method}, + pool_3_8::{ + bind_clevis_method, bind_keyring_method, clevis_infos_prop, free_token_slots_prop, + key_descs_prop, metadata_version_prop, rebind_clevis_method, rebind_keyring_method, + unbind_clevis_method, unbind_keyring_method, volume_key_loaded_prop, + }, + pool_3_9::{ + decrypt_pool_method, encrypt_pool_method, last_reencrypted_timestamp_prop, + reencrypt_pool_method, + }, + shared::{pool_prop, set_pool_prop}, + }, + types::FilesystemSpec, + }, + engine::{self, ActionAvailability, Engine, KeyDescription, Lockable, PoolUuid}, + stratis::StratisResult, +}; + +pub struct PoolR10 { + connection: Arc, + engine: Arc, + manager: Lockable>>, + counter: Arc, + uuid: PoolUuid, +} + +impl PoolR10 { + fn new( + engine: Arc, + connection: Arc, + manager: Lockable>>, + counter: Arc, + uuid: PoolUuid, + ) -> Self { + PoolR10 { + connection, + engine, + manager, + counter, + uuid, + } + } + + pub async fn register( + engine: &Arc, + connection: &Arc, + manager: &Lockable>>, + counter: &Arc, + path: ObjectPath<'_>, + uuid: PoolUuid, + ) -> StratisResult<()> { + let pool = Self::new( + Arc::clone(engine), + Arc::clone(connection), + manager.clone(), + Arc::clone(counter), + uuid, + ); + + connection.object_server().at(path, pool).await?; + Ok(()) + } + + pub async fn unregister( + connection: &Arc, + path: ObjectPath<'_>, + ) -> StratisResult<()> { + connection + .object_server() + .remove::(path) + .await?; + Ok(()) + } +} + +#[interface(name = "org.storage.stratis3.pool.r10")] +impl PoolR10 { + #[zbus(out_args("results", "return_code", "return_string"))] + async fn create_filesystems( + &self, + specs: FilesystemSpec<'_>, + ) -> ((bool, Vec<(OwnedObjectPath, String)>), u16, String) { + create_filesystems_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + self.uuid, + specs, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn destroy_filesystems( + &self, + filesystems: Vec>, + ) -> ((bool, Vec), u16, String) { + destroy_filesystems_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + filesystems, + ) + .await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn snapshot_filesystem( + &self, + origin: ObjectPath<'_>, + snapshot_name: String, + ) -> ((bool, OwnedObjectPath), u16, String) { + snapshot_filesystem_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + self.uuid, + origin, + snapshot_name, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn add_data_devs( + &self, + devices: Vec, + ) -> ((bool, Vec), u16, String) { + add_data_devs_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + self.uuid, + devices, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn init_cache( + &self, + devices: Vec, + ) -> ((bool, Vec), u16, String) { + init_cache_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + self.uuid, + devices, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn add_cache_devs( + &self, + devices: Vec, + ) -> ((bool, Vec), u16, String) { + add_cache_devs_method( + &self.engine, + &self.connection, + &self.manager, + &self.counter, + self.uuid, + devices, + ) + .await + } + + #[zbus(out_args("result", "return_code", "return_string"))] + async fn set_name(&self, name: &str) -> ((bool, String), u16, String) { + set_name_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + name, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn bind_clevis( + &self, + pin: String, + json: &str, + token_slot: (bool, u32), + ) -> (bool, u16, String) { + bind_clevis_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + pin, + json, + token_slot, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn bind_keyring( + &self, + key_desc: KeyDescription, + token_slot: (bool, u32), + ) -> (bool, u16, String) { + bind_keyring_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + key_desc, + token_slot, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn rebind_clevis(&self, token_slot: (bool, u32)) -> (bool, u16, String) { + rebind_clevis_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + token_slot, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn rebind_keyring( + &self, + key_desc: KeyDescription, + token_slot: (bool, u32), + ) -> (bool, u16, String) { + rebind_keyring_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + key_desc, + token_slot, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn unbind_clevis(&self, token_slot: (bool, u32)) -> (bool, u16, String) { + unbind_clevis_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + token_slot, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn unbind_keyring(&self, token_slot: (bool, u32)) -> (bool, u16, String) { + unbind_keyring_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + token_slot, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn grow_physical_device(&self, dev: &str) -> (bool, u16, String) { + grow_physical_device_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + dev, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn metadata(&self, current: bool) -> (String, u16, String) { + metadata_method(&self.engine, self.uuid, current).await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn filesystem_metadata( + &self, + fs_name: (bool, &str), + current: bool, + ) -> (String, u16, String) { + filesystem_metadata_method(&self.engine, self.uuid, fs_name, current).await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn encrypt_pool( + &self, + key_descs: Vec<((bool, u32), KeyDescription)>, + clevis_infos: Vec<((bool, u32), &str, &str)>, + ) -> (bool, u16, String) { + encrypt_pool_method( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + key_descs, + clevis_infos, + ) + .await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn reencrypt_pool(&self) -> (bool, u16, String) { + reencrypt_pool_method(&self.engine, &self.connection, &self.manager, self.uuid).await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn decrypt_pool(&self) -> (bool, u16, String) { + decrypt_pool_method(&self.engine, &self.connection, &self.manager, self.uuid).await + } + + #[zbus(out_args("results", "return_code", "return_string"))] + async fn remove_cache(&self) -> ((bool, Vec), u16, String) { + remove_cache_method(&self.engine, &self.connection, &self.manager, self.uuid).await + } + + #[zbus(property(emits_changed_signal = "const"))] + fn uuid(&self) -> String { + self.uuid.simple().to_string() + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn name(&self) -> Result { + pool_prop(&self.engine, self.uuid, name_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn encrypted(&self) -> Result { + pool_prop(&self.engine, self.uuid, encrypted_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn available_actions(&self) -> Result { + pool_prop(&self.engine, self.uuid, avail_actions_property).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn key_descriptions(&self) -> Result, Error> { + pool_prop(&self.engine, self.uuid, key_descs_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn clevis_infos(&self) -> Result, Error> { + pool_prop(&self.engine, self.uuid, clevis_infos_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn has_cache(&self) -> Result { + pool_prop(&self.engine, self.uuid, has_cache_property).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn total_physical_size(&self) -> Result { + pool_prop(&self.engine, self.uuid, size_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn total_physical_used(&self) -> Result<(bool, String), Error> { + pool_prop(&self.engine, self.uuid, used_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn allocated_size(&self) -> Result { + pool_prop(&self.engine, self.uuid, allocated_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn fs_limit(&self) -> Result { + pool_prop(&self.engine, self.uuid, fs_limit_prop).await + } + + #[zbus(property)] + async fn set_fs_limit(&self, fs_limit: u64) -> Result<(), Error> { + set_pool_prop( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + set_fs_limit_prop, + fs_limit, + send_fs_limit_signal_on_change, + ) + .await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn overprovisioning(&self) -> Result { + pool_prop(&self.engine, self.uuid, enable_overprovisioning_prop).await + } + + #[zbus(property)] + async fn set_overprovisioning(&self, overprov: bool) -> Result<(), Error> { + set_pool_prop( + &self.engine, + &self.connection, + &self.manager, + self.uuid, + set_enable_overprovisioning_prop, + overprov, + send_enable_overprovisioning_signal_on_change, + ) + .await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn no_alloc_space(&self) -> Result { + pool_prop(&self.engine, self.uuid, no_alloc_space_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn free_token_slots(&self) -> Result<(bool, u8), Error> { + pool_prop(&self.engine, self.uuid, free_token_slots_prop).await + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn volume_key_loaded(&self) -> Result, Error> { + pool_prop(&self.engine, self.uuid, volume_key_loaded_prop).await + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn metadata_version(&self) -> Result { + pool_prop(&self.engine, self.uuid, metadata_version_prop).await + } + + #[zbus(property(emits_changed_signal = "true"))] + async fn last_reencrypted_timestamp(&self) -> Result<(bool, String), Error> { + pool_prop(&self.engine, self.uuid, last_reencrypted_timestamp_prop).await + } +} diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 47e33048ca..1d1010de10 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -158,6 +158,15 @@ pub trait Pool: Debug + Send + Sync { supports_encrypted: bool, ) -> StratisResult>; + /// Remove the cache tier from the pool. Tears down the dm-cache + /// device, wipes the cache blockdevs, and removes the cache tier. + /// Returns the UUIDs of the cache devices that were removed. + fn remove_cache( + &mut self, + pool_uuid: PoolUuid, + pool_name: &str, + ) -> StratisResult>; + /// Creates the filesystems specified by specs. /// Returns a list of the names of filesystems actually created. /// Returns an error if any of the specified names are already in use diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index dc6b5221d0..7a5a3b7981 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -224,6 +224,20 @@ impl Pool for SimPool { } } + fn remove_cache( + &mut self, + _pool_uuid: PoolUuid, + _pool_name: &str, + ) -> StratisResult> { + if self.has_cache() { + let uuids: Vec<_> = self.cache_devs.keys().cloned().collect(); + self.cache_devs.clear(); + Ok(SetDeleteAction::new(uuids, vec![])) + } else { + Ok(SetDeleteAction::empty()) + } + } + fn create_filesystems( &mut self, _pool_name: &str, @@ -1475,4 +1489,69 @@ mod tests { _ => false, }); } + + #[test] + fn remove_cache_empty() { + let engine = SimEngine::default(); + let pool_name = "pool_name"; + let uuid = test_async!(engine.create_pool( + pool_name, + strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), + None, + IntegritySpec::default(), + )) + .unwrap() + .changed() + .unwrap(); + let mut pool = test_async!(engine.get_mut_pool(PoolIdentifier::Uuid(uuid))).unwrap(); + assert!(!pool.has_cache()); + let result = pool.remove_cache(uuid, pool_name).unwrap(); + assert!(!result.is_changed()); + } + + #[test] + fn remove_cache_with_cache() { + let engine = SimEngine::default(); + let pool_name = "pool_name"; + let uuid = test_async!(engine.create_pool( + pool_name, + strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), + None, + IntegritySpec::default(), + )) + .unwrap() + .changed() + .unwrap(); + let mut pool = test_async!(engine.get_mut_pool(PoolIdentifier::Uuid(uuid))).unwrap(); + pool.init_cache(uuid, pool_name, &[Path::new("/dev/cache1")], true) + .unwrap(); + assert!(pool.has_cache()); + let result = pool.remove_cache(uuid, pool_name).unwrap(); + assert!(result.is_changed()); + assert!(!pool.has_cache()); + } + + #[test] + fn remove_cache_idempotent() { + let engine = SimEngine::default(); + let pool_name = "pool_name"; + let uuid = test_async!(engine.create_pool( + pool_name, + strs_to_paths!(["/dev/one", "/dev/two", "/dev/three"]), + None, + IntegritySpec::default(), + )) + .unwrap() + .changed() + .unwrap(); + let mut pool = test_async!(engine.get_mut_pool(PoolIdentifier::Uuid(uuid))).unwrap(); + pool.init_cache(uuid, pool_name, &[Path::new("/dev/cache1")], true) + .unwrap(); + assert!(pool.has_cache()); + let result1 = pool.remove_cache(uuid, pool_name).unwrap(); + assert!(result1.is_changed()); + assert!(!pool.has_cache()); + let result2 = pool.remove_cache(uuid, pool_name).unwrap(); + assert!(!result2.is_changed()); + } } diff --git a/src/engine/strat_engine/backstore/backstore/v1.rs b/src/engine/strat_engine/backstore/backstore/v1.rs index 11bc66b031..fb4658c404 100644 --- a/src/engine/strat_engine/backstore/backstore/v1.rs +++ b/src/engine/strat_engine/backstore/backstore/v1.rs @@ -30,11 +30,12 @@ use crate::{ metadata::MDADataSize, names::{format_backstore_ids, CacheRole}, serde_structs::{BackstoreSave, CapSave, Recordable}, + thinpool::ThinPool, writing::wipe_sectors, }, types::{ ActionAvailability, BlockDevTier, DevUuid, EncryptionInfo, InputEncryptionInfo, - KeyDescription, Name, PoolEncryptionInfo, PoolUuid, SizedKeyMemory, + KeyDescription, Name, OffsetDirection, PoolEncryptionInfo, PoolUuid, SizedKeyMemory, }, }, stratis::{StratisError, StratisResult}, @@ -877,6 +878,76 @@ impl Backstore { } } + /// Remove caching from the pool. Removes the dm-cache device and + /// its cache-sub and meta-sub sub-devices, preserving the origin + /// device. Wipes the cache blockdevs and removes the cache tier. + /// + /// The caller is responsible for suspending the thinpool before + /// calling this method, redirecting the thinpool to the new + /// backstore device via set_device, and resuming the thinpool. + /// + /// Precondition: self.cache_tier.is_some() && self.cache.is_some() + /// Postcondition: self.cache_tier.is_none() && self.cache.is_none() + /// && self.linear.is_some() + /// + /// WARNING: metadata changing event + pub fn remove_cache( + &mut self, + thinpool: &mut ThinPool, + pool_uuid: PoolUuid, + ) -> StratisResult<()> { + let mut cache_tier = self + .cache_tier + .take() + .ok_or_else(|| StratisError::Msg("Pool does not have a cache".to_string()))?; + let cache = self + .cache + .take() + .expect("cache_tier.is_some() <=> self.cache.is_some()"); + + let device = match self.device() { + Some(d) => d, + None => { + self.cache_tier = Some(cache_tier); + self.cache = Some(cache); + return Err(StratisError::Msg("No cap device found".to_string())); + } + }; + + thinpool.suspend()?; + thinpool.set_device(device, Sectors(0), OffsetDirection::Forwards)?; + thinpool.resume()?; + + // Remove cache, cache-sub, and meta-sub DM devices. + // The origin-sub device is preserved. + // Order matters: cache must be removed before its sub-devices. + let (cache_name, _) = format_backstore_ids(pool_uuid, CacheRole::Cache); + let (cache_sub_name, _) = format_backstore_ids(pool_uuid, CacheRole::CacheSub); + let (meta_sub_name, _) = format_backstore_ids(pool_uuid, CacheRole::MetaSub); + if let Err(e) = remove_optional_devices(vec![cache_name, cache_sub_name, meta_sub_name]) { + warn!("Failed to clean up cache devices: {e}; they may need to be manually removed"); + } + + // The OriginSub DM device still exists in the kernel. + // Re-wrap it as a LinearDev. + let (dm_name, dm_uuid) = format_backstore_ids(pool_uuid, CacheRole::OriginSub); + let origin = LinearDev::setup( + get_dm(), + &dm_name, + Some(&dm_uuid), + self.data_tier.segments.map_to_dm(), + ) + .map_err(|e| StratisError::ActionAvailabilityError { + error: Box::new(StratisError::from(e)), + level: ActionAvailability::NoPoolChanges, + })?; + self.linear = Some(origin); + + cache_tier.destroy()?; + + Ok(()) + } + pub fn grow(&mut self, dev: DevUuid) -> StratisResult { self.data_tier.grow(dev) } diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index e084f49c02..479ebce841 100644 --- a/src/engine/strat_engine/backstore/backstore/v2.rs +++ b/src/engine/strat_engine/backstore/backstore/v2.rs @@ -17,7 +17,8 @@ use tempfile::TempDir; use devicemapper::{ CacheDev, CacheDevTargetTable, CacheTargetParams, DevId, Device, DmDevice, DmFlags, DmOptions, - LinearDev, LinearDevTargetParams, LinearTargetParams, Sectors, TargetLine, TargetTable, + LinearDev, LinearDevTargetParams, LinearDevTargetTable, LinearTargetParams, Sectors, + TargetLine, TargetTable, }; use crate::{ @@ -1390,6 +1391,79 @@ impl Backstore { } } + /// Remove caching from the pool. Replaces the dm-cache table on the + /// CacheRole::Cache device with a linear table pointing at the origin, + /// tears down the now-orphaned cache-sub and meta-sub devices, wipes + /// the cache blockdevs, and removes the cache tier. + /// + /// Precondition: self.cache_tier.is_some() && self.cap_device.cache.is_some() + /// Postcondition: self.cache_tier.is_none() && self.cap_device.cache.is_none() + /// && self.cap_device.origin.is_some() + /// && self.cap_device.placeholder.is_some() + /// + /// WARNING: metadata changing event + pub fn remove_cache(&mut self, pool_uuid: PoolUuid) -> StratisResult<()> { + let mut cache_tier = self + .cache_tier + .take() + .ok_or_else(|| StratisError::Msg("Pool does not have a cache".to_string()))?; + + self.cap_device + .cache + .take() + .expect("cache_tier.is_some() <=> self.cap_device.cache.is_some()"); + + let dm = get_dm(); + + // Recreate the origin linear device. init_cache consumed it via + // .take(), so cap_device.origin is None; rebuild from data tier + // segments. + let (origin_name, origin_uuid) = format_backstore_ids(pool_uuid, CacheRole::OriginSub); + let origin = LinearDev::setup( + dm, + &origin_name, + Some(&origin_uuid), + self.data_tier.segments.map_to_dm(), + )?; + + // Suspend the CacheRole::Cache device (currently a dm-cache target). + let (dm_name, _) = format_backstore_ids(pool_uuid, CacheRole::Cache); + dm.device_suspend( + &DevId::Name(&dm_name), + DmOptions::default().set_flags(DmFlags::DM_SUSPEND), + )?; + + // Replace the cache table with a linear table pointing at the + // origin — same shape as make_placeholder_dev. + let table = vec![TargetLine::new( + Sectors(0), + origin.size(), + LinearDevTargetParams::Linear(LinearTargetParams::new(origin.device(), Sectors(0))), + )]; + let raw_table = LinearDevTargetTable::new(table.clone()).to_raw_table(); + dm.table_load(&DevId::Name(&dm_name), &raw_table, DmOptions::default())?; + + // Resume with the new linear table. + dm.device_suspend(&DevId::Name(&dm_name), DmOptions::private())?; + + // Tear down the now-orphaned cache-sub and meta-sub devices. + let (cache_sub_name, _) = format_backstore_ids(pool_uuid, CacheRole::CacheSub); + let (meta_sub_name, _) = format_backstore_ids(pool_uuid, CacheRole::MetaSub); + remove_optional_devices(vec![cache_sub_name, meta_sub_name])?; + + // Wrap the CacheRole::Cache device as a LinearDev placeholder. + let (dm_name, dm_uuid) = format_backstore_ids(pool_uuid, CacheRole::Cache); + let placeholder = LinearDev::setup(dm, &dm_name, Some(&dm_uuid), table)?; + + self.cap_device.origin = Some(origin); + self.cap_device.placeholder = Some(placeholder); + + // Wipe the cache tier blockdevs. + cache_tier.destroy()?; + + Ok(()) + } + pub fn grow(&mut self, dev: DevUuid) -> StratisResult { self.data_tier.grow(dev) } diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index 44ead17a39..3652be414d 100644 --- a/src/engine/strat_engine/pool/dispatch.rs +++ b/src/engine/strat_engine/pool/dispatch.rs @@ -44,6 +44,17 @@ impl Pool for AnyPool { } } + fn remove_cache( + &mut self, + pool_uuid: PoolUuid, + pool_name: &str, + ) -> StratisResult> { + match self { + AnyPool::V1(p) => p.remove_cache(pool_uuid, pool_name), + AnyPool::V2(p) => p.remove_cache(pool_uuid, pool_name), + } + } + fn bind_clevis( &mut self, name: &Name, diff --git a/src/engine/strat_engine/pool/v1.rs b/src/engine/strat_engine/pool/v1.rs index b5035275ce..fa87691b0b 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -741,6 +741,29 @@ impl Pool for StratPool { } } + #[pool_mutating_action("NoRequests")] + fn remove_cache( + &mut self, + pool_uuid: PoolUuid, + pool_name: &str, + ) -> StratisResult> { + if !self.has_cache() { + return Ok(SetDeleteAction::empty()); + } + + let cache_uuids: Vec<_> = self + .backstore + .cachedevs() + .into_iter() + .map(|(uuid, _)| uuid) + .collect(); + + self.backstore + .remove_cache(&mut self.thin_pool, pool_uuid)?; + self.write_metadata(pool_name)?; + Ok(SetDeleteAction::new(cache_uuids, vec![])) + } + #[pool_mutating_action("NoRequests")] #[pool_rollback] fn bind_clevis( @@ -2140,6 +2163,106 @@ mod tests { ); } + fn test_remove_cache_direct(paths: &[&Path]) { + assert!(paths.len() > 1); + + let (cache_paths, data_paths) = paths.split_at(1); + + let devices = ProcessedPathInfos::try_from(data_paths).unwrap(); + let (stratis_devices, unowned_devices) = devices.unpack(); + stratis_devices.error_on_not_empty().unwrap(); + + let name = "stratis-test-pool"; + let (uuid, mut pool) = StratPool::initialize(name, unowned_devices, None).unwrap(); + invariant(&pool, name); + + assert!(!pool.has_cache()); + let result = pool.remove_cache(uuid, name).unwrap(); + assert!(!result.is_changed()); + + let (_, fs_uuid, _) = pool + .create_filesystems(name, uuid, &[("stratis-filesystem", None, None)]) + .unwrap() + .changed() + .and_then(|mut fs| fs.pop()) + .unwrap(); + invariant(&pool, name); + + let tmp_dir = tempfile::Builder::new() + .prefix("stratis_testing") + .tempdir() + .unwrap(); + let new_file = tmp_dir.path().join("stratis_test.txt"); + let bytestring = b"some bytes"; + { + let (_, fs) = pool.get_filesystem(fs_uuid).unwrap(); + mount( + Some(&fs.devnode()), + tmp_dir.path(), + Some("xfs"), + MsFlags::empty(), + None as Option<&str>, + ) + .unwrap(); + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&new_file) + .unwrap() + .write_all(bytestring) + .unwrap(); + } + + pool.init_cache(uuid, name, cache_paths, true).unwrap(); + invariant(&pool, name); + assert!(pool.has_cache()); + + let metadata = pool.record(name); + assert!(metadata.backstore.cache_tier.is_some()); + + let result = pool.remove_cache(uuid, name).unwrap(); + assert!(result.is_changed()); + assert!(!pool.has_cache()); + invariant(&pool, name); + + let metadata = pool.record(name); + assert_matches!(metadata.backstore.cache_tier, None); + + let mut buf = [0u8; 10]; + { + OpenOptions::new() + .read(true) + .open(&new_file) + .unwrap() + .read_exact(&mut buf) + .unwrap(); + } + assert_eq!(&buf, bytestring); + + let result = pool.remove_cache(uuid, name).unwrap(); + assert!(!result.is_changed()); + + umount(tmp_dir.path()).unwrap(); + pool.teardown(uuid).unwrap(); + } + + #[test] + fn loop_test_remove_cache_direct() { + loopbacked::test_with_spec( + &loopbacked::DeviceLimits::Range(3, 4, None), + test_remove_cache_direct, + ); + } + + #[test] + fn real_test_remove_cache_direct() { + real::test_with_spec( + &real::DeviceLimits::AtLeast(2, None, None), + test_remove_cache_direct, + ); + } + /// Tests online reencryption functionality by performing online reencryption and then stopping and /// starting the pool. fn clevis_test_online_reencrypt(paths: &[&Path]) { diff --git a/src/engine/strat_engine/pool/v2.rs b/src/engine/strat_engine/pool/v2.rs index 41cf57d18c..3f6d23fad8 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -673,6 +673,28 @@ impl Pool for StratPool { } } + #[pool_mutating_action("NoRequests")] + fn remove_cache( + &mut self, + pool_uuid: PoolUuid, + pool_name: &str, + ) -> StratisResult> { + if !self.has_cache() { + return Ok(SetDeleteAction::empty()); + } + + let cache_uuids: Vec<_> = self + .backstore + .cachedevs() + .into_iter() + .map(|(uuid, _)| uuid) + .collect(); + + self.backstore.remove_cache(pool_uuid)?; + self.write_metadata(pool_name)?; + Ok(SetDeleteAction::new(cache_uuids, vec![])) + } + #[pool_mutating_action("NoRequests")] fn bind_clevis( &mut self, @@ -2262,6 +2284,112 @@ mod tests { ); } + fn test_remove_cache_direct(paths: &[&Path]) { + assert!(paths.len() > 1); + + let (cache_paths, data_paths) = paths.split_at(1); + + let devices = ProcessedPathInfos::try_from(data_paths).unwrap(); + let (stratis_devices, unowned_devices) = devices.unpack(); + stratis_devices.error_on_not_empty().unwrap(); + + let name = "stratis-test-pool"; + let (uuid, mut pool) = StratPool::initialize( + name, + unowned_devices, + None, + ValidatedIntegritySpec::default(), + ) + .unwrap(); + invariant(&pool, name); + + assert!(!pool.has_cache()); + let result = pool.remove_cache(uuid, name).unwrap(); + assert!(!result.is_changed()); + + let (_, fs_uuid, _) = pool + .create_filesystems(name, uuid, &[("stratis-filesystem", None, None)]) + .unwrap() + .changed() + .and_then(|mut fs| fs.pop()) + .unwrap(); + invariant(&pool, name); + + let tmp_dir = tempfile::Builder::new() + .prefix("stratis_testing") + .tempdir() + .unwrap(); + let new_file = tmp_dir.path().join("stratis_test.txt"); + let bytestring = b"some bytes"; + { + let (_, fs) = pool.get_filesystem(fs_uuid).unwrap(); + mount( + Some(&fs.devnode()), + tmp_dir.path(), + Some("xfs"), + MsFlags::empty(), + None as Option<&str>, + ) + .unwrap(); + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&new_file) + .unwrap() + .write_all(bytestring) + .unwrap(); + } + + pool.init_cache(uuid, name, cache_paths, true).unwrap(); + invariant(&pool, name); + assert!(pool.has_cache()); + + let metadata = pool.record(name); + assert!(metadata.backstore.cache_tier.is_some()); + + let result = pool.remove_cache(uuid, name).unwrap(); + assert!(result.is_changed()); + assert!(!pool.has_cache()); + invariant(&pool, name); + + let metadata = pool.record(name); + assert_matches!(metadata.backstore.cache_tier, None); + + let mut buf = [0u8; 10]; + { + OpenOptions::new() + .read(true) + .open(&new_file) + .unwrap() + .read_exact(&mut buf) + .unwrap(); + } + assert_eq!(&buf, bytestring); + + let result = pool.remove_cache(uuid, name).unwrap(); + assert!(!result.is_changed()); + + umount(tmp_dir.path()).unwrap(); + pool.teardown(uuid).unwrap(); + } + + #[test] + fn loop_test_remove_cache_direct() { + loopbacked::test_with_spec( + &loopbacked::DeviceLimits::Range(3, 4, None), + test_remove_cache_direct, + ); + } + + #[test] + fn real_test_remove_cache_direct() { + real::test_with_spec( + &real::DeviceLimits::AtLeast(2, None, None), + test_remove_cache_direct, + ); + } + /// Tests online encryption functionality by performing online encryption and then stopping and /// starting the pool. fn clevis_test_online_encrypt(paths: &[&Path]) { diff --git a/src/engine/types/actions.rs b/src/engine/types/actions.rs index 414cb1cb88..e03a2ac8a4 100644 --- a/src/engine/types/actions.rs +++ b/src/engine/types/actions.rs @@ -665,6 +665,24 @@ impl Display for SetDeleteAction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.changed.is_empty() { + write!(f, "No cache devices to remove; no action taken") + } else { + write!( + f, + "Cache devices with UUIDs {} were successfully removed", + self.changed + .iter() + .map(|u| u.to_string()) + .collect::>() + .join(", ") + ) + } + } +} + /// Action indicating a Clevis binding regeneration pub struct RegenAction; diff --git a/stratisd.conf b/stratisd.conf index 44d468f1b3..b983a34cbf 100644 --- a/stratisd.conf +++ b/stratisd.conf @@ -46,6 +46,9 @@ + + @@ -94,6 +97,10 @@ send_interface="org.storage.stratis3.Manager.r9" send_member="EngineStateReport"/> + + @@ -133,6 +140,10 @@ + + diff --git a/tests/client-dbus/src/stratisd_client_dbus/_constants.py b/tests/client-dbus/src/stratisd_client_dbus/_constants.py index 15a32a8de0..63fb51fd15 100644 --- a/tests/client-dbus/src/stratisd_client_dbus/_constants.py +++ b/tests/client-dbus/src/stratisd_client_dbus/_constants.py @@ -18,7 +18,7 @@ SERVICE = "org.storage.stratis3" TOP_OBJECT = "/org/storage/stratis3" -REVISION_NUMBER = 9 +REVISION_NUMBER = 10 REVISION = f"r{REVISION_NUMBER}" diff --git a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py index 4088c2b396..ac03f6fd9f 100644 --- a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py +++ b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py @@ -14,8 +14,8 @@ """, - "org.storage.stratis3.Manager.r9": """ - + "org.storage.stratis3.Manager.r10": """ + @@ -84,8 +84,8 @@ """, - "org.storage.stratis3.Report.r9": """ - + "org.storage.stratis3.Report.r10": """ + @@ -94,8 +94,8 @@ """, - "org.storage.stratis3.blockdev.r9": """ - + "org.storage.stratis3.blockdev.r10": """ + @@ -122,8 +122,8 @@ """, - "org.storage.stratis3.filesystem.r9": """ - + "org.storage.stratis3.filesystem.r10": """ + @@ -150,8 +150,8 @@ """, - "org.storage.stratis3.pool.r9": """ - + "org.storage.stratis3.pool.r10": """ +