Skip to content

Commit 46ce820

Browse files
committed
feat: Interface status for info route
1 parent 8af3c85 commit 46ce820

5 files changed

Lines changed: 178 additions & 26 deletions

File tree

api/src/routes/info.rs

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::server_state::ServerState;
2-
use aruna_core::structs::Status;
32
use axum::extract::State;
43
use axum::http::StatusCode;
54
use axum::routing::get;
@@ -60,8 +59,31 @@ pub struct TimeoutConfig {
6059

6160
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
6261
pub struct InterfaceStatus {
63-
pub s3_status: String,
64-
pub rest_status: String,
62+
pub rest: RestInterfaceStatus,
63+
pub s3: S3InterfaceStatus,
64+
}
65+
66+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
67+
#[serde(tag = "status")]
68+
pub enum RestInterfaceStatus {
69+
Available {
70+
bind_address: String,
71+
base_url: String,
72+
api_base_url: String,
73+
info_url: String,
74+
swagger_ui_url: String,
75+
},
76+
Unavailable,
77+
}
78+
79+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
80+
#[serde(tag = "status")]
81+
pub enum S3InterfaceStatus {
82+
Available {
83+
bind_address: String,
84+
base_url: String,
85+
},
86+
Unavailable,
6587
}
6688

6789
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
@@ -138,12 +160,25 @@ pub async fn get_info(State(state): State<Arc<ServerState>>) -> (StatusCode, Jso
138160
}
139161
None => BlobStatus::Unavailable,
140162
};
141-
// TODO:
142-
// - Replace hardcoded values
143-
// - Add s3 interface address
163+
let interface_runtime = state.interface_state().await;
144164
let interface_status = InterfaceStatus {
145-
s3_status: Status::Available.to_string(),
146-
rest_status: Status::Available.to_string(),
165+
rest: match interface_runtime.rest {
166+
Some(rest) => RestInterfaceStatus::Available {
167+
bind_address: rest.bind_address.to_string(),
168+
base_url: rest.base_url,
169+
api_base_url: rest.api_base_url,
170+
info_url: rest.info_url,
171+
swagger_ui_url: rest.swagger_ui_url,
172+
},
173+
None => RestInterfaceStatus::Unavailable,
174+
},
175+
s3: match interface_runtime.s3 {
176+
Some(s3) => S3InterfaceStatus::Available {
177+
bind_address: s3.bind_address.to_string(),
178+
base_url: s3.base_url,
179+
},
180+
None => S3InterfaceStatus::Unavailable,
181+
},
147182
};
148183
let storage_metrics = state.get_ctx().storage_handle.snapshot_metrics();
149184
let database_status = DatabaseStatus {
@@ -169,11 +204,14 @@ pub async fn get_info(State(state): State<Arc<ServerState>>) -> (StatusCode, Jso
169204

170205
#[cfg(test)]
171206
mod tests {
172-
use super::{BlobStatus, InfoResponse, InterfaceStatus, NetStatus, DatabaseStatus, get_info};
207+
use super::{
208+
BlobStatus, DatabaseStatus, InfoResponse, InterfaceStatus, NetStatus,
209+
RestInterfaceStatus, S3InterfaceStatus, get_info,
210+
};
173211
use crate::openapi::ApiDoc;
174212
use crate::server_state::ServerState;
175213
use aruna_core::effects::StorageEffect;
176-
use aruna_core::structs::{NodeCapabilities, RealmId, Status};
214+
use aruna_core::structs::{NodeCapabilities, RealmId};
177215
use aruna_operations::driver::DriverContext;
178216
use aruna_storage::storage;
179217
use axum::Json;
@@ -229,8 +267,8 @@ mod tests {
229267
net_state: NetStatus::Unavailable,
230268
blob_status: BlobStatus::Unavailable,
231269
interface_status: InterfaceStatus {
232-
s3_status: Status::Available.to_string(),
233-
rest_status: Status::Available.to_string(),
270+
rest: RestInterfaceStatus::Unavailable,
271+
s3: S3InterfaceStatus::Unavailable,
234272
},
235273
database_status: DatabaseStatus {
236274
requests_total: baseline.requests_total,
@@ -246,6 +284,40 @@ mod tests {
246284
);
247285
}
248286

287+
#[tokio::test]
288+
async fn get_info_reports_registered_interface_paths() {
289+
let (state, _tempdir) = setup_state().await;
290+
state
291+
.register_rest_interface("0.0.0.0:3000".parse().unwrap())
292+
.await;
293+
state
294+
.register_s3_interface(
295+
"0.0.0.0:1337".parse().unwrap(),
296+
"http://localhost:1337".to_string(),
297+
)
298+
.await;
299+
300+
let (status, Json(response)) = get_info(State(state)).await;
301+
302+
assert_eq!(status, StatusCode::OK);
303+
assert_eq!(
304+
response.interface_status,
305+
InterfaceStatus {
306+
rest: RestInterfaceStatus::Available {
307+
bind_address: "0.0.0.0:3000".to_string(),
308+
base_url: "http://127.0.0.1:3000".to_string(),
309+
api_base_url: "http://127.0.0.1:3000/api/v1".to_string(),
310+
info_url: "http://127.0.0.1:3000/api/v1/info".to_string(),
311+
swagger_ui_url: "http://127.0.0.1:3000/swagger-ui".to_string(),
312+
},
313+
s3: S3InterfaceStatus::Available {
314+
bind_address: "0.0.0.0:1337".to_string(),
315+
base_url: "http://localhost:1337".to_string(),
316+
},
317+
}
318+
);
319+
}
320+
249321
#[tokio::test]
250322
async fn get_info_reports_storage_error_metrics() {
251323
let (state, _tempdir) = setup_state().await;

api/src/server.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ impl Server {
4242
}
4343

4444
pub async fn run(self) -> Result<(), ServerSetupError> {
45-
let router = self.build_router();
46-
47-
// Create TCP listener for main HTTP server
4845
let listener = TcpListener::bind(self.config.http_addr).await?;
46+
self.run_with_listener(listener).await
47+
}
48+
49+
pub async fn run_with_listener(self, listener: TcpListener) -> Result<(), ServerSetupError> {
50+
let bound_addr = listener.local_addr()?;
51+
self.state.register_rest_interface(bound_addr).await;
52+
let router = self.build_router();
4953

5054
axum::serve(
5155
listener,

api/src/server_state.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use iroh::EndpointAddr;
2727
use jsonwebtoken::DecodingKey;
2828
use serde::{Serialize, de::DeserializeOwned};
2929
use std::collections::{HashMap, HashSet};
30+
use std::net::SocketAddr;
3031
use std::sync::Arc;
3132
use std::sync::atomic::{AtomicBool, Ordering};
3233
use tokio::sync::RwLock;
@@ -58,6 +59,28 @@ pub struct ServerState {
5859
node_id: NodeId,
5960
// Contains OIDC config and Client
6061
oidc_validator: Option<Arc<OidcValidator>>,
62+
interface_state: Arc<RwLock<InterfaceRuntimeState>>,
63+
}
64+
65+
#[derive(Clone, Debug, Default)]
66+
pub struct InterfaceRuntimeState {
67+
pub rest: Option<RestInterfaceRuntime>,
68+
pub s3: Option<S3InterfaceRuntime>,
69+
}
70+
71+
#[derive(Clone, Debug, PartialEq, Eq)]
72+
pub struct RestInterfaceRuntime {
73+
pub bind_address: SocketAddr,
74+
pub base_url: String,
75+
pub api_base_url: String,
76+
pub info_url: String,
77+
pub swagger_ui_url: String,
78+
}
79+
80+
#[derive(Clone, Debug, PartialEq, Eq)]
81+
pub struct S3InterfaceRuntime {
82+
pub bind_address: SocketAddr,
83+
pub base_url: String,
6184
}
6285

6386
impl ServerState {
@@ -101,6 +124,7 @@ impl ServerState {
101124
trusted_realms_list: Arc::new(RwLock::new(trusted_realms)),
102125
issuer_keys: Arc::new(RwLock::new(HashMap::default())),
103126
initial_admin_claim,
127+
interface_state: Arc::new(RwLock::new(InterfaceRuntimeState::default())),
104128
};
105129
state.persist_trusted_realms().await;
106130
state
@@ -142,6 +166,23 @@ impl ServerState {
142166
.ok_or(OidcError::NotConfigured)
143167
}
144168

169+
pub async fn register_rest_interface(&self, bind_address: SocketAddr) {
170+
let mut interface_state = self.interface_state.write().await;
171+
interface_state.rest = Some(RestInterfaceRuntime::from_bind_address(bind_address));
172+
}
173+
174+
pub async fn register_s3_interface(&self, bind_address: SocketAddr, base_url: String) {
175+
let mut interface_state = self.interface_state.write().await;
176+
interface_state.s3 = Some(S3InterfaceRuntime {
177+
bind_address,
178+
base_url,
179+
});
180+
}
181+
182+
pub async fn interface_state(&self) -> InterfaceRuntimeState {
183+
self.interface_state.read().await.clone()
184+
}
185+
145186
pub async fn get_oidc_provider_by_token(
146187
&self,
147188
selector: &OidcTokenSelector,
@@ -463,3 +504,24 @@ where
463504
pub fn swagger_ui() -> SwaggerUi {
464505
SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())
465506
}
507+
508+
impl RestInterfaceRuntime {
509+
pub fn from_bind_address(bind_address: SocketAddr) -> Self {
510+
let host = match bind_address.ip() {
511+
std::net::IpAddr::V4(ip) if ip.is_unspecified() => {
512+
std::net::Ipv4Addr::LOCALHOST.to_string()
513+
}
514+
std::net::IpAddr::V6(ip) if ip.is_unspecified() => "::1".to_string(),
515+
std::net::IpAddr::V6(ip) => format!("[{ip}]"),
516+
std::net::IpAddr::V4(ip) => ip.to_string(),
517+
};
518+
let base_url = format!("http://{host}:{}", bind_address.port());
519+
Self {
520+
bind_address,
521+
api_base_url: format!("{base_url}/api/v1"),
522+
info_url: format!("{base_url}/api/v1/info"),
523+
swagger_ui_url: format!("{base_url}/swagger-ui"),
524+
base_url,
525+
}
526+
}
527+
}

aruna-doctor/src/info.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ mod tests {
306306
use aruna_operations::incoming::initialize_net_incoming;
307307
use aruna_operations::task_incoming::initialize_task_incoming;
308308
use aruna_tasks::TaskHandle;
309-
use axum::Router;
310309
use ed25519_dalek::SigningKey;
311310
use std::str::FromStr;
312311
use std::sync::Arc;
@@ -440,14 +439,9 @@ mod tests {
440439

441440
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
442441
let addr = listener.local_addr().unwrap();
443-
let router: Router = Server::new(state, ServerConfig { http_addr: addr }).build_router();
442+
let server = Server::new(state, ServerConfig { http_addr: addr });
444443
let server_task = tokio::spawn(async move {
445-
axum::serve(
446-
listener,
447-
router.into_make_service_with_connect_info::<std::net::SocketAddr>(),
448-
)
449-
.await
450-
.unwrap();
444+
server.run_with_listener(listener).await.unwrap();
451445
});
452446

453447
TestNode {
@@ -575,6 +569,15 @@ mod tests {
575569
let info = fetch_info(&config).await.unwrap();
576570

577571
assert!(matches!(info.net_state, aruna_api::routes::info::NetStatus::Available { .. }));
572+
assert!(matches!(
573+
info.interface_status.rest,
574+
aruna_api::routes::info::RestInterfaceStatus::Available {
575+
ref base_url,
576+
ref info_url,
577+
..
578+
} if base_url == &format!("http://{}", node.http_addr)
579+
&& info_url == &format!("http://{}/api/v1/info", node.http_addr)
580+
));
578581
node.shutdown().await;
579582
}
580583
}

aruna/src/main.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use aruna_operations::task_incoming::initialize_task_incoming;
3232
use aruna_tasks::TaskHandle;
3333
use std::collections::HashMap;
3434
use std::sync::Arc;
35+
use tokio::net::TcpListener;
3536
use tracing::{error, info, warn};
3637

3738
#[tokio::main]
@@ -228,7 +229,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
228229
let server_config = ServerConfig {
229230
http_addr: config.http_socket_addr,
230231
};
231-
let server = Server::new(state, server_config);
232+
let server = Server::new(state.clone(), server_config);
232233

233234
// S3 Server
234235
let s3_address = format!("{}:{}", config.s3_address, config.s3_port);
@@ -243,7 +244,17 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
243244
.await
244245
.unwrap();
245246

246-
let server_handle = s3_server.run().await.unwrap();
247+
let s3_listener = TcpListener::bind(&s3_address).await.unwrap();
248+
let s3_bound_addr = s3_listener.local_addr().unwrap();
249+
state
250+
.register_s3_interface(
251+
s3_bound_addr,
252+
format!("http://{}:{}", config.s3_host, s3_bound_addr.port()),
253+
)
254+
.await;
255+
let (_s3_addr, server_handle) = s3_server.run_with_listener(s3_listener).unwrap();
256+
257+
let rest_listener = TcpListener::bind(config.http_socket_addr).await?;
247258

248259
tokio::select! {
249260
res = server_handle => {
@@ -252,7 +263,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
252263
Err(e) => error!("S3 Server panicked: {:?}", e),
253264
}
254265
}
255-
res = server.run() => {
266+
res = server.run_with_listener(rest_listener) => {
256267
match res {
257268
Ok(_) => info!("REST Server stopped normally"),
258269
Err(e) => error!("REST Server panicked: {:?}", e),

0 commit comments

Comments
 (0)