dynamo_memory/nixl/
config.rs1use anyhow::{Result, bail};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39pub struct NixlBackendConfig {
40 #[serde(default)]
46 backends: HashMap<String, HashMap<String, String>>,
47}
48
49impl NixlBackendConfig {
50 pub fn new(backends: HashMap<String, HashMap<String, String>>) -> Self {
54 Self { backends }
55 }
56
57 pub fn from_env() -> Result<Self> {
66 let mut backends = HashMap::new();
67
68 for (key, value) in std::env::vars() {
70 if let Some(remainder) = key.strip_prefix("DYN_KVBM_NIXL_BACKEND_") {
71 if remainder.contains('_') {
73 bail!(
74 "Custom NIXL backend parameters are not yet supported. \
75 Found: {}. Please use only DYN_KVBM_NIXL_BACKEND_<backend>=true \
76 to enable backends with default parameters.",
77 key
78 );
79 }
80
81 let backend_name = remainder.to_uppercase();
85 match crate::parse_bool_opt(&value) {
86 Some(true) => {
87 backends.insert(backend_name, HashMap::new());
88 }
89 Some(false) => {
90 continue;
92 }
93 None => bail!(
94 "Invalid value for {}: '{}'. Expected one of: true/false, 1/0, on/off, yes/no",
95 key,
96 value
97 ),
98 }
99 }
100 }
101
102 Ok(Self { backends })
103 }
104
105 pub fn with_backend(mut self, backend: impl Into<String>) -> Self {
108 self.backends
109 .insert(backend.into().to_uppercase(), HashMap::new());
110 self
111 }
112
113 pub fn with_backend_params(
116 mut self,
117 backend: impl Into<String>,
118 params: HashMap<String, String>,
119 ) -> Self {
120 self.backends.insert(backend.into().to_uppercase(), params);
121 self
122 }
123
124 pub fn backends(&self) -> Vec<String> {
126 self.backends.keys().cloned().collect()
127 }
128
129 pub fn backend_params(&self, backend: &str) -> Option<&HashMap<String, String>> {
134 self.backends.get(&backend.to_uppercase())
135 }
136
137 pub fn has_backend(&self, backend: &str) -> bool {
139 self.backends.contains_key(&backend.to_uppercase())
140 }
141
142 pub fn merge(mut self, other: NixlBackendConfig) -> Self {
147 self.backends.extend(other.backends);
148 self
149 }
150
151 pub fn iter(&self) -> impl Iterator<Item = (&String, &HashMap<String, String>)> {
153 self.backends.iter()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_new_config_is_empty() {
163 let config = NixlBackendConfig::default();
164 assert_eq!(config.backends().len(), 0);
165 }
166
167 #[test]
168 fn test_default_is_empty() {
169 let config = NixlBackendConfig::default();
170 assert!(config.backends().is_empty()); }
172
173 #[test]
174 fn test_with_backend() {
175 let config = NixlBackendConfig::default()
176 .with_backend("ucx")
177 .with_backend("gds_mt");
178
179 assert!(config.has_backend("ucx"));
180 assert!(config.has_backend("UCX"));
181 assert!(config.has_backend("gds_mt"));
182 assert!(config.has_backend("GDS_MT"));
183 assert!(!config.has_backend("other"));
184 }
185
186 #[test]
187 fn test_with_backend_params() {
188 let mut params = HashMap::new();
189 params.insert("threads".to_string(), "4".to_string());
190 params.insert("buffer_size".to_string(), "1048576".to_string());
191
192 let config = NixlBackendConfig::default()
193 .with_backend("UCX")
194 .with_backend_params("GDS", params);
195
196 let ucx_params = config.backend_params("UCX").unwrap();
198 assert!(ucx_params.is_empty());
199
200 let gds_params = config.backend_params("GDS").unwrap();
202 assert_eq!(gds_params.get("threads"), Some(&"4".to_string()));
203 assert_eq!(gds_params.get("buffer_size"), Some(&"1048576".to_string()));
204 }
205
206 #[test]
207 fn test_merge_configs() {
208 let config1 = NixlBackendConfig::default().with_backend("ucx");
209 let config2 = NixlBackendConfig::default().with_backend("gds");
210
211 let merged = config1.merge(config2);
212
213 assert!(merged.has_backend("ucx"));
214 assert!(merged.has_backend("gds"));
215 }
216
217 #[test]
218 fn test_backend_name_case_insensitive() {
219 let config = NixlBackendConfig::default()
220 .with_backend("ucx")
221 .with_backend("Gds_mt")
222 .with_backend("OTHER");
223
224 assert!(config.has_backend("UCX"));
225 assert!(config.has_backend("ucx"));
226 assert!(config.has_backend("GDS_MT"));
227 assert!(config.has_backend("gds_mt"));
228 assert!(config.has_backend("OTHER"));
229 assert!(config.has_backend("other"));
230 }
231
232 #[test]
233 fn test_iter() {
234 let mut params = HashMap::new();
235 params.insert("key".to_string(), "value".to_string());
236
237 let config = NixlBackendConfig::default()
238 .with_backend("UCX")
239 .with_backend_params("GDS", params);
240
241 let items: Vec<_> = config.iter().collect();
242 assert_eq!(items.len(), 2);
243 }
244
245 }