From 4030295ede4b9b1cd113efadfed55ab96b88f572 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 1/3] 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 | 539 +++++++----------- 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, 1044 insertions(+), 737 deletions(-) diff --git a/src/rust/rust_kvs/examples/basic.rs b/src/rust/rust_kvs/examples/basic.rs index 0f37b8cc..abe619f8 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 f02b3512..0c7ce5e4 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 2e68ba98..fd00450d 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 ba51b4b6..6647c226 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 9a300faf..272b2e6d 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,182 +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( - working_dir: PathBuf, - kvs_map: KvsMap, - defaults_map: KvsMap, - ) -> 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: 3, - }; - GenericKvs::::new(data, parameters) + 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)), @@ -637,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)), @@ -660,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)), @@ -676,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); @@ -684,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)), @@ -699,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)), @@ -713,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)), @@ -728,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"))]), ); @@ -742,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"))]), ); @@ -755,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)), @@ -770,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"))]), ); @@ -782,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"))]), ); @@ -795,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)), @@ -811,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"))]), ); @@ -824,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)), @@ -839,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)), @@ -855,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)), @@ -869,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)), @@ -883,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)), @@ -899,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"); @@ -907,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(), ); @@ -919,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)), @@ -934,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)), @@ -952,24 +826,31 @@ 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] 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); } @@ -977,7 +858,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); } @@ -986,7 +871,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); @@ -1000,7 +889,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); } @@ -1008,7 +901,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(); @@ -1022,7 +919,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(); @@ -1037,7 +938,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(); @@ -1052,7 +957,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(); @@ -1062,52 +971,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 fc46f54b..e7c66f35 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 2b22edf4..ae3141e2 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 bc12af6e..7cb726e0 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 74184e96..730aa65e 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 c274b591..99afb133 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 6540e2a7..538e0ae1 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 90388f60..03f3a082 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 85d7beca..c6e7c51c 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 3b178758..c964828b 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 2878fbd7..92957de5 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 73227de5..b8984f7d 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 7c1d50da..c68920ac 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 896a4baf..6794d29c 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 5f60b32f..a5e7c82d 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"; From 50384676f989f8cb0be3cf99cfc4b6abcfcff613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20J=C4=99drzejewski?= Date: Tue, 7 Oct 2025 10:49:37 +0000 Subject: [PATCH 2/3] impl: backends registry, `KvsMap` backend params - Added `KvsBackendRegistry`. - `KvsBuilder` accepts backend params through `KvsMap`. - Built-in backend access is now hidden. - `backend_registration` example. --- README.md | 6 - .../rust_kvs/examples/backend_registration.rs | 111 ++++++ src/rust/rust_kvs/examples/basic.rs | 13 +- src/rust/rust_kvs/examples/defaults.rs | 11 +- src/rust/rust_kvs/examples/snapshots.rs | 11 +- src/rust/rust_kvs/src/error_code.rs | 9 + src/rust/rust_kvs/src/json_backend.rs | 98 ++++- src/rust/rust_kvs/src/kvs.rs | 121 ++---- src/rust/rust_kvs/src/kvs_backend.rs | 32 +- src/rust/rust_kvs/src/kvs_backend_registry.rs | 235 ++++++++++++ src/rust/rust_kvs/src/kvs_builder.rs | 350 ++++++++++-------- src/rust/rust_kvs/src/lib.rs | 7 +- src/rust/rust_kvs_tool/src/kvs_tool.rs | 100 +---- .../src/cit/default_values.rs | 4 +- .../src/cit/persistency.rs | 4 +- .../rust_test_scenarios/src/cit/snapshots.rs | 7 +- .../src/helpers/kvs_instance.rs | 23 +- tests/rust_test_scenarios/src/helpers/mod.rs | 25 +- 18 files changed, 760 insertions(+), 407 deletions(-) create mode 100644 src/rust/rust_kvs/examples/backend_registration.rs create mode 100644 src/rust/rust_kvs/src/kvs_backend_registry.rs diff --git a/README.md b/README.md index 9ecaa885..cf93adb6 100644 --- a/README.md +++ b/README.md @@ -183,12 +183,6 @@ Snapshot Count: Snapshot Restore: kvs_tool -o snapshotrestore -s 1 -Get KVS Filename: - kvs_tool -o getkvsfilename -s 1 - -Get Hash Filename: - kvs_tool -o gethashfilename -s 1 - --------------------------------------- Create Test Data: diff --git a/src/rust/rust_kvs/examples/backend_registration.rs b/src/rust/rust_kvs/examples/backend_registration.rs new file mode 100644 index 00000000..11194a58 --- /dev/null +++ b/src/rust/rust_kvs/examples/backend_registration.rs @@ -0,0 +1,111 @@ +//! Example for custom backend registration. +//! - Implementation of `KvsBackend` traits. +//! - Registration of custom backend. +//! - Creation of KVS instance utilizing custom backend. + +use rust_kvs::prelude::*; +use tempfile::tempdir; + +/// Mock backend implementation. +/// Only `load_kvs` is implemented. +struct MockBackend; + +impl KvsBackend for MockBackend { + fn load_kvs( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + println!("`load_kvs` used"); + Ok(KvsMap::new()) + } + + fn load_defaults(&self, _instance_id: InstanceId) -> Result { + unimplemented!() + } + + fn flush(&self, _instance_id: InstanceId, _kvs_map: &KvsMap) -> Result<(), ErrorCode> { + unimplemented!() + } + + fn snapshot_count(&self, _instance_id: InstanceId) -> usize { + unimplemented!() + } + + fn snapshot_max_count(&self) -> usize { + unimplemented!() + } + + fn snapshot_restore( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + unimplemented!() + } +} + +/// Mock backend factory implementation. +struct MockBackendFactory; + +impl KvsBackendFactory for MockBackendFactory { + fn create(&self, _parameters: &KvsMap) -> Result, ErrorCode> { + Ok(Box::new(MockBackend)) + } +} + +fn main() -> Result<(), ErrorCode> { + // Temporary directory. + let dir = tempdir()?; + let dir_string = dir.path().to_string_lossy().to_string(); + + // Register `MockBackendFactory`. + KvsBackendRegistry::register("mock", || Box::new(MockBackendFactory))?; + + // Build KVS instance with mock backend. + { + let instance_id = InstanceId(0); + let parameters = KvsMap::from([("name".to_string(), KvsValue::String("mock".to_string()))]); + let builder = KvsBuilder::new(instance_id) + .backend_parameters(parameters) + .defaults(KvsDefaults::Ignored); + let kvs = builder.build()?; + + println!( + "KVS instance with mock backend - parameters: {:?}", + kvs.parameters() + ); + } + + // Build KVS instance with JSON backend - default parameters. + { + let instance_id = InstanceId(1); + let builder = KvsBuilder::new(instance_id).defaults(KvsDefaults::Ignored); + let kvs = builder.build()?; + + println!( + "KVS instance with default JSON backend - parameters: {:?}", + kvs.parameters() + ); + } + + // Build KVS instance with JSON backend - `working_dir` set. + { + let instance_id = InstanceId(2); + let parameters = KvsMap::from([ + ("name".to_string(), KvsValue::String("json".to_string())), + ("working_dir".to_string(), KvsValue::String(dir_string)), + ]); + let builder = KvsBuilder::new(instance_id) + .backend_parameters(parameters) + .defaults(KvsDefaults::Ignored); + let kvs = builder.build()?; + + println!( + "KVS instance with JSON backend - parameters: {:?}", + kvs.parameters() + ); + } + + Ok(()) +} diff --git a/src/rust/rust_kvs/examples/basic.rs b/src/rust/rust_kvs/examples/basic.rs index abe619f8..122f4070 100644 --- a/src/rust/rust_kvs/examples/basic.rs +++ b/src/rust/rust_kvs/examples/basic.rs @@ -8,10 +8,13 @@ use std::collections::HashMap; use tempfile::tempdir; fn main() -> Result<(), ErrorCode> { - // Temporary directory and common backend. + // Temporary directory. let dir = tempdir()?; - let dir_path = dir.path().to_path_buf(); - let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); + let dir_string = dir.path().to_string_lossy().to_string(); + let backend_parameters = KvsMap::from([ + ("name".to_string(), KvsValue::String("json".to_string())), + ("working_dir".to_string(), KvsValue::String(dir_string)), + ]); // Instance ID for KVS object instances. let instance_id = InstanceId(0); @@ -21,7 +24,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) - .backend(backend.clone()) + .backend_parameters(backend_parameters.clone()) .kvs_load(KvsLoad::Optional); let kvs = builder.build()?; @@ -66,7 +69,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) - .backend(backend) + .backend_parameters(backend_parameters) .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 0c7ce5e4..a2e356c5 100644 --- a/src/rust/rust_kvs/examples/defaults.rs +++ b/src/rust/rust_kvs/examples/defaults.rs @@ -31,10 +31,13 @@ fn create_defaults_file(dir_path: PathBuf, instance_id: InstanceId) -> Result<() } fn main() -> Result<(), ErrorCode> { - // Temporary directory and common backend. + // Temporary directory. let dir = tempdir()?; - let dir_path = dir.path().to_path_buf(); - let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); + let dir_string = dir.path().to_string_lossy().to_string(); + let backend_parameters = KvsMap::from([ + ("name".to_string(), KvsValue::String("json".to_string())), + ("working_dir".to_string(), KvsValue::String(dir_string)), + ]); // Instance ID for KVS object instances. let instance_id = InstanceId(0); @@ -45,7 +48,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) - .backend(backend.clone()) + .backend_parameters(backend_parameters) .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 fd00450d..0526f3d0 100644 --- a/src/rust/rust_kvs/examples/snapshots.rs +++ b/src/rust/rust_kvs/examples/snapshots.rs @@ -8,8 +8,11 @@ use tempfile::tempdir; fn main() -> Result<(), ErrorCode> { // Temporary directory and common backend. let dir = tempdir()?; - let dir_path = dir.path().to_path_buf(); - let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); + let dir_string = dir.path().to_string_lossy().to_string(); + let backend_parameters = KvsMap::from([ + ("name".to_string(), KvsValue::String("json".to_string())), + ("working_dir".to_string(), KvsValue::String(dir_string)), + ]); // Instance ID for KVS object instances. let instance_id = InstanceId(0); @@ -18,7 +21,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).backend(backend.clone()); + let builder = KvsBuilder::new(instance_id).backend_parameters(backend_parameters.clone()); let kvs = builder.build()?; let max_count = kvs.snapshot_max_count() as u32; @@ -39,7 +42,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).backend(backend.clone()); + let builder = KvsBuilder::new(instance_id).backend_parameters(backend_parameters); let kvs = builder.build()?; let max_count = kvs.snapshot_max_count() as u32; diff --git a/src/rust/rust_kvs/src/error_code.rs b/src/rust/rust_kvs/src/error_code.rs index bfb69c6b..1ecde940 100644 --- a/src/rust/rust_kvs/src/error_code.rs +++ b/src/rust/rust_kvs/src/error_code.rs @@ -82,6 +82,15 @@ pub enum ErrorCode { /// Instance parameters mismatch InstanceParametersMismatch, + + /// Requested unknown backend + UnknownBackend, + + /// Backend already registered + BackendAlreadyRegistered, + + /// Invalid backend parameters. + InvalidBackendParameters, } impl From for ErrorCode { diff --git a/src/rust/rust_kvs/src/json_backend.rs b/src/rust/rust_kvs/src/json_backend.rs index 6647c226..0153b54f 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; +use crate::kvs_backend::{KvsBackend, KvsBackendFactory}; use crate::kvs_value::{KvsMap, KvsValue}; use std::collections::HashMap; use std::fs; @@ -151,7 +151,7 @@ impl From for ErrorCode { } /// Builder for `JsonBackend`. -pub struct JsonBackendBuilder { +pub(crate) struct JsonBackendBuilder { working_dir: PathBuf, snapshot_max_count: usize, } @@ -190,7 +190,7 @@ impl Default for JsonBackendBuilder { /// KVS backend implementation based on TinyJSON. #[derive(Clone, PartialEq)] -pub struct JsonBackend { +pub(crate) struct JsonBackend { working_dir: PathBuf, snapshot_max_count: usize, } @@ -255,7 +255,7 @@ impl JsonBackend { ext.is_some_and(|ep| ep.to_str().is_some_and(|es| es == extension)) } - pub(super) fn load(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result { + pub(crate) fn load(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result { if !Self::check_extension(kvs_path, "json") { return Err(ErrorCode::KvsFileReadError); } @@ -299,7 +299,7 @@ impl JsonBackend { } } - pub(super) fn save( + pub(crate) fn save( kvs_map: &KvsMap, kvs_path: &Path, hash_path: Option<&PathBuf>, @@ -433,13 +433,41 @@ impl KvsBackend for JsonBackend { } } +/// `JsonBackend` factory. +pub(crate) struct JsonBackendFactory; + +impl KvsBackendFactory for JsonBackendFactory { + fn create(&self, parameters: &KvsMap) -> Result, ErrorCode> { + let mut builder = JsonBackendBuilder::new(); + + // Set working directory. + if let Some(working_dir) = parameters.get("working_dir") { + if let KvsValue::String(working_dir) = working_dir { + builder = builder.working_dir(PathBuf::from(working_dir)); + } else { + return Err(ErrorCode::InvalidBackendParameters); + } + } + + // Set snapshot max count. + if let Some(snapshot_max_count) = parameters.get("snapshot_max_count") { + if let KvsValue::U64(snapshot_max_count) = snapshot_max_count { + builder = builder.snapshot_max_count(*snapshot_max_count as usize); + } else { + return Err(ErrorCode::InvalidBackendParameters); + } + } + + Ok(Box::new(builder.build())) + } +} + #[cfg(test)] mod json_value_to_kvs_value_conversion_tests { + use crate::kvs_value::{KvsMap, KvsValue}; use std::collections::HashMap; use tinyjson::JsonValue; - use crate::prelude::{KvsMap, KvsValue}; - #[test] fn test_i32_ok() { let jv = JsonValue::from(HashMap::from([ @@ -1356,3 +1384,59 @@ mod kvs_backend_tests { assert!(result.is_err_and(|e| e == ErrorCode::InvalidSnapshotId)); } } + +#[cfg(test)] +mod kvs_backend_factory_tests { + use crate::error_code::ErrorCode; + use crate::json_backend::JsonBackendFactory; + use crate::kvs_backend::KvsBackendFactory; + use crate::kvs_value::{KvsMap, KvsValue}; + + #[test] + fn test_create_default_ok() { + let factory = JsonBackendFactory; + let params = KvsMap::new(); + let backend = factory.create(¶ms).unwrap(); + // `working_dir` is not exposed in the API. + assert_eq!(backend.snapshot_max_count(), 3); + } + + #[test] + fn test_create_params_ok() { + let factory = JsonBackendFactory; + let params = KvsMap::from([ + ( + "working_dir".to_string(), + KvsValue::String("/some/path/".to_string()), + ), + ("snapshot_max_count".to_string(), KvsValue::U64(1234)), + ]); + let backend = factory.create(¶ms).unwrap(); + // `working_dir` is not exposed in the API. + assert_eq!(backend.snapshot_max_count(), 1234); + } + + #[test] + fn test_create_working_dir_invalid_type() { + let factory = JsonBackendFactory; + let params = KvsMap::from([("working_dir".to_string(), KvsValue::Boolean(true))]); + let result = factory.create(¶ms); + assert!(result.is_err_and(|e| e == ErrorCode::InvalidBackendParameters)); + } + + #[test] + fn test_create_snapshot_max_count_invalid_type() { + let factory = JsonBackendFactory; + let params = KvsMap::from([("snapshot_max_count".to_string(), KvsValue::I32(-123))]); + let result = factory.create(¶ms); + assert!(result.is_err_and(|e| e == ErrorCode::InvalidBackendParameters)); + } + + #[test] + fn test_create_unknown_param_ok() { + let factory = JsonBackendFactory; + let params = KvsMap::from([("unknown_param".to_string(), KvsValue::I32(12345))]); + let result = factory.create(¶ms); + assert!(result.is_ok()); + } +} diff --git a/src/rust/rust_kvs/src/kvs.rs b/src/rust/rust_kvs/src/kvs.rs index 272b2e6d..c97cb5e1 100644 --- a/src/rust/rust_kvs/src/kvs.rs +++ b/src/rust/rust_kvs/src/kvs.rs @@ -17,6 +17,7 @@ use crate::kvs_value::{KvsMap, KvsValue}; use std::sync::{Arc, Mutex}; /// KVS instance parameters. +#[derive(Debug, PartialEq)] pub struct KvsParameters { /// Instance ID. pub instance_id: InstanceId, @@ -27,17 +28,8 @@ pub struct KvsParameters { /// KVS load mode. pub kvs_load: KvsLoad, - /// Backend. - pub backend: Box, -} - -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()) - } + /// Backend parameters. + pub backend_parameters: KvsMap, } /// Key-value-storage data @@ -47,13 +39,25 @@ pub struct Kvs { /// KVS instance parameters. parameters: Arc, + + /// Backend. + backend: Box, } impl Kvs { - pub(crate) fn new(data: Arc>, parameters: Arc) -> Self { - Self { data, parameters } + pub(crate) fn new( + data: Arc>, + parameters: Arc, + backend: Box, + ) -> Self { + Self { + data, + parameters, + backend, + } } + /// KVS instance parameters. pub fn parameters(&self) -> &KvsParameters { &self.parameters } @@ -291,8 +295,7 @@ impl KvsApi for Kvs { } let data = self.data.lock()?; - self.parameters - .backend + self.backend .flush(self.parameters.instance_id, &data.kvs_map) } @@ -301,9 +304,7 @@ impl KvsApi for Kvs { /// # Return Values /// * usize: Count of found snapshots fn snapshot_count(&self) -> usize { - self.parameters - .backend - .snapshot_count(self.parameters.instance_id) + self.backend.snapshot_count(self.parameters.instance_id) } /// Return maximum number of snapshots to store. @@ -311,7 +312,7 @@ impl KvsApi for Kvs { /// # Return Values /// * usize: Maximum count of snapshots fn snapshot_max_count(&self) -> usize { - self.parameters.backend.snapshot_max_count() + self.backend.snapshot_max_count() } /// Recover key-value-storage from snapshot @@ -335,76 +336,12 @@ impl KvsApi for Kvs { fn snapshot_restore(&self, snapshot_id: SnapshotId) -> Result<(), ErrorCode> { let mut data = self.data.lock()?; data.kvs_map = self - .parameters .backend .snapshot_restore(self.parameters.instance_id, snapshot_id)?; Ok(()) } } -#[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; - - #[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(), - ); - - 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)); - } - - #[test] - fn test_eq_diff() { - let instance_id = InstanceId(0); - let defaults = KvsDefaults::Optional; - let kvs_load = KvsLoad::Optional; - - 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; @@ -466,9 +403,9 @@ mod kvs_tests { instance_id, defaults: KvsDefaults::Optional, kvs_load: KvsLoad::Optional, - backend, + backend_parameters: KvsMap::new(), }); - Kvs::new(data, parameters) + Kvs::new(data, parameters, backend) } #[test] @@ -480,10 +417,16 @@ mod kvs_tests { #[test] fn test_parameters_ok() { 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!(kvs.parameters().backend.dyn_eq(&MockBackend)); + + // Assert params as expected. + let expected_parameters = KvsParameters { + instance_id: InstanceId(1), + defaults: KvsDefaults::Optional, + kvs_load: KvsLoad::Optional, + backend_parameters: KvsMap::new(), + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] diff --git a/src/rust/rust_kvs/src/kvs_backend.rs b/src/rust/rust_kvs/src/kvs_backend.rs index ae3141e2..547b8623 100644 --- a/src/rust/rust_kvs/src/kvs_backend.rs +++ b/src/rust/rust_kvs/src/kvs_backend.rs @@ -12,32 +12,9 @@ use crate::error_code::ErrorCode; use crate::kvs_api::{InstanceId, SnapshotId}; use crate::kvs_value::KvsMap; -use std::any::Any; - -pub trait DynEq: Any { - fn dyn_eq(&self, other: &dyn Any) -> bool; - fn as_any(&self) -> &dyn Any; -} - -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 - } -} /// KVS backend interface. -pub trait KvsBackend: DynEq + Sync + Send { +pub trait KvsBackend { /// Load KVS content. fn load_kvs( &self, @@ -65,3 +42,10 @@ pub trait KvsBackend: DynEq + Sync + Send { snapshot_id: SnapshotId, ) -> Result; } + +/// KVS backend factory interface. +/// New backends must be registered using [`KvsBackendRegistry`](crate::kvs_backend_registry::KvsBackendRegistry). +pub trait KvsBackendFactory { + /// Create backend. + fn create(&self, parameters: &KvsMap) -> Result, ErrorCode>; +} diff --git a/src/rust/rust_kvs/src/kvs_backend_registry.rs b/src/rust/rust_kvs/src/kvs_backend_registry.rs new file mode 100644 index 00000000..2de6291e --- /dev/null +++ b/src/rust/rust_kvs/src/kvs_backend_registry.rs @@ -0,0 +1,235 @@ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::error_code::ErrorCode; +use crate::kvs_backend::KvsBackendFactory; +use crate::kvs_value::{KvsMap, KvsValue}; +use std::collections::HashMap; +use std::sync::{LazyLock, Mutex, MutexGuard, PoisonError}; + +/// Function providing backend factory. +type KvsBackendFactoryFn = fn() -> Box; + +/// Map containing names as strings and factory-creating functions as values. +type BackendMap = HashMap; + +/// Provide map containing default backends. +fn default_backends() -> BackendMap { + let mut backends: BackendMap = HashMap::new(); + // Register JSON backend. + { + use crate::json_backend::JsonBackendFactory; + backends.insert("json".to_string(), || Box::new(JsonBackendFactory)); + } + + backends +} + +/// Pool containing registered backend factories. +static REGISTERED_BACKENDS: LazyLock> = + LazyLock::new(|| Mutex::new(default_backends())); + +impl From>> for ErrorCode { + fn from(_cause: PoisonError>) -> Self { + ErrorCode::MutexLockFailed + } +} + +/// KVS backend registry. +pub struct KvsBackendRegistry; + +impl KvsBackendRegistry { + /// Get registered backend using name. + pub(crate) fn from_name(name: &str) -> Result, ErrorCode> { + let registered_backends = REGISTERED_BACKENDS.lock()?; + + match registered_backends.get(name) { + Some(backend_factory_fn) => Ok(backend_factory_fn()), + None => Err(ErrorCode::UnknownBackend), + } + } + + /// Get registered backend using 'name' field from parameters. + pub(crate) fn from_parameters( + parameters: &KvsMap, + ) -> Result, ErrorCode> { + let name = match parameters.get("name") { + Some(value) => match value { + KvsValue::String(name) => Ok(name), + _ => Err(ErrorCode::InvalidBackendParameters), + }, + None => Err(ErrorCode::KeyNotFound), + }?; + + Self::from_name(name) + } + + /// Register new backend factory. + pub fn register(name: &str, backend_factory_fn: KvsBackendFactoryFn) -> Result<(), ErrorCode> { + let mut registered_backends = REGISTERED_BACKENDS.lock()?; + + // Check backend factory already registered. + if registered_backends.contains_key(name) { + return Err(ErrorCode::BackendAlreadyRegistered); + } + + // Insert backend factory. + registered_backends.insert(name.to_string(), backend_factory_fn); + Ok(()) + } +} + +#[cfg(test)] +mod registry_tests { + use crate::error_code::ErrorCode; + use crate::kvs_api::{InstanceId, SnapshotId}; + use crate::kvs_backend::{KvsBackend, KvsBackendFactory}; + use crate::kvs_backend_registry::{default_backends, KvsBackendRegistry, REGISTERED_BACKENDS}; + use crate::kvs_value::{KvsMap, KvsValue}; + use std::ops::DerefMut; + use std::sync::{LazyLock, Mutex, MutexGuard}; + + /// Serial test execution mutex. + static SERIAL_TEST: LazyLock> = LazyLock::new(|| Mutex::new(())); + + /// Execute test serially with registry initialized to default. + fn lock_and_reset<'a>() -> MutexGuard<'a, ()> { + // Tests in this group must be executed serially. + let serial_lock: MutexGuard<'a, ()> = SERIAL_TEST.lock().unwrap(); + + // Reset `REGISTERED_BACKENDS` state to default. + // This is to mitigate `BackendAlreadyRegistered` errors between tests. + let mut registry = REGISTERED_BACKENDS.lock().unwrap(); + *registry.deref_mut() = default_backends(); + + serial_lock + } + + /// Mock backend. + struct MockBackend { + parameters: KvsMap, + } + + impl KvsBackend for MockBackend { + /// `load_kvs` is reused to access parameters used by factory. + fn load_kvs( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + Ok(self.parameters.clone()) + } + + fn load_defaults(&self, _instance_id: InstanceId) -> Result { + unimplemented!() + } + + fn flush(&self, _instance_id: InstanceId, _kvs_map: &KvsMap) -> Result<(), ErrorCode> { + unimplemented!() + } + + fn snapshot_count(&self, _instance_id: InstanceId) -> usize { + unimplemented!() + } + + fn snapshot_max_count(&self) -> usize { + unimplemented!() + } + + fn snapshot_restore( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + unimplemented!() + } + } + + /// Mock backend factory. + struct MockBackendFactory; + + impl KvsBackendFactory for MockBackendFactory { + fn create(&self, parameters: &KvsMap) -> Result, ErrorCode> { + Ok(Box::new(MockBackend { + parameters: parameters.clone(), + })) + } + } + + #[test] + fn test_from_name_ok() { + let _lock = lock_and_reset(); + + let result = KvsBackendRegistry::from_name("json"); + assert!(result.is_ok()); + } + + #[test] + fn test_from_name_unknown() { + let _lock = lock_and_reset(); + + let result = KvsBackendRegistry::from_name("unknown"); + assert!(result.is_err_and(|e| e == ErrorCode::UnknownBackend)); + } + + #[test] + fn test_from_parameters_ok() { + let _lock = lock_and_reset(); + + let params = KvsMap::from([("name".to_string(), KvsValue::String("json".to_string()))]); + let result = KvsBackendRegistry::from_parameters(¶ms); + assert!(result.is_ok()); + } + + #[test] + fn test_from_parameters_unknown() { + let _lock = lock_and_reset(); + + let params = KvsMap::from([("name".to_string(), KvsValue::String("unknown".to_string()))]); + let result = KvsBackendRegistry::from_parameters(¶ms); + assert!(result.is_err_and(|e| e == ErrorCode::UnknownBackend)); + } + + #[test] + fn test_from_parameters_invalid_type() { + let _lock = lock_and_reset(); + + let params = KvsMap::from([("name".to_string(), KvsValue::I64(123))]); + let result = KvsBackendRegistry::from_parameters(¶ms); + assert!(result.is_err_and(|e| e == ErrorCode::InvalidBackendParameters)); + } + + #[test] + fn test_from_parameters_missing_name() { + let _lock = lock_and_reset(); + + let params = KvsMap::new(); + let result = KvsBackendRegistry::from_parameters(¶ms); + assert!(result.is_err_and(|e| e == ErrorCode::KeyNotFound)); + } + + #[test] + fn test_register_ok() { + let _lock = lock_and_reset(); + + let result = KvsBackendRegistry::register("mock", || Box::new(MockBackendFactory)); + assert!(result.is_ok()); + } + + #[test] + fn test_register_already_registered() { + let _lock = lock_and_reset(); + + KvsBackendRegistry::register("mock", || Box::new(MockBackendFactory)).unwrap(); + let result = KvsBackendRegistry::register("mock", || Box::new(MockBackendFactory)); + assert!(result.is_err_and(|e| e == ErrorCode::BackendAlreadyRegistered)) + } +} diff --git a/src/rust/rust_kvs/src/kvs_builder.rs b/src/rust/rust_kvs/src/kvs_builder.rs index 7cb726e0..831c37a9 100644 --- a/src/rust/rust_kvs/src/kvs_builder.rs +++ b/src/rust/rust_kvs/src/kvs_builder.rs @@ -10,11 +10,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error_code::ErrorCode; -use crate::json_backend::JsonBackendBuilder; use crate::kvs::{Kvs, KvsParameters}; use crate::kvs_api::{InstanceId, KvsDefaults, KvsLoad, SnapshotId}; use crate::kvs_backend::KvsBackend; -use crate::kvs_value::KvsMap; +use crate::kvs_backend_registry::KvsBackendRegistry; +use crate::kvs_value::{KvsMap, KvsValue}; use std::sync::{Arc, LazyLock, Mutex, MutexGuard, PoisonError}; /// Maximum number of instances. @@ -76,7 +76,10 @@ impl KvsBuilder { instance_id, defaults: KvsDefaults::Optional, kvs_load: KvsLoad::Optional, - backend: Box::new(JsonBackendBuilder::new().build()), + backend_parameters: KvsMap::from([( + "name".to_string(), + KvsValue::String("json".to_string()), + )]), }; Self { parameters } @@ -114,18 +117,23 @@ impl KvsBuilder { self } - /// Set backend used by KVS instance. + /// Set backend parameters. /// /// # Parameters - /// # `backend`: KVS backend (default: [`JsonBackend`](JsonBackend)) + /// * `parameters`: KVS backend parameters. /// /// # Return Values /// * KvsBuilder instance - pub fn backend(mut self, backend: Box) -> Self { - self.parameters.backend = backend; + pub fn backend_parameters(mut self, parameters: KvsMap) -> Self { + self.parameters.backend_parameters = parameters; self } + fn create_backend(backend_parameters: &KvsMap) -> Result, ErrorCode> { + let factory = KvsBackendRegistry::from_parameters(backend_parameters)?; + factory.create(backend_parameters) + } + /// Finalize the builder and open the key-value-storage /// /// Calls `Kvs::open` with the configured settings. @@ -146,7 +154,6 @@ impl KvsBuilder { let parameters = Arc::new(self.parameters); let instance_id = parameters.instance_id; let instance_id_index: usize = instance_id.into(); - let backend = ¶meters.backend; // Check if instance already exists. { @@ -170,13 +177,18 @@ impl KvsBuilder { // Return existing instance if initialized. if let Some(kvs_inner) = kvs_inner_option { + let backend = Self::create_backend(¶meters.backend_parameters)?; return Ok(Kvs::new( kvs_inner.data.clone(), kvs_inner.parameters.clone(), + backend, )); } } + // Initialize backend. + let backend = Self::create_backend(¶meters.backend_parameters)?; + // Initialize KVS instance with provided parameters. // Load defaults. let defaults_map = match parameters.defaults { @@ -225,7 +237,7 @@ impl KvsBuilder { }); } - Ok(Kvs::new(data, parameters)) + Ok(Kvs::new(data, parameters, backend)) } } @@ -234,6 +246,7 @@ mod kvs_builder_tests { // Tests reuse JSON backend to ensure valid load/save behavior. use crate::error_code::ErrorCode; use crate::json_backend::{JsonBackend, JsonBackendBuilder}; + use crate::kvs::KvsParameters; use crate::kvs_api::{InstanceId, KvsDefaults, KvsLoad, SnapshotId}; use crate::kvs_builder::{KvsBuilder, KVS_MAX_INSTANCES, KVS_POOL}; use crate::kvs_value::{KvsMap, KvsValue}; @@ -258,6 +271,58 @@ mod kvs_builder_tests { serial_lock } + /// Create `KvsMap` based on provided parameters. + struct BackendParametersBuilder { + name: KvsValue, + working_dir: Option, + snapshot_max_count: Option, + } + + impl BackendParametersBuilder { + pub fn new() -> Self { + Self { + name: KvsValue::String("json".to_string()), + working_dir: None, + snapshot_max_count: None, + } + } + + #[allow(dead_code)] + pub fn name(mut self, name: String) -> Self { + self.name = KvsValue::String(name); + self + } + + pub fn working_dir(mut self, working_dir: PathBuf) -> Self { + self.working_dir = Some(KvsValue::String(working_dir.to_string_lossy().to_string())); + self + } + + pub fn snapshot_max_count(mut self, snapshot_max_count: usize) -> Self { + self.snapshot_max_count = Some(KvsValue::U64(snapshot_max_count as u64)); + self + } + + pub fn build(self) -> KvsMap { + let mut backend_parameters = KvsMap::new(); + + // Set name. + backend_parameters.insert("name".to_string(), self.name); + + // Set working directory. + if let Some(working_dir) = self.working_dir { + backend_parameters.insert("working_dir".to_string(), working_dir); + } + + // Set snapshot max count. + if let Some(snapshot_max_count) = self.snapshot_max_count { + backend_parameters.insert("snapshot_max_count".to_string(), snapshot_max_count); + } + + backend_parameters + } + } + #[test] fn test_new_ok() { let _lock = lock_and_reset(); @@ -280,14 +345,15 @@ mod kvs_builder_tests { 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!(kvs - .parameters() - .backend - .dyn_eq(&JsonBackendBuilder::new().build())); + // Assert params as expected. + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Optional, + kvs_load: KvsLoad::Optional, + backend_parameters: BackendParametersBuilder::new().build(), + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -297,13 +363,16 @@ mod kvs_builder_tests { let instance_id = InstanceId(1); 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!(kvs - .parameters() - .backend - .dyn_eq(&JsonBackendBuilder::new().build())); + + // Assert params as expected. + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Ignored, + kvs_load: KvsLoad::Optional, + backend_parameters: BackendParametersBuilder::new().build(), + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -313,55 +382,42 @@ mod kvs_builder_tests { let instance_id = InstanceId(1); 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!(kvs - .parameters() - .backend - .dyn_eq(&JsonBackendBuilder::new().build())); + + // Assert params as expected. + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Optional, + kvs_load: KvsLoad::Ignored, + backend_parameters: BackendParametersBuilder::new().build(), + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] - fn test_parameters_backend() { + fn test_parameters_backend_parameters() { let _lock = lock_and_reset(); let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(5); - let builder = KvsBuilder::new(instance_id).backend(Box::new( - JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .build(), - )); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .snapshot_max_count(1234) + .build(); + let builder = KvsBuilder::new(instance_id).backend_parameters(backend_parameters.clone()); 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!(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(); + // Assert params as expected. + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Optional, + kvs_load: KvsLoad::Optional, + backend_parameters, + }; - 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!(kvs - .parameters() - .backend - .dyn_eq(&JsonBackendBuilder::new().snapshot_max_count(1234).build())); + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -372,25 +428,24 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(1); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .snapshot_max_count(1234) + .build(); let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .backend(Box::new( - JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .snapshot_max_count(1234) - .build(), - )); + .backend_parameters(backend_parameters.clone()); 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!(kvs.parameters().backend.dyn_eq( - &JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .snapshot_max_count(1234) - .build() - )); + + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Ignored, + kvs_load: KvsLoad::Ignored, + backend_parameters, + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -411,34 +466,30 @@ mod kvs_builder_tests { // Create two instances with same parameters. let instance_id = InstanceId(1); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); let builder1 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .backend(Box::new( - JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .build(), - )); + .backend_parameters(backend_parameters.clone()); let _ = builder1.build().unwrap(); let builder2 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .backend(Box::new( - JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .build(), - )); + .backend_parameters(backend_parameters.clone()); 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!(kvs - .parameters() - .backend - .dyn_eq(&JsonBackendBuilder::new().working_dir(dir_path).build())); + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Ignored, + kvs_load: KvsLoad::Ignored, + backend_parameters, + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -450,24 +501,19 @@ mod kvs_builder_tests { // Create two instances with same parameters. let instance_id = InstanceId(1); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); let builder1 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Optional) - .backend(Box::new( - JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .build(), - )); + .backend_parameters(backend_parameters.clone()); let _ = builder1.build().unwrap(); let builder2 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) .kvs_load(KvsLoad::Ignored) - .backend(Box::new( - JsonBackendBuilder::new() - .working_dir(dir_path.clone()) - .build(), - )); + .backend_parameters(backend_parameters.clone()); let result = builder2.build(); assert!(result.is_err_and(|e| e == ErrorCode::InstanceParametersMismatch)); @@ -530,12 +576,13 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); create_defaults_file(&dir_path, instance_id).unwrap(); let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Ignored); @@ -553,11 +600,12 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); @@ -575,12 +623,13 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); create_defaults_file(&dir_path, instance_id).unwrap(); let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); @@ -598,11 +647,12 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Required) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -616,12 +666,13 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); create_defaults_file(&dir_path, instance_id).unwrap(); let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Required) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Required); @@ -639,12 +690,13 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Ignored) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Ignored); @@ -662,11 +714,12 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); @@ -685,14 +738,15 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); 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) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); @@ -707,14 +761,15 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); 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) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -728,12 +783,13 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); @@ -751,11 +807,12 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -770,14 +827,15 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); 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) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); @@ -792,14 +850,15 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); 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) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -813,12 +872,13 @@ mod kvs_builder_tests { let dir_path = dir.path().to_path_buf(); let instance_id = InstanceId(2); + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); create_kvs_files(&dir_path, instance_id, SnapshotId(0)).unwrap(); let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .backend(Box::new( - JsonBackendBuilder::new().working_dir(dir_path).build(), - )); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Required); diff --git a/src/rust/rust_kvs/src/lib.rs b/src/rust/rust_kvs/src/lib.rs index 99afb133..8a97caca 100644 --- a/src/rust/rust_kvs/src/lib.rs +++ b/src/rust/rust_kvs/src/lib.rs @@ -132,10 +132,11 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] pub mod error_code; -pub mod json_backend; +mod json_backend; pub mod kvs; pub mod kvs_api; pub mod kvs_backend; +pub mod kvs_backend_registry; pub mod kvs_builder; pub mod kvs_mock; pub mod kvs_value; @@ -143,10 +144,10 @@ pub mod kvs_value; /// Prelude module for convenient imports pub mod prelude { pub use crate::error_code::ErrorCode; - 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_backend::KvsBackend; + pub use crate::kvs_backend::{KvsBackend, KvsBackendFactory}; + pub use crate::kvs_backend_registry::KvsBackendRegistry; pub use crate::kvs_builder::KvsBuilder; pub use crate::kvs_value::{KvsMap, KvsValue}; } diff --git a/src/rust/rust_kvs_tool/src/kvs_tool.rs b/src/rust/rust_kvs_tool/src/kvs_tool.rs index 538e0ae1..7443c575 100644 --- a/src/rust/rust_kvs_tool/src/kvs_tool.rs +++ b/src/rust/rust_kvs_tool/src/kvs_tool.rs @@ -59,12 +59,6 @@ //! Snapshot Restore: //! kvs_tool -o snapshotrestore -s 1 //! -//! Get KVS Filename: -//! kvs_tool -o getkvsfilename -s 1 -//! -//! Get Hash Filename: -//! kvs_tool -o gethashfilename -s 1 -//! //! --------------------------------------- //! //! Create Test Data: @@ -89,8 +83,6 @@ enum OperationMode { SnapshotCount, SnapshotMaxCount, SnapshotRestore, - GetKvsFilename, - GetHashFilename, CreateTestData, } @@ -333,75 +325,6 @@ fn _snapshotrestore(kvs: Kvs, mut args: Arguments) -> Result<(), ErrorCode> { Ok(()) } -/// Retrieves the KVS filename for a given snapshot ID. -fn _getkvsfilename(kvs: Kvs, mut args: Arguments) -> Result<(), ErrorCode> { - println!("----------------------"); - println!("Get KVS Filename"); - let snapshot_id: u32 = match args.opt_value_from_str("--snapshotid") { - Ok(Some(val)) => val, - Ok(None) | Err(_) => match args.opt_value_from_str("-s") { - Ok(Some(val)) => val, - _ => { - eprintln!("Error: Snapshot ID (-s or --snapshotid) needs to be specified!"); - return Err(ErrorCode::UnmappedError); - } - }, - }; - let instance_id = kvs.parameters().instance_id; - let snapshot_id = SnapshotId(snapshot_id as usize); - 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(()) -} - -/// Retrieves the hash filename for a given snapshot ID. -fn _gethashfilename(kvs: Kvs, mut args: Arguments) -> Result<(), ErrorCode> { - println!("----------------------"); - println!("Get Hash Filename"); - - let snapshot_id: u32 = match args.opt_value_from_str("--snapshotid") { - Ok(Some(val)) => val, - Ok(None) | Err(_) => match args.opt_value_from_str("-s") { - Ok(Some(val)) => val, - _ => { - eprintln!("Error: Snapshot ID (-s or --snapshotid) needs to be specified!"); - return Err(ErrorCode::UnmappedError); - } - }, - }; - let instance_id = kvs.parameters().instance_id; - let snapshot_id = SnapshotId(snapshot_id as usize); - 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(()) -} - /// Creates test data in the KVS based on the example code from the KVS. fn _createtestdata(kvs: Kvs) -> Result<(), ErrorCode> { println!("----------------------"); @@ -517,12 +440,6 @@ fn main() -> Result<(), ErrorCode> { Snapshot Restore: kvs_tool -o snapshotrestore -s 1 - Get KVS Filename: - kvs_tool -o getkvsfilename -s 1 - - Get Hash Filename: - kvs_tool -o gethashfilename -s 1 - --------------------------------------- Create Test Data: @@ -547,8 +464,11 @@ fn main() -> Result<(), ErrorCode> { .kvs_load(KvsLoad::Optional); let builder = if let Some(dir) = directory { - let backend = Box::new(JsonBackendBuilder::new().working_dir(dir.into()).build()); - builder.backend(backend) + let backend_parameters = KvsMap::from([ + ("name".to_string(), KvsValue::String("json".to_string())), + ("name".to_string(), KvsValue::String(dir)), + ]); + builder.backend_parameters(backend_parameters) } else { builder }; @@ -584,8 +504,6 @@ fn main() -> Result<(), ErrorCode> { "snapshotcount" => OperationMode::SnapshotCount, "snapshotmaxcount" => OperationMode::SnapshotMaxCount, "snapshotrestore" => OperationMode::SnapshotRestore, - "getkvsfilename" => OperationMode::GetKvsFilename, - "gethashfilename" => OperationMode::GetHashFilename, _ => OperationMode::Invalid, }, None => OperationMode::Invalid, @@ -624,14 +542,6 @@ fn main() -> Result<(), ErrorCode> { _snapshotrestore(kvs, args)?; Ok(()) } - OperationMode::GetKvsFilename => { - _getkvsfilename(kvs, args)?; - Ok(()) - } - OperationMode::GetHashFilename => { - _gethashfilename(kvs, args)?; - Ok(()) - } OperationMode::CreateTestData => { _createtestdata(kvs)?; Ok(()) diff --git a/tests/rust_test_scenarios/src/cit/default_values.rs b/tests/rust_test_scenarios/src/cit/default_values.rs index c6e7c51c..d7567f22 100644 --- a/tests/rust_test_scenarios/src/cit/default_values.rs +++ b/tests/rust_test_scenarios/src/cit/default_values.rs @@ -247,13 +247,15 @@ impl Scenario for Checksum { // Create KVS instance with provided params. let input_string = input.as_ref().expect("Test input is expected"); let params = KvsParameters::from_json(input_string).expect("Failed to parse parameters"); + let working_dir = params.dir.clone().expect("Working directory must be set"); let kvs_path; let hash_path; { // 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, hash_path) = kvs_hash_paths(&kvs, SnapshotId(0)); + (kvs_path, hash_path) = + kvs_hash_paths(&working_dir, kvs.parameters().instance_id, SnapshotId(0)); } info!( kvs_path = kvs_path.display().to_string(), diff --git a/tests/rust_test_scenarios/src/cit/persistency.rs b/tests/rust_test_scenarios/src/cit/persistency.rs index 92957de5..a704db40 100644 --- a/tests/rust_test_scenarios/src/cit/persistency.rs +++ b/tests/rust_test_scenarios/src/cit/persistency.rs @@ -25,6 +25,7 @@ impl Scenario for ExplicitFlush { // Check parameters. let input_string = input.as_ref().expect("Test input is expected"); let params = KvsParameters::from_json(input_string).expect("Failed to parse parameters"); + let working_dir = params.dir.clone().expect("Working directory must be set"); { // First KVS instance object - used for setting and flushing data. let kvs = kvs_instance(params.clone()).expect("Failed to create KVS instance"); @@ -41,7 +42,8 @@ impl Scenario for ExplicitFlush { { // Second KVS instance object - used for flush check. let kvs = kvs_instance(params).expect("Failed to create KVS instance"); - let (kvs_path, hash_path) = kvs_hash_paths(&kvs, SnapshotId(0)); + let (kvs_path, hash_path) = + kvs_hash_paths(&working_dir, kvs.parameters().instance_id, SnapshotId(0)); info!( kvs_path = to_str(&kvs_path), kvs_path_exists = kvs_path.exists(), diff --git a/tests/rust_test_scenarios/src/cit/snapshots.rs b/tests/rust_test_scenarios/src/cit/snapshots.rs index b8984f7d..9534e3a0 100644 --- a/tests/rust_test_scenarios/src/cit/snapshots.rs +++ b/tests/rust_test_scenarios/src/cit/snapshots.rs @@ -114,6 +114,7 @@ impl Scenario for SnapshotPaths { let snapshot_id = serde_json::from_value(v["snapshot_id"].clone()) .expect("Failed to parse \"snapshot_id\" field"); let params = KvsParameters::from_value(&v).expect("Failed to parse parameters"); + let working_dir = params.dir.clone().expect("Working directory must be set"); // Create snapshots. for i in 0..count { @@ -126,7 +127,11 @@ impl Scenario for SnapshotPaths { { let kvs = kvs_instance(params).expect("Failed to create KVS instance"); - let (kvs_path, hash_path) = kvs_hash_paths(&kvs, SnapshotId(snapshot_id)); + let (kvs_path, hash_path) = kvs_hash_paths( + &working_dir, + kvs.parameters().instance_id, + SnapshotId(snapshot_id), + ); info!( kvs_path = to_str(&kvs_path), kvs_path_exists = kvs_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 c68920ac..90654fd3 100644 --- a/tests/rust_test_scenarios/src/helpers/kvs_instance.rs +++ b/tests/rust_test_scenarios/src/helpers/kvs_instance.rs @@ -1,7 +1,7 @@ //! KVS instance test helpers. use crate::helpers::kvs_parameters::KvsParameters; -use rust_kvs::prelude::{ErrorCode, JsonBackendBuilder, Kvs, KvsBuilder}; +use rust_kvs::prelude::{ErrorCode, Kvs, KvsBuilder, KvsMap, KvsValue}; /// Create KVS instance based on provided parameters. pub fn kvs_instance(kvs_parameters: KvsParameters) -> Result { @@ -17,23 +17,32 @@ pub fn kvs_instance(kvs_parameters: KvsParameters) -> Result { kvs_builder = kvs_builder.kvs_load(flag); } - // Set working directory - part of backend. - let mut backend_builder = JsonBackendBuilder::new(); + // Set `backend_parameters`. + let mut backend_parameters = + KvsMap::from([("name".to_string(), KvsValue::String("json".to_string()))]); let mut set_backend = false; + + // Set working directory. if let Some(dir) = kvs_parameters.dir { - backend_builder = backend_builder.working_dir(dir); + backend_parameters.insert( + "working_dir".to_string(), + KvsValue::String(dir.to_string_lossy().to_string()), + ); set_backend = true; } - // Set max number of snapshots - part of backend. + // Set max number of snapshots. if let Some(snapshot_max_count) = kvs_parameters.snapshot_max_count { - backend_builder = backend_builder.snapshot_max_count(snapshot_max_count); + backend_parameters.insert( + "snapshot_max_count".to_string(), + KvsValue::U64(snapshot_max_count as u64), + ); set_backend = true; } // Set backend, if backend parameters were provided. if set_backend { - kvs_builder = kvs_builder.backend(Box::new(backend_builder.build())); + kvs_builder = kvs_builder.backend_parameters(backend_parameters); } let kvs: Kvs = kvs_builder.build()?; diff --git a/tests/rust_test_scenarios/src/helpers/mod.rs b/tests/rust_test_scenarios/src/helpers/mod.rs index 6794d29c..8e92af6a 100644 --- a/tests/rust_test_scenarios/src/helpers/mod.rs +++ b/tests/rust_test_scenarios/src/helpers/mod.rs @@ -1,5 +1,5 @@ -use rust_kvs::prelude::{JsonBackend, Kvs, SnapshotId}; -use std::path::PathBuf; +use rust_kvs::prelude::{InstanceId, SnapshotId}; +use std::path::{Path, PathBuf}; pub mod kvs_instance; pub mod kvs_parameters; @@ -9,19 +9,14 @@ 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); +/// Helper function to get expected KVS and hash file paths. +pub(crate) fn kvs_hash_paths( + working_dir: &Path, + instance_id: InstanceId, + snapshot_id: SnapshotId, +) -> (PathBuf, PathBuf) { + let kvs_path = working_dir.join(format!("kvs_{instance_id}_{snapshot_id}.json")); + let hash_path = working_dir.join(format!("kvs_{instance_id}_{snapshot_id}.hash")); (kvs_path, hash_path) } From 7f7b950015a4eebae1e42609dabeda3b1c3fb750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20J=C4=99drzejewski?= Date: Thu, 9 Oct 2025 09:50:54 +0000 Subject: [PATCH 3/3] impl: backend migration example Example on how migration between backends can be performed. --- src/rust/rust_kvs/examples/migration.rs | 98 +++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/rust/rust_kvs/examples/migration.rs diff --git a/src/rust/rust_kvs/examples/migration.rs b/src/rust/rust_kvs/examples/migration.rs new file mode 100644 index 00000000..19423fe5 --- /dev/null +++ b/src/rust/rust_kvs/examples/migration.rs @@ -0,0 +1,98 @@ +//! Example for migrations between backends. + +use rust_kvs::prelude::*; + +/// Example custom backend. +/// Returns some data on `load_kvs`. +struct FromBackend; + +impl KvsBackend for FromBackend { + fn load_kvs( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + Ok(KvsMap::from([( + "example_key".to_string(), + KvsValue::String("example_value".to_string()), + )])) + } + + fn load_defaults(&self, _instance_id: InstanceId) -> Result { + unimplemented!() + } + + fn flush(&self, _instance_id: InstanceId, _kvs_map: &KvsMap) -> Result<(), ErrorCode> { + unimplemented!() + } + + fn snapshot_count(&self, _instance_id: InstanceId) -> usize { + unimplemented!() + } + + fn snapshot_max_count(&self) -> usize { + unimplemented!() + } + + fn snapshot_restore( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + unimplemented!() + } +} + +/// Example custom backend. +/// Prints provided data to stdout. +struct ToBackend; + +impl KvsBackend for ToBackend { + fn load_kvs( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + unimplemented!() + } + + fn load_defaults(&self, _instance_id: InstanceId) -> Result { + unimplemented!() + } + + fn flush(&self, _instance_id: InstanceId, kvs_map: &KvsMap) -> Result<(), ErrorCode> { + println!("{kvs_map:?}"); + Ok(()) + } + + fn snapshot_count(&self, _instance_id: InstanceId) -> usize { + unimplemented!() + } + + fn snapshot_max_count(&self) -> usize { + unimplemented!() + } + + fn snapshot_restore( + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, + ) -> Result { + unimplemented!() + } +} + +fn main() -> Result<(), ErrorCode> { + // Load `KvsMap` from first backend. + // `instance_id` and `snapshot_id` normally must be provided, but aren't used in this example. + let from_backend = FromBackend; + let instance_id = InstanceId(0); + let snapshot_id = SnapshotId(0); + let data = from_backend.load_kvs(instance_id, snapshot_id).unwrap(); + + // Save `KvsMap` in second backend. + let to_backend = ToBackend; + to_backend.flush(instance_id, &data).unwrap(); + + Ok(()) +}