mockforge_http/management/
chaos_admin.rs1use axum::{
16 extract::State,
17 http::StatusCode,
18 response::{IntoResponse, Json},
19};
20use serde::Deserialize;
21use tracing::*;
22
23use super::ManagementState;
24
25pub(crate) async fn get_chaos_config(State(_state): State<ManagementState>) -> impl IntoResponse {
27 #[cfg(feature = "chaos")]
28 {
29 if let Some(chaos_state) = &_state.chaos_api_state {
30 let config = chaos_state.config.read().await;
31 Json(serde_json::json!({
33 "enabled": config.enabled,
34 "latency": config.latency.as_ref().map(|l| serde_json::to_value(l).unwrap_or(serde_json::Value::Null)),
35 "fault_injection": config.fault_injection.as_ref().map(|f| serde_json::to_value(f).unwrap_or(serde_json::Value::Null)),
36 "rate_limit": config.rate_limit.as_ref().map(|r| serde_json::to_value(r).unwrap_or(serde_json::Value::Null)),
37 "traffic_shaping": config.traffic_shaping.as_ref().map(|t| serde_json::to_value(t).unwrap_or(serde_json::Value::Null)),
38 }))
39 .into_response()
40 } else {
41 Json(serde_json::json!({
43 "enabled": false,
44 "latency": null,
45 "fault_injection": null,
46 "rate_limit": null,
47 "traffic_shaping": null,
48 }))
49 .into_response()
50 }
51 }
52 #[cfg(not(feature = "chaos"))]
53 {
54 Json(serde_json::json!({
56 "enabled": false,
57 "latency": null,
58 "fault_injection": null,
59 "rate_limit": null,
60 "traffic_shaping": null,
61 }))
62 .into_response()
63 }
64}
65
66#[derive(Debug, Deserialize)]
68pub struct ChaosConfigUpdate {
69 pub enabled: Option<bool>,
71 pub latency: Option<serde_json::Value>,
73 pub fault_injection: Option<serde_json::Value>,
75 pub rate_limit: Option<serde_json::Value>,
77 pub traffic_shaping: Option<serde_json::Value>,
79}
80
81pub(crate) async fn update_chaos_config(
83 State(_state): State<ManagementState>,
84 Json(_config_update): Json<ChaosConfigUpdate>,
85) -> impl IntoResponse {
86 #[cfg(feature = "chaos")]
87 {
88 if let Some(chaos_state) = &_state.chaos_api_state {
89 use mockforge_chaos::config::{
90 FaultInjectionConfig, LatencyConfig, RateLimitConfig, TrafficShapingConfig,
91 };
92
93 let mut config = chaos_state.config.write().await;
94
95 if let Some(enabled) = _config_update.enabled {
97 config.enabled = enabled;
98 }
99
100 if let Some(latency_json) = _config_update.latency {
102 if let Ok(latency) = serde_json::from_value::<LatencyConfig>(latency_json) {
103 config.latency = Some(latency);
104 }
105 }
106
107 if let Some(fault_json) = _config_update.fault_injection {
109 if let Ok(fault) = serde_json::from_value::<FaultInjectionConfig>(fault_json) {
110 config.fault_injection = Some(fault);
111 }
112 }
113
114 if let Some(rate_json) = _config_update.rate_limit {
116 if let Ok(rate) = serde_json::from_value::<RateLimitConfig>(rate_json) {
117 config.rate_limit = Some(rate);
118 }
119 }
120
121 if let Some(traffic_json) = _config_update.traffic_shaping {
123 if let Ok(traffic) = serde_json::from_value::<TrafficShapingConfig>(traffic_json) {
124 config.traffic_shaping = Some(traffic);
125 }
126 }
127
128 drop(config);
131
132 info!("Chaos configuration updated successfully");
133 Json(serde_json::json!({
134 "success": true,
135 "message": "Chaos configuration updated and applied"
136 }))
137 .into_response()
138 } else {
139 (
140 StatusCode::SERVICE_UNAVAILABLE,
141 Json(serde_json::json!({
142 "success": false,
143 "error": "Chaos API not available",
144 "message": "Chaos engineering is not enabled or configured"
145 })),
146 )
147 .into_response()
148 }
149 }
150 #[cfg(not(feature = "chaos"))]
151 {
152 (
153 StatusCode::NOT_IMPLEMENTED,
154 Json(serde_json::json!({
155 "success": false,
156 "error": "Chaos feature not enabled",
157 "message": "Chaos engineering feature is not compiled into this build"
158 })),
159 )
160 .into_response()
161 }
162}
163
164pub(crate) async fn list_network_profiles() -> impl IntoResponse {
166 use mockforge_chaos::core_network_profiles::NetworkProfileCatalog;
167
168 let catalog = NetworkProfileCatalog::default();
169 let profiles: Vec<serde_json::Value> = catalog
170 .list_profiles_with_description()
171 .iter()
172 .map(|(name, description)| {
173 serde_json::json!({
174 "name": name,
175 "description": description,
176 })
177 })
178 .collect();
179
180 Json(serde_json::json!({
181 "profiles": profiles
182 }))
183 .into_response()
184}
185
186#[derive(Debug, Deserialize)]
187pub struct ApplyNetworkProfileRequest {
189 pub profile_name: String,
191}
192
193pub(crate) async fn apply_network_profile(
195 State(state): State<ManagementState>,
196 Json(request): Json<ApplyNetworkProfileRequest>,
197) -> impl IntoResponse {
198 use mockforge_chaos::core_network_profiles::NetworkProfileCatalog;
199
200 let catalog = NetworkProfileCatalog::default();
201 if let Some(profile) = catalog.get(&request.profile_name) {
202 if let Some(server_config) = &state.server_config {
205 let mut config = server_config.write().await;
206
207 use mockforge_core::config::NetworkShapingConfig;
209
210 let network_shaping = NetworkShapingConfig {
214 enabled: profile.traffic_shaping.bandwidth.enabled
215 || profile.traffic_shaping.burst_loss.enabled,
216 bandwidth_limit_bps: profile.traffic_shaping.bandwidth.max_bytes_per_sec * 8, packet_loss_percent: profile.traffic_shaping.burst_loss.loss_rate_during_burst,
218 max_connections: 1000, };
220
221 if let Some(ref mut chaos) = config.observability.chaos {
224 chaos.traffic_shaping = Some(network_shaping);
225 } else {
226 use mockforge_core::config::ChaosEngConfig;
228 config.observability.chaos = Some(ChaosEngConfig {
229 enabled: true,
230 latency: None,
231 fault_injection: None,
232 rate_limit: None,
233 traffic_shaping: Some(network_shaping),
234 scenario: None,
235 });
236 }
237
238 info!("Network profile '{}' applied to server configuration", request.profile_name);
239 } else {
240 warn!("Server configuration not available in ManagementState - profile applied but not persisted");
241 }
242
243 #[cfg(feature = "chaos")]
245 {
246 if let Some(chaos_state) = &state.chaos_api_state {
247 use mockforge_chaos::config::TrafficShapingConfig;
248
249 let mut chaos_config = chaos_state.config.write().await;
250 let chaos_traffic_shaping = TrafficShapingConfig {
252 enabled: profile.traffic_shaping.bandwidth.enabled
253 || profile.traffic_shaping.burst_loss.enabled,
254 bandwidth_limit_bps: profile.traffic_shaping.bandwidth.max_bytes_per_sec * 8, packet_loss_percent: profile.traffic_shaping.burst_loss.loss_rate_during_burst,
256 max_connections: 0,
257 connection_timeout_ms: 30000,
258 };
259 chaos_config.traffic_shaping = Some(chaos_traffic_shaping);
260 chaos_config.enabled = true; drop(chaos_config);
262 info!("Network profile '{}' applied to chaos API state", request.profile_name);
263 }
264 }
265
266 Json(serde_json::json!({
267 "success": true,
268 "message": format!("Network profile '{}' applied", request.profile_name),
269 "profile": {
270 "name": profile.name,
271 "description": profile.description,
272 }
273 }))
274 .into_response()
275 } else {
276 (
277 StatusCode::NOT_FOUND,
278 Json(serde_json::json!({
279 "error": "Profile not found",
280 "message": format!("Network profile '{}' not found", request.profile_name)
281 })),
282 )
283 .into_response()
284 }
285}