Skip to main content

cmd/
frontend.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::fmt::Debug;
16use std::path::Path;
17use std::sync::Arc;
18use std::time::Duration;
19
20use async_trait::async_trait;
21use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
22use catalog::information_extension::DistributedInformationExtension;
23use catalog::information_schema::InformationExtensionRef;
24use catalog::kvbackend::{
25    CachedKvBackendBuilder, CatalogManagerConfiguratorRef, KvBackendCatalogManagerBuilder,
26    new_read_only_meta_kv_backend,
27};
28use catalog::process_manager::ProcessManager;
29use clap::Parser;
30use client::client_manager::NodeClients;
31use common_base::Plugins;
32use common_config::{Configurable, DEFAULT_DATA_HOME};
33use common_error::ext::BoxedError;
34use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder};
35use common_query::prelude::set_default_prefix;
36use common_stat::ResourceStatImpl;
37use common_telemetry::info;
38use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
39use common_time::timezone::set_default_timezone;
40use common_version::{short_version, verbose_version};
41use frontend::frontend::Frontend;
42use frontend::heartbeat::{
43    FrontendHeartbeatExtensions, HeartbeatTask, heartbeat_response_handler_executor,
44};
45use frontend::instance::builder::FrontendBuilder;
46use frontend::server::Services;
47use meta_client::{MetaClientOptions, MetaClientRef, MetaClientType};
48use plugins::PluginOptions;
49use plugins::frontend::context::{
50    CatalogManagerConfigureContext, DistributedCatalogManagerConfigureContext,
51};
52use plugins::options::PluginOptionsDeserializerImpl;
53use servers::addrs;
54use servers::grpc::GrpcOptions;
55use servers::tls::{TlsMode, TlsOption, merge_tls_option};
56use snafu::{OptionExt, ResultExt};
57use tracing_appender::non_blocking::WorkerGuard;
58
59use crate::error::{self, OtherSnafu, Result};
60use crate::options::{GlobalOptions, GreptimeOptions};
61use crate::{App, create_resource_limit_metrics, log_versions, maybe_activate_heap_profile};
62
63type FrontendOptions = GreptimeOptions<frontend::frontend::FrontendOptions>;
64
65pub struct Instance {
66    frontend: Frontend,
67    // Keep the logging guard to prevent the worker from being dropped.
68    _guard: Vec<WorkerGuard>,
69}
70
71pub const APP_NAME: &str = "greptime-frontend";
72
73impl Instance {
74    pub fn new(frontend: Frontend, _guard: Vec<WorkerGuard>) -> Self {
75        Self { frontend, _guard }
76    }
77
78    pub fn inner(&self) -> &Frontend {
79        &self.frontend
80    }
81
82    pub fn mut_inner(&mut self) -> &mut Frontend {
83        &mut self.frontend
84    }
85}
86
87#[async_trait]
88impl App for Instance {
89    fn name(&self) -> &str {
90        APP_NAME
91    }
92
93    async fn start(&mut self) -> Result<()> {
94        plugins::start_frontend_plugins(&self.frontend.instance)
95            .await
96            .context(error::StartFrontendSnafu)?;
97
98        self.frontend
99            .start()
100            .await
101            .context(error::StartFrontendSnafu)
102    }
103
104    async fn stop(&mut self) -> Result<()> {
105        self.frontend
106            .shutdown()
107            .await
108            .context(error::ShutdownFrontendSnafu)
109    }
110}
111
112#[derive(Parser)]
113pub struct Command {
114    #[clap(subcommand)]
115    pub subcmd: SubCommand,
116}
117
118impl Command {
119    pub async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
120        self.subcmd.build(opts).await
121    }
122
123    pub fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
124        self.subcmd.load_options(global_options)
125    }
126}
127
128#[derive(Parser)]
129pub enum SubCommand {
130    Start(StartCommand),
131}
132
133impl SubCommand {
134    async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
135        match self {
136            SubCommand::Start(cmd) => cmd.build(opts).await,
137        }
138    }
139
140    fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
141        match self {
142            SubCommand::Start(cmd) => cmd.load_options(global_options),
143        }
144    }
145}
146
147#[derive(Debug, Default, Parser)]
148pub struct StartCommand {
149    /// The address to bind the gRPC server.
150    #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
151    grpc_bind_addr: Option<String>,
152    /// The address advertised to the metasrv, and used for connections from outside the host.
153    /// If left empty or unset, the server will automatically use the IP address of the first network interface
154    /// on the host, with the same port number as the one specified in `grpc_bind_addr`.
155    #[clap(
156        long = "grpc-server-addr",
157        alias = "rpc-server-addr",
158        alias = "rpc-hostname"
159    )]
160    grpc_server_addr: Option<String>,
161    /// The address to bind the internal gRPC server.
162    #[clap(
163        long = "internal-grpc-bind-addr",
164        alias = "internal-rpc-bind-addr",
165        alias = "internal-rpc-addr"
166    )]
167    internal_grpc_bind_addr: Option<String>,
168    /// The address advertised to the metasrv, and used for connections from outside the host.
169    /// If left empty or unset, the server will automatically use the IP address of the first network interface
170    /// on the host, with the same port number as the one specified in `internal_grpc_bind_addr`.
171    #[clap(
172        long = "internal-grpc-server-addr",
173        alias = "internal-rpc-server-addr",
174        alias = "internal-rpc-hostname"
175    )]
176    internal_grpc_server_addr: Option<String>,
177    #[clap(long)]
178    http_addr: Option<String>,
179    #[clap(long)]
180    http_timeout: Option<u64>,
181    #[clap(long)]
182    mysql_addr: Option<String>,
183    #[clap(long)]
184    postgres_addr: Option<String>,
185    #[clap(short, long)]
186    pub config_file: Option<String>,
187    #[clap(short, long)]
188    influxdb_enable: Option<bool>,
189    #[clap(long, value_delimiter = ',', num_args = 1..)]
190    metasrv_addrs: Option<Vec<String>>,
191    #[clap(long)]
192    tls_mode: Option<TlsMode>,
193    #[clap(long)]
194    tls_cert_path: Option<String>,
195    #[clap(long)]
196    tls_key_path: Option<String>,
197    #[clap(long)]
198    tls_watch: bool,
199    #[clap(long)]
200    user_provider: Option<String>,
201    #[clap(long)]
202    disable_dashboard: Option<bool>,
203    #[clap(long, default_value = "GREPTIMEDB_FRONTEND")]
204    pub env_prefix: String,
205}
206
207impl StartCommand {
208    fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
209        let mut opts = FrontendOptions::load_layered_options(
210            self.config_file.as_deref(),
211            self.env_prefix.as_ref(),
212        )
213        .context(error::LoadLayeredConfigSnafu)?;
214
215        self.merge_with_cli_options(global_options, &mut opts)?;
216
217        Ok(opts)
218    }
219
220    // The precedence order is: cli > config file > environment variables > default values.
221    fn merge_with_cli_options(
222        &self,
223        global_options: &GlobalOptions,
224        opts: &mut FrontendOptions,
225    ) -> Result<()> {
226        let opts = &mut opts.component;
227
228        if let Some(dir) = &global_options.log_dir {
229            opts.logging.dir.clone_from(dir);
230        }
231
232        // If the logging dir is not set, use the default logs dir in the data home.
233        if opts.logging.dir.is_empty() {
234            opts.logging.dir = Path::new(DEFAULT_DATA_HOME)
235                .join(DEFAULT_LOGGING_DIR)
236                .to_string_lossy()
237                .to_string();
238        }
239
240        if global_options.log_level.is_some() {
241            opts.logging.level.clone_from(&global_options.log_level);
242        }
243
244        opts.tracing = TracingOptions {
245            #[cfg(feature = "tokio-console")]
246            tokio_console_addr: global_options.tokio_console_addr.clone(),
247        };
248
249        let tls_opts = TlsOption::new(
250            self.tls_mode,
251            self.tls_cert_path.clone(),
252            self.tls_key_path.clone(),
253            self.tls_watch,
254        );
255
256        if let Some(addr) = &self.http_addr {
257            opts.http.addr.clone_from(addr);
258        }
259
260        if let Some(http_timeout) = self.http_timeout {
261            opts.http.timeout = Duration::from_secs(http_timeout)
262        }
263
264        if let Some(disable_dashboard) = self.disable_dashboard {
265            opts.http.disable_dashboard = disable_dashboard;
266        }
267
268        if let Some(addr) = &self.grpc_bind_addr {
269            opts.grpc.bind_addr.clone_from(addr);
270            opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
271        }
272
273        if let Some(addr) = &self.grpc_server_addr {
274            opts.grpc.server_addr.clone_from(addr);
275        }
276
277        if let Some(addr) = &self.internal_grpc_bind_addr {
278            if let Some(internal_grpc) = &mut opts.internal_grpc {
279                internal_grpc.bind_addr = addr.clone();
280            } else {
281                let grpc_options = GrpcOptions {
282                    bind_addr: addr.clone(),
283                    ..Default::default()
284                };
285
286                opts.internal_grpc = Some(grpc_options);
287            }
288        }
289
290        if let Some(addr) = &self.internal_grpc_server_addr {
291            if let Some(internal_grpc) = &mut opts.internal_grpc {
292                internal_grpc.server_addr = addr.clone();
293            } else {
294                let grpc_options = GrpcOptions {
295                    server_addr: addr.clone(),
296                    ..Default::default()
297                };
298                opts.internal_grpc = Some(grpc_options);
299            }
300        }
301
302        if let Some(addr) = &self.mysql_addr {
303            opts.mysql.enable = true;
304            opts.mysql.addr.clone_from(addr);
305            opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
306        }
307
308        if let Some(addr) = &self.postgres_addr {
309            opts.postgres.enable = true;
310            opts.postgres.addr.clone_from(addr);
311            opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
312        }
313
314        if let Some(enable) = self.influxdb_enable {
315            opts.influxdb.enable = enable;
316        }
317
318        if let Some(metasrv_addrs) = &self.metasrv_addrs {
319            opts.meta_client
320                .get_or_insert_with(MetaClientOptions::default)
321                .metasrv_addrs
322                .clone_from(metasrv_addrs);
323        }
324
325        if let Some(user_provider) = &self.user_provider {
326            opts.user_provider = Some(user_provider.clone());
327        }
328
329        Ok(())
330    }
331
332    async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
333        common_runtime::init_global_runtimes(&opts.runtime);
334
335        let guard = common_telemetry::init_global_logging(
336            APP_NAME,
337            &opts.component.logging,
338            &opts.component.tracing,
339            opts.component.node_id.clone(),
340            Some(&opts.component.slow_query),
341        );
342
343        crate::options::flush_dropped_plugin_warnings();
344        log_versions(verbose_version(), short_version(), APP_NAME);
345        maybe_activate_heap_profile(&opts.component.memory);
346        create_resource_limit_metrics(APP_NAME);
347
348        info!("Frontend start command: {:#?}", self);
349        info!("Frontend options: {:#?}", opts);
350
351        let plugin_opts = opts.plugins;
352        let mut opts = opts.component;
353        opts.grpc.detect_server_addr();
354
355        set_default_timezone(opts.default_timezone.as_deref()).context(error::InitTimezoneSnafu)?;
356        set_default_prefix(opts.default_column_prefix.as_deref())
357            .map_err(BoxedError::new)
358            .context(error::BuildCliSnafu)?;
359
360        let meta_client_options = opts
361            .meta_client
362            .as_ref()
363            .context(error::MissingConfigSnafu {
364                msg: "'meta_client'",
365            })?;
366
367        let cache_max_capacity = meta_client_options.metadata_cache_max_capacity;
368        let cache_ttl = meta_client_options.metadata_cache_ttl;
369        let cache_tti = meta_client_options.metadata_cache_tti;
370
371        let meta_config: Vec<PluginOptions> = meta_client::create_meta_client(
372            MetaClientType::Frontend,
373            meta_client_options,
374            None,
375            None,
376        )
377        .await
378        .context(error::MetaClientInitSnafu)?
379        .pull_config(PluginOptionsDeserializerImpl)
380        .await
381        .context(error::MetaClientInitSnafu)?;
382
383        let mut plugins = Plugins::new();
384        plugins::setup_frontend_plugins_pre_build(
385            &mut plugins,
386            &plugin_opts,
387            &opts,
388            Some(&meta_config),
389        )
390        .await
391        .context(error::StartFrontendSnafu)?;
392
393        // now initialize the meta_client with plugins
394        let meta_client = meta_client::create_meta_client(
395            MetaClientType::Frontend,
396            meta_client_options,
397            Some(&plugins),
398            None,
399        )
400        .await
401        .context(error::MetaClientInitSnafu)?;
402
403        let readonly_meta_backend = new_read_only_meta_kv_backend(meta_client.clone());
404
405        // TODO(discord9): add helper function to ease the creation of cache registry&such
406        let cached_meta_backend = CachedKvBackendBuilder::new(readonly_meta_backend.clone())
407            .cache_max_capacity(cache_max_capacity)
408            .cache_ttl(cache_ttl)
409            .cache_tti(cache_tti)
410            .build();
411        let cached_meta_backend = Arc::new(cached_meta_backend);
412
413        // Builds cache registry
414        let layered_cache_builder = LayeredCacheRegistryBuilder::default().add_cache_registry(
415            CacheRegistryBuilder::default()
416                .add_cache(cached_meta_backend.clone())
417                .build(),
418        );
419        let fundamental_cache_registry =
420            build_fundamental_cache_registry(readonly_meta_backend.clone());
421        let mut layered_cache_builder = with_default_composite_cache_registry(
422            layered_cache_builder.add_cache_registry(fundamental_cache_registry),
423        )
424        .context(error::BuildCacheRegistrySnafu)?;
425
426        if let Some(plugin_cache_builder) = plugins::frontend::configure_cache_registry(&plugins) {
427            layered_cache_builder =
428                layered_cache_builder.add_cache_registry(plugin_cache_builder.build());
429        }
430
431        let layered_cache_registry = Arc::new(layered_cache_builder.build());
432
433        // frontend to datanode need not timeout.
434        // Some queries are expected to take long time.
435        let mut channel_config = opts.datanode.client.channel_config();
436        channel_config.timeout = None;
437        if opts.grpc.flight_compression.transport_compression() {
438            channel_config.accept_compression = true;
439            channel_config.send_compression = true;
440        }
441        let client = Arc::new(NodeClients::new(channel_config));
442
443        let information_extension = Arc::new(DistributedInformationExtension::new(
444            meta_client.clone(),
445            client.clone(),
446        ));
447        plugins.insert::<InformationExtensionRef>(information_extension.clone());
448
449        let process_manager = Arc::new(ProcessManager::new(
450            addrs::resolve_addr(&opts.grpc.bind_addr, Some(&opts.grpc.server_addr)),
451            Some(meta_client.clone()),
452        ));
453
454        let builder = KvBackendCatalogManagerBuilder::new(
455            information_extension,
456            cached_meta_backend.clone(),
457            layered_cache_registry.clone(),
458        )
459        .with_process_manager(process_manager.clone());
460        let builder = if let Some(configurator) =
461            plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
462        {
463            let ctx = DistributedCatalogManagerConfigureContext {
464                meta_client: meta_client.clone(),
465            };
466            let ctx = CatalogManagerConfigureContext::Distributed(ctx);
467
468            configurator
469                .configure(builder, ctx)
470                .await
471                .context(OtherSnafu)?
472        } else {
473            builder
474        };
475        let catalog_manager = builder.build();
476
477        let builder = FrontendBuilder::new(
478            opts.clone(),
479            cached_meta_backend.clone(),
480            layered_cache_registry.clone(),
481            catalog_manager,
482            client,
483            meta_client.clone(),
484            process_manager,
485        );
486
487        plugins::setup_frontend_plugins_post_build(&mut plugins, &plugin_opts, &builder)
488            .await
489            .context(error::StartFrontendSnafu)?;
490
491        let instance = builder
492            .with_plugin(plugins.clone())
493            .with_local_cache_invalidator(layered_cache_registry)
494            .try_build()
495            .await
496            .context(error::StartFrontendSnafu)?;
497
498        let instance = Arc::new(instance);
499
500        plugins::setup_frontend_heartbeat_extensions(&mut plugins, &plugin_opts, &instance)
501            .await
502            .context(error::StartFrontendSnafu)?;
503        let heartbeat_extensions = plugins
504            .get::<FrontendHeartbeatExtensions>()
505            .unwrap_or_default();
506        let heartbeat_task = Some(create_heartbeat_task_with_extensions(
507            &opts,
508            meta_client,
509            &instance,
510            heartbeat_extensions,
511        ));
512
513        let servers = Services::new(opts, instance.clone(), plugins)
514            .build()
515            .context(error::StartFrontendSnafu)?;
516
517        let frontend = Frontend {
518            instance,
519            servers,
520            heartbeat_task,
521        };
522
523        Ok(Instance::new(frontend, guard))
524    }
525}
526
527pub fn create_heartbeat_task(
528    options: &frontend::frontend::FrontendOptions,
529    meta_client: MetaClientRef,
530    instance: &frontend::instance::Instance,
531) -> HeartbeatTask {
532    create_heartbeat_task_with_extensions(options, meta_client, instance, Default::default())
533}
534
535fn create_heartbeat_task_with_extensions(
536    options: &frontend::frontend::FrontendOptions,
537    meta_client: MetaClientRef,
538    instance: &frontend::instance::Instance,
539    extensions: FrontendHeartbeatExtensions,
540) -> HeartbeatTask {
541    let executor = heartbeat_response_handler_executor(
542        &extensions,
543        instance.suspend_state(),
544        instance.cache_invalidator().clone(),
545    );
546
547    let stat = {
548        let mut stat = ResourceStatImpl::default();
549        stat.start_collect_cpu_usage();
550        Arc::new(stat)
551    };
552
553    HeartbeatTask::new(
554        instance.frontend_peer_addr().to_string(),
555        options,
556        meta_client,
557        executor,
558        stat,
559    )
560    .with_extensions(extensions)
561}
562
563#[cfg(test)]
564mod tests {
565    use std::io::Write;
566    use std::time::Duration;
567
568    use auth::{Identity, Password, UserProviderRef};
569    use clap::{CommandFactory, Parser};
570    use common_base::readable_size::ReadableSize;
571    use common_config::ENV_VAR_SEP;
572    use common_test_util::temp_dir::create_named_temp_file;
573    use servers::grpc::GrpcOptions;
574    use servers::http::HttpOptions;
575
576    use super::*;
577    use crate::options::GlobalOptions;
578
579    #[test]
580    fn test_try_from_start_command() {
581        let command = StartCommand {
582            http_addr: Some("127.0.0.1:1234".to_string()),
583            mysql_addr: Some("127.0.0.1:5678".to_string()),
584            postgres_addr: Some("127.0.0.1:5432".to_string()),
585            internal_grpc_bind_addr: Some("127.0.0.1:4010".to_string()),
586            internal_grpc_server_addr: Some("10.0.0.24:4010".to_string()),
587            influxdb_enable: Some(false),
588            disable_dashboard: Some(false),
589            ..Default::default()
590        };
591
592        let opts = command.load_options(&Default::default()).unwrap().component;
593
594        assert_eq!(opts.http.addr, "127.0.0.1:1234");
595        assert_eq!(ReadableSize::mb(64), opts.http.body_limit);
596        assert_eq!(opts.mysql.addr, "127.0.0.1:5678");
597        assert_eq!(opts.postgres.addr, "127.0.0.1:5432");
598
599        let internal_grpc = opts.internal_grpc.as_ref().unwrap();
600        assert_eq!(internal_grpc.bind_addr, "127.0.0.1:4010");
601        assert_eq!(internal_grpc.server_addr, "10.0.0.24:4010");
602
603        let default_opts = FrontendOptions::default().component;
604
605        assert_eq!(opts.grpc.bind_addr, default_opts.grpc.bind_addr);
606        assert!(opts.mysql.enable);
607        assert_eq!(opts.mysql.runtime_size, default_opts.mysql.runtime_size);
608        assert!(opts.postgres.enable);
609        assert_eq!(
610            opts.postgres.runtime_size,
611            default_opts.postgres.runtime_size
612        );
613        assert!(opts.opentsdb.enable);
614
615        assert!(!opts.influxdb.enable);
616    }
617
618    #[test]
619    fn test_read_from_config_file() {
620        let mut file = create_named_temp_file();
621        let toml_str = r#"
622            [http]
623            addr = "127.0.0.1:4000"
624            timeout = "0s"
625            body_limit = "2GB"
626
627            [opentsdb]
628            enable = false
629
630            [logging]
631            level = "debug"
632            dir = "./greptimedb_data/test/logs"
633        "#;
634        write!(file, "{}", toml_str).unwrap();
635
636        let command = StartCommand {
637            config_file: Some(file.path().to_str().unwrap().to_string()),
638            disable_dashboard: Some(false),
639            ..Default::default()
640        };
641
642        let fe_opts = command.load_options(&Default::default()).unwrap().component;
643
644        assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
645        assert_eq!(Duration::from_secs(0), fe_opts.http.timeout);
646
647        assert_eq!(ReadableSize::gb(2), fe_opts.http.body_limit);
648
649        assert_eq!("debug", fe_opts.logging.level.as_ref().unwrap());
650        assert_eq!(
651            "./greptimedb_data/test/logs".to_string(),
652            fe_opts.logging.dir
653        );
654        assert!(!fe_opts.opentsdb.enable);
655    }
656
657    #[tokio::test]
658    async fn test_try_from_start_command_to_anymap() {
659        let fe_opts = frontend::frontend::FrontendOptions {
660            http: HttpOptions {
661                disable_dashboard: false,
662                ..Default::default()
663            },
664            meta_client: Some(MetaClientOptions::default()),
665            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
666            ..Default::default()
667        };
668
669        let mut plugins = Plugins::new();
670        plugins::setup_frontend_plugins_pre_build(&mut plugins, &[], &fe_opts, None)
671            .await
672            .unwrap();
673
674        let provider = plugins.get::<UserProviderRef>().unwrap();
675        let result = provider
676            .authenticate(
677                Identity::UserId("test", None),
678                Password::PlainText("test".to_string().into()),
679            )
680            .await;
681        let _ = result.unwrap();
682    }
683
684    #[test]
685    fn test_load_log_options_from_cli() {
686        let cmd = StartCommand {
687            disable_dashboard: Some(false),
688            ..Default::default()
689        };
690
691        let options = cmd
692            .load_options(&GlobalOptions {
693                log_dir: Some("./greptimedb_data/test/logs".to_string()),
694                log_level: Some("debug".to_string()),
695
696                #[cfg(feature = "tokio-console")]
697                tokio_console_addr: None,
698            })
699            .unwrap()
700            .component;
701
702        let logging_opt = options.logging;
703        assert_eq!("./greptimedb_data/test/logs", logging_opt.dir);
704        assert_eq!("debug", logging_opt.level.as_ref().unwrap());
705    }
706
707    #[test]
708    fn test_config_precedence_order() {
709        let mut file = create_named_temp_file();
710        let toml_str = r#"
711            [http]
712            addr = "127.0.0.1:4000"
713
714            [meta_client]
715            timeout = "3s"
716            connect_timeout = "5s"
717            tcp_nodelay = true
718
719            [mysql]
720            addr = "127.0.0.1:4002"
721        "#;
722        write!(file, "{}", toml_str).unwrap();
723
724        let env_prefix = "FRONTEND_UT";
725        temp_env::with_vars(
726            [
727                (
728                    // mysql.addr = 127.0.0.1:14002
729                    [
730                        env_prefix.to_string(),
731                        "mysql".to_uppercase(),
732                        "addr".to_uppercase(),
733                    ]
734                    .join(ENV_VAR_SEP),
735                    Some("127.0.0.1:14002"),
736                ),
737                (
738                    // mysql.runtime_size = 11
739                    [
740                        env_prefix.to_string(),
741                        "mysql".to_uppercase(),
742                        "runtime_size".to_uppercase(),
743                    ]
744                    .join(ENV_VAR_SEP),
745                    Some("11"),
746                ),
747                (
748                    // http.addr = 127.0.0.1:24000
749                    [
750                        env_prefix.to_string(),
751                        "http".to_uppercase(),
752                        "addr".to_uppercase(),
753                    ]
754                    .join(ENV_VAR_SEP),
755                    Some("127.0.0.1:24000"),
756                ),
757                (
758                    // meta_client.metasrv_addrs = 127.0.0.1:3001,127.0.0.1:3002,127.0.0.1:3003
759                    [
760                        env_prefix.to_string(),
761                        "meta_client".to_uppercase(),
762                        "metasrv_addrs".to_uppercase(),
763                    ]
764                    .join(ENV_VAR_SEP),
765                    Some("127.0.0.1:3001,127.0.0.1:3002,127.0.0.1:3003"),
766                ),
767            ],
768            || {
769                let command = StartCommand {
770                    config_file: Some(file.path().to_str().unwrap().to_string()),
771                    http_addr: Some("127.0.0.1:14000".to_string()),
772                    env_prefix: env_prefix.to_string(),
773                    ..Default::default()
774                };
775
776                let fe_opts = command.load_options(&Default::default()).unwrap().component;
777
778                // Should be read from env, env > default values.
779                assert_eq!(fe_opts.mysql.runtime_size, 11);
780                assert_eq!(
781                    fe_opts.meta_client.unwrap().metasrv_addrs,
782                    vec![
783                        "127.0.0.1:3001".to_string(),
784                        "127.0.0.1:3002".to_string(),
785                        "127.0.0.1:3003".to_string()
786                    ]
787                );
788
789                // Should be read from config file, config file > env > default values.
790                assert_eq!(fe_opts.mysql.addr, "127.0.0.1:4002");
791
792                // Should be read from cli, cli > config file > env > default values.
793                assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
794
795                // Should be default value.
796                assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
797            },
798        );
799    }
800
801    #[test]
802    fn test_parse_grpc_cli_aliases() {
803        let command = StartCommand::try_parse_from([
804            "frontend",
805            "--grpc-bind-addr",
806            "127.0.0.1:14001",
807            "--grpc-server-addr",
808            "10.0.0.1:14001",
809            "--internal-grpc-bind-addr",
810            "127.0.0.1:14010",
811            "--internal-grpc-server-addr",
812            "10.0.0.1:14010",
813        ])
814        .unwrap();
815        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
816        assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.1:14001"));
817        assert_eq!(
818            command.internal_grpc_bind_addr.as_deref(),
819            Some("127.0.0.1:14010")
820        );
821        assert_eq!(
822            command.internal_grpc_server_addr.as_deref(),
823            Some("10.0.0.1:14010")
824        );
825
826        let command = StartCommand::try_parse_from([
827            "frontend",
828            "--rpc-bind-addr",
829            "127.0.0.1:24001",
830            "--rpc-server-addr",
831            "10.0.0.2:24001",
832            "--internal-rpc-bind-addr",
833            "127.0.0.1:24010",
834            "--internal-rpc-server-addr",
835            "10.0.0.2:24010",
836        ])
837        .unwrap();
838        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
839        assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.2:24001"));
840        assert_eq!(
841            command.internal_grpc_bind_addr.as_deref(),
842            Some("127.0.0.1:24010")
843        );
844        assert_eq!(
845            command.internal_grpc_server_addr.as_deref(),
846            Some("10.0.0.2:24010")
847        );
848
849        let command = StartCommand::try_parse_from([
850            "frontend",
851            "--rpc-addr",
852            "127.0.0.1:34001",
853            "--rpc-hostname",
854            "10.0.0.3:34001",
855            "--internal-rpc-addr",
856            "127.0.0.1:34010",
857            "--internal-rpc-hostname",
858            "10.0.0.3:34010",
859        ])
860        .unwrap();
861        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
862        assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.3:34001"));
863        assert_eq!(
864            command.internal_grpc_bind_addr.as_deref(),
865            Some("127.0.0.1:34010")
866        );
867        assert_eq!(
868            command.internal_grpc_server_addr.as_deref(),
869            Some("10.0.0.3:34010")
870        );
871    }
872
873    #[test]
874    fn test_help_uses_grpc_option_names() {
875        let mut cmd = StartCommand::command();
876        let mut help = Vec::new();
877        cmd.write_long_help(&mut help).unwrap();
878        let help = String::from_utf8(help).unwrap();
879
880        assert!(help.contains("--grpc-bind-addr"));
881        assert!(help.contains("--grpc-server-addr"));
882        assert!(help.contains("--internal-grpc-bind-addr"));
883        assert!(help.contains("--internal-grpc-server-addr"));
884        assert!(!help.contains("--rpc-bind-addr"));
885        assert!(!help.contains("--rpc-server-addr"));
886        assert!(!help.contains("--rpc-addr"));
887        assert!(!help.contains("--rpc-hostname"));
888        assert!(!help.contains("--internal-rpc-bind-addr"));
889        assert!(!help.contains("--internal-rpc-server-addr"));
890        assert!(!help.contains("--internal-rpc-addr"));
891        assert!(!help.contains("--internal-rpc-hostname"));
892    }
893}