Skip to content

Commit 26ffb18

Browse files
apollo_http_server: gate is_ready endpoint on accept_new_txs flag (#14206)
* apollo_http_server: gate is_ready endpoint on accept_new_txs flag When accept_new_txs is false, /gateway/is_ready now returns 503 so the load balancer drains traffic from this instance instead of routing requests that the tx handlers would reject anyway. /gateway/is_alive is intentionally left as a static 200 since liveness should not flip the pod into restart. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * apollo_http_server: merge is_ready test cases per review Consolidate is_ready_reflects_accept_new_txs and is_ready_observes_dynamic_config_updates into a single test that walks the true -> false -> true lifecycle, since the dynamic-update test already covers both flag values. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5c5827e commit 26ffb18

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

crates/apollo_http_server/src/http_server.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use apollo_proc_macros::sequencer_latency_histogram;
2424
use async_trait::async_trait;
2525
use axum::extract::DefaultBodyLimit;
2626
use axum::handler::Handler;
27-
use axum::http::HeaderMap;
27+
use axum::http::{HeaderMap, StatusCode};
2828
use axum::routing::{get, post, MethodRouter};
2929
use axum::{serve, Extension, Json, Router};
3030
use blockifier_reexecution::serde_utils::deserialize_transaction_json_to_starknet_api_tx;
@@ -139,10 +139,7 @@ impl HttpServer {
139139
"/gateway/is_alive",
140140
get(|| futures::future::ready("Gateway is alive".to_owned()))
141141
)
142-
.route(
143-
"/gateway/is_ready",
144-
get(|| futures::future::ready("Gateway is ready".to_owned()))
145-
)
142+
.route("/gateway/is_ready", get(is_ready))
146143
.layer(Extension(self.app_state.clone()))
147144
// Hard streaming limit on decompressed bytes — wraps the body in
148145
// http_body_util::Limited which errors during poll_frame() once the
@@ -217,6 +214,15 @@ async fn add_tx(
217214
add_tx_inner(app_state, headers, rpc_tx).await
218215
}
219216

217+
async fn is_ready(Extension(app_state): Extension<AppState>) -> (StatusCode, &'static str) {
218+
let HttpServerDynamicConfig { accept_new_txs, .. } = app_state.get_dynamic_config();
219+
if accept_new_txs {
220+
(StatusCode::OK, "Gateway is ready")
221+
} else {
222+
(StatusCode::SERVICE_UNAVAILABLE, "Gateway is not accepting new transactions")
223+
}
224+
}
225+
220226
fn check_new_transactions_are_allowed(accept_new_txs: bool) -> HttpServerResult<()> {
221227
match accept_new_txs {
222228
true => Ok(()),

crates/apollo_http_server/src/http_server_test.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::io::Write;
2+
use std::sync::Arc;
23

34
use apollo_gateway_types::communication::{GatewayClientError, MockGatewayClient};
45
use apollo_gateway_types::deprecated_gateway_error::{
@@ -13,11 +14,12 @@ use apollo_gateway_types::gateway_types::{
1314
GatewayOutput,
1415
InvokeGatewayOutput,
1516
};
17+
use apollo_http_server_config::config::HttpServerDynamicConfig;
1618
use apollo_infra::component_client::ClientError;
1719
use apollo_proc_macros::unique_u16;
1820
use axum::body::Bytes;
1921
use axum::response::{IntoResponse, Response};
20-
use axum::Json;
22+
use axum::{Extension, Json};
2123
use http::StatusCode;
2224
use http_body_util::BodyExt;
2325
use rstest::rstest;
@@ -26,10 +28,11 @@ use starknet_api::test_utils::read_json_file;
2628
use starknet_api::transaction::TransactionHash;
2729
use starknet_api::{class_hash, contract_address, tx_hash};
2830
use starknet_types_core::felt::Felt;
31+
use tokio::sync::watch;
2932
use tracing_test::traced_test;
3033

3134
use crate::errors::HttpServerError;
32-
use crate::http_server::CLIENT_REGION_HEADER;
35+
use crate::http_server::{is_ready, AppState, CLIENT_REGION_HEADER};
3336
use crate::test_utils::{
3437
deprecated_gateway_declare_tx,
3538
deprecated_gateway_deploy_account_tx,
@@ -111,6 +114,27 @@ async fn allow_new_txs() {
111114
assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE, "{status:?}");
112115
}
113116

117+
#[tokio::test]
118+
async fn is_ready_reflects_accept_new_txs() {
119+
let (tx, dynamic_config_rx) =
120+
watch::channel(HttpServerDynamicConfig { accept_new_txs: true, ..Default::default() });
121+
let app_state =
122+
AppState { gateway_client: Arc::new(MockGatewayClient::new()), dynamic_config_rx };
123+
124+
// Clone AppState to mirror how axum's Extension extractor hands a clone to each request:
125+
// updates to the watch channel must still be observed through the cloned receiver.
126+
let (status, _) = is_ready(Extension(app_state.clone())).await;
127+
assert_eq!(status, StatusCode::OK);
128+
129+
tx.send(HttpServerDynamicConfig { accept_new_txs: false, ..Default::default() }).unwrap();
130+
let (status, _) = is_ready(Extension(app_state.clone())).await;
131+
assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
132+
133+
tx.send(HttpServerDynamicConfig { accept_new_txs: true, ..Default::default() }).unwrap();
134+
let (status, _) = is_ready(Extension(app_state)).await;
135+
assert_eq!(status, StatusCode::OK);
136+
}
137+
114138
#[tokio::test]
115139
async fn error_into_response() {
116140
let error = HttpServerError::DeserializationError(

0 commit comments

Comments
 (0)