1use crate::scenario_orchestrator::{OrchestratedScenario, ScenarioStep};
7use chrono::{DateTime, Utc};
8use parking_lot::RwLock;
9use serde::{Deserialize, Serialize};
10use serde_json::Value as JsonValue;
11use std::collections::HashMap;
12use std::sync::Arc;
13use thiserror::Error;
14
15#[derive(Error, Debug)]
17pub enum OrchestrationError {
18 #[error("Assertion failed: {0}")]
19 AssertionFailed(String),
20
21 #[error("Hook execution failed: {0}")]
22 HookFailed(String),
23
24 #[error("Variable not found: {0}")]
25 VariableNotFound(String),
26
27 #[error("Condition evaluation failed: {0}")]
28 ConditionFailed(String),
29
30 #[error("Serialization error: {0}")]
31 SerializationError(String),
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(tag = "type", rename_all = "snake_case")]
37pub enum Condition {
38 Equals { variable: String, value: JsonValue },
40 NotEquals { variable: String, value: JsonValue },
42 GreaterThan { variable: String, value: f64 },
44 LessThan { variable: String, value: f64 },
46 GreaterThanOrEqual { variable: String, value: f64 },
48 LessThanOrEqual { variable: String, value: f64 },
50 Exists { variable: String },
52 And { conditions: Vec<Condition> },
54 Or { conditions: Vec<Condition> },
56 Not { condition: Box<Condition> },
58 PreviousStepSucceeded,
60 PreviousStepFailed,
62 MetricThreshold {
64 metric_name: String,
65 operator: ComparisonOperator,
66 threshold: f64,
67 },
68}
69
70#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum ComparisonOperator {
74 Equals,
75 NotEquals,
76 GreaterThan,
77 LessThan,
78 GreaterThanOrEqual,
79 LessThanOrEqual,
80}
81
82impl Condition {
83 pub fn evaluate(&self, context: &ExecutionContext) -> Result<bool, OrchestrationError> {
85 match self {
86 Condition::Equals { variable, value } => {
87 let var_value = context.get_variable(variable)?;
88 Ok(var_value == value)
89 }
90 Condition::NotEquals { variable, value } => {
91 let var_value = context.get_variable(variable)?;
92 Ok(var_value != value)
93 }
94 Condition::GreaterThan { variable, value } => {
95 let var_value = context.get_variable(variable)?;
96 if let Some(num) = var_value.as_f64() {
97 Ok(num > *value)
98 } else {
99 Err(OrchestrationError::ConditionFailed(format!(
100 "Variable {} is not a number",
101 variable
102 )))
103 }
104 }
105 Condition::LessThan { variable, value } => {
106 let var_value = context.get_variable(variable)?;
107 if let Some(num) = var_value.as_f64() {
108 Ok(num < *value)
109 } else {
110 Err(OrchestrationError::ConditionFailed(format!(
111 "Variable {} is not a number",
112 variable
113 )))
114 }
115 }
116 Condition::GreaterThanOrEqual { variable, value } => {
117 let var_value = context.get_variable(variable)?;
118 if let Some(num) = var_value.as_f64() {
119 Ok(num >= *value)
120 } else {
121 Err(OrchestrationError::ConditionFailed(format!(
122 "Variable {} is not a number",
123 variable
124 )))
125 }
126 }
127 Condition::LessThanOrEqual { variable, value } => {
128 let var_value = context.get_variable(variable)?;
129 if let Some(num) = var_value.as_f64() {
130 Ok(num <= *value)
131 } else {
132 Err(OrchestrationError::ConditionFailed(format!(
133 "Variable {} is not a number",
134 variable
135 )))
136 }
137 }
138 Condition::Exists { variable } => Ok(context.variables.contains_key(variable)),
139 Condition::And { conditions } => {
140 for cond in conditions {
141 if !cond.evaluate(context)? {
142 return Ok(false);
143 }
144 }
145 Ok(true)
146 }
147 Condition::Or { conditions } => {
148 for cond in conditions {
149 if cond.evaluate(context)? {
150 return Ok(true);
151 }
152 }
153 Ok(false)
154 }
155 Condition::Not { condition } => Ok(!condition.evaluate(context)?),
156 Condition::PreviousStepSucceeded => Ok(context.last_step_success),
157 Condition::PreviousStepFailed => Ok(!context.last_step_success),
158 Condition::MetricThreshold {
159 metric_name,
160 operator,
161 threshold,
162 } => {
163 if let Some(value) = context.metrics.get(metric_name) {
164 Ok(match operator {
165 ComparisonOperator::Equals => (value - threshold).abs() < f64::EPSILON,
166 ComparisonOperator::NotEquals => (value - threshold).abs() >= f64::EPSILON,
167 ComparisonOperator::GreaterThan => value > threshold,
168 ComparisonOperator::LessThan => value < threshold,
169 ComparisonOperator::GreaterThanOrEqual => value >= threshold,
170 ComparisonOperator::LessThanOrEqual => value <= threshold,
171 })
172 } else {
173 Err(OrchestrationError::ConditionFailed(format!(
174 "Metric {} not found",
175 metric_name
176 )))
177 }
178 }
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ConditionalStep {
186 pub name: String,
188 pub condition: Condition,
190 pub then_steps: Vec<AdvancedScenarioStep>,
192 pub else_steps: Vec<AdvancedScenarioStep>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198#[serde(rename_all = "snake_case")]
199pub enum HookType {
200 PreStep,
202 PostStep,
204 PreOrchestration,
206 PostOrchestration,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(tag = "type", rename_all = "snake_case")]
213pub enum HookAction {
214 SetVariable { name: String, value: JsonValue },
216 Log { message: String, level: LogLevel },
218 HttpRequest {
220 url: String,
221 method: String,
222 body: Option<String>,
223 },
224 Command { command: String, args: Vec<String> },
226 RecordMetric { name: String, value: f64 },
228}
229
230#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
232#[serde(rename_all = "lowercase")]
233pub enum LogLevel {
234 Trace,
235 Debug,
236 Info,
237 Warn,
238 Error,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct Hook {
244 pub name: String,
246 pub hook_type: HookType,
248 pub actions: Vec<HookAction>,
250 pub condition: Option<Condition>,
252}
253
254impl Hook {
255 pub async fn execute(&self, context: &mut ExecutionContext) -> Result<(), OrchestrationError> {
257 if let Some(condition) = &self.condition {
259 if !condition.evaluate(context)? {
260 return Ok(());
261 }
262 }
263
264 for action in &self.actions {
265 self.execute_action(action, context).await?;
266 }
267
268 Ok(())
269 }
270
271 async fn execute_action(
273 &self,
274 action: &HookAction,
275 context: &mut ExecutionContext,
276 ) -> Result<(), OrchestrationError> {
277 match action {
278 HookAction::SetVariable { name, value } => {
279 context.set_variable(name.clone(), value.clone());
280 Ok(())
281 }
282 HookAction::Log { message, level } => {
283 use tracing::{debug, error, info, trace, warn};
284 match level {
285 LogLevel::Trace => trace!("[Hook: {}] {}", self.name, message),
286 LogLevel::Debug => debug!("[Hook: {}] {}", self.name, message),
287 LogLevel::Info => info!("[Hook: {}] {}", self.name, message),
288 LogLevel::Warn => warn!("[Hook: {}] {}", self.name, message),
289 LogLevel::Error => error!("[Hook: {}] {}", self.name, message),
290 }
291 Ok(())
292 }
293 HookAction::HttpRequest { url, method, body } => {
294 tracing::info!("[Hook: {}] HTTP {} {} (body: {:?})", self.name, method, url, body);
297 Ok(())
298 }
299 HookAction::Command { command, args } => {
300 tracing::info!("[Hook: {}] Execute: {} {:?}", self.name, command, args);
303 Ok(())
304 }
305 HookAction::RecordMetric { name, value } => {
306 context.record_metric(name.clone(), *value);
307 Ok(())
308 }
309 }
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315#[serde(tag = "type", rename_all = "snake_case")]
316pub enum Assertion {
317 VariableEquals {
319 variable: String,
320 expected: JsonValue,
321 },
322 MetricInRange { metric: String, min: f64, max: f64 },
324 StepSucceeded { step_name: String },
326 StepFailed { step_name: String },
328 Condition { condition: Condition },
330}
331
332impl Assertion {
333 pub fn validate(&self, context: &ExecutionContext) -> Result<bool, OrchestrationError> {
335 match self {
336 Assertion::VariableEquals { variable, expected } => {
337 let value = context.get_variable(variable)?;
338 Ok(value == expected)
339 }
340 Assertion::MetricInRange { metric, min, max } => {
341 if let Some(value) = context.metrics.get(metric) {
342 Ok(*value >= *min && *value <= *max)
343 } else {
344 Ok(false)
345 }
346 }
347 Assertion::StepSucceeded { step_name } => {
348 if let Some(result) = context.step_results.get(step_name) {
349 Ok(result.success)
350 } else {
351 Ok(false)
352 }
353 }
354 Assertion::StepFailed { step_name } => {
355 if let Some(result) = context.step_results.get(step_name) {
356 Ok(!result.success)
357 } else {
358 Ok(false)
359 }
360 }
361 Assertion::Condition { condition } => condition.evaluate(context),
362 }
363 }
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct AdvancedScenarioStep {
369 #[serde(flatten)]
371 pub base: ScenarioStep,
372
373 pub condition: Option<Condition>,
375
376 pub pre_hooks: Vec<Hook>,
378
379 pub post_hooks: Vec<Hook>,
381
382 pub assertions: Vec<Assertion>,
384
385 pub variables: HashMap<String, JsonValue>,
387
388 pub timeout_seconds: Option<u64>,
390
391 pub retry: Option<RetryConfig>,
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize)]
397pub struct RetryConfig {
398 pub max_attempts: usize,
400 pub delay_seconds: u64,
402 pub exponential_backoff: bool,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct StepResult {
409 pub step_name: String,
411 pub success: bool,
413 pub start_time: DateTime<Utc>,
415 pub end_time: DateTime<Utc>,
417 pub duration_seconds: f64,
419 pub error: Option<String>,
421 pub assertion_results: Vec<AssertionResult>,
423 pub metrics: HashMap<String, f64>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct AssertionResult {
430 pub description: String,
432 pub passed: bool,
434 pub error: Option<String>,
436}
437
438#[derive(Debug, Clone)]
440pub struct ExecutionContext {
441 pub variables: HashMap<String, JsonValue>,
443 pub metrics: HashMap<String, f64>,
445 pub step_results: HashMap<String, StepResult>,
447 pub last_step_success: bool,
449 pub iteration: usize,
451}
452
453impl ExecutionContext {
454 pub fn new() -> Self {
456 Self {
457 variables: HashMap::new(),
458 metrics: HashMap::new(),
459 step_results: HashMap::new(),
460 last_step_success: true,
461 iteration: 0,
462 }
463 }
464
465 pub fn set_variable(&mut self, name: String, value: JsonValue) {
467 self.variables.insert(name, value);
468 }
469
470 pub fn get_variable(&self, name: &str) -> Result<&JsonValue, OrchestrationError> {
472 self.variables
473 .get(name)
474 .ok_or_else(|| OrchestrationError::VariableNotFound(name.to_string()))
475 }
476
477 pub fn record_metric(&mut self, name: String, value: f64) {
479 self.metrics.insert(name, value);
480 }
481
482 pub fn record_step_result(&mut self, result: StepResult) {
484 self.last_step_success = result.success;
485 self.step_results.insert(result.step_name.clone(), result);
486 }
487}
488
489impl Default for ExecutionContext {
490 fn default() -> Self {
491 Self::new()
492 }
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct AdvancedOrchestratedScenario {
498 #[serde(flatten)]
500 pub base: OrchestratedScenario,
501
502 pub advanced_steps: Vec<AdvancedScenarioStep>,
504
505 pub conditional_steps: Vec<ConditionalStep>,
507
508 pub hooks: Vec<Hook>,
510
511 pub assertions: Vec<Assertion>,
513
514 pub variables: HashMap<String, JsonValue>,
516
517 pub enable_reporting: bool,
519
520 pub report_path: Option<String>,
522}
523
524impl AdvancedOrchestratedScenario {
525 pub fn from_base(base: OrchestratedScenario) -> Self {
527 Self {
528 base,
529 advanced_steps: Vec::new(),
530 conditional_steps: Vec::new(),
531 hooks: Vec::new(),
532 assertions: Vec::new(),
533 variables: HashMap::new(),
534 enable_reporting: false,
535 report_path: None,
536 }
537 }
538
539 pub fn with_variable(mut self, name: String, value: JsonValue) -> Self {
541 self.variables.insert(name, value);
542 self
543 }
544
545 pub fn with_hook(mut self, hook: Hook) -> Self {
547 self.hooks.push(hook);
548 self
549 }
550
551 pub fn with_assertion(mut self, assertion: Assertion) -> Self {
553 self.assertions.push(assertion);
554 self
555 }
556
557 pub fn with_reporting(mut self, path: Option<String>) -> Self {
559 self.enable_reporting = true;
560 self.report_path = path;
561 self
562 }
563
564 pub fn to_json(&self) -> Result<String, OrchestrationError> {
566 serde_json::to_string_pretty(self)
567 .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
568 }
569
570 pub fn to_yaml(&self) -> Result<String, OrchestrationError> {
572 serde_yaml::to_string(self)
573 .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
574 }
575
576 pub fn from_json(json: &str) -> Result<Self, OrchestrationError> {
578 serde_json::from_str(json)
579 .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
580 }
581
582 pub fn from_yaml(yaml: &str) -> Result<Self, OrchestrationError> {
584 serde_yaml::from_str(yaml)
585 .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
586 }
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct ExecutionReport {
592 pub orchestration_name: String,
594 pub start_time: DateTime<Utc>,
596 pub end_time: DateTime<Utc>,
598 pub total_duration_seconds: f64,
600 pub success: bool,
602 pub step_results: Vec<StepResult>,
604 pub assertion_results: Vec<AssertionResult>,
606 pub final_variables: HashMap<String, JsonValue>,
608 pub final_metrics: HashMap<String, f64>,
610 pub errors: Vec<String>,
612}
613
614impl ExecutionReport {
615 pub fn new(orchestration_name: String, start_time: DateTime<Utc>) -> Self {
617 Self {
618 orchestration_name,
619 start_time,
620 end_time: Utc::now(),
621 total_duration_seconds: 0.0,
622 success: true,
623 step_results: Vec::new(),
624 assertion_results: Vec::new(),
625 final_variables: HashMap::new(),
626 final_metrics: HashMap::new(),
627 errors: Vec::new(),
628 }
629 }
630
631 pub fn finalize(mut self, context: &ExecutionContext) -> Self {
633 self.end_time = Utc::now();
634 self.total_duration_seconds =
635 (self.end_time - self.start_time).num_milliseconds() as f64 / 1000.0;
636 self.final_variables = context.variables.clone();
637 self.final_metrics = context.metrics.clone();
638 self.step_results = context.step_results.values().cloned().collect();
639 self.success = self.step_results.iter().all(|r| r.success) && self.errors.is_empty();
640 self
641 }
642
643 pub fn to_json(&self) -> Result<String, OrchestrationError> {
645 serde_json::to_string_pretty(self)
646 .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
647 }
648
649 pub fn to_html(&self) -> String {
651 format!(
652 r#"<!DOCTYPE html>
653<html>
654<head>
655 <title>Chaos Orchestration Report: {}</title>
656 <style>
657 body {{ font-family: Arial, sans-serif; margin: 20px; }}
658 .header {{ background: #f5f5f5; padding: 20px; border-radius: 5px; }}
659 .success {{ color: green; }}
660 .failure {{ color: red; }}
661 table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
662 th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
663 th {{ background: #f5f5f5; }}
664 </style>
665</head>
666<body>
667 <div class="header">
668 <h1>Chaos Orchestration Report</h1>
669 <h2>{}</h2>
670 <p><strong>Status:</strong> <span class="{}">{}</span></p>
671 <p><strong>Duration:</strong> {:.2} seconds</p>
672 <p><strong>Start Time:</strong> {}</p>
673 <p><strong>End Time:</strong> {}</p>
674 </div>
675
676 <h2>Step Results</h2>
677 <table>
678 <tr>
679 <th>Step</th>
680 <th>Status</th>
681 <th>Duration (s)</th>
682 <th>Assertions</th>
683 </tr>
684 {}
685 </table>
686
687 <h2>Metrics</h2>
688 <table>
689 <tr>
690 <th>Metric</th>
691 <th>Value</th>
692 </tr>
693 {}
694 </table>
695</body>
696</html>"#,
697 self.orchestration_name,
698 self.orchestration_name,
699 if self.success { "success" } else { "failure" },
700 if self.success { "SUCCESS" } else { "FAILURE" },
701 self.total_duration_seconds,
702 self.start_time,
703 self.end_time,
704 self.step_results
705 .iter()
706 .map(|r| format!(
707 "<tr><td>{}</td><td class=\"{}\">{}</td><td>{:.2}</td><td>{}/{}</td></tr>",
708 r.step_name,
709 if r.success { "success" } else { "failure" },
710 if r.success { "SUCCESS" } else { "FAILURE" },
711 r.duration_seconds,
712 r.assertion_results.iter().filter(|a| a.passed).count(),
713 r.assertion_results.len()
714 ))
715 .collect::<Vec<_>>()
716 .join("\n "),
717 self.final_metrics
718 .iter()
719 .map(|(k, v)| format!("<tr><td>{}</td><td>{:.2}</td></tr>", k, v))
720 .collect::<Vec<_>>()
721 .join("\n ")
722 )
723 }
724}
725
726#[derive(Debug, Clone)]
728pub struct OrchestrationLibrary {
729 orchestrations: Arc<RwLock<HashMap<String, AdvancedOrchestratedScenario>>>,
731}
732
733impl OrchestrationLibrary {
734 pub fn new() -> Self {
736 Self {
737 orchestrations: Arc::new(RwLock::new(HashMap::new())),
738 }
739 }
740
741 pub fn store(&self, name: String, orchestration: AdvancedOrchestratedScenario) {
743 let mut orch = self.orchestrations.write();
744 orch.insert(name, orchestration);
745 }
746
747 pub fn retrieve(&self, name: &str) -> Option<AdvancedOrchestratedScenario> {
749 let orch = self.orchestrations.read();
750 orch.get(name).cloned()
751 }
752
753 pub fn list(&self) -> Vec<String> {
755 let orch = self.orchestrations.read();
756 orch.keys().cloned().collect()
757 }
758
759 pub fn delete(&self, name: &str) -> bool {
761 let mut orch = self.orchestrations.write();
762 orch.remove(name).is_some()
763 }
764
765 pub fn import_from_directory(&self, _path: &str) -> Result<usize, OrchestrationError> {
767 Ok(0)
770 }
771
772 pub fn export_to_directory(&self, _path: &str) -> Result<usize, OrchestrationError> {
774 let orch = self.orchestrations.read();
777 Ok(orch.len())
778 }
779}
780
781impl Default for OrchestrationLibrary {
782 fn default() -> Self {
783 Self::new()
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790
791 #[test]
792 fn test_condition_equals() {
793 let mut context = ExecutionContext::new();
794 context.set_variable("test".to_string(), JsonValue::String("value".to_string()));
795
796 let condition = Condition::Equals {
797 variable: "test".to_string(),
798 value: JsonValue::String("value".to_string()),
799 };
800
801 assert!(condition.evaluate(&context).unwrap());
802 }
803
804 #[test]
805 fn test_condition_and() {
806 let mut context = ExecutionContext::new();
807 context.set_variable("a".to_string(), JsonValue::Number(5.into()));
808 context.set_variable("b".to_string(), JsonValue::Number(10.into()));
809
810 let condition = Condition::And {
811 conditions: vec![
812 Condition::GreaterThan {
813 variable: "a".to_string(),
814 value: 3.0,
815 },
816 Condition::LessThan {
817 variable: "b".to_string(),
818 value: 15.0,
819 },
820 ],
821 };
822
823 assert!(condition.evaluate(&context).unwrap());
824 }
825
826 #[test]
827 fn test_execution_context() {
828 let mut context = ExecutionContext::new();
829 context.set_variable("test".to_string(), JsonValue::String("value".to_string()));
830 context.record_metric("latency".to_string(), 100.0);
831
832 assert_eq!(context.get_variable("test").unwrap(), &JsonValue::String("value".to_string()));
833 assert_eq!(*context.metrics.get("latency").unwrap(), 100.0);
834 }
835
836 #[test]
837 fn test_orchestration_library() {
838 let library = OrchestrationLibrary::new();
839
840 let orch = AdvancedOrchestratedScenario::from_base(OrchestratedScenario::new("test"));
841
842 library.store("test".to_string(), orch.clone());
843
844 let retrieved = library.retrieve("test");
845 assert!(retrieved.is_some());
846
847 let list = library.list();
848 assert_eq!(list.len(), 1);
849
850 let deleted = library.delete("test");
851 assert!(deleted);
852
853 let list = library.list();
854 assert_eq!(list.len(), 0);
855 }
856
857 #[test]
858 fn test_execution_report() {
859 let report = ExecutionReport::new("test".to_string(), Utc::now());
860 let context = ExecutionContext::new();
861
862 let final_report = report.finalize(&context);
863 assert_eq!(final_report.orchestration_name, "test");
864 assert!(final_report.total_duration_seconds >= 0.0);
865 }
866}