From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id DFD301FF14C for ; Fri, 15 May 2026 10:29:05 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7DFC2FE2E; Fri, 15 May 2026 10:29:02 +0200 (CEST) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager v2 2/4] add api_cache as a specialized wrapper around the namespaced cache Date: Fri, 15 May 2026 10:28:53 +0200 Message-ID: <20260515082855.85698-3-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260515082855.85698-1-l.wagner@proxmox.com> References: <20260515082855.85698-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: 1778833730670 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.054 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment 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: CS2ZLY6EPX25CPCOLAFDGVV4AS5PX4OW X-Message-ID-Hash: CS2ZLY6EPX25CPCOLAFDGVV4AS5PX4OW 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: This is a thin wrapper around the previously introduced namespaced key-value cache, but introducing PDM-specific concepts. Instead of the higher-level read/write methods for locking a namespace, this wrapper provides {read,write}_remote and {read,write}_global, for accessing remote-specific and globally cached values. The cache-namespaces are 'global' and 'remote-'. The base directory for the cache is /run/proxmox-datacenter-manager/api-cache Signed-off-by: Lukas Wagner --- Notes: Changes since the RFC: - Changed the cache location to /run/proxmox-datacenter-manager/api-cache - Renamed the module from pdm_cache to api_cache - removed the instance() accessor and use freestanding functions instead. While the instance-based approach might come in useful at a later stage when dependency injection is more widely used in our stack, it's a premature step that can be reintroduced later without big troubles - Some minor code cleanup server/src/api_cache.rs | 126 ++++++++++++++++++ .../bin/proxmox-datacenter-privileged-api.rs | 9 +- server/src/lib.rs | 1 + 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 server/src/api_cache.rs diff --git a/server/src/api_cache.rs b/server/src/api_cache.rs new file mode 100644 index 00000000..b20f0535 --- /dev/null +++ b/server/src/api_cache.rs @@ -0,0 +1,126 @@ +//! Cache for API responses from remotes. +//! +//! This cache is namespaced by remote and also offers a 'global' namespace +//! for values that are valid across remotes (e.g. aggregations). +//! +//! The cache has both, a blocking, as well as an async interface that can be used to get or set +//! cache entries. +//! +//! A namespace (so, either the remote one's or the global one) must be locked before it can be +//! accessed. All locking functions use a 10 second timeout while waiting for the lock. +//! +//! ## Blocking interface +//! - [`read_remote_blocking`] +//! - [`write_remote_blocking`] +//! - [`read_global_blocking`] +//! - [`write_global_blocking`] +//! +//! These functions return [`BlockingReadableCacheNamespace`] and [`BlockingWritableCacheNamespace`], respectively. +//! Both only offer blocking operations for interacting with the entries of the locked namespace. +//! +//! ## `async` interface +//! - [`read_remote`] +//! - [`write_remote`] +//! - [`read_global`] +//! - [`write_global`] +//! +//! These functions return [`ReadableCacheNamespace`] and [`WritableCacheNamespace`], respectively. +//! Both offer an async wrapper for interacting with the entries of the locked namespace. +//! +//! ```no_run +//! use server::api_cache; +//! +//! #[derive(serde::Serialize, serde::Deserialize)] +//! struct CacheableData { +//! id: String, +//! } +//! +//! let data = CacheableData { +//! id: "some-id".to_string(), +//! }; +//! +//! // Lock the cache namespace for 'some-remote' for write access +//! let lock = api_cache::write_remote_blocking("some-remote").unwrap(); +//! +//! // Set some value (must be Serialize + Deserialize) +//! lock.set("some-key", data).unwrap(); +//! +//! // Retrieve the cached entry +//! let data: Option = lock.get("some-key").unwrap(); +//! +//! // Remove the cached entry +//! lock.remove("some-key").unwrap(); +//! +//! ``` + +use std::path::PathBuf; +use std::sync::LazyLock; +use std::time::Duration; + +use nix::sys::stat::Mode; + +use crate::namespaced_cache::{ + BlockingReadableCacheNamespace, BlockingWritableCacheNamespace, CacheError, NamespacedCache, + ReadableCacheNamespace, WritableCacheNamespace, +}; + +/// Path at which API responses are cached. +pub const PDM_API_CACHE_PATH: &str = concat!(pdm_buildcfg::PDM_RUN_DIR_M!(), "/api-cache"); + +const GLOBAL_NAMESPACE: &str = "global"; +const LOCK_TIMEOUT: Duration = Duration::from_secs(10); + +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) +}); + +fn format_remote_namespace(remote: &str) -> String { + format!("remote-{remote}") +} + +/// 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) +} + +/// 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) +} + +/// Lock the cache for reading global data (blocking interface). +pub fn read_global_blocking() -> Result { + CACHE.read_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT) +} + +/// Lock the cache for writing global data (blocking interface). +pub fn write_global_blocking() -> Result { + CACHE.write_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT) +} + +/// 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 +} + +/// 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 +} + +/// Lock the cache for reading global data (async interface). +pub async fn read_global() -> Result { + CACHE.read(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await +} + +/// Lock the cache for writing global data (async interface). +pub async fn write_global() -> Result { + CACHE.write(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await +} diff --git a/server/src/bin/proxmox-datacenter-privileged-api.rs b/server/src/bin/proxmox-datacenter-privileged-api.rs index 6b490f2b..a3c448cf 100644 --- a/server/src/bin/proxmox-datacenter-privileged-api.rs +++ b/server/src/bin/proxmox-datacenter-privileged-api.rs @@ -14,7 +14,7 @@ use proxmox_rest_server::{ApiConfig, RestServer}; use proxmox_router::RpcEnvironmentType; use proxmox_sys::fs::CreateOptions; -use server::auth; +use server::{api_cache, auth}; use pdm_buildcfg::configdir; @@ -102,6 +102,13 @@ fn create_directories() -> Result<(), Error> { 0o755, )?; + pdm_config::setup::mkdir_perms( + api_cache::PDM_API_CACHE_PATH, + api_user.uid, + api_user.gid, + 0o755, + )?; + server::jobstate::create_jobstate_dir()?; Ok(()) diff --git a/server/src/lib.rs b/server/src/lib.rs index 0b7642ab..89ab3035 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -2,6 +2,7 @@ pub mod acl; pub mod api; +pub mod api_cache; pub mod auth; pub mod context; pub mod env; -- 2.47.3