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/docs/class_diagram.puml b/docs/class_diagram.puml new file mode 100644 index 00000000..0ae07174 --- /dev/null +++ b/docs/class_diagram.puml @@ -0,0 +1,124 @@ +@startuml Persistency class diagram + +package "kvs_builder" { + ~class KvsInner { + ~parameters: KvsParameters + ~data: KvsData + } + + +class KvsBuilder { + {static} -KVS_POOL: list + -parameters: KvsParameters + + +new(instance_id: InstanceId): KvsBuilder + {static} +max_instances(): usize + +defaults(mode: KvsDefaults): KvsBuilder + +kvs_load(mode: KvsLoad): KvsBuilder + +backend_parameters(parameters: KvsMap): KvsBuilder + +build(): Kvs + } + + KvsInner <-- KvsBuilder +} + +package "kvs_api" { + +interface KvsApi { + +reset() + +reset_key(key: string) + +get_all_keys(): list + +key_exists(key: string): bool + +get_value(key: string): KvsValue + +get_value_as(key: string): T + +get_default_value(key: string): KvsValue + +is_value_default(key: string): bool + +set_value(key: string, value: T) + +remove_key(key: string) + +flush() + +snapshot_count(): usize + +snapshot_max_count(): usize + +snapshot_restore(snapshot_id: SnapshotId) + } +} + +package "kvs" { + +class Kvs { + -data: KvsData + -parameters: KvsParameters + -backend: KvsBackend + + ~new(data: KvsData, parameters: KvsParameters, backend: KvsBackend): Kvs + +parameters(): KvsParameters + + +reset() + +reset_key(key: string) + +get_all_keys(): list + +key_exists(key: string): bool + +get_value(key: string): KvsValue + +get_value_as(key: string): T + +get_default_value(key: string): KvsValue + +is_value_default(key: string): bool + +set_value(key: string, value: T) + +remove_key(key: string) + +flush() + +snapshot_count(): usize + +snapshot_max_count(): usize + +snapshot_restore(snapshot_id: SnapshotId) + } +} + +KvsApi <|.. Kvs +KvsBuilder -- Kvs + +package "kvs_backend_registry" { + +class KvsBackendRegistry { + {static} -REGISTERED_BACKENDS: map + + {static} ~from_name(name: string) -> Result<&Box, ErrorCode> + {static} ~from_parameters(parameters: KvsMap) -> Result<&Box, ErrorCode> + + {static} +register(backend_factory: KvsBackendFactory) + } +} + +KvsBackendRegistry .. KvsBuilder + +package "kvs_backend" { + +interface KvsBackend { + +load_kvs(instance_id: InstanceId, snapshot_id: SnapshotId): KvsMap + +load_defaults(instance_id: InstanceId): KvsMap + +flush(instance_id: InstanceId, kvs_map: KvsMap) + +snapshot_count(instance_id: InstanceId): usize + +snapshot_max_count(): usize + +snapshot_restore(instance_id: InstanceId, snapshot_id: SnapshotId): KvsMap + } + + +interface KvsBackendFactory { + +create(parameters: KvsMap): KvsBackend + } +} + +package "json_backend" { + ~class JsonBackend { + +load_kvs(instance_id: InstanceId, snapshot_id: SnapshotId): KvsMap + +load_defaults(instance_id: InstanceId): KvsMap + +flush(instance_id: InstanceId, kvs_map: KvsMap) + +snapshot_count(instance_id: InstanceId): usize + +snapshot_max_count(): usize + +snapshot_restore(instance_id: InstanceId, snapshot_id: SnapshotId): KvsMap + } + + ~class JsonBackendFactory { + +create(parameters: KvsMap): KvsBackend + } +} + +KvsBackend <-- Kvs + +KvsBackendRegistry --> KvsBackendFactory +KvsBackendFactory -- KvsBackend + +KvsBackend <|.. JsonBackend +KvsBackendFactory <|.. JsonBackendFactory +JsonBackendFactory -- JsonBackend + +@enduml \ No newline at end of file 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 0f37b8cc..122f4070 100644 --- a/src/rust/rust_kvs/examples/basic.rs +++ b/src/rust/rust_kvs/examples/basic.rs @@ -11,6 +11,10 @@ fn main() -> Result<(), ErrorCode> { // Temporary directory. let dir = tempdir()?; 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); @@ -20,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) - .dir(dir_string.clone()) + .backend_parameters(backend_parameters.clone()) .kvs_load(KvsLoad::Optional); let kvs = builder.build()?; @@ -65,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) - .dir(dir_string) + .backend_parameters(backend_parameters) .kvs_load(KvsLoad::Required); let kvs = builder.build()?; diff --git a/src/rust/rust_kvs/examples/custom_types.rs b/src/rust/rust_kvs/examples/custom_types.rs index dbf47f89..a91e9514 100644 --- a/src/rust/rust_kvs/examples/custom_types.rs +++ b/src/rust/rust_kvs/examples/custom_types.rs @@ -182,6 +182,10 @@ fn main() -> Result<(), ErrorCode> { // Temporary directory. let dir = tempdir()?; 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)), + ]); // Create initial example object. let object = Example { @@ -218,7 +222,7 @@ fn main() -> Result<(), ErrorCode> { let kvs = KvsBuilder::new(InstanceId(0)) .kvs_load(KvsLoad::Ignored) .defaults(KvsDefaults::Ignored) - .dir(dir_string) + .backend_parameters(backend_parameters) .build()?; // Serialize and set object. diff --git a/src/rust/rust_kvs/examples/defaults.rs b/src/rust/rust_kvs/examples/defaults.rs index f02b3512..a2e356c5 100644 --- a/src/rust/rust_kvs/examples/defaults.rs +++ b/src/rust/rust_kvs/examples/defaults.rs @@ -34,6 +34,10 @@ fn main() -> Result<(), ErrorCode> { // Temporary directory. let dir = tempdir()?; 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); @@ -44,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) - .dir(dir_string) + .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 2e68ba98..0526f3d0 100644 --- a/src/rust/rust_kvs/examples/snapshots.rs +++ b/src/rust/rust_kvs/examples/snapshots.rs @@ -6,9 +6,13 @@ 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 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); @@ -17,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).dir(dir_string.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; @@ -38,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).dir(dir_string.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 ff842527..8c8d8642 100644 --- a/src/rust/rust_kvs/src/error_code.rs +++ b/src/rust/rust_kvs/src/error_code.rs @@ -85,6 +85,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 ba51b4b6..dc135c2c 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, KvsBackendFactory}; 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(crate) 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(crate) 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(crate) 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(crate) fn save( kvs_map: &KvsMap, kvs_path: &Path, hash_path: Option<&PathBuf>, @@ -238,55 +323,151 @@ 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 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) } +} + +/// `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); + } + } - fn defaults_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf { - working_dir.join(Self::defaults_file_name(instance_id)) + // 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([ @@ -720,10 +901,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 +917,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 +1035,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 +1063,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 +1104,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 +1127,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 +1150,293 @@ 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)); + } +} + +#[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 d193f601..3cb02c50 100644 --- a/src/rust/rust_kvs/src/kvs.rs +++ b/src/rust/rust_kvs/src/kvs.rs @@ -11,16 +11,13 @@ 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)] +#[derive(Clone, Debug, PartialEq)] pub struct KvsParameters { /// Instance ID. pub instance_id: InstanceId, @@ -31,11 +28,8 @@ pub struct KvsParameters { /// KVS load mode. pub kvs_load: KvsLoad, - /// Working directory. - pub working_dir: PathBuf, - - /// Maximum number of snapshots to store. - pub snapshot_max_count: usize, + /// Backend parameters. + pub backend_parameters: KvsMap, } impl KvsParameters { @@ -44,108 +38,46 @@ impl KvsParameters { instance_id, defaults: KvsDefaults::Optional, kvs_load: KvsLoad::Optional, - working_dir: PathBuf::new(), - snapshot_max_count: 3, + backend_parameters: KvsMap::from([( + "name".to_string(), + KvsValue::String("json".to_string()), + )]), } } } /// 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, + parameters: Arc, - /// Marker for `PathResolver`. - _path_resolver_marker: PhantomData, + /// Backend. + backend: Box, } -impl GenericKvs { - pub(crate) fn new(data: Arc>, parameters: KvsParameters) -> Self { +impl Kvs { + pub(crate) fn new( + data: Arc>, + parameters: Arc, + backend: Box, + ) -> Self { Self { data, parameters, - _backend_marker: PhantomData, - _path_resolver_marker: PhantomData, + backend, } } + /// KVS instance 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 @@ -376,28 +308,9 @@ 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.backend + .flush(self.parameters.instance_id, &data.kvs_map) } /// Get the count of snapshots @@ -405,31 +318,15 @@ 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.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.backend.snapshot_max_count() } /// Recover key-value-storage from snapshot @@ -452,191 +349,104 @@ 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); - } - - if self.snapshot_count() < snapshot_id.0 { - eprintln!("error: tried to restore a non-existing snapshot"); - return Err(ErrorCode::InvalidSnapshotId); - } - - 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, - ); - data.kvs_map = Backend::load_kvs(&kvs_path, Some(&hash_path))?; - + data.kvs_map = self + .backend + .snapshot_restore(self.parameters.instance_id, snapshot_id)?; Ok(()) } - - /// 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) - } - } - - /// 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) - } - } } #[cfg(test)] mod kvs_tests { use crate::error_code::ErrorCode; - use crate::json_backend::JsonBackend; - use crate::kvs::{GenericKvs, KvsParameters}; + use crate::json_backend::JsonBackendBuilder; + use crate::kvs::{Kvs, KvsParameters}; use crate::kvs_api::{InstanceId, KvsApi, KvsDefaults, KvsLoad, SnapshotId}; - use crate::kvs_backend::{KvsBackend, KvsPathResolver}; + use crate::kvs_backend::KvsBackend; use crate::kvs_builder::KvsData; use crate::kvs_value::{KvsMap, KvsValue}; - use std::path::PathBuf; use std::sync::{Arc, Mutex}; use tempfile::tempdir; /// Most tests can be performed with mocked backend. /// Only those with file handling must use concrete implementation. + #[derive(PartialEq)] struct MockBackend; impl KvsBackend for MockBackend { fn load_kvs( - _kvs_path: &std::path::Path, - _hash_path: Option<&PathBuf>, + &self, + _instance_id: InstanceId, + _snapshot_id: SnapshotId, ) -> Result { unimplemented!() } - fn save_kvs( - _kvs_map: &KvsMap, - _kvs_path: &std::path::Path, - _hash_path: Option<&PathBuf>, - ) -> Result<(), ErrorCode> { + fn load_defaults(&self, _instance_id: InstanceId) -> Result { unimplemented!() } - } - impl KvsPathResolver for MockBackend { - fn kvs_file_name(_instance_id: InstanceId, _snapshot_id: SnapshotId) -> String { + fn flush(&self, _instance_id: InstanceId, _kvs_map: &KvsMap) -> Result<(), ErrorCode> { unimplemented!() } - fn kvs_file_path( - _working_dir: &std::path::Path, - _instance_id: InstanceId, - _snapshot_id: SnapshotId, - ) -> PathBuf { + fn snapshot_count(&self, _instance_id: InstanceId) -> usize { unimplemented!() } - fn hash_file_name(_instance_id: InstanceId, _snapshot_id: SnapshotId) -> String { + fn snapshot_max_count(&self) -> usize { unimplemented!() } - fn hash_file_path( - _working_dir: &std::path::Path, + fn snapshot_restore( + &self, _instance_id: InstanceId, _snapshot_id: SnapshotId, - ) -> PathBuf { - unimplemented!() - } - - fn defaults_file_name(_instance_id: InstanceId) -> String { - unimplemented!() - } - - fn defaults_file_path(_working_dir: &std::path::Path, _instance_id: InstanceId) -> PathBuf { + ) -> Result { unimplemented!() } } - fn get_kvs_snapshot_max_count( - working_dir: PathBuf, - kvs_map: KvsMap, - defaults_map: KvsMap, - snapshot_max_count: usize, - ) -> GenericKvs { + fn get_kvs(backend: Box, kvs_map: KvsMap, defaults_map: KvsMap) -> Kvs { let instance_id = InstanceId(1); let data = Arc::new(Mutex::new(KvsData { kvs_map, defaults_map, })); - let parameters = KvsParameters { + let parameters = Arc::new(KvsParameters { instance_id, defaults: KvsDefaults::Optional, kvs_load: KvsLoad::Optional, - working_dir, - snapshot_max_count, - }; - GenericKvs::::new(data, parameters) - } - - fn get_kvs( - working_dir: PathBuf, - kvs_map: KvsMap, - defaults_map: KvsMap, - ) -> GenericKvs { - get_kvs_snapshot_max_count(working_dir, kvs_map, defaults_map, 3) + backend_parameters: KvsMap::new(), + }); + Kvs::new(data, parameters, backend) } #[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()); - 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()); + let kvs = get_kvs(Box::new(MockBackend), KvsMap::new(), KvsMap::new()); + + // 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] 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)), @@ -658,8 +468,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)), @@ -681,8 +491,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)), @@ -697,7 +507,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); @@ -705,8 +515,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)), @@ -720,8 +530,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)), @@ -734,8 +544,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)), @@ -749,8 +559,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"))]), ); @@ -763,8 +573,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"))]), ); @@ -776,8 +586,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)), @@ -791,8 +601,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"))]), ); @@ -803,8 +613,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"))]), ); @@ -816,8 +626,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)), @@ -832,8 +642,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"))]), ); @@ -845,8 +655,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)), @@ -860,8 +670,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)), @@ -876,8 +686,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)), @@ -890,8 +700,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)), @@ -904,8 +714,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)), @@ -920,7 +730,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"); @@ -928,8 +738,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(), ); @@ -940,8 +750,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)), @@ -955,8 +765,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)), @@ -973,17 +783,20 @@ mod kvs_tests { fn test_flush() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::( - dir_path, + let backend = Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()); + let kvs = get_kvs( + backend.clone(), KvsMap::from([("key".to_string(), KvsValue::from("value"))]), KvsMap::new(), ); kvs.flush().unwrap(); - let snapshot_id = SnapshotId(0); + // Functions below check if file exist. - kvs.get_kvs_filename(snapshot_id).unwrap(); - kvs.get_hash_filename(snapshot_id).unwrap(); + let instance_id = kvs.parameters().instance_id; + let snapshot_id = SnapshotId(0); + assert!(backend.kvs_file_path(instance_id, snapshot_id).exists()); + assert!(backend.hash_file_path(instance_id, snapshot_id).exists()); } #[test] @@ -991,11 +804,15 @@ mod kvs_tests { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); const MAX_COUNT: usize = 0; - let kvs = get_kvs_snapshot_max_count::( - dir_path, + let kvs = get_kvs( + Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path) + .snapshot_max_count(MAX_COUNT) + .build(), + ), KvsMap::new(), KvsMap::new(), - MAX_COUNT, ); // Flush several times. @@ -1011,11 +828,15 @@ mod kvs_tests { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); const MAX_COUNT: usize = 1; - let kvs = get_kvs_snapshot_max_count::( - dir_path, + let kvs = get_kvs( + Box::new( + JsonBackendBuilder::new() + .working_dir(dir_path) + .snapshot_max_count(MAX_COUNT) + .build(), + ), KvsMap::new(), KvsMap::new(), - MAX_COUNT, ); // Flush several times. @@ -1031,7 +852,11 @@ mod kvs_tests { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); const EXPECTED_MAX_COUNT: usize = 3; - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); // Flush several times. for _ in 0..EXPECTED_MAX_COUNT + 1 { @@ -1045,7 +870,11 @@ mod kvs_tests { fn test_snapshot_count_zero() { let dir = tempdir().unwrap(); let dir_path = dir.path().to_path_buf(); - let kvs = get_kvs::(dir_path, KvsMap::new(), KvsMap::new()); + let kvs = get_kvs( + Box::new(JsonBackendBuilder::new().working_dir(dir_path).build()), + KvsMap::new(), + KvsMap::new(), + ); assert_eq!(kvs.snapshot_count(), 0); } @@ -1053,7 +882,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); } @@ -1062,7 +895,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); @@ -1076,7 +913,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); } @@ -1084,7 +925,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(); @@ -1098,7 +943,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(); @@ -1113,7 +962,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(); @@ -1128,7 +981,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(); @@ -1138,52 +995,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..547b8623 100644 --- a/src/rust/rust_kvs/src/kvs_backend.rs +++ b/src/rust/rust_kvs/src/kvs_backend.rs @@ -12,46 +12,40 @@ use crate::error_code::ErrorCode; use crate::kvs_api::{InstanceId, SnapshotId}; use crate::kvs_value::KvsMap; -use std::path::{Path, PathBuf}; /// 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>; -} - -/// KVS path resolver interface. -pub trait KvsPathResolver { - /// Get KVS file name. - fn kvs_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String; - - /// Get KVS file path in working directory. - fn kvs_file_path( - working_dir: &Path, + /// Load KVS content. + fn load_kvs( + &self, instance_id: InstanceId, snapshot_id: SnapshotId, - ) -> PathBuf; + ) -> Result; + + /// Load default values. + fn load_defaults(&self, instance_id: InstanceId) -> Result; - /// Get hash file name. - fn hash_file_name(instance_id: InstanceId, snapshot_id: SnapshotId) -> String; + /// 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>; - /// Get hash file path in working directory. - fn hash_file_path( - working_dir: &Path, + /// Count available snapshots. + fn snapshot_count(&self, instance_id: InstanceId) -> usize; + + /// Max number of snapshots. + fn snapshot_max_count(&self) -> usize; + + /// Restore snapshot with given ID. + fn snapshot_restore( + &self, instance_id: InstanceId, snapshot_id: SnapshotId, - ) -> PathBuf; - - /// Get defaults file name. - fn defaults_file_name(instance_id: InstanceId) -> String; + ) -> Result; +} - /// Get defaults file path in working directory. - fn defaults_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf; +/// 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 957631fc..a5198a0f 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::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_backend_registry::KvsBackendRegistry; 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>, @@ -68,11 +67,8 @@ struct KvsBuilderParameters { /// KVS load mode. pub kvs_load: Option, - /// Working directory. - pub working_dir: Option, - - /// Maximum number of snapshots to store. - pub snapshot_max_count: Option, + // Backend parameters. + pub backend_parameters: Option, } impl KvsBuilderParameters { @@ -81,13 +77,12 @@ impl KvsBuilderParameters { instance_id, defaults: None, kvs_load: None, - working_dir: None, - snapshot_max_count: None, + backend_parameters: None, } } pub fn create_parameters(self, kvs_parameters: &KvsParameters) -> KvsParameters { - let mut new_parameters = kvs_parameters.clone(); + let mut new_parameters: KvsParameters = kvs_parameters.clone(); if let Some(defaults) = self.defaults { new_parameters.defaults = defaults; @@ -97,12 +92,8 @@ impl KvsBuilderParameters { new_parameters.kvs_load = kvs_load; } - if let Some(working_dir) = self.working_dir { - new_parameters.working_dir = working_dir; - } - - if let Some(snapshot_max_count) = self.snapshot_max_count { - new_parameters.snapshot_max_count = snapshot_max_count; + if let Some(backend_parameters) = self.backend_parameters { + new_parameters.backend_parameters = backend_parameters; } new_parameters @@ -110,18 +101,12 @@ impl KvsBuilderParameters { } /// Key-value-storage builder. -pub struct GenericKvsBuilder { +pub struct KvsBuilder { /// KVS instance parameters. parameters: KvsBuilderParameters, - - /// 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 @@ -135,11 +120,7 @@ impl GenericKvsBuilder Self { let parameters = KvsBuilderParameters::new(instance_id); - Self { - parameters, - _backend_marker: PhantomData, - _path_resolver_marker: PhantomData, - } + Self { parameters } } /// Return maximum number of allowed KVS instances. @@ -174,28 +155,21 @@ impl GenericKvsBuilder>(mut self, dir: P) -> Self { - self.parameters.working_dir = Some(PathBuf::from(dir.into())); + pub fn backend_parameters(mut self, parameters: KvsMap) -> Self { + self.parameters.backend_parameters = Some(parameters); self } - /// Set the maximum number of snapshots to store. - /// - /// # Parameters - /// * `snapshot_max_count`: Maximum number of snapshots to store. - /// - /// # Return Values - /// * KvsBuilder instance - pub fn snapshot_max_count(mut self, snapshot_max_count: usize) -> Self { - self.parameters.snapshot_max_count = Some(snapshot_max_count); - 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 @@ -214,7 +188,7 @@ impl GenericKvsBuilder Result, ErrorCode> { + pub fn build(self) -> Result { let instance_id = self.parameters.instance_id; let instance_id_index: usize = instance_id.into(); @@ -229,7 +203,7 @@ impl GenericKvsBuilder GenericKvsBuilder::new( + let backend = Self::create_backend(&kvs_inner.parameters.backend_parameters)?; + return Ok(Kvs::new( kvs_inner.data.clone(), kvs_inner.parameters.clone(), + backend, )); } } // Initialize KVS instance with provided parameters. // Get parameters object. - let kvs_parameters = self - .parameters - .create_parameters(&KvsParameters::new(instance_id)); - // Load file containing defaults. - let defaults_path = - PathResolver::defaults_file_path(&kvs_parameters.working_dir, instance_id); + let kvs_parameters = Arc::new( + self.parameters + .create_parameters(&KvsParameters::new(instance_id)), + ); + + // Initialize backend. + let backend = Self::create_backend(&kvs_parameters.backend_parameters)?; + + // Load defaults. let defaults_map = match kvs_parameters.defaults { KvsDefaults::Ignored => 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(&kvs_parameters.working_dir, instance_id, snapshot_id); - let hash_path = - PathResolver::hash_file_path(&kvs_parameters.working_dir, instance_id, snapshot_id); let kvs_map = match kvs_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. @@ -309,17 +284,18 @@ impl GenericKvsBuilder; + /// 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() { @@ -353,12 +376,12 @@ mod kvs_builder_tests { // 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] @@ -366,15 +389,18 @@ 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 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] @@ -382,13 +408,18 @@ 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 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] @@ -396,44 +427,44 @@ 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 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_dir() { + fn test_parameters_backend_parameters() { 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 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_eq!(kvs.parameters().working_dir, dir.path()); - assert_eq!(kvs.snapshot_max_count(), 3); - } - #[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(1); - let builder = TestKvsBuilder::new(instance_id).snapshot_max_count(1234); - 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_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -441,20 +472,27 @@ 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 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) - .dir(dir_string) - .snapshot_max_count(1234); + .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_eq!(kvs.parameters().working_dir, dir.path()); - assert_eq!(kvs.snapshot_max_count(), 1234); + + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Ignored, + kvs_load: KvsLoad::Ignored, + backend_parameters, + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -462,7 +500,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(); } @@ -471,27 +509,34 @@ 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 backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); + let builder1 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Ignored) - .dir(dir_string.clone()); + .backend_parameters(backend_parameters.clone()); 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_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_eq!(kvs.parameters().working_dir, dir.path()); + let expected_parameters = KvsParameters { + instance_id, + defaults: KvsDefaults::Ignored, + kvs_load: KvsLoad::Ignored, + backend_parameters, + }; + + assert_eq!(*kvs.parameters(), expected_parameters); } #[test] @@ -499,20 +544,23 @@ 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 different parameters. let instance_id = InstanceId(1); - let builder1 = TestKvsBuilder::new(instance_id) + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); + let builder1 = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Ignored) .kvs_load(KvsLoad::Optional) - .dir(dir_string.clone()); + .backend_parameters(backend_parameters.clone()); 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_parameters(backend_parameters.clone()); let result = builder2.build(); assert!(result.is_err_and(|e| e == ErrorCode::InstanceParametersMismatch)); @@ -522,68 +570,54 @@ mod kvs_builder_tests { fn test_build_instance_exists_params_not_set() { let _lock = lock_and_reset(); - let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); - // Create two instances, first with parameters, second without. 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()); + .kvs_load(KvsLoad::Ignored); let _ = builder1.build().unwrap(); - let builder2 = TestKvsBuilder::new(instance_id); + let builder2 = KvsBuilder::new(instance_id); 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()); } #[test] fn test_build_instance_exists_single_matching_param_set() { let _lock = lock_and_reset(); - let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); - // Create two instances, first with parameters, second only with `defaults`. 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()); + .kvs_load(KvsLoad::Ignored); let _ = builder1.build().unwrap(); - let builder2 = TestKvsBuilder::new(instance_id).defaults(KvsDefaults::Ignored); + let builder2 = KvsBuilder::new(instance_id).defaults(KvsDefaults::Ignored); 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()); } #[test] fn test_build_instance_exists_single_mismatched_param_set() { let _lock = lock_and_reset(); - let dir = tempdir().unwrap(); - let dir_string = dir.path().to_string_lossy().to_string(); - // Create two instances, first with parameters, second only with `defaults`. 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()); + .kvs_load(KvsLoad::Ignored); let _ = builder1.build().unwrap(); - let builder2 = TestKvsBuilder::new(instance_id).defaults(KvsDefaults::Optional); + let builder2 = KvsBuilder::new(instance_id).defaults(KvsDefaults::Optional); let result = builder2.build(); assert!(result.is_err_and(|e| e == ErrorCode::InstanceParametersMismatch)); @@ -594,7 +628,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)); } @@ -603,13 +637,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) } @@ -620,14 +657,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)) } @@ -637,13 +677,16 @@ 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) + 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Ignored); @@ -658,12 +701,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); - let builder = TestKvsBuilder::new(instance_id) + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Optional) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); @@ -678,13 +724,16 @@ 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) + 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Optional); @@ -699,12 +748,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); - let builder = TestKvsBuilder::new(instance_id) + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); + let builder = KvsBuilder::new(instance_id) .defaults(KvsDefaults::Required) - .dir(dir_string); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -715,13 +767,16 @@ 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) + 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().defaults, KvsDefaults::Required); @@ -736,13 +791,16 @@ 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) + 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Ignored); @@ -757,12 +815,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); - let builder = TestKvsBuilder::new(instance_id) + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Optional) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); @@ -778,19 +839,18 @@ 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 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); @@ -802,19 +862,18 @@ 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 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -825,13 +884,16 @@ 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) + 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let kvs = builder.build().unwrap(); assert_eq!(kvs.parameters().kvs_load, KvsLoad::Optional); @@ -846,12 +908,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); - let builder = TestKvsBuilder::new(instance_id) + let backend_parameters = BackendParametersBuilder::new() + .working_dir(dir_path.clone()) + .build(); + let builder = KvsBuilder::new(instance_id) .kvs_load(KvsLoad::Required) - .dir(dir_string); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -863,19 +928,18 @@ 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 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::KvsHashFileReadError)); @@ -887,19 +951,18 @@ 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 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) - .dir(dir_string); + .backend_parameters(backend_parameters); let result = builder.build(); assert!(result.is_err_and(|e| e == ErrorCode::FileNotFound)); @@ -910,13 +973,16 @@ 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) + 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) - .dir(dir_string); + .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/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 544bea28..9ec8647b 100644 --- a/src/rust/rust_kvs/src/lib.rs +++ b/src/rust/rust_kvs/src/lib.rs @@ -135,23 +135,21 @@ pub mod error_code; mod json_backend; pub mod kvs; pub mod kvs_api; -mod kvs_backend; +pub mod kvs_backend; +pub mod kvs_backend_registry; pub mod kvs_builder; pub mod kvs_mock; pub mod kvs_serialize; 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::kvs::Kvs; pub use crate::kvs_api::{InstanceId, KvsApi, KvsDefaults, KvsLoad, SnapshotId}; - pub use crate::kvs_builder::GenericKvsBuilder; + pub use crate::kvs_backend::{KvsBackend, KvsBackendFactory}; + pub use crate::kvs_backend_registry::KvsBackendRegistry; + pub use crate::kvs_builder::KvsBuilder; pub use crate::kvs_serialize::{KvsDeserialize, KvsSerialize}; 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..d605fe00 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,49 +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 snapshot_id = SnapshotId(snapshot_id as usize); - let filename = kvs.get_kvs_filename(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 snapshot_id = SnapshotId(snapshot_id as usize); - let filename = kvs.get_hash_filename(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!("----------------------"); @@ -453,8 +402,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) @@ -491,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: @@ -521,7 +464,11 @@ fn main() -> Result<(), ErrorCode> { .kvs_load(KvsLoad::Optional); let builder = if let Some(dir) = directory { - builder.dir(dir) + let backend_parameters = KvsMap::from([ + ("name".to_string(), KvsValue::String("json".to_string())), + ("working_dir".to_string(), KvsValue::String(dir)), + ]); + builder.backend_parameters(backend_parameters) } else { builder }; @@ -557,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, @@ -597,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/python_test_cases/tests/test_cit_snapshots.py b/tests/python_test_cases/tests/test_cit_snapshots.py index b8440a31..39953a17 100644 --- a/tests/python_test_cases/tests/test_cit_snapshots.py +++ b/tests/python_test_cases/tests/test_cit_snapshots.py @@ -294,8 +294,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"]) @@ -320,6 +322,7 @@ def test_config(self, temp_dir: Path) -> dict[str, Any]: def test_error( self, + temp_dir: Path, results: ScenarioResult, logs_info_level: LogContainer, ): @@ -327,5 +330,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..d7567f22 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; @@ -247,18 +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 = 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(&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/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..a704db40 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; @@ -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,13 +42,13 @@ 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(&working_dir, kvs.parameters().instance_id, 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..9534e3a0 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}; @@ -112,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 { @@ -124,12 +127,16 @@ 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( + &working_dir, + kvs.parameters().instance_id, + 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..90654fd3 100644 --- a/tests/rust_test_scenarios/src/helpers/kvs_instance.rs +++ b/tests/rust_test_scenarios/src/helpers/kvs_instance.rs @@ -1,29 +1,51 @@ //! KVS instance test helpers. use crate::helpers::kvs_parameters::KvsParameters; -use rust_kvs::prelude::{ErrorCode, 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 { - 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 `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 { - builder = builder.dir(dir.to_string_lossy().to_string()); + backend_parameters.insert( + "working_dir".to_string(), + KvsValue::String(dir.to_string_lossy().to_string()), + ); + set_backend = true; } + // Set max number of snapshots. if let Some(snapshot_max_count) = kvs_parameters.snapshot_max_count { - builder = 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_parameters(backend_parameters); } - let kvs: Kvs = 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..8e92af6a 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::{InstanceId, SnapshotId}; +use std::path::{Path, PathBuf}; + pub mod kvs_instance; pub mod kvs_parameters; @@ -5,3 +8,15 @@ pub mod kvs_parameters; pub(crate) fn to_str(value: &T) -> String { format!("{value:?}") } + +/// 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) +} 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";