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