1717 */
1818
1919use crate :: handlers:: http:: logstream:: error:: StreamError ;
20- use crate :: handlers:: http:: { base_path, cross_origin_config, API_BASE_PATH , API_VERSION } ;
20+ use crate :: handlers:: http:: middleware:: RouteExt ;
21+ use crate :: handlers:: http:: {
22+ base_path, base_path_without_preceding_slash, cross_origin_config, API_BASE_PATH , API_VERSION ,
23+ } ;
24+ use crate :: rbac:: role:: Action ;
2125use crate :: { analytics, banner, metadata, metrics, migration, rbac, storage} ;
2226use actix_web:: http:: header;
23- use actix_web:: web;
2427use actix_web:: web:: ServiceConfig ;
28+ use actix_web:: { web, Responder } ;
2529use actix_web:: { App , HttpServer } ;
2630use async_trait:: async_trait;
2731use chrono:: { DateTime , Utc } ;
@@ -135,11 +139,22 @@ impl QueryServer {
135139 . service ( Server :: get_user_webscope ( ) )
136140 . service ( Server :: get_llm_webscope ( ) )
137141 . service ( Server :: get_oauth_webscope ( oidc_client) )
138- . service ( Server :: get_user_role_webscope ( ) ) ,
142+ . service ( Server :: get_user_role_webscope ( ) )
143+ . service ( Self :: get_cluster_info_web_scope ( ) ) ,
139144 )
140145 . service ( Server :: get_generated ( ) ) ;
141146 }
142147
148+ fn get_cluster_info_web_scope ( ) -> actix_web:: Scope {
149+ web:: scope ( "/cluster" ) . service (
150+ web:: resource ( "/info" ) . route (
151+ web:: get ( )
152+ . to ( Self :: get_cluster_info)
153+ . authorize ( Action :: ListCluster ) ,
154+ ) ,
155+ )
156+ }
157+
143158 // update the .query.json file and return the new IngesterMetadataArr
144159 pub async fn get_ingester_info ( ) -> anyhow:: Result < IngesterMetadataArr > {
145160 let store = CONFIG . storage ( ) . get_object_store ( ) ;
@@ -163,7 +178,7 @@ impl QueryServer {
163178 }
164179
165180 pub async fn check_liveness ( domain_name : & str ) -> bool {
166- let uri = Url :: parse ( & format ! ( "{}{}/ liveness" , domain_name, base_path ( ) ) ) . unwrap ( ) ;
181+ let uri = Url :: parse ( & format ! ( "{}liveness" , domain_name) ) . unwrap ( ) ;
167182
168183 let reqw = reqwest:: Client :: new ( )
169184 . get ( uri)
@@ -174,6 +189,38 @@ impl QueryServer {
174189 reqw. is_ok ( )
175190 }
176191
192+ async fn get_cluster_info ( ) -> Result < impl Responder , StreamError > {
193+ let ingester_infos = Self :: get_ingester_info ( ) . await . map_err ( |err| {
194+ log:: error!( "Fatal: failed to get ingester info: {:?}" , err) ;
195+ StreamError :: Custom {
196+ msg : format ! ( "failed to get ingester info\n {:?}" , err) ,
197+ status : StatusCode :: INTERNAL_SERVER_ERROR ,
198+ }
199+ } ) ?;
200+
201+ let mut infos = vec ! [ ] ;
202+
203+ for ingester in ingester_infos {
204+ let uri = Url :: parse ( & format ! ( "{}liveness" , ingester. domain_name) )
205+ . expect ( "should always be a valid url" ) ;
206+
207+ let reqw = reqwest:: Client :: new ( )
208+ . get ( uri)
209+ . header ( header:: CONTENT_TYPE , "application/json" )
210+ . send ( )
211+ . await ;
212+
213+ infos. push ( ClusterInfo :: new (
214+ & ingester. domain_name ,
215+ reqw. is_ok ( ) ,
216+ reqw. as_ref ( ) . err ( ) . map ( |e| e. to_string ( ) ) ,
217+ reqw. ok ( ) . map ( |r| r. status ( ) . to_string ( ) ) ,
218+ ) ) ;
219+ }
220+
221+ Ok ( actix_web:: HttpResponse :: Ok ( ) . json ( infos) )
222+ }
223+
177224 /// initialize the server, run migrations as needed and start the server
178225 async fn initialize ( & self ) -> anyhow:: Result < ( ) > {
179226 migration:: run_metadata_migration ( & CONFIG ) . await ?;
@@ -254,8 +301,8 @@ impl QueryServer {
254301 for ingester in ingester_infos. iter ( ) {
255302 let url = format ! (
256303 "{}{}/logstream/{}" ,
257- ingester. domain_name. to_string ( ) . trim_end_matches ( '/' ) ,
258- base_path ( ) ,
304+ ingester. domain_name,
305+ base_path_without_preceding_slash ( ) ,
259306 stream_name
260307 ) ;
261308
@@ -272,8 +319,8 @@ impl QueryServer {
272319 for ingester in ingester_infos {
273320 let url = format ! (
274321 "{}{}/logstream/{}" ,
275- ingester. domain_name. to_string ( ) . trim_end_matches ( '/' ) ,
276- base_path ( ) ,
322+ ingester. domain_name,
323+ base_path_without_preceding_slash ( ) ,
277324 stream_name
278325 ) ;
279326
@@ -306,8 +353,8 @@ impl QueryServer {
306353 for ingester in ingester_infos {
307354 let url = format ! (
308355 "{}{}/logstream/{}/stats" ,
309- ingester. domain_name. to_string ( ) . trim_end_matches ( '/' ) ,
310- base_path ( ) ,
356+ ingester. domain_name,
357+ base_path_without_preceding_slash ( ) ,
311358 stream_name
312359 ) ;
313360
@@ -615,3 +662,27 @@ impl StorageStats {
615662 }
616663 }
617664}
665+
666+ #[ derive( Debug , Default , Serialize , Deserialize ) ]
667+ struct ClusterInfo {
668+ domain_name : String ,
669+ reachable : bool ,
670+ error : Option < String > , // error message if the ingester is not reachable
671+ status : Option < String > , // status message if the ingester is reachable
672+ }
673+
674+ impl ClusterInfo {
675+ fn new (
676+ domain_name : & str ,
677+ reachable : bool ,
678+ error : Option < String > ,
679+ status : Option < String > ,
680+ ) -> Self {
681+ Self {
682+ domain_name : domain_name. to_string ( ) ,
683+ reachable,
684+ error,
685+ status,
686+ }
687+ }
688+ }
0 commit comments