public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH datacenter-manager 11/20] parallel fetcher: pass arguments to closure in a single type
Date: Mon, 17 Aug 2026 14:57:18 +0200	[thread overview]
Message-ID: <20260817125727.454039-12-l.wagner@proxmox.com> (raw)
In-Reply-To: <20260817125727.454039-1-l.wagner@proxmox.com>

This makes it much easier to pass more data later, e.g. a client
factory.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 server/src/api/pve/firewall.rs          | 31 +++++++++-----------
 server/src/api/sdn/controllers.rs       |  6 ++--
 server/src/api/sdn/vnets.rs             |  6 ++--
 server/src/api/sdn/zones.rs             |  6 ++--
 server/src/parallel_fetcher.rs          | 39 ++++++++++++++++++-------
 server/src/remote_tasks/refresh_task.rs | 23 +++++++--------
 server/src/remote_updates.rs            |  7 +++--
 7 files changed, 69 insertions(+), 49 deletions(-)

diff --git a/server/src/api/pve/firewall.rs b/server/src/api/pve/firewall.rs
index b381a14e..dc6c97b5 100644
--- a/server/src/api/pve/firewall.rs
+++ b/server/src/api/pve/firewall.rs
@@ -19,7 +19,7 @@ use pdm_api_types::{PRIV_RESOURCE_AUDIT, PRIV_RESOURCE_MODIFY, PRIV_SYS_MODIFY};
 use super::{connect_to_remote_by_id, find_node_for_vm};
 use crate::api::remotes::RemoteIterator;
 use crate::connection::PveClient;
-use crate::parallel_fetcher::ParallelFetcher;
+use crate::parallel_fetcher::{ParallelFetcher, ParallelFetcherArgs};
 
 // top-level firewall routers
 pub const PVE_FW_ROUTER: Router = Router::new()
@@ -112,11 +112,9 @@ struct ClusterFirewallData {
 }
 
 async fn fetch_cluster_firewall_data(
-    _context: (),
-    remote: Remote,
-    _node: String, // unused for cluster-level data
+    args: ParallelFetcherArgs<()>,
 ) -> Result<ClusterFirewallData, Error> {
-    let pve = crate::connection::make_pve_client(&remote)?;
+    let pve = crate::connection::make_pve_client(&args.remote)?;
 
     let guests = match pve.cluster_resources(Some(ClusterResourceKind::Vm)).await {
         Ok(guests) => guests,
@@ -204,14 +202,12 @@ async fn load_guests_firewall_status(
 }
 
 async fn fetch_node_firewall_status(
-    context: FirewallFetchContext,
-    remote: Remote,
-    node: String,
+    args: ParallelFetcherArgs<FirewallFetchContext>,
 ) -> Result<NodeFirewallStatus, Error> {
-    let pve = crate::connection::make_pve_client(&remote)?;
+    let pve = crate::connection::make_pve_client(&args.remote)?;
 
-    let options_response = pve.node_firewall_options(&node);
-    let rules_response = pve.list_node_firewall_rules(&node);
+    let options_response = pve.node_firewall_options(&args.node);
+    let rules_response = pve.list_node_firewall_rules(&args.node);
 
     let enabled = options_response
         .await
@@ -227,10 +223,11 @@ async fn fetch_node_firewall_status(
         _ => None,
     };
 
-    let guests_status = load_guests_firewall_status(pve, node.clone(), &context.guests).await;
+    let guests_status =
+        load_guests_firewall_status(pve, args.node.clone(), &args.context.guests).await;
 
     Ok(NodeFirewallStatus {
-        node,
+        node: args.node,
         status,
         guests: guests_status,
     })
@@ -280,11 +277,11 @@ pub async fn pve_firewall_status(
 
     let node_fetcher = ParallelFetcher::new(context);
     let node_results = node_fetcher
-        .do_for_all_remote_nodes(pve_remotes.iter().cloned(), move |mut ctx, remote, node| {
-            if let Some(guests) = guests_per_remote.get(&remote.id) {
-                ctx.guests = guests.clone();
+        .do_for_all_remote_nodes(pve_remotes.iter().cloned(), move |mut args| {
+            if let Some(guests) = guests_per_remote.get(&args.remote.id) {
+                args.context.guests = guests.clone();
             }
-            fetch_node_firewall_status(ctx, remote, node)
+            fetch_node_firewall_status(args)
         })
         .await;
 
diff --git a/server/src/api/sdn/controllers.rs b/server/src/api/sdn/controllers.rs
index 060fef72..049358cd 100644
--- a/server/src/api/sdn/controllers.rs
+++ b/server/src/api/sdn/controllers.rs
@@ -86,9 +86,9 @@ pub async fn list_controllers(
     let fetcher = ParallelFetcher::new((pending, running, ty));
 
     let results = fetcher
-        .do_for_all_remotes(iter.into_remotes(), async |ctx, r, _| {
-            Ok(pve::connect(&r)?
-                .list_controllers(ctx.0, ctx.1, ctx.2)
+        .do_for_all_remotes(iter.into_remotes(), async |args| {
+            Ok(pve::connect(&args.remote)?
+                .list_controllers(args.context.0, args.context.1, args.context.2)
                 .await?)
         })
         .await;
diff --git a/server/src/api/sdn/vnets.rs b/server/src/api/sdn/vnets.rs
index 30d431bc..8e7ae52b 100644
--- a/server/src/api/sdn/vnets.rs
+++ b/server/src/api/sdn/vnets.rs
@@ -87,8 +87,10 @@ async fn list_vnets(
     let fetcher = ParallelFetcher::new((pending, running));
 
     let results = fetcher
-        .do_for_all_remotes(iter.into_remotes(), async |ctx, r, _| {
-            Ok(pve::connect(&r)?.list_vnets(ctx.0, ctx.1).await?)
+        .do_for_all_remotes(iter.into_remotes(), async |args| {
+            Ok(pve::connect(&args.remote)?
+                .list_vnets(args.context.0, args.context.1)
+                .await?)
         })
         .await;
 
diff --git a/server/src/api/sdn/zones.rs b/server/src/api/sdn/zones.rs
index d37dc46c..bb7a3822 100644
--- a/server/src/api/sdn/zones.rs
+++ b/server/src/api/sdn/zones.rs
@@ -93,8 +93,10 @@ pub async fn list_zones(
     let fetcher = ParallelFetcher::new((pending, running, ty));
 
     let results = fetcher
-        .do_for_all_remotes(iter.into_remotes(), async |ctx, r, _| {
-            Ok(pve::connect(&r)?.list_zones(ctx.0, ctx.1, ctx.2).await?)
+        .do_for_all_remotes(iter.into_remotes(), async |args| {
+            Ok(pve::connect(&args.remote)?
+                .list_zones(args.context.0, args.context.1, args.context.2)
+                .await?)
         })
         .await;
 
diff --git a/server/src/parallel_fetcher.rs b/server/src/parallel_fetcher.rs
index 8153f1fe..0819eb86 100644
--- a/server/src/parallel_fetcher.rs
+++ b/server/src/parallel_fetcher.rs
@@ -4,18 +4,16 @@
 //! # use anyhow::Error;
 //! #
 //! # use pdm_api_types::remotes::{RemoteType, Remote};
-//! # use server::parallel_fetcher::ParallelFetcher;
+//! # use server::parallel_fetcher::{ParallelFetcher, ParallelFetcherArgs};
 //! #
 //! # #[tokio::main]
 //! # async fn main() -> Result<(), Error> {
 //! #   let remotes: Vec<Remote> = Vec::new();
 //! #
 //!     async fn fetch_meaning(
-//!         _context: (),
-//!         remote: Remote,
-//!         node: String,
+//!         args: ParallelFetcherArgs<()>,
 //!     ) -> Result<i32, Error> {
-//!         match remote.ty {
+//!         match args.remote.ty {
 //!             RemoteType::Pve => {
 //!                 // Perform the API request here and return some result.
 //!                 Ok(42)
@@ -269,6 +267,20 @@ impl<C> ParallelFetcherBuilder<C> {
     }
 }
 
+#[non_exhaustive]
+/// The argument type that is passed when calling the closure passed to
+/// [`ParallelFetcher::do_for_all_remote_nodes`] or [`ParallelFetcher::do_for_all_remotes`].
+pub struct ParallelFetcherArgs<C> {
+    /// The context provided in [`ParallelFetcherBuilder::new`] or
+    /// [`ParallelFetcher::new`].
+    pub context: C,
+    /// The remote.
+    pub remote: Remote,
+    /// The node. This may be 'localhost' for PBS remotes or if using
+    /// [`ParallelFetcher::do_for_all_remotes`].
+    pub node: String,
+}
+
 /// Helper for parallelizing API requests to multiple remotes/nodes.
 pub struct ParallelFetcher<C> {
     max_connections: usize,
@@ -295,7 +307,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
     ) -> FetcherResponse<MultipleNodesResponse<T>>
     where
         A: Iterator<Item = Remote>,
-        F: Fn(C, Remote, String) -> Ft + Clone + Send + 'static,
+        F: Fn(ParallelFetcherArgs<C>) -> Ft + Clone + Send + 'static,
         Ft: Future<Output = Result<T, Error>> + Send + 'static,
         T: Send + Debug + 'static,
     {
@@ -346,7 +358,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
         max_connections_per_remote: usize,
     ) -> RemoteResponse<MultipleNodesResponse<T>>
     where
-        F: Fn(C, Remote, String) -> Ft + Clone + Send + 'static,
+        F: Fn(ParallelFetcherArgs<C>) -> Ft + Clone + Send + 'static,
         Ft: Future<Output = Result<T, Error>> + Send + 'static,
         T: Send + Debug + 'static,
     {
@@ -456,12 +468,19 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
         _per_remote_connections_permit: Option<OwnedSemaphorePermit>,
     ) -> NodeResponse<T>
     where
-        F: Fn(C, Remote, String) -> Ft + Clone + Send + 'static,
+        F: Fn(ParallelFetcherArgs<C>) -> Ft + Clone + Send + 'static,
         Ft: Future<Output = Result<T, Error>> + Send + 'static,
         T: Send + Debug + 'static,
     {
         let now = Instant::now();
-        let result = func(context, remote.clone(), node.clone()).await;
+
+        let parallel_fetcher_context = ParallelFetcherArgs {
+            context,
+            remote,
+            node: node.clone(),
+        };
+
+        let result = func(parallel_fetcher_context).await;
         let api_response_time = now.elapsed();
 
         NodeResponse {
@@ -479,7 +498,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
     ) -> FetcherResponse<NodeResponse<T>>
     where
         A: Iterator<Item = Remote>,
-        F: Fn(C, Remote, String) -> Ft + Clone + Send + 'static,
+        F: Fn(ParallelFetcherArgs<C>) -> Ft + Clone + Send + 'static,
         Ft: Future<Output = Result<T, Error>> + Send + 'static,
         T: Send + Debug + 'static,
     {
diff --git a/server/src/remote_tasks/refresh_task.rs b/server/src/remote_tasks/refresh_task.rs
index 668b63e4..c5d25284 100644
--- a/server/src/remote_tasks/refresh_task.rs
+++ b/server/src/remote_tasks/refresh_task.rs
@@ -11,7 +11,7 @@ use proxmox_section_config::typed::SectionConfigData;
 
 use crate::api;
 use crate::connection;
-use crate::parallel_fetcher::ParallelFetcher;
+use crate::parallel_fetcher::{ParallelFetcher, ParallelFetcherArgs};
 use crate::pbs_client;
 use crate::remote_tasks::{
     KEEP_OLD_FILES, ROTATE_AFTER,
@@ -277,17 +277,16 @@ async fn fetch_remotes(
 }
 
 async fn fetch_tasks_from_single_node(
-    context: Arc<State>,
-    remote: Remote,
-    node: String,
+    args: ParallelFetcherArgs<Arc<State>>,
 ) -> Result<Vec<TaskCacheItem>, Error> {
-    let since = context
-        .cutoff_timestamp(&remote.id, &node)
+    let since = args
+        .context
+        .cutoff_timestamp(&args.remote.id, &args.node)
         .unwrap_or_else(|| {
             proxmox_time::epoch_i64() - (KEEP_OLD_FILES as u64 * ROTATE_AFTER) as i64
         });
 
-    match remote.ty {
+    match args.remote.ty {
         RemoteType::Pve => {
             let params = pve_api_types::ListTasks {
                 source: Some(pve_api_types::ListTasksSource::All),
@@ -297,13 +296,13 @@ async fn fetch_tasks_from_single_node(
                 ..Default::default()
             };
 
-            let client = connection::make_pve_client(&remote)?;
+            let client = connection::make_pve_client(&args.remote)?;
 
             let task_list = client
-                .get_task_list(&node, params)
+                .get_task_list(&args.node, params)
                 .await?
                 .into_iter()
-                .map(|task| map_pve_task(task, remote.id.clone()))
+                .map(|task| map_pve_task(task, args.remote.id.clone()))
                 .collect();
 
             Ok(task_list)
@@ -315,13 +314,13 @@ async fn fetch_tasks_from_single_node(
                 limit: Some(MAX_TASKS_TO_FETCH),
             };
 
-            let client = connection::make_pbs_client(&remote)?;
+            let client = connection::make_pbs_client(&args.remote)?;
 
             let task_list = client
                 .get_task_list(params)
                 .await?
                 .into_iter()
-                .map(|task| map_pbs_task(task, remote.id.clone()))
+                .map(|task| map_pbs_task(task, args.remote.id.clone()))
                 .collect();
 
             Ok(task_list)
diff --git a/server/src/remote_updates.rs b/server/src/remote_updates.rs
index 855d9507..83b5a68f 100644
--- a/server/src/remote_updates.rs
+++ b/server/src/remote_updates.rs
@@ -232,14 +232,15 @@ pub async fn refresh_update_summary_cache(remotes: Vec<Remote>) -> Result<(), Er
     let fetcher = ParallelFetcher::new(());
 
     let fetch_response = fetcher
-        .do_for_all_remote_nodes(remotes.into_iter(), |context, remote, node| async move {
-            let result = fetch_available_updates(context, remote.clone(), node.clone()).await;
+        .do_for_all_remote_nodes(remotes.into_iter(), |args| async move {
+            let result = fetch_available_updates((), args.remote.clone(), args.node.clone()).await;
 
             let summary = match &result {
                 Ok(update_info) => update_info.into(),
                 Err(err) => node_error_summary(err),
             };
-            if let Err(err) = update_cached_summary_for_node(remote, node, summary).await {
+            if let Err(err) = update_cached_summary_for_node(args.remote, args.node, summary).await
+            {
                 log::error!("could not update 'remote-updates' API cache entry: {err}");
             }
 
-- 
2.47.3





  parent reply	other threads:[~2026-08-17 12:58 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17 12:57 [PATCH datacenter-manager/proxmox 00/20] inject application context via API macro for easier integration testing Lukas Wagner
2026-08-17 12:57 ` [PATCH proxmox 01/20] router: introduce shared state Lukas Wagner
2026-08-17 13:26   ` Lukas Wagner
2026-08-20 11:23   ` Lukas Wagner
2026-08-21 13:59   ` Robert Obkircher
2026-08-17 12:57 ` [PATCH proxmox 02/20] rest-server: allow to inject " Lukas Wagner
2026-08-17 12:57 ` [PATCH proxmox 03/20] api-macro: support shared state extraction type Lukas Wagner
2026-08-21 13:59   ` Robert Obkircher
2026-08-17 12:57 ` [PATCH datacenter-manager 04/20] context: promote context to a dir-style module Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 05/20] pdm-config: remotes: rename trait methods to read/write/lock Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 06/20] pdm-config: subscriptions: " Lukas Wagner
2026-08-21 14:00   ` Robert Obkircher
2026-08-17 12:57 ` [PATCH datacenter-manager 07/20] remote iterator: pass remote config reader explicitly Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 08/20] context: introduce a ContextFactory to build application context Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 09/20] context: establish PdmApplication object Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 10/20] context: register PdmApplication in router Lukas Wagner
2026-08-17 12:57 ` Lukas Wagner [this message]
2026-08-21 14:00   ` [PATCH datacenter-manager 11/20] parallel fetcher: pass arguments to closure in a single type Robert Obkircher
2026-08-17 12:57 ` [PATCH datacenter-manager 12/20] parallel fetcher: support a custom client factory Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 13/20] api: sdn: use PdmApplication handle for accessing remotes Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 14/20] tests: add helpers for building API-handler-level integration tests Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 15/20] tests: add example tests for SDN API routes Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 16/20] api-cache: add wrapper type Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 17/20] context: provide api-cache on the app object Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 18/20] api: subscriptions: use PdmApplication instead of globals Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 19/20] pdm-config: subscriptions: drop unused accessor functions Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 20/20] tests: add example tests for remote subscription management Lukas Wagner
2026-08-20 14:54 ` superseded: [PATCH datacenter-manager/proxmox 00/20] inject application context via API macro for easier integration testing Lukas Wagner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260817125727.454039-12-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal