[pdm-devel] [PATCH proxmox-datacenter-manager 03/15] task cache: add basic test for TaskCache
Lukas Wagner
l.wagner at proxmox.com
Tue Jan 28 13:25:08 CET 2025
This one helps us to verify that we did not break the core functionality
in the upcoming changes.
Signed-off-by: Lukas Wagner <l.wagner at proxmox.com>
---
server/src/task_cache.rs | 100 +++++++++++++++++++++++++++++++--------
1 file changed, 79 insertions(+), 21 deletions(-)
diff --git a/server/src/task_cache.rs b/server/src/task_cache.rs
index 7ded540..211beb4 100644
--- a/server/src/task_cache.rs
+++ b/server/src/task_cache.rs
@@ -25,8 +25,13 @@ pub async fn get_tasks(max_age: i64, filters: TaskFilters) -> Result<Vec<TaskLis
let mut all_tasks = Vec::new();
+ let api_uid = pdm_config::api_user()?.uid;
+ let api_gid = pdm_config::api_group()?.gid;
+
+ let file_options = CreateOptions::new().owner(api_uid).group(api_gid);
+
let cache_path = Path::new(pdm_buildcfg::PDM_CACHE_DIR).join("taskcache.json");
- let mut cache = TaskCache::new(cache_path)?;
+ let mut cache = TaskCache::new(cache_path, file_options)?;
// Force a refresh for all tasks of a remote if a task is finished.
// Not super nice, but saves us from persisting finished tasks. Also,
@@ -246,27 +251,30 @@ struct TaskCache {
/// File-location at which the cached tasks are stored.
cachefile_path: PathBuf,
+
+ /// File mode/owner/group for the cache file.
+ cachefile_options: CreateOptions,
}
impl TaskCache {
/// Create a new tasks cache instance by loading
/// the cache from disk.
- fn new(cachefile_path: PathBuf) -> Result<Self, Error> {
+ fn new(cachefile_path: PathBuf, cachefile_options: CreateOptions) -> Result<Self, Error> {
Ok(Self {
- content: Self::load_content()?,
+ content: Self::load_content(&cachefile_path)?,
new_or_updated: Default::default(),
dirty: false,
cachefile_path,
+ cachefile_options,
})
}
/// Load the task cache contents from disk.
- fn load_content() -> Result<TaskCacheContent, Error> {
- let taskcache_path = Path::new(pdm_buildcfg::PDM_CACHE_DIR).join("taskcache.json");
- let content = proxmox_sys::fs::file_read_optional_string(taskcache_path)?;
+ fn load_content(path: &Path) -> Result<TaskCacheContent, Error> {
+ let content = proxmox_sys::fs::file_read_optional_string(path)?;
let content = if let Some(content) = content {
- serde_json::from_str(&content)?
+ serde_json::from_str(&content).unwrap_or_default()
} else {
Default::default()
};
@@ -293,7 +301,7 @@ impl TaskCache {
let _guard = self.lock(Duration::from_secs(5))?;
// Read content again, in case somebody has changed it in the meanwhile
- let mut content = Self::load_content()?;
+ let mut content = Self::load_content(&self.cachefile_path)?;
for (remote_name, entry) in self.new_or_updated.remote_tasks.drain() {
if let Some(existing_entry) = content.remote_tasks.get_mut(&remote_name) {
@@ -308,12 +316,12 @@ impl TaskCache {
let bytes = serde_json::to_vec_pretty(&content)?;
- let api_uid = pdm_config::api_user()?.uid;
- let api_gid = pdm_config::api_group()?.gid;
-
- let file_options = CreateOptions::new().owner(api_uid).group(api_gid);
-
- proxmox_sys::fs::replace_file(&self.cachefile_path, &bytes, file_options, true)?;
+ proxmox_sys::fs::replace_file(
+ &self.cachefile_path,
+ &bytes,
+ self.cachefile_options.clone(),
+ true,
+ )?;
self.dirty = false;
@@ -358,22 +366,23 @@ impl TaskCache {
// without a lock, since the cache file is replaced atomically
// when updating.
fn lock(&self, duration: Duration) -> Result<File, Error> {
- let api_uid = pdm_config::api_user()?.uid;
- let api_gid = pdm_config::api_group()?.gid;
-
- let file_options = CreateOptions::new().owner(api_uid).group(api_gid);
- proxmox_sys::fs::open_file_locked(self.lockfile_path(), duration, true, file_options)
+ proxmox_sys::fs::open_file_locked(
+ self.lockfile_path(),
+ duration,
+ true,
+ self.cachefile_options.clone(),
+ )
}
}
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
/// Per-remote entry in the task cache.
struct TaskCacheEntry {
timestamp: i64,
tasks: Vec<TaskListItem>,
}
-#[derive(Default, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize)]
/// Content of the task cache file.
struct TaskCacheContent {
remote_tasks: HashMap<String, TaskCacheEntry>,
@@ -522,3 +531,52 @@ pub fn tasktype(status: &str) -> TaskStateType {
TaskStateType::Error
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_support::temp::NamedTempFile;
+
+ #[test]
+ fn basic_task_cache() -> Result<(), Error> {
+ let options = CreateOptions::new()
+ .owner(nix::unistd::Uid::effective())
+ .group(nix::unistd::Gid::effective())
+ .perm(nix::sys::stat::Mode::from_bits_truncate(0o600));
+
+ let temp_file = NamedTempFile::new(options.clone())?;
+
+ let mut cache = TaskCache::new(temp_file.path().into(), options.clone())?;
+
+ let mut tasks = Vec::new();
+
+ let now = proxmox_time::epoch_i64();
+ for i in (0..20).rev() {
+ let upid: PveUpid =
+ "UPID:pve-node:0000C530:001C9BEC:677E934A:stopall::root at pam:".parse()?;
+
+ tasks.push(TaskListItem {
+ upid: upid.to_string(),
+ node: upid.node,
+ pid: upid.pid as i64,
+ pstart: upid.pstart,
+ starttime: now - 10 * i,
+ worker_type: upid.worker_type,
+ worker_id: upid.worker_id,
+ user: upid.auth_id,
+ endtime: None,
+ status: None,
+ });
+ }
+
+ cache.set_tasks("some-remote", tasks.clone(), now);
+ cache.save()?;
+
+ let cache = TaskCache::new(temp_file.path().into(), options)?;
+
+ let res = cache.get_tasks("some-remote", now, 10000).unwrap();
+ assert_eq!(tasks, res);
+
+ Ok(())
+ }
+}
--
2.39.5
More information about the pdm-devel
mailing list