Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(web): bring back zFCP support (new HTTP / JSON API, queries, and TypeScript) #1570

Merged
merged 121 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
9e8cc77
start with new web API for DASD
jreidinger Aug 21, 2024
daf6124
[WIP] Try to add a supported check to DASD API
teclator Aug 22, 2024
e05a7cc
try to have working dasd#supported
jreidinger Aug 22, 2024
5e246d1
Replaced DASD is supported check
teclator Aug 22, 2024
c772472
[WIP] Start adapting DASDPage
teclator Aug 22, 2024
7e4b34d
Temporal fix for showing dasds
teclator Aug 22, 2024
d866bd8
adapt ids
jreidinger Aug 22, 2024
8788502
Merge branch 'http-dasd' into web-dasd
teclator Aug 22, 2024
08edd00
Start moving DASD pages to Typescript
teclator Aug 22, 2024
e796206
add dasd probe
jreidinger Aug 22, 2024
2b40f73
add more api and queries for dasd
jreidinger Aug 22, 2024
455fa66
add format api call
jreidinger Aug 23, 2024
fa7ac90
fix sort types
jreidinger Aug 23, 2024
b2d7f6c
add backspace icon
jreidinger Aug 23, 2024
6251f72
Move DASD actions to queries
teclator Aug 23, 2024
6e8e581
add jobs api
jreidinger Aug 23, 2024
d4be0c8
Merge remote-tracking branch 'origin/http-dasd' into web-dasd
jreidinger Aug 23, 2024
fc3497f
Moved DASD filters to queries
teclator Aug 23, 2024
41251bf
Move DASD selection to queries
teclator Aug 23, 2024
b7ab21c
Merge remote-tracking branch 'origin/http-dasd' into web-dasd
jreidinger Aug 23, 2024
897bbf4
first attempt to implement proper format with storage jobs
jreidinger Aug 23, 2024
fc8f79c
optimize dasd devices query to not use invalidate
jreidinger Aug 27, 2024
f7530fd
Merge branch 'http-dasd' into web-dasd
teclator Aug 27, 2024
87e4e13
Fix jobs url
teclator Aug 27, 2024
fa3eae7
Getting rid of the DASD initial state
teclator Aug 27, 2024
e2da06a
Use the DASD id for notifing format job changes
teclator Aug 27, 2024
8991d01
Small fixes for the DASD Format progress dialog
teclator Aug 27, 2024
d67205d
Fix jobs summary test
teclator Aug 27, 2024
d7033e5
Merge remote-tracking branch 'origin/master' into web-dasd
jreidinger Aug 28, 2024
94f92fa
add non-working test for component with mutating queries
jreidinger Aug 28, 2024
23405eb
add test for dasd format progress together with fix it reveals
jreidinger Aug 28, 2024
5dba41c
transform DASDFormatProgress component to typescript
jreidinger Aug 29, 2024
40baceb
Update web/src/api/dasd.ts
jreidinger Aug 29, 2024
4e8e129
Removing dasd client
teclator Aug 29, 2024
eeba75f
Removing leftovers
teclator Aug 29, 2024
ad92d01
use connsistent name in API
jreidinger Aug 29, 2024
6b97d1d
Remove not needed code from DASD Page
teclator Aug 29, 2024
c13bfbf
Merge branch 'master' into web-dasd
teclator Aug 29, 2024
debcfb7
Merge remote-tracking branch 'origin/web-dasd' into dasd_failing_test
jreidinger Aug 29, 2024
1a3d485
fix dasd table test
jreidinger Aug 29, 2024
a6cb882
Improve DASDFormat progress dialog
teclator Aug 29, 2024
df7ca94
Notify id in Jobs events
teclator Aug 29, 2024
7ddbba5
Handle JobAdded event
teclator Aug 29, 2024
f585806
Remove running jobs when finish
teclator Aug 29, 2024
b028a3b
Fix DASDFormatProgress unit test
teclator Sep 2, 2024
9fd4544
Removed leftover Deviceinfo
teclator Sep 2, 2024
dbdf722
Changes based on CR
teclator Sep 2, 2024
8bbf57b
Changes based on code review
teclator Sep 3, 2024
d0650ac
Implement http backend for zfcp
jreidinger Aug 29, 2024
c4f6472
format rust
jreidinger Sep 3, 2024
918a5e3
Use mutations instead of API direct calls
teclator Sep 3, 2024
a7a027d
fix autoapi
jreidinger Sep 4, 2024
4405715
More changes based on code review
teclator Sep 4, 2024
6d140be
Replace dasd filter queries by component state
teclator Sep 4, 2024
8fc01b9
Replace dasd selection queries by component state
teclator Sep 4, 2024
50e76ad
Do not set any info about formatting.
teclator Sep 4, 2024
a75354f
Added DASD route only if supported
teclator Sep 4, 2024
669f102
Removed selected DASD leftover queries
teclator Sep 4, 2024
f296c55
add method to get disks and add id to zfcp controller to be able to c…
jreidinger Sep 4, 2024
8ebc3a7
add zFCP types and api for web
jreidinger Sep 4, 2024
76d295b
Use only one mutation for modifying DASD state
teclator Sep 4, 2024
a705ac6
Do not modify existent cache but return new object
teclator Sep 4, 2024
d0bf495
Another inmutability fix
teclator Sep 4, 2024
211df16
add initial zfcp queries
jreidinger Sep 4, 2024
3c9c319
Merge branch 'master' into web-dasd
teclator Sep 4, 2024
1c9dfdd
Added changelog
teclator Sep 4, 2024
9b96a93
Apply suggestions from CR
teclator Sep 5, 2024
5188009
Merge remote-tracking branch 'origin/web-dasd' into zfcp_adapt
jreidinger Sep 5, 2024
77d744e
add route to zfcp page
jreidinger Sep 5, 2024
a9cc9d5
show properly zfcp link
jreidinger Sep 5, 2024
8b908b0
Bring back ZFCP to the devices tech menu and adapted tests
teclator Sep 5, 2024
f87cd51
Merge branch 'master' into zfcp_adapt
teclator Sep 5, 2024
e2141f6
initial WIP for zfcp web UI
jreidinger Sep 5, 2024
b62e58c
split ZFCP controllers table
jreidinger Sep 5, 2024
a28c9bc
add config call to zfcp
jreidinger Sep 6, 2024
4392c08
change backend to provide more info about controllers to avoid doing …
jreidinger Sep 6, 2024
2e63383
fix typo
jreidinger Sep 9, 2024
88ac2be
Fix indentation
teclator Sep 9, 2024
e888d0b
Fix ZFCP controller path
teclator Sep 9, 2024
2a2fa15
Fixed indentation and run prettier
teclator Sep 9, 2024
82303ee
Moving to typescript
teclator Sep 9, 2024
b9adfca
move getting id from path to own method with test
jreidinger Sep 9, 2024
bc543b3
do not use client and manager in disk popup in zfcp page
jreidinger Sep 9, 2024
6c41ec7
change naming for acronyms in ZFCP and switch to hooks
jreidinger Sep 10, 2024
f2d0f15
fix cases to display properly zfcp
jreidinger Sep 10, 2024
fac2422
fix type for luns map
jreidinger Sep 10, 2024
31abfce
Added close action and removed leftovers
teclator Sep 10, 2024
6badb0b
add initial test for zfcp disk table
jreidinger Sep 10, 2024
6c4123f
add initial test for controllers
jreidinger Sep 10, 2024
1f8cfa5
Return json answer in activation calls
teclator Sep 11, 2024
c6ffc79
Fixed activation result
teclator Sep 11, 2024
d9fec02
Navigate to target-device when close
teclator Sep 11, 2024
e5ddfb8
fix zfcp page test
jreidinger Sep 11, 2024
2435305
Close disk activation popup when success
teclator Sep 11, 2024
08a1b89
Moved ZFCP Disk activation form to a page
teclator Sep 12, 2024
cf25379
Fix ZFCPDiskForm unit tests
teclator Sep 12, 2024
0e865bd
Modify ZFCP activation page header and run prettier
teclator Sep 13, 2024
b373be2
Not return queries when subscribing to changes
teclator Sep 13, 2024
1649ef2
Merge branch 'master' into zfcp_adapt
dgdavid Sep 13, 2024
c0be7e0
fix(web): adapt zFCP pages
dgdavid Sep 13, 2024
1f95321
refactor(web): migrate core/ServerError to TypeScript
dgdavid Sep 13, 2024
9ddc9ed
fix(web): improve the ZFCPage internals
dgdavid Sep 13, 2024
8b3b28e
fix(web): better naming
dgdavid Sep 13, 2024
352444a
Change Back button and rename zFCP and DASD functions
teclator Sep 13, 2024
bfacdb0
Choose a better action for navigating to selection device
teclator Sep 17, 2024
9efa66e
Change zfcp queries as suggested
teclator Sep 17, 2024
f0bd5f9
zFCP queries and api changes based on CR
teclator Sep 17, 2024
89efc50
Move zfcp components under its own folder
teclator Sep 17, 2024
cbb7f9a
Move dasd components under its own folder
teclator Sep 17, 2024
1ae7849
Merge branch 'master' into zfcp_adapt
teclator Sep 17, 2024
268d0b9
prettier fixes
teclator Sep 17, 2024
5caebea
Do not export not needed components
teclator Sep 17, 2024
faa5e8b
Added changelog
teclator Sep 17, 2024
65a0903
Ensure changes are applied even if activating.
teclator Sep 17, 2024
cd11f27
Update rust/agama-lib/src/storage/client/zfcp.rs
teclator Sep 18, 2024
92b51ab
Use already defined method for obtaining the id from path
teclator Sep 18, 2024
d5720ec
Move queries and api for dasd and zfcp under storage dir
teclator Sep 18, 2024
7af24f8
Try to simplify zfcp and dasd changes queries
teclator Sep 18, 2024
8268a2f
Fix test
teclator Sep 18, 2024
8b1af6b
Move ZFCPOptions to the web server
teclator Sep 18, 2024
19a242c
Merge branch 'master' into zfcp_adapt
teclator Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/agama-lib/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod store;

pub use client::{
iscsi::{ISCSIAuth, ISCSIClient, ISCSIInitiator, ISCSINode},
zfcp::ZFCPClient,
StorageClient,
};
pub use settings::StorageSettings;
Expand Down
1 change: 1 addition & 0 deletions rust/agama-lib/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use zbus::zvariant::{OwnedObjectPath, OwnedValue};
use zbus::Connection;
pub mod dasd;
pub mod iscsi;
pub mod zfcp;

type DBusObject = (
OwnedObjectPath,
Expand Down
190 changes: 190 additions & 0 deletions rust/agama-lib/src/storage/client/zfcp.rs
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(
teclator marked this conversation as resolved.
Show resolved Hide resolved
&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))
}
}
}
1 change: 1 addition & 0 deletions rust/agama-lib/src/storage/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use zbus::zvariant::{OwnedValue, Value};
use crate::dbus::{get_optional_property, get_property};

pub mod dasd;
pub mod zfcp;

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DeviceSid(u32);
Expand Down
50 changes: 50 additions & 0 deletions rust/agama-lib/src/storage/model/zfcp.rs
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>>,
}
73 changes: 73 additions & 0 deletions rust/agama-lib/src/storage/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,76 @@ trait DASDDevice {
#[dbus_proxy(property)]
fn type_(&self) -> zbus::Result<String>;
}

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.ZFCP.Manager",
default_service = "org.opensuse.Agama.Storage1",
default_path = "/org/opensuse/Agama/Storage1"
)]
trait ZFCPManager {
/// Probe method
fn probe(&self) -> zbus::Result<()>;

/// AllowLUNScan property
#[dbus_proxy(property, name = "AllowLUNScan")]
fn allow_lunscan(&self) -> zbus::Result<bool>;
}

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.ZFCP.Controller",
default_service = "org.opensuse.Agama.Storage1",
default_path = "/org/opensuse/Agama/Storage1"
)]
trait ZFCPController {
/// Activate method
fn activate(&self) -> zbus::Result<u32>;

/// ActivateDisk method
fn activate_disk(&self, wwpn: &str, lun: &str) -> zbus::Result<u32>;

/// DeactivateDisk method
fn deactivate_disk(&self, wwpn: &str, lun: &str) -> zbus::Result<u32>;

/// GetLUNs method
#[dbus_proxy(name = "GetLUNs")]
fn get_luns(&self, wwpn: &str) -> zbus::Result<Vec<String>>;

/// GetWWPNs method
#[dbus_proxy(name = "GetWWPNs")]
fn get_wwpns(&self) -> zbus::Result<Vec<String>>;

/// Active property
#[dbus_proxy(property)]
fn active(&self) -> zbus::Result<bool>;

/// Channel property
#[dbus_proxy(property)]
fn channel(&self) -> zbus::Result<String>;

/// LUNScan property
#[dbus_proxy(property, name = "LUNScan")]
fn lunscan(&self) -> zbus::Result<bool>;
}

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.ZFCP.Disk",
default_service = "org.opensuse.Agama.Storage1",
default_path = "/org/opensuse/Agama/Storage1"
)]
trait Disk {
/// Channel property
#[dbus_proxy(property)]
fn channel(&self) -> zbus::Result<String>;

/// LUN property
#[dbus_proxy(property, name = "LUN")]
fn lun(&self) -> zbus::Result<String>;

/// Name property
#[dbus_proxy(property)]
fn name(&self) -> zbus::Result<String>;

/// WWPN property
#[dbus_proxy(property, name = "WWPN")]
fn wwpn(&self) -> zbus::Result<String>;
}
6 changes: 6 additions & 0 deletions rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ use axum::{
};
use serde::{Deserialize, Serialize};
use tokio_stream::{Stream, StreamExt};
use zfcp::{zfcp_service, zfcp_stream};

pub mod dasd;
pub mod iscsi;
pub mod zfcp;

use crate::{
error::Error,
Expand All @@ -45,9 +47,11 @@ pub async fn storage_streams(dbus: zbus::Connection) -> Result<EventStreams, Err
)];
let mut iscsi = iscsi_stream(&dbus).await?;
let mut dasd = dasd_stream(&dbus).await?;
let mut zfcp = zfcp_stream(&dbus).await?;

result.append(&mut iscsi);
result.append(&mut dasd);
result.append(&mut zfcp);
Ok(result)
}

Expand Down Expand Up @@ -82,6 +86,7 @@ pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
let issues_router = issues_router(&dbus, DBUS_SERVICE, DBUS_PATH).await?;
let iscsi_router = iscsi_service(&dbus).await?;
let dasd_router = dasd_service(&dbus).await?;
let zfcp_router = zfcp_service(&dbus).await?;
let jobs_router = jobs_service(&dbus, DBUS_DESTINATION, DBUS_PATH).await?;

let client = StorageClient::new(dbus.clone()).await?;
Expand All @@ -106,6 +111,7 @@ pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
.nest("/issues", issues_router)
.nest("/iscsi", iscsi_router)
.nest("/dasd", dasd_router)
.nest("/zfcp", zfcp_router)
.with_state(state);
Ok(router)
}
Expand Down
Loading
Loading