Skip to content

Commit

Permalink
rust: Document storage http API
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed May 16, 2024
1 parent 85d77fc commit cf10ccf
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 32 deletions.
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 @@ -476,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
157 changes: 125 additions & 32 deletions rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
//! * `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::{
Expand All @@ -15,13 +13,12 @@ use agama_lib::{
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 @@ -109,52 +97,106 @@ pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
}

/// 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?))
}

/// Whether the system is in a deprecated status.
/// 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?))
}

/// Information about the current storage devices in the system.
/// 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?))
}

/// Information about the target storage devices for the installation.
/// 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?))
}

/// Actions to perform to transform system into staging.
async fn actions(State(state): State<StorageState<'_>>) -> Result<Json<Vec<Action>>, Error> {
Ok(Json(state.client.actions().await?))
}

/// The default values for a volume with the given mount path.
/// 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?,
))
}

/// Some information about the selected product, for example, the pre-defined mount paths or the
/// available encryption methods.
#[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 @@ -165,24 +207,75 @@ async fn product_params(
Ok(Json(params))
}

/// The SID of the devices usable for the installation.
#[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>,
}

/// 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))
}

/// Settings used for calculating the proposal.
/// 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?))
}

/// Calculates a new proposal with the given settings.
/// 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>,
Expand Down
37 changes: 37 additions & 0 deletions rust/agama-server/src/web/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ use utoipa::OpenApi;
crate::software::web::products,
crate::software::web::proposal,
crate::software::web::set_config,
crate::storage::web::actions,
crate::storage::web::devices_dirty,
crate::storage::web::get_proposal_settings,
crate::storage::web::probe,
crate::storage::web::product_params,
crate::storage::web::set_proposal_settings,
crate::storage::web::staging_devices,
crate::storage::web::system_devices,
crate::storage::web::usable_devices,
crate::storage::web::volume_for,
crate::storage::web::iscsi::delete_node,
crate::storage::web::iscsi::discover,
crate::storage::web::iscsi::initiator,
Expand All @@ -49,6 +59,32 @@ use utoipa::OpenApi;
schemas(agama_lib::network::types::DeviceType),
schemas(agama_lib::product::Product),
schemas(agama_lib::software::Pattern),
schemas(agama_lib::storage::model::Action),
schemas(agama_lib::storage::model::BlockDevice),
schemas(agama_lib::storage::model::Component),
schemas(agama_lib::storage::model::Device),
schemas(agama_lib::storage::model::DeviceInfo),
schemas(agama_lib::storage::model::DeviceSid),
schemas(agama_lib::storage::model::Drive),
schemas(agama_lib::storage::model::DriveInfo),
schemas(agama_lib::storage::model::DeviceSize),
schemas(agama_lib::storage::model::Filesystem),
schemas(agama_lib::storage::model::LvmLv),
schemas(agama_lib::storage::model::LvmVg),
schemas(agama_lib::storage::model::Md),
schemas(agama_lib::storage::model::Multipath),
schemas(agama_lib::storage::model::Partition),
schemas(agama_lib::storage::model::PartitionTable),
schemas(agama_lib::storage::model::ProposalSettings),
schemas(agama_lib::storage::model::ProposalSettingsPatch),
schemas(agama_lib::storage::model::ProposalTarget),
schemas(agama_lib::storage::model::Raid),
schemas(agama_lib::storage::model::SpaceAction),
schemas(agama_lib::storage::model::SpaceActionSettings),
schemas(agama_lib::storage::model::UnusedSlot),
schemas(agama_lib::storage::model::Volume),
schemas(agama_lib::storage::model::VolumeOutline),
schemas(agama_lib::storage::model::VolumeTarget),
schemas(agama_lib::storage::client::iscsi::ISCSIAuth),
schemas(agama_lib::storage::client::iscsi::ISCSIInitiator),
schemas(agama_lib::storage::client::iscsi::ISCSINode),
Expand All @@ -69,6 +105,7 @@ use utoipa::OpenApi;
schemas(crate::questions::web::QuestionWithPassword),
schemas(crate::software::web::SoftwareConfig),
schemas(crate::software::web::SoftwareProposal),
schemas(crate::storage::web::ProductParams),
schemas(crate::storage::web::iscsi::DiscoverParams),
schemas(crate::storage::web::iscsi::InitiatorParams),
schemas(crate::storage::web::iscsi::LoginParams),
Expand Down

0 comments on commit cf10ccf

Please sign in to comment.