veridian_kernel/services/lb/
config.rs1#![allow(dead_code)]
7
8use alloc::{string::String, vec::Vec};
9
10use super::l4::LbAlgorithm;
11
12#[derive(Debug, Clone)]
18pub struct VipConfig {
19 pub address: String,
21 pub port: u16,
23 pub algorithm: LbAlgorithm,
25 pub backends: Vec<String>,
27}
28
29#[derive(Debug, Clone)]
31pub struct RouteConfig {
32 pub path_prefix: String,
34 pub host: String,
36 pub backend_group: String,
38}
39
40#[derive(Debug, Clone)]
42pub struct LbConfig {
43 pub vips: Vec<VipConfig>,
45 pub routes: Vec<RouteConfig>,
47 pub health_check_interval: u64,
49 pub rate_limit_rps: u32,
51 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#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum ConfigError {
70 InvalidLine(String),
72 MissingField(String),
74 InvalidPort(String),
76 UnknownAlgorithm(String),
78}
79
80pub 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 }
123 }
124 }
125 }
126
127 Ok(config)
128}
129
130fn 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
151fn 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
167fn 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
179fn 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
194fn 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
203fn 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#[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}