From: Lukas Wagner <l.wagner@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH datacenter-manager v3 2/4] add api_cache as a specialized wrapper around the namespaced cache
Date: Fri, 15 May 2026 16:49:25 +0200 [thread overview]
Message-ID: <20260515144927.427114-3-l.wagner@proxmox.com> (raw)
In-Reply-To: <20260515144927.427114-1-l.wagner@proxmox.com>
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-<remote-name>'.
The base directory for the cache is
/run/proxmox-datacenter-manager/api-cache
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
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<CacheableData> = 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<NamespacedCache> = 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<BlockingReadableCacheNamespace, CacheError> {
+ 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<BlockingWritableCacheNamespace, CacheError> {
+ CACHE.write_blocking(&format_remote_namespace(remote), LOCK_TIMEOUT)
+}
+
+/// Lock the cache for reading global data (blocking interface).
+pub fn read_global_blocking() -> Result<BlockingReadableCacheNamespace, CacheError> {
+ CACHE.read_blocking(GLOBAL_NAMESPACE, LOCK_TIMEOUT)
+}
+
+/// Lock the cache for writing global data (blocking interface).
+pub fn write_global_blocking() -> Result<BlockingWritableCacheNamespace, CacheError> {
+ 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<ReadableCacheNamespace, CacheError> {
+ 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<WritableCacheNamespace, CacheError> {
+ CACHE
+ .write(&format_remote_namespace(remote), LOCK_TIMEOUT)
+ .await
+}
+
+/// Lock the cache for reading global data (async interface).
+pub async fn read_global() -> Result<ReadableCacheNamespace, CacheError> {
+ CACHE.read(GLOBAL_NAMESPACE, LOCK_TIMEOUT).await
+}
+
+/// Lock the cache for writing global data (async interface).
+pub async fn write_global() -> Result<WritableCacheNamespace, CacheError> {
+ 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..2f73167c 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,
+ 0o750,
+ )?;
+
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
next prev parent reply other threads:[~2026-05-15 14:50 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-15 14:49 [PATCH datacenter-manager v3 0/4] add generic, per-remote (and global) cache for remote API responses Lukas Wagner
2026-05-15 14:49 ` [PATCH datacenter-manager v3 1/4] add persistent, generic, namespaced key-value cache implementation Lukas Wagner
2026-05-15 14:49 ` Lukas Wagner [this message]
2026-05-15 14:49 ` [PATCH datacenter-manager v3 3/4] api: resources: subscriptions: switch over to api_cache Lukas Wagner
2026-05-15 14:49 ` [PATCH datacenter-manager v3 4/4] remote-updates: switch over to new api_cache 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=20260515144927.427114-3-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.