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 mut 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        // Browsers never talk to the internal server.
224        if external && grpc.enable_cors {
225            builder = builder.with_cors(grpc.cors_allowed_origins.clone());
226        }
227
228        let user_provider = if external {
229            self.plugins.get::<UserProviderRef>()
230        } else {
231            // skip authentication for internal grpc port
232            None
233        };
234
235        // Determine whether it is Standalone or Distributed mode based on whether the meta client is configured.
236        let runtime = if meta_client.is_none() {
237            Some(builder.runtime().clone())
238        } else {
239            None
240        };
241
242        let greptime_request_handler = GreptimeRequestHandler::new(
243            self.instance.clone(),
244            user_provider.clone(),
245            runtime,
246            grpc.flight_compression,
247        );
248
249        let default_flight_handler = Arc::new(greptime_request_handler.clone()) as FlightCraftRef;
250        let flight_handler = if external {
251            self.flight_handler
252                .clone()
253                .unwrap_or(default_flight_handler)
254        } else {
255            self.internal_flight_handler
256                .clone()
257                .unwrap_or(default_flight_handler)
258        };
259
260        let grpc_server = builder
261            .name(name)
262            .database_handler(greptime_request_handler.clone())
263            .prometheus_handler(self.instance.clone(), user_provider.clone())
264            .otel_arrow_handler(OtelArrowServiceHandler::new(
265                self.instance.clone(),
266                user_provider.clone(),
267            ))
268            .flight_handler(flight_handler)
269            .add_layer(axum::middleware::from_fn_with_state(
270                self.instance.clone(),
271                move |State(state): State<Arc<Instance>>, mut request: Request, next: Next| async move {
272                    if state.is_suspended() {
273                        let status = Status::from(servers::error::SuspendedSnafu.build());
274                        return status.into_http();
275                    }
276                    // The listener owns this marker; clients cannot set request extensions.
277                    if !external {
278                        request.extensions_mut().insert(Channel::Internal);
279                    }
280                    next.run(request).await
281                },
282            ));
283
284        let grpc_server = if !external {
285            let frontend_grpc_handler =
286                FrontendGrpcHandler::new(self.instance.process_manager().clone());
287            grpc_server.frontend_grpc_handler(frontend_grpc_handler)
288        } else {
289            grpc_server
290        }
291        .build();
292
293        Ok(grpc_server)
294    }
295
296    fn build_http_server(
297        &mut self,
298        opts: &FrontendOptions,
299        toml: String,
300        request_memory_limiter: ServerMemoryLimiter,
301    ) -> Result<(HttpServer, Option<HttpServer>)> {
302        let builder = if let Some(builder) = self.http_server_builder.take() {
303            builder
304        } else {
305            self.http_server_builder(opts, request_memory_limiter)
306        };
307
308        // The API server is configured entirely under `[http]` (`enable_api_server`,
309        // `api_server_host`, `api_server_port`) and shares every other `[http]`
310        // option with the main server.
311        let (internal, api) = builder
312            .with_metrics_handler(MetricsHandler)
313            .with_greptime_config_options(toml)
314            .build_servers();
315        Ok((internal, api))
316    }
317
318    pub fn build(mut self) -> Result<ServerHandlers> {
319        let opts = self.opts.clone();
320        let instance = self.instance.clone();
321
322        let toml = opts.to_toml().context(TomlFormatSnafu)?;
323        let opts: FrontendOptions = opts.into();
324
325        let handlers = ServerHandlers::default();
326
327        let user_provider = self.plugins.get::<UserProviderRef>();
328
329        {
330            // Always init GRPC server
331            let grpc_addr = parse_addr(&opts.grpc.bind_addr)?;
332            let grpc_server = self.build_grpc_server(
333                &opts.grpc,
334                &opts.meta_client,
335                None,
336                true,
337                self.server_memory_limiter.clone(),
338            )?;
339            handlers.insert((Box::new(grpc_server), grpc_addr));
340        }
341
342        if let Some(internal_grpc) = &opts.internal_grpc {
343            // Always init Internal GRPC server
344            let grpc_addr = parse_addr(&internal_grpc.bind_addr)?;
345            let grpc_server = self.build_grpc_server(
346                internal_grpc,
347                &opts.meta_client,
348                Some("INTERNAL_GRPC_SERVER".to_string()),
349                false,
350                self.server_memory_limiter.clone(),
351            )?;
352            handlers.insert((Box::new(grpc_server), grpc_addr));
353        }
354
355        {
356            // Always init the internal/full HTTP server (v1 + internal interfaces)
357            // and, when enabled, the dedicated HTTP API server (v1 + dashboard only).
358            let http_options = &opts.http;
359            let http_addr = parse_addr(&http_options.addr)?;
360            let (http_server, http_api_server) =
361                self.build_http_server(&opts, toml, self.server_memory_limiter.clone())?;
362            handlers.insert((Box::new(http_server), http_addr));
363
364            if let Some(http_api_server) = http_api_server {
365                let http_api_addr = parse_addr(&http_options.api_server_addr)?;
366                info!("HTTP API server is enabled at {}", http_api_addr);
367                handlers.insert((Box::new(http_api_server), http_api_addr));
368            }
369        }
370
371        let table_batcher = opts.table_batcher_options();
372        let batching_enabled = table_batcher.pending_rows_batching_enabled();
373        let mysql_batching =
374            batching_enabled && table_batcher.protocols.contains(&BatchingProtocol::Mysql);
375        let postgres_batching = batching_enabled
376            && table_batcher
377                .protocols
378                .contains(&BatchingProtocol::Postgres);
379
380        if opts.mysql.enable {
381            // Init MySQL server
382            let opts = &opts.mysql;
383            let mysql_addr = parse_addr(&opts.addr)?;
384
385            let tls_server_config = Arc::new(
386                ReloadableTlsServerConfig::try_new(opts.tls.clone()).context(StartServerSnafu)?,
387            );
388
389            // will not watch if watch is disabled in tls option
390            maybe_watch_server_tls_config(tls_server_config.clone()).context(StartServerSnafu)?;
391
392            let mysql_server = MysqlServer::create_server(
393                common_runtime::global_runtime(),
394                Arc::new(MysqlSpawnRef::new(instance.clone(), user_provider.clone())),
395                Arc::new(
396                    MysqlSpawnConfig::new(
397                        opts.tls.should_force_tls(),
398                        tls_server_config,
399                        opts.keep_alive.as_secs(),
400                        opts.reject_no_database.unwrap_or(false),
401                        opts.prepared_stmt_cache_size,
402                    )
403                    .with_batching_enabled(mysql_batching),
404                ),
405                Some(instance.process_manager().clone()),
406            );
407            handlers.insert((mysql_server, mysql_addr));
408        }
409
410        if opts.postgres.enable {
411            // Init PosgresSQL Server
412            let with_metric_engine = effective_prom_store_options(&opts).with_metric_engine;
413            let opts = &opts.postgres;
414            let pg_addr = parse_addr(&opts.addr)?;
415
416            let tls_server_config = Arc::new(
417                ReloadableTlsServerConfig::try_new(opts.tls.clone()).context(StartServerSnafu)?,
418            );
419
420            maybe_watch_server_tls_config(tls_server_config.clone()).context(StartServerSnafu)?;
421
422            let pg_server = Box::new(
423                PostgresServer::new(
424                    instance.clone(),
425                    instance.clone(),
426                    opts.tls.should_force_tls(),
427                    tls_server_config,
428                    opts.keep_alive.as_secs(),
429                    common_runtime::global_runtime(),
430                    user_provider.clone(),
431                    Some(self.instance.process_manager().clone()),
432                )
433                .with_influxdb_handler(instance.clone())
434                .with_otlp_handler(instance.clone(), with_metric_engine)
435                .with_memory_limiter(self.server_memory_limiter.clone())
436                .with_batching_enabled(postgres_batching),
437            ) as Box<dyn Server>;
438
439            handlers.insert((pg_server, pg_addr));
440        }
441
442        Ok(handlers)
443    }
444}
445
446/// Selected shared controls override legacy Prom batching knobs, not protocol behavior.
447fn effective_prom_store_options(opts: &FrontendOptions) -> PromStoreOptions {
448    let mut prom_store = opts.prom_store.clone();
449    let shared = opts.logical_batcher_options();
450    if shared.protocols.contains(&BatchingProtocol::Prom) {
451        prom_store.pending_rows_flush_interval = shared.pending_rows_flush_interval;
452        prom_store.max_batch_rows = shared.max_batch_rows;
453        prom_store.max_concurrent_flushes = shared.max_concurrent_flushes;
454        prom_store.worker_channel_capacity = shared.worker_channel_capacity;
455        prom_store.max_inflight_requests = shared.max_inflight_requests;
456        prom_store.flow_notification_queue_capacity = shared.flow_notification_queue_capacity;
457    } else {
458        prom_store.pending_rows_flush_interval = Duration::ZERO;
459    }
460    prom_store
461}
462
463/// Resolves HTTP options, allowing synchronous batches enough time to flush.
464pub fn effective_http_options(opts: &FrontendOptions) -> HttpOptions {
465    effective_http_options_with_sync(opts, pending_rows_batch_sync_enabled())
466}
467
468fn effective_http_options_with_sync(opts: &FrontendOptions, batch_sync: bool) -> HttpOptions {
469    let mut http = opts.http.clone();
470    let prom_store = effective_prom_store_options(opts);
471    let shared = opts.table_batcher_options();
472    let common_enabled = batch_sync
473        && shared.pending_rows_batching_enabled()
474        && shared.protocols.iter().any(|protocol| {
475            !matches!(
476                protocol,
477                BatchingProtocol::Mysql | BatchingProtocol::Postgres
478            ) && (*protocol != BatchingProtocol::Prom
479                || (prom_store.enable && !prom_store.with_metric_engine))
480        });
481    let common_interval = common_enabled.then_some(shared.pending_rows_flush_interval);
482    let prom_interval = (prom_store.pending_rows_batching_enabled() && batch_sync)
483        .then_some(prom_store.pending_rows_flush_interval);
484    let logical = opts.logical_batcher_options();
485    let otlp_interval = (batch_sync
486        && opts.otlp.enable
487        && opts.prom_store.with_metric_engine
488        && logical.protocols.contains(&BatchingProtocol::Otlp)
489        && logical.pending_rows_batching_enabled())
490    .then_some(logical.pending_rows_flush_interval);
491    let Some(flush_interval) = common_interval
492        .into_iter()
493        .chain(prom_interval)
494        .chain(otlp_interval)
495        .max()
496    else {
497        return http;
498    };
499    let fallback_timeout = flush_interval.saturating_add(Duration::from_secs(1));
500    if http.timeout.is_zero() || http.timeout > fallback_timeout {
501        return http;
502    }
503
504    let configured_timeout = http.timeout;
505    http.timeout = fallback_timeout;
506    warn!(
507        ?configured_timeout,
508        ?flush_interval,
509        ?fallback_timeout,
510        "HTTP request timeout is not longer than the pending-row timeout fallback; using the fallback"
511    );
512    http
513}
514
515fn parse_addr(addr: &str) -> Result<SocketAddr> {
516    addr.parse().context(error::ParseAddrSnafu { addr })
517}
518
519#[cfg(test)]
520mod tests {
521    use std::num::NonZeroUsize;
522    use std::sync::Arc;
523    use std::sync::atomic::{AtomicUsize, Ordering};
524    use std::time::Duration;
525
526    use api::v1::HealthCheckRequest;
527    use api::v1::health_check_client::HealthCheckClient;
528    use api::v1::meta::Role;
529    use arrow_flight::{FlightData, PutResult, Ticket};
530    use async_trait::async_trait;
531    use auth::{UserProviderRef, static_user_provider_from_option};
532    use client::{Client, Database};
533    use common_grpc::channel_manager::ChannelManager;
534    use meta_client::client::MetaClientBuilder;
535    use reqwest::header::{ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ORIGIN};
536    use servers::grpc::GRPC_SERVER;
537    use servers::grpc::flight::{FlightCraft, FlightCraftRef, TonicStream};
538    use tonic::{Code, Request, Response, Status, Streaming};
539
540    use crate::instance::builder::FrontendBuilder;
541    use crate::server::*;
542
543    #[tokio::test]
544    async fn test_logical_batcher_shared_without_prom_endpoint() {
545        use crate::service_config::BatcherOptions;
546        let mut options = FrontendOptions::default();
547        options.prom_store.enable = false;
548        options.pending_rows_batcher.logical_table = Some(BatcherOptions {
549            protocols: vec![BatchingProtocol::Otlp],
550            pending_rows_flush_interval: Duration::from_millis(5),
551            ..Default::default()
552        });
553        let meta_client = Arc::new(
554            MetaClientBuilder::new(0, Role::Frontend)
555                .enable_procedure()
556                .build(),
557        );
558        let instance = Arc::new(
559            FrontendBuilder::new_test(&options, meta_client)
560                .try_build()
561                .await
562                .unwrap(),
563        );
564        let mut disabled_options = options.clone();
565        disabled_options.otlp.enable = false;
566        instance.init_logical_batcher(&disabled_options);
567        assert!(instance.logical_batcher().is_none());
568
569        let services = Services::new(options.clone(), instance.clone(), Plugins::default());
570        let batcher = instance.logical_batcher().unwrap().clone();
571        instance.init_logical_batcher(&options);
572        assert!(Arc::ptr_eq(&batcher, instance.logical_batcher().unwrap()));
573        let weak = Arc::downgrade(&instance);
574        drop(services);
575        drop(instance);
576        assert!(
577            weak.upgrade().is_none(),
578            "batcher must not retain its schema owner"
579        );
580    }
581
582    #[test]
583    fn test_logical_batcher_http_timeout_and_prom_disable() {
584        use crate::service_config::pending_rows_batcher::BatcherOptions;
585        let mut opts = FrontendOptions::default();
586        opts.http.timeout = Duration::from_secs(1);
587        opts.prom_store.pending_rows_flush_interval = Duration::from_secs(2);
588        opts.pending_rows_batcher.logical_table = Some(BatcherOptions {
589            protocols: vec![BatchingProtocol::Otlp],
590            pending_rows_flush_interval: Duration::from_secs(5),
591            ..Default::default()
592        });
593        assert!(!effective_prom_store_options(&opts).pending_rows_batching_enabled());
594        // Both logical protocols follow the global acknowledgement policy,
595        // independently of enabling the Prom HTTP endpoint.
596        opts.prom_store.enable = false;
597        assert_eq!(
598            effective_http_options_with_sync(&opts, true).timeout,
599            Duration::from_secs(6)
600        );
601        assert_eq!(
602            effective_http_options_with_sync(&opts, false).timeout,
603            opts.http.timeout
604        );
605        opts.prom_store.with_metric_engine = false;
606        assert_eq!(
607            effective_http_options_with_sync(&opts, false).timeout,
608            opts.http.timeout
609        );
610        opts.prom_store.with_metric_engine = true;
611        opts.pending_rows_batcher
612            .logical_table
613            .as_mut()
614            .unwrap()
615            .protocols
616            .clear();
617        assert_eq!(
618            effective_http_options_with_sync(&opts, true).timeout,
619            opts.http.timeout
620        );
621    }
622
623    #[test]
624    fn test_effective_prom_batching_controls() {
625        // Only an enabled shared Prom selection replaces the legacy controls.
626        for (selected, shared_enabled, metric_engine, prom_enabled) in [
627            (true, true, true, true),
628            (false, true, true, true),
629            (true, false, true, true),
630            (true, true, false, true),
631            (true, true, true, false),
632        ] {
633            let mut opts = FrontendOptions::default();
634            opts.http.timeout = Duration::from_millis(1);
635            opts.prom_store.pending_rows_flush_interval = Duration::from_secs(2);
636            opts.prom_store.with_metric_engine = metric_engine;
637            opts.prom_store.enable = prom_enabled;
638            let shared = &mut opts.pending_rows_batcher.table;
639            shared.protocols = vec![if selected {
640                BatchingProtocol::Prom
641            } else {
642                BatchingProtocol::Influxdb
643            }];
644            shared.pending_rows_flush_interval = if shared_enabled {
645                Duration::from_secs(5)
646            } else {
647                Duration::ZERO
648            };
649            shared.max_batch_rows = 7;
650            shared.max_concurrent_flushes = 3;
651            shared.worker_channel_capacity = 11;
652            shared.max_inflight_requests = 13;
653            shared.flow_notification_queue_capacity = NonZeroUsize::new(17).unwrap();
654
655            let mut expected = opts.prom_store.clone();
656            if selected && shared_enabled {
657                expected.pending_rows_flush_interval = shared.pending_rows_flush_interval;
658                expected.max_batch_rows = shared.max_batch_rows;
659                expected.max_concurrent_flushes = shared.max_concurrent_flushes;
660                expected.worker_channel_capacity = shared.worker_channel_capacity;
661                expected.max_inflight_requests = shared.max_inflight_requests;
662                expected.flow_notification_queue_capacity = shared.flow_notification_queue_capacity;
663            }
664            let actual = effective_prom_store_options(&opts);
665            assert_eq!(actual, expected);
666            assert_eq!(
667                actual.pending_rows_batching_enabled(),
668                metric_engine && prom_enabled
669            );
670        }
671    }
672
673    #[test]
674    fn test_http_timeout_covers_synchronous_batchers() {
675        // Only synchronous batchers extend the HTTP timeout.
676        for (
677            protocols,
678            metric_engine,
679            batch_sync,
680            shared_secs,
681            legacy_secs,
682            timeout_secs,
683            expected_secs,
684        ) in [
685            (vec![BatchingProtocol::Prom], false, false, 5, 2, 1, 1),
686            (vec![BatchingProtocol::Mysql], false, false, 5, 0, 1, 1),
687            (vec![BatchingProtocol::Postgres], false, false, 5, 0, 1, 1),
688            (vec![BatchingProtocol::Mysql], false, true, 5, 0, 1, 1),
689            (vec![BatchingProtocol::Postgres], false, true, 5, 0, 1, 1),
690            (vec![BatchingProtocol::Prom], true, false, 5, 2, 1, 1),
691            (vec![BatchingProtocol::Prom], true, true, 5, 2, 1, 6),
692            (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 1, 1),
693            (vec![BatchingProtocol::Influxdb], true, true, 5, 8, 1, 9),
694            (vec![BatchingProtocol::Influxdb], true, true, 8, 5, 1, 9),
695            (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 0, 0),
696            (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 10, 10),
697            (vec![BatchingProtocol::Prom], false, false, 0, 2, 1, 1),
698            (vec![], true, false, 5, 2, 1, 1),
699            (vec![], true, true, 5, 2, 1, 3),
700        ] {
701            let mut opts = FrontendOptions::default();
702            opts.http.timeout = Duration::from_secs(timeout_secs);
703            opts.prom_store.with_metric_engine = metric_engine;
704            opts.prom_store.pending_rows_flush_interval = Duration::from_secs(legacy_secs);
705            opts.pending_rows_batcher.table.protocols = protocols;
706            opts.pending_rows_batcher.table.pending_rows_flush_interval =
707                Duration::from_secs(shared_secs);
708            assert_eq!(
709                effective_http_options_with_sync(&opts, batch_sync).timeout,
710                Duration::from_secs(expected_secs)
711            );
712        }
713    }
714
715    struct CountingFlightCraft {
716        inner: FlightCraftRef,
717        do_get_calls: AtomicUsize,
718        do_put_calls: AtomicUsize,
719    }
720
721    #[async_trait]
722    impl FlightCraft for CountingFlightCraft {
723        async fn do_get(
724            &self,
725            request: Request<Ticket>,
726        ) -> std::result::Result<Response<TonicStream<FlightData>>, Status> {
727            assert_eq!(
728                request.extensions().get::<Channel>(),
729                Some(&Channel::Internal)
730            );
731            self.do_get_calls.fetch_add(1, Ordering::SeqCst);
732            self.inner.do_get(request).await
733        }
734
735        async fn do_put(
736            &self,
737            request: Request<Streaming<FlightData>>,
738        ) -> std::result::Result<Response<TonicStream<PutResult>>, Status> {
739            assert_eq!(
740                request.extensions().get::<Channel>(),
741                Some(&Channel::Internal)
742            );
743            self.do_put_calls.fetch_add(1, Ordering::SeqCst);
744            self.inner.do_put(request).await
745        }
746    }
747
748    #[test]
749    fn test_effective_http_timeout_for_pending_rows() {
750        let cases = [
751            ("disabled timeout", 0, 5000, true, true, 0),
752            ("disabled prom store", 1000, 5000, false, true, 1000),
753            ("disabled metric engine", 1000, 5000, true, false, 1000),
754            ("disabled batching", 1000, 0, true, true, 1000),
755            ("timeout below flush interval", 4000, 5000, true, true, 6000),
756            (
757                "timeout equals flush interval",
758                5000,
759                5000,
760                true,
761                true,
762                6000,
763            ),
764            ("timeout below fallback", 5500, 5000, true, true, 6000),
765            ("timeout equals fallback", 6000, 5000, true, true, 6000),
766            ("timeout above fallback", 7000, 5000, true, true, 7000),
767        ];
768
769        for (name, timeout, flush_interval, enable, with_metric_engine, expected) in cases {
770            let mut opts = FrontendOptions::default();
771            opts.http.timeout = Duration::from_millis(timeout);
772            opts.prom_store.pending_rows_flush_interval = Duration::from_millis(flush_interval);
773            opts.prom_store.enable = enable;
774            opts.prom_store.with_metric_engine = with_metric_engine;
775
776            assert_eq!(
777                Duration::from_millis(expected),
778                effective_http_options_with_sync(&opts, true).timeout,
779                "{name}"
780            );
781        }
782    }
783
784    #[test]
785    fn test_effective_http_timeout_skips_fallback_in_async_batch_mode() {
786        // With `PENDING_ROWS_BATCH_SYNC=false`, submissions return right after
787        // enqueue and no request waits for a pending-row flush, so the
788        // timeout must not be raised.
789        let mut opts = FrontendOptions::default();
790        opts.http.timeout = Duration::from_millis(1000);
791        opts.prom_store.pending_rows_flush_interval = Duration::from_millis(5000);
792
793        assert_eq!(
794            Duration::from_millis(1000),
795            effective_http_options_with_sync(&opts, false).timeout,
796        );
797        assert_eq!(
798            Duration::from_millis(6000),
799            effective_http_options_with_sync(&opts, true).timeout,
800        );
801    }
802
803    #[test]
804    fn test_invalid_shared_batching_preserves_prom_store_options() {
805        type KnobMutator = fn(&mut FrontendOptions);
806        let cases: [KnobMutator; 5] = [
807            |opts| opts.pending_rows_batcher.table.max_concurrent_flushes = usize::MAX,
808            |opts| opts.pending_rows_batcher.table.worker_channel_capacity = usize::MAX,
809            |opts| opts.pending_rows_batcher.table.max_inflight_requests = usize::MAX,
810            |opts| {
811                opts.pending_rows_batcher
812                    .table
813                    .flow_notification_queue_capacity = NonZeroUsize::new(usize::MAX).unwrap()
814            },
815            |opts| opts.pending_rows_batcher.table.pending_rows_flush_interval = Duration::MAX,
816        ];
817        for invalidate in cases {
818            let mut opts = FrontendOptions::default();
819            opts.http.timeout = Duration::from_secs(1);
820            opts.prom_store.pending_rows_flush_interval = Duration::from_secs(5);
821            opts.pending_rows_batcher.table.protocols =
822                vec![BatchingProtocol::Prom, BatchingProtocol::Influxdb];
823            opts.pending_rows_batcher.table.pending_rows_flush_interval = Duration::from_secs(10);
824            invalidate(&mut opts);
825            assert!(
826                !opts
827                    .pending_rows_batcher
828                    .table
829                    .pending_rows_batching_enabled()
830            );
831            assert_eq!(opts.prom_store, effective_prom_store_options(&opts));
832            assert_eq!(
833                Duration::from_secs(6),
834                effective_http_options_with_sync(&opts, true).timeout
835            );
836            opts.prom_store.pending_rows_flush_interval = Duration::ZERO;
837            assert_eq!(
838                Duration::from_secs(1),
839                effective_http_options_with_sync(&opts, true).timeout
840            );
841        }
842    }
843
844    #[test]
845    fn test_effective_http_timeout_skips_fallback_when_batcher_disabled() {
846        // Mirrors the conditions under which `LogicalTablePendingRowsBatcher::try_new`
847        // returns `None`; in these cases no request can wait for a pending-row
848        // flush, so the timeout must not be raised.
849        type KnobMutator = fn(&mut FrontendOptions);
850        let cases: [(&str, KnobMutator); 9] = [
851            ("oversized flush concurrency", |opts| {
852                opts.prom_store.max_concurrent_flushes = usize::MAX
853            }),
854            ("oversized worker channel", |opts| {
855                opts.prom_store.worker_channel_capacity = usize::MAX
856            }),
857            ("oversized inflight limit", |opts| {
858                opts.prom_store.max_inflight_requests = usize::MAX
859            }),
860            ("oversized flow queue", |opts| {
861                opts.prom_store.flow_notification_queue_capacity =
862                    std::num::NonZeroUsize::new(usize::MAX).unwrap()
863            }),
864            ("unrepresentable deadline", |opts| {
865                opts.prom_store.pending_rows_flush_interval = Duration::MAX
866            }),
867            ("zero max_batch_rows", |opts| {
868                opts.prom_store.max_batch_rows = 0
869            }),
870            ("zero max_concurrent_flushes", |opts| {
871                opts.prom_store.max_concurrent_flushes = 0
872            }),
873            ("zero worker_channel_capacity", |opts| {
874                opts.prom_store.worker_channel_capacity = 0
875            }),
876            ("zero max_inflight_requests", |opts| {
877                opts.prom_store.max_inflight_requests = 0
878            }),
879        ];
880
881        for (name, disable_batcher) in cases {
882            let mut opts = FrontendOptions::default();
883            opts.http.timeout = Duration::from_millis(1000);
884            opts.prom_store.pending_rows_flush_interval = Duration::from_millis(5000);
885            disable_batcher(&mut opts);
886
887            assert_eq!(
888                Duration::from_millis(1000),
889                effective_http_options_with_sync(&opts, true).timeout,
890                "{name}"
891            );
892        }
893    }
894
895    #[tokio::test]
896    async fn test_database_sql_authentication_differs_between_public_and_internal_grpc() {
897        let options = FrontendOptions {
898            http: HttpOptions {
899                addr: "127.0.0.1:0".to_string(),
900                ..Default::default()
901            },
902            grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:0"),
903            internal_grpc: Some(GrpcOptions::default().with_bind_addr("127.0.0.1:0")),
904            mysql: crate::service_config::MysqlOptions {
905                enable: false,
906                ..Default::default()
907            },
908            postgres: crate::service_config::PostgresOptions {
909                enable: false,
910                ..Default::default()
911            },
912            ..Default::default()
913        };
914        let meta_client = Arc::new(
915            MetaClientBuilder::new(0, Role::Frontend)
916                .enable_procedure()
917                .build(),
918        );
919        let instance = Arc::new(
920            FrontendBuilder::new_test(&options, meta_client)
921                .try_build()
922                .await
923                .unwrap(),
924        );
925        let plugins = Plugins::new();
926        let provider =
927            static_user_provider_from_option("static_user_provider:cmd:greptime=greptime").unwrap();
928        plugins.insert::<UserProviderRef>(Arc::new(provider));
929        let public_flight_handler = Arc::new(GreptimeRequestHandler::new(
930            instance.clone(),
931            plugins.get::<UserProviderRef>(),
932            None,
933            options.grpc.flight_compression,
934        )) as FlightCraftRef;
935        let internal_flight_handler = Arc::new(CountingFlightCraft {
936            inner: Arc::new(GreptimeRequestHandler::new(
937                instance.clone(),
938                None,
939                None,
940                options.grpc.flight_compression,
941            )),
942            do_get_calls: AtomicUsize::new(0),
943            do_put_calls: AtomicUsize::new(0),
944        });
945        let internal_flight_handler_ref = internal_flight_handler.clone() as FlightCraftRef;
946        let mut services = Services::new(options, instance, plugins)
947            .with_flight_handler(public_flight_handler)
948            .with_internal_flight_handler(internal_flight_handler_ref)
949            .build()
950            .unwrap();
951
952        services.start_all().await.unwrap();
953        let public_addr = services.addr(GRPC_SERVER).unwrap();
954        let internal_addr = services.addr("INTERNAL_GRPC_SERVER").unwrap();
955        let public_database = Database::new(
956            "greptime",
957            "public",
958            Client::with_query_and_control_managers(
959                ChannelManager::new(),
960                ChannelManager::new(),
961                [public_addr.to_string()],
962            ),
963        );
964        let internal_database = Database::new(
965            "greptime",
966            "public",
967            Client::with_query_and_control_managers(
968                ChannelManager::new(),
969                ChannelManager::new(),
970                [internal_addr.to_string()],
971            ),
972        );
973
974        let internal_result = internal_database.sql("SELECT 1").await;
975        let put_result = internal_database
976            .do_put(Box::pin(futures::stream::empty()))
977            .await;
978        let public_result = public_database.sql("SELECT 1").await;
979
980        services.shutdown_all().await.unwrap();
981
982        assert!(internal_result.is_ok());
983        assert!(put_result.is_ok());
984        assert_eq!(
985            1,
986            internal_flight_handler.do_get_calls.load(Ordering::SeqCst)
987        );
988        assert_eq!(
989            1,
990            internal_flight_handler.do_put_calls.load(Ordering::SeqCst)
991        );
992        assert_eq!(
993            Some(Code::Unauthenticated),
994            public_result
995                .as_ref()
996                .err()
997                .and_then(|err| err.tonic_code())
998        );
999    }
1000
1001    #[tokio::test]
1002    async fn test_services_builder_health_check_is_reachable() {
1003        // Arrange
1004        let options = FrontendOptions {
1005            http: HttpOptions {
1006                addr: "127.0.0.1:0".to_string(),
1007                ..Default::default()
1008            },
1009            grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:0"),
1010            mysql: crate::service_config::MysqlOptions {
1011                enable: false,
1012                ..Default::default()
1013            },
1014            postgres: crate::service_config::PostgresOptions {
1015                enable: false,
1016                ..Default::default()
1017            },
1018            ..Default::default()
1019        };
1020        let meta_client = Arc::new(
1021            MetaClientBuilder::new(0, Role::Frontend)
1022                .enable_procedure()
1023                .build(),
1024        );
1025        let instance = Arc::new(
1026            FrontendBuilder::new_test(&options, meta_client)
1027                .try_build()
1028                .await
1029                .unwrap(),
1030        );
1031        let mut services = Services::new(options, instance, Default::default())
1032            .build()
1033            .unwrap();
1034
1035        // Act
1036        services.start_all().await.unwrap();
1037        let addr = services.addr(GRPC_SERVER).unwrap();
1038        let health_check = HealthCheckClient::connect(format!("http://{addr}"))
1039            .await
1040            .unwrap()
1041            .health_check(HealthCheckRequest {})
1042            .await;
1043        services.shutdown_all().await.unwrap();
1044
1045        // Assert
1046        assert!(health_check.is_ok());
1047    }
1048
1049    #[tokio::test]
1050    async fn test_internal_grpc_server_never_serves_cors() {
1051        let options = FrontendOptions {
1052            http: HttpOptions {
1053                addr: "127.0.0.1:0".to_string(),
1054                ..Default::default()
1055            },
1056            grpc: GrpcOptions {
1057                enable_cors: true,
1058                ..GrpcOptions::default().with_bind_addr("127.0.0.1:0")
1059            },
1060            internal_grpc: Some(GrpcOptions {
1061                enable_cors: true,
1062                ..GrpcOptions::default().with_bind_addr("127.0.0.1:0")
1063            }),
1064            mysql: crate::service_config::MysqlOptions {
1065                enable: false,
1066                ..Default::default()
1067            },
1068            postgres: crate::service_config::PostgresOptions {
1069                enable: false,
1070                ..Default::default()
1071            },
1072            ..Default::default()
1073        };
1074        let meta_client = Arc::new(
1075            MetaClientBuilder::new(0, Role::Frontend)
1076                .enable_procedure()
1077                .build(),
1078        );
1079        let instance = Arc::new(
1080            FrontendBuilder::new_test(&options, meta_client)
1081                .try_build()
1082                .await
1083                .unwrap(),
1084        );
1085        let mut services = Services::new(options, instance, Default::default())
1086            .build()
1087            .unwrap();
1088
1089        services.start_all().await.unwrap();
1090        let public_addr = services.addr(GRPC_SERVER).unwrap();
1091        let internal_addr = services.addr("INTERNAL_GRPC_SERVER").unwrap();
1092        let public = send_cors_preflight(public_addr).await;
1093        let internal = send_cors_preflight(internal_addr).await;
1094        services.shutdown_all().await.unwrap();
1095
1096        assert!(public.headers().contains_key(ACCESS_CONTROL_ALLOW_ORIGIN));
1097        assert!(!internal.headers().contains_key(ACCESS_CONTROL_ALLOW_ORIGIN));
1098    }
1099
1100    async fn send_cors_preflight(addr: std::net::SocketAddr) -> reqwest::Response {
1101        reqwest::Client::new()
1102            .request(
1103                reqwest::Method::OPTIONS,
1104                format!("http://{addr}/greptime.v1.HealthCheck/Check"),
1105            )
1106            .header(ORIGIN, "https://example.com")
1107            .header(ACCESS_CONTROL_REQUEST_METHOD, "POST")
1108            .send()
1109            .await
1110            .unwrap()
1111    }
1112}