111 lines
3.5 KiB
Rust
111 lines
3.5 KiB
Rust
use actix_web::{web, HttpResponse};
|
|
use sqlx::PgPool;
|
|
use std::collections::HashMap;
|
|
|
|
use crate::errors::AppError;
|
|
use crate::models::health::{HealthResponse, ServiceHealth};
|
|
use crate::services::cache::CacheService;
|
|
use crate::services::martin::MartinService;
|
|
use crate::services::osrm::OsrmService;
|
|
use crate::services::photon::PhotonService;
|
|
|
|
/// GET /api/health
|
|
///
|
|
/// Checks connectivity to all upstream services and returns aggregated status.
|
|
/// Always returns HTTP 200; the client should inspect the `status` field.
|
|
pub async fn health_check(
|
|
martin: web::Data<MartinService>,
|
|
photon: web::Data<PhotonService>,
|
|
osrm: web::Data<OsrmService>,
|
|
pool: web::Data<PgPool>,
|
|
cache: web::Data<CacheService>,
|
|
start_time: web::Data<std::time::Instant>,
|
|
) -> Result<HttpResponse, AppError> {
|
|
let mut services = HashMap::new();
|
|
|
|
// Check Martin
|
|
let martin_health = check_service("martin", martin.health_check()).await;
|
|
services.insert("martin".to_string(), martin_health);
|
|
|
|
// Check Photon
|
|
let photon_health = check_service("photon", photon.health_check()).await;
|
|
services.insert("photon".to_string(), photon_health);
|
|
|
|
// Check OSRM instances
|
|
let osrm_driving_health = check_service("osrm_driving", osrm.health_check("driving")).await;
|
|
services.insert("osrm_driving".to_string(), osrm_driving_health);
|
|
|
|
let osrm_walking_health = check_service("osrm_walking", osrm.health_check("walking")).await;
|
|
services.insert("osrm_walking".to_string(), osrm_walking_health);
|
|
|
|
let osrm_cycling_health = check_service("osrm_cycling", osrm.health_check("cycling")).await;
|
|
services.insert("osrm_cycling".to_string(), osrm_cycling_health);
|
|
|
|
// Check PostgreSQL
|
|
let postgres_health = check_postgres(&pool).await;
|
|
services.insert("postgres".to_string(), postgres_health);
|
|
|
|
// Check Redis
|
|
let redis_health = check_service("redis", cache.ping()).await;
|
|
services.insert("redis".to_string(), redis_health);
|
|
|
|
let status = HealthResponse::compute_status(&services);
|
|
let uptime = start_time.elapsed().as_secs();
|
|
|
|
let response = HealthResponse {
|
|
status,
|
|
version: "1.0.0".to_string(),
|
|
uptime_seconds: uptime,
|
|
services,
|
|
};
|
|
|
|
Ok(HttpResponse::Ok()
|
|
.content_type("application/json; charset=utf-8")
|
|
.insert_header(("Cache-Control", "no-store"))
|
|
.json(response))
|
|
}
|
|
|
|
/// Check a service that returns Result<latency_ms, ()>.
|
|
async fn check_service(
|
|
_name: &str,
|
|
future: impl std::future::Future<Output = Result<u64, ()>>,
|
|
) -> ServiceHealth {
|
|
match future.await {
|
|
Ok(latency_ms) => ServiceHealth {
|
|
status: if latency_ms > 2000 {
|
|
"degraded".to_string()
|
|
} else {
|
|
"ok".to_string()
|
|
},
|
|
latency_ms,
|
|
},
|
|
Err(()) => ServiceHealth {
|
|
status: "down".to_string(),
|
|
latency_ms: 0,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Check PostgreSQL connectivity.
|
|
async fn check_postgres(pool: &PgPool) -> ServiceHealth {
|
|
let start = std::time::Instant::now();
|
|
let result = sqlx::query_scalar::<_, i32>("SELECT 1")
|
|
.fetch_one(pool)
|
|
.await;
|
|
let latency_ms = start.elapsed().as_millis() as u64;
|
|
|
|
match result {
|
|
Ok(_) => ServiceHealth {
|
|
status: if latency_ms > 2000 {
|
|
"degraded".to_string()
|
|
} else {
|
|
"ok".to_string()
|
|
},
|
|
latency_ms,
|
|
},
|
|
Err(_) => ServiceHealth {
|
|
status: "down".to_string(),
|
|
latency_ms: 0,
|
|
},
|
|
}
|
|
}
|