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}