From 91602385dbbae61bd48dd69bec1f18fa7efaeffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 26 Feb 2026 07:25:29 +0000 Subject: [PATCH 01/22] Do not block storage in SetConfig calls * Use a proxy to access D-Bus properties. * Run SetConfig on a separate Tokio task. --- rust/agama-storage/src/client.rs | 115 ++++++++++++++++++++------- rust/agama-storage/src/lib.rs | 2 + rust/agama-storage/src/message.rs | 6 +- rust/agama-storage/src/proxies.rs | 83 +++++++++++++++++++ rust/agama-storage/src/service.rs | 17 ++-- rust/agama-storage/src/test_utils.rs | 10 ++- 6 files changed, 193 insertions(+), 40 deletions(-) create mode 100644 rust/agama-storage/src/proxies.rs diff --git a/rust/agama-storage/src/client.rs b/rust/agama-storage/src/client.rs index de67d37714..b1d491d61f 100644 --- a/rust/agama-storage/src/client.rs +++ b/rust/agama-storage/src/client.rs @@ -26,10 +26,12 @@ use agama_utils::{ }; use async_trait::async_trait; use serde_json::Value; -use std::sync::Arc; -use tokio::sync::RwLock; +use std::{future::Future, sync::Arc}; +use tokio::sync::{oneshot, RwLock}; use zbus::{names::BusName, zvariant::OwnedObjectPath, Connection, Message}; +use crate::proxies::Storage1Proxy; + const SERVICE_NAME: &str = "org.opensuse.Agama.Storage1"; const OBJECT_PATH: &str = "/org/opensuse/Agama/Storage1"; const INTERFACE: &str = "org.opensuse.Agama.Storage1"; @@ -63,20 +65,26 @@ pub trait StorageClient { &self, product: Arc>, config: Option, - ) -> Result<(), Error>; + ) -> oneshot::Receiver>; async fn solve_config_model(&self, model: Value) -> Result, Error>; async fn set_locale(&self, locale: String) -> Result<(), Error>; } /// D-Bus client for the storage service #[derive(Clone)] -pub struct Client { +pub struct Client<'a> { connection: Connection, + proxy: Storage1Proxy<'a>, } -impl Client { - pub fn new(connection: Connection) -> Self { - Self { connection } +impl<'a> Client<'a> { + pub async fn new(connection: Connection) -> Self { + let proxy = Storage1Proxy::new(&connection).await.unwrap(); + proxy + .config() + .await + .expect("Failed to read the storage D-Bus interface."); + Self { connection, proxy } } async fn call( @@ -94,7 +102,7 @@ impl Client { } #[async_trait] -impl StorageClient for Client { +impl<'a> StorageClient for Client<'a> { async fn activate(&self) -> Result<(), Error> { self.call("Activate", &()).await?; Ok(()) @@ -121,48 +129,53 @@ impl StorageClient for Client { } async fn get_system(&self) -> Result, Error> { - let message = self.call("GetSystem", &()).await?; - try_from_message(message) + let raw_json = self.proxy.system().await?; + try_from_string(&raw_json) } async fn get_config(&self) -> Result, Error> { - let message = self.call("GetConfig", &()).await?; - try_from_message(message) + let raw_json = self.proxy.config().await?; + try_from_string(&raw_json) } async fn get_config_from_model(&self, model: Value) -> Result, Error> { - let message = self - .call("GetConfigFromModel", &(model.to_string())) - .await?; - try_from_message(message) + let raw_json = self.proxy.get_config_from_model(&model.to_string()).await?; + try_from_string(&raw_json) } async fn get_config_model(&self) -> Result, Error> { - let message = self.call("GetConfigModel", &()).await?; - try_from_message(message) + let raw_json = self.proxy.config_model().await?; + try_from_string(&raw_json) } async fn get_proposal(&self) -> Result, Error> { - let message = self.call("GetProposal", &()).await?; - try_from_message(message) + let raw_json = self.proxy.proposal().await?; + try_from_string(&raw_json) } async fn get_issues(&self) -> Result, Error> { - let message = self.call("GetIssues", &()).await?; - try_from_message(message) + let raw_json = self.proxy.issues().await?; + try_from_string(&raw_json) } async fn set_config( &self, product: Arc>, config: Option, - ) -> Result<(), Error> { - let product_guard = product.read().await; - let product_json = serde_json::to_string(&*product_guard)?; - let config = config.filter(|c| c.has_value()); - let config_json = serde_json::to_string(&config)?; - self.call("SetConfig", &(product_json, config_json)).await?; - Ok(()) + ) -> oneshot::Receiver> { + let product = product.clone(); + let client = DBusClient::new(self.connection.clone()); + run_in_background(async move { + let product_guard = product.read().await; + let product_json = serde_json::to_string(&*product_guard)?; + let config = config.filter(|c| c.has_value()); + let config_json = serde_json::to_string(&config)?; + let result: Result<(), Error> = client + .call("SetConfig", &(product_json, config_json)) + .await + .map(|_| ()); + result + }) } async fn solve_config_model(&self, model: Value) -> Result, Error> { @@ -176,6 +189,27 @@ impl StorageClient for Client { } } +fn run_in_background(func: F) -> oneshot::Receiver> +where + F: Future> + Send + 'static, +{ + let (tx, rx) = oneshot::channel::>(); + tokio::spawn(async move { + let result = func.await; + _ = tx.send(result); + }); + rx +} + +fn try_from_string(raw_json: &str) -> Result { + let json: Value = serde_json::from_str(&raw_json)?; + if json.is_null() { + return Ok(T::default()); + } + let value = serde_json::from_value(json)?; + Ok(value) +} + fn try_from_message( message: Message, ) -> Result { @@ -187,3 +221,26 @@ fn try_from_message( let value = serde_json::from_value(json)?; Ok(value) } + +#[derive(Clone)] +pub struct DBusClient { + connection: Connection, +} + +impl DBusClient { + pub fn new(connection: Connection) -> Self { + Self { connection } + } + async fn call( + &self, + method: &str, + body: &T, + ) -> Result { + let bus = BusName::try_from(SERVICE_NAME.to_string())?; + let path = OwnedObjectPath::try_from(OBJECT_PATH)?; + self.connection + .call_method(Some(&bus), &path, Some(INTERFACE), method, body) + .await + .map_err(|e| e.into()) + } +} diff --git a/rust/agama-storage/src/lib.rs b/rust/agama-storage/src/lib.rs index 7e9daad3b1..d47604a09c 100644 --- a/rust/agama-storage/src/lib.rs +++ b/rust/agama-storage/src/lib.rs @@ -25,6 +25,8 @@ pub mod client; pub mod message; mod monitor; +mod proxies; + pub mod test_utils; #[cfg(test)] diff --git a/rust/agama-storage/src/message.rs b/rust/agama-storage/src/message.rs index 362d12974e..7e707cfdfc 100644 --- a/rust/agama-storage/src/message.rs +++ b/rust/agama-storage/src/message.rs @@ -21,7 +21,9 @@ use agama_utils::{actor::Message, api::storage::Config, products::ProductSpec}; use serde_json::Value; use std::sync::Arc; -use tokio::sync::RwLock; +use tokio::sync::{oneshot, RwLock}; + +use crate::client; #[derive(Clone)] pub struct Activate; @@ -121,7 +123,7 @@ impl SetConfig { } impl Message for SetConfig { - type Reply = (); + type Reply = oneshot::Receiver>; } #[derive(Clone)] diff --git a/rust/agama-storage/src/proxies.rs b/rust/agama-storage/src/proxies.rs new file mode 100644 index 0000000000..57ce0902cf --- /dev/null +++ b/rust/agama-storage/src/proxies.rs @@ -0,0 +1,83 @@ +//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1` +//! +//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. +//! Source: `storage.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + default_service = "org.opensuse.Agama.Storage1", + default_path = "/org/opensuse/Agama/Storage1", + interface = "org.opensuse.Agama.Storage1", + assume_defaults = true +)] +pub trait Storage1 { + /// Activate method + fn activate(&self) -> zbus::Result<()>; + + /// Finish method + fn finish(&self) -> zbus::Result<()>; + + /// GetConfigFromModel method + fn get_config_from_model(&self, model: &str) -> zbus::Result; + + /// Install method + fn install(&self) -> zbus::Result<()>; + + /// Probe method + fn probe(&self) -> zbus::Result<()>; + + /// SetConfig method + fn set_config(&self, product: &str, config: &str) -> zbus::Result<()>; + + /// SetLocale method + fn set_locale(&self, locale: &str) -> zbus::Result<()>; + + /// SolveConfigModel method + fn solve_config_model(&self, model: &str) -> zbus::Result; + + /// Umount method + fn umount(&self) -> zbus::Result<()>; + + /// ProgressChanged signal + #[zbus(signal)] + fn progress_changed(&self, progress: &str) -> zbus::Result<()>; + + /// ProgressFinished signal + #[zbus(signal)] + fn progress_finished(&self) -> zbus::Result<()>; + + /// Config property + #[zbus(property)] + fn config(&self) -> zbus::Result; + + /// ConfigModel property + #[zbus(property)] + fn config_model(&self) -> zbus::Result; + + /// Issues property + #[zbus(property)] + fn issues(&self) -> zbus::Result; + + /// Proposal property + #[zbus(property)] + fn proposal(&self) -> zbus::Result; + + /// System property + #[zbus(property)] + fn system(&self) -> zbus::Result; +} diff --git a/rust/agama-storage/src/service.rs b/rust/agama-storage/src/service.rs index b79ed0ebd5..11d0522009 100644 --- a/rust/agama-storage/src/service.rs +++ b/rust/agama-storage/src/service.rs @@ -30,6 +30,7 @@ use agama_utils::{ }; use async_trait::async_trait; use serde_json::Value; +use tokio::sync::oneshot; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -75,13 +76,13 @@ impl Starter { pub async fn start(self) -> Result, Error> { let client = match self.client { Some(client) => client, - None => Box::new(Client::new(self.dbus.clone())), + None => Box::new(Client::new(self.dbus.clone()).await), }; let service = Service { client }; let handler = actor::spawn(service); - let monitor = Monitor::new(self.progress, self.issues, self.events, self.dbus); + let monitor = Monitor::new(self.progress, self.issues, self.events, self.dbus).await; monitor::spawn(monitor)?; Ok(handler) } @@ -190,11 +191,15 @@ impl MessageHandler for Service { #[async_trait] impl MessageHandler for Service { - async fn handle(&mut self, message: message::SetConfig) -> Result<(), Error> { - self.client + async fn handle( + &mut self, + message: message::SetConfig, + ) -> Result>, Error> { + let rx = self + .client .set_config(message.product, message.config) - .await?; - Ok(()) + .await; + Ok(rx) } } diff --git a/rust/agama-storage/src/test_utils.rs b/rust/agama-storage/src/test_utils.rs index 5f3859ab3d..81deddec7a 100644 --- a/rust/agama-storage/src/test_utils.rs +++ b/rust/agama-storage/src/test_utils.rs @@ -31,7 +31,7 @@ use agama_utils::{ }; use async_trait::async_trait; use serde_json::Value; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::{oneshot, Mutex, RwLock}; use crate::{ client::{Error, StorageClient}, @@ -149,10 +149,14 @@ impl StorageClient for TestClient { &self, _product: Arc>, config: Option, - ) -> Result<(), Error> { + ) -> oneshot::Receiver> { let mut state = self.state.lock().await; state.config = config; - Ok(()) + let (tx, rx) = oneshot::channel::>(); + tokio::spawn(async move { + _ = tx.send(Ok(())); + }); + rx } async fn solve_config_model(&self, _model: Value) -> Result, Error> { From 35b755865fd69f535cab8ee5b26346d70b93155d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 26 Feb 2026 10:57:18 +0000 Subject: [PATCH 02/22] Add an call_future to the actors --- rust/agama-utils/src/actor.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rust/agama-utils/src/actor.rs b/rust/agama-utils/src/actor.rs index e3f0bb5ed3..3c9072138f 100644 --- a/rust/agama-utils/src/actor.rs +++ b/rust/agama-utils/src/actor.rs @@ -253,6 +253,26 @@ impl Handler { .map_err(|_| Error::Send(A::name()))?; Ok(()) } + + /// Sends a message and returns a channel to get the answer later. + /// + /// TODO: return a future instead of a oneshot channel. + /// + /// * `msg`: message to send to the actor. + pub fn call_future( + &self, + msg: M, + ) -> Result>, A::Error> + where + A: MessageHandler, + { + let (tx, rx) = oneshot::channel(); + let message = Envelope::new(msg, Some(tx)); + self.sender + .send(Box::new(message)) + .map_err(|_| Error::Send(A::name()))?; + Ok(rx) + } } /// Spawns a Tokio task and process the messages coming from the action handler. From 115c659b2cca5fef3fcea64a4e38e768ae52abcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 26 Feb 2026 11:01:14 +0000 Subject: [PATCH 03/22] Temporarily disable iSCSI, bootloader and storage monitor code --- rust/agama-manager/src/service.rs | 19 +++++++++++-------- rust/agama-manager/src/tasks/runner.rs | 6 +++--- rust/agama-storage/src/monitor.rs | 9 +++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index b6c1a9f2c3..79d1731ba2 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -622,7 +622,7 @@ impl MessageHandler for Service { let l10n = self.l10n.call(l10n::message::GetSystem).await?; let manager = self.system.clone(); let storage = self.storage.call(storage::message::GetSystem).await?; - let iscsi = self.iscsi.call(iscsi::message::GetSystem).await?; + // let iscsi = self.iscsi.call(iscsi::message::GetSystem).await?; let network = self.network.get_system().await?; let s390 = if let Some(s390) = &self.s390 { @@ -645,7 +645,7 @@ impl MessageHandler for Service { manager, network, storage, - iscsi, + iscsi: Default::default(), s390, software, }) @@ -658,13 +658,15 @@ impl MessageHandler for Service { /// /// It includes user and default values. async fn handle(&mut self, _message: message::GetExtendedConfig) -> Result { - let bootloader = self - .bootloader - .call(bootloader::message::GetConfig) - .await? - .to_option(); + let bootloader = None; + // let bootloader = self + // .bootloader + // .call(bootloader::message::GetConfig) + // .await? + // .to_option(); let hostname = self.hostname.call(hostname::message::GetConfig).await?; - let iscsi = self.iscsi.call(iscsi::message::GetConfig).await?; + // let iscsi = self.iscsi.call(iscsi::message::GetConfig).await?; + let iscsi = Default::default(); let l10n = self.l10n.call(l10n::message::GetConfig).await?; // FIXME: the security service might be busy asking some question, so it cannot answer. // By now, let's consider that the whole security configuration is set by the user @@ -722,6 +724,7 @@ impl MessageHandler for Service { /// Sets the user configuration with the given values. async fn handle(&mut self, message: message::SetConfig) -> Result<(), Error> { checks::check_stage(&self.progress, Stage::Configuring).await?; + tracing::debug!("DEBUG: SetConfig handler (calling set_config)"); self.set_config(message.config).await } } diff --git a/rust/agama-manager/src/tasks/runner.rs b/rust/agama-manager/src/tasks/runner.rs index 7bb9835a2f..97ffb398bb 100644 --- a/rust/agama-manager/src/tasks/runner.rs +++ b/rust/agama-manager/src/tasks/runner.rs @@ -375,9 +375,9 @@ impl SetConfigAction { self.progress .call(progress::message::Next::new(Scope::Manager)) .await?; - self.iscsi - .call(iscsi::message::SetConfig::new(config.iscsi.clone())) - .await?; + // self.iscsi + // .call(iscsi::message::SetConfig::new(config.iscsi.clone())) + // .await?; if let Some(s390) = &self.s390 { self.progress diff --git a/rust/agama-storage/src/monitor.rs b/rust/agama-storage/src/monitor.rs index 7b98fcea09..1d89bf0813 100644 --- a/rust/agama-storage/src/monitor.rs +++ b/rust/agama-storage/src/monitor.rs @@ -104,11 +104,11 @@ pub struct Monitor { issues: Handler, events: event::Sender, connection: Connection, - client: Client, + // client: Client<'a>, } impl Monitor { - pub fn new( + pub async fn new( progress: Handler, issues: Handler, events: event::Sender, @@ -119,7 +119,7 @@ impl Monitor { issues, events, connection: connection.clone(), - client: Client::new(connection), + // client: Client::new(connection).await, } } @@ -142,7 +142,8 @@ impl Monitor { } async fn update_issues(&self) -> Result<(), Error> { - let issues = self.client.get_issues().await?; + // let issues = self.client.get_issues().await?; + let issues = vec![]; self.issues .cast(issue::message::Set::new(Scope::Storage, issues))?; Ok(()) From 01e582fc7902b2109c87bf97029738fa12f02e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 27 Feb 2026 09:21:16 +0000 Subject: [PATCH 04/22] Add a client to interact with the D-Bus service --- rust/Cargo.lock | 15 ++ rust/Cargo.toml | 1 + rust/agama-storage-client/Cargo.toml | 17 ++ rust/agama-storage-client/src/dbus.rs | 120 +++++++++ rust/agama-storage-client/src/lib.rs | 32 +++ rust/agama-storage-client/src/message.rs | 157 ++++++++++++ rust/agama-storage-client/src/proxies.rs | 25 ++ .../src/proxies/bootloader.rs | 35 +++ .../src/proxies/storage1.rs | 83 +++++++ rust/agama-storage-client/src/service.rs | 231 ++++++++++++++++++ rust/agama-utils/src/lib.rs | 9 + 11 files changed, 725 insertions(+) create mode 100644 rust/agama-storage-client/Cargo.toml create mode 100644 rust/agama-storage-client/src/dbus.rs create mode 100644 rust/agama-storage-client/src/lib.rs create mode 100644 rust/agama-storage-client/src/message.rs create mode 100644 rust/agama-storage-client/src/proxies.rs create mode 100644 rust/agama-storage-client/src/proxies/bootloader.rs create mode 100644 rust/agama-storage-client/src/proxies/storage1.rs create mode 100644 rust/agama-storage-client/src/service.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index ea440e35db..1b6e557609 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -415,6 +415,21 @@ dependencies = [ "zbus", ] +[[package]] +name = "agama-storage-client" +version = "0.1.0" +dependencies = [ + "agama-utils", + "async-trait", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "zbus", +] + [[package]] name = "agama-transfer" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 11ce98cef5..956ea3c27a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -16,6 +16,7 @@ members = [ "agama-server", "agama-software", "agama-storage", + "agama-storage-client", "agama-transfer", "agama-utils", "agama-users", diff --git a/rust/agama-storage-client/Cargo.toml b/rust/agama-storage-client/Cargo.toml new file mode 100644 index 0000000000..ee74da750d --- /dev/null +++ b/rust/agama-storage-client/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "agama-storage-client" +version = "0.1.0" +rust-version.workspace = true +edition.workspace = true + +[dependencies] +agama-utils = { path = "../agama-utils" } +thiserror = "2.0.16" +async-trait = "0.1.89" +zbus = "5.7.1" +tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "sync"] } +tokio-stream = "0.1.16" +serde = { version = "1.0.228" } +serde_json = "1.0.140" +tracing = "0.1.44" + diff --git a/rust/agama-storage-client/src/dbus.rs b/rust/agama-storage-client/src/dbus.rs new file mode 100644 index 0000000000..93d48012ca --- /dev/null +++ b/rust/agama-storage-client/src/dbus.rs @@ -0,0 +1,120 @@ +// Copyright (c) [2025-2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use agama_utils::{api::storage::Config, products::ProductSpec}; +use serde_json::Value; +use zbus::{names::BusName, zvariant::OwnedObjectPath, Message}; + +const SERVICE_NAME: &str = "org.opensuse.Agama.Storage1"; +const OBJECT_PATH: &str = "/org/opensuse/Agama/Storage1"; +const INTERFACE: &str = "org.opensuse.Agama.Storage1"; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + DBus(#[from] zbus::Error), + #[error(transparent)] + DBusName(#[from] zbus::names::Error), + #[error(transparent)] + DBusVariant(#[from] zbus::zvariant::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), +} + +/// Generic client for the storage D-Bus service. +#[derive(Clone)] +pub struct StorageDBusClient { + connection: zbus::Connection, +} + +impl StorageDBusClient { + pub fn new(connection: zbus::Connection) -> Self { + Self { connection } + } + + pub async fn call_action(&self, action: String) -> Result<(), Error> { + self.call(&action, &()).await?; + Ok(()) + } + + /// Sets the storage configuration. + pub async fn set_storage_config( + &self, + product: &ProductSpec, + config: Option, + ) -> Result<(), Error> { + let product_json = serde_json::to_string(&*product)?; + let config = config.filter(|c| c.has_value()); + let config_json = serde_json::to_string(&config)?; + self.call("SetConfig", &(product_json, config_json)).await?; + Ok(()) + } + + pub async fn set_bootloader_config(&self, config: serde_json::Value) -> Result<(), Error> { + self.call("SetConfig", &(config.to_string())).await?; + Ok(()) + } + + /// Solves the configuration model. + pub async fn solve_config_model(&self, model: Value) -> Result, Error> { + let message = self.call("SolveConfigModel", &(model.to_string())).await?; + try_from_message(message) + } + + pub async fn set_locale(&self, locale: String) -> Result<(), Error> { + self.call("SetLocale", &(locale)).await?; + Ok(()) + } + + /// Calls the given method on the `org.opensuse.Agama.Storage1` interaface. + pub async fn call( + &self, + method: &str, + body: &T, + ) -> Result { + let bus = BusName::try_from(SERVICE_NAME.to_string())?; + let path = OwnedObjectPath::try_from(OBJECT_PATH)?; + self.connection + .call_method(Some(&bus), &path, Some(INTERFACE), method, body) + .await + .map_err(|e| e.into()) + } +} + +fn try_from_message( + message: Message, +) -> Result { + let raw_json: String = message.body().deserialize()?; + Ok(try_from_string(&raw_json)?) +} + +/// Converts a string into a Value. +/// +/// If the string is "null", return the default value. +pub fn try_from_string( + raw_json: &str, +) -> Result { + let json: serde_json::Value = serde_json::from_str(&raw_json)?; + if json.is_null() { + return Ok(T::default()); + } + let value = serde_json::from_value(json)?; + Ok(value) +} diff --git a/rust/agama-storage-client/src/lib.rs b/rust/agama-storage-client/src/lib.rs new file mode 100644 index 0000000000..07957d1669 --- /dev/null +++ b/rust/agama-storage-client/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +//! Service to interact with the storage D-Bus interface. +//! +//! This service tries to simplify the interaction with the storage D-Bus service. It implements an +//! actor that offers a set of operations to read and set the configuration. + +pub mod service; +pub use service::{Error, Service}; + +pub mod message; + +mod dbus; +mod proxies; diff --git a/rust/agama-storage-client/src/message.rs b/rust/agama-storage-client/src/message.rs new file mode 100644 index 0000000000..b63b4b2bfc --- /dev/null +++ b/rust/agama-storage-client/src/message.rs @@ -0,0 +1,157 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use std::sync::Arc; + +use agama_utils::{ + actor::Message, + api::{bootloader, storage::Config, Issue}, + products::ProductSpec, + BoxFuture, +}; +use tokio::sync::RwLock; + +use crate::service; + +pub struct CallAction { + pub action: String, +} + +impl CallAction { + pub fn new(action: &str) -> Self { + Self { + action: action.to_string(), + } + } +} + +impl Message for CallAction { + type Reply = (); +} + +pub struct SetStorageConfig { + pub product: Arc>, + pub config: Option, +} + +impl SetStorageConfig { + pub fn new(product: Arc>, config: Option) -> Self { + Self { product, config } + } +} + +impl Message for SetStorageConfig { + type Reply = BoxFuture>; +} + +pub struct GetStorageConfig; + +impl Message for GetStorageConfig { + type Reply = Option; +} + +pub struct GetSystem; + +impl Message for GetSystem { + type Reply = Option; +} + +pub struct GetProposal; + +impl Message for GetProposal { + type Reply = Option; +} + +pub struct GetIssues; + +impl Message for GetIssues { + type Reply = Vec; +} + +pub struct GetConfigFromModel { + pub model: serde_json::Value, +} + +impl GetConfigFromModel { + pub fn new(model: serde_json::Value) -> Self { + Self { model } + } +} + +impl Message for GetConfigFromModel { + type Reply = Option; +} + +pub struct GetConfigModel; + +impl Message for GetConfigModel { + type Reply = Option; +} + +pub struct SolveConfigModel { + pub model: serde_json::Value, +} + +impl SolveConfigModel { + pub fn new(model: serde_json::Value) -> Self { + Self { model } + } +} + +impl Message for SolveConfigModel { + type Reply = Option; +} + +pub struct GetBootloaderConfig; + +impl Message for GetBootloaderConfig { + type Reply = bootloader::Config; +} + +pub struct SetBootloaderConfig { + pub config: serde_json::Value, +} + +impl SetBootloaderConfig { + // FIXME: To be consistent, this action should receive + // the bootloader configuration. However, it uses an internal + // FullConfig struct defined in the agama-bootloader::client module. + pub fn new(config: serde_json::Value) -> Self { + Self { config } + } +} + +impl Message for SetBootloaderConfig { + type Reply = (); +} + +pub struct SetLocale { + pub locale: String, +} + +impl SetLocale { + pub fn new(locale: String) -> Self { + Self { locale } + } +} + +impl Message for SetLocale { + type Reply = (); +} diff --git a/rust/agama-storage-client/src/proxies.rs b/rust/agama-storage-client/src/proxies.rs new file mode 100644 index 0000000000..be7b204886 --- /dev/null +++ b/rust/agama-storage-client/src/proxies.rs @@ -0,0 +1,25 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +mod storage1; +pub use storage1::Storage1Proxy; + +mod bootloader; +pub use bootloader::BootloaderProxy; diff --git a/rust/agama-storage-client/src/proxies/bootloader.rs b/rust/agama-storage-client/src/proxies/bootloader.rs new file mode 100644 index 0000000000..aea9f058f2 --- /dev/null +++ b/rust/agama-storage-client/src/proxies/bootloader.rs @@ -0,0 +1,35 @@ +//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1.Bootloader` +//! +//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. +//! Source: `storage.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + default_service = "org.opensuse.Agama.Storage1", + default_path = "/org/opensuse/Agama/Storage1", + interface = "org.opensuse.Agama.Storage1.Bootloader", + assume_defaults = true +)] +pub trait Bootloader { + /// SetConfig method + fn set_config(&self, serialized_config: &str) -> zbus::Result; + + /// Config property + #[zbus(property)] + fn config(&self) -> zbus::Result; +} diff --git a/rust/agama-storage-client/src/proxies/storage1.rs b/rust/agama-storage-client/src/proxies/storage1.rs new file mode 100644 index 0000000000..57ce0902cf --- /dev/null +++ b/rust/agama-storage-client/src/proxies/storage1.rs @@ -0,0 +1,83 @@ +//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1` +//! +//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. +//! Source: `storage.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + default_service = "org.opensuse.Agama.Storage1", + default_path = "/org/opensuse/Agama/Storage1", + interface = "org.opensuse.Agama.Storage1", + assume_defaults = true +)] +pub trait Storage1 { + /// Activate method + fn activate(&self) -> zbus::Result<()>; + + /// Finish method + fn finish(&self) -> zbus::Result<()>; + + /// GetConfigFromModel method + fn get_config_from_model(&self, model: &str) -> zbus::Result; + + /// Install method + fn install(&self) -> zbus::Result<()>; + + /// Probe method + fn probe(&self) -> zbus::Result<()>; + + /// SetConfig method + fn set_config(&self, product: &str, config: &str) -> zbus::Result<()>; + + /// SetLocale method + fn set_locale(&self, locale: &str) -> zbus::Result<()>; + + /// SolveConfigModel method + fn solve_config_model(&self, model: &str) -> zbus::Result; + + /// Umount method + fn umount(&self) -> zbus::Result<()>; + + /// ProgressChanged signal + #[zbus(signal)] + fn progress_changed(&self, progress: &str) -> zbus::Result<()>; + + /// ProgressFinished signal + #[zbus(signal)] + fn progress_finished(&self) -> zbus::Result<()>; + + /// Config property + #[zbus(property)] + fn config(&self) -> zbus::Result; + + /// ConfigModel property + #[zbus(property)] + fn config_model(&self) -> zbus::Result; + + /// Issues property + #[zbus(property)] + fn issues(&self) -> zbus::Result; + + /// Proposal property + #[zbus(property)] + fn proposal(&self) -> zbus::Result; + + /// System property + #[zbus(property)] + fn system(&self) -> zbus::Result; +} diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs new file mode 100644 index 0000000000..82a1e7c409 --- /dev/null +++ b/rust/agama-storage-client/src/service.rs @@ -0,0 +1,231 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use std::future::Future; + +use agama_utils::{ + actor::{self, Actor, Handler, MessageHandler}, + api::{bootloader, storage::Config, Issue}, + BoxFuture, +}; +use async_trait::async_trait; +use tokio::sync::oneshot; + +use crate::{ + dbus::{try_from_string, StorageDBusClient}, + message, + proxies::{self, BootloaderProxy, Storage1Proxy}, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Actor(#[from] actor::Error), + #[error(transparent)] + DBus(#[from] zbus::Error), + #[error(transparent)] + DBusClient(#[from] crate::dbus::Error), + #[error(transparent)] + JSON(#[from] serde_json::Error), +} + +/// Builds and starts the service. +pub struct Starter { + dbus: zbus::Connection, +} + +impl Starter { + pub fn new(dbus: zbus::Connection) -> Self { + Self { dbus } + } + + /// Starts the service. + /// + /// As part of the initialization process, it reads the information + /// from each proxy to cache the current values. + pub async fn start(self) -> Result, Error> { + let storage_proxy = proxies::Storage1Proxy::new(&self.dbus).await?; + storage_proxy.config().await?; + + let bootloader_proxy = proxies::BootloaderProxy::new(&self.dbus).await?; + bootloader_proxy.config().await?; + + let service = Service { + storage_dbus: StorageDBusClient::new(self.dbus), + storage_proxy, + bootloader_proxy, + }; + let handler = actor::spawn(service); + Ok(handler) + } +} + +pub struct Service { + storage_dbus: StorageDBusClient, + storage_proxy: Storage1Proxy<'static>, + bootloader_proxy: BootloaderProxy<'static>, +} + +impl Actor for Service { + type Error = Error; +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::CallAction) -> Result<(), Error> { + Ok(self.storage_dbus.call_action(message.action).await?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::GetStorageConfig, + ) -> Result, Error> { + let raw_json = self.storage_proxy.config().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::GetSystem, + ) -> Result, Error> { + let raw_json = self.storage_proxy.system().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::GetProposal, + ) -> Result, Error> { + let raw_json = self.storage_proxy.proposal().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::GetIssues) -> Result, Error> { + let raw_json = self.storage_proxy.issues().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + message: message::GetConfigFromModel, + ) -> Result, Error> { + let raw_json = self + .storage_proxy + .get_config_from_model(&message.model.to_string()) + .await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::GetConfigModel, + ) -> Result, Error> { + let raw_json = self.storage_proxy.config_model().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + message: message::SolveConfigModel, + ) -> Result, Error> { + Ok(self.storage_dbus.solve_config_model(message.model).await?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::SetLocale) -> Result<(), Error> { + Ok(self.storage_dbus.set_locale(message.locale).await?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + message: message::SetStorageConfig, + ) -> Result>, Error> { + let client = self.storage_dbus.clone(); + let result = run_in_background(async move { + let product = message.product.read().await; + client.set_storage_config(&*product, message.config).await?; + Ok(()) + }); + Ok(Box::pin(async move { + result + .await + .map_err(|_| Error::Actor(actor::Error::Response(Self::name())))? + })) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::GetBootloaderConfig, + ) -> Result { + let raw_json = self.bootloader_proxy.config().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::SetBootloaderConfig) -> Result<(), Error> { + self.storage_dbus + .set_bootloader_config(message.config) + .await?; + Ok(()) + } +} + +fn run_in_background(func: F) -> oneshot::Receiver> +where + F: Future> + Send + 'static, +{ + let (tx, rx) = oneshot::channel::>(); + tokio::spawn(async move { + let result = func.await; + _ = tx.send(result); + }); + rx +} diff --git a/rust/agama-utils/src/lib.rs b/rust/agama-utils/src/lib.rs index fd33eac4fa..1eb102fe59 100644 --- a/rust/agama-utils/src/lib.rs +++ b/rust/agama-utils/src/lib.rs @@ -35,6 +35,15 @@ pub mod progress; pub mod question; pub mod test; +use std::future::Future; +use std::pin::Pin; + +/// A pinned, boxed, and sendable future. +pub type BoxFuture = Pin + Send + 'static>>; + +/// A pinned, boxed, and sendable future with a custom lifetime. +pub type BoxFutureWithLifetime<'a, T> = Pin + Send + 'a>>; + /// Does nothing at runtime, marking the text for translation. /// /// This is useful when you need both the untranslated id From b03bd4f3bf8f564ac1ccd4d5f28ffa17f5830baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 27 Feb 2026 10:55:57 +0000 Subject: [PATCH 05/22] Adapt agama-storage to the new storage client --- rust/Cargo.lock | 2 + rust/agama-storage/Cargo.toml | 2 + rust/agama-storage/src/client.rs | 152 +++++++++------------------ rust/agama-storage/src/lib.rs | 2 - rust/agama-storage/src/message.rs | 18 +++- rust/agama-storage/src/monitor.rs | 2 +- rust/agama-storage/src/proxies.rs | 83 --------------- rust/agama-storage/src/service.rs | 15 ++- rust/agama-storage/src/test_utils.rs | 19 ++-- 9 files changed, 90 insertions(+), 205 deletions(-) delete mode 100644 rust/agama-storage/src/proxies.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 1b6e557609..9d2f558dee 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -403,6 +403,7 @@ dependencies = [ name = "agama-storage" version = "0.1.0" dependencies = [ + "agama-storage-client", "agama-utils", "async-trait", "serde", @@ -412,6 +413,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-test", + "tracing", "zbus", ] diff --git a/rust/agama-storage/Cargo.toml b/rust/agama-storage/Cargo.toml index 6d1ade1f75..ccf287534f 100644 --- a/rust/agama-storage/Cargo.toml +++ b/rust/agama-storage/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] agama-utils = { path = "../agama-utils" } +agama-storage-client = { path = "../agama-storage-client" } thiserror = "2.0.16" async-trait = "0.1.89" zbus = "5.7.1" @@ -13,6 +14,7 @@ tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "sync"] } tokio-stream = "0.1.16" serde = { version = "1.0.228" } serde_json = "1.0.140" +tracing = "0.1.44" [dev-dependencies] test-context = "0.4.1" diff --git a/rust/agama-storage/src/client.rs b/rust/agama-storage/src/client.rs index b1d491d61f..4b08ef4fbb 100644 --- a/rust/agama-storage/src/client.rs +++ b/rust/agama-storage/src/client.rs @@ -20,18 +20,19 @@ //! Implements a client to access Agama's storage service. +use agama_storage_client::message; use agama_utils::{ + actor::{self, Handler}, api::{storage::Config, Issue}, products::ProductSpec, + BoxFuture, }; use async_trait::async_trait; use serde_json::Value; -use std::{future::Future, sync::Arc}; -use tokio::sync::{oneshot, RwLock}; +use std::sync::Arc; +use tokio::sync::RwLock; use zbus::{names::BusName, zvariant::OwnedObjectPath, Connection, Message}; -use crate::proxies::Storage1Proxy; - const SERVICE_NAME: &str = "org.opensuse.Agama.Storage1"; const OBJECT_PATH: &str = "/org/opensuse/Agama/Storage1"; const INTERFACE: &str = "org.opensuse.Agama.Storage1"; @@ -46,6 +47,10 @@ pub enum Error { DBusVariant(#[from] zbus::zvariant::Error), #[error(transparent)] Json(#[from] serde_json::Error), + #[error(transparent)] + Actor(#[from] actor::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } #[async_trait] @@ -65,163 +70,107 @@ pub trait StorageClient { &self, product: Arc>, config: Option, - ) -> oneshot::Receiver>; + ) -> Result>, Error>; async fn solve_config_model(&self, model: Value) -> Result, Error>; async fn set_locale(&self, locale: String) -> Result<(), Error>; } /// D-Bus client for the storage service #[derive(Clone)] -pub struct Client<'a> { - connection: Connection, - proxy: Storage1Proxy<'a>, +pub struct Client { + storage_dbus: Handler, } -impl<'a> Client<'a> { - pub async fn new(connection: Connection) -> Self { - let proxy = Storage1Proxy::new(&connection).await.unwrap(); - proxy - .config() - .await - .expect("Failed to read the storage D-Bus interface."); - Self { connection, proxy } +impl Client { + pub fn new(storage_dbus: Handler) -> Self { + Self { storage_dbus } } - async fn call( - &self, - method: &str, - body: &T, - ) -> Result { - let bus = BusName::try_from(SERVICE_NAME.to_string())?; - let path = OwnedObjectPath::try_from(OBJECT_PATH)?; - self.connection - .call_method(Some(&bus), &path, Some(INTERFACE), method, body) - .await - .map_err(|e| e.into()) + async fn call_action(&self, action: &str) -> Result<(), Error> { + self.storage_dbus + .call(message::CallAction::new(action)) + .await?; + Ok(()) } } #[async_trait] -impl<'a> StorageClient for Client<'a> { +impl StorageClient for Client { async fn activate(&self) -> Result<(), Error> { - self.call("Activate", &()).await?; - Ok(()) + self.call_action("Activate").await } async fn probe(&self) -> Result<(), Error> { - self.call("Probe", &()).await?; - Ok(()) + self.call_action("Probe").await } async fn install(&self) -> Result<(), Error> { - self.call("Install", &()).await?; - Ok(()) + self.call_action("Install").await } async fn finish(&self) -> Result<(), Error> { - self.call("Finish", &()).await?; - Ok(()) + self.call_action("Finish").await } async fn umount(&self) -> Result<(), Error> { - self.call("Umount", &()).await?; - Ok(()) + self.call_action("Umount").await } async fn get_system(&self) -> Result, Error> { - let raw_json = self.proxy.system().await?; - try_from_string(&raw_json) + let value = self.storage_dbus.call(message::GetSystem).await?; + Ok(value) } async fn get_config(&self) -> Result, Error> { - let raw_json = self.proxy.config().await?; - try_from_string(&raw_json) + let value = self.storage_dbus.call(message::GetStorageConfig).await?; + Ok(value) } async fn get_config_from_model(&self, model: Value) -> Result, Error> { - let raw_json = self.proxy.get_config_from_model(&model.to_string()).await?; - try_from_string(&raw_json) + let message = message::GetConfigFromModel::new(model); + let value = self.storage_dbus.call(message).await?; + Ok(value) } async fn get_config_model(&self) -> Result, Error> { - let raw_json = self.proxy.config_model().await?; - try_from_string(&raw_json) + let value = self.storage_dbus.call(message::GetConfigModel).await?; + Ok(value) } async fn get_proposal(&self) -> Result, Error> { - let raw_json = self.proxy.proposal().await?; - try_from_string(&raw_json) + let value = self.storage_dbus.call(message::GetProposal).await?; + Ok(value) } async fn get_issues(&self) -> Result, Error> { - let raw_json = self.proxy.issues().await?; - try_from_string(&raw_json) + let value = self.storage_dbus.call(message::GetIssues).await?; + Ok(value) } async fn set_config( &self, product: Arc>, config: Option, - ) -> oneshot::Receiver> { - let product = product.clone(); - let client = DBusClient::new(self.connection.clone()); - run_in_background(async move { - let product_guard = product.read().await; - let product_json = serde_json::to_string(&*product_guard)?; - let config = config.filter(|c| c.has_value()); - let config_json = serde_json::to_string(&config)?; - let result: Result<(), Error> = client - .call("SetConfig", &(product_json, config_json)) - .await - .map(|_| ()); - result - }) + ) -> Result>, Error> { + let message = message::SetStorageConfig::new(product.clone(), config); + let rx = self.storage_dbus.call(message).await?; + Ok(Box::pin(async move { rx.await.map_err(Error::from) })) } async fn solve_config_model(&self, model: Value) -> Result, Error> { - let message = self.call("SolveConfigModel", &(model.to_string())).await?; - try_from_message(message) + let message = message::SolveConfigModel::new(model); + let value = self.storage_dbus.call(message).await?; + Ok(value) } async fn set_locale(&self, locale: String) -> Result<(), Error> { - self.call("SetLocale", &(locale)).await?; + self.storage_dbus + .call(message::SetLocale::new(locale)) + .await?; Ok(()) } } -fn run_in_background(func: F) -> oneshot::Receiver> -where - F: Future> + Send + 'static, -{ - let (tx, rx) = oneshot::channel::>(); - tokio::spawn(async move { - let result = func.await; - _ = tx.send(result); - }); - rx -} - -fn try_from_string(raw_json: &str) -> Result { - let json: Value = serde_json::from_str(&raw_json)?; - if json.is_null() { - return Ok(T::default()); - } - let value = serde_json::from_value(json)?; - Ok(value) -} - -fn try_from_message( - message: Message, -) -> Result { - let raw_json: String = message.body().deserialize()?; - let json: Value = serde_json::from_str(&raw_json)?; - if json.is_null() { - return Ok(T::default()); - } - let value = serde_json::from_value(json)?; - Ok(value) -} - #[derive(Clone)] pub struct DBusClient { connection: Connection, @@ -231,7 +180,8 @@ impl DBusClient { pub fn new(connection: Connection) -> Self { Self { connection } } - async fn call( + + pub async fn call( &self, method: &str, body: &T, diff --git a/rust/agama-storage/src/lib.rs b/rust/agama-storage/src/lib.rs index d47604a09c..7e9daad3b1 100644 --- a/rust/agama-storage/src/lib.rs +++ b/rust/agama-storage/src/lib.rs @@ -25,8 +25,6 @@ pub mod client; pub mod message; mod monitor; -mod proxies; - pub mod test_utils; #[cfg(test)] diff --git a/rust/agama-storage/src/message.rs b/rust/agama-storage/src/message.rs index 7e707cfdfc..5fe752ce8a 100644 --- a/rust/agama-storage/src/message.rs +++ b/rust/agama-storage/src/message.rs @@ -18,10 +18,15 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use agama_utils::{actor::Message, api::storage::Config, products::ProductSpec}; +use agama_utils::{ + actor::Message, + api::{storage::Config, Issue}, + products::ProductSpec, + BoxFuture, +}; use serde_json::Value; use std::sync::Arc; -use tokio::sync::{oneshot, RwLock}; +use tokio::sync::RwLock; use crate::client; @@ -103,6 +108,13 @@ impl Message for GetProposal { type Reply = Option; } +#[derive(Clone)] +pub struct GetIssues; + +impl Message for GetIssues { + type Reply = Vec; +} + #[derive(Clone)] pub struct SetConfig { pub product: Arc>, @@ -123,7 +135,7 @@ impl SetConfig { } impl Message for SetConfig { - type Reply = oneshot::Receiver>; + type Reply = BoxFuture>; } #[derive(Clone)] diff --git a/rust/agama-storage/src/monitor.rs b/rust/agama-storage/src/monitor.rs index 1d89bf0813..d83d72cb41 100644 --- a/rust/agama-storage/src/monitor.rs +++ b/rust/agama-storage/src/monitor.rs @@ -18,7 +18,7 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::client::{self, Client, StorageClient}; +use crate::client; use agama_utils::{ actor::Handler, api::{ diff --git a/rust/agama-storage/src/proxies.rs b/rust/agama-storage/src/proxies.rs deleted file mode 100644 index 57ce0902cf..0000000000 --- a/rust/agama-storage/src/proxies.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1` -//! -//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. -//! Source: `storage.xml`. -//! -//! You may prefer to adapt it, instead of using it verbatim. -//! -//! More information can be found in the [Writing a client proxy] section of the zbus -//! documentation. -//! -//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the -//! following zbus API can be used: -//! -//! * [`zbus::fdo::PropertiesProxy`] -//! * [`zbus::fdo::IntrospectableProxy`] -//! -//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. -//! -//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html -//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, -use zbus::proxy; -#[proxy( - default_service = "org.opensuse.Agama.Storage1", - default_path = "/org/opensuse/Agama/Storage1", - interface = "org.opensuse.Agama.Storage1", - assume_defaults = true -)] -pub trait Storage1 { - /// Activate method - fn activate(&self) -> zbus::Result<()>; - - /// Finish method - fn finish(&self) -> zbus::Result<()>; - - /// GetConfigFromModel method - fn get_config_from_model(&self, model: &str) -> zbus::Result; - - /// Install method - fn install(&self) -> zbus::Result<()>; - - /// Probe method - fn probe(&self) -> zbus::Result<()>; - - /// SetConfig method - fn set_config(&self, product: &str, config: &str) -> zbus::Result<()>; - - /// SetLocale method - fn set_locale(&self, locale: &str) -> zbus::Result<()>; - - /// SolveConfigModel method - fn solve_config_model(&self, model: &str) -> zbus::Result; - - /// Umount method - fn umount(&self) -> zbus::Result<()>; - - /// ProgressChanged signal - #[zbus(signal)] - fn progress_changed(&self, progress: &str) -> zbus::Result<()>; - - /// ProgressFinished signal - #[zbus(signal)] - fn progress_finished(&self) -> zbus::Result<()>; - - /// Config property - #[zbus(property)] - fn config(&self) -> zbus::Result; - - /// ConfigModel property - #[zbus(property)] - fn config_model(&self) -> zbus::Result; - - /// Issues property - #[zbus(property)] - fn issues(&self) -> zbus::Result; - - /// Proposal property - #[zbus(property)] - fn proposal(&self) -> zbus::Result; - - /// System property - #[zbus(property)] - fn system(&self) -> zbus::Result; -} diff --git a/rust/agama-storage/src/service.rs b/rust/agama-storage/src/service.rs index 11d0522009..3e06ba69ed 100644 --- a/rust/agama-storage/src/service.rs +++ b/rust/agama-storage/src/service.rs @@ -26,11 +26,10 @@ use crate::{ use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, api::{event, storage::Config}, - issue, progress, + issue, progress, BoxFuture, }; use async_trait::async_trait; use serde_json::Value; -use tokio::sync::oneshot; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -76,7 +75,13 @@ impl Starter { pub async fn start(self) -> Result, Error> { let client = match self.client { Some(client) => client, - None => Box::new(Client::new(self.dbus.clone()).await), + None => { + let storage_dbus = agama_storage_client::service::Starter::new(self.dbus.clone()) + .start() + .await + .map_err(client::Error::from)?; + Box::new(Client::new(storage_dbus)) + } }; let service = Service { client }; @@ -194,11 +199,11 @@ impl MessageHandler for Service { async fn handle( &mut self, message: message::SetConfig, - ) -> Result>, Error> { + ) -> Result>, Error> { let rx = self .client .set_config(message.product, message.config) - .await; + .await?; Ok(rx) } } diff --git a/rust/agama-storage/src/test_utils.rs b/rust/agama-storage/src/test_utils.rs index 81deddec7a..c23f762992 100644 --- a/rust/agama-storage/src/test_utils.rs +++ b/rust/agama-storage/src/test_utils.rs @@ -27,11 +27,11 @@ use agama_utils::{ api::{event, storage::Config, Issue}, issue, products::ProductSpec, - progress, + progress, BoxFuture, }; use async_trait::async_trait; use serde_json::Value; -use tokio::sync::{oneshot, Mutex, RwLock}; +use tokio::sync::{Mutex, RwLock}; use crate::{ client::{Error, StorageClient}, @@ -149,14 +149,13 @@ impl StorageClient for TestClient { &self, _product: Arc>, config: Option, - ) -> oneshot::Receiver> { - let mut state = self.state.lock().await; - state.config = config; - let (tx, rx) = oneshot::channel::>(); - tokio::spawn(async move { - _ = tx.send(Ok(())); - }); - rx + ) -> Result>, Error> { + let state = self.state.clone(); + Ok(Box::pin(async move { + let mut state = state.lock().await; + state.config = config; + Ok(()) + })) } async fn solve_config_model(&self, _model: Value) -> Result, Error> { From d223c9fb3ca1f9a3cd89364451c4adaf5dad8b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 27 Feb 2026 15:42:07 +0000 Subject: [PATCH 06/22] Adapt agama-bootloader to the new storage client --- rust/Cargo.lock | 1 + rust/agama-bootloader/Cargo.toml | 1 + rust/agama-bootloader/src/client.rs | 34 +++++++++++++-------------- rust/agama-bootloader/src/dbus.rs | 35 ---------------------------- rust/agama-bootloader/src/lib.rs | 1 - rust/agama-bootloader/src/service.rs | 12 ++++++++-- rust/agama-manager/src/service.rs | 11 ++++----- 7 files changed, 33 insertions(+), 62 deletions(-) delete mode 100644 rust/agama-bootloader/src/dbus.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9d2f558dee..60e4e754c4 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -27,6 +27,7 @@ dependencies = [ name = "agama-bootloader" version = "0.1.0" dependencies = [ + "agama-storage-client", "agama-utils", "anyhow", "async-trait", diff --git a/rust/agama-bootloader/Cargo.toml b/rust/agama-bootloader/Cargo.toml index e11def782e..9ec00a2521 100644 --- a/rust/agama-bootloader/Cargo.toml +++ b/rust/agama-bootloader/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] agama-utils = { path = "../agama-utils" } +agama-storage-client = { path = "../agama-storage-client" } anyhow = "1.0.99" async-trait = "0.1.89" gettext-rs = { version = "0.7.2", features = ["gettext-system"] } diff --git a/rust/agama-bootloader/src/client.rs b/rust/agama-bootloader/src/client.rs index a238b2b494..bb7e16778e 100644 --- a/rust/agama-bootloader/src/client.rs +++ b/rust/agama-bootloader/src/client.rs @@ -22,12 +22,10 @@ use std::collections::HashMap; -use agama_utils::api::bootloader::Config; +use agama_storage_client::message; +use agama_utils::{actor::Handler, api::bootloader::Config}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use zbus::Connection; - -use crate::dbus::BootloaderProxy; /// Errors that can occur when using the Bootloader client. #[derive(thiserror::Error, Debug)] @@ -38,6 +36,8 @@ pub enum Error { /// Error parsing or generating JSON data. #[error("Passed json data is not correct: {0}")] InvalidJson(#[from] serde_json::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } /// Trait defining the interface for the Bootloader client. @@ -71,28 +71,24 @@ pub type ClientResult = Result; /// Client to connect to Agama's D-Bus API for Bootloader management. #[derive(Clone)] -pub struct Client<'a> { - bootloader_proxy: BootloaderProxy<'a>, +pub struct Client { + storage_dbus: Handler, kernel_args: HashMap, } -impl<'a> Client<'a> { - pub async fn new(connection: Connection) -> ClientResult> { - let bootloader_proxy = BootloaderProxy::new(&connection).await?; - +impl Client { + pub async fn new(storage_dbus: Handler) -> ClientResult { Ok(Self { - bootloader_proxy, + storage_dbus, kernel_args: HashMap::new(), }) } } #[async_trait] -impl<'a> BootloaderClient for Client<'a> { +impl BootloaderClient for Client { async fn get_config(&self) -> ClientResult { - let serialized_string = self.bootloader_proxy.get_config().await?; - let settings = serde_json::from_str(serialized_string.as_str())?; - Ok(settings) + Ok(self.storage_dbus.call(message::GetBootloaderConfig).await?) } async fn set_config(&self, config: &Config) -> ClientResult<()> { @@ -103,9 +99,11 @@ impl<'a> BootloaderClient for Client<'a> { tracing::info!("sending bootloader config {:?}", full_config); // ignore return value as currently it does not fail and who knows what future brings // but it should not be part of result and instead transformed to Issue - self.bootloader_proxy - .set_config(serde_json::to_string(&full_config)?.as_str()) - .await?; + let value = serde_json::to_value(&full_config)?; + _ = self + .storage_dbus + .call(message::SetBootloaderConfig::new(value)) + .await; Ok(()) } diff --git a/rust/agama-bootloader/src/dbus.rs b/rust/agama-bootloader/src/dbus.rs deleted file mode 100644 index 0998e49a70..0000000000 --- a/rust/agama-bootloader/src/dbus.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! # D-Bus interface proxy for: `org.freedesktop.locale1` -//! -//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. -//! Source: `Interface '/org/freedesktop/locale1' from service 'org.freedesktop.locale1' on system bus`. -//! -//! You may prefer to adapt it, instead of using it verbatim. -//! -//! More information can be found in the [Writing a client proxy] section of the zbus -//! documentation. -//! -//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the -//! following zbus API can be used: -//! -//! * [`zbus::fdo::PeerProxy`] -//! * [`zbus::fdo::IntrospectableProxy`] -//! * [`zbus::fdo::PropertiesProxy`] -//! -//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. -//! -//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html -//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, -use zbus::proxy; -#[proxy( - default_service = "org.opensuse.Agama.Storage1", - default_path = "/org/opensuse/Agama/Storage1", - interface = "org.opensuse.Agama.Storage1.Bootloader", - assume_defaults = true -)] -pub trait Bootloader { - /// GetConfig method - fn get_config(&self) -> zbus::Result; - - /// SetConfig method - fn set_config(&self, serialized_config: &str) -> zbus::Result; -} diff --git a/rust/agama-bootloader/src/lib.rs b/rust/agama-bootloader/src/lib.rs index 151674e0de..64b793a854 100644 --- a/rust/agama-bootloader/src/lib.rs +++ b/rust/agama-bootloader/src/lib.rs @@ -39,6 +39,5 @@ pub mod service; pub use service::{Service, Starter}; pub mod client; -mod dbus; pub mod message; pub mod test_utils; diff --git a/rust/agama-bootloader/src/service.rs b/rust/agama-bootloader/src/service.rs index 35a0c84546..d547de221f 100644 --- a/rust/agama-bootloader/src/service.rs +++ b/rust/agama-bootloader/src/service.rs @@ -35,6 +35,8 @@ pub enum Error { Actor(#[from] actor::Error), #[error(transparent)] Client(#[from] client::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } /// Builds and spawns the bootloader service. @@ -67,9 +69,15 @@ impl Starter { pub async fn start(self) -> Result, Error> { let client = match self.client { Some(client) => client, - None => Box::new(Client::new(self.connection.clone()).await?), + None => { + let storage_dbus = + agama_storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + Box::new(Client::new(storage_dbus).await?) + } }; - let service = Service { client }; + let service = Service { client: client }; let handler = actor::spawn(service); Ok(handler) } diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 79d1731ba2..22172cbe8a 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -658,12 +658,11 @@ impl MessageHandler for Service { /// /// It includes user and default values. async fn handle(&mut self, _message: message::GetExtendedConfig) -> Result { - let bootloader = None; - // let bootloader = self - // .bootloader - // .call(bootloader::message::GetConfig) - // .await? - // .to_option(); + let bootloader = self + .bootloader + .call(bootloader::message::GetConfig) + .await? + .to_option(); let hostname = self.hostname.call(hostname::message::GetConfig).await?; // let iscsi = self.iscsi.call(iscsi::message::GetConfig).await?; let iscsi = Default::default(); From 3d4d562f6c1314045ecea2a184cd8005c802dbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 27 Feb 2026 15:43:00 +0000 Subject: [PATCH 07/22] Adapt agama-manager to the new storage client --- rust/agama-manager/src/tasks/runner.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/agama-manager/src/tasks/runner.rs b/rust/agama-manager/src/tasks/runner.rs index 97ffb398bb..59d74bd4de 100644 --- a/rust/agama-manager/src/tasks/runner.rs +++ b/rust/agama-manager/src/tasks/runner.rs @@ -410,12 +410,14 @@ impl SetConfigAction { self.progress .call(progress::message::Next::new(Scope::Manager)) .await?; - self.storage + let future = self + .storage .call(storage::message::SetConfig::new( Arc::clone(product), config.storage.clone(), )) .await?; + let _ = future.await; // call bootloader always after storage to ensure that bootloader reflect new storage settings self.progress From 9d9dfeb0ad3e91ab007107694f734ed32daeff75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 27 Feb 2026 18:45:56 +0000 Subject: [PATCH 08/22] Adapt agama-iscsi to the new storage client --- rust/Cargo.lock | 3 +- rust/agama-iscsi/Cargo.toml | 1 + rust/agama-iscsi/src/client.rs | 45 ++++++++---------- rust/agama-iscsi/src/lib.rs | 1 - rust/agama-iscsi/src/monitor.rs | 8 ++-- rust/agama-iscsi/src/service.rs | 10 +++- rust/agama-manager/src/service.rs | 7 ++- rust/agama-manager/src/tasks/runner.rs | 6 +-- rust/agama-storage-client/src/lib.rs | 2 +- rust/agama-storage-client/src/message.rs | 42 ++++++++++++++++- rust/agama-storage-client/src/proxies.rs | 3 ++ .../src/proxies/iscsi.rs} | 23 ++++++---- rust/agama-storage-client/src/service.rs | 46 ++++++++++++++++++- 13 files changed, 142 insertions(+), 55 deletions(-) rename rust/{agama-iscsi/src/dbus.rs => agama-storage-client/src/proxies/iscsi.rs} (70%) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 60e4e754c4..1bd879d601 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -109,6 +109,7 @@ name = "agama-iscsi" version = "0.1.0" dependencies = [ "agama-storage", + "agama-storage-client", "agama-utils", "async-trait", "serde", diff --git a/rust/agama-iscsi/Cargo.toml b/rust/agama-iscsi/Cargo.toml index 25920d183e..652e6a0f39 100644 --- a/rust/agama-iscsi/Cargo.toml +++ b/rust/agama-iscsi/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true [dependencies] agama-utils = { path = "../agama-utils" } agama-storage = { path = "../agama-storage" } +agama-storage-client = { path = "../agama-storage-client" } thiserror = "2.0.16" async-trait = "0.1.89" zbus = "5.7.1" diff --git a/rust/agama-iscsi/src/client.rs b/rust/agama-iscsi/src/client.rs index 63c93f3ba5..937dcdf2ce 100644 --- a/rust/agama-iscsi/src/client.rs +++ b/rust/agama-iscsi/src/client.rs @@ -18,14 +18,14 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -//! Implements a client to access Agama's D-Bus API related to Bootloader management. +//! Implements a client to access Agama's D-Bus API related to iSCSI management. -use crate::dbus::ISCSIProxy; +use agama_storage_client::message; +use agama_utils::actor::Handler; use agama_utils::api::iscsi::Config; use agama_utils::api::iscsi::DiscoverConfig; use async_trait::async_trait; use serde_json::Value; -use zbus::Connection; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -33,6 +33,8 @@ pub enum Error { DBus(#[from] zbus::Error), #[error(transparent)] Json(#[from] serde_json::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } pub enum DiscoverResult { @@ -49,23 +51,24 @@ pub trait ISCSIClient { } #[derive(Clone)] -pub struct Client<'a> { - proxy: ISCSIProxy<'a>, +pub struct Client { + storage_dbus: Handler, } -impl<'a> Client<'a> { - pub async fn new(connection: Connection) -> Result, Error> { - let proxy = ISCSIProxy::new(&connection).await?; - Ok(Self { proxy }) +impl Client { + pub async fn new( + storage_dbus: Handler, + ) -> Result { + Ok(Self { storage_dbus }) } } #[async_trait] -impl<'a> ISCSIClient for Client<'a> { +impl ISCSIClient for Client { async fn discover(&self, config: DiscoverConfig) -> Result { let result = self - .proxy - .discover(serde_json::to_string(&config)?.as_str()) + .storage_dbus + .call(message::ISCSIDiscover::new(config)) .await?; match result { 0 => Ok(DiscoverResult::Success), @@ -74,26 +77,16 @@ impl<'a> ISCSIClient for Client<'a> { } async fn get_system(&self) -> Result, Error> { - let serialized_system = self.proxy.get_system().await?; - let system: Value = serde_json::from_str(serialized_system.as_str())?; - match system { - Value::Null => Ok(None), - _ => Ok(Some(system)), - } + Ok(self.storage_dbus.call(message::ISCSIGetSystem).await?) } async fn get_config(&self) -> Result, Error> { - let serialized_config = self.proxy.get_config().await?; - let value: Value = serde_json::from_str(serialized_config.as_str())?; - match value { - Value::Null => Ok(None), - _ => Ok(Some(Config(value))), - } + Ok(self.storage_dbus.call(message::ISCSIGetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { - self.proxy - .set_config(serde_json::to_string(&config)?.as_str()) + self.storage_dbus + .call(message::ISCSISetConfig::new(config)) .await?; Ok(()) } diff --git a/rust/agama-iscsi/src/lib.rs b/rust/agama-iscsi/src/lib.rs index 219af4c87c..367d7b9a2c 100644 --- a/rust/agama-iscsi/src/lib.rs +++ b/rust/agama-iscsi/src/lib.rs @@ -25,7 +25,6 @@ pub mod client; pub mod message; pub mod test_utils; -mod dbus; mod monitor; use agama_storage as storage; diff --git a/rust/agama-iscsi/src/monitor.rs b/rust/agama-iscsi/src/monitor.rs index 1f07985251..ab8d4ba339 100644 --- a/rust/agama-iscsi/src/monitor.rs +++ b/rust/agama-iscsi/src/monitor.rs @@ -18,10 +18,8 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::{ - dbus::{ISCSIProxy, ProgressChanged, ProgressFinished, SystemChanged}, - storage, -}; +use crate::storage; +use agama_storage_client::proxies::{ISCSIProxy, ProgressChanged, ProgressFinished, SystemChanged}; use agama_utils::{ actor::Handler, api::{ @@ -130,7 +128,7 @@ impl Monitor { async fn handle_progress_changed(&self, signal: ProgressChanged) -> Result<(), Error> { let args = signal.args()?; - let progress_data = serde_json::from_str::(args.progress)?; + let progress_data = serde_json::from_str::(args.serialized_progress)?; self.progress .call(progress::message::SetProgress::new(progress_data.into())) .await?; diff --git a/rust/agama-iscsi/src/service.rs b/rust/agama-iscsi/src/service.rs index 8ec851f94c..723aab9d57 100644 --- a/rust/agama-iscsi/src/service.rs +++ b/rust/agama-iscsi/src/service.rs @@ -40,6 +40,8 @@ pub enum Error { Client(#[from] client::Error), #[error(transparent)] Monitor(#[from] monitor::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } pub struct Starter { @@ -74,7 +76,13 @@ impl Starter { pub async fn start(self) -> Result, Error> { let client = match self.client { Some(client) => client, - None => Box::new(Client::new(self.connection.clone()).await?), + None => { + let storage_dbus = + agama_storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + Box::new(Client::new(storage_dbus).await?) + } }; let service = Service { client }; let handler = actor::spawn(service); diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 22172cbe8a..0e042bc976 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -622,7 +622,7 @@ impl MessageHandler for Service { let l10n = self.l10n.call(l10n::message::GetSystem).await?; let manager = self.system.clone(); let storage = self.storage.call(storage::message::GetSystem).await?; - // let iscsi = self.iscsi.call(iscsi::message::GetSystem).await?; + let iscsi = self.iscsi.call(iscsi::message::GetSystem).await?; let network = self.network.get_system().await?; let s390 = if let Some(s390) = &self.s390 { @@ -645,7 +645,7 @@ impl MessageHandler for Service { manager, network, storage, - iscsi: Default::default(), + iscsi, s390, software, }) @@ -664,8 +664,7 @@ impl MessageHandler for Service { .await? .to_option(); let hostname = self.hostname.call(hostname::message::GetConfig).await?; - // let iscsi = self.iscsi.call(iscsi::message::GetConfig).await?; - let iscsi = Default::default(); + let iscsi = self.iscsi.call(iscsi::message::GetConfig).await?; let l10n = self.l10n.call(l10n::message::GetConfig).await?; // FIXME: the security service might be busy asking some question, so it cannot answer. // By now, let's consider that the whole security configuration is set by the user diff --git a/rust/agama-manager/src/tasks/runner.rs b/rust/agama-manager/src/tasks/runner.rs index 59d74bd4de..1b3b08c08e 100644 --- a/rust/agama-manager/src/tasks/runner.rs +++ b/rust/agama-manager/src/tasks/runner.rs @@ -375,9 +375,9 @@ impl SetConfigAction { self.progress .call(progress::message::Next::new(Scope::Manager)) .await?; - // self.iscsi - // .call(iscsi::message::SetConfig::new(config.iscsi.clone())) - // .await?; + self.iscsi + .call(iscsi::message::SetConfig::new(config.iscsi.clone())) + .await?; if let Some(s390) = &self.s390 { self.progress diff --git a/rust/agama-storage-client/src/lib.rs b/rust/agama-storage-client/src/lib.rs index 07957d1669..6007008109 100644 --- a/rust/agama-storage-client/src/lib.rs +++ b/rust/agama-storage-client/src/lib.rs @@ -29,4 +29,4 @@ pub use service::{Error, Service}; pub mod message; mod dbus; -mod proxies; +pub mod proxies; diff --git a/rust/agama-storage-client/src/message.rs b/rust/agama-storage-client/src/message.rs index b63b4b2bfc..915f0e0396 100644 --- a/rust/agama-storage-client/src/message.rs +++ b/rust/agama-storage-client/src/message.rs @@ -22,7 +22,7 @@ use std::sync::Arc; use agama_utils::{ actor::Message, - api::{bootloader, storage::Config, Issue}, + api::{bootloader, iscsi, storage::Config, Issue}, products::ProductSpec, BoxFuture, }; @@ -155,3 +155,43 @@ impl SetLocale { impl Message for SetLocale { type Reply = (); } + +pub struct ISCSIDiscover { + pub config: iscsi::DiscoverConfig, +} + +impl ISCSIDiscover { + pub fn new(config: iscsi::DiscoverConfig) -> Self { + Self { config } + } +} + +impl Message for ISCSIDiscover { + type Reply = u32; +} + +pub struct ISCSIGetSystem; + +impl Message for ISCSIGetSystem { + type Reply = Option; +} + +pub struct ISCSIGetConfig; + +impl Message for ISCSIGetConfig { + type Reply = Option; +} + +pub struct ISCSISetConfig { + pub config: Option, +} + +impl ISCSISetConfig { + pub fn new(config: Option) -> Self { + Self { config } + } +} + +impl Message for ISCSISetConfig { + type Reply = (); +} diff --git a/rust/agama-storage-client/src/proxies.rs b/rust/agama-storage-client/src/proxies.rs index be7b204886..d52a79ead9 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -23,3 +23,6 @@ pub use storage1::Storage1Proxy; mod bootloader; pub use bootloader::BootloaderProxy; + +mod iscsi; +pub use iscsi::{ISCSIProxy, ProgressChanged, ProgressFinished, SystemChanged}; diff --git a/rust/agama-iscsi/src/dbus.rs b/rust/agama-storage-client/src/proxies/iscsi.rs similarity index 70% rename from rust/agama-iscsi/src/dbus.rs rename to rust/agama-storage-client/src/proxies/iscsi.rs index 019baf071c..893c16b735 100644 --- a/rust/agama-iscsi/src/dbus.rs +++ b/rust/agama-storage-client/src/proxies/iscsi.rs @@ -1,6 +1,6 @@ //! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1.ISCSI` //! -//! This code was generated by `zbus-xmlgen` `5.2.0` from D-Bus introspection data. +//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. //! Source: `iscsi.xml`. //! //! You may prefer to adapt it, instead of using it verbatim. @@ -16,7 +16,7 @@ //! //! Consequently `zbus-xmlgen` did not generate code for the above interfaces. //! -//! [Writing a client proxy]: https://z-galaxy.github.io/zbus/client.html +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html //! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, use zbus::proxy; #[proxy( @@ -29,18 +29,12 @@ pub trait ISCSI { /// Discover method fn discover(&self, serialized_options: &str) -> zbus::Result; - /// GetConfig method - fn get_config(&self) -> zbus::Result; - - /// GetSystem method - fn get_system(&self) -> zbus::Result; - /// SetConfig method fn set_config(&self, serialized_config: &str) -> zbus::Result<()>; /// ProgressChanged signal #[zbus(signal)] - fn progress_changed(&self, progress: &str) -> zbus::Result<()>; + fn progress_changed(&self, serialized_progress: &str) -> zbus::Result<()>; /// ProgressFinished signal #[zbus(signal)] @@ -48,5 +42,14 @@ pub trait ISCSI { /// SystemChanged signal #[zbus(signal)] - fn system_changed(&self, system: &str) -> zbus::Result<()>; + fn system_changed(&self, serialized_system: &str) -> zbus::Result<()>; + + /// Config property + #[zbus(property)] + fn config(&self) -> zbus::Result; + + /// System property + /// Temporary rename to avoid clashing with the system_changed signal. + #[zbus(property, name = "System")] + fn iscsi_system(&self) -> zbus::Result; } diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 82a1e7c409..70d07e1562 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -22,7 +22,7 @@ use std::future::Future; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, - api::{bootloader, storage::Config, Issue}, + api::{bootloader, iscsi, storage::Config, Issue}, BoxFuture, }; use async_trait::async_trait; @@ -31,7 +31,7 @@ use tokio::sync::oneshot; use crate::{ dbus::{try_from_string, StorageDBusClient}, message, - proxies::{self, BootloaderProxy, Storage1Proxy}, + proxies::{self, BootloaderProxy, ISCSIProxy, Storage1Proxy}, }; #[derive(thiserror::Error, Debug)] @@ -67,10 +67,13 @@ impl Starter { let bootloader_proxy = proxies::BootloaderProxy::new(&self.dbus).await?; bootloader_proxy.config().await?; + let iscsi_proxy = proxies::ISCSIProxy::new(&self.dbus).await?; + let service = Service { storage_dbus: StorageDBusClient::new(self.dbus), storage_proxy, bootloader_proxy, + iscsi_proxy, }; let handler = actor::spawn(service); Ok(handler) @@ -81,6 +84,7 @@ pub struct Service { storage_dbus: StorageDBusClient, storage_proxy: Storage1Proxy<'static>, bootloader_proxy: BootloaderProxy<'static>, + iscsi_proxy: ISCSIProxy<'static>, } impl Actor for Service { @@ -218,6 +222,44 @@ impl MessageHandler for Service { } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::ISCSIDiscover) -> Result { + let options = serde_json::to_string(&message.config)?; + Ok(self.iscsi_proxy.discover(&options).await?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::ISCSIGetSystem, + ) -> Result, Error> { + let raw_json = self.iscsi_proxy.iscsi_system().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::ISCSIGetConfig, + ) -> Result, Error> { + let raw_json = self.iscsi_proxy.config().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::ISCSISetConfig) -> Result<(), Error> { + let config = serde_json::to_string(&message.config)?; + Ok(self.iscsi_proxy.set_config(&config).await?) + } +} + fn run_in_background(func: F) -> oneshot::Receiver> where F: Future> + Send + 'static, From c92a06003be54054fa6ec881ec60fcb021a1b396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 27 Feb 2026 19:41:10 +0000 Subject: [PATCH 09/22] Adapt agama-s390 to the new storage client --- rust/Cargo.lock | 1 + rust/agama-s390/Cargo.toml | 1 + rust/agama-s390/src/dasd.rs | 2 - rust/agama-s390/src/dasd/client.rs | 44 +++++-------- rust/agama-s390/src/dasd/dbus.rs | 60 ----------------- rust/agama-s390/src/dasd/monitor.rs | 9 ++- rust/agama-s390/src/service.rs | 10 ++- rust/agama-storage-client/src/message.rs | 32 +++++++++ rust/agama-storage-client/src/proxies.rs | 6 ++ rust/agama-storage-client/src/proxies/dasd.rs | 66 +++++++++++++++++++ rust/agama-storage-client/src/service.rs | 42 +++++++++++- 11 files changed, 178 insertions(+), 95 deletions(-) delete mode 100644 rust/agama-s390/src/dasd/dbus.rs create mode 100644 rust/agama-storage-client/src/proxies/dasd.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 1bd879d601..37af36ad03 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -282,6 +282,7 @@ name = "agama-s390" version = "0.1.0" dependencies = [ "agama-storage", + "agama-storage-client", "agama-utils", "async-trait", "serde", diff --git a/rust/agama-s390/Cargo.toml b/rust/agama-s390/Cargo.toml index 09c963d541..e8f842d344 100644 --- a/rust/agama-s390/Cargo.toml +++ b/rust/agama-s390/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true [dependencies] agama-utils = { path = "../agama-utils" } agama-storage = { path = "../agama-storage" } +agama-storage-client = { path = "../agama-storage-client" } thiserror = "2.0.16" async-trait = "0.1.89" zbus = "5.7.1" diff --git a/rust/agama-s390/src/dasd.rs b/rust/agama-s390/src/dasd.rs index aead70c64e..112c34f4eb 100644 --- a/rust/agama-s390/src/dasd.rs +++ b/rust/agama-s390/src/dasd.rs @@ -23,5 +23,3 @@ pub use client::Client; pub mod monitor; pub use monitor::Monitor; - -mod dbus; diff --git a/rust/agama-s390/src/dasd/client.rs b/rust/agama-s390/src/dasd/client.rs index 3bf75f1adf..237a63e846 100644 --- a/rust/agama-s390/src/dasd/client.rs +++ b/rust/agama-s390/src/dasd/client.rs @@ -18,13 +18,13 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -//! Implements a client to access Agama's D-Bus API related to Bootloader management. +//! Implements a client to access Agama's D-Bus API related to DASD management. -use crate::dasd::dbus::DASDProxy; +use agama_storage_client::message; +use agama_utils::actor::Handler; use agama_utils::api::RawConfig; use async_trait::async_trait; use serde_json::Value; -use zbus::Connection; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -32,6 +32,8 @@ pub enum Error { DBus(#[from] zbus::Error), #[error(transparent)] Json(#[from] serde_json::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } #[async_trait] @@ -43,45 +45,35 @@ pub trait DASDClient { } #[derive(Clone)] -pub struct Client<'a> { - proxy: DASDProxy<'a>, +pub struct Client { + storage_dbus: Handler, } -impl<'a> Client<'a> { - pub async fn new(connection: Connection) -> Result, Error> { - let proxy = DASDProxy::new(&connection).await?; - Ok(Self { proxy }) +impl Client { + pub async fn new( + storage_dbus: Handler, + ) -> Result { + Ok(Self { storage_dbus }) } } #[async_trait] -impl<'a> DASDClient for Client<'a> { +impl DASDClient for Client { async fn probe(&self) -> Result<(), Error> { - self.proxy.probe().await?; - Ok(()) + Ok(self.storage_dbus.call(message::DASDProbe).await?) } async fn get_system(&self) -> Result, Error> { - let serialized_system = self.proxy.get_system().await?; - let system: Value = serde_json::from_str(serialized_system.as_str())?; - match system { - Value::Null => Ok(None), - _ => Ok(Some(system)), - } + Ok(self.storage_dbus.call(message::DASDGetSystem).await?) } async fn get_config(&self) -> Result, Error> { - let serialized_config = self.proxy.get_config().await?; - let value: Value = serde_json::from_str(serialized_config.as_str())?; - match value { - Value::Null => Ok(None), - _ => Ok(Some(RawConfig(value))), - } + Ok(self.storage_dbus.call(message::DASDGetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { - self.proxy - .set_config(serde_json::to_string(&config)?.as_str()) + self.storage_dbus + .call(message::DASDSetConfig::new(config)) .await?; Ok(()) } diff --git a/rust/agama-s390/src/dasd/dbus.rs b/rust/agama-s390/src/dasd/dbus.rs deleted file mode 100644 index 9eebf3a5c2..0000000000 --- a/rust/agama-s390/src/dasd/dbus.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1.DASD` -//! -//! This code was generated by `zbus-xmlgen` `5.2.0` from D-Bus introspection data. -//! Source: `iscsi.xml`. -//! -//! You may prefer to adapt it, instead of using it verbatim. -//! -//! More information can be found in the [Writing a client proxy] section of the zbus -//! documentation. -//! -//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the -//! following zbus API can be used: -//! -//! * [`zbus::fdo::PropertiesProxy`] -//! * [`zbus::fdo::IntrospectableProxy`] -//! -//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. -//! -//! [Writing a client proxy]: https://z-galaxy.github.io/zbus/client.html -//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, -use zbus::proxy; -#[proxy( - default_service = "org.opensuse.Agama.Storage1", - default_path = "/org/opensuse/Agama/Storage1/DASD", - interface = "org.opensuse.Agama.Storage1.DASD", - assume_defaults = true -)] -pub trait DASD { - /// Probe method - fn probe(&self) -> zbus::Result<()>; - - /// GetConfig method - fn get_config(&self) -> zbus::Result; - - /// GetSystem method - fn get_system(&self) -> zbus::Result; - - /// SetConfig method - fn set_config(&self, serialized_config: &str) -> zbus::Result<()>; - - /// FormatChanged signal - #[zbus(signal)] - fn format_changed(&self, summary: &str) -> zbus::Result<()>; - - /// FormatFinished signal - #[zbus(signal)] - fn format_finished(&self, status: &str) -> zbus::Result<()>; - - /// SystemChanged signal - #[zbus(signal)] - fn system_changed(&self, system: &str) -> zbus::Result<()>; - - /// ProgressChanged signal - #[zbus(signal)] - fn progress_changed(&self, progress: &str) -> zbus::Result<()>; - - /// ProgressFinished signal - #[zbus(signal)] - fn progress_finished(&self) -> zbus::Result<()>; -} diff --git a/rust/agama-s390/src/dasd/monitor.rs b/rust/agama-s390/src/dasd/monitor.rs index 6c34e844bb..6c713f0a1e 100644 --- a/rust/agama-s390/src/dasd/monitor.rs +++ b/rust/agama-s390/src/dasd/monitor.rs @@ -18,11 +18,10 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::{ - dasd::dbus::{ - DASDProxy, FormatChanged, FormatFinished, ProgressChanged, ProgressFinished, SystemChanged, - }, - storage, +use crate::storage; +use agama_storage_client::proxies::{ + DASDProgressChanged as ProgressChanged, DASDProgressFinished as ProgressFinished, DASDProxy, + DASDSystemChanged as SystemChanged, FormatChanged, FormatFinished, }; use agama_utils::{ actor::Handler, diff --git a/rust/agama-s390/src/service.rs b/rust/agama-s390/src/service.rs index 161ebb7fd8..1c332778cb 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -40,6 +40,8 @@ pub enum Error { DASDClient(#[from] dasd::client::Error), #[error(transparent)] DASDMonitor(#[from] dasd::monitor::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } pub struct Starter { @@ -74,7 +76,13 @@ impl Starter { pub async fn start(self) -> Result, Error> { let dasd_client = match self.dasd { Some(client) => client, - None => Box::new(dasd::Client::new(self.connection.clone()).await?), + None => { + let storage_dbus = + agama_storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + Box::new(dasd::Client::new(storage_dbus).await?) + } }; let service = Service { dasd: dasd_client }; let handler = actor::spawn(service); diff --git a/rust/agama-storage-client/src/message.rs b/rust/agama-storage-client/src/message.rs index 915f0e0396..259bd436c2 100644 --- a/rust/agama-storage-client/src/message.rs +++ b/rust/agama-storage-client/src/message.rs @@ -195,3 +195,35 @@ impl ISCSISetConfig { impl Message for ISCSISetConfig { type Reply = (); } + +pub struct DASDProbe; + +impl Message for DASDProbe { + type Reply = (); +} + +pub struct DASDGetSystem; + +impl Message for DASDGetSystem { + type Reply = Option; +} + +pub struct DASDGetConfig; + +impl Message for DASDGetConfig { + type Reply = Option; +} + +pub struct DASDSetConfig { + pub config: Option, +} + +impl DASDSetConfig { + pub fn new(config: Option) -> Self { + Self { config } + } +} + +impl Message for DASDSetConfig { + type Reply = (); +} diff --git a/rust/agama-storage-client/src/proxies.rs b/rust/agama-storage-client/src/proxies.rs index d52a79ead9..2680c389d5 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -26,3 +26,9 @@ pub use bootloader::BootloaderProxy; mod iscsi; pub use iscsi::{ISCSIProxy, ProgressChanged, ProgressFinished, SystemChanged}; + +mod dasd; +pub use dasd::{ + DASDProxy, FormatChanged, FormatFinished, ProgressChanged as DASDProgressChanged, + ProgressFinished as DASDProgressFinished, SystemChanged as DASDSystemChanged, +}; diff --git a/rust/agama-storage-client/src/proxies/dasd.rs b/rust/agama-storage-client/src/proxies/dasd.rs new file mode 100644 index 0000000000..aae0b2101a --- /dev/null +++ b/rust/agama-storage-client/src/proxies/dasd.rs @@ -0,0 +1,66 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1.DASD` + +use zbus::proxy; + +#[proxy( + default_service = "org.opensuse.Agama.Storage1", + default_path = "/org/opensuse/Agama/Storage1/DASD", + interface = "org.opensuse.Agama.Storage1.DASD", + assume_defaults = true +)] +pub trait DASD { + /// Probe method + fn probe(&self) -> zbus::Result<()>; + + /// Config property + #[zbus(property)] + fn config(&self) -> zbus::Result; + + /// System property + /// Temporary rename to avoid clashing with the system_changed signal. + #[zbus(property, name = "System")] + fn dasd_system(&self) -> zbus::Result; + + /// SetConfig method + fn set_config(&self, serialized_config: &str) -> zbus::Result<()>; + + /// FormatChanged signal + #[zbus(signal)] + fn format_changed(&self, summary: &str) -> zbus::Result<()>; + + /// FormatFinished signal + #[zbus(signal)] + fn format_finished(&self, status: &str) -> zbus::Result<()>; + + /// SystemChanged signal + #[zbus(signal)] + fn system_changed(&self, system: &str) -> zbus::Result<()>; + + /// ProgressChanged signal + #[zbus(signal)] + fn progress_changed(&self, progress: &str) -> zbus::Result<()>; + + /// ProgressFinished signal + #[zbus(signal)] + fn progress_finished(&self, status: &str) -> zbus::Result<()>; +} diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 70d07e1562..a25f647b1d 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -31,7 +31,7 @@ use tokio::sync::oneshot; use crate::{ dbus::{try_from_string, StorageDBusClient}, message, - proxies::{self, BootloaderProxy, ISCSIProxy, Storage1Proxy}, + proxies::{self, BootloaderProxy, DASDProxy, ISCSIProxy, Storage1Proxy}, }; #[derive(thiserror::Error, Debug)] @@ -68,12 +68,14 @@ impl Starter { bootloader_proxy.config().await?; let iscsi_proxy = proxies::ISCSIProxy::new(&self.dbus).await?; + let dasd_proxy = proxies::DASDProxy::new(&self.dbus).await?; let service = Service { storage_dbus: StorageDBusClient::new(self.dbus), storage_proxy, bootloader_proxy, iscsi_proxy, + dasd_proxy, }; let handler = actor::spawn(service); Ok(handler) @@ -85,6 +87,7 @@ pub struct Service { storage_proxy: Storage1Proxy<'static>, bootloader_proxy: BootloaderProxy<'static>, iscsi_proxy: ISCSIProxy<'static>, + dasd_proxy: DASDProxy<'static>, } impl Actor for Service { @@ -260,6 +263,43 @@ impl MessageHandler for Service { } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::DASDProbe) -> Result<(), Error> { + Ok(self.dasd_proxy.probe().await?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::DASDGetSystem, + ) -> Result, Error> { + let raw_json = self.dasd_proxy.dasd_system().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::DASDGetConfig, + ) -> Result, Error> { + let raw_json = self.dasd_proxy.config().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::DASDSetConfig) -> Result<(), Error> { + let config = serde_json::to_string(&message.config)?; + Ok(self.dasd_proxy.set_config(&config).await?) + } +} + fn run_in_background(func: F) -> oneshot::Receiver> where F: Future> + Send + 'static, From 273e82ac365ac54959fe6b4ca9b0dd1af3dc04e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 2 Mar 2026 11:15:14 +0000 Subject: [PATCH 10/22] Revert "Add an call_future to the actors" This reverts commit 35b755865fd69f535cab8ee5b26346d70b93155d. --- rust/agama-utils/src/actor.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/rust/agama-utils/src/actor.rs b/rust/agama-utils/src/actor.rs index 3c9072138f..e3f0bb5ed3 100644 --- a/rust/agama-utils/src/actor.rs +++ b/rust/agama-utils/src/actor.rs @@ -253,26 +253,6 @@ impl Handler { .map_err(|_| Error::Send(A::name()))?; Ok(()) } - - /// Sends a message and returns a channel to get the answer later. - /// - /// TODO: return a future instead of a oneshot channel. - /// - /// * `msg`: message to send to the actor. - pub fn call_future( - &self, - msg: M, - ) -> Result>, A::Error> - where - A: MessageHandler, - { - let (tx, rx) = oneshot::channel(); - let message = Envelope::new(msg, Some(tx)); - self.sender - .send(Box::new(message)) - .map_err(|_| Error::Send(A::name()))?; - Ok(rx) - } } /// Spawns a Tokio task and process the messages coming from the action handler. From d5bba051cf508bfe0c5eec3323461356877f5975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 2 Mar 2026 12:24:27 +0000 Subject: [PATCH 11/22] Make Clippy happy --- rust/agama-bootloader/src/service.rs | 2 +- rust/agama-storage-client/src/dbus.rs | 6 +++--- rust/agama-storage-client/src/service.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/agama-bootloader/src/service.rs b/rust/agama-bootloader/src/service.rs index d547de221f..b6c3f721a7 100644 --- a/rust/agama-bootloader/src/service.rs +++ b/rust/agama-bootloader/src/service.rs @@ -77,7 +77,7 @@ impl Starter { Box::new(Client::new(storage_dbus).await?) } }; - let service = Service { client: client }; + let service = Service { client }; let handler = actor::spawn(service); Ok(handler) } diff --git a/rust/agama-storage-client/src/dbus.rs b/rust/agama-storage-client/src/dbus.rs index 93d48012ca..228a2e3df5 100644 --- a/rust/agama-storage-client/src/dbus.rs +++ b/rust/agama-storage-client/src/dbus.rs @@ -60,7 +60,7 @@ impl StorageDBusClient { product: &ProductSpec, config: Option, ) -> Result<(), Error> { - let product_json = serde_json::to_string(&*product)?; + let product_json = serde_json::to_string(&product)?; let config = config.filter(|c| c.has_value()); let config_json = serde_json::to_string(&config)?; self.call("SetConfig", &(product_json, config_json)).await?; @@ -102,7 +102,7 @@ fn try_from_message( message: Message, ) -> Result { let raw_json: String = message.body().deserialize()?; - Ok(try_from_string(&raw_json)?) + try_from_string(&raw_json) } /// Converts a string into a Value. @@ -111,7 +111,7 @@ fn try_from_message( pub fn try_from_string( raw_json: &str, ) -> Result { - let json: serde_json::Value = serde_json::from_str(&raw_json)?; + let json: serde_json::Value = serde_json::from_str(raw_json)?; if json.is_null() { return Ok(T::default()); } diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index a25f647b1d..d3ad51272d 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -193,7 +193,7 @@ impl MessageHandler for Service { let client = self.storage_dbus.clone(); let result = run_in_background(async move { let product = message.product.read().await; - client.set_storage_config(&*product, message.config).await?; + client.set_storage_config(&product, message.config).await?; Ok(()) }); Ok(Box::pin(async move { From 2fee0f011c6fdb93a64a9350a3ab39a09052be35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 2 Mar 2026 12:51:31 +0000 Subject: [PATCH 12/22] Move iSCSI and DASD messages to separate modules --- rust/agama-iscsi/src/client.rs | 8 +- rust/agama-s390/src/dasd/client.rs | 8 +- rust/agama-storage-client/src/message.rs | 77 +------------------ rust/agama-storage-client/src/message/dasd.rs | 53 +++++++++++++ .../agama-storage-client/src/message/iscsi.rs | 61 +++++++++++++++ rust/agama-storage-client/src/service.rs | 32 ++++---- 6 files changed, 142 insertions(+), 97 deletions(-) create mode 100644 rust/agama-storage-client/src/message/dasd.rs create mode 100644 rust/agama-storage-client/src/message/iscsi.rs diff --git a/rust/agama-iscsi/src/client.rs b/rust/agama-iscsi/src/client.rs index 937dcdf2ce..8f4c513b0e 100644 --- a/rust/agama-iscsi/src/client.rs +++ b/rust/agama-iscsi/src/client.rs @@ -68,7 +68,7 @@ impl ISCSIClient for Client { async fn discover(&self, config: DiscoverConfig) -> Result { let result = self .storage_dbus - .call(message::ISCSIDiscover::new(config)) + .call(message::iscsi::Discover::new(config)) .await?; match result { 0 => Ok(DiscoverResult::Success), @@ -77,16 +77,16 @@ impl ISCSIClient for Client { } async fn get_system(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::ISCSIGetSystem).await?) + Ok(self.storage_dbus.call(message::iscsi::GetSystem).await?) } async fn get_config(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::ISCSIGetConfig).await?) + Ok(self.storage_dbus.call(message::iscsi::GetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { self.storage_dbus - .call(message::ISCSISetConfig::new(config)) + .call(message::iscsi::SetConfig::new(config)) .await?; Ok(()) } diff --git a/rust/agama-s390/src/dasd/client.rs b/rust/agama-s390/src/dasd/client.rs index 237a63e846..58ccc859ec 100644 --- a/rust/agama-s390/src/dasd/client.rs +++ b/rust/agama-s390/src/dasd/client.rs @@ -60,20 +60,20 @@ impl Client { #[async_trait] impl DASDClient for Client { async fn probe(&self) -> Result<(), Error> { - Ok(self.storage_dbus.call(message::DASDProbe).await?) + Ok(self.storage_dbus.call(message::dasd::Probe).await?) } async fn get_system(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::DASDGetSystem).await?) + Ok(self.storage_dbus.call(message::dasd::GetSystem).await?) } async fn get_config(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::DASDGetConfig).await?) + Ok(self.storage_dbus.call(message::dasd::GetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { self.storage_dbus - .call(message::DASDSetConfig::new(config)) + .call(message::dasd::SetConfig::new(config)) .await?; Ok(()) } diff --git a/rust/agama-storage-client/src/message.rs b/rust/agama-storage-client/src/message.rs index 259bd436c2..a942dc7020 100644 --- a/rust/agama-storage-client/src/message.rs +++ b/rust/agama-storage-client/src/message.rs @@ -22,7 +22,7 @@ use std::sync::Arc; use agama_utils::{ actor::Message, - api::{bootloader, iscsi, storage::Config, Issue}, + api::{bootloader, storage::Config, Issue}, products::ProductSpec, BoxFuture, }; @@ -30,6 +30,9 @@ use tokio::sync::RwLock; use crate::service; +pub mod dasd; +pub mod iscsi; + pub struct CallAction { pub action: String, } @@ -155,75 +158,3 @@ impl SetLocale { impl Message for SetLocale { type Reply = (); } - -pub struct ISCSIDiscover { - pub config: iscsi::DiscoverConfig, -} - -impl ISCSIDiscover { - pub fn new(config: iscsi::DiscoverConfig) -> Self { - Self { config } - } -} - -impl Message for ISCSIDiscover { - type Reply = u32; -} - -pub struct ISCSIGetSystem; - -impl Message for ISCSIGetSystem { - type Reply = Option; -} - -pub struct ISCSIGetConfig; - -impl Message for ISCSIGetConfig { - type Reply = Option; -} - -pub struct ISCSISetConfig { - pub config: Option, -} - -impl ISCSISetConfig { - pub fn new(config: Option) -> Self { - Self { config } - } -} - -impl Message for ISCSISetConfig { - type Reply = (); -} - -pub struct DASDProbe; - -impl Message for DASDProbe { - type Reply = (); -} - -pub struct DASDGetSystem; - -impl Message for DASDGetSystem { - type Reply = Option; -} - -pub struct DASDGetConfig; - -impl Message for DASDGetConfig { - type Reply = Option; -} - -pub struct DASDSetConfig { - pub config: Option, -} - -impl DASDSetConfig { - pub fn new(config: Option) -> Self { - Self { config } - } -} - -impl Message for DASDSetConfig { - type Reply = (); -} diff --git a/rust/agama-storage-client/src/message/dasd.rs b/rust/agama-storage-client/src/message/dasd.rs new file mode 100644 index 0000000000..39194c822c --- /dev/null +++ b/rust/agama-storage-client/src/message/dasd.rs @@ -0,0 +1,53 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use agama_utils::actor::Message; + +pub struct Probe; + +impl Message for Probe { + type Reply = (); +} + +pub struct GetSystem; + +impl Message for GetSystem { + type Reply = Option; +} + +pub struct GetConfig; + +impl Message for GetConfig { + type Reply = Option; +} + +pub struct SetConfig { + pub config: Option, +} + +impl SetConfig { + pub fn new(config: Option) -> Self { + Self { config } + } +} + +impl Message for SetConfig { + type Reply = (); +} diff --git a/rust/agama-storage-client/src/message/iscsi.rs b/rust/agama-storage-client/src/message/iscsi.rs new file mode 100644 index 0000000000..bae5f5d7dc --- /dev/null +++ b/rust/agama-storage-client/src/message/iscsi.rs @@ -0,0 +1,61 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use agama_utils::{actor::Message, api::iscsi}; + +pub struct Discover { + pub config: iscsi::DiscoverConfig, +} + +impl Discover { + pub fn new(config: iscsi::DiscoverConfig) -> Self { + Self { config } + } +} + +impl Message for Discover { + type Reply = u32; +} + +pub struct GetSystem; + +impl Message for GetSystem { + type Reply = Option; +} + +pub struct GetConfig; + +impl Message for GetConfig { + type Reply = Option; +} + +pub struct SetConfig { + pub config: Option, +} + +impl SetConfig { + pub fn new(config: Option) -> Self { + Self { config } + } +} + +impl Message for SetConfig { + type Reply = (); +} diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index d3ad51272d..9088311182 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -226,18 +226,18 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, message: message::ISCSIDiscover) -> Result { +impl MessageHandler for Service { + async fn handle(&mut self, message: message::iscsi::Discover) -> Result { let options = serde_json::to_string(&message.config)?; Ok(self.iscsi_proxy.discover(&options).await?) } } #[async_trait] -impl MessageHandler for Service { +impl MessageHandler for Service { async fn handle( &mut self, - _message: message::ISCSIGetSystem, + _message: message::iscsi::GetSystem, ) -> Result, Error> { let raw_json = self.iscsi_proxy.iscsi_system().await?; Ok(try_from_string(&raw_json)?) @@ -245,10 +245,10 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { +impl MessageHandler for Service { async fn handle( &mut self, - _message: message::ISCSIGetConfig, + _message: message::iscsi::GetConfig, ) -> Result, Error> { let raw_json = self.iscsi_proxy.config().await?; Ok(try_from_string(&raw_json)?) @@ -256,25 +256,25 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, message: message::ISCSISetConfig) -> Result<(), Error> { +impl MessageHandler for Service { + async fn handle(&mut self, message: message::iscsi::SetConfig) -> Result<(), Error> { let config = serde_json::to_string(&message.config)?; Ok(self.iscsi_proxy.set_config(&config).await?) } } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, _message: message::DASDProbe) -> Result<(), Error> { +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::dasd::Probe) -> Result<(), Error> { Ok(self.dasd_proxy.probe().await?) } } #[async_trait] -impl MessageHandler for Service { +impl MessageHandler for Service { async fn handle( &mut self, - _message: message::DASDGetSystem, + _message: message::dasd::GetSystem, ) -> Result, Error> { let raw_json = self.dasd_proxy.dasd_system().await?; Ok(try_from_string(&raw_json)?) @@ -282,10 +282,10 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { +impl MessageHandler for Service { async fn handle( &mut self, - _message: message::DASDGetConfig, + _message: message::dasd::GetConfig, ) -> Result, Error> { let raw_json = self.dasd_proxy.config().await?; Ok(try_from_string(&raw_json)?) @@ -293,8 +293,8 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, message: message::DASDSetConfig) -> Result<(), Error> { +impl MessageHandler for Service { + async fn handle(&mut self, message: message::dasd::SetConfig) -> Result<(), Error> { let config = serde_json::to_string(&message.config)?; Ok(self.dasd_proxy.set_config(&config).await?) } From c9f3644766c60396ae117da3af53682ee8f125fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 2 Mar 2026 17:16:27 +0000 Subject: [PATCH 13/22] Use Storage1Proxy instead of the old StorageDBusClient --- rust/agama-storage-client/src/dbus.rs | 120 ----------------------- rust/agama-storage-client/src/lib.rs | 1 - rust/agama-storage-client/src/service.rs | 50 +++++++--- 3 files changed, 38 insertions(+), 133 deletions(-) delete mode 100644 rust/agama-storage-client/src/dbus.rs diff --git a/rust/agama-storage-client/src/dbus.rs b/rust/agama-storage-client/src/dbus.rs deleted file mode 100644 index 228a2e3df5..0000000000 --- a/rust/agama-storage-client/src/dbus.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) [2025-2026] SUSE LLC -// -// All Rights Reserved. -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 2 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along -// with this program; if not, contact SUSE LLC. -// -// To contact SUSE LLC about this file by physical or electronic mail, you may -// find current contact information at www.suse.com. - -use agama_utils::{api::storage::Config, products::ProductSpec}; -use serde_json::Value; -use zbus::{names::BusName, zvariant::OwnedObjectPath, Message}; - -const SERVICE_NAME: &str = "org.opensuse.Agama.Storage1"; -const OBJECT_PATH: &str = "/org/opensuse/Agama/Storage1"; -const INTERFACE: &str = "org.opensuse.Agama.Storage1"; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - DBus(#[from] zbus::Error), - #[error(transparent)] - DBusName(#[from] zbus::names::Error), - #[error(transparent)] - DBusVariant(#[from] zbus::zvariant::Error), - #[error(transparent)] - Json(#[from] serde_json::Error), -} - -/// Generic client for the storage D-Bus service. -#[derive(Clone)] -pub struct StorageDBusClient { - connection: zbus::Connection, -} - -impl StorageDBusClient { - pub fn new(connection: zbus::Connection) -> Self { - Self { connection } - } - - pub async fn call_action(&self, action: String) -> Result<(), Error> { - self.call(&action, &()).await?; - Ok(()) - } - - /// Sets the storage configuration. - pub async fn set_storage_config( - &self, - product: &ProductSpec, - config: Option, - ) -> Result<(), Error> { - let product_json = serde_json::to_string(&product)?; - let config = config.filter(|c| c.has_value()); - let config_json = serde_json::to_string(&config)?; - self.call("SetConfig", &(product_json, config_json)).await?; - Ok(()) - } - - pub async fn set_bootloader_config(&self, config: serde_json::Value) -> Result<(), Error> { - self.call("SetConfig", &(config.to_string())).await?; - Ok(()) - } - - /// Solves the configuration model. - pub async fn solve_config_model(&self, model: Value) -> Result, Error> { - let message = self.call("SolveConfigModel", &(model.to_string())).await?; - try_from_message(message) - } - - pub async fn set_locale(&self, locale: String) -> Result<(), Error> { - self.call("SetLocale", &(locale)).await?; - Ok(()) - } - - /// Calls the given method on the `org.opensuse.Agama.Storage1` interaface. - pub async fn call( - &self, - method: &str, - body: &T, - ) -> Result { - let bus = BusName::try_from(SERVICE_NAME.to_string())?; - let path = OwnedObjectPath::try_from(OBJECT_PATH)?; - self.connection - .call_method(Some(&bus), &path, Some(INTERFACE), method, body) - .await - .map_err(|e| e.into()) - } -} - -fn try_from_message( - message: Message, -) -> Result { - let raw_json: String = message.body().deserialize()?; - try_from_string(&raw_json) -} - -/// Converts a string into a Value. -/// -/// If the string is "null", return the default value. -pub fn try_from_string( - raw_json: &str, -) -> Result { - let json: serde_json::Value = serde_json::from_str(raw_json)?; - if json.is_null() { - return Ok(T::default()); - } - let value = serde_json::from_value(json)?; - Ok(value) -} diff --git a/rust/agama-storage-client/src/lib.rs b/rust/agama-storage-client/src/lib.rs index 6007008109..43425cd508 100644 --- a/rust/agama-storage-client/src/lib.rs +++ b/rust/agama-storage-client/src/lib.rs @@ -28,5 +28,4 @@ pub use service::{Error, Service}; pub mod message; -mod dbus; pub mod proxies; diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 9088311182..8296d33009 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -29,7 +29,6 @@ use async_trait::async_trait; use tokio::sync::oneshot; use crate::{ - dbus::{try_from_string, StorageDBusClient}, message, proxies::{self, BootloaderProxy, DASDProxy, ISCSIProxy, Storage1Proxy}, }; @@ -41,9 +40,9 @@ pub enum Error { #[error(transparent)] DBus(#[from] zbus::Error), #[error(transparent)] - DBusClient(#[from] crate::dbus::Error), - #[error(transparent)] JSON(#[from] serde_json::Error), + #[error("Method not found: {0}")] + MethodNotFound(String), } /// Builds and starts the service. @@ -71,7 +70,6 @@ impl Starter { let dasd_proxy = proxies::DASDProxy::new(&self.dbus).await?; let service = Service { - storage_dbus: StorageDBusClient::new(self.dbus), storage_proxy, bootloader_proxy, iscsi_proxy, @@ -83,7 +81,6 @@ impl Starter { } pub struct Service { - storage_dbus: StorageDBusClient, storage_proxy: Storage1Proxy<'static>, bootloader_proxy: BootloaderProxy<'static>, iscsi_proxy: ISCSIProxy<'static>, @@ -97,7 +94,17 @@ impl Actor for Service { #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::CallAction) -> Result<(), Error> { - Ok(self.storage_dbus.call_action(message.action).await?) + match message.action.as_str() { + "Activate" => self.storage_proxy.activate().await?, + "Probe" => self.storage_proxy.probe().await?, + "Install" => self.storage_proxy.install().await?, + "Finish" => self.storage_proxy.finish().await?, + "Umount" => self.storage_proxy.umount().await?, + _ => { + return Err(Error::MethodNotFound(message.action)); + } + } + Ok(()) } } @@ -173,14 +180,18 @@ impl MessageHandler for Service { &mut self, message: message::SolveConfigModel, ) -> Result, Error> { - Ok(self.storage_dbus.solve_config_model(message.model).await?) + let raw_json = self + .storage_proxy + .solve_config_model(&message.model.to_string()) + .await?; + Ok(try_from_string(&raw_json)?) } } #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::SetLocale) -> Result<(), Error> { - Ok(self.storage_dbus.set_locale(message.locale).await?) + Ok(self.storage_proxy.set_locale(&message.locale).await?) } } @@ -190,10 +201,13 @@ impl MessageHandler for Service { &mut self, message: message::SetStorageConfig, ) -> Result>, Error> { - let client = self.storage_dbus.clone(); + let proxy = self.storage_proxy.clone(); let result = run_in_background(async move { let product = message.product.read().await; - client.set_storage_config(&product, message.config).await?; + let product_json = serde_json::to_string(&*product)?; + let config = message.config.filter(|c| c.has_value()); + let config_json = serde_json::to_string(&config)?; + proxy.set_config(&product_json, &config_json).await?; Ok(()) }); Ok(Box::pin(async move { @@ -218,8 +232,8 @@ impl MessageHandler for Service { #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::SetBootloaderConfig) -> Result<(), Error> { - self.storage_dbus - .set_bootloader_config(message.config) + self.bootloader_proxy + .set_config(&message.config.to_string()) .await?; Ok(()) } @@ -311,3 +325,15 @@ where }); rx } + +/// Converts a string into a Value. +/// +/// If the string is "null", return the default value. +fn try_from_string(raw_json: &str) -> Result { + let json: serde_json::Value = serde_json::from_str(raw_json)?; + if json.is_null() { + return Ok(T::default()); + } + let value = serde_json::from_value(json)?; + Ok(value) +} From 98d4fd64d511df04abd51683af648797f0021c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 09:13:40 +0000 Subject: [PATCH 14/22] Move bootloader messages to its own module --- rust/agama-bootloader/src/client.rs | 7 ++- rust/agama-storage-client/src/message.rs | 26 +---------- .../src/message/bootloader.rs | 44 +++++++++++++++++++ rust/agama-storage-client/src/service.rs | 8 ++-- 4 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 rust/agama-storage-client/src/message/bootloader.rs diff --git a/rust/agama-bootloader/src/client.rs b/rust/agama-bootloader/src/client.rs index bb7e16778e..55de79bcf1 100644 --- a/rust/agama-bootloader/src/client.rs +++ b/rust/agama-bootloader/src/client.rs @@ -88,7 +88,10 @@ impl Client { #[async_trait] impl BootloaderClient for Client { async fn get_config(&self) -> ClientResult { - Ok(self.storage_dbus.call(message::GetBootloaderConfig).await?) + Ok(self + .storage_dbus + .call(message::bootloader::GetConfig) + .await?) } async fn set_config(&self, config: &Config) -> ClientResult<()> { @@ -102,7 +105,7 @@ impl BootloaderClient for Client { let value = serde_json::to_value(&full_config)?; _ = self .storage_dbus - .call(message::SetBootloaderConfig::new(value)) + .call(message::bootloader::SetConfig::new(value)) .await; Ok(()) } diff --git a/rust/agama-storage-client/src/message.rs b/rust/agama-storage-client/src/message.rs index a942dc7020..385e1ecfd1 100644 --- a/rust/agama-storage-client/src/message.rs +++ b/rust/agama-storage-client/src/message.rs @@ -22,7 +22,7 @@ use std::sync::Arc; use agama_utils::{ actor::Message, - api::{bootloader, storage::Config, Issue}, + api::{storage::Config, Issue}, products::ProductSpec, BoxFuture, }; @@ -30,6 +30,7 @@ use tokio::sync::RwLock; use crate::service; +pub mod bootloader; pub mod dasd; pub mod iscsi; @@ -122,29 +123,6 @@ impl Message for SolveConfigModel { type Reply = Option; } -pub struct GetBootloaderConfig; - -impl Message for GetBootloaderConfig { - type Reply = bootloader::Config; -} - -pub struct SetBootloaderConfig { - pub config: serde_json::Value, -} - -impl SetBootloaderConfig { - // FIXME: To be consistent, this action should receive - // the bootloader configuration. However, it uses an internal - // FullConfig struct defined in the agama-bootloader::client module. - pub fn new(config: serde_json::Value) -> Self { - Self { config } - } -} - -impl Message for SetBootloaderConfig { - type Reply = (); -} - pub struct SetLocale { pub locale: String, } diff --git a/rust/agama-storage-client/src/message/bootloader.rs b/rust/agama-storage-client/src/message/bootloader.rs new file mode 100644 index 0000000000..0e2bbf4f0a --- /dev/null +++ b/rust/agama-storage-client/src/message/bootloader.rs @@ -0,0 +1,44 @@ +// Copyright (c) [2026] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use agama_utils::{actor::Message, api::bootloader}; + +pub struct GetConfig; + +impl Message for GetConfig { + type Reply = bootloader::Config; +} + +pub struct SetConfig { + pub config: serde_json::Value, +} + +impl SetConfig { + // FIXME: To be consistent, this action should receive + // the bootloader configuration. However, it uses an internal + // FullConfig struct defined in the agama-bootloader::client module. + pub fn new(config: serde_json::Value) -> Self { + Self { config } + } +} + +impl Message for SetConfig { + type Reply = (); +} diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 8296d33009..96e2b9d423 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -219,10 +219,10 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { +impl MessageHandler for Service { async fn handle( &mut self, - _message: message::GetBootloaderConfig, + _message: message::bootloader::GetConfig, ) -> Result { let raw_json = self.bootloader_proxy.config().await?; Ok(try_from_string(&raw_json)?) @@ -230,8 +230,8 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, message: message::SetBootloaderConfig) -> Result<(), Error> { +impl MessageHandler for Service { + async fn handle(&mut self, message: message::bootloader::SetConfig) -> Result<(), Error> { self.bootloader_proxy .set_config(&message.config.to_string()) .await?; From a663d28f22e193567349bbfabc66f651702fd475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 09:18:56 +0000 Subject: [PATCH 15/22] Update from code review --- rust/agama-bootloader/src/client.rs | 5 ++--- rust/agama-storage-client/src/service.rs | 3 +++ rust/agama-utils/src/lib.rs | 3 --- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/rust/agama-bootloader/src/client.rs b/rust/agama-bootloader/src/client.rs index 55de79bcf1..0025d21930 100644 --- a/rust/agama-bootloader/src/client.rs +++ b/rust/agama-bootloader/src/client.rs @@ -103,10 +103,9 @@ impl BootloaderClient for Client { // ignore return value as currently it does not fail and who knows what future brings // but it should not be part of result and instead transformed to Issue let value = serde_json::to_value(&full_config)?; - _ = self - .storage_dbus + self.storage_dbus .call(message::bootloader::SetConfig::new(value)) - .await; + .await?; Ok(()) } diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 96e2b9d423..544c90b141 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -67,7 +67,10 @@ impl Starter { bootloader_proxy.config().await?; let iscsi_proxy = proxies::ISCSIProxy::new(&self.dbus).await?; + iscsi_proxy.config().await?; + let dasd_proxy = proxies::DASDProxy::new(&self.dbus).await?; + dasd_proxy.config().await?; let service = Service { storage_proxy, diff --git a/rust/agama-utils/src/lib.rs b/rust/agama-utils/src/lib.rs index 1eb102fe59..60569c6df1 100644 --- a/rust/agama-utils/src/lib.rs +++ b/rust/agama-utils/src/lib.rs @@ -41,9 +41,6 @@ use std::pin::Pin; /// A pinned, boxed, and sendable future. pub type BoxFuture = Pin + Send + 'static>>; -/// A pinned, boxed, and sendable future with a custom lifetime. -pub type BoxFutureWithLifetime<'a, T> = Pin + Send + 'a>>; - /// Does nothing at runtime, marking the text for translation. /// /// This is useful when you need both the untranslated id From 703c3e3f4379bdffb2e73373889a8d38ceb2c685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 09:29:03 +0000 Subject: [PATCH 16/22] Do not initialize the DASD proxy --- rust/agama-storage-client/src/service.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 544c90b141..a215776061 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -70,7 +70,6 @@ impl Starter { iscsi_proxy.config().await?; let dasd_proxy = proxies::DASDProxy::new(&self.dbus).await?; - dasd_proxy.config().await?; let service = Service { storage_proxy, From 7fc5c33d179f2cf97a9465c1b0028c978b4f7739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 09:42:34 +0000 Subject: [PATCH 17/22] Make the dasd_proxy field as optional --- rust/agama-storage-client/src/service.rs | 37 ++++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index a215776061..0873f7e08e 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -23,6 +23,7 @@ use std::future::Future; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, api::{bootloader, iscsi, storage::Config, Issue}, + arch::Arch, BoxFuture, }; use async_trait::async_trait; @@ -69,7 +70,11 @@ impl Starter { let iscsi_proxy = proxies::ISCSIProxy::new(&self.dbus).await?; iscsi_proxy.config().await?; - let dasd_proxy = proxies::DASDProxy::new(&self.dbus).await?; + let dasd_proxy = if Arch::is_s390() { + Some(proxies::DASDProxy::new(&self.dbus).await?) + } else { + None + }; let service = Service { storage_proxy, @@ -86,7 +91,7 @@ pub struct Service { storage_proxy: Storage1Proxy<'static>, bootloader_proxy: BootloaderProxy<'static>, iscsi_proxy: ISCSIProxy<'static>, - dasd_proxy: DASDProxy<'static>, + dasd_proxy: Option>, } impl Actor for Service { @@ -282,7 +287,10 @@ impl MessageHandler for Service { #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, _message: message::dasd::Probe) -> Result<(), Error> { - Ok(self.dasd_proxy.probe().await?) + if let Some(proxy) = &self.dasd_proxy { + proxy.probe().await?; + } + Ok(()) } } @@ -292,8 +300,12 @@ impl MessageHandler for Service { &mut self, _message: message::dasd::GetSystem, ) -> Result, Error> { - let raw_json = self.dasd_proxy.dasd_system().await?; - Ok(try_from_string(&raw_json)?) + if let Some(proxy) = &self.dasd_proxy { + let raw_json = proxy.dasd_system().await?; + Ok(try_from_string(&raw_json)?) + } else { + Ok(None) + } } } @@ -303,16 +315,23 @@ impl MessageHandler for Service { &mut self, _message: message::dasd::GetConfig, ) -> Result, Error> { - let raw_json = self.dasd_proxy.config().await?; - Ok(try_from_string(&raw_json)?) + if let Some(proxy) = &self.dasd_proxy { + let raw_json = proxy.config().await?; + Ok(try_from_string(&raw_json)?) + } else { + Ok(None) + } } } #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::dasd::SetConfig) -> Result<(), Error> { - let config = serde_json::to_string(&message.config)?; - Ok(self.dasd_proxy.set_config(&config).await?) + if let Some(proxy) = &self.dasd_proxy { + let config = serde_json::to_string(&message.config)?; + proxy.set_config(&config).await?; + } + Ok(()) } } From f4779ead734ece38b7bb3279143eeb95744315f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 09:45:29 +0000 Subject: [PATCH 18/22] Load the DASD proxy configuration --- rust/agama-storage-client/src/service.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 0873f7e08e..6f7dfe6655 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -71,7 +71,9 @@ impl Starter { iscsi_proxy.config().await?; let dasd_proxy = if Arch::is_s390() { - Some(proxies::DASDProxy::new(&self.dbus).await?) + let proxy = proxies::DASDProxy::new(&self.dbus).await?; + proxy.config().await?; + Some(proxy) } else { None }; From 11024ce02441b35e3135fbb9293a9ac8c10d402b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 10:00:47 +0000 Subject: [PATCH 19/22] Fix issues calculation in the storage Monitor --- rust/agama-storage/src/monitor.rs | 16 +++++++++------- rust/agama-storage/src/service.rs | 21 +++++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/rust/agama-storage/src/monitor.rs b/rust/agama-storage/src/monitor.rs index d83d72cb41..4ecf97b23d 100644 --- a/rust/agama-storage/src/monitor.rs +++ b/rust/agama-storage/src/monitor.rs @@ -18,7 +18,6 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::client; use agama_utils::{ actor::Handler, api::{ @@ -47,8 +46,8 @@ pub enum Error { DBus(#[from] zbus::Error), #[error(transparent)] Event(#[from] broadcast::error::SendError), - #[error(transparent)] - Client(#[from] client::Error), + #[error("Storage D-Bus server error: {0}")] + DBusClient(#[from] agama_storage_client::Error), } #[proxy( @@ -104,7 +103,7 @@ pub struct Monitor { issues: Handler, events: event::Sender, connection: Connection, - // client: Client<'a>, + storage_dbus: Handler, } impl Monitor { @@ -113,13 +112,14 @@ impl Monitor { issues: Handler, events: event::Sender, connection: Connection, + storage_dbus: Handler, ) -> Self { Self { progress, issues, events, connection: connection.clone(), - // client: Client::new(connection).await, + storage_dbus, } } @@ -142,8 +142,10 @@ impl Monitor { } async fn update_issues(&self) -> Result<(), Error> { - // let issues = self.client.get_issues().await?; - let issues = vec![]; + let issues = self + .storage_dbus + .call(agama_storage_client::message::GetIssues) + .await?; self.issues .cast(issue::message::Set::new(Scope::Storage, issues))?; Ok(()) diff --git a/rust/agama-storage/src/service.rs b/rust/agama-storage/src/service.rs index 3e06ba69ed..9e873eaa2b 100644 --- a/rust/agama-storage/src/service.rs +++ b/rust/agama-storage/src/service.rs @@ -73,21 +73,26 @@ impl Starter { /// Starts the service and returns a handler to communicate with it. pub async fn start(self) -> Result, Error> { + let storage_dbus = agama_storage_client::service::Starter::new(self.dbus.clone()) + .start() + .await + .map_err(client::Error::from)?; let client = match self.client { Some(client) => client, - None => { - let storage_dbus = agama_storage_client::service::Starter::new(self.dbus.clone()) - .start() - .await - .map_err(client::Error::from)?; - Box::new(Client::new(storage_dbus)) - } + None => Box::new(Client::new(storage_dbus.clone())), }; let service = Service { client }; let handler = actor::spawn(service); - let monitor = Monitor::new(self.progress, self.issues, self.events, self.dbus).await; + let monitor = Monitor::new( + self.progress, + self.issues, + self.events, + self.dbus, + storage_dbus, + ) + .await; monitor::spawn(monitor)?; Ok(handler) } From 5ee4a127c99e87a96c1c1de86fc185f1d4c0eaba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 10:07:13 +0000 Subject: [PATCH 20/22] Drop unused code --- rust/agama-storage/src/client.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/rust/agama-storage/src/client.rs b/rust/agama-storage/src/client.rs index 4b08ef4fbb..03c8e4301f 100644 --- a/rust/agama-storage/src/client.rs +++ b/rust/agama-storage/src/client.rs @@ -31,11 +31,6 @@ use async_trait::async_trait; use serde_json::Value; use std::sync::Arc; use tokio::sync::RwLock; -use zbus::{names::BusName, zvariant::OwnedObjectPath, Connection, Message}; - -const SERVICE_NAME: &str = "org.opensuse.Agama.Storage1"; -const OBJECT_PATH: &str = "/org/opensuse/Agama/Storage1"; -const INTERFACE: &str = "org.opensuse.Agama.Storage1"; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -170,27 +165,3 @@ impl StorageClient for Client { Ok(()) } } - -#[derive(Clone)] -pub struct DBusClient { - connection: Connection, -} - -impl DBusClient { - pub fn new(connection: Connection) -> Self { - Self { connection } - } - - pub async fn call( - &self, - method: &str, - body: &T, - ) -> Result { - let bus = BusName::try_from(SERVICE_NAME.to_string())?; - let path = OwnedObjectPath::try_from(OBJECT_PATH)?; - self.connection - .call_method(Some(&bus), &path, Some(INTERFACE), method, body) - .await - .map_err(|e| e.into()) - } -} From 53e3223e428b57f43d3acd8d94adae2a9c338bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 10:09:34 +0000 Subject: [PATCH 21/22] Update changes files --- rust/package/agama.changes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 2414debaea..bcd09eaf1a 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Tue Mar 3 10:08:58 UTC 2026 - Imobach Gonzalez Sosa + +- Cache storage data using the new attributes-based API (bsc#1258466). + ------------------------------------------------------------------- Thu Feb 26 16:44:32 UTC 2026 - David Diaz From afa4398f7c32a3c7fe2af68bde5636d45cb8cd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 3 Mar 2026 11:05:04 +0000 Subject: [PATCH 22/22] Fix agama-storage initialization in tests --- rust/agama-storage/src/service.rs | 40 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/rust/agama-storage/src/service.rs b/rust/agama-storage/src/service.rs index 9e873eaa2b..de1b5584cc 100644 --- a/rust/agama-storage/src/service.rs +++ b/rust/agama-storage/src/service.rs @@ -73,27 +73,35 @@ impl Starter { /// Starts the service and returns a handler to communicate with it. pub async fn start(self) -> Result, Error> { - let storage_dbus = agama_storage_client::service::Starter::new(self.dbus.clone()) - .start() - .await - .map_err(client::Error::from)?; - let client = match self.client { - Some(client) => client, - None => Box::new(Client::new(storage_dbus.clone())), + let (client, storage_dbus) = match self.client { + Some(client) => (client, None), + None => { + let storage_dbus = agama_storage_client::service::Starter::new(self.dbus.clone()) + .start() + .await + .map_err(client::Error::from)?; + ( + Box::new(Client::new(storage_dbus.clone())) + as Box, + Some(storage_dbus), + ) + } }; let service = Service { client }; let handler = actor::spawn(service); - let monitor = Monitor::new( - self.progress, - self.issues, - self.events, - self.dbus, - storage_dbus, - ) - .await; - monitor::spawn(monitor)?; + if let Some(storage_dbus) = storage_dbus { + let monitor = Monitor::new( + self.progress, + self.issues, + self.events, + self.dbus, + storage_dbus, + ) + .await; + monitor::spawn(monitor)?; + } Ok(handler) } }