1use crate::failure_injection::{FailureConfig, FailureInjector};
8use crate::latency::{FaultConfig, LatencyInjector, LatencyProfile};
9use rand::Rng;
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12use tokio::sync::RwLock;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ChaosConfig {
17 pub enabled: bool,
19 pub error_rate: f64,
21 pub delay_rate: f64,
23 pub min_delay_ms: u64,
25 pub max_delay_ms: u64,
27 pub status_codes: Vec<u16>,
29 pub inject_timeouts: bool,
31 pub timeout_ms: u64,
33}
34
35impl Default for ChaosConfig {
36 fn default() -> Self {
37 Self {
38 enabled: false,
39 error_rate: 0.1, delay_rate: 0.3, min_delay_ms: 100,
42 max_delay_ms: 2000,
43 status_codes: vec![500, 502, 503, 504],
44 inject_timeouts: false,
45 timeout_ms: 5000,
46 }
47 }
48}
49
50impl ChaosConfig {
51 pub fn new(error_rate: f64, delay_rate: f64) -> Self {
53 Self {
54 enabled: true,
55 error_rate: error_rate.clamp(0.0, 1.0),
56 delay_rate: delay_rate.clamp(0.0, 1.0),
57 ..Default::default()
58 }
59 }
60
61 pub fn with_delay_range(mut self, min_ms: u64, max_ms: u64) -> Self {
63 self.min_delay_ms = min_ms;
64 self.max_delay_ms = max_ms;
65 self
66 }
67
68 pub fn with_status_codes(mut self, codes: Vec<u16>) -> Self {
70 self.status_codes = codes;
71 self
72 }
73
74 pub fn with_timeouts(mut self, timeout_ms: u64) -> Self {
76 self.inject_timeouts = true;
77 self.timeout_ms = timeout_ms;
78 self
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct ChaosEngine {
85 config: Arc<RwLock<ChaosConfig>>,
86 latency_injector: Arc<RwLock<LatencyInjector>>,
87 failure_injector: Arc<RwLock<FailureInjector>>,
88}
89
90impl ChaosEngine {
91 pub fn new(config: ChaosConfig) -> Self {
93 let latency_profile = LatencyProfile::new(
95 (config.min_delay_ms + config.max_delay_ms) / 2,
96 (config.max_delay_ms - config.min_delay_ms) / 2,
97 );
98
99 let fault_config = FaultConfig {
100 failure_rate: config.error_rate,
101 status_codes: config.status_codes.clone(),
102 error_responses: Default::default(),
103 };
104
105 let latency_injector = LatencyInjector::new(latency_profile, fault_config);
106
107 let failure_config = FailureConfig {
109 global_error_rate: config.error_rate,
110 default_status_codes: config.status_codes.clone(),
111 tag_configs: Default::default(),
112 include_tags: Vec::new(),
113 exclude_tags: Vec::new(),
114 };
115
116 let failure_injector = FailureInjector::new(Some(failure_config), config.enabled);
117
118 Self {
119 config: Arc::new(RwLock::new(config)),
120 latency_injector: Arc::new(RwLock::new(latency_injector)),
121 failure_injector: Arc::new(RwLock::new(failure_injector)),
122 }
123 }
124
125 pub async fn is_enabled(&self) -> bool {
127 self.config.read().await.enabled
128 }
129
130 pub async fn set_enabled(&self, enabled: bool) {
132 let mut config = self.config.write().await;
133 config.enabled = enabled;
134
135 let mut latency = self.latency_injector.write().await;
137 latency.set_enabled(enabled);
138
139 let mut failure = self.failure_injector.write().await;
140 failure.set_enabled(enabled);
141 }
142
143 pub async fn update_config(&self, new_config: ChaosConfig) {
145 let mut config = self.config.write().await;
146 *config = new_config.clone();
147
148 let latency_profile = LatencyProfile::new(
150 (new_config.min_delay_ms + new_config.max_delay_ms) / 2,
151 (new_config.max_delay_ms - new_config.min_delay_ms) / 2,
152 );
153
154 let fault_config = FaultConfig {
155 failure_rate: new_config.error_rate,
156 status_codes: new_config.status_codes.clone(),
157 error_responses: Default::default(),
158 };
159
160 let mut latency = self.latency_injector.write().await;
161 *latency = LatencyInjector::new(latency_profile, fault_config);
162 latency.set_enabled(new_config.enabled);
163
164 let failure_config = FailureConfig {
165 global_error_rate: new_config.error_rate,
166 default_status_codes: new_config.status_codes.clone(),
167 tag_configs: Default::default(),
168 include_tags: Vec::new(),
169 exclude_tags: Vec::new(),
170 };
171
172 let mut failure = self.failure_injector.write().await;
173 failure.update_config(Some(failure_config));
174 failure.set_enabled(new_config.enabled);
175 }
176
177 pub async fn process_request(&self, _tags: &[String]) -> ChaosResult {
180 let config = self.config.read().await;
181
182 if !config.enabled {
183 return ChaosResult::Success;
184 }
185
186 let mut rng = rand::rng();
187
188 if rng.random_bool(config.error_rate) {
190 let status_code = if config.status_codes.is_empty() {
191 500
192 } else {
193 let index = rng.random_range(0..config.status_codes.len());
194 config.status_codes[index]
195 };
196
197 return ChaosResult::Error {
198 status_code,
199 message: format!("Chaos-injected error (rate: {:.1}%)", config.error_rate * 100.0),
200 };
201 }
202
203 if rng.random_bool(config.delay_rate) {
205 let delay_ms = rng.random_range(config.min_delay_ms..=config.max_delay_ms);
206 return ChaosResult::Delay { delay_ms };
207 }
208
209 if config.inject_timeouts && rng.random_bool(0.05) {
211 return ChaosResult::Timeout {
213 timeout_ms: config.timeout_ms,
214 };
215 }
216
217 ChaosResult::Success
218 }
219
220 pub async fn inject_latency(&self, tags: &[String]) -> crate::Result<()> {
222 let config = self.config.read().await;
223
224 if !config.enabled {
225 return Ok(());
226 }
227
228 let mut rng = rand::rng();
229
230 if rng.random_bool(config.delay_rate) {
232 let latency = self.latency_injector.read().await;
233 latency.inject_latency(tags).await?;
234 }
235
236 Ok(())
237 }
238
239 pub async fn should_inject_error(&self, tags: &[String]) -> bool {
241 let config = self.config.read().await;
242
243 if !config.enabled {
244 return false;
245 }
246
247 let failure = self.failure_injector.read().await;
248 failure.should_inject_failure(tags)
249 }
250
251 pub async fn get_error_response(&self) -> Option<(u16, String)> {
253 let config = self.config.read().await;
254
255 if !config.enabled {
256 return None;
257 }
258
259 let mut rng = rand::rng();
260 let status_code = if config.status_codes.is_empty() {
261 500
262 } else {
263 let index = rng.random_range(0..config.status_codes.len());
264 config.status_codes[index]
265 };
266
267 Some((
268 status_code,
269 format!("Chaos-injected error (rate: {:.1}%)", config.error_rate * 100.0),
270 ))
271 }
272
273 pub async fn get_config(&self) -> ChaosConfig {
275 self.config.read().await.clone()
276 }
277
278 pub async fn get_statistics(&self) -> ChaosStatistics {
280 let config = self.config.read().await;
281 ChaosStatistics {
282 enabled: config.enabled,
283 error_rate: config.error_rate,
284 delay_rate: config.delay_rate,
285 min_delay_ms: config.min_delay_ms,
286 max_delay_ms: config.max_delay_ms,
287 inject_timeouts: config.inject_timeouts,
288 }
289 }
290}
291
292impl Default for ChaosEngine {
293 fn default() -> Self {
294 Self::new(ChaosConfig::default())
295 }
296}
297
298#[derive(Debug, Clone)]
300pub enum ChaosResult {
301 Success,
303 Error {
305 status_code: u16,
307 message: String,
309 },
310 Delay {
312 delay_ms: u64,
314 },
315 Timeout {
317 timeout_ms: u64,
319 },
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct ChaosStatistics {
325 pub enabled: bool,
327 pub error_rate: f64,
329 pub delay_rate: f64,
331 pub min_delay_ms: u64,
333 pub max_delay_ms: u64,
335 pub inject_timeouts: bool,
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_chaos_config_default() {
345 let config = ChaosConfig::default();
346 assert!(!config.enabled);
347 assert_eq!(config.error_rate, 0.1);
348 assert_eq!(config.delay_rate, 0.3);
349 assert!(!config.inject_timeouts);
350 }
351
352 #[test]
353 fn test_chaos_config_new() {
354 let config = ChaosConfig::new(0.2, 0.5);
355 assert!(config.enabled);
356 assert_eq!(config.error_rate, 0.2);
357 assert_eq!(config.delay_rate, 0.5);
358 }
359
360 #[test]
361 fn test_chaos_config_builder() {
362 let config = ChaosConfig::new(0.1, 0.2)
363 .with_delay_range(50, 500)
364 .with_status_codes(vec![500, 503])
365 .with_timeouts(3000);
366
367 assert_eq!(config.min_delay_ms, 50);
368 assert_eq!(config.max_delay_ms, 500);
369 assert_eq!(config.status_codes, vec![500, 503]);
370 assert!(config.inject_timeouts);
371 assert_eq!(config.timeout_ms, 3000);
372 }
373
374 #[tokio::test]
375 async fn test_chaos_engine_creation() {
376 let config = ChaosConfig::new(0.5, 0.5);
377 let engine = ChaosEngine::new(config);
378
379 assert!(engine.is_enabled().await);
380 }
381
382 #[tokio::test]
383 async fn test_chaos_engine_enable_disable() {
384 let config = ChaosConfig::new(0.5, 0.5);
385 let engine = ChaosEngine::new(config);
386
387 assert!(engine.is_enabled().await);
388
389 engine.set_enabled(false).await;
390 assert!(!engine.is_enabled().await);
391
392 engine.set_enabled(true).await;
393 assert!(engine.is_enabled().await);
394 }
395
396 #[tokio::test]
397 async fn test_chaos_engine_disabled_returns_success() {
398 let mut config = ChaosConfig::new(1.0, 1.0); config.enabled = false;
400 let engine = ChaosEngine::new(config);
401
402 let result = engine.process_request(&[]).await;
403 assert!(matches!(result, ChaosResult::Success));
404 }
405
406 #[tokio::test]
407 async fn test_chaos_engine_high_error_rate() {
408 let config = ChaosConfig::new(1.0, 0.0) .with_status_codes(vec![503]);
410 let engine = ChaosEngine::new(config);
411
412 let mut error_count = 0;
413 for _ in 0..10 {
414 let result = engine.process_request(&[]).await;
415 if let ChaosResult::Error { status_code, .. } = result {
416 assert_eq!(status_code, 503);
417 error_count += 1;
418 }
419 }
420
421 assert_eq!(error_count, 10);
423 }
424
425 #[tokio::test]
426 async fn test_chaos_engine_high_delay_rate() {
427 let config = ChaosConfig::new(0.0, 1.0) .with_delay_range(100, 200);
429 let engine = ChaosEngine::new(config);
430
431 let mut delay_count = 0;
432 for _ in 0..10 {
433 let result = engine.process_request(&[]).await;
434 if let ChaosResult::Delay { delay_ms } = result {
435 assert!((100..=200).contains(&delay_ms));
436 delay_count += 1;
437 }
438 }
439
440 assert_eq!(delay_count, 10);
442 }
443
444 #[tokio::test]
445 async fn test_chaos_engine_update_config() {
446 let config = ChaosConfig::new(0.5, 0.5);
447 let engine = ChaosEngine::new(config);
448
449 let new_config = ChaosConfig::new(0.2, 0.8).with_delay_range(50, 100);
450
451 engine.update_config(new_config).await;
452
453 let updated = engine.get_config().await;
454 assert_eq!(updated.error_rate, 0.2);
455 assert_eq!(updated.delay_rate, 0.8);
456 assert_eq!(updated.min_delay_ms, 50);
457 assert_eq!(updated.max_delay_ms, 100);
458 }
459
460 #[tokio::test]
461 async fn test_chaos_engine_statistics() {
462 let config = ChaosConfig::new(0.3, 0.4).with_delay_range(100, 500).with_timeouts(2000);
463
464 let engine = ChaosEngine::new(config);
465 let stats = engine.get_statistics().await;
466
467 assert!(stats.enabled);
468 assert_eq!(stats.error_rate, 0.3);
469 assert_eq!(stats.delay_rate, 0.4);
470 assert_eq!(stats.min_delay_ms, 100);
471 assert_eq!(stats.max_delay_ms, 500);
472 assert!(stats.inject_timeouts);
473 }
474
475 #[tokio::test]
476 async fn test_chaos_result_variants() {
477 let success = ChaosResult::Success;
478 assert!(matches!(success, ChaosResult::Success));
479
480 let error = ChaosResult::Error {
481 status_code: 500,
482 message: "Error".to_string(),
483 };
484 if let ChaosResult::Error { status_code, .. } = error {
485 assert_eq!(status_code, 500);
486 } else {
487 panic!("Expected Error variant");
488 }
489
490 let delay = ChaosResult::Delay { delay_ms: 100 };
491 if let ChaosResult::Delay { delay_ms } = delay {
492 assert_eq!(delay_ms, 100);
493 } else {
494 panic!("Expected Delay variant");
495 }
496 }
497}