cat_gateway/service/api/health/
started_get.rs

1//! # Implementation of the GET /health/started endpoint
2//!
3//! This module provides an HTTP endpoint to monitor the start of the Cat Gateway
4//! service, which depends on multiple parameters. It uses atomic booleans to track the
5//! status of the service and its dependencies.
6//!
7//! ## Key Features
8//!
9//! 1. **Atomic Booleans for Initialization**:
10//!    - `STARTED`: Indicates whether the service has fully initialized. It defaults to
11//!      `false` on startup and can only transition to `true` once all required conditions
12//!      are met. Once set to `true`, it cannot be changed back to `false`.
13//!    - `LIVE_INDEX_DB` and `LIVE_EVENT_DB`: Track the connection status of the Index DB
14//!      and Event DB, respectively. Both default to `false` on startup and are set to
15//!      `true` once a successful connection is established.
16//!    - A third atomic boolean (e.g., `INITIAL_FOLLOWER_TIP_REACHED`) is used to track
17//!      whether the chain indexer has reached the tip of the chain for the first time.
18//!      This also defaults to `false` and is set to `true` once the condition is met.
19//!
20//! 2. **Initialization Logic**:
21//!    - On startup, the service attempts to connect to both databases (Index DB and Event
22//!      DB). It will keep retrying until both connections are successful. Once both
23//!      databases are connected, their respective flags (`LIVE_INDEX_DB` and
24//!      `LIVE_EVENT_DB`) are set to `true`.
25//!    - The `STARTED` flag is set to `true` only when all three conditions are met
26//!      simultaneously:
27//!       1. `LIVE_INDEX_DB` is `true`.
28//!       2. `LIVE_EVENT_DB` is `true`.
29//!       3. `INITIAL_FOLLOWER_TIP_REACHED` is `true`.
30//!    - Once `STARTED` is set to `true`, it cannot be changed back to `false`.
31//!
32//! 3. **Response Logic**:
33//!    - If `STARTED` is `true`, the endpoint returns a `204 No Content` response,
34//!      indicating that the service is fully initialized and operational.
35//!    - If `STARTED` is `false`, the endpoint returns a `503 Service Unavailable`
36//!      response, indicating that the service is not yet ready to handle requests.
37//!
38//! ## How It Works
39//!
40//! - On startup, the service attempts to connect to the Index DB and Event DB. It retries
41//!   until both connections are successful. Once connected, their respective flags
42//!   (`LIVE_INDEX_DB` and `LIVE_EVENT_DB`) are set to `true`.
43//! - The chain indexer monitors the blockchain and sets `INITIAL_FOLLOWER_TIP_REACHED` to
44//!   `true` once it reaches the tip of the chain for the first time.
45//! - When all three flags (`LIVE_INDEX_DB`, `LIVE_EVENT_DB`, and
46//!   `INITIAL_FOLLOWER_TIP_REACHED`) are `true`, the `STARTED` flag is set to `true`.
47//! - Once `STARTED` is `true`, the endpoint will always respond with `204 No Content`.
48//! - If `STARTED` is `false`, the endpoint will respond with `503 Service Unavailable`.
49//!
50//! ## Example Scenarios
51//!
52//! 1. **Startup Phase**:
53//!    - The service is starting up.
54//!    - `LIVE_INDEX_DB`, `LIVE_EVENT_DB`, and `INITIAL_FOLLOWER_TIP_REACHED` are all
55//!      `false`.
56//!    - `STARTED` is `false`.
57//!    - The endpoint returns `503 Service Unavailable`.
58//!
59//! 2. **Databases Connected, Indexer Not Ready**:
60//!    - The Index DB and Event DB are connected (`LIVE_INDEX_DB` and `LIVE_EVENT_DB` are
61//!      `true`).
62//!    - The chain indexer has not yet reached the tip of the chain
63//!      (`INITIAL_FOLLOWER_TIP_REACHED` is `false`).
64//!    - `STARTED` is `false`.
65//!    - The endpoint returns `503 Service Unavailable`.
66//!
67//! 3. **All Conditions Met**:
68//!    - The Index DB and Event DB are connected (`LIVE_INDEX_DB` and `LIVE_EVENT_DB` are
69//!      `true`).
70//!    - The chain indexer has reached the tip of the chain
71//!      (`INITIAL_FOLLOWER_TIP_REACHED` is `true`).
72//!    - `STARTED` is set to `true`.
73//!    - The endpoint returns `204 No Content`.
74//!
75//! 4. **After Initialization**:
76//!    - All flags (`LIVE_INDEX_DB`, `LIVE_EVENT_DB`, `INITIAL_FOLLOWER_TIP_REACHED`, and
77//!      `STARTED`) are `true`.
78//!    - The endpoint continues to return `204 No Content`.
79//!
80//! ## Notes
81//!
82//! - All booleans are atomic, meaning they are thread-safe and can be accessed
83//!   concurrently without issues.
84//! - Once `STARTED` is set to `true`, it cannot be reverted, ensuring that the service
85//!   remains in a consistent state after initialization.
86//!
87//! This endpoint is useful for monitoring the initialization status of a service that
88//! depends on multiple external systems, ensuring that the service only becomes available
89//! once all dependencies are ready.
90
91use poem_openapi::ApiResponse;
92
93use crate::service::{
94    common::{responses::WithErrorResponses, types::headers::retry_after::RetryAfterOption},
95    utilities::health::service_has_started,
96};
97
98/// Endpoint responses.
99#[derive(ApiResponse)]
100pub(crate) enum Responses {
101    /// ## No Content
102    ///
103    /// Service is Started and can serve requests.
104    #[oai(status = 204)]
105    NoContent,
106}
107
108/// All responses.
109pub(crate) type AllResponses = WithErrorResponses<Responses>;
110
111/// # GET /health/started
112///
113/// Service Started endpoint.
114///
115/// Kubernetes (and others) use this endpoint to determine if the service has started
116/// properly and is able to serve requests.
117///
118/// In this service, started is guaranteed if this endpoint is reachable.
119/// So, it will always just return 204.
120///
121/// In theory it can also return 503 is the service has some startup processing
122/// to complete before it is ready to serve requests.
123///
124/// An example of not being started could be that bulk data needs to be read
125/// into memory or processed in some way before the API can return valid
126/// responses.  In that scenario this endpoint would return 503 until that
127/// startup processing was fully completed.
128pub(crate) fn endpoint() -> AllResponses {
129    if service_has_started() {
130        Responses::NoContent.into()
131    } else {
132        AllResponses::service_unavailable_with_msg(
133            "Service is not started, do not send other requests.".to_string(),
134            RetryAfterOption::Default,
135        )
136    }
137}