Skip to content

Commit

Permalink
Merge branch 'master' into restart-web-server
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs authored May 16, 2024
2 parents cf2d00b + 438b811 commit 1c661f7
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 242 deletions.
316 changes: 149 additions & 167 deletions rust/agama-lib/src/storage/client.rs

Large diffs are not rendered by default.

10 changes: 1 addition & 9 deletions rust/agama-lib/src/storage/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ use zbus::zvariant::{OwnedValue, Value};

use crate::dbus::{get_optional_property, get_property};

/// Represents a storage device
/// Just for backward compatibility with CLI.
/// See struct Device
#[derive(Serialize, Debug)]
pub struct StorageDevice {
pub name: String,
pub description: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DeviceSid(u32);

Expand Down Expand Up @@ -485,6 +476,7 @@ impl TryFrom<HashMap<String, OwnedValue>> for Volume {
}

/// Information about system device created by composition to reflect different devices on system
// FIXME Device schema is not generated because it collides with the network Device.
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Device {
Expand Down
19 changes: 0 additions & 19 deletions rust/agama-lib/src/storage/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,6 @@ trait Proposal {
) -> zbus::Result<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>;
}

#[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>;
}

#[dbus_proxy(
interface = "org.opensuse.Agama.Storage1.ISCSI.Initiator",
default_service = "org.opensuse.Agama.Storage1",
Expand Down
17 changes: 9 additions & 8 deletions rust/agama-server/src/software/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ pub struct FailureDetails {
context_path = "/api/software",
responses(
(status = 204, description = "registration successfull"),
(status = 422, description = "Registration failed. Details are in body", body=FailureDetails),
(status = 422, description = "Registration failed. Details are in body", body = FailureDetails),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
Expand Down Expand Up @@ -327,7 +327,7 @@ async fn register(
context_path = "/api/software",
responses(
(status = 200, description = "deregistration successfull"),
(status = 422, description = "De-registration failed. Details are in body", body=FailureDetails),
(status = 422, description = "De-registration failed. Details are in body", body = FailureDetails),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
Expand All @@ -349,7 +349,7 @@ async fn deregister(State(state): State<SoftwareState<'_>>) -> Result<impl IntoR
/// * `state`: service state.
#[utoipa::path(
get,
path = "patterns",
path = "/patterns",
context_path = "/api/software",
responses(
(status = 200, description = "List of known software patterns", body = Vec<Pattern>),
Expand All @@ -365,8 +365,9 @@ async fn patterns(State(state): State<SoftwareState<'_>>) -> Result<Json<Vec<Pat
///
/// * `state`: service state.
/// * `config`: software configuration.
#[utoipa::path(put,
path = "/software/config",
#[utoipa::path(
put,
path = "/config",
context_path = "/api/software",
operation_id = "set_software_config",
responses(
Expand Down Expand Up @@ -394,7 +395,7 @@ async fn set_config(
/// * `state` : service state.
#[utoipa::path(
get,
path = "/software/config",
path = "/config",
context_path = "/api/software",
operation_id = "get_software_config",
responses(
Expand Down Expand Up @@ -439,7 +440,7 @@ pub struct SoftwareProposal {
/// At this point, only the required space is reported.
#[utoipa::path(
get,
path = "/software/proposal",
path = "/proposal",
context_path = "/api/software",
responses(
(status = 200, description = "Software proposal", body = SoftwareProposal)
Expand All @@ -457,7 +458,7 @@ async fn proposal(State(state): State<SoftwareState<'_>>) -> Result<Json<Softwar
/// At this point, only the required space is reported.
#[utoipa::path(
post,
path = "/software/probe",
path = "/probe",
context_path = "/api/software",
responses(
(status = 200, description = "Read repositories data"),
Expand Down
174 changes: 143 additions & 31 deletions rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,20 @@
//! * `storage_service` which returns the Axum service.
//! * `storage_stream` which offers an stream that emits the storage events coming from D-Bus.
use std::collections::HashMap;

use agama_lib::{
error::ServiceError,
storage::{
model::{Action, Device, ProposalSettings, ProposalSettingsPatch, Volume},
model::{Action, Device, DeviceSid, ProposalSettings, ProposalSettingsPatch, Volume},
proxies::Storage1Proxy,
StorageClient,
},
};
use anyhow::anyhow;
use axum::{
extract::{Query, State},
routing::{get, post},
Json, Router,
};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use tokio_stream::{Stream, StreamExt};

pub mod iscsi;
Expand Down Expand Up @@ -66,15 +63,6 @@ struct StorageState<'a> {
client: StorageClient<'a>,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ProductParams {
/// List of mount points defined in product
mount_points: Vec<String>,
/// list of encryption methods defined in product
encryption_methods: Vec<String>,
}

/// 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";
Expand Down Expand Up @@ -108,38 +96,107 @@ pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
Ok(router)
}

/// Probes the storage devices.
#[utoipa::path(
post,
path = "/probe",
context_path = "/api/storage",
responses(
(status = 200, description = "Devices were probed and an initial proposal were performed"),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn probe(State(state): State<StorageState<'_>>) -> Result<Json<()>, Error> {
Ok(Json(state.client.probe().await?))
}

/// Gets whether the system is in a deprecated status.
///
/// The system is usually set as deprecated as effect of managing some kind of devices, for example,
/// when iSCSI sessions are created or when a zFCP disk is activated.
///
/// A deprecated system means that the probed system could not match with the current system.
///
/// It is expected that clients probe devices again if the system is deprecated.
#[utoipa::path(
get,
path = "/devices/dirty",
context_path = "/api/storage",
responses(
(status = 200, description = "Whether the devices have changed", body = bool),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn devices_dirty(State(state): State<StorageState<'_>>) -> Result<Json<bool>, Error> {
Ok(Json(state.client.devices_dirty_bit().await?))
}

/// Gets the probed devices.
#[utoipa::path(
get,
path = "/devices/system",
context_path = "/api/storage",
responses(
(status = 200, description = "List of devices", body = Vec<Device>),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn system_devices(State(state): State<StorageState<'_>>) -> Result<Json<Vec<Device>>, Error> {
Ok(Json(state.client.system_devices().await?))
}

/// Gets the resulting devices of applying the requested actions.
#[utoipa::path(
get,
path = "/devices/result",
context_path = "/api/storage",
responses(
(status = 200, description = "List of devices", body = Vec<Device>),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
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?))
}

/// Gets the default values for a volume with the given mount path.
#[utoipa::path(
get,
path = "/product/volume_for",
context_path = "/api/storage",
params(VolumeForQuery),
responses(
(status = 200, description = "Volume specification", body = Volume),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn volume_for(
State(state): State<StorageState<'_>>,
Query(params): Query<HashMap<String, String>>,
query: Query<VolumeForQuery>,
) -> 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?))
Ok(Json(
state.client.volume_for(query.mount_path.as_str()).await?,
))
}

#[derive(Deserialize, utoipa::IntoParams)]
struct VolumeForQuery {
/// Mount path of the volume (empty for an arbitrary volume).
mount_path: String,
}

/// Gets information about the selected product.
#[utoipa::path(
get,
path = "/product/params",
context_path = "/api/storage",
responses(
(status = 200, description = "Product information", body = ProductParams),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn product_params(
State(state): State<StorageState<'_>>,
) -> Result<Json<ProductParams>, Error> {
Expand All @@ -150,24 +207,79 @@ async fn product_params(
Ok(Json(params))
}

async fn usable_devices(State(state): State<StorageState<'_>>) -> Result<Json<Vec<String>>, Error> {
let devices = state.client.available_devices().await?;
let devices_names = devices.into_iter().map(|d| d.name).collect();
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ProductParams {
/// Mount points defined by the product.
mount_points: Vec<String>,
/// Encryption methods allowed by the product.
encryption_methods: Vec<String>,
}

Ok(Json(devices_names))
/// Gets the actions to perform in the storage devices.
#[utoipa::path(
get,
path = "/proposal/actions",
context_path = "/api/storage",
responses(
(status = 200, description = "List of actions", body = Vec<Action>),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn actions(State(state): State<StorageState<'_>>) -> Result<Json<Vec<Action>>, Error> {
Ok(Json(state.client.actions().await?))
}

/// Gets the SID (Storage ID) of the devices usable for the installation.
///
/// Note that not all the existing devices can be selected as target device for the installation.
#[utoipa::path(
get,
path = "/proposal/usable_devices",
context_path = "/api/storage",
responses(
(status = 200, description = "Lis of SIDs", body = Vec<DeviceSid>),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn usable_devices(
State(state): State<StorageState<'_>>,
) -> Result<Json<Vec<DeviceSid>>, Error> {
let sids = state.client.available_devices().await?;
Ok(Json(sids))
}

/// Gets the settings that were used for calculating the current proposal.
#[utoipa::path(
get,
path = "/proposal/settings",
context_path = "/api/storage",
responses(
(status = 200, description = "Settings", body = ProposalSettings),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn get_proposal_settings(
State(state): State<StorageState<'_>>,
) -> Result<Json<ProposalSettings>, Error> {
Ok(Json(state.client.proposal_settings().await?))
}

/// Tries to calculates a new proposal with the given settings.
#[utoipa::path(
put,
path = "/proposal/settings",
context_path = "/api/storage",
request_body(content = ProposalSettingsPatch, description = "Proposal settings", content_type = "application/json"),
responses(
(status = 200, description = "Whether the proposal was successfully calculated", body = bool),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn set_proposal_settings(
State(state): State<StorageState<'_>>,
Json(config): Json<ProposalSettingsPatch>,
) -> Result<(), Error> {
state.client.calculate2(config).await?;

Ok(())
) -> Result<Json<bool>, Error> {
let result = state.client.calculate2(config).await?;
Ok(Json(result == 0))
}
Loading

0 comments on commit 1c661f7

Please sign in to comment.