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

Storage adapt #1169

Merged
merged 18 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
3 changes: 2 additions & 1 deletion rust/agama-lib/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Implements support for handling the storage settings

mod client;
pub mod client;
mod device;
mod proxies;
mod settings;
mod store;
Expand Down
86 changes: 77 additions & 9 deletions rust/agama-lib/src/storage/client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
//! Implements a client to access Agama's storage service.

use super::proxies::{BlockDeviceProxy, ProposalCalculatorProxy, ProposalProxy, Storage1Proxy};
use super::device::{Device, DeviceInfo};
use super::proxies::{
BlockProxy, DeviceProxy, ProposalCalculatorProxy, ProposalProxy, Storage1Proxy,
};
use super::StorageSettings;
use crate::error::ServiceError;
use anyhow::Context;
use futures_util::future::join_all;
use serde::Serialize;
use std::collections::HashMap;
use zbus::zvariant::OwnedObjectPath;
use zbus::fdo::ObjectManagerProxy;
use zbus::names::OwnedInterfaceName;
use zbus::zvariant::{OwnedObjectPath, OwnedValue};
use zbus::Connection;

/// Represents a storage device
Expand All @@ -17,17 +23,24 @@ pub struct StorageDevice {
}

/// D-Bus client for the storage service
#[derive(Clone)]
pub struct StorageClient<'a> {
pub connection: Connection,
calculator_proxy: ProposalCalculatorProxy<'a>,
storage_proxy: Storage1Proxy<'a>,
object_manager_proxy: ObjectManagerProxy<'a>,
}

impl<'a> StorageClient<'a> {
pub async fn new(connection: Connection) -> Result<StorageClient<'a>, ServiceError> {
Ok(Self {
calculator_proxy: ProposalCalculatorProxy::new(&connection).await?,
storage_proxy: Storage1Proxy::new(&connection).await?,
object_manager_proxy: ObjectManagerProxy::builder(&connection)
.destination("org.opensuse.Agama.Storage1")?
.path("/org/opensuse/Agama/Storage1")?
.build()
.await?,
connection,
})
}
Expand All @@ -40,6 +53,10 @@ impl<'a> StorageClient<'a> {
Ok(ProposalProxy::new(&self.connection).await?)
}

pub async fn devices_dirty_bit(&self) -> Result<bool, ServiceError> {
Ok(self.storage_proxy.deprecated_system().await?)
}

/// Returns the available devices
///
/// These devices can be used for installing the system.
Expand All @@ -60,17 +77,15 @@ impl<'a> StorageClient<'a> {
&self,
dbus_path: OwnedObjectPath,
) -> Result<StorageDevice, ServiceError> {
let proxy = BlockDeviceProxy::builder(&self.connection)
let proxy = DeviceProxy::builder(&self.connection)
.path(dbus_path)?
.build()
.await?;

let name = proxy.name().await?;
// TODO: The description is not used yet. Decide what info to show, for example the device
// size, see https://crates.io/crates/size.
let description = name.clone();

Ok(StorageDevice { name, description })
Ok(StorageDevice {
name: proxy.name().await?,
description: proxy.description().await?,
})
}

/// Returns the boot device proposal setting
Expand Down Expand Up @@ -140,4 +155,57 @@ impl<'a> StorageClient<'a> {

Ok(self.calculator_proxy.calculate(dbus_settings).await?)
}

async fn build_device(
&self,
object: &(
OwnedObjectPath,
HashMap<OwnedInterfaceName, HashMap<std::string::String, OwnedValue>>,
),
) -> Result<Device, ServiceError> {
Ok(Device {
device_info: self.build_device_info(&object.0).await?,
component: None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could implement Default or, if not possible, have some kind of constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking that in the end I will add to Device struct method try_from that will construct itself from that Object Manager struct. and here will be just Ok(object.into())

drive: None,
block_device: None,
filesystem: None,
lvm_lv: None,
lvm_vg: None,
md: None,
multipath: None,
partition: None,
partition_table: None,
raid: None,
})
}

pub async fn system_devices(&self) -> Result<Vec<Device>, ServiceError> {
let objects = self
.object_manager_proxy
.get_managed_objects()
.await
.context("Failed to get managed objects")?;
jreidinger marked this conversation as resolved.
Show resolved Hide resolved
let mut result = vec![];
for object in objects {
if !object.0.as_str().contains("Storage1/system") {
continue;
}

result.push(self.build_device(&object).await?)
}

Ok(result)
}

async fn build_device_info(&self, path: &OwnedObjectPath) -> Result<DeviceInfo, ServiceError> {
let proxy = DeviceProxy::builder(&self.connection)
.path(path)?
.build()
.await?;
Ok(DeviceInfo {
sid: proxy.sid().await?,
name: proxy.name().await?,
description: proxy.description().await?,
})
}
}
58 changes: 58 additions & 0 deletions rust/agama-lib/src/storage/device.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider putting these structs (and others in the client module) into a unique module (storage::model or something like that).

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use serde::{Deserialize, Serialize};

/// Information about system device created by composition to reflect different devices on system
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Device {
pub device_info: DeviceInfo,
pub block_device: Option<BlockDevice>,
pub component: Option<Component>,
pub drive: Option<Drive>,
pub filesystem: Option<Filesystem>,
pub lvm_lv: Option<LvmLv>,
pub lvm_vg: Option<LvmVg>,
pub md: Option<MD>,
pub multipath: Option<Multipath>,
pub partition: Option<Partition>,
pub partition_table: Option<PartitionTable>,
pub raid: Option<Raid>,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DeviceInfo {
pub sid: u32,
pub name: String,
pub description: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BlockDevice {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Component {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Drive {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Filesystem {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct LvmLv {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct LvmVg {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MD {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Multipath {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Partition {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct PartitionTable {}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Raid {}
105 changes: 101 additions & 4 deletions rust/agama-lib/src/storage/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,30 @@ trait Proposal {

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.Block",
default_service = "org.opensuse.Agama.Storage1"
default_service = "org.opensuse.Agama.Storage1",
default_path = "/org/opensuse/Agama/Storage1"
)]
trait BlockDevice {
trait Block {
/// Active property
#[dbus_proxy(property)]
fn active(&self) -> zbus::Result<bool>;

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

/// RecoverableSize property
#[dbus_proxy(property)]
fn recoverable_size(&self) -> zbus::Result<u64>;

/// Size property
#[dbus_proxy(property)]
fn size(&self) -> zbus::Result<u64>;

/// Start property
#[dbus_proxy(property)]
fn start(&self) -> zbus::Result<u64>;

/// Systems property
#[dbus_proxy(property)]
fn systems(&self) -> zbus::Result<Vec<String>>;
Expand All @@ -130,3 +139,91 @@ trait BlockDevice {
#[dbus_proxy(property)]
fn udev_paths(&self) -> zbus::Result<Vec<String>>;
}

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

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

/// Driver property
#[dbus_proxy(property)]
fn driver(&self) -> zbus::Result<Vec<String>>;

/// Info property
#[dbus_proxy(property)]
fn info(&self) -> zbus::Result<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>;

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

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

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

/// Vendor property
#[dbus_proxy(property)]
fn vendor(&self) -> zbus::Result<String>;
}

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.Multipath",
default_service = "org.opensuse.Agama.Storage1",
default_path = "/org/opensuse/Agama/Storage1"
)]
trait Multipath {
/// Wires property
#[dbus_proxy(property)]
fn wires(&self) -> zbus::Result<Vec<zbus::zvariant::OwnedObjectPath>>;
}

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.PartitionTable",
default_service = "org.opensuse.Agama.Storage1",
default_path = "/org/opensuse/Agama/Storage1"
)]
trait PartitionTable {
/// Partitions property
#[dbus_proxy(property)]
fn partitions(&self) -> zbus::Result<Vec<zbus::zvariant::OwnedObjectPath>>;

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

/// UnusedSlots property
#[dbus_proxy(property)]
fn unused_slots(&self) -> zbus::Result<Vec<(u64, u64)>>;
}

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

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

/// SID property
#[dbus_proxy(property, name = "SID")]
fn sid(&self) -> zbus::Result<u32>;
}
1 change: 1 addition & 0 deletions rust/agama-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod manager;
pub mod network;
pub mod questions;
pub mod software;
pub mod storage;
pub mod users;
pub mod web;
pub use web::service;
2 changes: 2 additions & 0 deletions rust/agama-server/src/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod web;
pub use web::{storage_service, storage_streams};
51 changes: 51 additions & 0 deletions rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! This module implements the web API for the storage service.
//!
//! The module offers two public functions:
//!
//! * `storage_service` which returns the Axum service.
//! * `storage_stream` which offers an stream that emits the storage events coming from D-Bus.

use agama_lib::{error::ServiceError, storage::StorageClient};
use axum::{extract::State, routing::get, Json, Router};

use crate::{
error::Error,
web::{
common::{issues_router, progress_router, service_status_router, EventStreams},
Event,
},
};

pub async fn storage_streams(dbus: zbus::Connection) -> Result<EventStreams, Error> {
let result: EventStreams = vec![]; // TODO:
Ok(result)
}

#[derive(Clone)]
struct StorageState<'a> {
client: StorageClient<'a>,
}

/// Sets up and returns the axum service for the software module.
pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceError> {
const DBUS_SERVICE: &str = "org.opensuse.Agama.Storage1";
const DBUS_PATH: &str = "/org/opensuse/Agama/Storage1";

let status_router = service_status_router(&dbus, DBUS_SERVICE, DBUS_PATH).await?;
let progress_router = progress_router(&dbus, DBUS_SERVICE, DBUS_PATH).await?;
let issues_router = issues_router(&dbus, DBUS_SERVICE, DBUS_PATH).await?;

let client = StorageClient::new(dbus.clone()).await?;
let state = StorageState { client };
let router = Router::new()
.route("/devices/dirty", get(devices_dirty))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether it might be useful to have a more generic endpoint which could have more "status-like" attributes (apart from dirty). But if there is no need...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, more generic will be probably that proposal settings. Currently for devices I am aware only about this dirty flag that signal that re probe is needed. ( and staging can be no longer valid )

.merge(status_router)
.merge(progress_router)
.nest("/issues", issues_router)
.with_state(state);
Ok(router)
}

async fn devices_dirty(State(state): State<StorageState<'_>>) -> Result<Json<bool>, Error> {
Ok(Json(state.client.devices_dirty_bit().await?))
}
Loading
Loading