1use crate::resilience::{
4 BulkheadManager, BulkheadStats, CircuitBreakerManager, CircuitState, CircuitStats,
5};
6use axum::{
7 extract::{Path, State},
8 http::StatusCode,
9 response::IntoResponse,
10 routing::{get, post},
11 Json, Router,
12};
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CircuitBreakerStateResponse {
19 pub endpoint: String,
20 pub state: String,
21 pub stats: CircuitStatsResponse,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct CircuitStatsResponse {
27 pub total_requests: u64,
28 pub successful_requests: u64,
29 pub failed_requests: u64,
30 pub rejected_requests: u64,
31 pub consecutive_failures: u64,
32 pub consecutive_successes: u64,
33 pub success_rate: f64,
34 pub failure_rate: f64,
35}
36
37impl From<CircuitStats> for CircuitStatsResponse {
38 fn from(stats: CircuitStats) -> Self {
39 let success_rate = if stats.total_requests > 0 {
40 (stats.successful_requests as f64 / stats.total_requests as f64) * 100.0
41 } else {
42 0.0
43 };
44
45 let failure_rate = if stats.total_requests > 0 {
46 (stats.failed_requests as f64 / stats.total_requests as f64) * 100.0
47 } else {
48 0.0
49 };
50
51 Self {
52 total_requests: stats.total_requests,
53 successful_requests: stats.successful_requests,
54 failed_requests: stats.failed_requests,
55 rejected_requests: stats.rejected_requests,
56 consecutive_failures: stats.consecutive_failures,
57 consecutive_successes: stats.consecutive_successes,
58 success_rate,
59 failure_rate,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct BulkheadStateResponse {
67 pub service: String,
68 pub stats: BulkheadStatsResponse,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct BulkheadStatsResponse {
74 pub active_requests: u32,
75 pub queued_requests: u32,
76 pub total_requests: u64,
77 pub rejected_requests: u64,
78 pub timeout_requests: u64,
79 pub utilization_percent: f64,
80}
81
82impl BulkheadStatsResponse {
83 fn from_stats(stats: BulkheadStats, max_concurrent: u32) -> Self {
84 let utilization_percent = if max_concurrent > 0 {
85 (stats.active_requests as f64 / max_concurrent as f64) * 100.0
86 } else {
87 0.0
88 };
89
90 Self {
91 active_requests: stats.active_requests,
92 queued_requests: stats.queued_requests,
93 total_requests: stats.total_requests,
94 rejected_requests: stats.rejected_requests,
95 timeout_requests: stats.timeout_requests,
96 utilization_percent,
97 }
98 }
99}
100
101#[derive(Clone)]
103pub struct ResilienceApiState {
104 pub circuit_breaker_manager: Arc<CircuitBreakerManager>,
105 pub bulkhead_manager: Arc<BulkheadManager>,
106}
107
108async fn get_all_circuit_breakers(State(state): State<ResilienceApiState>) -> impl IntoResponse {
110 let states = state.circuit_breaker_manager.get_all_states().await;
111 let mut responses = Vec::new();
112
113 for (endpoint, cb_state) in states {
114 let breaker = state.circuit_breaker_manager.get_breaker(&endpoint).await;
115 let stats = breaker.stats().await;
116
117 responses.push(CircuitBreakerStateResponse {
118 endpoint: endpoint.clone(),
119 state: format!("{:?}", cb_state),
120 stats: stats.into(),
121 });
122 }
123
124 Json(responses)
125}
126
127async fn get_circuit_breaker(
129 State(state): State<ResilienceApiState>,
130 Path(endpoint): Path<String>,
131) -> impl IntoResponse {
132 let breaker = state.circuit_breaker_manager.get_breaker(&endpoint).await;
133 let cb_state = breaker.state().await;
134 let stats = breaker.stats().await;
135
136 Json(CircuitBreakerStateResponse {
137 endpoint: endpoint.clone(),
138 state: format!("{:?}", cb_state),
139 stats: stats.into(),
140 })
141}
142
143async fn reset_circuit_breaker(
145 State(state): State<ResilienceApiState>,
146 Path(endpoint): Path<String>,
147) -> impl IntoResponse {
148 let breaker = state.circuit_breaker_manager.get_breaker(&endpoint).await;
149 breaker.reset().await;
150
151 (StatusCode::OK, "Circuit breaker reset")
152}
153
154async fn get_all_bulkheads(State(state): State<ResilienceApiState>) -> impl IntoResponse {
156 let stats_map = state.bulkhead_manager.get_all_stats().await;
157 let mut responses = Vec::new();
158
159 for (service, stats) in stats_map {
160 let bulkhead = state.bulkhead_manager.get_bulkhead(&service).await;
161 let config = bulkhead.config().await;
162
163 responses.push(BulkheadStateResponse {
164 service: service.clone(),
165 stats: BulkheadStatsResponse::from_stats(stats, config.max_concurrent_requests),
166 });
167 }
168
169 Json(responses)
170}
171
172async fn get_bulkhead(
174 State(state): State<ResilienceApiState>,
175 Path(service): Path<String>,
176) -> impl IntoResponse {
177 let bulkhead = state.bulkhead_manager.get_bulkhead(&service).await;
178 let stats = bulkhead.stats().await;
179 let config = bulkhead.config().await;
180
181 Json(BulkheadStateResponse {
182 service: service.clone(),
183 stats: BulkheadStatsResponse::from_stats(stats, config.max_concurrent_requests),
184 })
185}
186
187async fn reset_bulkhead(
189 State(state): State<ResilienceApiState>,
190 Path(service): Path<String>,
191) -> impl IntoResponse {
192 let bulkhead = state.bulkhead_manager.get_bulkhead(&service).await;
193 bulkhead.reset().await;
194
195 (StatusCode::OK, "Bulkhead statistics reset")
196}
197
198async fn get_dashboard_summary(State(state): State<ResilienceApiState>) -> impl IntoResponse {
200 let circuit_states = state.circuit_breaker_manager.get_all_states().await;
201 let bulkhead_stats = state.bulkhead_manager.get_all_stats().await;
202
203 let mut open_circuits = 0;
204 let mut half_open_circuits = 0;
205 let mut closed_circuits = 0;
206
207 for cb_state in circuit_states.values() {
208 match cb_state {
209 CircuitState::Open => open_circuits += 1,
210 CircuitState::HalfOpen => half_open_circuits += 1,
211 CircuitState::Closed => closed_circuits += 1,
212 }
213 }
214
215 let total_active_requests: u32 = bulkhead_stats.values().map(|s| s.active_requests).sum();
216 let total_queued_requests: u32 = bulkhead_stats.values().map(|s| s.queued_requests).sum();
217
218 let summary = serde_json::json!({
219 "circuit_breakers": {
220 "total": circuit_states.len(),
221 "open": open_circuits,
222 "half_open": half_open_circuits,
223 "closed": closed_circuits,
224 },
225 "bulkheads": {
226 "total": bulkhead_stats.len(),
227 "active_requests": total_active_requests,
228 "queued_requests": total_queued_requests,
229 },
230 });
231
232 Json(summary)
233}
234
235pub fn create_resilience_router(state: ResilienceApiState) -> Router {
237 Router::new()
238 .route("/circuit-breakers", get(get_all_circuit_breakers))
239 .route("/circuit-breakers/:endpoint", get(get_circuit_breaker))
240 .route("/circuit-breakers/:endpoint/reset", post(reset_circuit_breaker))
241 .route("/bulkheads", get(get_all_bulkheads))
242 .route("/bulkheads/:service", get(get_bulkhead))
243 .route("/bulkheads/:service/reset", post(reset_bulkhead))
244 .route("/dashboard/summary", get(get_dashboard_summary))
245 .with_state(state)
246}