Skip to content

Commit 3542f97

Browse files
siegfriedwebersbernauernightkr
authored
feat(stackable-operator): Add cache structure (#943)
* feat(stackable-operator): Add cache structure * feat(stackable-operator): Change type for TtlCache::max_entries from i32 to u32 * Update crates/stackable-operator/src/commons/cache.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * feat(stackable-operator): Add the type UserInformationCache * docs(stackable-operator): Fix schema description of TtlCache * Update crates/stackable-operator/CHANGELOG.md Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * Update crates/stackable-operator/src/commons/cache.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * Use a trait for default values, rather than const generics * docs(stackable-operator): Change the comment for the TtlCache --------- Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> Co-authored-by: Natalie Klestrup Röijezon <nat.roijezon@stackable.tech>
1 parent 59506c6 commit 3542f97

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ darling = "0.20.10"
2121
delegate = "0.13.0"
2222
dockerfile-parser = "0.9.0"
2323
ecdsa = { version = "0.16.9", features = ["digest", "pem"] }
24-
educe = { version = "0.6.0", default-features = false, features = ["Clone", "Debug", "Default", "PartialEq"] }
24+
educe = { version = "0.6.0", default-features = false, features = ["Clone", "Debug", "Default", "PartialEq", "Eq"] }
2525
either = "1.13.0"
2626
futures = "0.3.30"
2727
futures-util = "0.3.30"

crates/stackable-operator/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Add generic `TtlCache` structure as well as a `UserInformationCache` type ([#943]).
10+
11+
[#943]: https://github.com/stackabletech/operator-rs/pull/943
12+
713
## [0.85.0] - 2025-01-28
814

915
### Changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use std::marker::PhantomData;
2+
3+
use educe::Educe;
4+
use schemars::JsonSchema;
5+
use serde::{Deserialize, Serialize};
6+
7+
use crate::time::Duration;
8+
9+
/// [`TtlCache`] with sensible defaults for a user information cache
10+
pub type UserInformationCache = TtlCache<UserInformationCacheDefaults>;
11+
12+
/// Default tunings for [`UserInformationCache`].
13+
#[derive(JsonSchema)]
14+
pub struct UserInformationCacheDefaults;
15+
16+
impl TtlCacheDefaults for UserInformationCacheDefaults {
17+
fn entry_time_to_live() -> Duration {
18+
Duration::from_secs(30)
19+
}
20+
21+
fn max_entries() -> u32 {
22+
10_000
23+
}
24+
}
25+
26+
/// Structure to configure a TTL cache in a product.
27+
#[derive(Deserialize, Educe, JsonSchema, Serialize)]
28+
#[serde(
29+
rename_all = "camelCase",
30+
bound(deserialize = "D: TtlCacheDefaults", serialize = "D: TtlCacheDefaults")
31+
)]
32+
#[schemars(
33+
description = "Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value.",
34+
// We don't care about the fields, but we also use JsonSchema to derive the name for the composite type
35+
bound(serialize = "D: TtlCacheDefaults + JsonSchema")
36+
)]
37+
#[educe(
38+
Clone(bound = false),
39+
Debug(bound = false),
40+
PartialEq(bound = false),
41+
Eq
42+
)]
43+
pub struct TtlCache<D> {
44+
/// Time to live per entry; Entries which were not queried within the given duration, are
45+
/// removed.
46+
#[serde(default = "D::entry_time_to_live")]
47+
pub entry_time_to_live: Duration,
48+
49+
/// Maximum number of entries in the cache; If this threshold is reached then the least
50+
/// recently used item is removed.
51+
#[serde(default = "D::max_entries")]
52+
pub max_entries: u32,
53+
54+
#[serde(skip)]
55+
pub _defaults: PhantomData<D>,
56+
}
57+
58+
impl<D: TtlCacheDefaults> Default for TtlCache<D> {
59+
fn default() -> Self {
60+
Self {
61+
entry_time_to_live: D::entry_time_to_live(),
62+
max_entries: D::max_entries(),
63+
_defaults: PhantomData,
64+
}
65+
}
66+
}
67+
68+
/// A set of default values for [`TtlCache`].
69+
///
70+
/// This is extracted to a separate trait in order to be able to provide different
71+
/// default tunings for different use cases.
72+
pub trait TtlCacheDefaults {
73+
/// The default TTL the entries should have.
74+
fn entry_time_to_live() -> Duration;
75+
/// The default for the maximum number of entries
76+
fn max_entries() -> u32;
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use super::*;
82+
83+
type MyCache = TtlCache<UserInformationCacheDefaults>;
84+
85+
#[test]
86+
fn test_defaults() {
87+
let my_cache: MyCache = Default::default();
88+
assert_eq!(my_cache.entry_time_to_live, Duration::from_secs(30));
89+
assert_eq!(my_cache.max_entries, 10_000);
90+
}
91+
92+
#[test]
93+
fn test_deserialization_defaults() {
94+
let my_cache: MyCache = serde_json::from_str("{}").unwrap();
95+
96+
assert_eq!(my_cache.entry_time_to_live, Duration::from_secs(30));
97+
assert_eq!(my_cache.max_entries, 10_000);
98+
}
99+
100+
#[test]
101+
fn test_deserialization() {
102+
let my_cache: MyCache = serde_yaml::from_str("entryTimeToLive: 13h\n").unwrap();
103+
104+
assert_eq!(
105+
my_cache.entry_time_to_live,
106+
Duration::from_hours_unchecked(13)
107+
);
108+
// As the field is not specified we default
109+
assert_eq!(my_cache.max_entries, 10_000);
110+
}
111+
}

crates/stackable-operator/src/commons/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod affinity;
44
pub mod authentication;
5+
pub mod cache;
56
pub mod cluster_operation;
67
pub mod listener;
78
pub mod networking;

0 commit comments

Comments
 (0)