-
Notifications
You must be signed in to change notification settings - Fork 74
feat(rust): add an HTTP/JSON API to DASD operations #1532
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
Changes from all commits
acd0d5b
ca965f1
92c8770
e7a6cea
40280bc
04ff47d
62c9fa8
b06002a
ecee4f1
3a6da9c
eca2140
9784427
e40441e
6ae69e6
a226fec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| //! This module implements support for the so-called Jobs. It is a concept hat represents running | ||
| //! an external command that may take some time, like formatting a DASD device. It is exposed via | ||
| //! D-Bus and, at this time, only the storage service makes use of it. | ||
|
|
||
| use std::collections::HashMap; | ||
|
|
||
| use serde::Serialize; | ||
| use zbus::zvariant::OwnedValue; | ||
|
|
||
| use crate::{dbus::get_property, error::ServiceError}; | ||
|
|
||
| pub mod client; | ||
|
|
||
| /// Represents a job. | ||
| #[derive(Clone, Debug, Default, Serialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct Job { | ||
| /// Artificial job identifier. | ||
| pub id: String, | ||
| /// Whether the job is running. | ||
| pub running: bool, | ||
| /// Job exit code. | ||
| pub exit_code: u32, | ||
| } | ||
|
|
||
| impl TryFrom<&HashMap<String, OwnedValue>> for Job { | ||
| type Error = ServiceError; | ||
|
|
||
| fn try_from(value: &HashMap<String, OwnedValue>) -> Result<Self, Self::Error> { | ||
| Ok(Job { | ||
| running: get_property(value, "Running")?, | ||
| exit_code: get_property(value, "ExitCode")?, | ||
| ..Default::default() | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| //! Implements a client to access Agama's Jobs API. | ||
|
|
||
| use zbus::{fdo::ObjectManagerProxy, zvariant::OwnedObjectPath, Connection}; | ||
|
|
||
| use crate::error::ServiceError; | ||
|
|
||
| use super::Job; | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct JobsClient<'a> { | ||
| object_manager_proxy: ObjectManagerProxy<'a>, | ||
| } | ||
|
|
||
| impl<'a> JobsClient<'a> { | ||
| pub async fn new( | ||
| connection: Connection, | ||
| destination: &'static str, | ||
| path: &'static str, | ||
| ) -> Result<Self, ServiceError> { | ||
| let object_manager_proxy = ObjectManagerProxy::builder(&connection) | ||
| .destination(destination)? | ||
| .path(path)? | ||
| .build() | ||
| .await?; | ||
|
|
||
| Ok(Self { | ||
| object_manager_proxy, | ||
| }) | ||
| } | ||
|
|
||
| pub async fn jobs(&self) -> Result<Vec<(OwnedObjectPath, Job)>, ServiceError> { | ||
| let managed_objects = self.object_manager_proxy.get_managed_objects().await?; | ||
|
|
||
| let mut jobs = vec![]; | ||
| for (path, ifaces) in managed_objects { | ||
| let Some(properties) = ifaces.get("org.opensuse.Agama.Storage1.Job") else { | ||
| continue; | ||
| }; | ||
|
|
||
| match Job::try_from(properties) { | ||
| Ok(mut job) => { | ||
| job.id = path.to_string(); | ||
| jobs.push((path, job)); | ||
| } | ||
| Err(error) => { | ||
| log::warn!("Not a valid job: {}", error); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(jobs) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| //! Implements a client to access Agama's D-Bus API related to DASD management. | ||
|
|
||
| use zbus::{ | ||
| fdo::ObjectManagerProxy, | ||
| zvariant::{ObjectPath, OwnedObjectPath}, | ||
| Connection, | ||
| }; | ||
|
|
||
| use crate::{ | ||
| error::ServiceError, | ||
| storage::{model::dasd::DASDDevice, proxies::DASDManagerProxy}, | ||
| }; | ||
|
|
||
| /// Client to connect to Agama's D-Bus API for DASD management. | ||
| #[derive(Clone)] | ||
| pub struct DASDClient<'a> { | ||
| manager_proxy: DASDManagerProxy<'a>, | ||
| object_manager_proxy: ObjectManagerProxy<'a>, | ||
| } | ||
|
|
||
| impl<'a> DASDClient<'a> { | ||
| pub async fn new(connection: Connection) -> Result<DASDClient<'a>, ServiceError> { | ||
| let manager_proxy = DASDManagerProxy::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, | ||
| }) | ||
| } | ||
|
|
||
| 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.DASD.Manager")) | ||
| } | ||
|
|
||
| pub async fn probe(&self) -> Result<(), ServiceError> { | ||
| Ok(self.manager_proxy.probe().await?) | ||
| } | ||
|
|
||
| pub async fn devices(&self) -> Result<Vec<(OwnedObjectPath, DASDDevice)>, ServiceError> { | ||
| let managed_objects = self.object_manager_proxy.get_managed_objects().await?; | ||
|
|
||
| let mut devices: Vec<(OwnedObjectPath, DASDDevice)> = vec![]; | ||
| for (path, ifaces) in managed_objects { | ||
| if let Some(properties) = ifaces.get("org.opensuse.Agama.Storage1.DASD.Device") { | ||
| match DASDDevice::try_from(properties) { | ||
| Ok(device) => { | ||
| devices.push((path, device)); | ||
| } | ||
| Err(error) => { | ||
| log::warn!("Not a valid DASD device: {}", error); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Ok(devices) | ||
| } | ||
|
|
||
| pub async fn format(&self, ids: &[&str]) -> Result<String, ServiceError> { | ||
| let selected = self.find_devices(ids).await?; | ||
| let references = selected.iter().collect::<Vec<&ObjectPath<'_>>>(); | ||
| let (exit_code, job_path) = self.manager_proxy.format(&references).await?; | ||
| if exit_code != 0 { | ||
| return Err(ServiceError::UnsuccessfulAction("DASD format".to_string())); | ||
| } | ||
|
|
||
| Ok(job_path.to_string()) | ||
| } | ||
|
|
||
| pub async fn enable(&self, ids: &[&str]) -> Result<(), ServiceError> { | ||
| let selected = self.find_devices(ids).await?; | ||
| let references = selected.iter().collect::<Vec<&ObjectPath<'_>>>(); | ||
| self.manager_proxy.enable(&references).await?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub async fn disable(&self, ids: &[&str]) -> Result<(), ServiceError> { | ||
| let selected = self.find_devices(ids).await?; | ||
| let references = selected.iter().collect::<Vec<&ObjectPath<'_>>>(); | ||
| self.manager_proxy.disable(&references).await?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub async fn set_diag(&self, ids: &[&str], diag: bool) -> Result<(), ServiceError> { | ||
| let selected = self.find_devices(ids).await?; | ||
| let references = selected.iter().collect::<Vec<&ObjectPath<'_>>>(); | ||
| self.manager_proxy.set_diag(&references, diag).await?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| async fn find_devices(&self, ids: &[&str]) -> Result<Vec<ObjectPath<'_>>, ServiceError> { | ||
| let devices = self.devices().await?; | ||
| let selected: Vec<ObjectPath> = devices | ||
| .into_iter() | ||
| .filter_map(|(path, device)| { | ||
| if ids.contains(&device.id.as_str()) { | ||
| Some(path.into_inner()) | ||
| } else { | ||
| None | ||
| } | ||
| }) | ||
| .collect(); | ||
| Ok(selected) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| //! Implements a data model for DASD devices management. | ||
| use std::collections::HashMap; | ||
|
|
||
| use serde::Serialize; | ||
| use zbus::zvariant::OwnedValue; | ||
|
|
||
| use crate::{dbus::get_property, error::ServiceError}; | ||
|
|
||
| /// Represents a DASD device (specific to s390x systems). | ||
| #[derive(Clone, Debug, Serialize, Default, utoipa::ToSchema)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct DASDDevice { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the fields in this struct (
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw resulting json is not consistent with others. I think agreemend is snakeCase...so let me change it to |
||
| pub id: String, | ||
| pub enabled: bool, | ||
| pub device_name: String, | ||
| pub formatted: bool, | ||
| pub diag: bool, | ||
| pub status: String, | ||
| pub device_type: String, | ||
| pub access_type: String, | ||
| pub partition_info: String, | ||
| } | ||
| #[derive(Clone, Debug, Serialize, Default, utoipa::ToSchema)] | ||
| pub struct DASDFormatSummary { | ||
| pub total: u32, | ||
| pub step: u32, | ||
| pub done: bool, | ||
| } | ||
|
|
||
| impl TryFrom<&HashMap<String, OwnedValue>> for DASDDevice { | ||
| type Error = ServiceError; | ||
|
|
||
| fn try_from(value: &HashMap<String, OwnedValue>) -> Result<Self, Self::Error> { | ||
| Ok(DASDDevice { | ||
| id: get_property(value, "Id")?, | ||
| enabled: get_property(value, "Enabled")?, | ||
| device_name: get_property(value, "DeviceName")?, | ||
| formatted: get_property(value, "Formatted")?, | ||
| diag: get_property(value, "Diag")?, | ||
| status: get_property(value, "Status")?, | ||
| device_type: get_property(value, "Type")?, | ||
| access_type: get_property(value, "AccessType")?, | ||
| partition_info: get_property(value, "PartitionInfo")?, | ||
| }) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API looks strange, it allows formating multiple DASDs but it return only one path for job.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I deduced from https://github.com/openSUSE/agama/blob/master/service/lib/agama/dbus/storage/interfaces/dasd_manager.rb#L139.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is only one job, that is ok, but then we need to check the status for each device, I think the missing part is the job Summary call on formatProgress callback.
https://github.com/openSUSE/agama/blob/master/web/src/client/storage.js#L891
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I say nothing, it already uses the Summary but the DASD path is not sent at all...
see ... https://github.com/openSUSE/agama/pull/1532/files#diff-481b9b86ced79110f138995e6097a0f0cde62016924c72f1c5c6397940566654R210
and ... https://github.com/openSUSE/agama/pull/1532/files#diff-481b9b86ced79110f138995e6097a0f0cde62016924c72f1c5c6397940566654R220