From 79aac4b250ced5cc253c4ee3c1235993f1b81ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Tue, 3 Mar 2026 15:49:56 +0000 Subject: [PATCH 01/10] Add zFCP proxy --- rust/agama-storage-client/src/proxies.rs | 2 + rust/agama-storage-client/src/proxies/zfcp.rs | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 rust/agama-storage-client/src/proxies/zfcp.rs diff --git a/rust/agama-storage-client/src/proxies.rs b/rust/agama-storage-client/src/proxies.rs index 2680c389d5..d03d6e6539 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -32,3 +32,5 @@ pub use dasd::{ DASDProxy, FormatChanged, FormatFinished, ProgressChanged as DASDProgressChanged, ProgressFinished as DASDProgressFinished, SystemChanged as DASDSystemChanged, }; + +pub mod zfcp; diff --git a/rust/agama-storage-client/src/proxies/zfcp.rs b/rust/agama-storage-client/src/proxies/zfcp.rs new file mode 100644 index 0000000000..e1831660f0 --- /dev/null +++ b/rust/agama-storage-client/src/proxies/zfcp.rs @@ -0,0 +1,57 @@ +// 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.ZFCP` + +use zbus::proxy; + +#[proxy( + default_service = "org.opensuse.Agama.Storage1", + default_path = "/org/opensuse/Agama/Storage1/ZFCP", + interface = "org.opensuse.Agama.Storage1.ZFCP", + assume_defaults = true +)] +pub trait ZFCP { + /// Probe method + fn probe(&self) -> zbus::Result<()>; + + /// System property + #[zbus(property)] + fn system(&self) -> zbus::Result; + + /// Config property + #[zbus(property)] + fn config(&self) -> zbus::Result; + + /// Issues property + #[zbus(property)] + fn issues(&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<()>; + + /// ProgressFinished signal + #[zbus(signal)] + fn progress_finished(&self) -> zbus::Result<()>; +} From 109307acc43b719a00f6db6b7179c5c7f20e9aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Tue, 3 Mar 2026 16:56:44 +0000 Subject: [PATCH 02/10] Add management for zFCP --- rust/agama-s390/src/dasd/client.rs | 6 +- rust/agama-s390/src/lib.rs | 1 + rust/agama-s390/src/message.rs | 6 + rust/agama-s390/src/service.rs | 73 +++++++-- rust/agama-s390/src/zfcp.rs | 25 +++ rust/agama-s390/src/zfcp/client.rs | 78 ++++++++++ rust/agama-s390/src/zfcp/monitor.rs | 147 ++++++++++++++++++ rust/agama-storage-client/src/message.rs | 1 + rust/agama-storage-client/src/message/zfcp.rs | 53 +++++++ rust/agama-storage-client/src/proxies.rs | 3 + rust/agama-storage-client/src/proxies/dasd.rs | 2 +- rust/agama-storage-client/src/service.rs | 63 +++++++- rust/agama-utils/src/api/s390/config.rs | 2 + rust/agama-utils/src/api/s390/system_info.rs | 1 + rust/agama-utils/src/api/scope.rs | 3 + 15 files changed, 443 insertions(+), 21 deletions(-) create mode 100644 rust/agama-s390/src/zfcp.rs create mode 100644 rust/agama-s390/src/zfcp/client.rs create mode 100644 rust/agama-s390/src/zfcp/monitor.rs create mode 100644 rust/agama-storage-client/src/message/zfcp.rs diff --git a/rust/agama-s390/src/dasd/client.rs b/rust/agama-s390/src/dasd/client.rs index 58ccc859ec..f7577a702b 100644 --- a/rust/agama-s390/src/dasd/client.rs +++ b/rust/agama-s390/src/dasd/client.rs @@ -50,10 +50,8 @@ pub struct Client { } impl Client { - pub async fn new( - storage_dbus: Handler, - ) -> Result { - Ok(Self { storage_dbus }) + pub fn new(storage_dbus: Handler) -> Self { + Self { storage_dbus } } } diff --git a/rust/agama-s390/src/lib.rs b/rust/agama-s390/src/lib.rs index 2ac1105c6b..ce60f01c98 100644 --- a/rust/agama-s390/src/lib.rs +++ b/rust/agama-s390/src/lib.rs @@ -24,6 +24,7 @@ pub use service::{Service, Starter}; pub mod dasd; pub mod message; pub mod test_utils; +pub mod zfcp; use agama_storage as storage; diff --git a/rust/agama-s390/src/message.rs b/rust/agama-s390/src/message.rs index d8063c1936..0dd70b69ca 100644 --- a/rust/agama-s390/src/message.rs +++ b/rust/agama-s390/src/message.rs @@ -29,6 +29,12 @@ impl Message for ProbeDASD { type Reply = (); } +pub struct ProbeZFCP; + +impl Message for ProbeZFCP { + type Reply = (); +} + pub struct GetSystem; impl Message for GetSystem { diff --git a/rust/agama-s390/src/service.rs b/rust/agama-s390/src/service.rs index 1c332778cb..72333d9e33 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -21,6 +21,7 @@ use crate::{ dasd::{self, client::DASDClient}, message, storage, + zfcp::{self, client::ZFCPClient}, }; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, @@ -39,8 +40,12 @@ pub enum Error { #[error(transparent)] DASDClient(#[from] dasd::client::Error), #[error(transparent)] + ZFCPClient(#[from] zfcp::client::Error), + #[error(transparent)] DASDMonitor(#[from] dasd::monitor::Error), - #[error("Storage D-Bus server error: {0}")] + #[error(transparent)] + ZFCPMonitor(#[from] zfcp::monitor::Error), + #[error(transparent)] DBusClient(#[from] agama_storage_client::Error), } @@ -50,6 +55,7 @@ pub struct Starter { progress: Handler, connection: zbus::Connection, dasd: Option>, + zfcp: Option>, } impl Starter { @@ -65,6 +71,7 @@ impl Starter { progress, connection, dasd: None, + zfcp: None, } } @@ -73,30 +80,51 @@ impl Starter { self } + pub fn with_zfcp(mut self, client: impl ZFCPClient + Send + 'static) -> Self { + self.zfcp = Some(Box::new(client)); + self + } + pub async fn start(self) -> Result, Error> { + let storage_dbus = agama_storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + let dasd_client = match self.dasd { Some(client) => client, - None => { - let storage_dbus = - agama_storage_client::service::Starter::new(self.connection.clone()) - .start() - .await?; - Box::new(dasd::Client::new(storage_dbus).await?) - } + None => Box::new(dasd::Client::new(storage_dbus.clone())), + }; + + let zfcp_client = match self.zfcp { + Some(client) => client, + None => Box::new(zfcp::Client::new(storage_dbus)), + }; + + let service = Service { + dasd: dasd_client, + zfcp: zfcp_client, }; - let service = Service { dasd: dasd_client }; let handler = actor::spawn(service); - let dasd_monitor = - dasd::Monitor::new(self.storage, self.progress, self.events, self.connection); + let dasd_monitor = dasd::Monitor::new( + self.storage.clone(), + self.progress.clone(), + self.events.clone(), + self.connection.clone(), + ); dasd::monitor::spawn(dasd_monitor)?; + let zfcp_monitor = + zfcp::Monitor::new(self.storage, self.progress, self.events, self.connection); + zfcp::monitor::spawn(zfcp_monitor)?; + Ok(handler) } } pub struct Service { dasd: Box, + zfcp: Box, } impl Service { @@ -122,11 +150,20 @@ impl MessageHandler for Service { } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::ProbeZFCP) -> Result<(), Error> { + self.zfcp.probe().await?; + Ok(()) + } +} + #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, _message: message::GetSystem) -> Result { let dasd = self.dasd.get_system().await?; - Ok(SystemInfo { dasd }) + let zfcp = self.zfcp.get_system().await?; + Ok(SystemInfo { dasd, zfcp }) } } @@ -134,15 +171,21 @@ impl MessageHandler for Service { impl MessageHandler for Service { async fn handle(&mut self, _message: message::GetConfig) -> Result { let dasd = self.dasd.get_config().await?; - Ok(Config { dasd }) + let zfcp = self.zfcp.get_config().await?; + Ok(Config { dasd, zfcp }) } } #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::SetConfig) -> Result<(), Error> { - let config = message.config.and_then(|c| c.dasd); - self.dasd.set_config(config).await?; + if let Some(config) = message.config { + self.dasd.set_config(config.dasd).await?; + self.zfcp.set_config(config.zfcp).await?; + } else { + self.dasd.set_config(None).await?; + self.zfcp.set_config(None).await?; + } Ok(()) } } diff --git a/rust/agama-s390/src/zfcp.rs b/rust/agama-s390/src/zfcp.rs new file mode 100644 index 0000000000..112c34f4eb --- /dev/null +++ b/rust/agama-s390/src/zfcp.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. + +pub mod client; +pub use client::Client; + +pub mod monitor; +pub use monitor::Monitor; diff --git a/rust/agama-s390/src/zfcp/client.rs b/rust/agama-s390/src/zfcp/client.rs new file mode 100644 index 0000000000..2ad21c9eed --- /dev/null +++ b/rust/agama-s390/src/zfcp/client.rs @@ -0,0 +1,78 @@ +// 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. + +//! Implements a client to access Agama's D-Bus API related to zFCP management. + +use agama_storage_client::message; +use agama_utils::actor::Handler; +use agama_utils::api::RawConfig; +use async_trait::async_trait; +use serde_json::Value; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + DBus(#[from] zbus::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + DBusClient(#[from] agama_storage_client::Error), +} + +#[async_trait] +pub trait ZFCPClient { + async fn probe(&self) -> Result<(), Error>; + async fn get_system(&self) -> Result, Error>; + async fn get_config(&self) -> Result, Error>; + async fn set_config(&self, config: Option) -> Result<(), Error>; +} + +#[derive(Clone)] +pub struct Client { + storage_dbus: Handler, +} + +impl Client { + pub fn new(storage_dbus: Handler) -> Self { + Self { storage_dbus } + } +} + +#[async_trait] +impl ZFCPClient for Client { + async fn probe(&self) -> Result<(), Error> { + Ok(self.storage_dbus.call(message::zfcp::Probe).await?) + } + + async fn get_system(&self) -> Result, Error> { + Ok(self.storage_dbus.call(message::zfcp::GetSystem).await?) + } + + async fn get_config(&self) -> Result, Error> { + Ok(self.storage_dbus.call(message::zfcp::GetConfig).await?) + } + + async fn set_config(&self, config: Option) -> Result<(), Error> { + self.storage_dbus + .call(message::zfcp::SetConfig::new(config)) + .await?; + Ok(()) + } +} diff --git a/rust/agama-s390/src/zfcp/monitor.rs b/rust/agama-s390/src/zfcp/monitor.rs new file mode 100644 index 0000000000..36f78e2632 --- /dev/null +++ b/rust/agama-s390/src/zfcp/monitor.rs @@ -0,0 +1,147 @@ +// 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 crate::storage; +use agama_storage_client::proxies::{ + ZFCPProgressChanged as ProgressChanged, ZFCPProgressFinished as ProgressFinished, ZFCPProxy, +}; +use agama_utils::{ + actor::Handler, + api::{ + event::{self, Event}, + Progress, Scope, + }, + progress, +}; +use serde::Deserialize; +use serde_json; +use tokio::sync::broadcast; +use tokio_stream::StreamExt; +use zbus::{message, Connection, MatchRule, MessageStream}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Progress(#[from] progress::service::Error), + #[error(transparent)] + Event(#[from] broadcast::error::SendError), + #[error(transparent)] + DBus(#[from] zbus::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Storage(#[from] storage::service::Error), +} + +#[derive(Debug, Deserialize)] +struct ProgressData { + pub size: usize, + pub steps: Vec, + pub step: String, + pub index: usize, +} + +impl From for Progress { + fn from(data: ProgressData) -> Self { + Progress { + scope: Scope::ZFCP, + size: data.size, + steps: data.steps, + step: data.step, + index: data.index, + } + } +} + +pub struct Monitor { + storage: Handler, + progress: Handler, + events: event::Sender, + connection: Connection, +} + +impl Monitor { + pub fn new( + storage: Handler, + progress: Handler, + events: event::Sender, + connection: Connection, + ) -> Self { + Self { + storage, + progress, + events, + connection, + } + } + + async fn run(&self) -> Result<(), Error> { + let proxy = ZFCPProxy::new(&self.connection).await?; + let rule = MatchRule::builder() + .msg_type(message::Type::Signal) + .sender(proxy.inner().destination())? + .path(proxy.inner().path())? + .interface(proxy.inner().interface())? + .build(); + let mut stream = MessageStream::for_match_rule(rule, &self.connection, None).await?; + + while let Some(Ok(message)) = stream.next().await { + if let Some(signal) = ProgressChanged::from_message(message.clone()) { + self.handle_progress_changed(signal).await?; + continue; + } + if let Some(signal) = ProgressFinished::from_message(message.clone()) { + self.handle_progress_finished(signal).await?; + continue; + } + tracing::warn!("Unmanaged ZFCP signal: {message:?}"); + } + + Ok(()) + } + + async fn handle_progress_changed(&self, signal: ProgressChanged) -> Result<(), Error> { + let args = signal.args()?; + let progress_data = serde_json::from_str::(args.progress)?; + self.progress + .call(progress::message::SetProgress::new(progress_data.into())) + .await?; + Ok(()) + } + + async fn handle_progress_finished(&self, _signal: ProgressFinished) -> Result<(), Error> { + self.progress + .call(progress::message::Finish::new(Scope::ZFCP)) + .await?; + Ok(()) + } +} + +/// Spawns a Tokio task for the monitor. +/// +/// * `monitor`: monitor to spawn. +pub fn spawn(monitor: Monitor) -> Result<(), Error> { + tokio::spawn(async move { + if let Err(e) = monitor.run().await { + tracing::error!("Error running the ZFCP monitor: {e:?}"); + } + }); + Ok(()) +} diff --git a/rust/agama-storage-client/src/message.rs b/rust/agama-storage-client/src/message.rs index 385e1ecfd1..4da645d0e3 100644 --- a/rust/agama-storage-client/src/message.rs +++ b/rust/agama-storage-client/src/message.rs @@ -33,6 +33,7 @@ use crate::service; pub mod bootloader; pub mod dasd; pub mod iscsi; +pub mod zfcp; pub struct CallAction { pub action: String, diff --git a/rust/agama-storage-client/src/message/zfcp.rs b/rust/agama-storage-client/src/message/zfcp.rs new file mode 100644 index 0000000000..335832093a --- /dev/null +++ b/rust/agama-storage-client/src/message/zfcp.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, api::RawConfig}; + +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/proxies.rs b/rust/agama-storage-client/src/proxies.rs index d03d6e6539..a72ae6722c 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -34,3 +34,6 @@ pub use dasd::{ }; pub mod zfcp; +pub use zfcp::{ + ProgressChanged as ZFCPProgressChanged, ProgressFinished as ZFCPProgressFinished, ZFCPProxy, +}; diff --git a/rust/agama-storage-client/src/proxies/dasd.rs b/rust/agama-storage-client/src/proxies/dasd.rs index aae0b2101a..ffc987c7ea 100644 --- a/rust/agama-storage-client/src/proxies/dasd.rs +++ b/rust/agama-storage-client/src/proxies/dasd.rs @@ -62,5 +62,5 @@ pub trait DASD { /// ProgressFinished signal #[zbus(signal)] - fn progress_finished(&self, status: &str) -> zbus::Result<()>; + fn progress_finished(&self) -> zbus::Result<()>; } diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 6f7dfe6655..65eb5b5e21 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::{ message, - proxies::{self, BootloaderProxy, DASDProxy, ISCSIProxy, Storage1Proxy}, + proxies::{self, BootloaderProxy, DASDProxy, ISCSIProxy, Storage1Proxy, ZFCPProxy}, }; #[derive(thiserror::Error, Debug)] @@ -78,11 +78,20 @@ impl Starter { None }; + let zfcp_proxy = if Arch::is_s390() { + let proxy = proxies::ZFCPProxy::new(&self.dbus).await?; + proxy.config().await?; + Some(proxy) + } else { + None + }; + let service = Service { storage_proxy, bootloader_proxy, iscsi_proxy, dasd_proxy, + zfcp_proxy, }; let handler = actor::spawn(service); Ok(handler) @@ -94,6 +103,7 @@ pub struct Service { bootloader_proxy: BootloaderProxy<'static>, iscsi_proxy: ISCSIProxy<'static>, dasd_proxy: Option>, + zfcp_proxy: Option>, } impl Actor for Service { @@ -337,6 +347,57 @@ impl MessageHandler for Service { } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::zfcp::Probe) -> Result<(), Error> { + if let Some(proxy) = &self.zfcp_proxy { + proxy.probe().await?; + } + Ok(()) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::zfcp::GetSystem, + ) -> Result, Error> { + if let Some(proxy) = &self.zfcp_proxy { + let raw_json = proxy.system().await?; + Ok(try_from_string(&raw_json)?) + } else { + Ok(None) + } + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::zfcp::GetConfig, + ) -> Result, Error> { + if let Some(proxy) = &self.zfcp_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::zfcp::SetConfig) -> Result<(), Error> { + if let Some(proxy) = &self.zfcp_proxy { + let config = serde_json::to_string(&message.config)?; + proxy.set_config(&config).await?; + } + Ok(()) + } +} + fn run_in_background(func: F) -> oneshot::Receiver> where F: Future> + Send + 'static, diff --git a/rust/agama-utils/src/api/s390/config.rs b/rust/agama-utils/src/api/s390/config.rs index 4bd1e0c941..b21e3ac544 100644 --- a/rust/agama-utils/src/api/s390/config.rs +++ b/rust/agama-utils/src/api/s390/config.rs @@ -32,4 +32,6 @@ use serde_with::skip_serializing_none; pub struct Config { /// Configuration of the DASD devices. pub dasd: Option, + /// Configuration of the zFCP devices. + pub zfcp: Option, } diff --git a/rust/agama-utils/src/api/s390/system_info.rs b/rust/agama-utils/src/api/s390/system_info.rs index f3c4528817..de857cb0d4 100644 --- a/rust/agama-utils/src/api/s390/system_info.rs +++ b/rust/agama-utils/src/api/s390/system_info.rs @@ -28,4 +28,5 @@ use serde_with::skip_serializing_none; #[schema(as = s390::SystemInfo)] pub struct SystemInfo { pub dasd: Option, + pub zfcp: Option, } diff --git a/rust/agama-utils/src/api/scope.rs b/rust/agama-utils/src/api/scope.rs index 63e87d7cdc..18dfb782f0 100644 --- a/rust/agama-utils/src/api/scope.rs +++ b/rust/agama-utils/src/api/scope.rs @@ -53,5 +53,8 @@ pub enum Scope { #[strum(serialize = "dasd")] #[serde(rename = "dasd")] DASD, + #[strum(serialize = "zfcp")] + #[serde(rename = "zfcp")] + ZFCP, Users, } From 1b2704ee4a2dad86f9da40af07365816f50083d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 4 Mar 2026 11:48:53 +0000 Subject: [PATCH 03/10] Improve zFCP monitor to listen to PropertiesChanged --- rust/agama-s390/src/zfcp/monitor.rs | 37 ++++++++++++++++-------- rust/agama-storage-client/src/proxies.rs | 4 +-- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/rust/agama-s390/src/zfcp/monitor.rs b/rust/agama-s390/src/zfcp/monitor.rs index 36f78e2632..9b0217cf5a 100644 --- a/rust/agama-s390/src/zfcp/monitor.rs +++ b/rust/agama-s390/src/zfcp/monitor.rs @@ -19,9 +19,7 @@ // find current contact information at www.suse.com. use crate::storage; -use agama_storage_client::proxies::{ - ZFCPProgressChanged as ProgressChanged, ZFCPProgressFinished as ProgressFinished, ZFCPProxy, -}; +use agama_storage_client::proxies::{zfcp, ZFCPProxy}; use agama_utils::{ actor::Handler, api::{ @@ -34,7 +32,7 @@ use serde::Deserialize; use serde_json; use tokio::sync::broadcast; use tokio_stream::StreamExt; -use zbus::{message, Connection, MatchRule, MessageStream}; +use zbus::{fdo::PropertiesChanged, message, Connection, MatchRule, MessageStream}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -98,26 +96,41 @@ impl Monitor { .msg_type(message::Type::Signal) .sender(proxy.inner().destination())? .path(proxy.inner().path())? - .interface(proxy.inner().interface())? .build(); let mut stream = MessageStream::for_match_rule(rule, &self.connection, None).await?; - while let Some(Ok(message)) = stream.next().await { - if let Some(signal) = ProgressChanged::from_message(message.clone()) { + while let Some(message) = stream.next().await { + let message = message?; + + if let Some(signal) = PropertiesChanged::from_message(message.clone()) { + self.handle_properties_changed(signal).await?; + continue; + } + if let Some(signal) = zfcp::ProgressChanged::from_message(message.clone()) { self.handle_progress_changed(signal).await?; continue; } - if let Some(signal) = ProgressFinished::from_message(message.clone()) { + if let Some(signal) = zfcp::ProgressFinished::from_message(message.clone()) { self.handle_progress_finished(signal).await?; continue; } - tracing::warn!("Unmanaged ZFCP signal: {message:?}"); + tracing::warn!("Unmanaged zFCP signal: {message:?}"); } Ok(()) } - async fn handle_progress_changed(&self, signal: ProgressChanged) -> Result<(), Error> { + async fn handle_properties_changed(&self, signal: PropertiesChanged) -> Result<(), Error> { + let args = signal.args()?; + if args.changed_properties().get("System").is_some() { + self.events + .send(Event::SystemChanged { scope: Scope::ZFCP })?; + self.storage.cast(storage::message::Probe)?; + } + Ok(()) + } + + async fn handle_progress_changed(&self, signal: zfcp::ProgressChanged) -> Result<(), Error> { let args = signal.args()?; let progress_data = serde_json::from_str::(args.progress)?; self.progress @@ -126,7 +139,7 @@ impl Monitor { Ok(()) } - async fn handle_progress_finished(&self, _signal: ProgressFinished) -> Result<(), Error> { + async fn handle_progress_finished(&self, _signal: zfcp::ProgressFinished) -> Result<(), Error> { self.progress .call(progress::message::Finish::new(Scope::ZFCP)) .await?; @@ -140,7 +153,7 @@ impl Monitor { pub fn spawn(monitor: Monitor) -> Result<(), Error> { tokio::spawn(async move { if let Err(e) = monitor.run().await { - tracing::error!("Error running the ZFCP monitor: {e:?}"); + tracing::error!("Error running the zFCP monitor: {e:?}"); } }); Ok(()) diff --git a/rust/agama-storage-client/src/proxies.rs b/rust/agama-storage-client/src/proxies.rs index a72ae6722c..8e880ba025 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -34,6 +34,4 @@ pub use dasd::{ }; pub mod zfcp; -pub use zfcp::{ - ProgressChanged as ZFCPProgressChanged, ProgressFinished as ZFCPProgressFinished, ZFCPProxy, -}; +pub use zfcp::ZFCPProxy; From 3a6926a7df94e0d55e3373c18b41b046b1215252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 4 Mar 2026 12:06:47 +0000 Subject: [PATCH 04/10] Small improvements in DASD monitor --- rust/agama-s390/src/dasd/monitor.rs | 29 ++++++++++++------------ rust/agama-storage-client/src/proxies.rs | 7 ++---- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/rust/agama-s390/src/dasd/monitor.rs b/rust/agama-s390/src/dasd/monitor.rs index 6c713f0a1e..1c71e143b1 100644 --- a/rust/agama-s390/src/dasd/monitor.rs +++ b/rust/agama-s390/src/dasd/monitor.rs @@ -19,10 +19,7 @@ // find current contact information at www.suse.com. use crate::storage; -use agama_storage_client::proxies::{ - DASDProgressChanged as ProgressChanged, DASDProgressFinished as ProgressFinished, DASDProxy, - DASDSystemChanged as SystemChanged, FormatChanged, FormatFinished, -}; +use agama_storage_client::proxies::{dasd, DASDProxy}; use agama_utils::{ actor::Handler, api::{ @@ -104,24 +101,26 @@ impl Monitor { .build(); let mut stream = MessageStream::for_match_rule(rule, &self.connection, None).await?; - while let Some(Ok(message)) = stream.next().await { - if let Some(signal) = SystemChanged::from_message(message.clone()) { + while let Some(message) = stream.next().await { + let message = message?; + + if let Some(signal) = dasd::SystemChanged::from_message(message.clone()) { self.handle_system_changed(signal)?; continue; } - if let Some(signal) = ProgressChanged::from_message(message.clone()) { + if let Some(signal) = dasd::ProgressChanged::from_message(message.clone()) { self.handle_progress_changed(signal).await?; continue; } - if let Some(signal) = ProgressFinished::from_message(message.clone()) { + if let Some(signal) = dasd::ProgressFinished::from_message(message.clone()) { self.handle_progress_finished(signal).await?; continue; } - if let Some(signal) = FormatChanged::from_message(message.clone()) { + if let Some(signal) = dasd::FormatChanged::from_message(message.clone()) { self.handle_format_changed(signal)?; continue; } - if let Some(signal) = FormatFinished::from_message(message.clone()) { + if let Some(signal) = dasd::FormatFinished::from_message(message.clone()) { self.handle_format_finished(signal)?; continue; } @@ -131,14 +130,14 @@ impl Monitor { Ok(()) } - fn handle_system_changed(&self, _signal: SystemChanged) -> Result<(), Error> { + fn handle_system_changed(&self, _signal: dasd::SystemChanged) -> Result<(), Error> { self.events .send(Event::SystemChanged { scope: Scope::DASD })?; self.storage.cast(storage::message::Probe)?; Ok(()) } - async fn handle_progress_changed(&self, signal: ProgressChanged) -> Result<(), Error> { + async fn handle_progress_changed(&self, signal: dasd::ProgressChanged) -> Result<(), Error> { let args = signal.args()?; let progress_data = serde_json::from_str::(args.progress)?; self.progress @@ -147,21 +146,21 @@ impl Monitor { Ok(()) } - async fn handle_progress_finished(&self, _signal: ProgressFinished) -> Result<(), Error> { + async fn handle_progress_finished(&self, _signal: dasd::ProgressFinished) -> Result<(), Error> { self.progress .call(progress::message::Finish::new(Scope::DASD)) .await?; Ok(()) } - fn handle_format_changed(&self, signal: FormatChanged) -> Result<(), Error> { + fn handle_format_changed(&self, signal: dasd::FormatChanged) -> Result<(), Error> { let args = signal.args()?; let summary = serde_json::from_str::(args.summary)?; self.events.send(Event::DASDFormatChanged { summary })?; Ok(()) } - fn handle_format_finished(&self, _signal: FormatFinished) -> Result<(), Error> { + fn handle_format_finished(&self, _signal: dasd::FormatFinished) -> Result<(), Error> { self.events.send(Event::DASDFormatFinished)?; Ok(()) } diff --git a/rust/agama-storage-client/src/proxies.rs b/rust/agama-storage-client/src/proxies.rs index 8e880ba025..cf34c85137 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -27,11 +27,8 @@ 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, -}; +pub mod dasd; +pub use dasd::DASDProxy; pub mod zfcp; pub use zfcp::ZFCPProxy; From 7ee897d00f39ac5a15a1213c8eede6cd6d864794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 4 Mar 2026 12:43:12 +0000 Subject: [PATCH 05/10] Add tests for zFCP --- rust/agama-s390/src/lib.rs | 39 ++++++++++++++-- rust/agama-s390/src/service.rs | 31 ++++++------ rust/agama-s390/src/test_utils.rs | 78 ++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 20 deletions(-) diff --git a/rust/agama-s390/src/lib.rs b/rust/agama-s390/src/lib.rs index ce60f01c98..cd89962d61 100644 --- a/rust/agama-s390/src/lib.rs +++ b/rust/agama-s390/src/lib.rs @@ -31,7 +31,7 @@ use agama_storage as storage; #[cfg(test)] mod tests { use super::*; - use crate::test_utils::TestDASDClient; + use crate::test_utils::{TestDASDClient, TestZFCPClient}; use agama_utils::{ actor::Handler, api::{s390::Config, Event}, @@ -43,6 +43,7 @@ mod tests { struct Context { handler: Handler, dasd: TestDASDClient, + zfcp: TestZFCPClient, } impl AsyncTestContext for Context { @@ -59,19 +60,25 @@ mod tests { ) .await; let dasd = TestDASDClient::new(); + let zfcp = TestZFCPClient::new(); let handler = Service::starter(storage, events, progress, connection) .with_dasd(dasd.clone()) + .with_zfcp(zfcp.clone()) .start() .await - .expect("Could not start the DASD service"); + .expect("Could not start the s390 service"); - Context { handler, dasd } + Context { + handler, + dasd, + zfcp, + } } } #[test_context(Context)] #[tokio::test] - async fn test_probe(ctx: &mut Context) -> Result<(), service::Error> { + async fn test_probe_dasd(ctx: &mut Context) -> Result<(), service::Error> { ctx.handler.call(message::ProbeDASD).await?; let state = ctx.dasd.state().await; @@ -80,11 +87,23 @@ mod tests { Ok(()) } + #[test_context(Context)] + #[tokio::test] + async fn test_probe_zfcp(ctx: &mut Context) -> Result<(), service::Error> { + ctx.handler.call(message::ProbeZFCP).await?; + + let state = ctx.zfcp.state().await; + assert!(state.probed); + + Ok(()) + } + #[test_context(Context)] #[tokio::test] async fn test_get_system(ctx: &mut Context) -> Result<(), service::Error> { let system = ctx.handler.call(message::GetSystem).await?; assert!(system.dasd.is_some()); + assert!(system.zfcp.is_some()); Ok(()) } @@ -99,6 +118,17 @@ mod tests { "devices": [ { "channel": "0.0.0100", + "active": true, + "format": true + } + ] + }, + "zfcp": { + "devices": [ + { + "channel": "0.0.1a00", + "wwpn": "0x5005076300c20b8e", + "lun": "0x0001000000000000", "active": true } ] @@ -112,6 +142,7 @@ mod tests { let config = ctx.handler.call(message::GetConfig).await?; assert!(config.dasd.is_some()); + assert!(config.zfcp.is_some()); Ok(()) } diff --git a/rust/agama-s390/src/service.rs b/rust/agama-s390/src/service.rs index 72333d9e33..58d1d8f908 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -86,23 +86,24 @@ impl Starter { } pub async fn start(self) -> Result, Error> { - let storage_dbus = agama_storage_client::service::Starter::new(self.connection.clone()) - .start() - .await?; - - let dasd_client = match self.dasd { - Some(client) => client, - None => Box::new(dasd::Client::new(storage_dbus.clone())), - }; - - let zfcp_client = match self.zfcp { - Some(client) => client, - None => Box::new(zfcp::Client::new(storage_dbus)), - }; + let mut dasd_client = self.dasd; + let mut zfcp_client = self.zfcp; + + if dasd_client.is_none() || zfcp_client.is_none() { + let storage_dbus = agama_storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + if dasd_client.is_none() { + dasd_client = Some(Box::new(dasd::Client::new(storage_dbus.clone()))); + } + if zfcp_client.is_none() { + zfcp_client = Some(Box::new(zfcp::Client::new(storage_dbus))); + } + } let service = Service { - dasd: dasd_client, - zfcp: zfcp_client, + dasd: dasd_client.unwrap(), + zfcp: zfcp_client.unwrap(), }; let handler = actor::spawn(service); diff --git a/rust/agama-s390/src/test_utils.rs b/rust/agama-s390/src/test_utils.rs index 8830ce80f8..5bd3f095e4 100644 --- a/rust/agama-s390/src/test_utils.rs +++ b/rust/agama-s390/src/test_utils.rs @@ -24,6 +24,7 @@ use crate::{ dasd::client::{DASDClient, Error}, service::{Service, Starter}, storage, + zfcp::{self, client::ZFCPClient}, }; use agama_utils::{ actor::Handler, @@ -127,7 +128,80 @@ impl DASDClient for TestDASDClient { } } -/// Starts a testing DASD service. +#[derive(Default, Clone)] +pub struct TestZFCPClientState { + pub probed: bool, + pub config: Option, +} + +/// Test client for zFCP. +/// +/// This client implements a dummy client to replace the original [ZFCPClient]. +#[derive(Clone)] +pub struct TestZFCPClient { + state: Arc>, +} + +impl Default for TestZFCPClient { + fn default() -> Self { + Self::new() + } +} + +impl TestZFCPClient { + pub fn new() -> Self { + let state = TestZFCPClientState::default(); + Self { + state: Arc::new(Mutex::new(state)), + } + } + + pub async fn state(&self) -> TestZFCPClientState { + self.state.lock().await.clone() + } +} + +#[async_trait] +impl ZFCPClient for TestZFCPClient { + async fn probe(&self) -> Result<(), zfcp::client::Error> { + let mut state = self.state.lock().await; + state.probed = true; + Ok(()) + } + + async fn get_system(&self) -> Result, zfcp::client::Error> { + let system: Value = serde_json::from_str( + r#" + { + "devices": [ + { + "channel": "0.0.1a00", + "wwpn": "0x5005076300c20b8e", + "lun": "0x0001000000000000", + "active": true + } + ] + } + "#, + ) + .unwrap(); + + Ok(Some(system)) + } + + async fn get_config(&self) -> Result, zfcp::client::Error> { + let state = self.state.lock().await; + Ok(state.config.clone()) + } + + async fn set_config(&self, config: Option) -> Result<(), zfcp::client::Error> { + let mut state = self.state.lock().await; + state.config = config; + Ok(()) + } +} + +/// Starts a testing s390 service. pub async fn start_service( storage: Handler, events: event::Sender, @@ -135,8 +209,10 @@ pub async fn start_service( connection: zbus::Connection, ) -> Handler { let dasd = TestDASDClient::new(); + let zfcp = TestZFCPClient::new(); Starter::new(storage, events, progress, connection) .with_dasd(dasd) + .with_zfcp(zfcp) .start() .await .expect("Could not start a testing s390 service") From caaa3cb20ccd46d15ae42fdb443ebd23562a14c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 4 Mar 2026 16:27:25 +0000 Subject: [PATCH 06/10] Add zFCP issues --- rust/agama-manager/src/service.rs | 1 + rust/agama-manager/src/test_utils.rs | 1 + rust/agama-s390/src/lib.rs | 2 +- rust/agama-s390/src/service.rs | 58 +++++++++++++------ rust/agama-s390/src/test_utils.rs | 5 +- rust/agama-s390/src/zfcp/monitor.rs | 27 ++++++++- rust/agama-storage-client/src/message/zfcp.rs | 11 +++- rust/agama-storage-client/src/service.rs | 12 ++++ 8 files changed, 93 insertions(+), 24 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 0e042bc976..9dd81af502 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -358,6 +358,7 @@ impl Starter { storage.clone(), self.events.clone(), progress.clone(), + issues.clone(), self.dbus.clone(), ) .start() diff --git a/rust/agama-manager/src/test_utils.rs b/rust/agama-manager/src/test_utils.rs index 757c6de551..338971a97d 100644 --- a/rust/agama-manager/src/test_utils.rs +++ b/rust/agama-manager/src/test_utils.rs @@ -59,6 +59,7 @@ pub async fn start_service(events: event::Sender, dbus: zbus::Connection) -> Han storage.clone(), events.clone(), progress.clone(), + issues.clone(), dbus.clone(), ) .await; diff --git a/rust/agama-s390/src/lib.rs b/rust/agama-s390/src/lib.rs index cd89962d61..c60c74c307 100644 --- a/rust/agama-s390/src/lib.rs +++ b/rust/agama-s390/src/lib.rs @@ -61,7 +61,7 @@ mod tests { .await; let dasd = TestDASDClient::new(); let zfcp = TestZFCPClient::new(); - let handler = Service::starter(storage, events, progress, connection) + let handler = Service::starter(storage, events, progress, issues, connection) .with_dasd(dasd.clone()) .with_zfcp(zfcp.clone()) .start() diff --git a/rust/agama-s390/src/service.rs b/rust/agama-s390/src/service.rs index 58d1d8f908..415a663786 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -29,7 +29,7 @@ use agama_utils::{ event, s390::{Config, SystemInfo}, }, - progress, + issue, progress, }; use async_trait::async_trait; @@ -53,6 +53,7 @@ pub struct Starter { storage: Handler, events: event::Sender, progress: Handler, + issues: Handler, connection: zbus::Connection, dasd: Option>, zfcp: Option>, @@ -63,12 +64,14 @@ impl Starter { storage: Handler, events: event::Sender, progress: Handler, + issues: Handler, connection: zbus::Connection, ) -> Self { Self { storage, events, progress, + issues, connection, dasd: None, zfcp: None, @@ -86,25 +89,31 @@ impl Starter { } pub async fn start(self) -> Result, Error> { - let mut dasd_client = self.dasd; - let mut zfcp_client = self.zfcp; - - if dasd_client.is_none() || zfcp_client.is_none() { + // Create agama_storage_client only if needed. + let (service, storage_dbus) = if self.dasd.is_none() || self.zfcp.is_none() { let storage_dbus = agama_storage_client::service::Starter::new(self.connection.clone()) .start() .await?; - if dasd_client.is_none() { - dasd_client = Some(Box::new(dasd::Client::new(storage_dbus.clone()))); - } - if zfcp_client.is_none() { - zfcp_client = Some(Box::new(zfcp::Client::new(storage_dbus))); - } - } - let service = Service { - dasd: dasd_client.unwrap(), - zfcp: zfcp_client.unwrap(), + let dasd = self + .dasd + .unwrap_or(Box::new(dasd::Client::new(storage_dbus.clone()))); + + let zfcp = self + .zfcp + .unwrap_or(Box::new(zfcp::Client::new(storage_dbus.clone()))); + + let service = Service { dasd, zfcp }; + + (service, Some(storage_dbus)) + } else { + let service = Service { + dasd: self.dasd.unwrap(), + zfcp: self.zfcp.unwrap(), + }; + (service, None) }; + let handler = actor::spawn(service); let dasd_monitor = dasd::Monitor::new( @@ -115,9 +124,19 @@ impl Starter { ); dasd::monitor::spawn(dasd_monitor)?; - let zfcp_monitor = - zfcp::Monitor::new(self.storage, self.progress, self.events, self.connection); - zfcp::monitor::spawn(zfcp_monitor)?; + // FIXME: allow mocking agama_storage_client instead of preventing its creation during + // tests. + if let Some(storage_dbus) = storage_dbus { + let zfcp_monitor = zfcp::Monitor::new( + self.storage, + self.progress, + self.issues, + self.events, + self.connection, + storage_dbus.clone(), + ); + zfcp::monitor::spawn(zfcp_monitor)?; + } Ok(handler) } @@ -133,9 +152,10 @@ impl Service { storage: Handler, events: event::Sender, progress: Handler, + issues: Handler, connection: zbus::Connection, ) -> Starter { - Starter::new(storage, events, progress, connection) + Starter::new(storage, events, progress, issues, connection) } } diff --git a/rust/agama-s390/src/test_utils.rs b/rust/agama-s390/src/test_utils.rs index 5bd3f095e4..f8a231e40f 100644 --- a/rust/agama-s390/src/test_utils.rs +++ b/rust/agama-s390/src/test_utils.rs @@ -29,7 +29,7 @@ use crate::{ use agama_utils::{ actor::Handler, api::{event, RawConfig}, - progress, + issue, progress, }; use async_trait::async_trait; use serde_json::Value; @@ -206,11 +206,12 @@ pub async fn start_service( storage: Handler, events: event::Sender, progress: Handler, + issues: Handler, connection: zbus::Connection, ) -> Handler { let dasd = TestDASDClient::new(); let zfcp = TestZFCPClient::new(); - Starter::new(storage, events, progress, connection) + Starter::new(storage, events, progress, issues, connection) .with_dasd(dasd) .with_zfcp(zfcp) .start() diff --git a/rust/agama-s390/src/zfcp/monitor.rs b/rust/agama-s390/src/zfcp/monitor.rs index 9b0217cf5a..cc26ae4218 100644 --- a/rust/agama-s390/src/zfcp/monitor.rs +++ b/rust/agama-s390/src/zfcp/monitor.rs @@ -26,7 +26,7 @@ use agama_utils::{ event::{self, Event}, Progress, Scope, }, - progress, + issue, progress, }; use serde::Deserialize; use serde_json; @@ -41,11 +41,15 @@ pub enum Error { #[error(transparent)] Event(#[from] broadcast::error::SendError), #[error(transparent)] + Issue(#[from] issue::service::Error), + #[error(transparent)] DBus(#[from] zbus::Error), #[error(transparent)] Json(#[from] serde_json::Error), #[error(transparent)] Storage(#[from] storage::service::Error), + #[error(transparent)] + DBusClient(#[from] agama_storage_client::Error), } #[derive(Debug, Deserialize)] @@ -71,22 +75,28 @@ impl From for Progress { pub struct Monitor { storage: Handler, progress: Handler, + issues: Handler, events: event::Sender, connection: Connection, + storage_dbus: Handler, } impl Monitor { pub fn new( storage: Handler, progress: Handler, + issues: Handler, events: event::Sender, connection: Connection, + storage_dbus: Handler, ) -> Self { Self { storage, progress, + issues, events, connection, + storage_dbus, } } @@ -99,6 +109,8 @@ impl Monitor { .build(); let mut stream = MessageStream::for_match_rule(rule, &self.connection, None).await?; + self.update_issues().await?; + while let Some(message) = stream.next().await { let message = message?; @@ -120,6 +132,16 @@ impl Monitor { Ok(()) } + async fn update_issues(&self) -> Result<(), Error> { + let issues = self + .storage_dbus + .call(agama_storage_client::message::zfcp::GetIssues) + .await?; + self.issues + .cast(issue::message::Set::new(Scope::ZFCP, issues))?; + Ok(()) + } + async fn handle_properties_changed(&self, signal: PropertiesChanged) -> Result<(), Error> { let args = signal.args()?; if args.changed_properties().get("System").is_some() { @@ -127,6 +149,9 @@ impl Monitor { .send(Event::SystemChanged { scope: Scope::ZFCP })?; self.storage.cast(storage::message::Probe)?; } + if args.changed_properties().get("Issues").is_some() { + self.update_issues().await?; + } Ok(()) } diff --git a/rust/agama-storage-client/src/message/zfcp.rs b/rust/agama-storage-client/src/message/zfcp.rs index 335832093a..40fd6b6563 100644 --- a/rust/agama-storage-client/src/message/zfcp.rs +++ b/rust/agama-storage-client/src/message/zfcp.rs @@ -18,7 +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 agama_utils::{actor::Message, api::RawConfig}; +use agama_utils::{ + actor::Message, + api::{Issue, RawConfig}, +}; pub struct Probe; @@ -38,6 +41,12 @@ impl Message for GetConfig { type Reply = Option; } +pub struct GetIssues; + +impl Message for GetIssues { + type Reply = Vec; +} + pub struct SetConfig { pub config: Option, } diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 65eb5b5e21..3c7354303a 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -387,6 +387,18 @@ impl MessageHandler for Service { } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::zfcp::GetIssues) -> Result, Error> { + if let Some(proxy) = &self.zfcp_proxy { + let raw_json = proxy.issues().await?; + Ok(try_from_string(&raw_json)?) + } else { + Ok(vec![]) + } + } +} + #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::zfcp::SetConfig) -> Result<(), Error> { From e482e0e48aa088eb52aa3c6de5039c27a0f29fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 4 Mar 2026 16:43:04 +0000 Subject: [PATCH 07/10] Apply some renamings --- rust/agama-bootloader/src/client.rs | 27 +++++++++++------------ rust/agama-bootloader/src/lib.rs | 4 +++- rust/agama-bootloader/src/service.rs | 24 ++++++++++----------- rust/agama-iscsi/src/client.rs | 22 +++++++++---------- rust/agama-iscsi/src/lib.rs | 1 + rust/agama-iscsi/src/monitor.rs | 6 ++++-- rust/agama-iscsi/src/service.rs | 15 ++++++------- rust/agama-s390/src/dasd/client.rs | 20 ++++++++--------- rust/agama-s390/src/dasd/monitor.rs | 6 ++++-- rust/agama-s390/src/lib.rs | 1 + rust/agama-s390/src/service.rs | 23 ++++++++++---------- rust/agama-s390/src/zfcp/client.rs | 18 ++++++++-------- rust/agama-s390/src/zfcp/monitor.rs | 21 +++++++++++------- rust/agama-storage/src/client.rs | 32 ++++++++++++++-------------- rust/agama-storage/src/lib.rs | 6 ++++-- rust/agama-storage/src/monitor.rs | 17 ++++++++------- rust/agama-storage/src/service.rs | 27 ++++++++++++----------- 17 files changed, 139 insertions(+), 131 deletions(-) diff --git a/rust/agama-bootloader/src/client.rs b/rust/agama-bootloader/src/client.rs index 0025d21930..1300ca0f54 100644 --- a/rust/agama-bootloader/src/client.rs +++ b/rust/agama-bootloader/src/client.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2024] SUSE LLC +// Copyright (c) [2025-2026] SUSE LLC // // All Rights Reserved. // @@ -20,24 +20,21 @@ //! Implements a client to access Agama's D-Bus API related to Bootloader management. -use std::collections::HashMap; - -use agama_storage_client::message; +use crate::storage_client::{self, message}; use agama_utils::{actor::Handler, api::bootloader::Config}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; /// Errors that can occur when using the Bootloader client. #[derive(thiserror::Error, Debug)] pub enum Error { - /// Error originating from the D-Bus communication. - #[error("D-Bus service error: {0}")] + #[error(transparent)] DBus(#[from] zbus::Error), - /// Error parsing or generating JSON data. - #[error("Passed json data is not correct: {0}")] + #[error(transparent)] InvalidJson(#[from] serde_json::Error), - #[error("Storage D-Bus server error: {0}")] - DBusClient(#[from] agama_storage_client::Error), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } /// Trait defining the interface for the Bootloader client. @@ -72,14 +69,14 @@ pub type ClientResult = Result; /// Client to connect to Agama's D-Bus API for Bootloader management. #[derive(Clone)] pub struct Client { - storage_dbus: Handler, + storage_client: Handler, kernel_args: HashMap, } impl Client { - pub async fn new(storage_dbus: Handler) -> ClientResult { + pub async fn new(storage_client: Handler) -> ClientResult { Ok(Self { - storage_dbus, + storage_client, kernel_args: HashMap::new(), }) } @@ -89,7 +86,7 @@ impl Client { impl BootloaderClient for Client { async fn get_config(&self) -> ClientResult { Ok(self - .storage_dbus + .storage_client .call(message::bootloader::GetConfig) .await?) } @@ -103,7 +100,7 @@ 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_client .call(message::bootloader::SetConfig::new(value)) .await?; Ok(()) diff --git a/rust/agama-bootloader/src/lib.rs b/rust/agama-bootloader/src/lib.rs index 64b793a854..38d944ce99 100644 --- a/rust/agama-bootloader/src/lib.rs +++ b/rust/agama-bootloader/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2025] SUSE LLC +// Copyright (c) [2025-2026] SUSE LLC // // All Rights Reserved. // @@ -41,3 +41,5 @@ pub use service::{Service, Starter}; pub mod client; pub mod message; pub mod test_utils; + +use agama_storage_client as storage_client; diff --git a/rust/agama-bootloader/src/service.rs b/rust/agama-bootloader/src/service.rs index b6c3f721a7..038daab9a7 100644 --- a/rust/agama-bootloader/src/service.rs +++ b/rust/agama-bootloader/src/service.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2025] SUSE LLC +// Copyright (c) [2025-2026] SUSE LLC // // All Rights Reserved. // @@ -18,25 +18,24 @@ // 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}, + message, storage_client, +}; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, api::bootloader::Config, }; use async_trait::async_trait; -use crate::{ - client::{self, Client}, - message, -}; - #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Actor(#[from] actor::Error), #[error(transparent)] Client(#[from] client::Error), - #[error("Storage D-Bus server error: {0}")] - DBusClient(#[from] agama_storage_client::Error), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } /// Builds and spawns the bootloader service. @@ -70,11 +69,10 @@ impl Starter { let client = match self.client { Some(client) => client, None => { - let storage_dbus = - agama_storage_client::service::Starter::new(self.connection.clone()) - .start() - .await?; - Box::new(Client::new(storage_dbus).await?) + let storage_client = storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + Box::new(Client::new(storage_client).await?) } }; let service = Service { client }; diff --git a/rust/agama-iscsi/src/client.rs b/rust/agama-iscsi/src/client.rs index 8f4c513b0e..b85b33a002 100644 --- a/rust/agama-iscsi/src/client.rs +++ b/rust/agama-iscsi/src/client.rs @@ -20,7 +20,7 @@ //! Implements a client to access Agama's D-Bus API related to iSCSI management. -use agama_storage_client::message; +use crate::storage_client::{self, message}; use agama_utils::actor::Handler; use agama_utils::api::iscsi::Config; use agama_utils::api::iscsi::DiscoverConfig; @@ -33,8 +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), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } pub enum DiscoverResult { @@ -52,14 +52,12 @@ pub trait ISCSIClient { #[derive(Clone)] pub struct Client { - storage_dbus: Handler, + storage_client: Handler, } impl Client { - pub async fn new( - storage_dbus: Handler, - ) -> Result { - Ok(Self { storage_dbus }) + pub async fn new(storage_client: Handler) -> Result { + Ok(Self { storage_client }) } } @@ -67,7 +65,7 @@ impl Client { impl ISCSIClient for Client { async fn discover(&self, config: DiscoverConfig) -> Result { let result = self - .storage_dbus + .storage_client .call(message::iscsi::Discover::new(config)) .await?; match result { @@ -77,15 +75,15 @@ impl ISCSIClient for Client { } async fn get_system(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::iscsi::GetSystem).await?) + Ok(self.storage_client.call(message::iscsi::GetSystem).await?) } async fn get_config(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::iscsi::GetConfig).await?) + Ok(self.storage_client.call(message::iscsi::GetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { - self.storage_dbus + self.storage_client .call(message::iscsi::SetConfig::new(config)) .await?; Ok(()) diff --git a/rust/agama-iscsi/src/lib.rs b/rust/agama-iscsi/src/lib.rs index 367d7b9a2c..b2927d4b43 100644 --- a/rust/agama-iscsi/src/lib.rs +++ b/rust/agama-iscsi/src/lib.rs @@ -28,6 +28,7 @@ pub mod test_utils; mod monitor; use agama_storage as storage; +use agama_storage_client as storage_client; #[cfg(test)] mod tests { diff --git a/rust/agama-iscsi/src/monitor.rs b/rust/agama-iscsi/src/monitor.rs index ab8d4ba339..9392f9773e 100644 --- a/rust/agama-iscsi/src/monitor.rs +++ b/rust/agama-iscsi/src/monitor.rs @@ -18,8 +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::storage; -use agama_storage_client::proxies::{ISCSIProxy, ProgressChanged, ProgressFinished, SystemChanged}; +use crate::{ + storage, + storage_client::proxies::{ISCSIProxy, ProgressChanged, ProgressFinished, SystemChanged}, +}; use agama_utils::{ actor::Handler, api::{ diff --git a/rust/agama-iscsi/src/service.rs b/rust/agama-iscsi/src/service.rs index 723aab9d57..8dfa165ad6 100644 --- a/rust/agama-iscsi/src/service.rs +++ b/rust/agama-iscsi/src/service.rs @@ -22,7 +22,7 @@ use crate::{ client::{self, Client, DiscoverResult}, message, monitor::{self, Monitor}, - storage, + storage, storage_client, }; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, @@ -40,8 +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), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } pub struct Starter { @@ -77,11 +77,10 @@ impl Starter { let client = match self.client { Some(client) => client, None => { - let storage_dbus = - agama_storage_client::service::Starter::new(self.connection.clone()) - .start() - .await?; - Box::new(Client::new(storage_dbus).await?) + let storage_client = storage_client::service::Starter::new(self.connection.clone()) + .start() + .await?; + Box::new(Client::new(storage_client).await?) } }; let service = Service { client }; diff --git a/rust/agama-s390/src/dasd/client.rs b/rust/agama-s390/src/dasd/client.rs index f7577a702b..4cb9c93f26 100644 --- a/rust/agama-s390/src/dasd/client.rs +++ b/rust/agama-s390/src/dasd/client.rs @@ -20,7 +20,7 @@ //! Implements a client to access Agama's D-Bus API related to DASD management. -use agama_storage_client::message; +use crate::storage_client::{self, message}; use agama_utils::actor::Handler; use agama_utils::api::RawConfig; use async_trait::async_trait; @@ -32,8 +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), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } #[async_trait] @@ -46,31 +46,31 @@ pub trait DASDClient { #[derive(Clone)] pub struct Client { - storage_dbus: Handler, + storage_client: Handler, } impl Client { - pub fn new(storage_dbus: Handler) -> Self { - Self { storage_dbus } + pub fn new(storage_client: Handler) -> Self { + Self { storage_client } } } #[async_trait] impl DASDClient for Client { async fn probe(&self) -> Result<(), Error> { - Ok(self.storage_dbus.call(message::dasd::Probe).await?) + Ok(self.storage_client.call(message::dasd::Probe).await?) } async fn get_system(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::dasd::GetSystem).await?) + Ok(self.storage_client.call(message::dasd::GetSystem).await?) } async fn get_config(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::dasd::GetConfig).await?) + Ok(self.storage_client.call(message::dasd::GetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { - self.storage_dbus + self.storage_client .call(message::dasd::SetConfig::new(config)) .await?; Ok(()) diff --git a/rust/agama-s390/src/dasd/monitor.rs b/rust/agama-s390/src/dasd/monitor.rs index 1c71e143b1..0472f8ac54 100644 --- a/rust/agama-s390/src/dasd/monitor.rs +++ b/rust/agama-s390/src/dasd/monitor.rs @@ -18,8 +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::storage; -use agama_storage_client::proxies::{dasd, DASDProxy}; +use crate::{ + storage, + storage_client::proxies::{dasd, DASDProxy}, +}; use agama_utils::{ actor::Handler, api::{ diff --git a/rust/agama-s390/src/lib.rs b/rust/agama-s390/src/lib.rs index c60c74c307..100b47f317 100644 --- a/rust/agama-s390/src/lib.rs +++ b/rust/agama-s390/src/lib.rs @@ -27,6 +27,7 @@ pub mod test_utils; pub mod zfcp; use agama_storage as storage; +use agama_storage_client as storage_client; #[cfg(test)] mod tests { diff --git a/rust/agama-s390/src/service.rs b/rust/agama-s390/src/service.rs index 415a663786..ccd8d7d2cf 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -20,7 +20,7 @@ use crate::{ dasd::{self, client::DASDClient}, - message, storage, + message, storage, storage_client, zfcp::{self, client::ZFCPClient}, }; use agama_utils::{ @@ -46,7 +46,7 @@ pub enum Error { #[error(transparent)] ZFCPMonitor(#[from] zfcp::monitor::Error), #[error(transparent)] - DBusClient(#[from] agama_storage_client::Error), + StorageClient(#[from] storage_client::Error), } pub struct Starter { @@ -89,23 +89,23 @@ impl Starter { } pub async fn start(self) -> Result, Error> { - // Create agama_storage_client only if needed. - let (service, storage_dbus) = if self.dasd.is_none() || self.zfcp.is_none() { - let storage_dbus = agama_storage_client::service::Starter::new(self.connection.clone()) + // Create storage_client only if needed. + let (service, storage_client) = if self.dasd.is_none() || self.zfcp.is_none() { + let storage_client = storage_client::service::Starter::new(self.connection.clone()) .start() .await?; let dasd = self .dasd - .unwrap_or(Box::new(dasd::Client::new(storage_dbus.clone()))); + .unwrap_or(Box::new(dasd::Client::new(storage_client.clone()))); let zfcp = self .zfcp - .unwrap_or(Box::new(zfcp::Client::new(storage_dbus.clone()))); + .unwrap_or(Box::new(zfcp::Client::new(storage_client.clone()))); let service = Service { dasd, zfcp }; - (service, Some(storage_dbus)) + (service, Some(storage_client)) } else { let service = Service { dasd: self.dasd.unwrap(), @@ -124,16 +124,15 @@ impl Starter { ); dasd::monitor::spawn(dasd_monitor)?; - // FIXME: allow mocking agama_storage_client instead of preventing its creation during - // tests. - if let Some(storage_dbus) = storage_dbus { + // FIXME: allow mocking storage_client instead of preventing its creation during tests. + if let Some(storage_client) = storage_client { let zfcp_monitor = zfcp::Monitor::new( self.storage, self.progress, self.issues, self.events, self.connection, - storage_dbus.clone(), + storage_client.clone(), ); zfcp::monitor::spawn(zfcp_monitor)?; } diff --git a/rust/agama-s390/src/zfcp/client.rs b/rust/agama-s390/src/zfcp/client.rs index 2ad21c9eed..6104f4a73a 100644 --- a/rust/agama-s390/src/zfcp/client.rs +++ b/rust/agama-s390/src/zfcp/client.rs @@ -20,7 +20,7 @@ //! Implements a client to access Agama's D-Bus API related to zFCP management. -use agama_storage_client::message; +use crate::storage_client::{self, message}; use agama_utils::actor::Handler; use agama_utils::api::RawConfig; use async_trait::async_trait; @@ -33,7 +33,7 @@ pub enum Error { #[error(transparent)] Json(#[from] serde_json::Error), #[error(transparent)] - DBusClient(#[from] agama_storage_client::Error), + StorageClient(#[from] storage_client::Error), } #[async_trait] @@ -46,31 +46,31 @@ pub trait ZFCPClient { #[derive(Clone)] pub struct Client { - storage_dbus: Handler, + storage_client: Handler, } impl Client { - pub fn new(storage_dbus: Handler) -> Self { - Self { storage_dbus } + pub fn new(storage_client: Handler) -> Self { + Self { storage_client } } } #[async_trait] impl ZFCPClient for Client { async fn probe(&self) -> Result<(), Error> { - Ok(self.storage_dbus.call(message::zfcp::Probe).await?) + Ok(self.storage_client.call(message::zfcp::Probe).await?) } async fn get_system(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::zfcp::GetSystem).await?) + Ok(self.storage_client.call(message::zfcp::GetSystem).await?) } async fn get_config(&self) -> Result, Error> { - Ok(self.storage_dbus.call(message::zfcp::GetConfig).await?) + Ok(self.storage_client.call(message::zfcp::GetConfig).await?) } async fn set_config(&self, config: Option) -> Result<(), Error> { - self.storage_dbus + self.storage_client .call(message::zfcp::SetConfig::new(config)) .await?; Ok(()) diff --git a/rust/agama-s390/src/zfcp/monitor.rs b/rust/agama-s390/src/zfcp/monitor.rs index cc26ae4218..31591dcb16 100644 --- a/rust/agama-s390/src/zfcp/monitor.rs +++ b/rust/agama-s390/src/zfcp/monitor.rs @@ -18,8 +18,13 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::storage; -use agama_storage_client::proxies::{zfcp, ZFCPProxy}; +use crate::{ + storage, + storage_client::{ + self, + proxies::{zfcp, ZFCPProxy}, + }, +}; use agama_utils::{ actor::Handler, api::{ @@ -49,7 +54,7 @@ pub enum Error { #[error(transparent)] Storage(#[from] storage::service::Error), #[error(transparent)] - DBusClient(#[from] agama_storage_client::Error), + StorageClient(#[from] storage_client::Error), } #[derive(Debug, Deserialize)] @@ -78,7 +83,7 @@ pub struct Monitor { issues: Handler, events: event::Sender, connection: Connection, - storage_dbus: Handler, + storage_client: Handler, } impl Monitor { @@ -88,7 +93,7 @@ impl Monitor { issues: Handler, events: event::Sender, connection: Connection, - storage_dbus: Handler, + storage_client: Handler, ) -> Self { Self { storage, @@ -96,7 +101,7 @@ impl Monitor { issues, events, connection, - storage_dbus, + storage_client, } } @@ -134,8 +139,8 @@ impl Monitor { async fn update_issues(&self) -> Result<(), Error> { let issues = self - .storage_dbus - .call(agama_storage_client::message::zfcp::GetIssues) + .storage_client + .call(storage_client::message::zfcp::GetIssues) .await?; self.issues .cast(issue::message::Set::new(Scope::ZFCP, issues))?; diff --git a/rust/agama-storage/src/client.rs b/rust/agama-storage/src/client.rs index 03c8e4301f..91de2d0f35 100644 --- a/rust/agama-storage/src/client.rs +++ b/rust/agama-storage/src/client.rs @@ -20,7 +20,7 @@ //! Implements a client to access Agama's storage service. -use agama_storage_client::message; +use crate::storage_client::{self, message}; use agama_utils::{ actor::{self, Handler}, api::{storage::Config, Issue}, @@ -44,8 +44,8 @@ pub enum Error { Json(#[from] serde_json::Error), #[error(transparent)] Actor(#[from] actor::Error), - #[error("Storage D-Bus server error: {0}")] - DBusClient(#[from] agama_storage_client::Error), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } #[async_trait] @@ -73,16 +73,16 @@ pub trait StorageClient { /// D-Bus client for the storage service #[derive(Clone)] pub struct Client { - storage_dbus: Handler, + storage_client: Handler, } impl Client { - pub fn new(storage_dbus: Handler) -> Self { - Self { storage_dbus } + pub fn new(storage_client: Handler) -> Self { + Self { storage_client } } async fn call_action(&self, action: &str) -> Result<(), Error> { - self.storage_dbus + self.storage_client .call(message::CallAction::new(action)) .await?; Ok(()) @@ -112,33 +112,33 @@ impl StorageClient for Client { } async fn get_system(&self) -> Result, Error> { - let value = self.storage_dbus.call(message::GetSystem).await?; + let value = self.storage_client.call(message::GetSystem).await?; Ok(value) } async fn get_config(&self) -> Result, Error> { - let value = self.storage_dbus.call(message::GetStorageConfig).await?; + let value = self.storage_client.call(message::GetStorageConfig).await?; Ok(value) } async fn get_config_from_model(&self, model: Value) -> Result, Error> { let message = message::GetConfigFromModel::new(model); - let value = self.storage_dbus.call(message).await?; + let value = self.storage_client.call(message).await?; Ok(value) } async fn get_config_model(&self) -> Result, Error> { - let value = self.storage_dbus.call(message::GetConfigModel).await?; + let value = self.storage_client.call(message::GetConfigModel).await?; Ok(value) } async fn get_proposal(&self) -> Result, Error> { - let value = self.storage_dbus.call(message::GetProposal).await?; + let value = self.storage_client.call(message::GetProposal).await?; Ok(value) } async fn get_issues(&self) -> Result, Error> { - let value = self.storage_dbus.call(message::GetIssues).await?; + let value = self.storage_client.call(message::GetIssues).await?; Ok(value) } @@ -148,18 +148,18 @@ impl StorageClient for Client { config: Option, ) -> Result>, Error> { let message = message::SetStorageConfig::new(product.clone(), config); - let rx = self.storage_dbus.call(message).await?; + let rx = self.storage_client.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 = message::SolveConfigModel::new(model); - let value = self.storage_dbus.call(message).await?; + let value = self.storage_client.call(message).await?; Ok(value) } async fn set_locale(&self, locale: String) -> Result<(), Error> { - self.storage_dbus + self.storage_client .call(message::SetLocale::new(locale)) .await?; Ok(()) diff --git a/rust/agama-storage/src/lib.rs b/rust/agama-storage/src/lib.rs index 7e9daad3b1..9ecc038aa7 100644 --- a/rust/agama-storage/src/lib.rs +++ b/rust/agama-storage/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2025] SUSE LLC +// Copyright (c) [2025-2026] SUSE LLC // // All Rights Reserved. // @@ -23,9 +23,11 @@ pub use service::Service; pub mod client; pub mod message; +pub mod test_utils; + mod monitor; -pub mod test_utils; +use agama_storage_client as storage_client; #[cfg(test)] mod tests { diff --git a/rust/agama-storage/src/monitor.rs b/rust/agama-storage/src/monitor.rs index 4ecf97b23d..cd45d0bee4 100644 --- a/rust/agama-storage/src/monitor.rs +++ b/rust/agama-storage/src/monitor.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2025] SUSE LLC +// Copyright (c) [2025-2026] SUSE LLC // // All Rights Reserved. // @@ -18,6 +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::storage_client; use agama_utils::{ actor::Handler, api::{ @@ -46,8 +47,8 @@ pub enum Error { DBus(#[from] zbus::Error), #[error(transparent)] Event(#[from] broadcast::error::SendError), - #[error("Storage D-Bus server error: {0}")] - DBusClient(#[from] agama_storage_client::Error), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } #[proxy( @@ -103,7 +104,7 @@ pub struct Monitor { issues: Handler, events: event::Sender, connection: Connection, - storage_dbus: Handler, + storage_client: Handler, } impl Monitor { @@ -112,14 +113,14 @@ impl Monitor { issues: Handler, events: event::Sender, connection: Connection, - storage_dbus: Handler, + storage_client: Handler, ) -> Self { Self { progress, issues, events, connection: connection.clone(), - storage_dbus, + storage_client, } } @@ -143,8 +144,8 @@ impl Monitor { async fn update_issues(&self) -> Result<(), Error> { let issues = self - .storage_dbus - .call(agama_storage_client::message::GetIssues) + .storage_client + .call(storage_client::message::GetIssues) .await?; self.issues .cast(issue::message::Set::new(Scope::Storage, issues))?; diff --git a/rust/agama-storage/src/service.rs b/rust/agama-storage/src/service.rs index de1b5584cc..864b1991dd 100644 --- a/rust/agama-storage/src/service.rs +++ b/rust/agama-storage/src/service.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2025] SUSE LLC +// Copyright (c) [2025-2026] SUSE LLC // // All Rights Reserved. // @@ -22,6 +22,7 @@ use crate::{ client::{self, Client, StorageClient}, message, monitor::{self, Monitor}, + storage_client, }; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, @@ -46,7 +47,7 @@ pub struct Starter { events: event::Sender, issues: Handler, progress: Handler, - dbus: zbus::Connection, + connection: zbus::Connection, client: Option>, } @@ -55,13 +56,13 @@ impl Starter { events: event::Sender, issues: Handler, progress: Handler, - dbus: zbus::Connection, + connection: zbus::Connection, ) -> Self { Self { events, issues, progress, - dbus, + connection, client: None, } } @@ -73,17 +74,17 @@ impl Starter { /// Starts the service and returns a handler to communicate with it. pub async fn start(self) -> Result, Error> { - let (client, storage_dbus) = match self.client { + let (client, storage_client) = match self.client { Some(client) => (client, None), None => { - let storage_dbus = agama_storage_client::service::Starter::new(self.dbus.clone()) + let storage_client = storage_client::service::Starter::new(self.connection.clone()) .start() .await .map_err(client::Error::from)?; ( - Box::new(Client::new(storage_dbus.clone())) + Box::new(Client::new(storage_client.clone())) as Box, - Some(storage_dbus), + Some(storage_client), ) } }; @@ -91,13 +92,13 @@ impl Starter { let service = Service { client }; let handler = actor::spawn(service); - if let Some(storage_dbus) = storage_dbus { + if let Some(storage_client) = storage_client { let monitor = Monitor::new( self.progress, self.issues, self.events, - self.dbus, - storage_dbus, + self.connection, + storage_client, ) .await; monitor::spawn(monitor)?; @@ -116,9 +117,9 @@ impl Service { events: event::Sender, issues: Handler, progress: Handler, - dbus: zbus::Connection, + connection: zbus::Connection, ) -> Starter { - Starter::new(events, issues, progress, dbus) + Starter::new(events, issues, progress, connection) } } From 56f70b0fe7858bb47a87c98beee63a9759346034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Thu, 5 Mar 2026 12:04:55 +0000 Subject: [PATCH 08/10] Changelog --- rust/package/agama.changes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 7c6ed0b0b9..a8b8c2b468 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Thu Mar 5 12:04:00 UTC 2026 - José Iván López González + +- Integrate zFCP in the new HTTP API (gh#agama-project/agama#3235). + ------------------------------------------------------------------- Thu Mar 5 07:19:01 UTC 2026 - Ladislav Slezák From cc87667ddb41a2bd2c7a78de1bc3ffe409e0da48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Thu, 5 Mar 2026 12:52:08 +0000 Subject: [PATCH 09/10] Use let-else instead of if --- rust/agama-storage-client/src/service.rs | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 3c7354303a..6e7a84b0d5 100644 --- a/rust/agama-storage-client/src/service.rs +++ b/rust/agama-storage-client/src/service.rs @@ -363,12 +363,12 @@ impl MessageHandler for Service { &mut self, _message: message::zfcp::GetSystem, ) -> Result, Error> { - if let Some(proxy) = &self.zfcp_proxy { - let raw_json = proxy.system().await?; - Ok(try_from_string(&raw_json)?) - } else { - Ok(None) - } + let Some(proxy) = &self.zfcp_proxy else { + return Ok(None); + }; + + let raw_json = proxy.system().await?; + Ok(try_from_string(&raw_json)?) } } @@ -378,24 +378,24 @@ impl MessageHandler for Service { &mut self, _message: message::zfcp::GetConfig, ) -> Result, Error> { - if let Some(proxy) = &self.zfcp_proxy { - let raw_json = proxy.config().await?; - Ok(try_from_string(&raw_json)?) - } else { - Ok(None) - } + let Some(proxy) = &self.zfcp_proxy else { + return Ok(None); + }; + + let raw_json = proxy.config().await?; + Ok(try_from_string(&raw_json)?) } } #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, _message: message::zfcp::GetIssues) -> Result, Error> { - if let Some(proxy) = &self.zfcp_proxy { - let raw_json = proxy.issues().await?; - Ok(try_from_string(&raw_json)?) - } else { - Ok(vec![]) - } + let Some(proxy) = &self.zfcp_proxy else { + return Ok(vec![]); + }; + + let raw_json = proxy.system().await?; + Ok(try_from_string(&raw_json)?) } } From 7aa0851e09b1ad7cf25e6e92f67991da88364a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Thu, 5 Mar 2026 12:54:26 +0000 Subject: [PATCH 10/10] Add comment --- rust/agama-s390/src/service.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-s390/src/service.rs b/rust/agama-s390/src/service.rs index ccd8d7d2cf..22c25e8c2f 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -107,6 +107,7 @@ impl Starter { (service, Some(storage_client)) } else { + // Note that unwrap is secure here because the if branch covers any case of None. let service = Service { dasd: self.dasd.unwrap(), zfcp: self.zfcp.unwrap(),