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