Skip to content

Commit

Permalink
feature(storage): API for solved and unsolved configs (#1692)
Browse files Browse the repository at this point in the history
Extend D-Bus and HTTP APIs to get the solved storage config:

```
$ busctl --address unix:path=/run/agama/bus call org.opensuse.Agama.Storage1 /org/opensuse/Agama/Storage1 org.opensuse.Agama.Storage1 GetSolvedConfig
```

```
$ curl -H @headers.txt http://localhost/api/storage/solved_config
```
  • Loading branch information
joseivanlopez authored Oct 25, 2024
2 parents 825ad0c + a6eba50 commit fd2433e
Show file tree
Hide file tree
Showing 19 changed files with 559 additions and 204 deletions.
13 changes: 13 additions & 0 deletions doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,25 @@
<interface name="org.opensuse.Agama.Storage1">
<method name="Probe">
</method>
<method name="SetConfig">
<arg name="serialized_config" direction="in" type="s"/>
<arg name="result" direction="out" type="u"/>
</method>
<method name="GetConfig">
<arg name="serialized_config" direction="out" type="s"/>
</method>
<method name="GetSolvedConfig">
<arg name="serialized_config" direction="out" type="s"/>
</method>
<method name="Install">
</method>
<method name="Finish">
</method>
<property type="b" name="DeprecatedSystem" access="read"/>
</interface>
<interface name="org.opensuse.Agama.Storage1.Devices">
<property type="aa{sv}" name="Actions" access="read"/>
</interface>
<interface name="org.opensuse.Agama.Storage1.DASD.Manager">
<method name="Probe">
</method>
Expand Down
78 changes: 78 additions & 0 deletions doc/dbus/org.opensuse.Agama.Storage1.doc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,84 @@
<interface name="org.opensuse.Agama.Storage1">
<method name="Probe">
</method>
<!--
Sets the storage config.
-->
<method name="SetConfig">
<!--
E.g.,
{
"storage": {
"drives": [
{
"search": "/dev/vda",
"partitions": [
{ "generate": "default" }
]
}
]
}
}
-->
<arg name="serialized_config" direction="in" type="s"/>
<!--
Whether the proposal was correctly calculated:
0: success
1: failure
-->
<arg name="result" direction="out" type="u"/>
</method>
<!--
Gets the unsolved storage config.
-->
<method name="GetConfig">
<!--
E.g.,
{
"storage": {
"drives": [
{
"search": "/dev/vda",
"partitions": [
{ "generate": "default" }
]
}
]
}
}
-->
<arg name="serialized_config" direction="out" type="s"/>
</method>
<!--
Gets the solved storage config.
-->
<method name="GetSolvedConfig">
<!--
E.g.,
{
"storage": {
"drives": [
{
"search": "/dev/vda",
"partitions": [
{
"filesystem": {
"path": "/home",
"type": "xfs"
},
"size": {
"min": "10 GiB",
"max": "10 GiB"
}
}
]
}
]
}
}
-->
<arg name="serialized_config" direction="out" type="s"/>
</method>
<method name="Install">
</method>
<method name="Finish">
Expand Down
7 changes: 7 additions & 0 deletions rust/agama-lib/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ impl<'a> StorageClient<'a> {
Ok(settings)
}

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

pub async fn calculate(&self, settings: ProposalSettingsPatch) -> Result<u32, ServiceError> {
Ok(self.calculator_proxy.calculate(settings.into()).await?)
}
Expand Down
3 changes: 3 additions & 0 deletions rust/agama-lib/src/storage/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ trait Storage1 {
/// Get the current storage config according to the JSON schema
fn get_config(&self) -> zbus::Result<String>;

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

/// DeprecatedSystem property
#[dbus_proxy(property)]
fn deprecated_system(&self) -> zbus::Result<bool>;
Expand Down
26 changes: 26 additions & 0 deletions rust/agama-server/src/storage/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ pub async fn storage_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
let state = StorageState { client };
let router = Router::new()
.route("/config", put(set_config).get(get_config))
.route("/solved_config", get(get_solved_config))
.route("/probe", post(probe))
.route("/devices/dirty", get(devices_dirty))
.route("/devices/system", get(system_devices))
Expand Down Expand Up @@ -155,6 +156,31 @@ async fn get_config(State(state): State<StorageState<'_>>) -> Result<Json<Storag
Ok(Json(settings))
}

/// Returns the solved storage configuration.
///
/// * `state` : service state.
#[utoipa::path(
get,
path = "/solved_config",
context_path = "/api/storage",
operation_id = "get_storage_solved_config",
responses(
(status = 200, description = "storage solved configuration", body = StorageSettings),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn get_solved_config(
State(state): State<StorageState<'_>>,
) -> Result<Json<StorageSettings>, Error> {
// StorageSettings is just a wrapper over serde_json::value::RawValue
let settings = state
.client
.get_solved_config()
.await
.map_err(Error::Service)?;
Ok(Json(settings))
}

/// Sets the storage configuration.
///
/// * `state`: service state.
Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu Oct 24 14:19:46 UTC 2024 - José Iván López González <[email protected]>

- Storage: extend the HTTP API to allow getting the solved storage
config (gh#agama-project/agama#1692).

-------------------------------------------------------------------
Wed Oct 23 15:25:36 UTC 2024 - Imobach Gonzalez Sosa <[email protected]>

Expand Down
9 changes: 6 additions & 3 deletions service/lib/agama/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

require "yaml"
require "yast2/arch_filter"
require "agama/copyable"
require "agama/config_reader"
require "agama/product_reader"

Expand All @@ -31,6 +32,8 @@ module Agama
# This also means that config needs to be re-evaluated if conditions
# data change, like if user pick different distro to install.
class Config
include Copyable

# @return [Hash] configuration data
attr_accessor :pure_data
attr_accessor :logger
Expand Down Expand Up @@ -124,11 +127,11 @@ def multi_product?
def copy
logger = self.logger
@logger = nil # cannot dump logger as it can contain IO
res = Marshal.load(Marshal.dump(self))
new_config = super
@logger = logger
res.logger = logger
new_config.logger = logger

res
new_config
end

# Returns a new {Config} with the merge of the given ones
Expand Down
32 changes: 32 additions & 0 deletions service/lib/agama/copyable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

module Agama
# Mixin to make a class copyable.
module Copyable
# Copies the object by doing a deep dup.
#
# @return [Object] New instance of the object's class.
def copy
Marshal.load(Marshal.dump(self))
end
end
end
7 changes: 5 additions & 2 deletions service/lib/agama/dbus/storage/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ def apply_config(serialized_config)

# Gets and serializes the storage config used to calculate the current proposal.
#
# @param solved [Boolean] Whether to recover the solved config.
# @return [String] Serialized config according to the JSON schema.
def recover_config
JSON.pretty_generate(proposal.config_json)
def recover_config(solved: false)
json = proposal.storage_json(solved: solved)
JSON.pretty_generate(json)
end

def install
Expand All @@ -139,6 +141,7 @@ def deprecated_system
busy_while { apply_config(serialized_config) }
end
dbus_method(:GetConfig, "out serialized_config:s") { recover_config }
dbus_method(:GetSolvedConfig, "out serialized_config:s") { recover_config(solved: true) }
dbus_method(:Install) { install }
dbus_method(:Finish) { finish }
dbus_reader(:deprecated_system, "b")
Expand Down
3 changes: 3 additions & 0 deletions service/lib/agama/storage/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "agama/copyable"
require "agama/storage/configs/boot"
require "agama/storage/config_conversions/from_json"

module Agama
module Storage
# Settings used to calculate an storage proposal.
class Config
include Copyable

# Boot settings.
#
# @return [Configs::Boot]
Expand Down
14 changes: 6 additions & 8 deletions service/lib/agama/storage/configs/with_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "agama/copyable"

module Agama
module Storage
module Configs
# Mixin for configs with search.
module WithSearch
# Needed when a search returns multiple devices and the configuration needs to be replicated
# for each one.
include Copyable

# @return [Search, nil]
attr_accessor :search

Expand All @@ -35,14 +41,6 @@ module WithSearch
def found_device
search&.device
end

# Creates a deep copy of the config element
#
# Needed when a search returns multiple devices and the configuration needs to be replicated
# for each one.
def copy
Marshal.load(Marshal.dump(self))
end
end
end
end
Expand Down
Loading

0 comments on commit fd2433e

Please sign in to comment.