Skip to main content

frontend/
server.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::net::SocketAddr;
16use std::sync::Arc;
17use std::time::Duration;
18
19use auth::UserProviderRef;
20use axum::extract::{Request, State};
21use axum::middleware::Next;
22use axum::response::IntoResponse;
23use common_base::Plugins;
24use common_config::Configurable;
25use common_telemetry::{info, warn};
26use meta_client::MetaClientOptions;
27use servers::batcher::{BatchingProtocol, pending_rows_batch_sync_enabled};
28use servers::error::Error as ServerError;
29use servers::grpc::builder::GrpcServerBuilder;
30use servers::grpc::flight::FlightCraftRef;
31use servers::grpc::frontend_grpc_handler::FrontendGrpcHandler;
32use servers::grpc::greptime_handler::GreptimeRequestHandler;
33use servers::grpc::{GrpcOptions, GrpcServer};
34use servers::http::event::LogValidatorRef;
35use servers::http::result::error_result::ErrorResponse;
36use servers::http::utils::router::RouterConfigurator;
37use servers::http::{HttpOptions, HttpServer, HttpServerBuilder};
38use servers::interceptor::LogIngestInterceptorRef;
39use servers::metrics_handler::MetricsHandler;
40use servers::mysql::server::{MysqlServer, MysqlSpawnConfig, MysqlSpawnRef};
41use servers::otel_arrow::OtelArrowServiceHandler;
42use servers::postgres::PostgresServer;
43use servers::request_memory_limiter::ServerMemoryLimiter;
44use servers::server::{Server, ServerHandlers};
45use servers::tls::{ReloadableTlsServerConfig, maybe_watch_server_tls_config};
46use session::context::Channel;
47use snafu::ResultExt;
48use tonic::Status;
49
50use crate::error::{self, Result, StartServerSnafu, TomlFormatSnafu};
51use crate::frontend::FrontendOptions;
52use crate::instance::Instance;
53use crate::service_config::PromStoreOptions;
54
55pub struct Services<T>
56where
57    T: Into<FrontendOptions> + Configurable + Clone,
58{
59    opts: T,
60    instance: Arc<Instance>,
61    grpc_server_builder: Option<GrpcServerBuilder>,
62    http_server_builder: Option<HttpServerBuilder>,
63    plugins: Plugins,
64    flight_handler: Option<FlightCraftRef>,
65    internal_flight_handler: Option<FlightCraftRef>,
66    pub server_memory_limiter: ServerMemoryLimiter,
67}
68
69impl<T> Services<T>
70where
71    T: Into<FrontendOptions> + Configurable + Clone,
72{
73    pub fn new(opts: T, instance: Arc<Instance>, plugins: Plugins) -> Self {
74        let feopts = opts.clone().into();
75        instance.init_logical_batcher(&feopts);
76        // Create server request memory limiter for all server protocols
77        let server_memory_limiter = ServerMemoryLimiter::new(
78            feopts.max_in_flight_write_bytes.as_bytes(),
79            feopts.write_bytes_exhausted_policy,
80        );
81
82        Self {
83            opts,
84            instance,
85            grpc_server_builder: None,
86            http_server_builder: None,
87            plugins,
88            flight_handler: None,
89            internal_flight_handler: None,
90            server_memory_limiter,
91        }
92    }
93
94    pub fn grpc_server_builder(
95        &self,
96        opts: &GrpcOptions,
97        request_memory_limiter: ServerMemoryLimiter,
98    ) -> Result<GrpcServerBuilder> {
99        let builder = GrpcServerBuilder::new(opts.as_config(), common_runtime::global_runtime())
100            .with_memory_limiter(request_memory_limiter)
101            .with_tls_config(opts.tls.clone())
102            .context(error::InvalidTlsConfigSnafu)?;
103        Ok(builder)
104    }
105
106    pub fn http_server_builder(
107        &self,
108        opts: &FrontendOptions,
109        request_memory_limiter: ServerMemoryLimiter,
110    ) -> HttpServerBuilder {
111        let mut builder = HttpServerBuilder::new(effective_http_options(opts))
112            .with_batching_protocols(opts.table_batcher_options().protocols.clone())
113            .with_logical_batching_protocols(opts.logical_batcher_options().protocols)
114            .with_memory_limiter(request_memory_limiter)
115            .with_sql_handler(self.instance.clone());
116
117        let validator = self.plugins.get::<LogValidatorRef>();
118        let ingest_interceptor = self.plugins.get::<LogIngestInterceptorRef<ServerError>>();
119        builder =
120            builder.with_log_ingest_handler(self.instance.clone(), validator, ingest_interceptor);
121        builder = builder.with_logs_handler(self.instance.clone());
122
123        if let Some(user_provider) = self.plugins.get::<UserProviderRef>() {
124            builder = builder.with_user_provider(user_provider);
125        }
126
127        if opts.opentsdb.enable {
128            builder = builder.with_opentsdb_handler(self.instance.clone());
129        }
130
131        if opts.influxdb.enable {
132            builder = builder.with_influxdb_handler(self.instance.clone());
133        }
134
135        let prom_store = effective_prom_store_options(opts);
136        if prom_store.enable {
137            let pending_rows_batcher = opts
138                .logical_batcher_options()
139                .protocols
140                .contains(&BatchingProtocol::Prom)
141                .then(|| self.instance.logical_batcher().cloned())
142                .flatten();
143            builder = builder
144                .with_prom_handler(
145                    self.instance.clone(),
146                    Some(self.instance.clone()),
147                    opts.prom_store.with_metric_engine,
148                    opts.prom_store.prom_validation_mode,
149                    pending_rows_batcher,
150                )
151                .with_prometheus_handler(self.instance.clone());
152        }
153
154        if opts.otlp.enable {
155            builder = builder
156                .with_otlp_handler(self.instance.clone(), opts.prom_store.with_metric_engine);
157        }
158
159        if opts.jaeger.enable {
160            builder = builder.with_jaeger_handler(self.instance.clone());
161        }
162
163        builder = builder.with_dashboard_handler(self.instance.clone());
164
165        if let Some(configurator) = self.plugins.get::<RouterConfigurator>() {
166            info!("Adding extra router from plugins");
167            builder = builder.with_extra_router(configurator.router());
168        }
169
170        builder.add_layer(axum::middleware::from_fn_with_state(
171            self.instance.clone(),
172            async move |State(state): State<Arc<Instance>>, request: Request, next: Next| {
173                if state.is_suspended() {
174                    return ErrorResponse::from_error(servers::error::SuspendedSnafu.build())
175                        .into_response();
176                }
177                next.run(request).await
178            },
179        ))
180    }
181
182    pub fn with_grpc_server_builder(self, builder: GrpcServerBuilder) -> Self {
183        Self {
184            grpc_server_builder: Some(builder),
185            ..self
186        }
187    }
188
189    pub fn with_http_server_builder(self, builder: HttpServerBuilder) -> Self {
190        Self {
191            http_server_builder: Some(builder),
192            ..self
193        }
194    }
195
196    pub fn with_flight_handler(self, flight_handler: FlightCraftRef) -> Self {
197        Self {
198            flight_handler: Some(flight_handler),
199            ..self
200        }
201    }
202
203    pub fn with_internal_flight_handler(self, flight_handler: FlightCraftRef) -> Self {
204        Self {
205            internal_flight_handler: Some(flight_handler),
206            ..self
207        }
208    }
209
210    fn build_grpc_server(
211        &mut self,
212        grpc: &GrpcOptions,
213        meta_client: &Option<MetaClientOptions>,
214        name: Option<String>,
215        external: bool,
216        request_memory_limiter: ServerMemoryLimiter,
217    ) -> Result<GrpcServer> {
218        let builder = if let Some(builder) = self.grpc_server_builder.take() {
219            builder
220        } else {
221            self.grpc_server_builder(grpc, request_memory_limiter)?
222        };
223
224        let user_provider = if external {
225            self.plugins.get::<UserProviderRef>()
226        } else {
227            // skip authentication for internal grpc port
228            None
229        };
230
231        // Determine whether it is Standalone or Distributed mode based on whether the meta client is configured.
232        let runtime = if meta_client.is_none() {
233            Some(builder.runtime().clone())
234        } else {
235            None
236        };
237
238        let greptime_request_handler = GreptimeRequestHandler::new(
239            self.instance.clone(),
240            user_provider.clone(),
241            runtime,
242            grpc.flight_compression,
243        );
244
245        let default_flight_handler = Arc::new(greptime_request_handler.clone()) as FlightCraftRef;
246        let flight_handler = if external {
247            self.flight_handler
248                .clone()
249                .unwrap_or(default_flight_handler)
250        } else {
251            self.internal_flight_handler
252                .clone()
253                .unwrap_or(default_flight_handler)
254        };
255
256        let grpc_server = builder
257            .name(name)
258            .database_handler(greptime_request_handler.clone())
259            .prometheus_handler(self.instance.clone(), user_provider.clone())
260            .otel_arrow_handler(OtelArrowServiceHandler::new(
261                self.instance.clone(),
262                user_provider.clone(),
263            ))
264            .flight_handler(flight_handler)
265            .add_layer(axum::middleware::from_fn_with_state(
266                self.instance.clone(),
267                move |State(state): State<Arc<Instance>>, mut request: Request, next: Next| async move {
268                    if state.is_suspended() {
269                        let status = Status::from(servers::error::SuspendedSnafu.build());
270                        return status.into_http();
271                    }
272                    // The listener owns this marker; clients cannot set request extensions.
273                    if !external {
274                        request.extensions_mut().insert(Channel::Internal);
275                    }
276                    next.run(request).await
277                },
278            ));
279
280        let grpc_server = if !external {
281            let frontend_grpc_handler =
282                FrontendGrpcHandler::new(self.instance.process_manager().clone());
283            grpc_server.frontend_grpc_handler(frontend_grpc_handler)
284        } else {
285            grpc_server
286        }
287        .build();
288
289        Ok(grpc_server)
290    }
291
292    fn build_http_server(
293        &mut self,
294        opts: &FrontendOptions,
295        toml: String,
296        request_memory_limiter: ServerMemoryLimiter,
297    ) -> Result<(HttpServer, Option<HttpServer>)> {
298        let builder = if let Some(builder) = self.http_server_builder.take() {
299            builder
300        } else {
301            self.http_server_builder(opts, request_memory_limiter)
302        };
303
304        // The API server is configured entirely under `[http]` (`enable_api_server`,
305        // `api_server_host`, `api_server_port`) and shares every other `[http]`
306        // option with the main server.
307        let (internal, api) = builder
308            .with_metrics_handler(MetricsHandler)
309            .with_greptime_config_options(toml)
310            .build_servers();
311        Ok((internal, api))
312    }
313
314    pub fn build(mut self) -> Result<ServerHandlers> {
315        let opts = self.opts.clone();
316        let instance = self.instance.clone();
317
318        let toml = opts.to_toml().context(TomlFormatSnafu)?;
319        let opts: FrontendOptions = opts.into();
320
321        let handlers = ServerHandlers::default();
322
323        let user_provider = self.plugins.get::<UserProviderRef>();
324
325        {
326            // Always init GRPC server
327            let grpc_addr = parse_addr(&opts.grpc.bind_addr)?;
328            let grpc_server = self.build_grpc_server(
329                &opts.grpc,
330                &opts.meta_client,
331                None,
332                true,
333                self.server_memory_limiter.clone(),
334            )?;
335            handlers.insert((Box::new(grpc_server), grpc_addr));
336        }
337
338        if let Some(internal_grpc) = &opts.internal_grpc {
339            // Always init Internal GRPC server
340            let grpc_addr = parse_addr(&internal_grpc.bind_addr)?;
341            let grpc_server = self.build_grpc_server(
342                internal_grpc,
343                &opts.meta_client,
344                Some("INTERNAL_GRPC_SERVER".to_string()),
345                false,
346                self.server_memory_limiter.clone(),
347            )?;
348            handlers.insert((Box::new(grpc_server), grpc_addr));
349        }
350
351        {
352            // Always init the internal/full HTTP server (v1 + internal interfaces)
353            // and, when enabled, the dedicated HTTP API server (v1 + dashboard only).
354            let http_options = &opts.http;
355            let http_addr = parse_addr(&http_options.addr)?;
356            let (http_server, http_api_server) =
357                self.build_http_server(&opts, toml, self.server_memory_limiter.clone())?;
358            handlers.insert((Box::new(http_server), http_addr));
359
360            if let Some(http_api_server) = http_api_server {
361                let http_api_addr = parse_addr(&http_options.api_server_addr)?;
362                info!("HTTP API server is enabled at {}", http_api_addr);
363                handlers.insert((Box::new(http_api_server), http_api_addr));
364            }
365        }
366
367        let table_batcher = opts.table_batcher_options();
368        let batching_enabled = table_batcher.pending_rows_batching_enabled();
369        let mysql_batching =
370            batching_enabled && table_batcher.protocols.contains(&BatchingProtocol::Mysql);
371        let postgres_batching = batching_enabled
372            && table_batcher
373                .protocols
374                .contains(&BatchingProtocol::Postgres);
375
376        if opts.mysql.enable {
377            // Init MySQL server
378            let opts = &opts.mysql;
379            let mysql_addr = parse_addr(&opts.addr)?;
380
381            let tls_server_config = Arc::new(
382                ReloadableTlsServerConfig::try_new(opts.tls.clone()).context(StartServerSnafu)?,
383            );
384
385            // will not watch if watch is disabled in tls option
386            maybe_watch_server_tls_config(tls_server_config.clone()).context(StartServerSnafu)?;
387
388            let mysql_server = MysqlServer::create_server(
389                common_runtime::global_runtime(),
390                Arc::new(MysqlSpawnRef::new(instance.clone(), user_provider.clone())),
391                Arc::new(
392                    MysqlSpawnConfig::new(
393                        opts.tls.should_force_tls(),
394                        tls_server_config,
395                        opts.keep_alive.as_secs(),
396                        opts.reject_no_database.unwrap_or(false),
397                        opts.prepared_stmt_cache_size,
398                    )
399                    .with_batching_enabled(mysql_batching),
400                ),
401                Some(instance.process_manager().clone()),
402            );
403            handlers.insert((mysql_server, mysql_addr));
404        }
405
406        if opts.postgres.enable {
407            // Init PosgresSQL Server
408            let opts = &opts.postgres;
409            let pg_addr = parse_addr(&opts.addr)?;
410
411            let tls_server_config = Arc::new(
412                ReloadableTlsServerConfig::try_new(opts.tls.clone()).context(StartServerSnafu)?,
413            );
414
415            maybe_watch_server_tls_config(tls_server_config.clone()).context(StartServerSnafu)?;
416
417            let pg_server = Box::new(
418                PostgresServer::new(
419                    instance.clone(),
420                    opts.tls.should_force_tls(),
421                    tls_server_config,
422                    opts.keep_alive.as_secs(),
423                    common_runtime::global_runtime(),
424                    user_provider.clone(),
425                    Some(self.instance.process_manager().clone()),
426                )
427                .with_batching_enabled(postgres_batching),
428            ) as Box<dyn Server>;
429
430            handlers.insert((pg_server, pg_addr));
431        }
432
433        Ok(handlers)
434    }
435}
436
437/// Selected shared controls override legacy Prom batching knobs, not protocol behavior.
438fn effective_prom_store_options(opts: &FrontendOptions) -> PromStoreOptions {
439    let mut prom_store = opts.prom_store.clone();
440    let shared = opts.logical_batcher_options();
441    if shared.protocols.contains(&BatchingProtocol::Prom) {
442        prom_store.pending_rows_flush_interval = shared.pending_rows_flush_interval;
443        prom_store.max_batch_rows = shared.max_batch_rows;
444        prom_store.max_concurrent_flushes = shared.max_concurrent_flushes;
445        prom_store.worker_channel_capacity = shared.worker_channel_capacity;
446        prom_store.max_inflight_requests = shared.max_inflight_requests;
447        prom_store.flow_notification_queue_capacity = shared.flow_notification_queue_capacity;
448    } else {
449        prom_store.pending_rows_flush_interval = Duration::ZERO;
450    }
451    prom_store
452}
453
454fn effective_http_options(opts: &FrontendOptions) -> HttpOptions {
455    effective_http_options_with_sync(opts, pending_rows_batch_sync_enabled())
456}
457
458fn effective_http_options_with_sync(opts: &FrontendOptions, batch_sync: bool) -> HttpOptions {
459    let mut http = opts.http.clone();
460    let prom_store = effective_prom_store_options(opts);
461    let shared = opts.table_batcher_options();
462    let common_enabled = batch_sync
463        && shared.pending_rows_batching_enabled()
464        && shared.protocols.iter().any(|protocol| {
465            !matches!(
466                protocol,
467                BatchingProtocol::Mysql | BatchingProtocol::Postgres
468            ) && (*protocol != BatchingProtocol::Prom
469                || (prom_store.enable && !prom_store.with_metric_engine))
470        });
471    let common_interval = common_enabled.then_some(shared.pending_rows_flush_interval);
472    let prom_interval = (prom_store.pending_rows_batching_enabled() && batch_sync)
473        .then_some(prom_store.pending_rows_flush_interval);
474    let logical = opts.logical_batcher_options();
475    let otlp_interval = (batch_sync
476        && opts.otlp.enable
477        && opts.prom_store.with_metric_engine
478        && logical.protocols.contains(&BatchingProtocol::Otlp)
479        && logical.pending_rows_batching_enabled())
480    .then_some(logical.pending_rows_flush_interval);
481    let Some(flush_interval) = common_interval
482        .into_iter()
483        .chain(prom_interval)
484        .chain(otlp_interval)
485        .max()
486    else {
487        return http;
488    };
489    let fallback_timeout = flush_interval.saturating_add(Duration::from_secs(1));
490    if http.timeout.is_zero() || http.timeout > fallback_timeout {
491        return http;
492    }
493
494    let configured_timeout = http.timeout;
495    http.timeout = fallback_timeout;
496    warn!(
497        ?configured_timeout,
498        ?flush_interval,
499        ?fallback_timeout,
500        "HTTP request timeout is not longer than the pending-row timeout fallback; using the fallback"
501    );
502    http
503}
504
505fn parse_addr(addr: &str) -> Result<SocketAddr> {
506    addr.parse().context(error::ParseAddrSnafu { addr })
507}
508
509#[cfg(test)]
510mod tests {
511    use std::num::NonZeroUsize;
512    use std::sync::Arc;
513    use std::sync::atomic::{AtomicUsize, Ordering};
514    use std::time::Duration;
515
516    use api::v1::HealthCheckRequest;
517    use api::v1::health_check_client::HealthCheckClient;
518    use api::v1::meta::Role;
519    use arrow_flight::{FlightData, PutResult, Ticket};
520    use async_trait::async_trait;
521    use auth::{UserProviderRef, static_user_provider_from_option};
522    use client::{Client, Database};
523    use meta_client::client::MetaClientBuilder;
524    use servers::grpc::GRPC_SERVER;
525    use servers::grpc::flight::{FlightCraft, FlightCraftRef, TonicStream};
526    use tonic::{Code, Request, Response, Status, Streaming};
527
528    use crate::instance::builder::FrontendBuilder;
529    use crate::server::*;
530
531    #[tokio::test]
532    async fn test_logical_batcher_shared_without_prom_endpoint() {
533        use crate::service_config::BatcherOptions;
534        let mut options = FrontendOptions::default();
535        options.prom_store.enable = false;
536        options.pending_rows_batcher.logical_table = Some(BatcherOptions {
537            protocols: vec![BatchingProtocol::Otlp],
538            pending_rows_flush_interval: Duration::from_millis(5),
539            ..Default::default()
540        });
541        let meta_client = Arc::new(
542            MetaClientBuilder::new(0, Role::Frontend)
543                .enable_procedure()
544                .build(),
545        );
546        let instance = Arc::new(
547            FrontendBuilder::new_test(&options, meta_client)
548                .try_build()
549                .await
550                .unwrap(),
551        );
552        let services = Services::new(options.clone(), instance.clone(), Plugins::default());
553        let batcher = instance.logical_batcher().unwrap().clone();
554        instance.init_logical_batcher(&options);
555        assert!(Arc::ptr_eq(&batcher, instance.logical_batcher().unwrap()));
556        let weak = Arc::downgrade(&instance);
557        drop(services);
558        drop(instance);
559        assert!(
560            weak.upgrade().is_none(),
561            "batcher must not retain its schema owner"
562        );
563    }
564
565    #[test]
566    fn test_logical_batcher_http_timeout_and_prom_disable() {
567        use crate::service_config::pending_rows_batcher::BatcherOptions;
568        let mut opts = FrontendOptions::default();
569        opts.http.timeout = Duration::from_secs(1);
570        opts.prom_store.pending_rows_flush_interval = Duration::from_secs(2);
571        opts.pending_rows_batcher.logical_table = Some(BatcherOptions {
572            protocols: vec![BatchingProtocol::Otlp],
573            pending_rows_flush_interval: Duration::from_secs(5),
574            ..Default::default()
575        });
576        assert!(!effective_prom_store_options(&opts).pending_rows_batching_enabled());
577        // Both logical protocols follow the global acknowledgement policy,
578        // independently of enabling the Prom HTTP endpoint.
579        opts.prom_store.enable = false;
580        assert_eq!(
581            effective_http_options_with_sync(&opts, true).timeout,
582            Duration::from_secs(6)
583        );
584        assert_eq!(
585            effective_http_options_with_sync(&opts, false).timeout,
586            opts.http.timeout
587        );
588        opts.prom_store.with_metric_engine = false;
589        assert_eq!(
590            effective_http_options_with_sync(&opts, false).timeout,
591            opts.http.timeout
592        );
593        opts.prom_store.with_metric_engine = true;
594        opts.pending_rows_batcher
595            .logical_table
596            .as_mut()
597            .unwrap()
598            .protocols
599            .clear();
600        assert_eq!(
601            effective_http_options_with_sync(&opts, true).timeout,
602            opts.http.timeout
603        );
604    }
605
606    #[test]
607    fn test_effective_prom_batching_controls() {
608        // Only an enabled shared Prom selection replaces the legacy controls.
609        for (selected, shared_enabled, metric_engine, prom_enabled) in [
610            (true, true, true, true),
611            (false, true, true, true),
612            (true, false, true, true),
613            (true, true, false, true),
614            (true, true, true, false),
615        ] {
616            let mut opts = FrontendOptions::default();
617            opts.http.timeout = Duration::from_millis(1);
618            opts.prom_store.pending_rows_flush_interval = Duration::from_secs(2);
619            opts.prom_store.with_metric_engine = metric_engine;
620            opts.prom_store.enable = prom_enabled;
621            let shared = &mut opts.pending_rows_batcher.table;
622            shared.protocols = vec![if selected {
623                BatchingProtocol::Prom
624            } else {
625                BatchingProtocol::Influxdb
626            }];
627            shared.pending_rows_flush_interval = if shared_enabled {
628                Duration::from_secs(5)
629            } else {
630                Duration::ZERO
631            };
632            shared.max_batch_rows = 7;
633            shared.max_concurrent_flushes = 3;
634            shared.worker_channel_capacity = 11;
635            shared.max_inflight_requests = 13;
636            shared.flow_notification_queue_capacity = NonZeroUsize::new(17).unwrap();
637
638            let mut expected = opts.prom_store.clone();
639            if selected && shared_enabled {
640                expected.pending_rows_flush_interval = shared.pending_rows_flush_interval;
641                expected.max_batch_rows = shared.max_batch_rows;
642                expected.max_concurrent_flushes = shared.max_concurrent_flushes;
643                expected.worker_channel_capacity = shared.worker_channel_capacity;
644                expected.max_inflight_requests = shared.max_inflight_requests;
645                expected.flow_notification_queue_capacity = shared.flow_notification_queue_capacity;
646            }
647            let actual = effective_prom_store_options(&opts);
648            assert_eq!(actual, expected);
649            assert_eq!(
650                actual.pending_rows_batching_enabled(),
651                metric_engine && prom_enabled
652            );
653        }
654    }
655
656    #[test]
657    fn test_http_timeout_covers_synchronous_batchers() {
658        // Only synchronous batchers extend the HTTP timeout.
659        for (
660            protocols,
661            metric_engine,
662            batch_sync,
663            shared_secs,
664            legacy_secs,
665            timeout_secs,
666            expected_secs,
667        ) in [
668            (vec![BatchingProtocol::Prom], false, false, 5, 2, 1, 1),
669            (vec![BatchingProtocol::Mysql], false, false, 5, 0, 1, 1),
670            (vec![BatchingProtocol::Postgres], false, false, 5, 0, 1, 1),
671            (vec![BatchingProtocol::Mysql], false, true, 5, 0, 1, 1),
672            (vec![BatchingProtocol::Postgres], false, true, 5, 0, 1, 1),
673            (vec![BatchingProtocol::Prom], true, false, 5, 2, 1, 1),
674            (vec![BatchingProtocol::Prom], true, true, 5, 2, 1, 6),
675            (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 1, 1),
676            (vec![BatchingProtocol::Influxdb], true, true, 5, 8, 1, 9),
677            (vec![BatchingProtocol::Influxdb], true, true, 8, 5, 1, 9),
678            (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 0, 0),
679            (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 10, 10),
680            (vec![BatchingProtocol::Prom], false, false, 0, 2, 1, 1),
681            (vec![], true, false, 5, 2, 1, 1),
682            (vec![], true, true, 5, 2, 1, 3),
683        ] {
684            let mut opts = FrontendOptions::default();
685            opts.http.timeout = Duration::from_secs(timeout_secs);
686            opts.prom_store.with_metric_engine = metric_engine;
687            opts.prom_store.pending_rows_flush_interval = Duration::from_secs(legacy_secs);
688            opts.pending_rows_batcher.table.protocols = protocols;
689            opts.pending_rows_batcher.table.pending_rows_flush_interval =
690                Duration::from_secs(shared_secs);
691            assert_eq!(
692                effective_http_options_with_sync(&opts, batch_sync).timeout,
693                Duration::from_secs(expected_secs)
694            );
695        }
696    }
697
698    struct CountingFlightCraft {
699        inner: FlightCraftRef,
700        do_get_calls: AtomicUsize,
701        do_put_calls: AtomicUsize,
702    }
703
704    #[async_trait]
705    impl FlightCraft for CountingFlightCraft {
706        async fn do_get(
707            &self,
708            request: Request<Ticket>,
709        ) -> std::result::Result<Response<TonicStream<FlightData>>, Status> {
710            assert_eq!(
711                request.extensions().get::<Channel>(),
712                Some(&Channel::Internal)
713            );
714            self.do_get_calls.fetch_add(1, Ordering::SeqCst);
715            self.inner.do_get(request).await
716        }
717
718        async fn do_put(
719            &self,
720            request: Request<Streaming<FlightData>>,
721        ) -> std::result::Result<Response<TonicStream<PutResult>>, Status> {
722            assert_eq!(
723                request.extensions().get::<Channel>(),
724                Some(&Channel::Internal)
725            );
726            self.do_put_calls.fetch_add(1, Ordering::SeqCst);
727            self.inner.do_put(request).await
728        }
729    }
730
731    #[test]
732    fn test_effective_http_timeout_for_pending_rows() {
733        let cases = [
734            ("disabled timeout", 0, 5000, true, true, 0),
735            ("disabled prom store", 1000, 5000, false, true, 1000),
736            ("disabled metric engine", 1000, 5000, true, false, 1000),
737            ("disabled batching", 1000, 0, true, true, 1000),
738            ("timeout below flush interval", 4000, 5000, true, true, 6000),
739            (
740                "timeout equals flush interval",
741                5000,
742                5000,
743                true,
744                true,
745                6000,
746            ),
747            ("timeout below fallback", 5500, 5000, true, true, 6000),
748            ("timeout equals fallback", 6000, 5000, true, true, 6000),
749            ("timeout above fallback", 7000, 5000, true, true, 7000),
750        ];
751
752        for (name, timeout, flush_interval, enable, with_metric_engine, expected) in cases {
753            let mut opts = FrontendOptions::default();
754            opts.http.timeout = Duration::from_millis(timeout);
755            opts.prom_store.pending_rows_flush_interval = Duration::from_millis(flush_interval);
756            opts.prom_store.enable = enable;
757            opts.prom_store.with_metric_engine = with_metric_engine;
758
759            assert_eq!(
760                Duration::from_millis(expected),
761                effective_http_options_with_sync(&opts, true).timeout,
762                "{name}"
763            );
764        }
765    }
766
767    #[test]
768    fn test_effective_http_timeout_skips_fallback_in_async_batch_mode() {
769        // With `PENDING_ROWS_BATCH_SYNC=false`, submissions return right after
770        // enqueue and no request waits for a pending-row flush, so the
771        // timeout must not be raised.
772        let mut opts = FrontendOptions::default();
773        opts.http.timeout = Duration::from_millis(1000);
774        opts.prom_store.pending_rows_flush_interval = Duration::from_millis(5000);
775
776        assert_eq!(
777            Duration::from_millis(1000),
778            effective_http_options_with_sync(&opts, false).timeout,
779        );
780        assert_eq!(
781            Duration::from_millis(6000),
782            effective_http_options_with_sync(&opts, true).timeout,
783        );
784    }
785
786    #[test]
787    fn test_invalid_shared_batching_preserves_prom_store_options() {
788        type KnobMutator = fn(&mut FrontendOptions);
789        let cases: [KnobMutator; 5] = [
790            |opts| opts.pending_rows_batcher.table.max_concurrent_flushes = usize::MAX,
791            |opts| opts.pending_rows_batcher.table.worker_channel_capacity = usize::MAX,
792            |opts| opts.pending_rows_batcher.table.max_inflight_requests = usize::MAX,
793            |opts| {
794                opts.pending_rows_batcher
795                    .table
796                    .flow_notification_queue_capacity = NonZeroUsize::new(usize::MAX).unwrap()
797            },
798            |opts| opts.pending_rows_batcher.table.pending_rows_flush_interval = Duration::MAX,
799        ];
800        for invalidate in cases {
801            let mut opts = FrontendOptions::default();
802            opts.http.timeout = Duration::from_secs(1);
803            opts.prom_store.pending_rows_flush_interval = Duration::from_secs(5);
804            opts.pending_rows_batcher.table.protocols =
805                vec![BatchingProtocol::Prom, BatchingProtocol::Influxdb];
806            opts.pending_rows_batcher.table.pending_rows_flush_interval = Duration::from_secs(10);
807            invalidate(&mut opts);
808            assert!(
809                !opts
810                    .pending_rows_batcher
811                    .table
812                    .pending_rows_batching_enabled()
813            );
814            assert_eq!(opts.prom_store, effective_prom_store_options(&opts));
815            assert_eq!(
816                Duration::from_secs(6),
817                effective_http_options_with_sync(&opts, true).timeout
818            );
819            opts.prom_store.pending_rows_flush_interval = Duration::ZERO;
820            assert_eq!(
821                Duration::from_secs(1),
822                effective_http_options_with_sync(&opts, true).timeout
823            );
824        }
825    }
826
827    #[test]
828    fn test_effective_http_timeout_skips_fallback_when_batcher_disabled() {
829        // Mirrors the conditions under which `LogicalTablePendingRowsBatcher::try_new`
830        // returns `None`; in these cases no request can wait for a pending-row
831        // flush, so the timeout must not be raised.
832        type KnobMutator = fn(&mut FrontendOptions);
833        let cases: [(&str, KnobMutator); 9] = [
834            ("oversized flush concurrency", |opts| {
835                opts.prom_store.max_concurrent_flushes = usize::MAX
836            }),
837            ("oversized worker channel", |opts| {
838                opts.prom_store.worker_channel_capacity = usize::MAX
839            }),
840            ("oversized inflight limit", |opts| {
841                opts.prom_store.max_inflight_requests = usize::MAX
842            }),
843            ("oversized flow queue", |opts| {
844                opts.prom_store.flow_notification_queue_capacity =
845                    std::num::NonZeroUsize::new(usize::MAX).unwrap()
846            }),
847            ("unrepresentable deadline", |opts| {
848                opts.prom_store.pending_rows_flush_interval = Duration::MAX
849            }),
850            ("zero max_batch_rows", |opts| {
851                opts.prom_store.max_batch_rows = 0
852            }),
853            ("zero max_concurrent_flushes", |opts| {
854                opts.prom_store.max_concurrent_flushes = 0
855            }),
856            ("zero worker_channel_capacity", |opts| {
857                opts.prom_store.worker_channel_capacity = 0
858            }),
859            ("zero max_inflight_requests", |opts| {
860                opts.prom_store.max_inflight_requests = 0
861            }),
862        ];
863
864        for (name, disable_batcher) in cases {
865            let mut opts = FrontendOptions::default();
866            opts.http.timeout = Duration::from_millis(1000);
867            opts.prom_store.pending_rows_flush_interval = Duration::from_millis(5000);
868            disable_batcher(&mut opts);
869
870            assert_eq!(
871                Duration::from_millis(1000),
872                effective_http_options_with_sync(&opts, true).timeout,
873                "{name}"
874            );
875        }
876    }
877
878    #[tokio::test]
879    async fn test_database_sql_authentication_differs_between_public_and_internal_grpc() {
880        let options = FrontendOptions {
881            http: HttpOptions {
882                addr: "127.0.0.1:0".to_string(),
883                ..Default::default()
884            },
885            grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:0"),
886            internal_grpc: Some(GrpcOptions::default().with_bind_addr("127.0.0.1:0")),
887            mysql: crate::service_config::MysqlOptions {
888                enable: false,
889                ..Default::default()
890            },
891            postgres: crate::service_config::PostgresOptions {
892                enable: false,
893                ..Default::default()
894            },
895            ..Default::default()
896        };
897        let meta_client = Arc::new(
898            MetaClientBuilder::new(0, Role::Frontend)
899                .enable_procedure()
900                .build(),
901        );
902        let instance = Arc::new(
903            FrontendBuilder::new_test(&options, meta_client)
904                .try_build()
905                .await
906                .unwrap(),
907        );
908        let plugins = Plugins::new();
909        let provider =
910            static_user_provider_from_option("static_user_provider:cmd:greptime=greptime").unwrap();
911        plugins.insert::<UserProviderRef>(Arc::new(provider));
912        let public_flight_handler = Arc::new(GreptimeRequestHandler::new(
913            instance.clone(),
914            plugins.get::<UserProviderRef>(),
915            None,
916            options.grpc.flight_compression,
917        )) as FlightCraftRef;
918        let internal_flight_handler = Arc::new(CountingFlightCraft {
919            inner: Arc::new(GreptimeRequestHandler::new(
920                instance.clone(),
921                None,
922                None,
923                options.grpc.flight_compression,
924            )),
925            do_get_calls: AtomicUsize::new(0),
926            do_put_calls: AtomicUsize::new(0),
927        });
928        let internal_flight_handler_ref = internal_flight_handler.clone() as FlightCraftRef;
929        let mut services = Services::new(options, instance, plugins)
930            .with_flight_handler(public_flight_handler)
931            .with_internal_flight_handler(internal_flight_handler_ref)
932            .build()
933            .unwrap();
934
935        services.start_all().await.unwrap();
936        let public_addr = services.addr(GRPC_SERVER).unwrap();
937        let internal_addr = services.addr("INTERNAL_GRPC_SERVER").unwrap();
938        let public_database = Database::new(
939            "greptime",
940            "public",
941            Client::with_urls([public_addr.to_string()]),
942        );
943        let internal_database = Database::new(
944            "greptime",
945            "public",
946            Client::with_urls([internal_addr.to_string()]),
947        );
948
949        let internal_result = internal_database.sql("SELECT 1").await;
950        let put_result = internal_database
951            .do_put(Box::pin(futures::stream::empty()))
952            .await;
953        let public_result = public_database.sql("SELECT 1").await;
954
955        services.shutdown_all().await.unwrap();
956
957        assert!(internal_result.is_ok());
958        assert!(put_result.is_ok());
959        assert_eq!(
960            1,
961            internal_flight_handler.do_get_calls.load(Ordering::SeqCst)
962        );
963        assert_eq!(
964            1,
965            internal_flight_handler.do_put_calls.load(Ordering::SeqCst)
966        );
967        assert_eq!(
968            Some(Code::Unauthenticated),
969            public_result
970                .as_ref()
971                .err()
972                .and_then(|err| err.tonic_code())
973        );
974    }
975
976    #[tokio::test]
977    async fn test_services_builder_health_check_is_reachable() {
978        // Arrange
979        let options = FrontendOptions {
980            http: HttpOptions {
981                addr: "127.0.0.1:0".to_string(),
982                ..Default::default()
983            },
984            grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:0"),
985            mysql: crate::service_config::MysqlOptions {
986                enable: false,
987                ..Default::default()
988            },
989            postgres: crate::service_config::PostgresOptions {
990                enable: false,
991                ..Default::default()
992            },
993            ..Default::default()
994        };
995        let meta_client = Arc::new(
996            MetaClientBuilder::new(0, Role::Frontend)
997                .enable_procedure()
998                .build(),
999        );
1000        let instance = Arc::new(
1001            FrontendBuilder::new_test(&options, meta_client)
1002                .try_build()
1003                .await
1004                .unwrap(),
1005        );
1006        let mut services = Services::new(options, instance, Default::default())
1007            .build()
1008            .unwrap();
1009
1010        // Act
1011        services.start_all().await.unwrap();
1012        let addr = services.addr(GRPC_SERVER).unwrap();
1013        let health_check = HealthCheckClient::connect(format!("http://{addr}"))
1014            .await
1015            .unwrap()
1016            .health_check(HealthCheckRequest {})
1017            .await;
1018        services.shutdown_all().await.unwrap();
1019
1020        // Assert
1021        assert!(health_check.is_ok());
1022    }
1023}