micromegas/servers/
flight_sql_server.rs1use anyhow::Result;
2use micromegas_analytics::lakehouse::lakehouse_context::LakehouseContext;
3use micromegas_analytics::lakehouse::partition_cache::LivePartitionProvider;
4use micromegas_analytics::lakehouse::session_configurator::SessionConfigurator;
5use micromegas_analytics::lakehouse::static_tables_configurator::StaticTablesConfigurator;
6use micromegas_analytics::lakehouse::view_factory::{ViewFactory, default_view_factory};
7use micromegas_auth::tower::AuthService;
8use micromegas_auth::types::AuthProvider;
9use micromegas_ingestion::data_lake_connection::DataLakeConnection;
10use micromegas_tracing::prelude::*;
11use std::future::Future;
12use std::net::SocketAddr;
13use std::pin::Pin;
14use std::sync::Arc;
15use std::time::Duration;
16
17use arrow_flight::flight_service_server::FlightServiceServer;
18use datafusion::execution::runtime_env::RuntimeEnv;
19use tonic::transport::Server;
20use tower::ServiceBuilder;
21use tower::layer::layer_fn;
22
23use super::connect_info_layer::ConnectedIncoming;
24use super::flight_sql_service_impl::FlightSqlServiceImpl;
25use super::grpc_health_service::GrpcHealthService;
26use super::log_uri_service::LogUriService;
27
28type ViewFactoryFn = Box<
29 dyn FnOnce(
30 Arc<RuntimeEnv>,
31 Arc<DataLakeConnection>,
32 ) -> Pin<Box<dyn Future<Output = Result<ViewFactory>> + Send>>
33 + Send,
34>;
35
36pub struct FlightSqlServer;
56
57impl FlightSqlServer {
58 pub fn builder() -> FlightSqlServerBuilder {
59 FlightSqlServerBuilder::default()
60 }
61}
62
63pub struct FlightSqlServerBuilder {
64 view_factory_fn: Option<ViewFactoryFn>,
65 session_configurator: Option<Arc<dyn SessionConfigurator>>,
66 auth_provider: Option<Arc<dyn AuthProvider>>,
67 use_default_auth: bool,
68 max_decoding_message_size: usize,
69 listen_addr: SocketAddr,
70 shutdown_grace: Duration,
71 injected_lakehouse: Option<Arc<LakehouseContext>>,
72 injected_shutdown: Option<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
73 health_listen_addr: Option<SocketAddr>,
74}
75
76impl Default for FlightSqlServerBuilder {
77 fn default() -> Self {
78 Self {
79 view_factory_fn: None,
80 session_configurator: None,
81 auth_provider: None,
82 use_default_auth: false,
83 max_decoding_message_size: 100 * 1024 * 1024,
84 listen_addr: "0.0.0.0:50051"
85 .parse()
86 .expect("valid default listen address"),
87 shutdown_grace: Duration::from_secs(25),
88 injected_lakehouse: None,
89 injected_shutdown: None,
90 health_listen_addr: None,
91 }
92 }
93}
94
95impl FlightSqlServerBuilder {
96 pub fn with_view_factory_fn<F, Fut>(mut self, f: F) -> Self
100 where
101 F: FnOnce(Arc<RuntimeEnv>, Arc<DataLakeConnection>) -> Fut + Send + 'static,
102 Fut: Future<Output = Result<ViewFactory>> + Send + 'static,
103 {
104 self.view_factory_fn = Some(Box::new(move |runtime, lake| Box::pin(f(runtime, lake))));
105 self
106 }
107
108 pub fn with_session_configurator(mut self, cfg: Arc<dyn SessionConfigurator>) -> Self {
113 self.session_configurator = Some(cfg);
114 self
115 }
116
117 pub fn with_auth_provider(mut self, provider: Arc<dyn AuthProvider>) -> Self {
119 self.auth_provider = Some(provider);
120 self.use_default_auth = false;
121 self
122 }
123
124 pub fn with_default_auth(mut self) -> Self {
128 self.use_default_auth = true;
129 self.auth_provider = None;
130 self
131 }
132
133 pub fn with_max_decoding_message_size(mut self, bytes: usize) -> Self {
135 self.max_decoding_message_size = bytes;
136 self
137 }
138
139 pub fn with_listen_addr(mut self, addr: SocketAddr) -> Self {
141 self.listen_addr = addr;
142 self
143 }
144
145 pub fn with_shutdown_grace(mut self, grace: Duration) -> Self {
147 self.shutdown_grace = grace;
148 self
149 }
150
151 pub fn with_lakehouse(mut self, lakehouse: Arc<LakehouseContext>) -> Self {
155 self.injected_lakehouse = Some(lakehouse);
156 self
157 }
158
159 pub fn with_shutdown(mut self, shutdown: impl Future<Output = ()> + Send + 'static) -> Self {
163 self.injected_shutdown = Some(Box::pin(shutdown));
164 self
165 }
166
167 pub fn with_health_addr(mut self, addr: SocketAddr) -> Self {
172 self.health_listen_addr = Some(addr);
173 self
174 }
175
176 pub async fn build_and_serve(self) -> Result<()> {
180 let lakehouse = if let Some(lh) = self.injected_lakehouse {
182 lh
183 } else {
184 LakehouseContext::from_env().await?
185 };
186 let data_lake = lakehouse.lake().clone();
187 let probe_lake = lakehouse.lake().clone();
188 info!(
189 "created lakehouse context with metadata cache: {:?}",
190 lakehouse.metadata_cache()
191 );
192
193 let view_factory = if let Some(factory_fn) = self.view_factory_fn {
194 Arc::new(factory_fn(lakehouse.runtime().clone(), data_lake).await?)
195 } else {
196 Arc::new(default_view_factory(lakehouse.runtime().clone(), data_lake).await?)
197 };
198
199 let partition_provider =
200 Arc::new(LivePartitionProvider::new(lakehouse.lake().db_pool.clone()));
201
202 let session_configurator: Arc<dyn SessionConfigurator> =
203 if let Some(cfg) = self.session_configurator {
204 cfg
205 } else {
206 StaticTablesConfigurator::from_env(
207 "MICROMEGAS_STATIC_TABLES_URL",
208 lakehouse.runtime().clone(),
209 )
210 .await?
211 };
212
213 let svc = FlightServiceServer::new(FlightSqlServiceImpl::new(
214 lakehouse,
215 partition_provider,
216 view_factory,
217 session_configurator,
218 ))
219 .max_decoding_message_size(self.max_decoding_message_size);
220
221 let auth_provider: Option<Arc<dyn AuthProvider>> = if let Some(provider) =
222 self.auth_provider
223 {
224 Some(provider)
225 } else if self.use_default_auth {
226 match micromegas_auth::default_provider::provider().await? {
227 Some(provider) => Some(provider),
228 None => {
229 anyhow::bail!(
230 "Authentication required but no auth providers configured. Set MICROMEGAS_API_KEYS or MICROMEGAS_OIDC_CONFIG"
231 );
232 }
233 }
234 } else {
235 info!("Authentication disabled");
236 None
237 };
238
239 let layer = ServiceBuilder::new()
240 .layer(layer_fn(GrpcHealthService::new))
241 .layer(layer_fn(|service| LogUriService { service }))
242 .layer(layer_fn(move |inner| AuthService {
243 inner,
244 auth_provider: auth_provider.clone(),
245 }))
246 .into_inner();
247
248 use super::shutdown::{ShutdownFanout, wait_for_sigterm};
249
250 info!("Listening on {:?}", self.listen_addr);
251 let listener = std::net::TcpListener::bind(self.listen_addr)?;
252 let incoming = ConnectedIncoming::from_std_listener(listener)?;
253
254 let shutdown_future: Pin<Box<dyn Future<Output = ()> + Send + 'static>> = self
256 .injected_shutdown
257 .unwrap_or_else(|| Box::pin(wait_for_sigterm()));
258 let fanout = ShutdownFanout::new(shutdown_future);
259 let grace_secs = self.shutdown_grace.as_secs();
260 let grace = self.shutdown_grace;
261
262 if let Some(health_addr) = self.health_listen_addr {
263 use super::readiness::ReadinessProbe;
264 use axum::Extension;
265 use axum::Router;
266 use axum::routing::get;
267 use tokio::net::TcpListener;
268
269 let probe = std::sync::Arc::new(ReadinessProbe::new(probe_lake));
270 let sidecar_listener = TcpListener::bind(health_addr).await?;
271 let shutdown_rx = fanout.subscribe();
272 tokio::spawn(async move {
273 async fn sidecar_ready(
274 Extension(p): Extension<std::sync::Arc<ReadinessProbe>>,
275 ) -> axum::http::StatusCode {
276 if p.check_ready().await {
277 axum::http::StatusCode::OK
278 } else {
279 axum::http::StatusCode::SERVICE_UNAVAILABLE
280 }
281 }
282 let sidecar_app = Router::new()
283 .route("/health", get(|| async { axum::http::StatusCode::OK }))
284 .route("/ready", get(sidecar_ready))
285 .layer(Extension(probe));
286 let _ = axum::serve(sidecar_listener, sidecar_app)
287 .with_graceful_shutdown(shutdown_rx)
288 .await;
289 info!("FlightSQL health sidecar stopped");
290 });
291 info!("FlightSQL health sidecar listening on {health_addr}");
292 }
293
294 let serve = Server::builder()
295 .layer(layer)
296 .add_service(svc)
297 .serve_with_incoming_shutdown(incoming, fanout.subscribe());
298
299 let deadline = {
300 let d = fanout.subscribe();
301 async move {
302 d.await;
303 tokio::time::sleep(grace).await;
304 }
305 };
306
307 tokio::select! {
308 res = serve => {
309 info!("drain completed");
310 res?;
311 }
312 _ = deadline => {
313 warn!("grace period of {grace_secs}s elapsed with work still in flight");
314 }
315 }
316
317 info!("bye");
318 Ok(())
319 }
320}