Copied from api_gateway, removed REST handlers (port 8080), added tonic-web + CORS for grpc-web browser access. Binary renamed: api-gateway → api Changes: - Package name: api-gateway → api - Deleted src/handlers/ (REST ML endpoints on port 8080) - Added tonic-web 0.13 + tower-http CORS layer - Server::builder().accept_http1(true) for grpc-web - CORS_ORIGINS env var (default http://localhost:5173) - Metrics server on port 9091 (axum) preserved - All 95 lib tests pass, 0 clippy warnings - Added services/api to workspace members Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
134 lines
3.5 KiB
Rust
134 lines
3.5 KiB
Rust
//! Health check endpoints for API Gateway service
|
|
//!
|
|
//! Provides HTTP-based health and readiness endpoints for Kubernetes probes.
|
|
//! These endpoints don't require authentication to allow load balancers and
|
|
//! orchestrators to check service health.
|
|
|
|
use axum::{extract::State, http::StatusCode, routing::get, Json, Router};
|
|
use serde::Serialize;
|
|
use std::sync::Arc;
|
|
use sqlx::PgPool;
|
|
use redis::aio::ConnectionManager;
|
|
|
|
/// Health status response
|
|
#[derive(Serialize)]
|
|
pub struct HealthResponse {
|
|
/// Service status
|
|
pub status: &'static str,
|
|
/// Service name
|
|
pub service: &'static str,
|
|
/// Service version
|
|
pub version: &'static str,
|
|
}
|
|
|
|
/// Detailed health status with dependency checks
|
|
#[derive(Serialize)]
|
|
pub struct DetailedHealthResponse {
|
|
/// Service status
|
|
pub status: &'static str,
|
|
/// Service name
|
|
pub service: &'static str,
|
|
/// Service version
|
|
pub version: &'static str,
|
|
/// Database connectivity
|
|
pub database: bool,
|
|
/// Redis connectivity
|
|
pub redis: bool,
|
|
}
|
|
|
|
/// Shared health check state
|
|
#[derive(Clone)]
|
|
pub struct HealthState {
|
|
/// PostgreSQL connection pool
|
|
pub db_pool: Option<PgPool>,
|
|
/// Redis connection manager
|
|
pub redis: Option<ConnectionManager>,
|
|
}
|
|
|
|
/// Creates the health check router with /health and /ready endpoints
|
|
pub fn health_router(state: HealthState) -> Router {
|
|
Router::new()
|
|
.route("/health", get(health_check))
|
|
.route("/ready", get(readiness_check))
|
|
.with_state(Arc::new(state))
|
|
}
|
|
|
|
/// Liveness probe - indicates service is running
|
|
/// Returns 200 OK if the service process is alive
|
|
async fn health_check() -> Json<HealthResponse> {
|
|
Json(HealthResponse {
|
|
status: "healthy",
|
|
service: "api",
|
|
version: env!("CARGO_PKG_VERSION"),
|
|
})
|
|
}
|
|
|
|
/// Readiness probe - indicates service is ready to accept requests
|
|
/// Checks database and Redis connectivity
|
|
async fn readiness_check(
|
|
State(state): State<Arc<HealthState>>,
|
|
) -> Result<Json<DetailedHealthResponse>, StatusCode> {
|
|
let mut db_ok = false;
|
|
let mut redis_ok = false;
|
|
|
|
// Check database connectivity
|
|
if let Some(ref pool) = state.db_pool {
|
|
db_ok = sqlx::query("SELECT 1")
|
|
.fetch_one(pool)
|
|
.await
|
|
.is_ok();
|
|
}
|
|
|
|
// Check Redis connectivity
|
|
if let Some(ref mut redis_conn) = state.redis.clone() {
|
|
use redis::AsyncCommands;
|
|
redis_ok = redis_conn.ping::<()>().await.is_ok();
|
|
}
|
|
|
|
// Service is ready only if all dependencies are healthy
|
|
let is_ready = db_ok && redis_ok;
|
|
let status = if is_ready { "ready" } else { "not_ready" };
|
|
let http_status = if is_ready {
|
|
StatusCode::OK
|
|
} else {
|
|
StatusCode::SERVICE_UNAVAILABLE
|
|
};
|
|
|
|
let response = DetailedHealthResponse {
|
|
status,
|
|
service: "api",
|
|
version: env!("CARGO_PKG_VERSION"),
|
|
database: db_ok,
|
|
redis: redis_ok,
|
|
};
|
|
|
|
if is_ready {
|
|
Ok(Json(response))
|
|
} else {
|
|
Err(http_status)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_health_check() {
|
|
let response = health_check().await;
|
|
assert_eq!(response.status, "healthy");
|
|
assert_eq!(response.service, "api");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_readiness_check_without_deps() {
|
|
let state = Arc::new(HealthState {
|
|
db_pool: None,
|
|
redis: None,
|
|
});
|
|
|
|
let result = readiness_check(State(state)).await;
|
|
assert!(result.is_err());
|
|
}
|
|
}
|