Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions live/live-root/root/.bash_history
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mount | cut -f3 -d" " | grep /mnt | sort -r | xargs -r umount; swapon --show=NAME --noheadings | grep -v zram | xargs -r swapoff
systemctl restart agama.service agama-web-server.service && sleep 2 && systemctl restart x11-autologin.service
less /var/log/YaST2/y2log
journalctl -u agama-web-server.service
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-lib/src/product/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use zbus::Connection;
use super::proxies::RegistrationProxy;

/// Represents a software product
#[derive(Default, Debug, Serialize, utoipa::ToSchema)]
#[derive(Clone, Default, Debug, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Product {
/// Product ID (eg., "ALP", "Tumbleweed", etc.)
Expand Down
80 changes: 75 additions & 5 deletions rust/agama-server/src/software/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@

use crate::{
error::Error,
web::common::{issues_router, progress_router, service_status_router, EventStreams},
web::{
common::{issues_router, progress_router, service_status_router, EventStreams},
EventsReceiver,
},
};

use agama_lib::{
Expand All @@ -52,13 +55,19 @@ use axum::{
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_stream::{Stream, StreamExt};

#[derive(Clone)]
struct SoftwareState<'a> {
product: ProductClient<'a>,
software: SoftwareClient<'a>,
licenses: LicensesRepo,
// cache the software values, during installation the software service is
// not responsive (blocked in a libzypp call)
products: Arc<RwLock<Option<Vec<Product>>>>,
config: Arc<RwLock<Option<SoftwareConfig>>>,
}

/// Returns an stream that emits software related events coming from D-Bus.
Expand Down Expand Up @@ -178,8 +187,27 @@ fn reason_to_selected_by(
Ok(selected)
}

/// Process incoming events.
///
/// * `events`: channel to listen for events.
/// * `products`: list of products (shared behind a mutex).
pub async fn receive_events(
mut events: EventsReceiver,
products: Arc<RwLock<Option<Vec<Product>>>>,
) {
while let Ok(event) = events.recv().await {
if let Event::LocaleChanged { locale: _ } = event {
let mut cached_products = products.write().await;
*cached_products = None;
}
}
}

/// Sets up and returns the axum service for the software module.
pub async fn software_service(dbus: zbus::Connection) -> Result<Router, ServiceError> {
pub async fn software_service(
dbus: zbus::Connection,
events: EventsReceiver,
) -> Result<Router, ServiceError> {
const DBUS_SERVICE: &str = "org.opensuse.Agama.Software1";
const DBUS_PATH: &str = "/org/opensuse/Agama/Software1";
const DBUS_PRODUCT_PATH: &str = "/org/opensuse/Agama/Software1/Product";
Expand All @@ -200,7 +228,13 @@ pub async fn software_service(dbus: zbus::Connection) -> Result<Router, ServiceE
product,
software,
licenses: licenses_repo,
products: Arc::new(RwLock::new(None)),
config: Arc::new(RwLock::new(None)),
};

let cached_products = Arc::clone(&state.products);
tokio::spawn(async move { receive_events(events, cached_products).await });

let router = Router::new()
.route("/patterns", get(patterns))
.route("/repositories", get(repositories))
Expand Down Expand Up @@ -243,7 +277,18 @@ pub async fn software_service(dbus: zbus::Connection) -> Result<Router, ServiceE
)
)]
async fn products(State(state): State<SoftwareState<'_>>) -> Result<Json<Vec<Product>>, Error> {
let cached_products = state.products.read().await.clone();

if let Some(products) = cached_products {
tracing::info!("Returning cached products");
return Ok(Json(products));
}

let products = state.product.products().await?;

let mut cached_products_write = state.products.write().await;
*cached_products_write = Some(products.clone());

Ok(Json(products))
}

Expand Down Expand Up @@ -480,6 +525,12 @@ async fn set_config(
state.software.select_packages(packages).await?;
}

// load the config cache
let config = read_config(&state).await?;

let mut cached_config_write = state.config.write().await;
*cached_config_write = Some(config);

Ok(())
}

Expand All @@ -496,7 +547,26 @@ async fn set_config(
(status = 400, description = "The D-Bus service could not perform the action")
)
)]

async fn get_config(State(state): State<SoftwareState<'_>>) -> Result<Json<SoftwareConfig>, Error> {
let cached_config = state.config.read().await.clone();

if let Some(config) = cached_config {
tracing::info!("Returning cached software config");
return Ok(Json(config));
}

let config = read_config(&state).await?;

let mut cached_config_write = state.config.write().await;
*cached_config_write = Some(config.clone());

Ok(Json(config))
}

/// Helper function
/// * `state` : software service state
async fn read_config(state: &SoftwareState<'_>) -> Result<SoftwareConfig, Error> {
let product = state.product.product().await?;
let product = if product.is_empty() {
None
Expand All @@ -511,12 +581,12 @@ async fn get_config(State(state): State<SoftwareState<'_>>) -> Result<Json<Softw
.map(|p| (p, true))
.collect();
let packages = state.software.user_selected_packages().await?;
let config = SoftwareConfig {

Ok(SoftwareConfig {
patterns: Some(patterns),
packages: Some(packages),
product,
};
Ok(Json(config))
})
}

#[derive(Serialize, utoipa::ToSchema)]
Expand Down
5 changes: 4 additions & 1 deletion rust/agama-server/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ where
.add_service("/l10n", l10n_service(dbus.clone(), events.clone()).await?)
.add_service("/manager", manager_service(dbus.clone()).await?)
.add_service("/security", security_service(dbus.clone()).await?)
.add_service("/software", software_service(dbus.clone()).await?)
.add_service(
"/software",
software_service(dbus.clone(), events.subscribe()).await?,
)
.add_service("/storage", storage_service(dbus.clone()).await?)
.add_service("/iscsi", iscsi_service(dbus.clone()).await?)
.add_service("/bootloader", bootloader_service(dbus.clone()).await?)
Expand Down
7 changes: 7 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Tue May 20 14:54:17 UTC 2025 - Ladislav Slezák <[email protected]>

- Cache the software configuration and products in the web server,
the software backend is blocked during package installation
(bsc#1241208)

-------------------------------------------------------------------
Wed May 14 15:20:42 UTC 2025 - Knut Anderssen <[email protected]>

Expand Down
Loading