Skip to content

Commit

Permalink
implement volume for
Browse files Browse the repository at this point in the history
  • Loading branch information
jreidinger committed Apr 26, 2024
1 parent b4d9ae5 commit 5b23d77
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 19 deletions.
20 changes: 11 additions & 9 deletions rust/agama-lib/src/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,34 @@ pub type OwnedNestedHash = HashMap<String, HashMap<String, zvariant::OwnedValue>
pub fn get_property<'a, T>(
properties: &'a HashMap<String, OwnedValue>,
name: &str,
) -> Result<T, ServiceError>
) -> Result<T, zbus::zvariant::Error>
where
T: TryFrom<Value<'a>>,
<T as TryFrom<Value<'a>>>::Error: Into<zbus::zvariant::Error>,
{
let value: Value = properties
.get(name)
.context(format!("Failed to find property '{}'", name))?
.ok_or(zbus::zvariant::Error::Message(format!(
"Failed to find property '{}'",
name
)))?
.into();
T::try_from(value).map_err(|e| e.into().into())

T::try_from(value).map_err(|e| e.into())
}

pub fn get_optional_property<'a, T>(
properties: &'a HashMap<String, OwnedValue>,
name: &str,
) -> Result<Option<T>, ServiceError>
) -> Result<Option<T>, zbus::zvariant::Error>
where
T: TryFrom<Value<'a>>,
<T as TryFrom<Value<'a>>>::Error: Into<zbus::zvariant::Error>,
{
if let Some(value) = properties.get(name) {
let value : Value = value.into();
T::try_from(value)
.map(|v| Some(v))
.map_err(|e| e.into().into() )
let value: Value = value.into();
T::try_from(value).map(|v| Some(v)).map_err(|e| e.into())
} else {
Ok(None)
}
}
}
45 changes: 39 additions & 6 deletions rust/agama-lib/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,27 @@ pub enum VolumeTarget {
Filesystem,
}

impl TryFrom<zbus::zvariant::Value<'_>> for VolumeTarget {
type Error = zbus::zvariant::Error;

fn try_from(value: zbus::zvariant::Value) -> Result<Self, zbus::zvariant::Error> {
let svalue: String = value.try_into()?;
match svalue.as_str() {
"default" => Ok(VolumeTarget::Default),
"new_partition" => Ok(VolumeTarget::NewPartition),
"new_vg" => Ok(VolumeTarget::NewVg),
"device" => Ok(VolumeTarget::Device),
"filesystem" => Ok(VolumeTarget::Filesystem),
_ => Err(zbus::zvariant::Error::Message(
format!("Wrong value for Target: {}", svalue).to_string(),
)),
}
}
}

/// Represents volume outline aka requirements for volume
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "PascalCase")]
#[serde(rename_all = "camelCase")]
pub struct VolumeOutline {
required: bool,
fs_types: Vec<String>,
Expand All @@ -55,10 +72,27 @@ pub struct VolumeOutline {
size_relevant_volumes: Vec<String>,
}

impl TryFrom<zbus::zvariant::Value<'_>> for VolumeOutline {
type Error = zbus::zvariant::Error;

fn try_from(value: zbus::zvariant::Value) -> Result<Self, zbus::zvariant::Error> {
let mvalue: HashMap<String, OwnedValue> = value.try_into()?;
let res = VolumeOutline {
required: get_property(&mvalue, "Required")?,
fs_types: get_property(&mvalue, "FsTypes")?,
support_auto_size: get_property(&mvalue, "SupportAutoSize")?,
snapshots_configurable: get_property(&mvalue, "SnapshotsConfigurable")?,
snaphosts_affect_sizes: get_property(&mvalue, "SnapshotsAffectSizes")?,
size_relevant_volumes: get_property(&mvalue, "SizeRelevantVolumes")?,
};

Ok(res)
}
}

/// Represents single volume
// TODO: Do we really want to expose PascalCase from dbus or use more consistent snakeCase?
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "PascalCase")]
#[serde(rename_all = "camelCase")]
pub struct Volume {
mount_path: String,
mount_options: Vec<String>,
Expand Down Expand Up @@ -142,8 +176,7 @@ impl<'a> StorageClient<'a> {
}

pub async fn volume_for(&self, mount_path: &str) -> Result<Volume, ServiceError> {
let volume_hash = self.calculator_proxy
.default_volume(mount_path).await?;
let volume_hash = self.calculator_proxy.default_volume(mount_path).await?;
let volume = Volume {
mount_path: get_property(&volume_hash, "MountPath")?,
mount_options: get_property(&volume_hash, "MountOptions")?,
Expand All @@ -154,7 +187,7 @@ impl<'a> StorageClient<'a> {
auto_size: get_property(&volume_hash, "AutoSize")?,
snapshots: get_optional_property(&volume_hash, "Snapshots")?,
transactional: get_optional_property(&volume_hash, "Transactional")?,
outline: get_property(&volume_hash, "Outline")?,
outline: get_optional_property(&volume_hash, "Outline")?,
};

Ok(volume)
Expand Down
35 changes: 31 additions & 4 deletions rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,22 @@
//! * `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::{client::Action, device::Device, StorageClient}};
use axum::{extract::State, routing::get, Json, Router};
use std::collections::HashMap;

use agama_lib::{
error::ServiceError,
storage::{
client::{Action, Volume},
device::Device,
StorageClient,
},
};
use anyhow::anyhow;
use axum::{
extract::{Query, State},
routing::get,
Json, Router,
};

use crate::{
error::Error,
Expand Down Expand Up @@ -41,6 +55,7 @@ pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
.route("/devices/dirty", get(devices_dirty))
.route("/devices/system", get(system_devices))
.route("/devices/result", get(staging_devices))
.route("/product/volume_for", get(volume_for))
.route("/proposal/actions", get(actions))
.merge(status_router)
.merge(progress_router)
Expand All @@ -57,10 +72,22 @@ async fn system_devices(State(state): State<StorageState<'_>>) -> Result<Json<Ve
Ok(Json(state.client.system_devices().await?))
}

async fn staging_devices(State(state): State<StorageState<'_>>) -> Result<Json<Vec<Device>>, Error> {
async fn staging_devices(
State(state): State<StorageState<'_>>,
) -> Result<Json<Vec<Device>>, Error> {
Ok(Json(state.client.staging_devices().await?))
}

async fn actions(State(state): State<StorageState<'_>>) -> Result<Json<Vec<Action>>, Error> {
Ok(Json(state.client.actions().await?))
}
}

async fn volume_for(
State(state): State<StorageState<'_>>,
Query(params): Query<HashMap<String, String>>,
) -> Result<Json<Volume>, Error> {
let mount_path = params
.get("mount_path")
.ok_or(anyhow!("Missing mount_path parameter"))?;
Ok(Json(state.client.volume_for(mount_path).await?))
}

0 comments on commit 5b23d77

Please sign in to comment.