Skip to main content

heliosdb_proxy/routing/
config.rs

1//! Routing Configuration
2//!
3//! Configuration for query routing hints and policies.
4
5use super::{ConsistencyLevel, RouteTarget};
6use std::collections::HashMap;
7use std::time::Duration;
8
9/// Routing configuration
10#[derive(Debug, Clone)]
11pub struct RoutingConfig {
12    /// Default routing policy
13    pub default: DefaultPolicy,
14    /// Hint configuration
15    pub hints: HintConfig,
16    /// Consistency level definitions
17    pub consistency: HashMap<ConsistencyLevel, ConsistencyConfig>,
18    /// Route aliases (e.g., "vector" -> ["standby-vector-1", "standby-vector-2"])
19    pub aliases: HashMap<String, Vec<String>>,
20    /// Per-route configuration
21    pub routes: HashMap<RouteTarget, RouteConfig>,
22}
23
24impl Default for RoutingConfig {
25    fn default() -> Self {
26        let mut consistency = HashMap::new();
27        consistency.insert(
28            ConsistencyLevel::Strong,
29            ConsistencyConfig {
30                allowed_nodes: vec!["primary".to_string(), "standby-sync".to_string()],
31                max_lag_ms: 0,
32            },
33        );
34        consistency.insert(
35            ConsistencyLevel::Bounded,
36            ConsistencyConfig {
37                allowed_nodes: vec![
38                    "primary".to_string(),
39                    "standby-sync".to_string(),
40                    "standby-semisync".to_string(),
41                ],
42                max_lag_ms: 1000,
43            },
44        );
45        consistency.insert(
46            ConsistencyLevel::Eventual,
47            ConsistencyConfig {
48                allowed_nodes: vec!["*".to_string()],
49                max_lag_ms: u64::MAX,
50            },
51        );
52
53        Self {
54            default: DefaultPolicy::default(),
55            hints: HintConfig::default(),
56            consistency,
57            aliases: HashMap::new(),
58            routes: HashMap::new(),
59        }
60    }
61}
62
63impl RoutingConfig {
64    /// Create a new routing configuration
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Add a route alias
70    pub fn add_alias(&mut self, alias: &str, nodes: Vec<String>) {
71        self.aliases.insert(alias.to_string(), nodes);
72    }
73
74    /// Get nodes for an alias
75    pub fn resolve_alias(&self, alias: &str) -> Option<&Vec<String>> {
76        self.aliases.get(alias)
77    }
78
79    /// Get consistency config for level
80    pub fn get_consistency_config(&self, level: ConsistencyLevel) -> Option<&ConsistencyConfig> {
81        self.consistency.get(&level)
82    }
83
84    /// Check if a hint is allowed
85    pub fn is_hint_allowed(&self, hint_name: &str) -> bool {
86        if !self.hints.enabled {
87            return false;
88        }
89
90        match hint_name {
91            "node" => self.hints.allow_node_hints,
92            "route" => true,
93            "primary" => self.hints.allow_primary_reads,
94            _ => true,
95        }
96    }
97}
98
99/// Default routing policy
100#[derive(Debug, Clone)]
101pub struct DefaultPolicy {
102    /// Default target for read queries
103    pub read_target: RouteTarget,
104    /// Default target for write queries
105    pub write_target: RouteTarget,
106    /// Default consistency level
107    pub consistency: ConsistencyLevel,
108    /// Default query timeout
109    pub timeout: Duration,
110    /// Enable read/write auto-detection
111    pub auto_detect_writes: bool,
112}
113
114impl Default for DefaultPolicy {
115    fn default() -> Self {
116        Self {
117            read_target: RouteTarget::Standby,
118            write_target: RouteTarget::Primary,
119            consistency: ConsistencyLevel::Eventual,
120            timeout: Duration::from_secs(30),
121            auto_detect_writes: true,
122        }
123    }
124}
125
126/// Hint configuration
127#[derive(Debug, Clone)]
128pub struct HintConfig {
129    /// Enable routing hints
130    pub enabled: bool,
131    /// Allow routing to specific nodes by name
132    pub allow_node_hints: bool,
133    /// Allow routing reads to primary
134    pub allow_primary_reads: bool,
135    /// Require authentication for hints
136    pub require_auth: bool,
137    /// Strip hints before sending to backend
138    pub strip_hints: bool,
139    /// Log routing decisions
140    pub log_decisions: bool,
141    /// Maximum lag override allowed via hint (None = no limit)
142    pub max_lag_override: Option<Duration>,
143}
144
145impl Default for HintConfig {
146    fn default() -> Self {
147        Self {
148            enabled: true,
149            allow_node_hints: true,
150            allow_primary_reads: true,
151            require_auth: false,
152            strip_hints: true,
153            log_decisions: false,
154            max_lag_override: Some(Duration::from_secs(60)),
155        }
156    }
157}
158
159/// Consistency level configuration
160#[derive(Debug, Clone)]
161pub struct ConsistencyConfig {
162    /// Allowed node patterns
163    pub allowed_nodes: Vec<String>,
164    /// Maximum lag in milliseconds (0 = no lag allowed)
165    pub max_lag_ms: u64,
166}
167
168impl ConsistencyConfig {
169    /// Check if a node name matches this consistency config
170    pub fn allows_node(&self, node_name: &str) -> bool {
171        self.allowed_nodes.iter().any(|pattern| {
172            if pattern == "*" {
173                true
174            } else if pattern.ends_with('*') {
175                node_name.starts_with(&pattern[..pattern.len() - 1])
176            } else {
177                node_name == pattern
178            }
179        })
180    }
181}
182
183/// Per-route configuration
184#[derive(Debug, Clone)]
185pub struct RouteConfig {
186    /// Nodes that can serve this route
187    pub node_patterns: Vec<String>,
188    /// Maximum lag for this route
189    pub max_lag_ms: u64,
190    /// Load balancing strategy override
191    pub load_balance: Option<LoadBalanceStrategy>,
192}
193
194/// Load balancing strategies for routes
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum LoadBalanceStrategy {
197    /// Round-robin selection
198    RoundRobin,
199    /// Weighted round-robin
200    Weighted,
201    /// Least connections
202    LeastConnections,
203    /// Latency-based selection
204    LatencyBased,
205    /// Random selection
206    Random,
207}
208
209/// Route alias configuration
210#[derive(Debug, Clone)]
211pub struct AliasConfig {
212    /// Alias name
213    pub name: String,
214    /// Target nodes for this alias
215    pub nodes: Vec<String>,
216    /// Whether this is an auto-detected alias
217    pub auto: bool,
218}
219
220impl AliasConfig {
221    /// Create a new alias configuration
222    pub fn new(name: &str, nodes: Vec<String>) -> Self {
223        Self {
224            name: name.to_string(),
225            nodes,
226            auto: false,
227        }
228    }
229
230    /// Create an auto-detected alias
231    pub fn auto(name: &str) -> Self {
232        Self {
233            name: name.to_string(),
234            nodes: Vec::new(),
235            auto: true,
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_default_config() {
246        let config = RoutingConfig::default();
247
248        assert!(config.hints.enabled);
249        assert!(config.hints.allow_node_hints);
250        assert!(config.hints.allow_primary_reads);
251        assert_eq!(config.default.read_target, RouteTarget::Standby);
252        assert_eq!(config.default.write_target, RouteTarget::Primary);
253    }
254
255    #[test]
256    fn test_consistency_config() {
257        let config = RoutingConfig::default();
258
259        let strong = config
260            .get_consistency_config(ConsistencyLevel::Strong)
261            .unwrap();
262        assert_eq!(strong.max_lag_ms, 0);
263        assert!(strong.allowed_nodes.contains(&"primary".to_string()));
264
265        let eventual = config
266            .get_consistency_config(ConsistencyLevel::Eventual)
267            .unwrap();
268        assert!(eventual.allows_node("any-node"));
269    }
270
271    #[test]
272    fn test_alias_resolution() {
273        let mut config = RoutingConfig::default();
274        config.add_alias(
275            "vector",
276            vec![
277                "standby-vector-1".to_string(),
278                "standby-vector-2".to_string(),
279            ],
280        );
281
282        let nodes = config.resolve_alias("vector").unwrap();
283        assert_eq!(nodes.len(), 2);
284    }
285
286    #[test]
287    fn test_node_pattern_matching() {
288        let config = ConsistencyConfig {
289            allowed_nodes: vec!["standby-*".to_string()],
290            max_lag_ms: 1000,
291        };
292
293        assert!(config.allows_node("standby-sync-1"));
294        assert!(config.allows_node("standby-async-2"));
295        assert!(!config.allows_node("primary"));
296    }
297
298    #[test]
299    fn test_hint_allowed() {
300        let mut config = RoutingConfig::default();
301        assert!(config.is_hint_allowed("route"));
302        assert!(config.is_hint_allowed("node"));
303
304        config.hints.allow_node_hints = false;
305        assert!(!config.is_hint_allowed("node"));
306
307        config.hints.enabled = false;
308        assert!(!config.is_hint_allowed("route"));
309    }
310}