brz_memcache/
cacheservice.rs1use std::collections::HashMap;
4
5use serde::Deserialize;
6
7#[derive(Clone, Debug, Deserialize)]
10pub struct CacheServiceConfig(HashMap<String, CacheNamespaceConf>);
11
12impl CacheServiceConfig {
13 pub fn from_yaml_str(yaml: &str) -> Result<Self, CacheServiceError> {
15 serde_yaml::from_str(yaml).map_err(|e| CacheServiceError::Yaml(e.to_string()))
16 }
17
18 pub fn masters_of(&self, namespace: &str) -> Option<&[String]> {
20 self.0.get(namespace).map(|c| c.master.as_slice())
21 }
22
23 pub fn namespace(&self, namespace: &str) -> Option<&CacheNamespaceConf> {
25 self.0.get(namespace)
26 }
27}
28
29#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
31pub struct CacheNamespaceConf {
32 #[serde(default)]
33 pub hash: Option<String>,
34 #[serde(default)]
35 pub distribution: Option<String>,
36 #[serde(default)]
37 pub master: Vec<String>,
38 #[serde(default)]
39 pub slave: Vec<String>,
40 #[serde(default, rename = "master_l1")]
41 pub master_l1: Vec<Vec<String>>,
42 #[serde(default, rename = "slave_l1")]
43 pub slave_l1: Vec<Vec<String>>,
44 #[serde(default)]
45 pub exptime: i64,
46 #[serde(default)]
47 pub timeout_ms_master: u32,
48 #[serde(default)]
49 pub timeout_ms_slave: u32,
50 #[serde(default = "default_true")]
51 pub update_slave_l1: bool,
52 #[serde(default)]
53 pub local_affinity: bool,
54 #[serde(default)]
55 pub backend_no_storage: bool,
56}
57
58impl CacheNamespaceConf {
59 pub fn single_master(endpoint: String) -> Self {
60 Self {
61 hash: None,
62 distribution: None,
63 master: vec![endpoint],
64 slave: Vec::new(),
65 master_l1: Vec::new(),
66 slave_l1: Vec::new(),
67 exptime: 0,
68 timeout_ms_master: 0,
69 timeout_ms_slave: 0,
70 update_slave_l1: true,
71 local_affinity: false,
72 backend_no_storage: false,
73 }
74 }
75
76 pub fn hash(&self) -> Option<&str> {
78 self.hash.as_deref()
79 }
80
81 pub fn distribution(&self) -> Option<&str> {
83 self.distribution.as_deref()
84 }
85
86 pub fn masters(&self) -> &[String] {
88 &self.master
89 }
90
91 pub fn slaves(&self) -> &[String] {
94 &self.slave
95 }
96
97 pub fn master_l1(&self) -> &[Vec<String>] {
99 &self.master_l1
100 }
101
102 pub fn slave_l1(&self) -> &[Vec<String>] {
104 &self.slave_l1
105 }
106
107 pub fn writeback_expiration_ms(&self) -> i64 {
110 self.exptime
111 }
112
113 pub fn timeout_ms_master(&self) -> u32 {
114 self.timeout_ms_master
115 }
116
117 pub fn timeout_ms_slave(&self) -> u32 {
118 self.timeout_ms_slave
119 }
120
121 pub fn update_slave_l1(&self) -> bool {
122 self.update_slave_l1
123 }
124
125 pub fn local_affinity(&self) -> bool {
126 self.local_affinity
127 }
128
129 pub fn backend_no_storage(&self) -> bool {
130 self.backend_no_storage
131 }
132}
133
134fn default_true() -> bool {
135 true
136}
137
138#[derive(Debug, thiserror::Error)]
140pub enum CacheServiceError {
141 #[error("cache-service yaml parse error: {0}")]
143 Yaml(String),
144
145 #[error("invalid cache-service namespace: {0:?}")]
147 InvalidNamespace(String),
148
149 #[error("cache-service namespace not found: {0}")]
151 MissingNamespace(String),
152}
153
154pub fn masters_from_yaml(yaml: &str, namespace: &str) -> Result<Vec<String>, CacheServiceError> {
164 if namespace.trim().is_empty() || namespace.chars().any(char::is_control) {
165 return Err(CacheServiceError::InvalidNamespace(namespace.to_string()));
166 }
167 let cfg = CacheServiceConfig::from_yaml_str(yaml)?;
168 cfg.masters_of(namespace)
169 .map(|m| m.to_vec())
170 .ok_or_else(|| CacheServiceError::MissingNamespace(namespace.to_string()))
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 const SAMPLE_YAML: &str = "example-abtest:\n hash: crc32\n distribution: modula\n hash_tag: example-abtest\n master:\n - 192.0.4.103:15138\n - 192.0.0.95:15138\n - 192.0.0.131:15138\n - 192.0.0.209:15138\n slave_l1:\n - - 192.0.2.203:15138\n - 192.0.2.204:15138\n";
178
179 #[test]
180 fn parses_cache_service_yaml_namespace() {
181 let cfg = CacheServiceConfig::from_yaml_str(SAMPLE_YAML).unwrap();
182 let ns = cfg.namespace("example-abtest").unwrap();
183 assert_eq!(ns.hash(), Some("crc32"));
185 assert_eq!(ns.distribution(), Some(crate::sharding::DIST_MODULA));
186 assert_eq!(
187 ns.masters(),
188 &[
189 "192.0.4.103:15138",
190 "192.0.0.95:15138",
191 "192.0.0.131:15138",
192 "192.0.0.209:15138",
193 ]
194 );
195 assert_eq!(ns.slave_l1().len(), 1);
196 assert_eq!(
197 ns.slave_l1()[0],
198 &["192.0.2.203:15138", "192.0.2.204:15138"]
199 );
200 }
201
202 #[test]
203 fn masters_of_returns_none_for_missing_namespace() {
204 let cfg = CacheServiceConfig::from_yaml_str(SAMPLE_YAML).unwrap();
205 assert!(cfg.masters_of("nope").is_none());
206 }
207
208 #[test]
209 fn masters_from_yaml_returns_master_list() {
210 let masters = masters_from_yaml(SAMPLE_YAML, "example-abtest").unwrap();
211 assert_eq!(
212 masters,
213 vec![
214 "192.0.4.103:15138",
215 "192.0.0.95:15138",
216 "192.0.0.131:15138",
217 "192.0.0.209:15138",
218 ]
219 );
220 }
221
222 #[test]
223 fn masters_from_yaml_errors_on_missing_namespace() {
224 assert!(matches!(
225 masters_from_yaml(SAMPLE_YAML, "missing-namespace"),
226 Err(CacheServiceError::MissingNamespace(n)) if n == "missing-namespace"
227 ));
228 }
229
230 #[test]
231 fn masters_from_yaml_rejects_empty_namespace() {
232 assert!(matches!(
233 masters_from_yaml(SAMPLE_YAML, " \t"),
234 Err(CacheServiceError::InvalidNamespace(_))
235 ));
236 }
237
238 #[test]
239 fn ignores_unknown_yaml_fields() {
240 let yaml = "other-namespace:\n hash: bkdr\nexample-abtest:\n master:\n - 1.2.3.4:15138\n custom_field: whatever\n nested:\n a: 1\n";
243 let masters = masters_from_yaml(yaml, "example-abtest").unwrap();
244 assert_eq!(masters, vec!["1.2.3.4:15138"]);
245 }
246
247 #[test]
248 fn masters_defaults_to_empty_when_absent() {
249 let yaml = "example-abtest:\n hash: crc32\n";
252 let cfg = CacheServiceConfig::from_yaml_str(yaml).unwrap();
253 assert_eq!(cfg.masters_of("example-abtest").unwrap(), &[] as &[String]);
254 }
255
256 #[test]
257 fn malformed_yaml_is_an_error() {
258 assert!(matches!(
259 CacheServiceConfig::from_yaml_str("not: [valid: yaml"),
260 Err(CacheServiceError::Yaml(_))
261 ));
262 }
263}