autoendpoint/routes/
health.rs

1//! Health and Dockerflow routes
2use std::collections::HashMap;
3use std::thread;
4
5use actix_web::{
6    web::{Data, Json},
7    HttpResponse,
8};
9use reqwest::StatusCode;
10use serde_json::json;
11
12use crate::error::{ApiErrorKind, ApiResult};
13use crate::server::AppState;
14use autopush_common::metric_name::MetricName;
15use autopush_common::metrics::StatsdClientExt;
16use autopush_common::util::b64_encode_url;
17use autopush_common::{db::error::DbResult, errors::ApcError};
18
19/// Handle the `/health` and `/__heartbeat__` routes
20pub async fn health_route(state: Data<AppState>) -> Json<serde_json::Value> {
21    let router_health = interpret_table_health(state.db.router_table_exists().await);
22    let message_health = interpret_table_health(state.db.message_table_exists().await);
23    let mut routers: HashMap<&str, bool> = HashMap::new();
24    routers.insert("apns", state.apns_router.active());
25    routers.insert("fcm", state.fcm_router.active());
26
27    let mut health = json!({
28        "status": if state
29            .db
30            .health_check()
31            .await
32            .map_err(|e| {
33                error!("Autoendpoint health error: {:?}", e);
34                e
35            })
36            .is_ok() {
37            "OK"
38        } else {
39            "ERROR"
40        },
41        "version": env!("CARGO_PKG_VERSION"),
42        "router_table": router_health,
43        "message_table": message_health,
44        "routers": routers,
45    });
46
47    #[cfg(feature = "reliable_report")]
48    {
49        let reliability_health: Result<String, ApcError> = state
50            .reliability
51            .health_check()
52            .await
53            .map(|_| {
54                let keys: Vec<String> = state
55                    .settings
56                    .tracking_keys()
57                    .unwrap_or_default()
58                    .iter()
59                    .map(|k|
60                        // Hint the key values
61                        b64_encode_url(k)[..8].to_string())
62                    .collect();
63                if keys.is_empty() {
64                    Ok("NO_TRACKING_KEYS".to_owned())
65                } else {
66                    Ok(format!("OK: {}", keys.join(",")))
67                }
68            })
69            .unwrap_or_else(|e| {
70                // Record that Redis is down.
71                state
72                    .metrics
73                    .incr_with_tags(MetricName::ReliabilityErrorRedisUnavailable)
74                    .with_tag("application", "autoendpoint")
75                    .send();
76                error!("🔍🟥 Reliability reporting down: {:?}", e);
77                Ok("STORE_ERROR".to_owned())
78            });
79        health["reliability"] = json!(reliability_health);
80    }
81    Json(health)
82}
83
84/// Convert the result of a DB health check to JSON
85fn interpret_table_health(health: DbResult<bool>) -> serde_json::Value {
86    match health {
87        Ok(true) => json!({
88            "status": "OK"
89        }),
90        Ok(false) => json!({
91            "status": "NOT OK",
92            "cause": "Nonexistent table"
93        }),
94        Err(e) => {
95            error!("Autoendpoint health error: {:?}", e);
96            json!({
97                "status": "NOT OK",
98                "cause": e.to_string()
99            })
100        }
101    }
102}
103
104/// Handle the `/status` route
105pub async fn status_route() -> ApiResult<Json<serde_json::Value>> {
106    Ok(Json(json!({
107        "status": "OK",
108        "version": env!("CARGO_PKG_VERSION"),
109    })))
110}
111
112/// Handle the `/__lbheartbeat__` route
113pub async fn lb_heartbeat_route() -> HttpResponse {
114    // Used by the load balancers, just return OK.
115    HttpResponse::Ok().finish()
116}
117
118/// Handle the `/__version__` route
119pub async fn version_route() -> HttpResponse {
120    // Return the contents of the version.json file created by circleci
121    // and stored in the docker root
122    HttpResponse::Ok()
123        .content_type("application/json")
124        .body(include_str!("../../../version.json"))
125}
126
127/// Handle the `/v1/err` route
128pub async fn log_check() -> ApiResult<String> {
129    error!(
130        "Test Critical Message";
131        "status_code" => StatusCode::IM_A_TEAPOT.as_u16(),
132        "errno" => 999,
133    );
134
135    thread::spawn(|| {
136        panic!("LogCheck");
137    });
138
139    Err(ApiErrorKind::LogCheck.into())
140}