Skip to main content

heliosdb_proxy/routing/
config.rs

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