From 3bdac66c238cc1d3dcf3923a363cc8c1feec6121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20J=C4=99drzejewski?= Date: Mon, 29 Sep 2025 13:50:06 +0000 Subject: [PATCH] impl: backends independent from filesystems - New `KvsBackend` API - independent from filesystem. - Make `JsonBackend` explicitly default, allow others. - Add `JsonBackendBuilder`. - This is required to allow for defaults override without setters. - E.g., `snapshot_max_count` can be set, but shouldn't change after init. - Update tests. --- src/rust/rust_kvs/examples/basic.rs | 9 +- src/rust/rust_kvs/examples/defaults.rs | 7 +- src/rust/rust_kvs/examples/snapshots.rs | 9 +- src/rust/rust_kvs/src/json_backend.rs | 531 +++++++++++++--- src/rust/rust_kvs/src/kvs.rs | 574 +++++++----------- src/rust/rust_kvs/src/kvs_api.rs | 11 +- src/rust/rust_kvs/src/kvs_backend.rs | 74 ++- src/rust/rust_kvs/src/kvs_builder.rs | 419 +++++++------ src/rust/rust_kvs/src/kvs_mock.rs | 14 - src/rust/rust_kvs/src/lib.rs | 15 +- src/rust/rust_kvs_tool/src/kvs_tool.rs | 39 +- .../tests/test_cit_snapshots.py | 13 +- .../src/cit/default_values.rs | 9 +- .../src/cit/multiple_kvs.rs | 3 +- .../src/cit/persistency.rs | 13 +- .../rust_test_scenarios/src/cit/snapshots.rs | 14 +- .../src/helpers/kvs_instance.rs | 27 +- tests/rust_test_scenarios/src/helpers/mod.rs | 20 + tests/rust_test_scenarios/src/test_basic.rs | 15 +- 19 files changed, 1063 insertions(+), 753 deletions(-) diff --git a/src/rust/rust_kvs/examples/basic.rs b/src/rust/rust_kvs/examples/basic.rs index 0f37b8c..abe619f 100644 --- a/src/rust/rust_kvs/examples/basic.rs +++ b/src/rust/rust_kvs/examples/basic.rs @@ -8,9 +8,10 @@ use std::collections::HashMap; use tempfile::tempdir; fn main() -> Result<(), ErrorCode> { - // Temporary directory. + // Temporary directory and common backend. let dir = tempdir()?; - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); + let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); // Instance ID for KVS object instances. let instance_id = InstanceId(0); @@ -20,7 +21,7 @@ fn main() -> Result<(), ErrorCode> { // `kvs_load` is explicitly set to `KvsLoad::Optional`, but this is the default value. // KVS files are not required. let builder = KvsBuilder::new(instance_id) - .dir(dir_string.clone()) + .backend(backend.clone()) .kvs_load(KvsLoad::Optional); let kvs = builder.build()?; @@ -65,7 +66,7 @@ fn main() -> Result<(), ErrorCode> { // Build KVS instance for given instance ID and temporary directory. // `kvs_load` is set to `KvsLoad::Required` - KVS files must already exist from previous KVS instance. let builder = KvsBuilder::new(instance_id) - .dir(dir_string) + .backend(backend) .kvs_load(KvsLoad::Required); let kvs = builder.build()?; diff --git a/src/rust/rust_kvs/examples/defaults.rs b/src/rust/rust_kvs/examples/defaults.rs index f02b351..0c7ce5e 100644 --- a/src/rust/rust_kvs/examples/defaults.rs +++ b/src/rust/rust_kvs/examples/defaults.rs @@ -31,9 +31,10 @@ fn create_defaults_file(dir_path: PathBuf, instance_id: InstanceId) -> Result<() } fn main() -> Result<(), ErrorCode> { - // Temporary directory. + // Temporary directory and common backend. let dir = tempdir()?; - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); + let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); // Instance ID for KVS object instances. let instance_id = InstanceId(0); @@ -44,7 +45,7 @@ fn main() -> Result<(), ErrorCode> { // Build KVS instance for given instance ID and temporary directory. // `defaults` is set to `KvsDefaults::Required` - defaults are required. let builder = KvsBuilder::new(instance_id) - .dir(dir_string) + .backend(backend.clone()) .defaults(KvsDefaults::Required); let kvs = builder.build()?; diff --git a/src/rust/rust_kvs/examples/snapshots.rs b/src/rust/rust_kvs/examples/snapshots.rs index 2e68ba9..fd00450 100644 --- a/src/rust/rust_kvs/examples/snapshots.rs +++ b/src/rust/rust_kvs/examples/snapshots.rs @@ -6,9 +6,10 @@ use rust_kvs::prelude::*; use tempfile::tempdir; fn main() -> Result<(), ErrorCode> { - // Temporary directory. + // Temporary directory and common backend. let dir = tempdir()?; - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); + let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); // Instance ID for KVS object instances. let instance_id = InstanceId(0); @@ -17,7 +18,7 @@ fn main() -> Result<(), ErrorCode> { println!("-> `snapshot_count` and `snapshot_max_count` usage"); // Build KVS instance for given instance ID and temporary directory. - let builder = KvsBuilder::new(instance_id).dir(dir_string.clone()); + let builder = KvsBuilder::new(instance_id).backend(backend.clone()); let kvs = builder.build()?; let max_count = kvs.snapshot_max_count() as u32; @@ -38,7 +39,7 @@ fn main() -> Result<(), ErrorCode> { println!("-> `snapshot_restore` usage"); // Build KVS instance for given instance ID and temporary directory. - let builder = KvsBuilder::new(instance_id).dir(dir_string.clone()); + let builder = KvsBuilder::new(instance_id).backend(backend.clone()); let kvs = builder.build()?; let max_count = kvs.snapshot_max_count() as u32; diff --git a/src/rust/rust_kvs/src/json_backend.rs b/src/rust/rust_kvs/src/json_backend.rs index ba51b4b..9d85cbe 100644 --- a/src/rust/rust_kvs/src/json_backend.rs +++ b/src/rust/rust_kvs/src/json_backend.rs @@ -11,7 +11,7 @@ use crate::error_code::ErrorCode; use crate::kvs_api::{InstanceId, SnapshotId}; -use crate::kvs_backend::{KvsBackend, KvsPathResolver}; +use crate::kvs_backend::KvsBackend; use crate::kvs_value::{KvsMap, KvsValue}; use std::collections::HashMap; use std::fs; @@ -150,8 +150,50 @@ impl From for ErrorCode { } } +/// Builder for `JsonBackend`. +pub struct JsonBackendBuilder { + working_dir: PathBuf, + snapshot_max_count: usize, +} + +impl JsonBackendBuilder { + pub fn new() -> Self { + Self { + working_dir: PathBuf::new(), + snapshot_max_count: 3, + } + } + + pub fn working_dir(mut self, working_dir: PathBuf) -> Self { + self.working_dir = working_dir; + self + } + + pub fn snapshot_max_count(mut self, snapshot_max_count: usize) -> Self { + self.snapshot_max_count = snapshot_max_count; + self + } + + pub fn build(self) -> JsonBackend { + JsonBackend { + working_dir: self.working_dir, + snapshot_max_count: self.snapshot_max_count, + } + } +} + +impl Default for JsonBackendBuilder { + fn default() -> Self { + Self::new() + } +} + /// KVS backend implementation based on TinyJSON. -pub struct JsonBackend; +#[derive(Clone, PartialEq)] +pub struct JsonBackend { + working_dir: PathBuf, + snapshot_max_count: usize, +} impl JsonBackend { fn parse(s: &str) -> Result { @@ -162,15 +204,58 @@ impl JsonBackend { val.stringify().map_err(ErrorCode::from) } + /// Rotate snapshots + /// + /// # Features + /// * `FEAT_REQ__KVS__snapshots` + /// + /// # Return Values + /// * Ok: Rotation successful, also if no rotation was needed + /// * `ErrorCode::UnmappedError`: Unmapped error + fn snapshot_rotate(&self, instance_id: InstanceId) -> Result<(), ErrorCode> { + for idx in (1..self.snapshot_max_count()).rev() { + let old_snapshot_id = SnapshotId(idx - 1); + let new_snapshot_id = SnapshotId(idx); + + let hash_path_old = self.hash_file_path(instance_id, old_snapshot_id); + let hash_path_new = self.hash_file_path(instance_id, new_snapshot_id); + let snap_name_old = Self::kvs_file_name(instance_id, old_snapshot_id); + let snap_path_old = self.kvs_file_path(instance_id, old_snapshot_id); + let snap_name_new = Self::kvs_file_name(instance_id, new_snapshot_id); + let snap_path_new = self.kvs_file_path(instance_id, new_snapshot_id); + + println!("rotating: {snap_name_old} -> {snap_name_new}"); + + // Check snapshot and hash files exist. + let snap_old_exists = snap_path_old.exists(); + let hash_old_exists = hash_path_old.exists(); + + // If both exist - rename them. + if snap_old_exists && hash_old_exists { + fs::rename(hash_path_old, hash_path_new)?; + fs::rename(snap_path_old, snap_path_new)?; + } + // If neither exist - continue. + else if !snap_old_exists && !hash_old_exists { + continue; + } + // In other case - this is erroneous scenario. + // Either snapshot or hash file got removed. + else { + return Err(ErrorCode::IntegrityCorrupted); + } + } + + Ok(()) + } + /// Check path have correct extension. fn check_extension(path: &Path, extension: &str) -> bool { let ext = path.extension(); ext.is_some_and(|ep| ep.to_str().is_some_and(|es| es == extension)) } -} -impl KvsBackend for JsonBackend { - fn load_kvs(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result { + pub(super) fn load(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result { if !Self::check_extension(kvs_path, "json") { return Err(ErrorCode::KvsFileReadError); } @@ -214,7 +299,7 @@ impl KvsBackend for JsonBackend { } } - fn save_kvs( + pub(super) fn save( kvs_map: &KvsMap, kvs_path: &Path, hash_path: Option<&PathBuf>, @@ -238,45 +323,113 @@ impl KvsBackend for JsonBackend { // Generate hash and save to hash file. if let Some(hash_path) = hash_path { let hash = adler32::RollingAdler32::from_buffer(json_str.as_bytes()).hash(); - fs::write(hash_path, hash.to_be_bytes())? + fs::write(hash_path, hash.to_be_bytes())?; } Ok(()) } -} -/// KVS backend path resolver for `JsonBackend`. -impl KvsPathResolver for JsonBackend { - fn kvs_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String { + /// Get KVS file name. + pub fn kvs_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String { format!("kvs_{instance_id}_{snapshot_id}.json") } - fn kvs_file_path( - working_dir: &Path, - instance_id: InstanceId, - snapshot_id: SnapshotId, - ) -> PathBuf { - working_dir.join(Self::kvs_file_name(instance_id, snapshot_id)) + /// Get KVS file path in working directory. + pub fn kvs_file_path(&self, instance_id: InstanceId, snapshot_id: SnapshotId) -> PathBuf { + self.working_dir + .join(Self::kvs_file_name(instance_id, snapshot_id)) } - fn hash_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String { + /// Get hash file name. + pub fn hash_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String { format!("kvs_{instance_id}_{snapshot_id}.hash") } - fn hash_file_path( - working_dir: &Path, + /// Get hash file path in working directory. + pub fn hash_file_path(&self, instance_id: InstanceId, snapshot_id: SnapshotId) -> PathBuf { + self.working_dir + .join(Self::hash_file_name(instance_id, snapshot_id)) + } + + /// Get defaults file name. + pub fn defaults_file_name(instance_id: InstanceId) -> String { + format!("kvs_{instance_id}_default.json") + } + + /// Get defaults file path in working directory. + pub fn defaults_file_path(&self, instance_id: InstanceId) -> PathBuf { + self.working_dir.join(Self::defaults_file_name(instance_id)) + } +} + +impl KvsBackend for JsonBackend { + fn load_kvs( + &self, instance_id: InstanceId, snapshot_id: SnapshotId, - ) -> PathBuf { - working_dir.join(Self::hash_file_name(instance_id, snapshot_id)) + ) -> Result { + let kvs_path = self.kvs_file_path(instance_id, snapshot_id); + let hash_path = self.hash_file_path(instance_id, snapshot_id); + Self::load(&kvs_path, Some(&hash_path)) + } + + fn load_defaults(&self, instance_id: InstanceId) -> Result { + let defaults_path = self.defaults_file_path(instance_id); + Self::load(&defaults_path, None) + } + + fn flush(&self, instance_id: InstanceId, kvs_map: &KvsMap) -> Result<(), ErrorCode> { + self.snapshot_rotate(instance_id).map_err(|e| { + eprintln!("error: snapshot_rotate failed: {e:?}"); + e + })?; + let snapshot_id = SnapshotId(0); + let kvs_path = self.kvs_file_path(instance_id, snapshot_id); + let hash_path = self.hash_file_path(instance_id, snapshot_id); + Self::save(kvs_map, &kvs_path, Some(&hash_path)).map_err(|e| { + eprintln!("error: save failed: {e:?}"); + e + })?; + Ok(()) } - fn defaults_file_name(instance_id: InstanceId) -> String { - format!("kvs_{instance_id}_default.json") + fn snapshot_count(&self, instance_id: InstanceId) -> usize { + let mut count = 0; + + for idx in 0..self.snapshot_max_count { + let snapshot_id = SnapshotId(idx); + let snapshot_path = self.kvs_file_path(instance_id, snapshot_id); + if !snapshot_path.exists() { + break; + } + + count += 1; + } + + count } - fn defaults_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf { - working_dir.join(Self::defaults_file_name(instance_id)) + fn snapshot_max_count(&self) -> usize { + self.snapshot_max_count + } + + fn snapshot_restore( + &self, + instance_id: InstanceId, + snapshot_id: SnapshotId, + ) -> Result { + // fail if the snapshot ID is the current KVS + if snapshot_id == SnapshotId(0) { + eprintln!("error: tried to restore current KVS as snapshot"); + return Err(ErrorCode::InvalidSnapshotId); + } + + if self.snapshot_count(instance_id) < snapshot_id.0 { + eprintln!("error: tried to restore a non-existing snapshot"); + return Err(ErrorCode::InvalidSnapshotId); + } + + self.load_kvs(instance_id, snapshot_id) } } @@ -720,10 +873,10 @@ mod error_code_tests { } #[cfg(test)] -mod backend_tests { +mod json_backend_tests { use crate::error_code::ErrorCode; - use crate::json_backend::JsonBackend; - use crate::kvs_backend::KvsBackend; + use crate::json_backend::{JsonBackend, JsonBackendBuilder}; + use crate::kvs_api::{InstanceId, SnapshotId}; use crate::kvs_value::{KvsMap, KvsValue}; use std::path::{Path, PathBuf}; use tempfile::tempdir; @@ -736,121 +889,115 @@ mod backend_tests { ]); let kvs_path = working_dir.join("kvs.json"); let hash_path = working_dir.join("kvs.hash"); - JsonBackend::save_kvs(&kvs_map, &kvs_path, Some(&hash_path)).unwrap(); + JsonBackend::save(&kvs_map, &kvs_path, Some(&hash_path)).unwrap(); (kvs_path, hash_path) } #[test] - fn test_load_kvs_ok() { + fn test_load_ok() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let (kvs_path, _hash_path) = create_kvs_files(&dir_path); - let kvs_map = JsonBackend::load_kvs(&kvs_path, None).unwrap(); + let kvs_map = JsonBackend::load(&kvs_path, None).unwrap(); assert_eq!(kvs_map.len(), 3); } #[test] - fn test_load_kvs_not_found() { + fn test_load_not_found() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_path = dir_path.join("kvs.json"); - assert!(JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::FileNotFound)); + assert!(JsonBackend::load(&kvs_path, None).is_err_and(|e| e == ErrorCode::FileNotFound)); } #[test] - fn test_load_kvs_invalid_extension() { + fn test_load_invalid_extension() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_path = dir_path.join("kvs.invalid_ext"); - assert!( - JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::KvsFileReadError) - ); + assert!(JsonBackend::load(&kvs_path, None).is_err_and(|e| e == ErrorCode::KvsFileReadError)); } #[test] - fn test_load_kvs_malformed_json() { + fn test_load_malformed_json() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_path = dir_path.join("kvs.json"); std::fs::write(kvs_path.clone(), "{\"malformed_json\"}").unwrap(); - assert!( - JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::JsonParserError) - ); + assert!(JsonBackend::load(&kvs_path, None).is_err_and(|e| e == ErrorCode::JsonParserError)); } #[test] - fn test_load_kvs_invalid_data() { + fn test_load_invalid_data() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_path = dir_path.join("kvs.json"); std::fs::write(kvs_path.clone(), "[123.4, 567.8]").unwrap(); - assert!( - JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::JsonParserError) - ); + assert!(JsonBackend::load(&kvs_path, None).is_err_and(|e| e == ErrorCode::JsonParserError)); } #[test] - fn test_load_kvs_hash_path_some_ok() { + fn test_load_hash_path_some_ok() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let (kvs_path, hash_path) = create_kvs_files(&dir_path); - let kvs_map = JsonBackend::load_kvs(&kvs_path, Some(&hash_path)).unwrap(); + let kvs_map = JsonBackend::load(&kvs_path, Some(&hash_path)).unwrap(); assert_eq!(kvs_map.len(), 3); } #[test] - fn test_load_kvs_hash_path_some_invalid_extension() { + fn test_load_hash_path_some_invalid_extension() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let (kvs_path, hash_path) = create_kvs_files(&dir_path); let new_hash_path = hash_path.with_extension("invalid_ext"); std::fs::rename(hash_path, new_hash_path.clone()).unwrap(); - assert!(JsonBackend::load_kvs(&kvs_path, Some(&new_hash_path)) + assert!(JsonBackend::load(&kvs_path, Some(&new_hash_path)) .is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); } #[test] - fn test_load_kvs_hash_path_some_not_found() { + fn test_load_hash_path_some_not_found() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let (kvs_path, hash_path) = create_kvs_files(&dir_path); std::fs::remove_file(hash_path.clone()).unwrap(); - assert!(JsonBackend::load_kvs(&kvs_path, Some(&hash_path)) + assert!(JsonBackend::load(&kvs_path, Some(&hash_path)) .is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); } #[test] - fn test_load_kvs_invalid_hash_content() { + fn test_load_invalid_hash_content() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let (kvs_path, hash_path) = create_kvs_files(&dir_path); std::fs::write(hash_path.clone(), vec![0x12, 0x34, 0x56, 0x78]).unwrap(); - assert!(JsonBackend::load_kvs(&kvs_path, Some(&hash_path)) + assert!(JsonBackend::load(&kvs_path, Some(&hash_path)) .is_err_and(|e| e == ErrorCode::ValidationFailed)); } #[test] - fn test_load_kvs_invalid_hash_len() { + fn test_load_invalid_hash_len() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let (kvs_path, hash_path) = create_kvs_files(&dir_path); std::fs::write(hash_path.clone(), vec![0x12, 0x34, 0x56]).unwrap(); - assert!(JsonBackend::load_kvs(&kvs_path, Some(&hash_path)) + assert!(JsonBackend::load(&kvs_path, Some(&hash_path)) .is_err_and(|e| e == ErrorCode::ValidationFailed)); } #[test] - fn test_save_kvs_ok() { + fn test_save_ok() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); @@ -860,24 +1007,24 @@ mod backend_tests { ("k3".to_string(), KvsValue::from(123.4)), ]); let kvs_path = dir_path.join("kvs.json"); - JsonBackend::save_kvs(&kvs_map, &kvs_path, None).unwrap(); + JsonBackend::save(&kvs_map, &kvs_path, None).unwrap(); assert!(kvs_path.exists()); } #[test] - fn test_save_kvs_invalid_extension() { + fn test_save_invalid_extension() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_map = KvsMap::new(); let kvs_path = dir_path.join("kvs.invalid_ext"); - assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, None) + assert!(JsonBackend::save(&kvs_map, &kvs_path, None) .is_err_and(|e| e == ErrorCode::KvsFileReadError)); } #[test] - fn test_save_kvs_hash_path_some_ok() { + fn test_save_hash_path_some_ok() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); @@ -888,42 +1035,34 @@ mod backend_tests { ]); let kvs_path = dir_path.join("kvs.json"); let hash_path = dir_path.join("kvs.hash"); - JsonBackend::save_kvs(&kvs_map, &kvs_path, Some(&hash_path)).unwrap(); + JsonBackend::save(&kvs_map, &kvs_path, Some(&hash_path)).unwrap(); assert!(kvs_path.exists()); assert!(hash_path.exists()); } #[test] - fn test_save_kvs_hash_path_some_invalid_extension() { + fn test_save_hash_path_some_invalid_extension() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_map = KvsMap::new(); let kvs_path = dir_path.join("kvs.json"); let hash_path = dir_path.join("kvs.invalid_ext"); - assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, Some(&hash_path)) + assert!(JsonBackend::save(&kvs_map, &kvs_path, Some(&hash_path)) .is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); } #[test] - fn test_save_kvs_impossible_str() { + fn test_save_impossible_str() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let kvs_map = KvsMap::from([("inf".to_string(), KvsValue::from(f64::INFINITY))]); let kvs_path = dir_path.join("kvs.json"); - assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, None) + assert!(JsonBackend::save(&kvs_map, &kvs_path, None) .is_err_and(|e| e == ErrorCode::JsonGeneratorError)); } -} - -#[cfg(test)] -mod path_resolver_tests { - use crate::json_backend::JsonBackend; - use crate::kvs_api::{InstanceId, SnapshotId}; - use crate::kvs_backend::KvsPathResolver; - use tempfile::tempdir; #[test] fn test_kvs_file_name() { @@ -937,12 +1076,15 @@ mod path_resolver_tests { #[test] fn test_kvs_file_path() { let dir = tempdir().unwrap(); - let dir_path = dir.path(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(); let instance_id = InstanceId(123); let snapshot_id = SnapshotId(2); let exp_name = dir_path.join(format!("kvs_{instance_id}_{snapshot_id}.json")); - let act_name = JsonBackend::kvs_file_path(dir_path, instance_id, snapshot_id); + let act_name = backend.kvs_file_path(instance_id, snapshot_id); assert_eq!(exp_name, act_name); } #[test] @@ -957,12 +1099,15 @@ mod path_resolver_tests { #[test] fn test_hash_file_path() { let dir = tempdir().unwrap(); - let dir_path = dir.path(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(); let instance_id = InstanceId(123); let snapshot_id = SnapshotId(2); let exp_name = dir_path.join(format!("kvs_{instance_id}_{snapshot_id}.hash")); - let act_name = JsonBackend::hash_file_path(dir_path, instance_id, snapshot_id); + let act_name = backend.hash_file_path(instance_id, snapshot_id); assert_eq!(exp_name, act_name); } @@ -977,11 +1122,237 @@ mod path_resolver_tests { #[test] fn test_defaults_file_path() { let dir = tempdir().unwrap(); - let dir_path = dir.path(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(); let instance_id = InstanceId(123); let exp_name = dir_path.join(format!("kvs_{instance_id}_default.json")); - let act_name = JsonBackend::defaults_file_path(dir_path, instance_id); + let act_name = backend.defaults_file_path(instance_id); assert_eq!(exp_name, act_name); } } + +#[cfg(test)] +mod kvs_backend_tests { + use crate::error_code::ErrorCode; + use crate::json_backend::{JsonBackend, JsonBackendBuilder}; + use crate::kvs_api::{InstanceId, SnapshotId}; + use crate::kvs_backend::KvsBackend; + use crate::kvs_value::{KvsMap, KvsValue}; + use std::fs; + use tempfile::tempdir; + + fn create_kvs_files(backend: &JsonBackend, instance_id: InstanceId, snapshot_id: SnapshotId) { + let kvs_map = KvsMap::from([ + ("k1".to_string(), KvsValue::from("v1")), + ("k2".to_string(), KvsValue::from(true)), + ("k3".to_string(), KvsValue::from(123.4)), + ]); + let kvs_path = backend.kvs_file_path(instance_id, snapshot_id); + let hash_path = backend.hash_file_path(instance_id, snapshot_id); + JsonBackend::save(&kvs_map, &kvs_path, Some(&hash_path)).unwrap(); + } + + fn create_defaults_file(backend: &JsonBackend, instance_id: InstanceId) { + let kvs_map = KvsMap::from([ + ("k4".to_string(), KvsValue::from("v4")), + ("k5".to_string(), KvsValue::from(432.1)), + ]); + let defaults_path = backend.defaults_file_path(instance_id); + JsonBackend::save(&kvs_map, &defaults_path, None).unwrap(); + } + + #[test] + fn test_load_kvs_ok() { + // Main `load` tests are performed by `test_load_*` tests. + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(1); + let snapshot_id = SnapshotId(1); + create_kvs_files(&backend, instance_id, snapshot_id); + + let kvs_map = backend.load_kvs(instance_id, snapshot_id).unwrap(); + assert_eq!(kvs_map.len(), 3); + } + + #[test] + fn test_load_defaults_ok() { + // Main `load` tests are performed by `test_load_*` tests. + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(1); + create_defaults_file(&backend, instance_id); + + let kvs_map = backend.load_defaults(instance_id).unwrap(); + assert_eq!(kvs_map.len(), 2); + } + + #[test] + fn test_flush_ok() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(1); + + // Flush. + let kvs_map = KvsMap::from([("key".to_string(), KvsValue::from("value"))]); + backend.flush(instance_id, &kvs_map).unwrap(); + + // Check files exist. + let snapshot_id = SnapshotId(0); + let kvs_path = backend.kvs_file_path(instance_id, snapshot_id); + let hash_path = backend.hash_file_path(instance_id, snapshot_id); + assert!(kvs_path.exists()); + assert!(hash_path.exists()); + } + + #[test] + fn test_flush_kvs_removed() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(1); + + // Flush. + let kvs_map = KvsMap::from([("key".to_string(), KvsValue::from("value"))]); + backend.flush(instance_id, &kvs_map).unwrap(); + + // Remove KVS file. + let snapshot_id = SnapshotId(0); + let kvs_path = backend.kvs_file_path(instance_id, snapshot_id); + fs::remove_file(kvs_path).unwrap(); + + // Flush again. + let result = backend.flush(instance_id, &kvs_map); + assert!(result.is_err_and(|e| e == ErrorCode::IntegrityCorrupted)); + } + + #[test] + fn test_flush_hash_removed() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(1); + + // Flush. + let kvs_map = KvsMap::from([("key".to_string(), KvsValue::from("value"))]); + backend.flush(instance_id, &kvs_map).unwrap(); + + // Remove KVS file. + let snapshot_id = SnapshotId(0); + let hash_path = backend.hash_file_path(instance_id, snapshot_id); + fs::remove_file(hash_path).unwrap(); + + // Flush again. + let result = backend.flush(instance_id, &kvs_map); + assert!(result.is_err_and(|e| e == ErrorCode::IntegrityCorrupted)); + } + + #[test] + fn test_snapshot_count_zero() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(2); + + assert_eq!(backend.snapshot_count(instance_id), 0); + } + + #[test] + fn test_snapshot_count_to_one() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(2); + + backend.flush(instance_id, &KvsMap::new()).unwrap(); + assert_eq!(backend.snapshot_count(instance_id), 1); + } + + #[test] + fn test_snapshot_count_to_max() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(2); + + for i in 1..=backend.snapshot_max_count() { + backend.flush(instance_id, &KvsMap::new()).unwrap(); + assert_eq!(backend.snapshot_count(instance_id), i); + } + + backend.flush(instance_id, &KvsMap::new()).unwrap(); + backend.flush(instance_id, &KvsMap::new()).unwrap(); + assert_eq!( + backend.snapshot_count(instance_id), + backend.snapshot_max_count() + ); + } + + #[test] + fn test_snapshot_max_count() { + let max_count = 1234; + let backend = JsonBackendBuilder::new() + .snapshot_max_count(max_count) + .build(); + assert_eq!(backend.snapshot_max_count(), max_count); + } + + #[test] + fn test_snapshot_restore_ok() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(2); + + // Prepare snapshots. + for i in 1..=backend.snapshot_max_count() { + let kvs_map = KvsMap::from([("counter".to_string(), KvsValue::I32(i as i32))]); + backend.flush(instance_id, &kvs_map).unwrap(); + } + + // Check restore. + let kvs_map = backend + .snapshot_restore(instance_id, SnapshotId(1)) + .unwrap(); + assert_eq!(*kvs_map.get("counter").unwrap(), KvsValue::I32(2)); + } + + #[test] + fn test_snapshot_restore_invalid_id() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(2); + + // Prepare snapshots. + for i in 1..=backend.snapshot_max_count() { + let kvs_map = KvsMap::from([("counter".to_string(), KvsValue::I32(i as i32))]); + backend.flush(instance_id, &kvs_map).unwrap(); + } + + let result = backend.snapshot_restore(instance_id, SnapshotId(123)); + assert!(result.is_err_and(|e| e == ErrorCode::InvalidSnapshotId)); + } + + #[test] + fn test_snapshot_restore_current_id() { + let dir = tempdir().unwrap(); + let dir_path = dir.path().to_path_buf(); + let backend = JsonBackendBuilder::new().working_dir(dir_path).build(); + let instance_id = InstanceId(2); + + // Prepare snapshots. + for i in 1..=backend.snapshot_max_count() { + let kvs_map = KvsMap::from([("counter".to_string(), KvsValue::I32(i as i32))]); + backend.flush(instance_id, &kvs_map).unwrap(); + } + + let result = backend.snapshot_restore(instance_id, SnapshotId(0)); + assert!(result.is_err_and(|e| e == ErrorCode::InvalidSnapshotId)); + } +} diff --git a/src/rust/rust_kvs/src/kvs.rs b/src/rust/rust_kvs/src/kvs.rs index 0acf4e4..5aa4c51 100644 --- a/src/rust/rust_kvs/src/kvs.rs +++ b/src/rust/rust_kvs/src/kvs.rs @@ -11,16 +11,12 @@ use crate::error_code::ErrorCode; use crate::kvs_api::{InstanceId, KvsApi, KvsDefaults, KvsLoad, SnapshotId}; -use crate::kvs_backend::{KvsBackend, KvsPathResolver}; +use crate::kvs_backend::KvsBackend; use crate::kvs_builder::KvsData; use crate::kvs_value::{KvsMap, KvsValue}; -use std::fs; -use std::marker::PhantomData; -use std::path::PathBuf; use std::sync::{Arc, Mutex}; /// KVS instance parameters. -#[derive(Clone, PartialEq)] pub struct KvsParameters { /// Instance ID. pub instance_id: InstanceId, @@ -31,109 +27,39 @@ pub struct KvsParameters { /// KVS load mode. pub kvs_load: KvsLoad, - /// Working directory. - pub working_dir: PathBuf, + /// Backend. + pub backend: Box, +} - /// Maximum number of snapshots to store. - pub snapshot_max_count: usize, +impl PartialEq for KvsParameters { + fn eq(&self, other: &Self) -> bool { + self.instance_id == other.instance_id + && self.defaults == other.defaults + && self.kvs_load == other.kvs_load + && self.backend.dyn_eq(other.backend.as_any()) + } } /// Key-value-storage data -pub struct GenericKvs { +pub struct Kvs { /// KVS instance data. data: Arc>, /// KVS instance parameters. - parameters: KvsParameters, - - /// Marker for `Backend`. - _backend_marker: PhantomData, - - /// Marker for `PathResolver`. - _path_resolver_marker: PhantomData, + parameters: Arc, } -impl GenericKvs { - pub(crate) fn new(data: Arc>, parameters: KvsParameters) -> Self { - Self { - data, - parameters, - _backend_marker: PhantomData, - _path_resolver_marker: PhantomData, - } +impl Kvs { + pub(crate) fn new(data: Arc>, parameters: Arc) -> Self { + Self { data, parameters } } pub fn parameters(&self) -> &KvsParameters { &self.parameters } - - /// Rotate snapshots - /// - /// # Features - /// * `FEAT_REQ__KVS__snapshots` - /// - /// # Return Values - /// * Ok: Rotation successful, also if no rotation was needed - /// * `ErrorCode::UnmappedError`: Unmapped error - fn snapshot_rotate(&self) -> Result<(), ErrorCode> { - for idx in (1..self.snapshot_max_count()).rev() { - let old_snapshot_id = SnapshotId(idx - 1); - let new_snapshot_id = SnapshotId(idx); - - let hash_path_old = PathResolver::hash_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - old_snapshot_id, - ); - let hash_path_new = PathResolver::hash_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - new_snapshot_id, - ); - let snap_name_old = - PathResolver::kvs_file_name(self.parameters.instance_id, old_snapshot_id); - let snap_path_old = PathResolver::kvs_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - old_snapshot_id, - ); - let snap_name_new = - PathResolver::kvs_file_name(self.parameters.instance_id, new_snapshot_id); - let snap_path_new = PathResolver::kvs_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - new_snapshot_id, - ); - - println!("rotating: {snap_name_old} -> {snap_name_new}"); - - // Check snapshot and hash files exist. - let snap_old_exists = snap_path_old.exists(); - let hash_old_exists = hash_path_old.exists(); - - // If both exist - rename them. - if snap_old_exists && hash_old_exists { - fs::rename(hash_path_old, hash_path_new)?; - fs::rename(snap_path_old, snap_path_new)?; - } - // If neither exist - continue. - else if !snap_old_exists && !hash_old_exists { - continue; - } - // In other case - this is erroneous scenario. - // Either snapshot or hash file got removed. - else { - return Err(ErrorCode::IntegrityCorrupted); - } - } - - Ok(()) - } } -impl KvsApi - for GenericKvs -{ +impl KvsApi for Kvs { /// Resets a key-value-storage to its initial state /// /// # Return Values @@ -364,28 +290,10 @@ impl KvsApi return Ok(()); } - self.snapshot_rotate().map_err(|e| { - eprintln!("error: snapshot_rotate failed: {e:?}"); - e - })?; - let snapshot_id = SnapshotId(0); - let kvs_path = PathResolver::kvs_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, - ); - let hash_path = PathResolver::hash_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, - ); - let data = self.data.lock()?; - Backend::save_kvs(&data.kvs_map, &kvs_path, Some(&hash_path)).map_err(|e| { - eprintln!("error: save_kvs failed: {e:?}"); - e - })?; - Ok(()) + self.parameters + .backend + .flush(self.parameters.instance_id, &data.kvs_map) } /// Get the count of snapshots @@ -393,31 +301,17 @@ impl KvsApi /// # Return Values /// * usize: Count of found snapshots fn snapshot_count(&self) -> usize { - let mut count = 0; - - for idx in 0..self.snapshot_max_count() { - let snapshot_id = SnapshotId(idx); - let snapshot_path = PathResolver::kvs_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, - ); - if !snapshot_path.exists() { - break; - } - - count += 1; - } - - count + self.parameters + .backend + .snapshot_count(self.parameters.instance_id) } /// Return maximum number of snapshots to store. /// /// # Return Values - /// * usize: Maximum number of snapshots to store. + /// * usize: Maximum count of snapshots fn snapshot_max_count(&self) -> usize { - self.parameters().snapshot_max_count + self.parameters.backend.snapshot_max_count() } /// Recover key-value-storage from snapshot @@ -440,191 +334,162 @@ impl KvsApi /// * `ErrorCode::UnmappedError`: Generic error fn snapshot_restore(&self, snapshot_id: SnapshotId) -> Result<(), ErrorCode> { let mut data = self.data.lock()?; - // fail if the snapshot ID is the current KVS - if snapshot_id == SnapshotId(0) { - eprintln!("error: tried to restore current KVS as snapshot"); - return Err(ErrorCode::InvalidSnapshotId); - } + data.kvs_map = self + .parameters + .backend + .snapshot_restore(self.parameters.instance_id, snapshot_id)?; + Ok(()) + } +} - if self.snapshot_count() < snapshot_id.0 { - eprintln!("error: tried to restore a non-existing snapshot"); - return Err(ErrorCode::InvalidSnapshotId); - } +#[cfg(test)] +mod kvs_parameters_tests { + use crate::json_backend::JsonBackendBuilder; + use crate::kvs::KvsParameters; + use crate::kvs_api::{InstanceId, KvsDefaults, KvsLoad}; + use std::path::PathBuf; - let kvs_path = PathResolver::kvs_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, - ); - let hash_path = PathResolver::hash_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, + #[test] + fn test_eq_same() { + let instance_id = InstanceId(0); + let defaults = KvsDefaults::Optional; + let kvs_load = KvsLoad::Optional; + let backend = Box::new( + JsonBackendBuilder::new() + .working_dir(PathBuf::from("/tmp")) + .build(), ); - data.kvs_map = Backend::load_kvs(&kvs_path, Some(&hash_path))?; - Ok(()) + let first = KvsParameters { + instance_id, + defaults: defaults.clone(), + kvs_load: kvs_load.clone(), + backend: backend.clone(), + }; + let second = KvsParameters { + instance_id, + defaults, + kvs_load, + backend, + }; + assert!(first.eq(&second)); } - /// Return the KVS-filename for a given snapshot ID - /// - /// # Parameters - /// * `id`: Snapshot ID to get the filename for - /// - /// # Return Values - /// * `Ok`: Filename for ID - /// * `ErrorCode::FileNotFound`: KVS file for snapshot ID not found - fn get_kvs_filename(&self, snapshot_id: SnapshotId) -> Result { - let path = PathResolver::kvs_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, - ); - if !path.exists() { - Err(ErrorCode::FileNotFound) - } else { - Ok(path) - } - } + #[test] + fn test_eq_diff() { + let instance_id = InstanceId(0); + let defaults = KvsDefaults::Optional; + let kvs_load = KvsLoad::Optional; - /// Return the hash-filename for a given snapshot ID - /// - /// # Parameters - /// * `id`: Snapshot ID to get the hash filename for - /// - /// # Return Values - /// * `Ok`: Hash filename for ID - /// * `ErrorCode::FileNotFound`: Hash file for snapshot ID not found - fn get_hash_filename(&self, snapshot_id: SnapshotId) -> Result { - let path = PathResolver::hash_file_path( - &self.parameters.working_dir, - self.parameters.instance_id, - snapshot_id, - ); - if !path.exists() { - Err(ErrorCode::FileNotFound) - } else { - Ok(path) - } + let first = KvsParameters { + instance_id, + defaults: defaults.clone(), + kvs_load: kvs_load.clone(), + backend: Box::new( + JsonBackendBuilder::new() + .working_dir(PathBuf::from("/tmp/x")) + .build(), + ), + }; + let second = KvsParameters { + instance_id, + defaults, + kvs_load, + backend: Box::new( + JsonBackendBuilder::new() + .working_dir(PathBuf::from("/tmp/y")) + .build(), + ), + }; + assert!(first.ne(&second)); } } #[cfg(test)] mod kvs_tests { use crate::error_code::ErrorCode; - use crate::json_backend::JsonBackend; - use crate::kvs::{GenericKvs, KvsParameters}; + use crate::json_backend::JsonBackendBuilder; + use crate::kvs::{Kvs, KvsParameters}; use crate::kvs_api::{InstanceId, KvsApi, KvsDefaults, KvsLoad, SnapshotId}; - use crate::kvs_backend::{KvsBackend, KvsPathResolver}; + use crate::kvs_backend::KvsBackend; use crate::kvs_builder::KvsData; use crate::kvs_value::{KvsMap, KvsValue}; - use std::path::PathBuf; use std::sync::{Arc, Mutex}; use tempfile::tempdir; /// Most tests can be performed with mocked backend. /// Only those with file handling must use concrete implementation. + #[derive(PartialEq)] struct MockBackend; impl KvsBackend for MockBackend { fn load_kvs( - _kvs_path: &std::path::Path, - _hash_path: Option<&PathBuf>, + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, ) -> Result { unimplemented!() } - fn save_kvs( - _kvs_map: &KvsMap, - _kvs_path: &std::path::Path, - _hash_path: Option<&PathBuf>, - ) -> Result<(), ErrorCode> { + fn load_defaults(&self, _instance_id: InstanceId) -> Result { unimplemented!() } - } - impl KvsPathResolver for MockBackend { - fn kvs_file_name(_instance_id: InstanceId, _snapshot_id: SnapshotId) -> String { + fn flush(&self, _instance_id: InstanceId, _kvs_map: &KvsMap) -> Result<(), ErrorCode> { unimplemented!() } - fn kvs_file_path( - _working_dir: &std::path::Path, - _instance_id: InstanceId, - _snapshot_id: SnapshotId, - ) -> PathBuf { + fn snapshot_count(&self, _instance_id: InstanceId) -> usize { unimplemented!() } - fn hash_file_name(_instance_id: InstanceId, _snapshot_id: SnapshotId) -> String { + fn snapshot_max_count(&self) -> usize { unimplemented!() } - fn hash_file_path( - _working_dir: &std::path::Path, + fn snapshot_restore( + &self, _instance_id: InstanceId, _snapshot_id: SnapshotId, - ) -> PathBuf { - unimplemented!() - } - - fn defaults_file_name(_instance_id: InstanceId) -> String { - unimplemented!() - } - - fn defaults_file_path(_working_dir: &std::path::Path, _instance_id: InstanceId) -> PathBuf { + ) -> Result { unimplemented!() } } - fn get_kvs_snapshot_max_count( - working_dir: PathBuf, - kvs_map: KvsMap, - defaults_map: KvsMap, - snapshot_max_count: usize, - ) -> GenericKvs { + fn get_kvs(backend: Box, kvs_map: KvsMap, defaults_map: KvsMap) -> Kvs { let instance_id = InstanceId(1); let data = Arc::new(Mutex::new(KvsData { kvs_map, defaults_map, })); - let parameters = KvsParameters { + let parameters = Arc::new(KvsParameters { instance_id, defaults: KvsDefaults::Optional, kvs_load: KvsLoad::Optional, - working_dir, - snapshot_max_count, - }; - GenericKvs::::new(data, parameters) - } - - fn get_kvs( - working_dir: PathBuf, - kvs_map: KvsMap, - defaults_map: KvsMap, - ) -> GenericKvs { - get_kvs_snapshot_max_count(working_dir, kvs_map, defaults_map, 3) + backend, + }); + Kvs::new(data, parameters) } #[test] fn test_new_ok() { // Check only if panic happens. - get_kvs::(PathBuf::new(), KvsMap::new(), KvsMap::new()); + get_kvs(Box::new(MockBackend), KvsMap::new(), KvsMap::new()); } #[test] fn test_parameters_ok() { - let kvs = get_kvs::(PathBuf::new(), KvsMap::new(), KvsMap::new()); + let kvs = get_kvs(Box::new(MockBackend), KvsMap::new(), KvsMap::new()); assert_eq!(kvs.parameters().instance_id, InstanceId(1)); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); - assert_eq!(kvs.parameters().working_dir, PathBuf::new()); + assert!(kvs.parameters().backend.dyn_eq(&MockBackend)); } #[test] fn test_reset() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("explicit_value")), ("example2".to_string(), KvsValue::from(true)), @@ -646,8 +511,8 @@ mod kvs_tests { #[cfg_attr(miri, ignore)] #[test] fn test_reset_key() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("explicit_value")), ("example2".to_string(), KvsValue::from(true)), @@ -669,8 +534,8 @@ mod kvs_tests { #[test] fn test_get_all_keys_some() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -685,7 +550,7 @@ mod kvs_tests { #[test] fn test_get_all_keys_empty() { - let kvs = get_kvs::(PathBuf::new(), KvsMap::new(), KvsMap::new()); + let kvs = get_kvs(Box::new(MockBackend), KvsMap::new(), KvsMap::new()); let keys = kvs.get_all_keys().unwrap(); assert_eq!(keys.len(), 0); @@ -693,8 +558,8 @@ mod kvs_tests { #[test] fn test_key_exists_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -708,8 +573,8 @@ mod kvs_tests { #[test] fn test_key_exists_not_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -722,8 +587,8 @@ mod kvs_tests { #[test] fn test_get_value_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -737,8 +602,8 @@ mod kvs_tests { #[test] fn test_get_value_available_default() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([("example2".to_string(), KvsValue::from(true))]), KvsMap::from([("example1".to_string(), KvsValue::from("default_value"))]), ); @@ -751,8 +616,8 @@ mod kvs_tests { #[test] fn test_get_value_not_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([("example2".to_string(), KvsValue::from(true))]), KvsMap::from([("example1".to_string(), KvsValue::from("default_value"))]), ); @@ -764,8 +629,8 @@ mod kvs_tests { #[test] fn test_get_value_as_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -779,8 +644,8 @@ mod kvs_tests { #[test] fn test_get_value_as_available_default() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([("example2".to_string(), KvsValue::from(true))]), KvsMap::from([("example1".to_string(), KvsValue::from("default_value"))]), ); @@ -791,8 +656,8 @@ mod kvs_tests { #[test] fn test_get_value_as_not_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([("example2".to_string(), KvsValue::from(true))]), KvsMap::from([("example1".to_string(), KvsValue::from("default_value"))]), ); @@ -804,8 +669,8 @@ mod kvs_tests { #[test] fn test_get_value_as_invalid_type() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -820,8 +685,8 @@ mod kvs_tests { #[test] fn test_get_value_as_default_invalid_type() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([("example2".to_string(), KvsValue::from(true))]), KvsMap::from([("example1".to_string(), KvsValue::from("default_value"))]), ); @@ -833,8 +698,8 @@ mod kvs_tests { #[test] fn test_get_default_value_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -848,8 +713,8 @@ mod kvs_tests { #[test] fn test_get_default_value_not_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -864,8 +729,8 @@ mod kvs_tests { #[test] fn test_is_value_default_false() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -878,8 +743,8 @@ mod kvs_tests { #[test] fn test_is_value_default_true() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -892,8 +757,8 @@ mod kvs_tests { #[test] fn test_is_value_default_not_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -908,7 +773,7 @@ mod kvs_tests { #[test] fn test_set_value_new() { - let kvs = get_kvs::(PathBuf::new(), KvsMap::new(), KvsMap::new()); + let kvs = get_kvs(Box::new(MockBackend), KvsMap::new(), KvsMap::new()); kvs.set_value("key", "value").unwrap(); assert_eq!(kvs.get_value_as::("key").unwrap(), "value"); @@ -916,8 +781,8 @@ mod kvs_tests { #[test] fn test_set_value_exists() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([("key".to_string(), KvsValue::from("old_value"))]), KvsMap::new(), ); @@ -928,8 +793,8 @@ mod kvs_tests { #[test] fn test_remove_key_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -943,8 +808,8 @@ mod kvs_tests { #[test] fn test_remove_key_not_found() { - let kvs = get_kvs::( - PathBuf::new(), + let kvs = get_kvs( + Box::new(MockBackend), KvsMap::from([ ("example1".to_string(), KvsValue::from("value")), ("example2".to_string(), KvsValue::from(true)), @@ -961,17 +826,20 @@ mod kvs_tests { fn test_flush() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::( - dir_path, + let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); + let kvs = get_kvs( + backend.clone(), KvsMap::from([("key".to_string(), KvsValue::from("value"))]), KvsMap::new(), ); kvs.flush().unwrap(); - let snapshot_id = SnapshotId(0); + // Functions below check if file exist. - kvs.get_kvs_filename(snapshot_id).unwrap(); - kvs.get_hash_filename(snapshot_id).unwrap(); + let instance_id = kvs.parameters().instance_id; + let snapshot_id = SnapshotId(0); + assert!(backend.kvs_file_path(instance_id, snapshot_id).exists()); + assert!(backend.hash_file_path(instance_id, snapshot_id).exists()); } #[test] @@ -979,11 +847,15 @@ mod kvs_tests { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); const MAX_COUNT: usize = 0; - let kvs = get_kvs_snapshot_max_count::( - dir_path, + let kvs = get_kvs( + Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path) + .snapshot_max_count(MAX_COUNT) + .build(), + ), KvsMap::new(), KvsMap::new(), - MAX_COUNT, ); // Flush several times. @@ -999,11 +871,15 @@ mod kvs_tests { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); const MAX_COUNT: usize = 1; - let kvs = get_kvs_snapshot_max_count::( - dir_path, + let kvs = get_kvs( + Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path) + .snapshot_max_count(MAX_COUNT) + .build(), + ), KvsMap::new(), KvsMap::new(), - MAX_COUNT, ); // Flush several times. @@ -1019,7 +895,11 @@ mod kvs_tests { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); const EXPECTED_MAX_COUNT: usize = 3; - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); // Flush several times. for _ in 0..EXPECTED_MAX_COUNT + 1 { @@ -1033,7 +913,11 @@ mod kvs_tests { fn test_snapshot_count_zero() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); assert_eq!(kvs.snapshot_count(), 0); } @@ -1041,7 +925,11 @@ mod kvs_tests { fn test_snapshot_count_to_one() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); kvs.flush().unwrap(); assert_eq!(kvs.snapshot_count(), 1); } @@ -1050,7 +938,11 @@ mod kvs_tests { fn test_snapshot_count_to_max() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); for i in 1..=kvs.snapshot_max_count() { kvs.flush().unwrap(); assert_eq!(kvs.snapshot_count(), i); @@ -1064,7 +956,11 @@ mod kvs_tests { fn test_snapshot_max_count() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); assert_eq!(kvs.snapshot_max_count(), 3); } @@ -1072,7 +968,11 @@ mod kvs_tests { fn test_snapshot_restore_ok() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); for i in 1..=kvs.snapshot_max_count() { kvs.set_value("counter", KvsValue::I32(i as i32)).unwrap(); kvs.flush().unwrap(); @@ -1086,7 +986,11 @@ mod kvs_tests { fn test_snapshot_restore_invalid_id() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); for i in 1..=kvs.snapshot_max_count() { kvs.set_value("counter", KvsValue::I32(i as i32)).unwrap(); kvs.flush().unwrap(); @@ -1101,7 +1005,11 @@ mod kvs_tests { fn test_snapshot_restore_current_id() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); for i in 1..=kvs.snapshot_max_count() { kvs.set_value("counter", KvsValue::I32(i as i32)).unwrap(); kvs.flush().unwrap(); @@ -1116,7 +1024,11 @@ mod kvs_tests { fn test_snapshot_restore_not_available() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); for i in 1..=2 { kvs.set_value("counter", KvsValue::I32(i)).unwrap(); kvs.flush().unwrap(); @@ -1126,52 +1038,4 @@ mod kvs_tests { .snapshot_restore(SnapshotId(3)) .is_err_and(|e| e == ErrorCode::InvalidSnapshotId)); } - - #[test] - fn test_get_kvs_filename_found() { - let dir = tempdir().unwrap(); - let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); - - kvs.flush().unwrap(); - kvs.flush().unwrap(); - let kvs_path = kvs.get_kvs_filename(SnapshotId(1)).unwrap(); - let kvs_name = kvs_path.file_name().unwrap().to_str().unwrap(); - assert_eq!(kvs_name, "kvs_1_1.json"); - } - - #[test] - fn test_get_kvs_filename_not_found() { - let dir = tempdir().unwrap(); - let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); - - assert!(kvs - .get_kvs_filename(SnapshotId(1)) - .is_err_and(|e| e == ErrorCode::FileNotFound)); - } - - #[test] - fn test_get_hash_filename_found() { - let dir = tempdir().unwrap(); - let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); - - kvs.flush().unwrap(); - kvs.flush().unwrap(); - let hash_path = kvs.get_hash_filename(SnapshotId(1)).unwrap(); - let hash_name = hash_path.file_name().unwrap().to_str().unwrap(); - assert_eq!(hash_name, "kvs_1_1.hash"); - } - - #[test] - fn test_get_hash_filename_not_found() { - let dir = tempdir().unwrap(); - let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); - - assert!(kvs - .get_hash_filename(SnapshotId(1)) - .is_err_and(|e| e == ErrorCode::FileNotFound)); - } } diff --git a/src/rust/rust_kvs/src/kvs_api.rs b/src/rust/rust_kvs/src/kvs_api.rs index fc46f54..e7c66f3 100644 --- a/src/rust/rust_kvs/src/kvs_api.rs +++ b/src/rust/rust_kvs/src/kvs_api.rs @@ -12,10 +12,9 @@ use crate::error_code::ErrorCode; use crate::kvs_value::KvsValue; use core::fmt; -use std::path::PathBuf; /// Instance ID -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InstanceId(pub usize); impl fmt::Display for InstanceId { @@ -31,7 +30,7 @@ impl From for usize { } /// Snapshot ID -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SnapshotId(pub usize); impl fmt::Display for SnapshotId { @@ -47,7 +46,7 @@ impl From for usize { } /// Defaults handling mode. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum KvsDefaults { /// Defaults are not loaded. Ignored, @@ -60,7 +59,7 @@ pub enum KvsDefaults { } /// KVS load mode. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum KvsLoad { /// KVS is not loaded. Ignored, @@ -94,8 +93,6 @@ pub trait KvsApi { fn snapshot_count(&self) -> usize; fn snapshot_max_count(&self) -> usize; fn snapshot_restore(&self, snapshot_id: SnapshotId) -> Result<(), ErrorCode>; - fn get_kvs_filename(&self, snapshot_id: SnapshotId) -> Result; - fn get_hash_filename(&self, snapshot_id: SnapshotId) -> Result; } #[cfg(test)] diff --git a/src/rust/rust_kvs/src/kvs_backend.rs b/src/rust/rust_kvs/src/kvs_backend.rs index 2b22edf..ae3141e 100644 --- a/src/rust/rust_kvs/src/kvs_backend.rs +++ b/src/rust/rust_kvs/src/kvs_backend.rs @@ -12,46 +12,56 @@ use crate::error_code::ErrorCode; use crate::kvs_api::{InstanceId, SnapshotId}; use crate::kvs_value::KvsMap; -use std::path::{Path, PathBuf}; +use std::any::Any; -/// KVS backend interface. -pub trait KvsBackend { - /// Load KvsMap from given file. - fn load_kvs(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result; - - /// Store KvsMap at given file path. - fn save_kvs( - kvs_map: &KvsMap, - kvs_path: &Path, - hash_path: Option<&PathBuf>, - ) -> Result<(), ErrorCode>; +pub trait DynEq: Any { + fn dyn_eq(&self, other: &dyn Any) -> bool; + fn as_any(&self) -> &dyn Any; } -/// KVS path resolver interface. -pub trait KvsPathResolver { - /// Get KVS file name. - fn kvs_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String; +impl DynEq for T +where + T: KvsBackend, +{ + fn dyn_eq(&self, other: &dyn Any) -> bool { + if let Some(other) = other.downcast_ref::() { + self == other + } else { + false + } + } + + fn as_any(&self) -> &dyn Any { + self + } +} - /// Get KVS file path in working directory. - fn kvs_file_path( - working_dir: &Path, +/// KVS backend interface. +pub trait KvsBackend: DynEq + Sync + Send { + /// Load KVS content. + fn load_kvs( + &self, instance_id: InstanceId, snapshot_id: SnapshotId, - ) -> PathBuf; + ) -> Result; - /// Get hash file name. - fn hash_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String; + /// Load default values. + fn load_defaults(&self, instance_id: InstanceId) -> Result; - /// Get hash file path in working directory. - fn hash_file_path( - working_dir: &Path, - instance_id: InstanceId, - snapshot_id: SnapshotId, - ) -> PathBuf; + /// Flush KvsMap to persistent storage. + /// Snapshots are rotated and current state is stored as first (0). + fn flush(&self, instance_id: InstanceId, kvs_map: &KvsMap) -> Result<(), ErrorCode>; + + /// Count available snapshots. + fn snapshot_count(&self, instance_id: InstanceId) -> usize; - /// Get defaults file name. - fn defaults_file_name(instance_id: InstanceId) -> String; + /// Max number of snapshots. + fn snapshot_max_count(&self) -> usize; - /// Get defaults file path in working directory. - fn defaults_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf; + /// Restore snapshot with given ID. + fn snapshot_restore( + &self, + instance_id: InstanceId, + snapshot_id: SnapshotId, + ) -> Result; } diff --git a/src/rust/rust_kvs/src/kvs_builder.rs b/src/rust/rust_kvs/src/kvs_builder.rs index bc12af6..7cb726e 100644 --- a/src/rust/rust_kvs/src/kvs_builder.rs +++ b/src/rust/rust_kvs/src/kvs_builder.rs @@ -10,12 +10,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error_code::ErrorCode; -use crate::kvs::{GenericKvs, KvsParameters}; +use crate::json_backend::JsonBackendBuilder; +use crate::kvs::{Kvs, KvsParameters}; use crate::kvs_api::{InstanceId, KvsDefaults, KvsLoad, SnapshotId}; -use crate::kvs_backend::{KvsBackend, KvsPathResolver}; +use crate::kvs_backend::KvsBackend; use crate::kvs_value::KvsMap; -use std::marker::PhantomData; -use std::path::PathBuf; use std::sync::{Arc, LazyLock, Mutex, MutexGuard, PoisonError}; /// Maximum number of instances. @@ -40,7 +39,7 @@ impl From>> for ErrorCode { /// KVS instance inner representation. pub(crate) struct KvsInner { /// KVS instance parameters. - pub(crate) parameters: KvsParameters, + pub(crate) parameters: Arc, /// KVS instance data. pub(crate) data: Arc>, @@ -56,18 +55,12 @@ impl From; KVS_MAX_INSTANCES]>>> fo } /// Key-value-storage builder. -pub struct GenericKvsBuilder { +pub struct KvsBuilder { /// KVS instance parameters. parameters: KvsParameters, - - /// Marker for `Backend`. - _backend_marker: PhantomData, - - /// Marker for `PathResolver`. - _path_resolver_marker: PhantomData, } -impl GenericKvsBuilder { +impl KvsBuilder { /// Create a builder to open the key-value-storage /// /// Only the instance ID must be set. All other settings are using default values until changed @@ -83,15 +76,10 @@ impl GenericKvsBuilder GenericKvsBuilder>(mut self, dir: P) -> Self { - self.parameters.working_dir = PathBuf::from(dir.into()); - self - } - - /// Set the maximum number of snapshots to store. + /// Set backend used by KVS instance. /// /// # Parameters - /// * `snapshot_max_count`: Maximum number of snapshots to store. + /// # `backend`: KVS backend (default: [`JsonBackend`](JsonBackend)) /// /// # Return Values /// * KvsBuilder instance - pub fn snapshot_max_count(mut self, snapshot_max_count: usize) -> Self { - self.parameters.snapshot_max_count = snapshot_max_count; + pub fn backend(mut self, backend: Box) -> Self { + self.parameters.backend = backend; self } @@ -166,10 +142,11 @@ impl GenericKvsBuilder Result, ErrorCode> { - let instance_id = self.parameters.clone().instance_id; + pub fn build(self) -> Result { + let parameters = Arc::new(self.parameters); + let instance_id = parameters.instance_id; let instance_id_index: usize = instance_id.into(); - let working_dir = self.parameters.clone().working_dir; + let backend = ¶meters.backend; // Check if instance already exists. { @@ -178,7 +155,7 @@ impl GenericKvsBuilder match kvs_pool_entry { // If instance exists then parameters must match. Some(kvs_inner) => { - if kvs_inner.parameters == self.parameters { + if kvs_inner.parameters == parameters { Ok(Some(kvs_inner)) } else { Err(ErrorCode::InstanceParametersMismatch) @@ -193,7 +170,7 @@ impl GenericKvsBuilder::new( + return Ok(Kvs::new( kvs_inner.data.clone(), kvs_inner.parameters.clone(), )); @@ -201,34 +178,31 @@ impl GenericKvsBuilder KvsMap::new(), - KvsDefaults::Optional => { - if defaults_path.exists() { - Backend::load_kvs(&defaults_path, None)? - } else { - KvsMap::new() - } - } - KvsDefaults::Required => Backend::load_kvs(&defaults_path, None)?, + KvsDefaults::Optional => match backend.load_defaults(instance_id) { + Ok(map) => map, + Err(e) => match e { + ErrorCode::FileNotFound => KvsMap::new(), + _ => return Err(e), + }, + }, + KvsDefaults::Required => backend.load_defaults(instance_id)?, }; // Load KVS and hash files. let snapshot_id = SnapshotId(0); - let kvs_path = PathResolver::kvs_file_path(&working_dir, instance_id, snapshot_id); - let hash_path = PathResolver::hash_file_path(&working_dir, instance_id, snapshot_id); - let kvs_map = match self.parameters.kvs_load { + let kvs_map = match parameters.kvs_load { KvsLoad::Ignored => KvsMap::new(), - KvsLoad::Optional => { - if kvs_path.exists() && hash_path.exists() { - Backend::load_kvs(&kvs_path, Some(&hash_path))? - } else { - KvsMap::new() - } - } - KvsLoad::Required => Backend::load_kvs(&kvs_path, Some(&hash_path))?, + KvsLoad::Optional => match backend.load_kvs(instance_id, snapshot_id) { + Ok(map) => map, + Err(e) => match e { + ErrorCode::FileNotFound => KvsMap::new(), + _ => return Err(e), + }, + }, + KvsLoad::Required => backend.load_kvs(instance_id, snapshot_id)?, }; // Shared object containing data. @@ -246,22 +220,22 @@ impl GenericKvsBuilder; - #[test] fn test_new_ok() { let _lock = lock_and_reset(); // Check only if panic happens. let instance_id = InstanceId(0); - let _ = TestKvsBuilder::new(instance_id); + let _ = KvsBuilder::new(instance_id); } #[test] fn test_max_instances() { - assert_eq!(TestKvsBuilder::max_instances(), KVS_MAX_INSTANCES); + assert_eq!(KvsBuilder::max_instances(), KVS_MAX_INSTANCES); } #[test] @@ -308,15 +277,17 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let instance_id = InstanceId(1); - let builder = TestKvsBuilder::new(instance_id); + let builder = KvsBuilder::new(instance_id); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().instance_id, instance_id); // Check default values. assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); - assert_eq!(kvs.parameters().working_dir, PathBuf::new()); - assert_eq!(kvs.snapshot_max_count(), 3); + assert!(kvs + .parameters() + .backend + .dyn_eq(&JsonBackendBuilder::new().build())); } #[test] @@ -324,13 +295,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let instance_id = InstanceId(1); - let builder = TestKvsBuilder::new(instance_id).defaults(KvsDefaults::Ignored); + let builder = KvsBuilder::new(instance_id).defaults(KvsDefaults::Ignored); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().instance_id, instance_id); assert_eq!(kvs.parameters().defaults, KvsDefaults::Ignored); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); - assert_eq!(kvs.parameters().working_dir, PathBuf::new()); - assert_eq!(kvs.snapshot_max_count(), 3); + assert!(kvs + .parameters() + .backend + .dyn_eq(&JsonBackendBuilder::new().build())); } #[test] @@ -338,44 +311,57 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let instance_id = InstanceId(1); - let builder = TestKvsBuilder::new(instance_id).kvs_load(KvsLoad::Ignored); + let builder = KvsBuilder::new(instance_id).kvs_load(KvsLoad::Ignored); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().instance_id, instance_id); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Ignored); - assert_eq!(kvs.parameters().working_dir, PathBuf::new()); - assert_eq!(kvs.snapshot_max_count(), 3); + assert!(kvs + .parameters() + .backend + .dyn_eq(&JsonBackendBuilder::new().build())); } #[test] - fn test_parameters_dir() { + fn test_parameters_backend() { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(5); - let builder = TestKvsBuilder::new(instance_id).dir(dir_string.clone()); + let builder = KvsBuilder::new(instance_id).backend(Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().instance_id, instance_id); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); - assert_eq!(kvs.parameters().working_dir, dir.path()); - assert_eq!(kvs.snapshot_max_count(), 3); + assert!(kvs.parameters().backend.dyn_eq( + &JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build() + )); } #[test] fn test_parameters_snapshot_max_count() { let _lock = lock_and_reset(); - let instance_id = InstanceId(1); - let builder = TestKvsBuilder::new(instance_id).snapshot_max_count(1234); + let instance_id = InstanceId(5); + let builder = KvsBuilder::new(instance_id).backend(Box::new( + JsonBackendBuilder::new().snapshot_max_count(1234).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().instance_id, instance_id); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); - assert_eq!(kvs.parameters().working_dir, PathBuf::new()); - assert_eq!(kvs.snapshot_max_count(), 1234); + assert!(kvs + .parameters() + .backend + .dyn_eq(&JsonBackendBuilder::new().snapshot_max_count(1234).build())); } #[test] @@ -383,20 +369,28 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(1); - let builder = TestKvsBuilder::new(instance_id) + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .dir(dir_string) - .snapshot_max_count(1234); + .backend(Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .snapshot_max_count(1234) + .build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().instance_id, instance_id); assert_eq!(kvs.parameters().defaults, KvsDefaults::Ignored); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Ignored); - assert_eq!(kvs.parameters().working_dir, dir.path()); - assert_eq!(kvs.snapshot_max_count(), 1234); + assert!(kvs.parameters().backend.dyn_eq( + &JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .snapshot_max_count(1234) + .build() + )); } #[test] @@ -404,7 +398,7 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let instance_id = InstanceId(1); - let builder = TestKvsBuilder::new(instance_id); + let builder = KvsBuilder::new(instance_id); let _ = builder.build().unwrap(); } @@ -413,27 +407,38 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); // Create two instances with same parameters. let instance_id = InstanceId(1); - let builder1 = TestKvsBuilder::new(instance_id) + let builder1 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .dir(dir_string.clone()); + .backend(Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(), + )); let _ = builder1.build().unwrap(); - let builder2 = TestKvsBuilder::new(instance_id) + let builder2 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(), + )); let kvs = builder2.build().unwrap(); // Assert params as expected. assert_eq!(kvs.parameters().instance_id, instance_id); assert_eq!(kvs.parameters().defaults, KvsDefaults::Ignored); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Ignored); - assert_eq!(kvs.parameters().working_dir, dir.path()); + assert!(kvs + .parameters() + .backend + .dyn_eq(&JsonBackendBuilder::new().working_dir(dir_path).build())); } #[test] @@ -441,20 +446,28 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); // Create two instances with same parameters. let instance_id = InstanceId(1); - let builder1 = TestKvsBuilder::new(instance_id) + let builder1 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Optional) - .dir(dir_string.clone()); + .backend(Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(), + )); let _ = builder1.build().unwrap(); - let builder2 = TestKvsBuilder::new(instance_id) + let builder2 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) .kvs_load(KvsLoad::Ignored) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path.clone()) + .build(), + )); let result = builder2.build(); assert!(result.is_err_and(|e| e == ErrorCode::InstanceParametersMismatch)); @@ -465,7 +478,7 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let instance_id = InstanceId(123); - let result = TestKvsBuilder::new(instance_id).build(); + let result = KvsBuilder::new(instance_id).build(); assert!(result.is_err_and(|e| e == ErrorCode::InvalidInstanceId)); } @@ -474,13 +487,16 @@ mod kvs_builder_tests { working_dir: &Path, instance_id: InstanceId, ) -> Result { - let defaults_file_path = TestBackend::defaults_file_path(working_dir, instance_id); + let backend = JsonBackendBuilder::new() + .working_dir(working_dir.to_path_buf()) + .build(); + let defaults_file_path = backend.defaults_file_path(instance_id); let kvs_map = KvsMap::from([ ("number1".to_string(), KvsValue::F64(123.0)), ("bool1".to_string(), KvsValue::Boolean(true)), ("string1".to_string(), KvsValue::String("Hello".to_string())), ]); - TestBackend::save_kvs(&kvs_map, &defaults_file_path, None)?; + JsonBackend::save(&kvs_map, &defaults_file_path, None)?; Ok(defaults_file_path) } @@ -491,14 +507,17 @@ mod kvs_builder_tests { instance_id: InstanceId, snapshot_id: SnapshotId, ) -> Result<(PathBuf, PathBuf), ErrorCode> { - let kvs_file_path = TestBackend::kvs_file_path(working_dir, instance_id, snapshot_id); - let hash_file_path = TestBackend::hash_file_path(working_dir, instance_id, snapshot_id); + let backend = JsonBackendBuilder::new() + .working_dir(working_dir.to_path_buf()) + .build(); + let kvs_file_path = backend.kvs_file_path(instance_id, snapshot_id); + let hash_file_path = backend.hash_file_path(instance_id, snapshot_id); let kvs_map = KvsMap::from([ ("number1".to_string(), KvsValue::F64(321.0)), ("bool1".to_string(), KvsValue::Boolean(false)), ("string1".to_string(), KvsValue::String("Hi".to_string())), ]); - TestBackend::save_kvs(&kvs_map, &kvs_file_path, Some(&hash_file_path))?; + JsonBackend::save(&kvs_map, &kvs_file_path, Some(&hash_file_path))?; Ok((kvs_file_path, hash_file_path)) } @@ -508,13 +527,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_defaults_file(dir.path(), instance_id).unwrap(); - let builder = TestKvsBuilder::new(instance_id) + create_defaults_file(&dir_path, instance_id).unwrap(); + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Ignored); @@ -529,12 +550,14 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - let builder = TestKvsBuilder::new(instance_id) + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); @@ -549,13 +572,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_defaults_file(dir.path(), instance_id).unwrap(); - let builder = TestKvsBuilder::new(instance_id) + create_defaults_file(&dir_path, instance_id).unwrap(); + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); @@ -570,12 +595,14 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - let builder = TestKvsBuilder::new(instance_id) + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Required) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -586,13 +613,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_defaults_file(dir.path(), instance_id).unwrap(); - let builder = TestKvsBuilder::new(instance_id) + create_defaults_file(&dir_path, instance_id).unwrap(); + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Required) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Required); @@ -607,13 +636,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - let builder = TestKvsBuilder::new(instance_id) + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Ignored) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Ignored); @@ -628,12 +659,14 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - let builder = TestKvsBuilder::new(instance_id) + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); @@ -649,19 +682,17 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - std::fs::remove_file(TestBackend::hash_file_path( - dir.path(), - instance_id, - SnapshotId(0), - )) - .unwrap(); - let builder = TestKvsBuilder::new(instance_id) + let (_kvs_path, hash_path) = + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + std::fs::remove_file(hash_path).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); @@ -673,19 +704,17 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - std::fs::remove_file(TestBackend::kvs_file_path( - dir.path(), - instance_id, - SnapshotId(0), - )) - .unwrap(); - let builder = TestKvsBuilder::new(instance_id) + let (kvs_path, _hash_path) = + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + std::fs::remove_file(kvs_path).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -696,13 +725,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - let builder = TestKvsBuilder::new(instance_id) + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); @@ -717,12 +748,14 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - let builder = TestKvsBuilder::new(instance_id) + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -734,19 +767,17 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - std::fs::remove_file(TestBackend::hash_file_path( - dir.path(), - instance_id, - SnapshotId(0), - )) - .unwrap(); - let builder = TestKvsBuilder::new(instance_id) + let (_kvs_path, hash_path) = + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + std::fs::remove_file(hash_path).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); @@ -758,19 +789,17 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - std::fs::remove_file(TestBackend::kvs_file_path( - dir.path(), - instance_id, - SnapshotId(0), - )) - .unwrap(); - let builder = TestKvsBuilder::new(instance_id) + let (kvs_path, _hash_path) = + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + std::fs::remove_file(kvs_path).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -781,13 +810,15 @@ mod kvs_builder_tests { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); + let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); - create_kvs_files(dir.path(), instance_id, SnapshotId(0)).unwrap(); - let builder = TestKvsBuilder::new(instance_id) + create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .dir(dir_string); + .backend(Box::new( + JsonBackendBuilder::new().working_dir(dir_path).build(), + )); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Required); diff --git a/src/rust/rust_kvs/src/kvs_mock.rs b/src/rust/rust_kvs/src/kvs_mock.rs index 74184e9..730aa65 100644 --- a/src/rust/rust_kvs/src/kvs_mock.rs +++ b/src/rust/rust_kvs/src/kvs_mock.rs @@ -139,18 +139,6 @@ impl KvsApi for MockKvs { } Ok(()) } - fn get_kvs_filename(&self, _id: SnapshotId) -> Result { - if self.fail { - return Err(ErrorCode::UnmappedError); - } - Err(ErrorCode::FileNotFound) - } - fn get_hash_filename(&self, _id: SnapshotId) -> Result { - if self.fail { - return Err(ErrorCode::UnmappedError); - } - Err(ErrorCode::FileNotFound) - } } #[cfg(test)] @@ -190,8 +178,6 @@ mod tests { assert!(kvs_fail.reset_key("a").is_err()); assert!(kvs_fail.get_default_value("a").is_err()); assert!(kvs_fail.is_value_default("a").is_err()); - assert!(kvs_fail.get_kvs_filename(SnapshotId(0)).is_err()); - assert!(kvs_fail.get_hash_filename(SnapshotId(0)).is_err()); assert!(kvs_fail.snapshot_restore(SnapshotId(0)).is_err()); } } diff --git a/src/rust/rust_kvs/src/lib.rs b/src/rust/rust_kvs/src/lib.rs index c274b59..99afb13 100644 --- a/src/rust/rust_kvs/src/lib.rs +++ b/src/rust/rust_kvs/src/lib.rs @@ -132,24 +132,21 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] pub mod error_code; -mod json_backend; +pub mod json_backend; pub mod kvs; pub mod kvs_api; -mod kvs_backend; +pub mod kvs_backend; pub mod kvs_builder; pub mod kvs_mock; pub mod kvs_value; -use json_backend::JsonBackend; -pub type KvsBuilder = kvs_builder::GenericKvsBuilder; -pub type Kvs = kvs::GenericKvs; - /// Prelude module for convenient imports pub mod prelude { pub use crate::error_code::ErrorCode; - pub use crate::kvs::GenericKvs; + pub use crate::json_backend::{JsonBackend, JsonBackendBuilder}; + pub use crate::kvs::Kvs; pub use crate::kvs_api::{InstanceId, KvsApi, KvsDefaults, KvsLoad, SnapshotId}; - pub use crate::kvs_builder::GenericKvsBuilder; + pub use crate::kvs_backend::KvsBackend; + pub use crate::kvs_builder::KvsBuilder; pub use crate::kvs_value::{KvsMap, KvsValue}; - pub use crate::{Kvs, KvsBuilder}; } diff --git a/src/rust/rust_kvs_tool/src/kvs_tool.rs b/src/rust/rust_kvs_tool/src/kvs_tool.rs index 6540e2a..538e0ae 100644 --- a/src/rust/rust_kvs_tool/src/kvs_tool.rs +++ b/src/rust/rust_kvs_tool/src/kvs_tool.rs @@ -347,8 +347,21 @@ fn _getkvsfilename(kvs: Kvs, mut args: Arguments) -> Result<(), ErrorCode> { } }, }; + let instance_id = kvs.parameters().instance_id; let snapshot_id = SnapshotId(snapshot_id as usize); - let filename = kvs.get_kvs_filename(snapshot_id)?; + let backend = match kvs + .parameters() + .backend + .as_any() + .downcast_ref::() + { + Some(b) => b, + None => { + eprintln!("Failed to cast backend object!"); + return Err(ErrorCode::UnmappedError); + } + }; + let filename = backend.kvs_file_path(instance_id, snapshot_id); println!("KVS Filename: {}", filename.display()); println!("----------------------"); Ok(()) @@ -369,9 +382,22 @@ fn _gethashfilename(kvs: Kvs, mut args: Arguments) -> Result<(), ErrorCode> { } }, }; + let instance_id = kvs.parameters().instance_id; let snapshot_id = SnapshotId(snapshot_id as usize); - let filename = kvs.get_hash_filename(snapshot_id); - println!("Hash Filename: {}", filename?.display()); + let backend = match kvs + .parameters() + .backend + .as_any() + .downcast_ref::() + { + Some(b) => b, + None => { + eprintln!("Failed to cast backend object!"); + return Err(ErrorCode::UnmappedError); + } + }; + let filename = backend.hash_file_path(instance_id, snapshot_id); + println!("Hash Filename: {}", filename.display()); println!("----------------------"); Ok(()) } @@ -453,8 +479,8 @@ fn main() -> Result<(), ErrorCode> { Options: -h, --help Show this help message and exit - -o, --operation Specify the operation to perform (setkey, getkey, removekey, - listkeys, reset, snapshotcount, snapshotmaxcount, snapshotrestore, + -o, --operation Specify the operation to perform (setkey, getkey, removekey, + listkeys, reset, snapshotcount, snapshotmaxcount, snapshotrestore, getkvsfilename, gethashfilename, createtestdata) -k, --key Specify the key to operate on (for key operations) -p, --payload Specify the value to write (for set operations) @@ -521,7 +547,8 @@ fn main() -> Result<(), ErrorCode> { .kvs_load(KvsLoad::Optional); let builder = if let Some(dir) = directory { - builder.dir(dir) + let backend = Box::new(JsonBackendBuilder::new().working_dir(dir.into()).build()); + builder.backend(backend) } else { builder }; diff --git a/tests/python_test_cases/tests/test_cit_snapshots.py b/tests/python_test_cases/tests/test_cit_snapshots.py index 69361d6..5bc5684 100644 --- a/tests/python_test_cases/tests/test_cit_snapshots.py +++ b/tests/python_test_cases/tests/test_cit_snapshots.py @@ -282,8 +282,10 @@ def test_ok( paths_log = logs_info_level.find_log("kvs_path") assert paths_log is not None - assert paths_log.kvs_path == f'Ok("{temp_dir}/kvs_1_1.json")' - assert paths_log.hash_path == f'Ok("{temp_dir}/kvs_1_1.hash")' + assert paths_log.kvs_path == f'"{temp_dir}/kvs_1_1.json"' + assert paths_log.kvs_path_exists + assert paths_log.hash_path == f'"{temp_dir}/kvs_1_1.hash"' + assert paths_log.hash_path_exists @pytest.mark.PartiallyVerifies(["comp_req__persistency__snapshot_creation"]) @@ -308,6 +310,7 @@ def test_config(self, temp_dir: Path) -> dict[str, Any]: def test_error( self, + temp_dir: Path, results: ScenarioResult, logs_info_level: LogContainer, ): @@ -315,5 +318,7 @@ def test_error( paths_log = logs_info_level.find_log("kvs_path") assert paths_log is not None - assert paths_log.kvs_path == "Err(FileNotFound)" - assert paths_log.hash_path == "Err(FileNotFound)" + assert paths_log.kvs_path == f'"{temp_dir}/kvs_1_2.json"' + assert not paths_log.kvs_path_exists + assert paths_log.hash_path == f'"{temp_dir}/kvs_1_2.hash"' + assert not paths_log.hash_path_exists diff --git a/tests/rust_test_scenarios/src/cit/default_values.rs b/tests/rust_test_scenarios/src/cit/default_values.rs index 85d7bec..c6e7c51 100644 --- a/tests/rust_test_scenarios/src/cit/default_values.rs +++ b/tests/rust_test_scenarios/src/cit/default_values.rs @@ -1,6 +1,6 @@ use crate::helpers::kvs_instance::kvs_instance; use crate::helpers::kvs_parameters::KvsParameters; -use crate::helpers::to_str; +use crate::helpers::{kvs_hash_paths, to_str}; use rust_kvs::prelude::*; use test_scenarios_rust::scenario::{Scenario, ScenarioGroup, ScenarioGroupImpl}; use tracing::info; @@ -253,12 +253,7 @@ impl Scenario for Checksum { // Create instance, flush, store paths to files, close instance. let kvs = kvs_instance(params.clone()).expect("Failed to create KVS instance"); kvs.flush().expect("Failed to flush"); - kvs_path = kvs - .get_kvs_filename(SnapshotId(0)) - .expect("Failed to get KVS file path"); - hash_path = kvs - .get_hash_filename(SnapshotId(0)) - .expect("Failed to get hash file path"); + (kvs_path, hash_path) = kvs_hash_paths(&kvs, SnapshotId(0)); } info!( kvs_path = kvs_path.display().to_string(), diff --git a/tests/rust_test_scenarios/src/cit/multiple_kvs.rs b/tests/rust_test_scenarios/src/cit/multiple_kvs.rs index 3b17875..c964828 100644 --- a/tests/rust_test_scenarios/src/cit/multiple_kvs.rs +++ b/tests/rust_test_scenarios/src/cit/multiple_kvs.rs @@ -1,4 +1,5 @@ -use crate::helpers::{kvs_instance::kvs_instance, kvs_parameters::KvsParameters}; +use crate::helpers::kvs_instance::kvs_instance; +use crate::helpers::kvs_parameters::KvsParameters; use rust_kvs::prelude::KvsApi; use serde_json::Value; use test_scenarios_rust::scenario::{Scenario, ScenarioGroup, ScenarioGroupImpl}; diff --git a/tests/rust_test_scenarios/src/cit/persistency.rs b/tests/rust_test_scenarios/src/cit/persistency.rs index 2878fbd..92957de 100644 --- a/tests/rust_test_scenarios/src/cit/persistency.rs +++ b/tests/rust_test_scenarios/src/cit/persistency.rs @@ -1,6 +1,6 @@ use crate::helpers::kvs_instance::kvs_instance; use crate::helpers::kvs_parameters::KvsParameters; -use crate::helpers::to_str; +use crate::helpers::{kvs_hash_paths, to_str}; use rust_kvs::prelude::*; use test_scenarios_rust::scenario::{Scenario, ScenarioGroup, ScenarioGroupImpl}; use tracing::info; @@ -41,13 +41,12 @@ impl Scenario for ExplicitFlush { { // Second KVS instance object - used for flush check. let kvs = kvs_instance(params).expect("Failed to create KVS instance"); - - let snapshot_id = SnapshotId(0); - let kvs_path_result = kvs.get_kvs_filename(snapshot_id); - let hash_path_result = kvs.get_hash_filename(snapshot_id); + let (kvs_path, hash_path) = kvs_hash_paths(&kvs, SnapshotId(0)); info!( - kvs_path = format!("{kvs_path_result:?}"), - hash_path = format!("{hash_path_result:?}") + kvs_path = to_str(&kvs_path), + kvs_path_exists = kvs_path.exists(), + hash_path = to_str(&hash_path), + hash_path_exists = hash_path.exists(), ); // Get values. diff --git a/tests/rust_test_scenarios/src/cit/snapshots.rs b/tests/rust_test_scenarios/src/cit/snapshots.rs index 73227de..b8984f7 100644 --- a/tests/rust_test_scenarios/src/cit/snapshots.rs +++ b/tests/rust_test_scenarios/src/cit/snapshots.rs @@ -1,4 +1,6 @@ -use crate::helpers::{kvs_instance::kvs_instance, kvs_parameters::KvsParameters}; +use crate::helpers::kvs_instance::kvs_instance; +use crate::helpers::kvs_parameters::KvsParameters; +use crate::helpers::{kvs_hash_paths, to_str}; use rust_kvs::prelude::{KvsApi, SnapshotId}; use serde_json::Value; use test_scenarios_rust::scenario::{Scenario, ScenarioGroup, ScenarioGroupImpl}; @@ -124,12 +126,12 @@ impl Scenario for SnapshotPaths { { let kvs = kvs_instance(params).expect("Failed to create KVS instance"); - - let kvs_path_result = kvs.get_kvs_filename(SnapshotId(snapshot_id)); - let hash_path_result = kvs.get_hash_filename(SnapshotId(snapshot_id)); + let (kvs_path, hash_path) = kvs_hash_paths(&kvs, SnapshotId(snapshot_id)); info!( - kvs_path = format!("{kvs_path_result:?}"), - hash_path = format!("{hash_path_result:?}") + kvs_path = to_str(&kvs_path), + kvs_path_exists = kvs_path.exists(), + hash_path = to_str(&hash_path), + hash_path_exists = hash_path.exists(), ); } diff --git a/tests/rust_test_scenarios/src/helpers/kvs_instance.rs b/tests/rust_test_scenarios/src/helpers/kvs_instance.rs index 7c1d50d..c68920a 100644 --- a/tests/rust_test_scenarios/src/helpers/kvs_instance.rs +++ b/tests/rust_test_scenarios/src/helpers/kvs_instance.rs @@ -1,29 +1,42 @@ //! KVS instance test helpers. use crate::helpers::kvs_parameters::KvsParameters; -use rust_kvs::prelude::{ErrorCode, Kvs, KvsBuilder}; +use rust_kvs::prelude::{ErrorCode, JsonBackendBuilder, Kvs, KvsBuilder}; /// Create KVS instance based on provided parameters. pub fn kvs_instance(kvs_parameters: KvsParameters) -> Result { - let mut builder = KvsBuilder::new(kvs_parameters.instance_id); + let mut kvs_builder = KvsBuilder::new(kvs_parameters.instance_id); + // Set `defaults` mode. if let Some(flag) = kvs_parameters.defaults { - builder = builder.defaults(flag); + kvs_builder = kvs_builder.defaults(flag); } + // Set `kvs_load` mode. if let Some(flag) = kvs_parameters.kvs_load { - builder = builder.kvs_load(flag); + kvs_builder = kvs_builder.kvs_load(flag); } + // Set working directory - part of backend. + let mut backend_builder = JsonBackendBuilder::new(); + let mut set_backend = false; if let Some(dir) = kvs_parameters.dir { - builder = builder.dir(dir.to_string_lossy().to_string()); + backend_builder = backend_builder.working_dir(dir); + set_backend = true; } + // Set max number of snapshots - part of backend. if let Some(snapshot_max_count) = kvs_parameters.snapshot_max_count { - builder = builder.snapshot_max_count(snapshot_max_count); + backend_builder = backend_builder.snapshot_max_count(snapshot_max_count); + set_backend = true; } - let kvs: Kvs = builder.build()?; + // Set backend, if backend parameters were provided. + if set_backend { + kvs_builder = kvs_builder.backend(Box::new(backend_builder.build())); + } + + let kvs: Kvs = kvs_builder.build()?; Ok(kvs) } diff --git a/tests/rust_test_scenarios/src/helpers/mod.rs b/tests/rust_test_scenarios/src/helpers/mod.rs index 896a4ba..6794d29 100644 --- a/tests/rust_test_scenarios/src/helpers/mod.rs +++ b/tests/rust_test_scenarios/src/helpers/mod.rs @@ -1,3 +1,6 @@ +use rust_kvs::prelude::{JsonBackend, Kvs, SnapshotId}; +use std::path::PathBuf; + pub mod kvs_instance; pub mod kvs_parameters; @@ -5,3 +8,20 @@ pub mod kvs_parameters; pub(crate) fn to_str(value: &T) -> String { format!("{value:?}") } + +/// Helper function to get `JsonBackend` from KVS object. +pub(crate) fn json_backend(kvs: &Kvs) -> &JsonBackend { + let backend = &kvs.parameters().backend; + let cast_result = backend.as_any().downcast_ref::(); + cast_result.expect("Failed to cast backend to JsonBackend") +} + +/// Helper function to get KVS and hash file paths from KVS instance. +pub(crate) fn kvs_hash_paths(kvs: &Kvs, snapshot_id: SnapshotId) -> (PathBuf, PathBuf) { + let backend = json_backend(kvs); + let instance_id = kvs.parameters().instance_id; + let kvs_path = backend.kvs_file_path(instance_id, snapshot_id); + let hash_path = backend.hash_file_path(instance_id, snapshot_id); + + (kvs_path, hash_path) +} diff --git a/tests/rust_test_scenarios/src/test_basic.rs b/tests/rust_test_scenarios/src/test_basic.rs index 5f60b32..a5e7c82 100644 --- a/tests/rust_test_scenarios/src/test_basic.rs +++ b/tests/rust_test_scenarios/src/test_basic.rs @@ -1,3 +1,4 @@ +use crate::helpers::kvs_instance::kvs_instance; use crate::helpers::kvs_parameters::KvsParameters; use rust_kvs::prelude::*; use test_scenarios_rust::scenario::Scenario; @@ -22,20 +23,8 @@ impl Scenario for BasicScenario { let params = KvsParameters::from_json(input_string).expect("Failed to parse parameters"); - // Set builder parameters. - let mut builder = KvsBuilder::new(params.instance_id); - if let Some(flag) = params.defaults { - builder = builder.defaults(flag); - } - if let Some(flag) = params.kvs_load { - builder = builder.kvs_load(flag); - } - if let Some(dir) = params.dir { - builder = builder.dir(dir.to_string_lossy().to_string()); - } - // Create KVS. - let kvs: Kvs = builder.build().expect("Failed to build KVS instance"); + let kvs = kvs_instance(params).expect("Failed to create KVS instance"); // Simple set/get. let key = "example_key";