1use crate::config::DEFAULT_FLIGHTSQL_LISTEN_ADDR;
2use anyhow::{Context, Result};
3use micromegas_analytics::lakehouse::lakehouse_context::LakehouseContext;
4use micromegas_analytics::lakehouse::partition_cache::LivePartitionProvider;
5use micromegas_analytics::lakehouse::read_scope::IsolationConfig;
6use micromegas_analytics::lakehouse::session_configurator::SessionConfigurator;
7use micromegas_analytics::lakehouse::static_tables_configurator::StaticTablesConfigurator;
8use micromegas_analytics::lakehouse::view_definition_store::PgViewDefinitionStore;
9use micromegas_analytics::lakehouse::view_factory::{ViewFactory, default_view_factory};
10use micromegas_analytics::lakehouse::view_registry::ViewRegistry;
11use micromegas_auth::db_api_key::{ApiKeyTable, dedicated_key_store_pool};
12use micromegas_auth::db_audience_grants::{DbAudienceGrantsConfig, DbAudienceGrantsSource};
13use micromegas_auth::default_provider::ProviderBuilder;
14use micromegas_auth::policy::{AudienceGrants, AudienceReadPolicy, ReadPolicy};
15use micromegas_auth::tower::AuthService;
16use micromegas_auth::types::AuthProvider;
17use micromegas_ingestion::data_lake_connection::{DataLakeConnection, WritablePolicy};
18use micromegas_ingestion::sql_migration::warn_if_data_lake_schema_stale;
19use micromegas_tracing::prelude::*;
20use std::future::Future;
21use std::net::SocketAddr;
22use std::pin::Pin;
23use std::sync::Arc;
24use std::time::Duration;
25
26use arrow_flight::flight_service_server::FlightServiceServer;
27use datafusion::execution::runtime_env::RuntimeEnv;
28use tonic::transport::Server;
29use tower::ServiceBuilder;
30use tower::layer::layer_fn;
31
32use super::connect_info_layer::ConnectedIncoming;
33use super::flight_sql_service_impl::FlightSqlServiceImpl;
34use super::grpc_health_service::GrpcHealthService;
35use super::log_uri_service::LogUriService;
36
37type ViewFactoryFn = Box<
38 dyn FnOnce(
39 Arc<RuntimeEnv>,
40 Arc<DataLakeConnection>,
41 ) -> Pin<Box<dyn Future<Output = Result<ViewFactory>> + Send>>
42 + Send,
43>;
44
45type AuthAndDefaults = (Option<Arc<dyn AuthProvider>>, Arc<dyn ReadPolicy>);
48
49fn resolve_isolation_config(
53 explicit: Option<Arc<IsolationConfig>>,
54) -> Result<Arc<IsolationConfig>> {
55 match explicit {
56 Some(config) => Ok(config),
57 None => Ok(Arc::new(IsolationConfig::from_env()?)),
58 }
59}
60
61pub struct FlightSqlServer;
81
82impl FlightSqlServer {
83 pub fn builder() -> FlightSqlServerBuilder {
84 FlightSqlServerBuilder::default()
85 }
86}
87
88pub struct FlightSqlServerBuilder {
89 view_factory_fn: Option<ViewFactoryFn>,
90 session_configurator: Option<Arc<dyn SessionConfigurator>>,
91 auth_provider: Option<Arc<dyn AuthProvider>>,
92 read_policy: Option<Arc<dyn ReadPolicy>>,
93 isolation_config: Option<Arc<IsolationConfig>>,
94 use_default_auth: bool,
95 max_decoding_message_size: usize,
96 listen_addr: SocketAddr,
97 shutdown_grace: Duration,
98 injected_lakehouse: Option<Arc<LakehouseContext>>,
99 injected_shutdown: Option<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
100 health_listen_addr: Option<SocketAddr>,
101}
102
103impl Default for FlightSqlServerBuilder {
104 fn default() -> Self {
105 Self {
106 view_factory_fn: None,
107 session_configurator: None,
108 auth_provider: None,
109 read_policy: None,
110 isolation_config: None,
111 use_default_auth: false,
112 max_decoding_message_size: 100 * 1024 * 1024,
113 listen_addr: DEFAULT_FLIGHTSQL_LISTEN_ADDR,
114 shutdown_grace: Duration::from_secs(25),
115 injected_lakehouse: None,
116 injected_shutdown: None,
117 health_listen_addr: None,
118 }
119 }
120}
121
122impl FlightSqlServerBuilder {
123 pub fn with_view_factory_fn<F, Fut>(mut self, f: F) -> Self
127 where
128 F: FnOnce(Arc<RuntimeEnv>, Arc<DataLakeConnection>) -> Fut + Send + 'static,
129 Fut: Future<Output = Result<ViewFactory>> + Send + 'static,
130 {
131 self.view_factory_fn = Some(Box::new(move |runtime, lake| Box::pin(f(runtime, lake))));
132 self
133 }
134
135 pub fn with_session_configurator(mut self, cfg: Arc<dyn SessionConfigurator>) -> Self {
140 self.session_configurator = Some(cfg);
141 self
142 }
143
144 pub fn with_auth_provider(mut self, provider: Arc<dyn AuthProvider>) -> Self {
146 self.auth_provider = Some(provider);
147 self.use_default_auth = false;
148 self
149 }
150
151 pub fn with_read_policy(mut self, policy: Arc<dyn ReadPolicy>) -> Self {
163 self.read_policy = Some(policy);
164 self
165 }
166
167 pub fn with_isolation_config(mut self, config: Arc<IsolationConfig>) -> Self {
173 self.isolation_config = Some(config);
174 self
175 }
176
177 pub fn with_default_auth(mut self) -> Self {
181 self.use_default_auth = true;
182 self.auth_provider = None;
183 self
184 }
185
186 pub fn with_max_decoding_message_size(mut self, bytes: usize) -> Self {
188 self.max_decoding_message_size = bytes;
189 self
190 }
191
192 pub fn with_listen_addr(mut self, addr: SocketAddr) -> Self {
194 self.listen_addr = addr;
195 self
196 }
197
198 pub fn with_shutdown_grace(mut self, grace: Duration) -> Self {
200 self.shutdown_grace = grace;
201 self
202 }
203
204 pub fn with_lakehouse(mut self, lakehouse: Arc<LakehouseContext>) -> Self {
208 self.injected_lakehouse = Some(lakehouse);
209 self
210 }
211
212 pub fn with_shutdown(mut self, shutdown: impl Future<Output = ()> + Send + 'static) -> Self {
216 self.injected_shutdown = Some(Box::pin(shutdown));
217 self
218 }
219
220 pub fn with_health_addr(mut self, addr: SocketAddr) -> Self {
225 self.health_listen_addr = Some(addr);
226 self
227 }
228
229 pub async fn build_and_serve(self) -> Result<()> {
233 let isolation_config = resolve_isolation_config(self.isolation_config)?;
236
237 let lakehouse = if let Some(lh) = self.injected_lakehouse {
243 lh
244 } else {
245 LakehouseContext::from_env(WritablePolicy::Prefer).await?
246 };
247 let data_lake = lakehouse.lake().clone();
248 let probe_lake = lakehouse.lake().clone();
249 let query_denials = lakehouse.query_denials().clone();
254 info!(
255 "created lakehouse context with metadata cache: {:?}",
256 lakehouse.metadata_cache()
257 );
258
259 let base_view_factory: Arc<ViewFactory> = if let Some(factory_fn) = self.view_factory_fn {
260 Arc::new(factory_fn(lakehouse.runtime().clone(), data_lake.clone()).await?)
261 } else {
262 Arc::new(
263 default_view_factory(
264 lakehouse.runtime().clone(),
265 data_lake.clone(),
266 lakehouse.default_audience(),
267 )
268 .await?,
269 )
270 };
271
272 let partition_provider =
273 Arc::new(LivePartitionProvider::new(lakehouse.lake().db_pool.clone()));
274 let lake_pool_for_keys = lakehouse.lake().db_pool.clone();
278
279 let session_configurator: Arc<dyn SessionConfigurator> =
280 if let Some(cfg) = self.session_configurator {
281 cfg
282 } else {
283 StaticTablesConfigurator::from_env(
284 "MICROMEGAS_STATIC_TABLES_URL",
285 lakehouse.runtime().clone(),
286 )
287 .await?
288 };
289
290 let view_registry = Arc::new(ViewRegistry::new(
297 base_view_factory,
298 Arc::new(PgViewDefinitionStore::new(lakehouse.lake().db_pool.clone())),
299 lakehouse.runtime().clone(),
300 data_lake,
301 session_configurator.clone(),
302 ));
303 view_registry
304 .reload()
305 .await
306 .with_context(|| "initial view_registry reload")?;
307
308 let (auth_provider, default_policy): AuthAndDefaults =
316 if let Some(provider) = self.auth_provider {
317 let policy: Arc<dyn ReadPolicy> = if self.read_policy.is_none() {
331 let key_store_pool = dedicated_key_store_pool(&lake_pool_for_keys);
332 warn_if_data_lake_schema_stale(&lake_pool_for_keys).await;
333 let audience_grants_config = DbAudienceGrantsConfig::from_env_with_prefix("");
334 let audience_grants_store = Arc::new(DbAudienceGrantsSource::new(
335 key_store_pool,
336 Duration::from_secs(audience_grants_config.cache_ttl_secs),
337 ));
338 Arc::new(AudienceReadPolicy::default().with_store(audience_grants_store))
339 } else {
340 Arc::new(AudienceReadPolicy::new(AudienceGrants::empty()))
341 };
342 (Some(provider), policy)
343 } else if self.use_default_auth {
344 let key_store_pool = dedicated_key_store_pool(&lake_pool_for_keys);
345 let group_store_pool = dedicated_key_store_pool(&lake_pool_for_keys);
346 warn_if_data_lake_schema_stale(&lake_pool_for_keys).await;
347 let provider = match ProviderBuilder::new("")
348 .with_db_key_store(key_store_pool, ApiKeyTable::Analytics)
349 .with_group_store(group_store_pool)
350 .build()
351 .await?
352 {
353 Some(provider) => provider,
354 None => {
355 anyhow::bail!(
356 "Authentication required but no auth providers configured. Set \
357 MICROMEGAS_OIDC_CONFIG, or populate the analytics_api_keys DB table"
358 );
359 }
360 };
361 let audience_grants_pool = dedicated_key_store_pool(&lake_pool_for_keys);
368 let audience_grants_config = DbAudienceGrantsConfig::from_env_with_prefix("");
369 let audience_grants_store = Arc::new(DbAudienceGrantsSource::new(
370 audience_grants_pool,
371 Duration::from_secs(audience_grants_config.cache_ttl_secs),
372 ));
373 let policy: Arc<dyn ReadPolicy> =
374 Arc::new(AudienceReadPolicy::default().with_store(audience_grants_store));
375 (Some(provider), policy)
376 } else {
377 info!("Authentication disabled");
378 (
387 None,
388 Arc::new(AudienceReadPolicy::new(AudienceGrants::empty())),
389 )
390 };
391 let read_policy = self.read_policy.unwrap_or(default_policy);
392
393 let svc = FlightServiceServer::new(FlightSqlServiceImpl::new(
394 lakehouse,
395 partition_provider,
396 view_registry.clone(),
397 session_configurator,
398 read_policy,
399 isolation_config,
400 ))
401 .max_decoding_message_size(self.max_decoding_message_size);
402
403 let layer = ServiceBuilder::new()
404 .layer(layer_fn(GrpcHealthService::new))
405 .layer(layer_fn(|service| LogUriService { service }))
406 .layer(layer_fn(move |inner| AuthService {
407 inner,
408 auth_provider: auth_provider.clone(),
409 }))
410 .into_inner();
411
412 use super::shutdown::{ShutdownFanout, wait_for_sigterm};
413
414 info!("Listening on {:?}", self.listen_addr);
415 let listener = std::net::TcpListener::bind(self.listen_addr)?;
416 let incoming = ConnectedIncoming::from_std_listener(listener)?;
417
418 let shutdown_future: Pin<Box<dyn Future<Output = ()> + Send + 'static>> = self
420 .injected_shutdown
421 .unwrap_or_else(|| Box::pin(wait_for_sigterm()));
422 let fanout = ShutdownFanout::new(shutdown_future);
423 let grace_secs = self.shutdown_grace.as_secs();
424 let grace = self.shutdown_grace;
425
426 query_denials.spawn_refresh_task(fanout.subscribe());
430
431 view_registry.spawn_refresh_task(fanout.subscribe());
437
438 if let Some(health_addr) = self.health_listen_addr {
439 use super::readiness::ReadinessProbe;
440 use axum::Extension;
441 use axum::Router;
442 use axum::routing::get;
443 use tokio::net::TcpListener;
444
445 let probe = std::sync::Arc::new(ReadinessProbe::new(probe_lake));
446 let sidecar_listener = TcpListener::bind(health_addr).await?;
447 let shutdown_rx = fanout.subscribe();
448 tokio::spawn(async move {
449 async fn sidecar_ready(
450 Extension(p): Extension<std::sync::Arc<ReadinessProbe>>,
451 ) -> axum::http::StatusCode {
452 if p.check_ready().await {
453 axum::http::StatusCode::OK
454 } else {
455 axum::http::StatusCode::SERVICE_UNAVAILABLE
456 }
457 }
458 let sidecar_app = Router::new()
459 .route("/health", get(|| async { axum::http::StatusCode::OK }))
460 .route("/ready", get(sidecar_ready))
461 .layer(Extension(probe));
462 let _ = axum::serve(sidecar_listener, sidecar_app)
463 .with_graceful_shutdown(shutdown_rx)
464 .await;
465 info!("FlightSQL health sidecar stopped");
466 });
467 info!("FlightSQL health sidecar listening on {health_addr}");
468 }
469
470 let serve = Server::builder()
471 .layer(layer)
472 .add_service(svc)
473 .serve_with_incoming_shutdown(incoming, fanout.subscribe());
474
475 let deadline = {
476 let d = fanout.subscribe();
477 async move {
478 d.await;
479 tokio::time::sleep(grace).await;
480 }
481 };
482
483 tokio::select! {
484 res = serve => {
485 info!("drain completed");
486 res?;
487 }
488 _ = deadline => {
489 warn!("grace period of {grace_secs}s elapsed with work still in flight");
490 }
491 }
492
493 info!("bye");
494 Ok(())
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 use serial_test::serial;
502
503 const PUBLIC_VIEW_SETS_VAR: &str = "MICROMEGAS_PUBLIC_VIEW_SETS";
504
505 struct EnvGuard;
507
508 impl Drop for EnvGuard {
509 fn drop(&mut self) {
510 unsafe {
512 std::env::remove_var(PUBLIC_VIEW_SETS_VAR);
513 }
514 }
515 }
516
517 #[test]
518 #[serial]
519 fn env_set_is_parsed() {
520 let _guard = EnvGuard;
521 unsafe {
523 std::env::set_var(PUBLIC_VIEW_SETS_VAR, "log_stats,images");
524 }
525 let config = resolve_isolation_config(None).expect("resolve_isolation_config");
526 assert_eq!(
527 config.public_view_sets,
528 vec!["log_stats".to_string(), "images".to_string()]
529 );
530 }
531
532 #[test]
533 #[serial]
534 fn env_unset_is_empty() {
535 let _guard = EnvGuard;
536 unsafe {
538 std::env::remove_var(PUBLIC_VIEW_SETS_VAR);
539 }
540 let config = resolve_isolation_config(None).expect("resolve_isolation_config");
541 assert!(
542 config.public_view_sets.is_empty(),
543 "an unset var must resolve to no public view sets"
544 );
545 }
546
547 #[test]
548 #[serial]
549 fn env_malformed_is_err() {
550 let _guard = EnvGuard;
551 unsafe {
553 std::env::set_var(PUBLIC_VIEW_SETS_VAR, r#"["log_stats"]"#);
554 }
555 let err =
556 resolve_isolation_config(None).expect_err("a JSON-array-shaped value must be rejected");
557 assert!(err.to_string().contains(PUBLIC_VIEW_SETS_VAR));
558 }
559
560 #[test]
564 #[serial]
565 fn explicit_config_wins_over_a_different_env_value() {
566 let _guard = EnvGuard;
567 unsafe {
569 std::env::set_var(PUBLIC_VIEW_SETS_VAR, "log_stats");
570 }
571 let explicit = Arc::new(IsolationConfig {
572 public_view_sets: vec!["images".to_string()],
573 });
574 let config =
575 resolve_isolation_config(Some(explicit.clone())).expect("resolve_isolation_config");
576 assert_eq!(config.public_view_sets, explicit.public_view_sets);
577 }
578
579 #[test]
583 #[serial]
584 fn explicit_config_wins_over_a_malformed_env_value() {
585 let _guard = EnvGuard;
586 unsafe {
588 std::env::set_var(PUBLIC_VIEW_SETS_VAR, r#"["log_stats"]"#);
589 }
590 let explicit = Arc::new(IsolationConfig {
591 public_view_sets: vec!["images".to_string()],
592 });
593 let config =
594 resolve_isolation_config(Some(explicit.clone())).expect("resolve_isolation_config");
595 assert_eq!(config.public_view_sets, explicit.public_view_sets);
596 }
597}