From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id A475A1FF09B for ; Mon, 17 Aug 2026 14:58:05 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 8705421779; Mon, 17 Aug 2026 14:57:55 +0200 (CEST) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager 16/20] api-cache: add wrapper type Date: Mon, 17 Aug 2026 14:57:23 +0200 Message-ID: <20260817125727.454039-17-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260817125727.454039-1-l.wagner@proxmox.com> References: <20260817125727.454039-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1786971437058 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.905 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: YEMF6GPKIFRXXWGUC2533KIFN3DQWCJ2 X-Message-ID-Hash: YEMF6GPKIFRXXWGUC2533KIFN3DQWCJ2 X-MailFrom: l.wagner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Wrap the underlying NamespacedCache in a new ApiCache type that exposes the same read/write helpers as methods. The global CACHE static and its free functions remain, just implemented as thin wrappers around the new type, so this only prepares ApiCache to be owned by PdmApplication in the next commit. Signed-off-by: Lukas Wagner --- server/src/api_cache.rs | 98 +++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/server/src/api_cache.rs b/server/src/api_cache.rs index b20f0535..6dc8a043 100644 --- a/server/src/api_cache.rs +++ b/server/src/api_cache.rs @@ -59,6 +59,8 @@ use std::time::Duration; use nix::sys::stat::Mode; +use proxmox_sys::fs::CreateOptions; + use crate::namespaced_cache::{ BlockingReadableCacheNamespace, BlockingWritableCacheNamespace, CacheError, NamespacedCache, ReadableCacheNamespace, WritableCacheNamespace, @@ -70,57 +72,125 @@ pub const PDM_API_CACHE_PATH: &str = concat!(pdm_buildcfg::PDM_RUN_DIR_M!(), "/a const GLOBAL_NAMESPACE: &str = "global"; const LOCK_TIMEOUT: Duration = Duration::from_secs(10); -static CACHE: LazyLock = LazyLock::new(|| { +static CACHE: LazyLock = LazyLock::new(|| { let file_options = proxmox_product_config::default_create_options(); let dir_options = file_options.perm(Mode::from_bits_truncate(0o750)); - NamespacedCache::new(PathBuf::from(PDM_API_CACHE_PATH), dir_options, file_options) + ApiCache::new(PathBuf::from(PDM_API_CACHE_PATH), dir_options, file_options) }); fn format_remote_namespace(remote: &str) -> String { format!("remote-{remote}") } +/// Cache for API responses from remotes. +/// +/// Thin wrapper around a [`NamespacedCache`], providing remote-specific and +/// global namespaces as described in the module documentation. +pub struct ApiCache { + cache: NamespacedCache, +} + +impl ApiCache { + pub fn new>( + base_directory: P, + dir_options: CreateOptions, + file_options: CreateOptions, + ) -> Self { + Self { + cache: NamespacedCache::new(base_directory, dir_options, file_options), + } + } + + /// Lock the cache for reading remote-specific data (blocking interface). + pub fn read_remote_blocking( + &self, + remote: &str, + ) -> Result { + self.cache + .read_blocking(&format_remote_namespace(remote), LOCK_TIMEOUT) + } + + /// Lock the cache for writing remote-specific data (blocking interface). + pub fn write_remote_blocking( + &self, + remote: &str, + ) -> Result { + self.cache + .write_blocking(&format_remote_namespace(remote), LOCK_TIMEOUT) + } + + /// Lock the cache for reading global data (blocking interface). + pub fn read_global_blocking(&self) -> Result { + self.cache.read_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT) + } + + /// Lock the cache for writing global data (blocking interface). + pub fn write_global_blocking(&self) -> Result { + self.cache.write_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT) + } + + /// Lock the cache for reading remote-specific data (async interface). + pub async fn read_remote(&self, remote: &str) -> Result { + self.cache + .read(&format_remote_namespace(remote), LOCK_TIMEOUT) + .await + } + + /// Lock the cache for writing remote-specific data (async interface). + pub async fn write_remote(&self, remote: &str) -> Result { + self.cache + .write(&format_remote_namespace(remote), LOCK_TIMEOUT) + .await + } + + /// Lock the cache for reading global data (async interface). + pub async fn read_global(&self) -> Result { + self.cache.read(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await + } + + /// Lock the cache for writing global data (async interface). + pub async fn write_global(&self) -> Result { + self.cache.write(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await + } +} + /// Lock the cache for reading remote-specific data (blocking interface). pub fn read_remote_blocking(remote: &str) -> Result { - CACHE.read_blocking(&format_remote_namespace(remote), LOCK_TIMEOUT) + CACHE.read_remote_blocking(remote) } /// Lock the cache for writing remote-specific data (blocking interface). pub fn write_remote_blocking(remote: &str) -> Result { - CACHE.write_blocking(&format_remote_namespace(remote), LOCK_TIMEOUT) + CACHE.write_remote_blocking(remote) } /// Lock the cache for reading global data (blocking interface). pub fn read_global_blocking() -> Result { - CACHE.read_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT) + CACHE.read_global_blocking() } /// Lock the cache for writing global data (blocking interface). pub fn write_global_blocking() -> Result { - CACHE.write_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT) + CACHE.write_global_blocking() } /// Lock the cache for reading remote-specific data (async interface). pub async fn read_remote(remote: &str) -> Result { - CACHE - .read(&format_remote_namespace(remote), LOCK_TIMEOUT) - .await + CACHE.read_remote(remote).await } /// Lock the cache for writing remote-specific data (async interface). pub async fn write_remote(remote: &str) -> Result { - CACHE - .write(&format_remote_namespace(remote), LOCK_TIMEOUT) - .await + CACHE.write_remote(remote).await } /// Lock the cache for reading global data (async interface). pub async fn read_global() -> Result { - CACHE.read(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await + CACHE.read_global().await } /// Lock the cache for writing global data (async interface). pub async fn write_global() -> Result { - CACHE.write(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await + CACHE.write_global().await } -- 2.47.3