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-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/dasd/client.rs b/rust/agama-s390/src/dasd/client.rs index 58ccc859ec..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,33 +46,31 @@ pub trait DASDClient { #[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 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 6c713f0a1e..0472f8ac54 100644 --- a/rust/agama-s390/src/dasd/monitor.rs +++ b/rust/agama-s390/src/dasd/monitor.rs @@ -18,10 +18,9 @@ // 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::{ - DASDProgressChanged as ProgressChanged, DASDProgressFinished as ProgressFinished, DASDProxy, - DASDSystemChanged as SystemChanged, FormatChanged, FormatFinished, +use crate::{ + storage, + storage_client::proxies::{dasd, DASDProxy}, }; use agama_utils::{ actor::Handler, @@ -104,24 +103,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 +132,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 +148,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-s390/src/lib.rs b/rust/agama-s390/src/lib.rs index 2ac1105c6b..100b47f317 100644 --- a/rust/agama-s390/src/lib.rs +++ b/rust/agama-s390/src/lib.rs @@ -24,13 +24,15 @@ pub use service::{Service, Starter}; pub mod dasd; pub mod message; pub mod test_utils; +pub mod zfcp; use agama_storage as storage; +use agama_storage_client as storage_client; #[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}, @@ -42,6 +44,7 @@ mod tests { struct Context { handler: Handler, dasd: TestDASDClient, + zfcp: TestZFCPClient, } impl AsyncTestContext for Context { @@ -58,19 +61,25 @@ mod tests { ) .await; let dasd = TestDASDClient::new(); - let handler = Service::starter(storage, events, progress, connection) + let zfcp = TestZFCPClient::new(); + let handler = Service::starter(storage, events, progress, issues, 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; @@ -79,11 +88,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(()) } @@ -98,6 +119,17 @@ mod tests { "devices": [ { "channel": "0.0.0100", + "active": true, + "format": true + } + ] + }, + "zfcp": { + "devices": [ + { + "channel": "0.0.1a00", + "wwpn": "0x5005076300c20b8e", + "lun": "0x0001000000000000", "active": true } ] @@ -111,6 +143,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/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..22c25e8c2f 100644 --- a/rust/agama-s390/src/service.rs +++ b/rust/agama-s390/src/service.rs @@ -20,7 +20,8 @@ use crate::{ dasd::{self, client::DASDClient}, - message, storage, + message, storage, storage_client, + zfcp::{self, client::ZFCPClient}, }; use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, @@ -28,7 +29,7 @@ use agama_utils::{ event, s390::{Config, SystemInfo}, }, - progress, + issue, progress, }; use async_trait::async_trait; @@ -39,17 +40,23 @@ 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}")] - DBusClient(#[from] agama_storage_client::Error), + #[error(transparent)] + ZFCPMonitor(#[from] zfcp::monitor::Error), + #[error(transparent)] + StorageClient(#[from] storage_client::Error), } pub struct Starter { storage: Handler, events: event::Sender, progress: Handler, + issues: Handler, connection: zbus::Connection, dasd: Option>, + zfcp: Option>, } impl Starter { @@ -57,14 +64,17 @@ 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, } } @@ -73,30 +83,68 @@ 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 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?) - } + // 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_client.clone()))); + + let zfcp = self + .zfcp + .unwrap_or(Box::new(zfcp::Client::new(storage_client.clone()))); + + let service = Service { dasd, zfcp }; + + (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(), + }; + (service, None) }; - 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)?; + // 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_client.clone(), + ); + zfcp::monitor::spawn(zfcp_monitor)?; + } + Ok(handler) } } pub struct Service { dasd: Box, + zfcp: Box, } impl Service { @@ -104,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) } } @@ -122,11 +171,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 +192,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/test_utils.rs b/rust/agama-s390/src/test_utils.rs index 8830ce80f8..f8a231e40f 100644 --- a/rust/agama-s390/src/test_utils.rs +++ b/rust/agama-s390/src/test_utils.rs @@ -24,11 +24,12 @@ use crate::{ dasd::client::{DASDClient, Error}, service::{Service, Starter}, storage, + zfcp::{self, client::ZFCPClient}, }; use agama_utils::{ actor::Handler, api::{event, RawConfig}, - progress, + issue, progress, }; use async_trait::async_trait; use serde_json::Value; @@ -127,16 +128,92 @@ 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, progress: Handler, + issues: Handler, connection: zbus::Connection, ) -> Handler { let dasd = TestDASDClient::new(); - Starter::new(storage, events, progress, connection) + let zfcp = TestZFCPClient::new(); + Starter::new(storage, events, progress, issues, connection) .with_dasd(dasd) + .with_zfcp(zfcp) .start() .await .expect("Could not start a testing s390 service") 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..6104f4a73a --- /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 crate::storage_client::{self, 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)] + StorageClient(#[from] 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_client: Handler, +} + +impl Client { + 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_client.call(message::zfcp::Probe).await?) + } + + async fn get_system(&self) -> Result, Error> { + Ok(self.storage_client.call(message::zfcp::GetSystem).await?) + } + + async fn get_config(&self) -> Result, Error> { + Ok(self.storage_client.call(message::zfcp::GetConfig).await?) + } + + async fn set_config(&self, config: Option) -> Result<(), Error> { + 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 new file mode 100644 index 0000000000..31591dcb16 --- /dev/null +++ b/rust/agama-s390/src/zfcp/monitor.rs @@ -0,0 +1,190 @@ +// 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, + storage_client::{ + self, + proxies::{zfcp, ZFCPProxy}, + }, +}; +use agama_utils::{ + actor::Handler, + api::{ + event::{self, Event}, + Progress, Scope, + }, + issue, progress, +}; +use serde::Deserialize; +use serde_json; +use tokio::sync::broadcast; +use tokio_stream::StreamExt; +use zbus::{fdo::PropertiesChanged, 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)] + 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)] + StorageClient(#[from] storage_client::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, + issues: Handler, + events: event::Sender, + connection: Connection, + storage_client: Handler, +} + +impl Monitor { + pub fn new( + storage: Handler, + progress: Handler, + issues: Handler, + events: event::Sender, + connection: Connection, + storage_client: Handler, + ) -> Self { + Self { + storage, + progress, + issues, + events, + connection, + storage_client, + } + } + + 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())? + .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?; + + 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) = zfcp::ProgressFinished::from_message(message.clone()) { + self.handle_progress_finished(signal).await?; + continue; + } + tracing::warn!("Unmanaged zFCP signal: {message:?}"); + } + + Ok(()) + } + + async fn update_issues(&self) -> Result<(), Error> { + let issues = self + .storage_client + .call(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() { + self.events + .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(()) + } + + 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 + .call(progress::message::SetProgress::new(progress_data.into())) + .await?; + Ok(()) + } + + async fn handle_progress_finished(&self, _signal: zfcp::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..40fd6b6563 --- /dev/null +++ b/rust/agama-storage-client/src/message/zfcp.rs @@ -0,0 +1,62 @@ +// 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::{Issue, 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 GetIssues; + +impl Message for GetIssues { + type Reply = Vec; +} + +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 2680c389d5..cf34c85137 100644 --- a/rust/agama-storage-client/src/proxies.rs +++ b/rust/agama-storage-client/src/proxies.rs @@ -27,8 +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; 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/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<()>; +} diff --git a/rust/agama-storage-client/src/service.rs b/rust/agama-storage-client/src/service.rs index 6f7dfe6655..6e7a84b0d5 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,69 @@ 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> { + let Some(proxy) = &self.zfcp_proxy else { + return Ok(None); + }; + + let raw_json = proxy.system().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[async_trait] +impl MessageHandler for Service { + async fn handle( + &mut self, + _message: message::zfcp::GetConfig, + ) -> Result, Error> { + 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> { + let Some(proxy) = &self.zfcp_proxy else { + return Ok(vec![]); + }; + + let raw_json = proxy.system().await?; + Ok(try_from_string(&raw_json)?) + } +} + +#[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-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) } } 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, } 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