Skip to content

Commit

Permalink
feat(storage): load storage config for guided proposal (#1293)
Browse files Browse the repository at this point in the history
Add CLI support for setting and getting the storage config.

### Load (set) storage settings from a config file:

~~~
$ agama config load < config.json
~~~

* The given settings must match the JSON schema defined by
#1263 (not finished yet).
* The storage service calculates either a guided proposal or an AutoYaST
proposal, depending on the given config.

Config for a guided proposal:

~~~json
{
  "storage": {
    "guided": {
      "target": {
        "disk": "/dev/vdc"
      }
    }
  }
}
~~~

Config for an AutoYaST proposal:

~~~json
{
  "legacyAutoyastStorage": [
    {
      "device": "/dev/vdc",
      "use": "all"
    }
  ]
}
~~~

### Recover (get) the storage config:

~~~
$ agama config show > config.json
$ agama config edit
~~~

NOTES

* The storage service should check whether the given settings fulfill
the JSON schema.
* Checking the schema is not done yet because the schema definition is
not finished.
  • Loading branch information
joseivanlopez authored Jun 26, 2024
2 parents f003fb4 + 0059e2f commit 7c5639c
Show file tree
Hide file tree
Showing 29 changed files with 1,770 additions and 282 deletions.
12 changes: 6 additions & 6 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions rust/agama-lib/src/install_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! This module implements the mechanisms to load and store the installation settings.
use crate::{
localization::LocalizationSettings, network::NetworkSettings, product::ProductSettings,
software::SoftwareSettings, storage::StorageSettings, users::UserSettings,
software::SoftwareSettings, users::UserSettings,
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
Expand All @@ -26,8 +26,10 @@ pub struct InstallSettings {
#[serde(default)]
pub product: Option<ProductSettings>,
#[serde(default)]
pub storage: Option<StorageSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<Box<RawValue>>,
#[serde(default, rename = "legacyAutoyastStorage")]
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_autoyast: Option<Box<RawValue>>,
#[serde(default)]
pub network: Option<NetworkSettings>,
Expand Down
2 changes: 0 additions & 2 deletions rust/agama-lib/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
//! Implements support for handling the storage settings

mod autoyast;
pub mod client;
pub mod model;
pub mod proxies;
mod settings;
mod store;

pub use autoyast::store::StorageAutoyastStore;
pub use client::{
iscsi::{ISCSIAuth, ISCSIClient, ISCSIInitiator, ISCSINode},
StorageClient,
Expand Down
2 changes: 0 additions & 2 deletions rust/agama-lib/src/storage/autoyast.rs

This file was deleted.

26 changes: 0 additions & 26 deletions rust/agama-lib/src/storage/autoyast/store.rs

This file was deleted.

75 changes: 15 additions & 60 deletions rust/agama-lib/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

use super::model::{
Action, BlockDevice, Component, Device, DeviceInfo, DeviceSid, Drive, Filesystem, LvmLv, LvmVg,
Md, Multipath, Partition, PartitionTable, ProposalSettings, ProposalSettingsPatch,
ProposalTarget, Raid, Volume,
Md, Multipath, Partition, PartitionTable, ProposalSettings, ProposalSettingsPatch, Raid,
Volume,
};
use super::proxies::{ProposalCalculatorProxy, ProposalProxy, Storage1Proxy};
use super::StorageSettings;
Expand Down Expand Up @@ -105,73 +105,28 @@ impl<'a> StorageClient<'a> {
Ok(self.proposal_proxy.settings().await?.try_into()?)
}

/// Returns the boot device proposal setting
/// DEPRECATED, use proposal_settings instead
pub async fn boot_device(&self) -> Result<Option<String>, ServiceError> {
let settings = self.proposal_settings().await?;
let boot_device = settings.boot_device;

if boot_device.is_empty() {
Ok(None)
} else {
Ok(Some(boot_device))
}
}

/// Returns the lvm proposal setting
/// DEPRECATED, use proposal_settings instead
pub async fn lvm(&self) -> Result<Option<bool>, ServiceError> {
let settings = self.proposal_settings().await?;
Ok(Some(!matches!(settings.target, ProposalTarget::Disk)))
}

/// Returns the encryption password proposal setting
/// DEPRECATED, use proposal_settings instead
pub async fn encryption_password(&self) -> Result<Option<String>, ServiceError> {
let settings = self.proposal_settings().await?;
let value = settings.encryption_password;

if value.is_empty() {
Ok(None)
} else {
Ok(Some(value))
}
}

/// Runs the probing process
pub async fn probe(&self) -> Result<(), ServiceError> {
Ok(self.storage_proxy.probe().await?)
}

/// TODO: remove calculate when CLI will be adapted
pub async fn calculate2(&self, settings: ProposalSettingsPatch) -> Result<u32, ServiceError> {
Ok(self.calculator_proxy.calculate(settings.into()).await?)
/// Set the storage config according to the JSON schema
pub async fn set_config(&self, settings: StorageSettings) -> Result<u32, ServiceError> {
Ok(self
.storage_proxy
.set_config(serde_json::to_string(&settings).unwrap().as_str())
.await?)
}

pub async fn calculate_autoyast(&self, settings: &str) -> Result<u32, ServiceError> {
Ok(self.calculator_proxy.calculate_autoyast(settings).await?)
/// Get the storage config according to the JSON schema
pub async fn get_config(&self) -> Result<StorageSettings, ServiceError> {
let serialized_settings = self.storage_proxy.get_config().await?;
let settings = serde_json::from_str(serialized_settings.as_str()).unwrap();
Ok(settings)
}

/// Calculates a new proposal with the given settings.
pub async fn calculate(&self, settings: &StorageSettings) -> Result<u32, ServiceError> {
let mut dbus_settings: HashMap<&str, zbus::zvariant::Value<'_>> = HashMap::new();

if let Some(boot_device) = settings.boot_device.clone() {
dbus_settings.insert("BootDevice", zbus::zvariant::Value::new(boot_device));
}

if let Some(encryption_password) = settings.encryption_password.clone() {
dbus_settings.insert(
"EncryptionPassword",
zbus::zvariant::Value::new(encryption_password),
);
}

if let Some(lvm) = settings.lvm {
dbus_settings.insert("LVM", zbus::zvariant::Value::new(lvm));
}

Ok(self.calculator_proxy.calculate(dbus_settings).await?)
pub async fn calculate(&self, settings: ProposalSettingsPatch) -> Result<u32, ServiceError> {
Ok(self.calculator_proxy.calculate(settings.into()).await?)
}

/// Probed devices.
Expand Down
9 changes: 6 additions & 3 deletions rust/agama-lib/src/storage/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ trait Storage1 {
/// Probe method
fn probe(&self) -> zbus::Result<()>;

/// Set the storage config according to the JSON schema
fn set_config(&self, settings: &str) -> zbus::Result<u32>;

/// Get the current storage config according to the JSON schema
fn get_config(&self) -> zbus::Result<String>;

/// DeprecatedSystem property
#[dbus_proxy(property)]
fn deprecated_system(&self) -> zbus::Result<bool>;
Expand All @@ -35,9 +41,6 @@ trait ProposalCalculator {
settings: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>,
) -> zbus::Result<u32>;

/// Calculate AutoYaST proposal
fn calculate_autoyast(&self, settings: &str) -> zbus::Result<u32>;

/// DefaultVolume method
fn default_volume(
&self,
Expand Down
23 changes: 17 additions & 6 deletions rust/agama-lib/src/storage/settings.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
//! Representation of the storage settings

use crate::install_settings::InstallSettings;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;

/// Storage settings for installation
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageSettings {
/// Whether LVM should be enabled
pub lvm: Option<bool>,
/// Encryption password for the storage devices (in clear text)
pub encryption_password: Option<String>,
/// Boot device to use in the installation
pub boot_device: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<Box<RawValue>>,
#[serde(default, rename = "legacyAutoyastStorage")]
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_autoyast: Option<Box<RawValue>>,
}

impl From<&InstallSettings> for StorageSettings {
fn from(install_settings: &InstallSettings) -> Self {
StorageSettings {
storage: install_settings.storage.clone(),
storage_autoyast: install_settings.storage_autoyast.clone(),
}
}
}
18 changes: 3 additions & 15 deletions rust/agama-lib/src/storage/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,11 @@ impl<'a> StorageStore<'a> {
}

pub async fn load(&self) -> Result<StorageSettings, ServiceError> {
// If it is not possible to get the settings (e.g., there are no settings yet), return
// the default.
let Ok(boot_device) = self.storage_client.boot_device().await else {
return Ok(StorageSettings::default());
};
let lvm = self.storage_client.lvm().await?;
let encryption_password = self.storage_client.encryption_password().await?;

Ok(StorageSettings {
boot_device,
lvm,
encryption_password,
})
Ok(self.storage_client.get_config().await?)
}

pub async fn store(&self, settings: &StorageSettings) -> Result<(), ServiceError> {
self.storage_client.calculate(settings).await?;
pub async fn store(&self, settings: StorageSettings) -> Result<(), ServiceError> {
self.storage_client.set_config(settings).await?;
Ok(())
}
}
26 changes: 8 additions & 18 deletions rust/agama-lib/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use crate::error::ServiceError;
use crate::install_settings::InstallSettings;
use crate::{
localization::LocalizationStore, network::NetworkStore, product::ProductStore,
software::SoftwareStore, storage::StorageAutoyastStore, storage::StorageStore,
users::UsersStore,
software::SoftwareStore, storage::StorageStore, users::UsersStore,
};
use zbus::Connection;

Expand All @@ -22,7 +21,6 @@ pub struct Store<'a> {
product: ProductStore<'a>,
software: SoftwareStore<'a>,
storage: StorageStore<'a>,
storage_autoyast: StorageAutoyastStore<'a>,
localization: LocalizationStore<'a>,
}

Expand All @@ -37,25 +35,23 @@ impl<'a> Store<'a> {
network: NetworkStore::new(http_client).await?,
product: ProductStore::new(connection.clone()).await?,
software: SoftwareStore::new(connection.clone()).await?,
storage: StorageStore::new(connection.clone()).await?,
storage_autoyast: StorageAutoyastStore::new(connection).await?,
storage: StorageStore::new(connection).await?,
})
}

/// Loads the installation settings from the HTTP interface.
///
/// NOTE: The storage AutoYaST settings cannot be loaded because they cannot be modified. The
/// ability of using the storage AutoYaST settings from a JSON config file is temporary and it
/// will be removed in the future.
pub async fn load(&self) -> Result<InstallSettings, ServiceError> {
let mut settings: InstallSettings = Default::default();
settings.network = Some(self.network.load().await?);
settings.storage = Some(self.storage.load().await?);
settings.software = Some(self.software.load().await?);
settings.user = Some(self.users.load().await?);
settings.product = Some(self.product.load().await?);
settings.localization = Some(self.localization.load().await?);

let storage_settings = self.storage.load().await?;
settings.storage = storage_settings.storage.clone();
settings.storage_autoyast = storage_settings.storage_autoyast.clone();

// TODO: use try_join here
Ok(settings)
}
Expand All @@ -80,14 +76,8 @@ impl<'a> Store<'a> {
if let Some(user) = &settings.user {
self.users.store(user).await?;
}
if let Some(storage) = &settings.storage {
self.storage.store(storage).await?;
}
if let Some(storage_autoyast) = &settings.storage_autoyast {
// Storage scope has precedence.
if settings.storage.is_none() {
self.storage_autoyast.store(storage_autoyast.get()).await?;
}
if settings.storage.is_some() || settings.storage_autoyast.is_some() {
self.storage.store(settings.into()).await?
}
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,6 @@ async fn set_proposal_settings(
State(state): State<StorageState<'_>>,
Json(config): Json<ProposalSettingsPatch>,
) -> Result<Json<bool>, Error> {
let result = state.client.calculate2(config).await?;
let result = state.client.calculate(config).await?;
Ok(Json(result == 0))
}
5 changes: 5 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
-------------------------------------------------------------------
Wed Jun 26 10:29:05 UTC 2024 - José Iván López González <[email protected]>

- Set and get storage config (gh#openSUSE/agama#1293).

-------------------------------------------------------------------
Tue Jun 25 15:16:33 UTC 2024 - Imobach Gonzalez Sosa <[email protected]>

Expand Down
Loading

0 comments on commit 7c5639c

Please sign in to comment.