From 9606bfc3f1cbf147bc7e566d30b1d5761e7dfbaa Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 16 Jan 2025 13:40:05 +0100 Subject: [PATCH 1/9] feat(stackable-operator): Add cache structure --- crates/stackable-operator/CHANGELOG.md | 6 +++ .../stackable-operator/src/commons/cache.rs | 38 +++++++++++++++++++ crates/stackable-operator/src/commons/mod.rs | 1 + 3 files changed, 45 insertions(+) create mode 100644 crates/stackable-operator/src/commons/cache.rs diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index e81e0d9ab..329a58370 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add `TtlCache` structure ([#943]). + +[#943]: https://github.com/stackabletech/operator-rs/pull/943 + ## [0.84.0] - 2025-01-16 ### Added diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs new file mode 100644 index 000000000..32cb0534e --- /dev/null +++ b/crates/stackable-operator/src/commons/cache.rs @@ -0,0 +1,38 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::time::Duration; + +/// Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. +#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TtlCache { + /// Time to live per entry; Entries which were not queried within the given duration, are + /// removed. + #[serde(default = "TtlCache::default_entry_time_to_live")] + pub entry_time_to_live: Duration, + + /// Maximum number of entries in the cache; If this threshold is reached then the least + /// recently used item is removed. + #[serde(default = "TtlCache::default_max_entries")] + pub max_entries: i32, +} + +impl TtlCache { + const fn default_entry_time_to_live() -> Duration { + Duration::from_secs(30) + } + + const fn default_max_entries() -> i32 { + 1000 + } +} + +impl Default for TtlCache { + fn default() -> Self { + Self { + entry_time_to_live: Self::default_entry_time_to_live(), + max_entries: Self::default_max_entries(), + } + } +} diff --git a/crates/stackable-operator/src/commons/mod.rs b/crates/stackable-operator/src/commons/mod.rs index 2e61dfe23..b29d078ae 100644 --- a/crates/stackable-operator/src/commons/mod.rs +++ b/crates/stackable-operator/src/commons/mod.rs @@ -2,6 +2,7 @@ pub mod affinity; pub mod authentication; +pub mod cache; pub mod cluster_operation; pub mod listener; pub mod networking; From 88daadead8f9502a19416d4f5ebe43cb48870a99 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 17 Jan 2025 17:04:06 +0100 Subject: [PATCH 2/9] feat(stackable-operator): Change type for TtlCache::max_entries from i32 to u32 --- crates/stackable-operator/src/commons/cache.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index 32cb0534e..aab807a7b 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -15,7 +15,7 @@ pub struct TtlCache { /// Maximum number of entries in the cache; If this threshold is reached then the least /// recently used item is removed. #[serde(default = "TtlCache::default_max_entries")] - pub max_entries: i32, + pub max_entries: u32, } impl TtlCache { @@ -23,7 +23,7 @@ impl TtlCache { Duration::from_secs(30) } - const fn default_max_entries() -> i32 { + const fn default_max_entries() -> u32 { 1000 } } From 17bb2b5264c028781dfaf174f10667a0d50accd8 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 20 Jan 2025 16:56:29 +0100 Subject: [PATCH 3/9] Update crates/stackable-operator/src/commons/cache.rs Co-authored-by: Sebastian Bernauer --- .../stackable-operator/src/commons/cache.rs | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index aab807a7b..3d513de0c 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -4,31 +4,39 @@ use serde::{Deserialize, Serialize}; use crate::time::Duration; /// Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. +/// +/// This struct has two const generics, so that different use-cases can have different default +/// values: +/// +/// * `D_TTL_SEC` is the default TTL (in seconds) the entries should have. +/// * `D_MAX_ENTRIES` is the default for the maximum number of entries #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct TtlCache { +pub struct TtlCache { /// Time to live per entry; Entries which were not queried within the given duration, are /// removed. - #[serde(default = "TtlCache::default_entry_time_to_live")] + #[serde(default = "TtlCache::::default_entry_time_to_live")] pub entry_time_to_live: Duration, /// Maximum number of entries in the cache; If this threshold is reached then the least /// recently used item is removed. - #[serde(default = "TtlCache::default_max_entries")] + #[serde(default = "TtlCache::::default_max_entries")] pub max_entries: u32, } -impl TtlCache { +impl TtlCache { const fn default_entry_time_to_live() -> Duration { - Duration::from_secs(30) + Duration::from_secs(D_TTL_SEC) } const fn default_max_entries() -> u32 { - 1000 + D_MAX_ENTRIES } } -impl Default for TtlCache { +impl Default + for TtlCache +{ fn default() -> Self { Self { entry_time_to_live: Self::default_entry_time_to_live(), @@ -36,3 +44,38 @@ impl Default for TtlCache { } } } + +#[cfg(test)] +mod tests { + use super::*; + + type MyCache = TtlCache<30, 10_000>; + + #[test] + fn test_defaults() { + let my_cache: MyCache = Default::default(); + assert_eq!(my_cache.entry_time_to_live, Duration::from_secs(30)); + assert_eq!(my_cache.max_entries, 10_000); + } + + #[test] + fn test_deserialization_defaults() { + let my_cache: MyCache = serde_json::from_str("{}").unwrap(); + + assert_eq!(my_cache.entry_time_to_live, Duration::from_secs(30)); + assert_eq!(my_cache.max_entries, 10_000); + } + + #[test] + fn test_deserialization() { + let my_cache: MyCache = serde_yaml::from_str("entryTimeToLive: 13h\n").unwrap(); + + assert_eq!( + my_cache.entry_time_to_live, + Duration::from_hours_unchecked(13) + ); + // As the field is not specified we default + assert_eq!(my_cache.max_entries, 10_000); + } +} + From 54f52b378c32f59ed1c72a5de27829f7cfe9be57 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 20 Jan 2025 17:11:42 +0100 Subject: [PATCH 4/9] feat(stackable-operator): Add the type UserInformationCache --- crates/stackable-operator/src/commons/cache.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index 3d513de0c..da7917206 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -3,6 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::time::Duration; +/// TtlCache with sensible defaults for a user information cache +pub type UserInformationCache = TtlCache<30, 10_000>; + /// Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. /// /// This struct has two const generics, so that different use-cases can have different default @@ -78,4 +81,3 @@ mod tests { assert_eq!(my_cache.max_entries, 10_000); } } - From 7af060b6f821d7aa82c8ecb0abf6e328a024106a Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 21 Jan 2025 09:33:04 +0100 Subject: [PATCH 5/9] docs(stackable-operator): Fix schema description of TtlCache --- crates/stackable-operator/src/commons/cache.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index da7917206..9149c8b65 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -15,6 +15,9 @@ pub type UserInformationCache = TtlCache<30, 10_000>; /// * `D_MAX_ENTRIES` is the default for the maximum number of entries #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[schemars( + description = "Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value." +)] pub struct TtlCache { /// Time to live per entry; Entries which were not queried within the given duration, are /// removed. From 3ff038aa55ac2fec999826de32961e9f787f2af0 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 21 Jan 2025 17:48:28 +0100 Subject: [PATCH 6/9] Update crates/stackable-operator/CHANGELOG.md Co-authored-by: Sebastian Bernauer --- crates/stackable-operator/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index ae7be9e36..f32448179 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ### Added -- Add `TtlCache` structure ([#943]). +- Add generic `TtlCache` structure as well as a `UserInformationCache` type ([#943]). ### Fixed From 8eee877afa2c46a19debe59c8de7ea368b37298e Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 21 Jan 2025 17:48:44 +0100 Subject: [PATCH 7/9] Update crates/stackable-operator/src/commons/cache.rs Co-authored-by: Sebastian Bernauer --- crates/stackable-operator/src/commons/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index 9149c8b65..36ecca81a 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::time::Duration; -/// TtlCache with sensible defaults for a user information cache +/// [`TtlCache`] with sensible defaults for a user information cache pub type UserInformationCache = TtlCache<30, 10_000>; /// Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. From 3d4df2f7c9418441de12c594431651ef216473c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 23 Jan 2025 15:32:37 +0100 Subject: [PATCH 8/9] Use a trait for default values, rather than const generics --- Cargo.toml | 2 +- .../stackable-operator/src/commons/cache.rs | 81 ++++++++++++------- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5df44d09b..6a2703a12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ darling = "0.20.10" delegate = "0.13.0" dockerfile-parser = "0.9.0" ecdsa = { version = "0.16.9", features = ["digest", "pem"] } -educe = { version = "0.6.0", default-features = false, features = ["Clone", "Debug", "Default", "PartialEq"] } +educe = { version = "0.6.0", default-features = false, features = ["Clone", "Debug", "Default", "PartialEq", "Eq"] } either = "1.13.0" futures = "0.3.30" futures-util = "0.3.30" diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index 36ecca81a..5aeadeb06 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -1,61 +1,86 @@ +use std::marker::PhantomData; + +use educe::Educe; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::time::Duration; /// [`TtlCache`] with sensible defaults for a user information cache -pub type UserInformationCache = TtlCache<30, 10_000>; +pub type UserInformationCache = TtlCache; + +/// Default tunings for [`UserInformationCache`]. +#[derive(JsonSchema)] +pub struct UserInformationCacheDefaults; + +impl TtlCacheDefaults for UserInformationCacheDefaults { + fn entry_time_to_live() -> Duration { + Duration::from_secs(30) + } + + fn max_entries() -> u32 { + 10_000 + } +} /// Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. -/// -/// This struct has two const generics, so that different use-cases can have different default -/// values: -/// -/// * `D_TTL_SEC` is the default TTL (in seconds) the entries should have. -/// * `D_MAX_ENTRIES` is the default for the maximum number of entries -#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] +#[derive(Deserialize, Educe, JsonSchema, Serialize)] +#[serde( + rename_all = "camelCase", + bound(deserialize = "D: TtlCacheDefaults", serialize = "D: TtlCacheDefaults") +)] #[schemars( - description = "Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value." + description = "Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value.", + // We don't care about the fields, but we also use JsonSchema to derive the name for the composite type + bound(serialize = "D: TtlCacheDefaults + JsonSchema") +)] +#[educe( + Clone(bound = false), + Debug(bound = false), + PartialEq(bound = false), + Eq )] -pub struct TtlCache { +pub struct TtlCache { /// Time to live per entry; Entries which were not queried within the given duration, are /// removed. - #[serde(default = "TtlCache::::default_entry_time_to_live")] + #[serde(default = "D::entry_time_to_live")] pub entry_time_to_live: Duration, /// Maximum number of entries in the cache; If this threshold is reached then the least /// recently used item is removed. - #[serde(default = "TtlCache::::default_max_entries")] + #[serde(default = "D::max_entries")] pub max_entries: u32, -} - -impl TtlCache { - const fn default_entry_time_to_live() -> Duration { - Duration::from_secs(D_TTL_SEC) - } - const fn default_max_entries() -> u32 { - D_MAX_ENTRIES - } + #[serde(skip)] + pub _defaults: PhantomData, } -impl Default - for TtlCache -{ +impl Default for TtlCache { fn default() -> Self { Self { - entry_time_to_live: Self::default_entry_time_to_live(), - max_entries: Self::default_max_entries(), + entry_time_to_live: D::entry_time_to_live(), + max_entries: D::max_entries(), + _defaults: PhantomData, } } } +/// A set of default values for [`TtlCache`]. +/// +/// This is extracted to a separate trait in order to be able to provide different +/// default tunings for different use cases. +pub trait TtlCacheDefaults { + /// The default TTL the entries should have. + fn entry_time_to_live() -> Duration; + /// The default for the maximum number of entries + fn max_entries() -> u32; +} + #[cfg(test)] mod tests { use super::*; - type MyCache = TtlCache<30, 10_000>; + type MyCache = TtlCache; #[test] fn test_defaults() { From 8281fca02d93b1622e0e3b4cfd142d491150e425 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 23 Jan 2025 16:55:03 +0100 Subject: [PATCH 9/9] docs(stackable-operator): Change the comment for the TtlCache --- crates/stackable-operator/src/commons/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/commons/cache.rs b/crates/stackable-operator/src/commons/cache.rs index 5aeadeb06..f6623417e 100644 --- a/crates/stackable-operator/src/commons/cache.rs +++ b/crates/stackable-operator/src/commons/cache.rs @@ -23,7 +23,7 @@ impl TtlCacheDefaults for UserInformationCacheDefaults { } } -/// Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. +/// Structure to configure a TTL cache in a product. #[derive(Deserialize, Educe, JsonSchema, Serialize)] #[serde( rename_all = "camelCase",