-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(web): bring back zFCP support (new HTTP / JSON API, queries,…
… and TypeScript) (#1570) Trello: https://trello.com/c/KMtaVltF This PR could be seen as a follow up of https://trello.com/c/2LC8JEZR but in this case bringing back the support for managing zFCP devices on s390x architectures. It includes: - Extending the HTTP API to expose zFCP devices management and zFCP related events. - Adapting the WEB UI for using Tanstack queries and the new HTTP API. - Adopting Typescript - Move to route pages. --------- Co-authored-by: Knut Anderssen <[email protected]> Co-authored-by: Imobach González Sosa <[email protected]> Co-authored-by: David Díaz González <[email protected]> Co-authored-by: Knut Alejandro Anderssen González <[email protected]>
- Loading branch information
1 parent
eb86857
commit 0b66de2
Showing
45 changed files
with
2,436 additions
and
1,293 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
//! Implements a client to access Agama's D-Bus API related to zFCP management. | ||
|
||
use std::collections::HashMap; | ||
|
||
use futures_util::future::join_all; | ||
use zbus::{fdo::ObjectManagerProxy, zvariant::OwnedObjectPath, Connection}; | ||
|
||
use crate::{ | ||
dbus::{extract_id_from_path, get_property}, | ||
error::ServiceError, | ||
storage::{ | ||
model::zfcp::{ZFCPController, ZFCPDisk}, | ||
proxies::{ZFCPControllerProxy, ZFCPManagerProxy}, | ||
}, | ||
}; | ||
|
||
const ZFCP_CONTROLLER_PREFIX: &'static str = "/org/opensuse/Agama/Storage1/zfcp_controllers"; | ||
|
||
/// Client to connect to Agama's D-Bus API for zFCP management. | ||
#[derive(Clone)] | ||
pub struct ZFCPClient<'a> { | ||
manager_proxy: ZFCPManagerProxy<'a>, | ||
object_manager_proxy: ObjectManagerProxy<'a>, | ||
connection: Connection, | ||
} | ||
|
||
impl<'a> ZFCPClient<'a> { | ||
pub async fn new(connection: Connection) -> Result<Self, ServiceError> { | ||
let manager_proxy = ZFCPManagerProxy::new(&connection).await?; | ||
let object_manager_proxy = ObjectManagerProxy::builder(&connection) | ||
.destination("org.opensuse.Agama.Storage1")? | ||
.path("/org/opensuse/Agama/Storage1")? | ||
.build() | ||
.await?; | ||
Ok(Self { | ||
manager_proxy, | ||
object_manager_proxy, | ||
connection, | ||
}) | ||
} | ||
|
||
pub async fn supported(&self) -> Result<bool, ServiceError> { | ||
let introspect = self.manager_proxy.introspect().await?; | ||
// simply check if introspection contain given interface | ||
Ok(introspect.contains("org.opensuse.Agama.Storage1.ZFCP.Manager")) | ||
} | ||
|
||
pub async fn is_lun_scan_allowed(&self) -> Result<bool, ServiceError> { | ||
let allowed = self.manager_proxy.allow_lunscan().await?; | ||
// simply check if introspection contain given interface | ||
Ok(allowed) | ||
} | ||
|
||
pub async fn probe(&self) -> Result<(), ServiceError> { | ||
Ok(self.manager_proxy.probe().await?) | ||
} | ||
|
||
pub async fn get_disks(&self) -> Result<Vec<(OwnedObjectPath, ZFCPDisk)>, ServiceError> { | ||
let managed_objects = self.object_manager_proxy.get_managed_objects().await?; | ||
|
||
let mut devices: Vec<(OwnedObjectPath, ZFCPDisk)> = vec![]; | ||
for (path, ifaces) in managed_objects { | ||
if let Some(properties) = ifaces.get("org.opensuse.Agama.Storage1.ZFCP.Disk") { | ||
match ZFCPDisk::try_from(properties) { | ||
Ok(device) => { | ||
devices.push((path, device)); | ||
} | ||
Err(error) => { | ||
log::warn!("Not a valid zFCP disk: {}", error); | ||
} | ||
} | ||
} | ||
} | ||
Ok(devices) | ||
} | ||
|
||
pub async fn get_controllers( | ||
&self, | ||
) -> Result<Vec<(OwnedObjectPath, ZFCPController)>, ServiceError> { | ||
let managed_objects = self.object_manager_proxy.get_managed_objects().await?; | ||
|
||
let mut devices: Vec<(OwnedObjectPath, ZFCPController)> = vec![]; | ||
for (path, ifaces) in managed_objects { | ||
if let Some(properties) = ifaces.get("org.opensuse.Agama.Storage1.ZFCP.Controller") { | ||
let id = extract_id_from_path(&path)?.to_string(); | ||
devices.push(( | ||
path, | ||
ZFCPController { | ||
id: id.clone(), | ||
channel: get_property(properties, "Channel")?, | ||
lun_scan: get_property(properties, "LUNScan")?, | ||
active: get_property(properties, "Active")?, | ||
luns_map: self.get_luns_map(id.as_str()).await?, | ||
}, | ||
)) | ||
} | ||
} | ||
Ok(devices) | ||
} | ||
|
||
async fn get_controller_proxy( | ||
&self, | ||
controller_id: &str, | ||
) -> Result<ZFCPControllerProxy, ServiceError> { | ||
let dbus = ZFCPControllerProxy::builder(&self.connection) | ||
.path(ZFCP_CONTROLLER_PREFIX.to_string() + "/" + controller_id)? | ||
.build() | ||
.await?; | ||
Ok(dbus) | ||
} | ||
|
||
pub async fn activate_controller(&self, controller_id: &str) -> Result<(), ServiceError> { | ||
let controller = self.get_controller_proxy(controller_id).await?; | ||
controller.activate().await?; | ||
Ok(()) | ||
} | ||
|
||
pub async fn get_wwpns(&self, controller_id: &str) -> Result<Vec<String>, ServiceError> { | ||
let controller = self.get_controller_proxy(controller_id).await?; | ||
let result = controller.get_wwpns().await?; | ||
Ok(result) | ||
} | ||
|
||
pub async fn get_luns( | ||
&self, | ||
controller_id: &str, | ||
wwpn: &str, | ||
) -> Result<Vec<String>, ServiceError> { | ||
let controller = self.get_controller_proxy(controller_id).await?; | ||
let result = controller.get_luns(wwpn).await?; | ||
Ok(result) | ||
} | ||
|
||
/// Obtains a LUNs map for the given controller | ||
/// | ||
/// Given a controller id it returns a HashMap with each of its WWPNs as keys and the list of | ||
/// LUNS corresponding to that specific WWPN as values. | ||
/// | ||
/// Arguments: | ||
/// | ||
/// `controller_id`: controller id | ||
pub async fn get_luns_map( | ||
&self, | ||
controller_id: &str, | ||
) -> Result<HashMap<String, Vec<String>>, ServiceError> { | ||
let wwpns = self.get_wwpns(controller_id).await?; | ||
let aresult = wwpns.into_iter().map(|wwpn| async move { | ||
Ok(( | ||
wwpn.clone(), | ||
self.get_luns(controller_id, wwpn.as_str()).await?, | ||
)) | ||
}); | ||
let sresult = join_all(aresult).await; | ||
sresult | ||
.into_iter() | ||
.collect::<Result<HashMap<String, Vec<String>>, _>>() | ||
} | ||
|
||
pub async fn activate_disk( | ||
&self, | ||
controller_id: &str, | ||
wwpn: &str, | ||
lun: &str, | ||
) -> Result<(), ServiceError> { | ||
let controller = self.get_controller_proxy(controller_id).await?; | ||
let result = controller.activate_disk(wwpn, lun).await?; | ||
if result == 0 { | ||
Ok(()) | ||
} else { | ||
let text = format!("Failed to activate disk. chzdev exit code {}", result); | ||
Err(ServiceError::UnsuccessfulAction(text)) | ||
} | ||
} | ||
|
||
pub async fn deactivate_disk( | ||
&self, | ||
controller_id: &str, | ||
wwpn: &str, | ||
lun: &str, | ||
) -> Result<(), ServiceError> { | ||
let controller = self.get_controller_proxy(controller_id).await?; | ||
let result = controller.deactivate_disk(wwpn, lun).await?; | ||
if result == 0 { | ||
Ok(()) | ||
} else { | ||
let text = format!("Failed to deactivate disk. chzdev exit code {}", result); | ||
Err(ServiceError::UnsuccessfulAction(text)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
//! Implements a data model for zFCP devices management. | ||
use std::collections::HashMap; | ||
|
||
use serde::Serialize; | ||
use zbus::zvariant::OwnedValue; | ||
|
||
use crate::{dbus::get_property, error::ServiceError}; | ||
|
||
/// Represents a zFCP disk (specific to s390x systems). | ||
#[derive(Clone, Debug, Serialize, Default, utoipa::ToSchema)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ZFCPDisk { | ||
/// Name of the zFCP device (e.g., /dev/sda) | ||
pub name: String, | ||
/// zFCP controller channel id (e.g., 0.0.fa00) | ||
pub channel: String, | ||
/// WWPN of the targer port (e.g., 0x500507630300c562) | ||
pub wwpn: String, | ||
/// LUN of the SCSI device (e.g. 0x4010403300000000) | ||
pub lun: String, | ||
} | ||
|
||
impl TryFrom<&HashMap<String, OwnedValue>> for ZFCPDisk { | ||
type Error = ServiceError; | ||
|
||
fn try_from(value: &HashMap<String, OwnedValue>) -> Result<Self, Self::Error> { | ||
Ok(ZFCPDisk { | ||
name: get_property(value, "Name")?, | ||
channel: get_property(value, "Channel")?, | ||
wwpn: get_property(value, "WWPN")?, | ||
lun: get_property(value, "LUN")?, | ||
}) | ||
} | ||
} | ||
|
||
/// Represents a zFCP controller (specific to s390x systems). | ||
#[derive(Clone, Debug, Serialize, Default, utoipa::ToSchema)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ZFCPController { | ||
/// unique internal ID for given controller | ||
pub id: String, | ||
/// zFCP controller channel id (e.g., 0.0.fa00) | ||
pub channel: String, | ||
/// flag whenever channel is performing LUN auto scan | ||
pub lun_scan: bool, | ||
/// flag whenever channel is active | ||
pub active: bool, | ||
/// map of associated WWPNs and its LUNs | ||
pub luns_map: HashMap<String, Vec<String>>, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.