⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/services/lb/
config.rs

1//! Load Balancer Configuration
2//!
3//! Provides configuration parsing and shell command integration
4//! for the L4/L7 load balancers.
5
6#![allow(dead_code)]
7
8use alloc::{string::String, vec::Vec};
9
10use super::l4::LbAlgorithm;
11
12// ---------------------------------------------------------------------------
13// Configuration
14// ---------------------------------------------------------------------------
15
16/// VIP configuration entry.
17#[derive(Debug, Clone)]
18pub struct VipConfig {
19    /// VIP address.
20    pub address: String,
21    /// VIP port.
22    pub port: u16,
23    /// Algorithm name.
24    pub algorithm: LbAlgorithm,
25    /// Backend addresses (addr:port).
26    pub backends: Vec<String>,
27}
28
29/// Route configuration entry.
30#[derive(Debug, Clone)]
31pub struct RouteConfig {
32    /// Path prefix.
33    pub path_prefix: String,
34    /// Host header.
35    pub host: String,
36    /// Backend group name.
37    pub backend_group: String,
38}
39
40/// Load balancer configuration.
41#[derive(Debug, Clone)]
42pub struct LbConfig {
43    /// VIP definitions.
44    pub vips: Vec<VipConfig>,
45    /// Route definitions.
46    pub routes: Vec<RouteConfig>,
47    /// Health check interval in ticks.
48    pub health_check_interval: u64,
49    /// Default rate limit (requests per second).
50    pub rate_limit_rps: u32,
51    /// Default rate limit burst.
52    pub rate_limit_burst: u32,
53}
54
55impl Default for LbConfig {
56    fn default() -> Self {
57        LbConfig {
58            vips: Vec::new(),
59            routes: Vec::new(),
60            health_check_interval: 30,
61            rate_limit_rps: 100,
62            rate_limit_burst: 200,
63        }
64    }
65}
66
67/// Config parse error.
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum ConfigError {
70    /// Invalid configuration line.
71    InvalidLine(String),
72    /// Missing required field.
73    MissingField(String),
74    /// Invalid port number.
75    InvalidPort(String),
76    /// Unknown algorithm.
77    UnknownAlgorithm(String),
78}
79
80/// Parse a load balancer configuration from key=value format.
81///
82/// Recognized keys:
83/// - `health_check_interval=N`
84/// - `rate_limit_rps=N`
85/// - `rate_limit_burst=N`
86/// - `vip=addr:port:algorithm` (e.g., `vip=10.96.0.1:80:roundrobin`)
87/// - `backend=vip_addr:vip_port:backend_addr:backend_port`
88/// - `route=path_prefix:host:backend_group`
89pub fn parse_config(input: &str) -> Result<LbConfig, ConfigError> {
90    let mut config = LbConfig::default();
91
92    for line in input.lines() {
93        let line = line.trim();
94        if line.is_empty() || line.starts_with('#') {
95            continue;
96        }
97
98        if let Some((key, value)) = line.split_once('=') {
99            let key = key.trim();
100            let value = value.trim();
101
102            match key {
103                "health_check_interval" => {
104                    config.health_check_interval = parse_u64(value)?;
105                }
106                "rate_limit_rps" => {
107                    config.rate_limit_rps = parse_u32(value)?;
108                }
109                "rate_limit_burst" => {
110                    config.rate_limit_burst = parse_u32(value)?;
111                }
112                "vip" => {
113                    let vip = parse_vip_config(value)?;
114                    config.vips.push(vip);
115                }
116                "route" => {
117                    let route = parse_route_config(value)?;
118                    config.routes.push(route);
119                }
120                _ => {
121                    // Unknown keys are ignored
122                }
123            }
124        }
125    }
126
127    Ok(config)
128}
129
130/// Parse a VIP config: "addr:port:algorithm"
131fn parse_vip_config(value: &str) -> Result<VipConfig, ConfigError> {
132    let parts: Vec<&str> = value.splitn(3, ':').collect();
133    if parts.len() < 3 {
134        return Err(ConfigError::MissingField(String::from(
135            "vip requires addr:port:algorithm",
136        )));
137    }
138
139    let address = String::from(parts[0]);
140    let port = parse_u16(parts[1])?;
141    let algorithm = parse_algorithm(parts[2])?;
142
143    Ok(VipConfig {
144        address,
145        port,
146        algorithm,
147        backends: Vec::new(),
148    })
149}
150
151/// Parse a route config: "path_prefix:host:backend_group"
152fn parse_route_config(value: &str) -> Result<RouteConfig, ConfigError> {
153    let parts: Vec<&str> = value.splitn(3, ':').collect();
154    if parts.len() < 3 {
155        return Err(ConfigError::MissingField(String::from(
156            "route requires path:host:group",
157        )));
158    }
159
160    Ok(RouteConfig {
161        path_prefix: String::from(parts[0]),
162        host: String::from(parts[1]),
163        backend_group: String::from(parts[2]),
164    })
165}
166
167/// Parse algorithm name.
168fn parse_algorithm(s: &str) -> Result<LbAlgorithm, ConfigError> {
169    match s {
170        "roundrobin" | "rr" => Ok(LbAlgorithm::RoundRobin),
171        "leastconn" | "lc" => Ok(LbAlgorithm::LeastConnections),
172        "weighted" | "wrr" => Ok(LbAlgorithm::WeightedRoundRobin),
173        "random" => Ok(LbAlgorithm::Random),
174        "iphash" => Ok(LbAlgorithm::IpHash),
175        _ => Err(ConfigError::UnknownAlgorithm(String::from(s))),
176    }
177}
178
179/// Parse u64 from string.
180fn parse_u64(s: &str) -> Result<u64, ConfigError> {
181    let mut result: u64 = 0;
182    for ch in s.bytes() {
183        if !ch.is_ascii_digit() {
184            return Err(ConfigError::InvalidLine(String::from(s)));
185        }
186        result = result
187            .checked_mul(10)
188            .and_then(|r| r.checked_add((ch - b'0') as u64))
189            .ok_or_else(|| ConfigError::InvalidLine(String::from(s)))?;
190    }
191    Ok(result)
192}
193
194/// Parse u32 from string.
195fn parse_u32(s: &str) -> Result<u32, ConfigError> {
196    let v = parse_u64(s)?;
197    if v > u32::MAX as u64 {
198        return Err(ConfigError::InvalidLine(String::from(s)));
199    }
200    Ok(v as u32)
201}
202
203/// Parse u16 from string.
204fn parse_u16(s: &str) -> Result<u16, ConfigError> {
205    let v = parse_u64(s)?;
206    if v > u16::MAX as u64 {
207        return Err(ConfigError::InvalidPort(String::from(s)));
208    }
209    Ok(v as u16)
210}
211
212// ---------------------------------------------------------------------------
213// Tests
214// ---------------------------------------------------------------------------
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn test_parse_empty_config() {
222        let config = parse_config("").unwrap();
223        assert!(config.vips.is_empty());
224        assert_eq!(config.health_check_interval, 30);
225    }
226
227    #[test]
228    fn test_parse_basic_config() {
229        let input = "\
230health_check_interval=60
231rate_limit_rps=50
232rate_limit_burst=100
233vip=10.96.0.1:80:roundrobin
234route=/api:example.com:api-group
235";
236        let config = parse_config(input).unwrap();
237        assert_eq!(config.health_check_interval, 60);
238        assert_eq!(config.rate_limit_rps, 50);
239        assert_eq!(config.vips.len(), 1);
240        assert_eq!(config.vips[0].port, 80);
241        assert_eq!(config.vips[0].algorithm, LbAlgorithm::RoundRobin);
242        assert_eq!(config.routes.len(), 1);
243        assert_eq!(config.routes[0].path_prefix, "/api");
244    }
245
246    #[test]
247    fn test_parse_algorithms() {
248        assert_eq!(
249            parse_algorithm("roundrobin").unwrap(),
250            LbAlgorithm::RoundRobin
251        );
252        assert_eq!(
253            parse_algorithm("leastconn").unwrap(),
254            LbAlgorithm::LeastConnections
255        );
256        assert_eq!(parse_algorithm("iphash").unwrap(), LbAlgorithm::IpHash);
257        assert!(parse_algorithm("unknown").is_err());
258    }
259
260    #[test]
261    fn test_parse_with_comments() {
262        let input = "# comment\nhealth_check_interval=10\n# another\n";
263        let config = parse_config(input).unwrap();
264        assert_eq!(config.health_check_interval, 10);
265    }
266}