1use std::fmt::Debug;
16use std::future::Future;
17use std::path::Path;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::time::Duration;
21
22use async_trait::async_trait;
23use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
24use catalog::information_extension::DistributedInformationExtension;
25use catalog::information_schema::InformationExtensionRef;
26use catalog::kvbackend::{
27 CachedKvBackendBuilder, CatalogManagerConfiguratorRef, KvBackendCatalogManagerBuilder,
28 new_read_only_meta_kv_backend,
29};
30use clap::Parser;
31use client::client_manager::NodeClients;
32use common_base::Plugins;
33use common_base::secrets::SecretString;
34use common_config::{Configurable, DEFAULT_DATA_HOME};
35use common_error::ext::BoxedError;
36use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder};
37use common_query::prelude::set_default_prefix;
38use common_stat::ResourceStatImpl;
39use common_telemetry::info;
40use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
41use common_time::timezone::set_default_timezone;
42use common_version::{short_version, verbose_version};
43use frontend::frontend::Frontend;
44use frontend::heartbeat::{
45 FrontendHeartbeatExtensions, HeartbeatTask, heartbeat_response_handler_executor,
46};
47use frontend::instance::builder::FrontendBuilder;
48use frontend::server::Services;
49use meta_client::{MetaClientOptions, MetaClientRef, MetaClientType};
50use plugins::PluginOptions;
51use plugins::frontend::context::{
52 CatalogManagerConfigureContext, DistributedCatalogManagerConfigureContext,
53};
54use plugins::options::PluginOptionsDeserializerImpl;
55use servers::grpc::GrpcOptions;
56use servers::tls::{TlsMode, TlsOption, merge_tls_option};
57use snafu::{OptionExt, ResultExt};
58use tracing_appender::non_blocking::WorkerGuard;
59
60use crate::error::{self, OtherSnafu, Result};
61use crate::options::{GlobalOptions, GreptimeOptions};
62use crate::{App, create_resource_limit_metrics, log_versions, maybe_activate_heap_profile};
63
64type FrontendOptions = GreptimeOptions<frontend::frontend::FrontendOptions>;
65type HeartbeatExtensionSetupFuture<'a> = Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
66
67pub struct Instance {
68 frontend: Frontend,
69 _guard: Vec<WorkerGuard>,
71}
72
73pub const APP_NAME: &str = "greptime-frontend";
74
75impl Instance {
76 pub fn new(frontend: Frontend, _guard: Vec<WorkerGuard>) -> Self {
77 Self { frontend, _guard }
78 }
79
80 pub fn inner(&self) -> &Frontend {
81 &self.frontend
82 }
83
84 pub fn mut_inner(&mut self) -> &mut Frontend {
85 &mut self.frontend
86 }
87}
88
89#[async_trait]
90impl App for Instance {
91 fn name(&self) -> &str {
92 APP_NAME
93 }
94
95 async fn start(&mut self) -> Result<()> {
96 plugins::start_frontend_plugins(&self.frontend.instance)
97 .await
98 .context(error::StartFrontendSnafu)?;
99
100 self.frontend
101 .start()
102 .await
103 .context(error::StartFrontendSnafu)
104 }
105
106 async fn stop(&mut self) -> Result<()> {
107 self.frontend
108 .shutdown()
109 .await
110 .context(error::ShutdownFrontendSnafu)
111 }
112}
113
114#[derive(Parser)]
115pub struct Command {
116 #[clap(subcommand)]
117 pub subcmd: SubCommand,
118}
119
120impl Command {
121 pub async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
122 self.build_with_heartbeat_extensions(opts, FrontendHeartbeatExtensions::default())
123 .await
124 }
125
126 pub async fn build_with_heartbeat_extensions(
133 &self,
134 opts: FrontendOptions,
135 heartbeat_extensions: FrontendHeartbeatExtensions,
136 ) -> Result<Instance> {
137 let plugins = plugins_with_heartbeat_extensions(heartbeat_extensions);
138 self.forward_build_with_plugins(opts, plugins, |command, opts, plugins| {
139 command.build_with_plugins(opts, plugins)
140 })
141 .await
142 }
143
144 fn forward_build_with_plugins<'a, T>(
145 &'a self,
146 opts: FrontendOptions,
147 plugins: Plugins,
148 build: impl FnOnce(&'a StartCommand, FrontendOptions, Plugins) -> T,
149 ) -> T {
150 self.subcmd.forward_build_with_plugins(opts, plugins, build)
151 }
152
153 pub fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
154 self.subcmd.load_options(global_options)
155 }
156}
157
158#[derive(Parser)]
159pub enum SubCommand {
160 Start(StartCommand),
161}
162
163impl SubCommand {
164 fn forward_build_with_plugins<'a, T>(
165 &'a self,
166 opts: FrontendOptions,
167 plugins: Plugins,
168 build: impl FnOnce(&'a StartCommand, FrontendOptions, Plugins) -> T,
169 ) -> T {
170 match self {
171 SubCommand::Start(cmd) => build(cmd, opts, plugins),
172 }
173 }
174
175 fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
176 match self {
177 SubCommand::Start(cmd) => cmd.load_options(global_options),
178 }
179 }
180}
181
182#[derive(Debug, Default, Parser)]
183pub struct StartCommand {
184 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
186 grpc_bind_addr: Option<String>,
187 #[clap(
191 long = "grpc-server-addr",
192 alias = "rpc-server-addr",
193 alias = "rpc-hostname"
194 )]
195 grpc_server_addr: Option<String>,
196 #[clap(
198 long = "internal-grpc-bind-addr",
199 alias = "internal-rpc-bind-addr",
200 alias = "internal-rpc-addr"
201 )]
202 internal_grpc_bind_addr: Option<String>,
203 #[clap(
207 long = "internal-grpc-server-addr",
208 alias = "internal-rpc-server-addr",
209 alias = "internal-rpc-hostname"
210 )]
211 internal_grpc_server_addr: Option<String>,
212 #[clap(long)]
213 http_addr: Option<String>,
214 #[clap(long)]
217 http_api_server_addr: Option<String>,
218 #[clap(long)]
219 http_timeout: Option<u64>,
220 #[clap(long)]
221 mysql_addr: Option<String>,
222 #[clap(long)]
223 postgres_addr: Option<String>,
224 #[clap(short, long)]
225 pub config_file: Option<String>,
226 #[clap(short, long)]
227 influxdb_enable: Option<bool>,
228 #[clap(long, value_delimiter = ',', num_args = 1..)]
229 metasrv_addrs: Option<Vec<String>>,
230 #[clap(long)]
231 tls_mode: Option<TlsMode>,
232 #[clap(long)]
233 tls_cert_path: Option<String>,
234 #[clap(long)]
235 tls_key_path: Option<String>,
236 #[clap(long)]
237 tls_watch: bool,
238 #[clap(long)]
239 user_provider: Option<SecretString>,
240 #[clap(long)]
241 disable_dashboard: Option<bool>,
242 #[clap(long, default_value = "GREPTIMEDB_FRONTEND")]
243 pub env_prefix: String,
244}
245
246impl StartCommand {
247 fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
248 let mut opts = FrontendOptions::load_layered_options(
249 self.config_file.as_deref(),
250 self.env_prefix.as_ref(),
251 )
252 .context(error::LoadLayeredConfigSnafu)?;
253
254 self.merge_with_cli_options(global_options, &mut opts)?;
255
256 Ok(opts)
257 }
258
259 fn merge_with_cli_options(
261 &self,
262 global_options: &GlobalOptions,
263 opts: &mut FrontendOptions,
264 ) -> Result<()> {
265 let opts = &mut opts.component;
266
267 if let Some(dir) = &global_options.log_dir {
268 opts.logging.dir.clone_from(dir);
269 }
270
271 if opts.logging.dir.is_empty() {
273 opts.logging.dir = Path::new(DEFAULT_DATA_HOME)
274 .join(DEFAULT_LOGGING_DIR)
275 .to_string_lossy()
276 .to_string();
277 }
278
279 if global_options.log_level.is_some() {
280 opts.logging.level.clone_from(&global_options.log_level);
281 }
282
283 opts.tracing = TracingOptions {
284 #[cfg(feature = "tokio-console")]
285 tokio_console_addr: global_options.tokio_console_addr.clone(),
286 };
287
288 let tls_opts = TlsOption::new(
289 self.tls_mode,
290 self.tls_cert_path.clone(),
291 self.tls_key_path.clone(),
292 self.tls_watch,
293 );
294
295 if let Some(addr) = &self.http_addr {
296 opts.http.addr.clone_from(addr);
297 }
298
299 if let Some(http_timeout) = self.http_timeout {
300 opts.http.timeout = Duration::from_secs(http_timeout)
301 }
302
303 if let Some(disable_dashboard) = self.disable_dashboard {
304 opts.http.disable_dashboard = disable_dashboard;
305 }
306
307 if let Some(addr) = &self.http_api_server_addr {
308 opts.http.api_server_addr.clone_from(addr);
309 opts.http.enable_api_server = true;
310 }
311
312 if let Some(addr) = &self.grpc_bind_addr {
313 opts.grpc.bind_addr.clone_from(addr);
314 opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
315 }
316
317 if let Some(addr) = &self.grpc_server_addr {
318 opts.grpc.server_addr.clone_from(addr);
319 }
320
321 if let Some(addr) = &self.internal_grpc_bind_addr {
322 if let Some(internal_grpc) = &mut opts.internal_grpc {
323 internal_grpc.bind_addr = addr.clone();
324 } else {
325 let grpc_options = GrpcOptions {
326 bind_addr: addr.clone(),
327 ..Default::default()
328 };
329
330 opts.internal_grpc = Some(grpc_options);
331 }
332 }
333
334 if let Some(addr) = &self.internal_grpc_server_addr {
335 if let Some(internal_grpc) = &mut opts.internal_grpc {
336 internal_grpc.server_addr = addr.clone();
337 } else {
338 let grpc_options = GrpcOptions {
339 server_addr: addr.clone(),
340 ..Default::default()
341 };
342 opts.internal_grpc = Some(grpc_options);
343 }
344 }
345
346 if let Some(addr) = &self.mysql_addr {
347 opts.mysql.enable = true;
348 opts.mysql.addr.clone_from(addr);
349 opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
350 }
351
352 if let Some(addr) = &self.postgres_addr {
353 opts.postgres.enable = true;
354 opts.postgres.addr.clone_from(addr);
355 opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
356 }
357
358 if let Some(enable) = self.influxdb_enable {
359 opts.influxdb.enable = enable;
360 }
361
362 if let Some(metasrv_addrs) = &self.metasrv_addrs {
363 opts.meta_client
364 .get_or_insert_with(MetaClientOptions::default)
365 .metasrv_addrs
366 .clone_from(metasrv_addrs);
367 }
368
369 if let Some(user_provider) = &self.user_provider {
370 opts.user_provider = Some(user_provider.clone());
371 }
372
373 Ok(())
374 }
375
376 async fn build_with_plugins(
377 &self,
378 opts: FrontendOptions,
379 plugins: Plugins,
380 ) -> Result<Instance> {
381 let guard = common_telemetry::init_global_logging(
382 APP_NAME,
383 &opts.component.logging,
384 &opts.component.tracing,
385 opts.component.node_id.clone(),
386 Some(&opts.component.slow_query),
387 );
388
389 common_runtime::init_global_runtimes(&opts.runtime);
390
391 crate::options::flush_dropped_plugin_warnings();
392 log_versions(verbose_version(), short_version(), APP_NAME);
393 maybe_activate_heap_profile(&opts.component.memory);
394 create_resource_limit_metrics(APP_NAME);
395
396 info!("Frontend start command: {:#?}", self);
397 info!("Frontend options: {:#?}", opts);
398
399 let plugin_opts = opts.plugins;
400 let mut opts = opts.component;
401 opts.grpc.detect_server_addr();
402
403 set_default_timezone(opts.default_timezone.as_deref()).context(error::InitTimezoneSnafu)?;
404 set_default_prefix(opts.default_column_prefix.as_deref())
405 .map_err(BoxedError::new)
406 .context(error::BuildCliSnafu)?;
407
408 let meta_client_options = opts
409 .meta_client
410 .as_ref()
411 .context(error::MissingConfigSnafu {
412 msg: "'meta_client'",
413 })?;
414
415 let cache_max_capacity = meta_client_options.metadata_cache_max_capacity;
416 let cache_ttl = meta_client_options.metadata_cache_ttl;
417 let cache_tti = meta_client_options.metadata_cache_tti;
418
419 let meta_config: Vec<PluginOptions> = meta_client::create_meta_client(
420 MetaClientType::Frontend,
421 meta_client_options,
422 None,
423 None,
424 )
425 .await
426 .context(error::MetaClientInitSnafu)?
427 .pull_config(PluginOptionsDeserializerImpl)
428 .await
429 .context(error::MetaClientInitSnafu)?;
430
431 let mut plugins =
432 prepare_frontend_plugins(plugins, &plugin_opts, &opts, Some(&meta_config)).await?;
433
434 let meta_client = meta_client::create_meta_client(
436 MetaClientType::Frontend,
437 meta_client_options,
438 Some(&plugins),
439 None,
440 )
441 .await
442 .context(error::MetaClientInitSnafu)?;
443
444 let readonly_meta_backend = new_read_only_meta_kv_backend(meta_client.clone());
445
446 let cached_meta_backend = CachedKvBackendBuilder::new(readonly_meta_backend.clone())
448 .cache_max_capacity(cache_max_capacity)
449 .cache_ttl(cache_ttl)
450 .cache_tti(cache_tti)
451 .build();
452 let cached_meta_backend = Arc::new(cached_meta_backend);
453
454 let layered_cache_builder = LayeredCacheRegistryBuilder::default().add_cache_registry(
456 CacheRegistryBuilder::default()
457 .add_cache(cached_meta_backend.clone())
458 .build(),
459 );
460 let fundamental_cache_registry =
461 build_fundamental_cache_registry(readonly_meta_backend.clone());
462 let mut layered_cache_builder = with_default_composite_cache_registry(
463 layered_cache_builder.add_cache_registry(fundamental_cache_registry),
464 )
465 .context(error::BuildCacheRegistrySnafu)?;
466
467 if let Some(plugin_cache_builder) = plugins::frontend::configure_cache_registry(&plugins) {
468 layered_cache_builder =
469 layered_cache_builder.add_cache_registry(plugin_cache_builder.build());
470 }
471
472 let layered_cache_registry = Arc::new(layered_cache_builder.build());
473
474 let mut channel_config = opts.datanode.client.channel_config();
477 channel_config.timeout = None;
478 if opts.grpc.flight_compression.transport_compression() {
479 channel_config.accept_compression = true;
480 channel_config.send_compression = true;
481 }
482 let client = Arc::new(NodeClients::new(channel_config));
483
484 let information_extension = Arc::new(DistributedInformationExtension::new(
485 meta_client.clone(),
486 client.clone(),
487 ));
488 plugins.insert::<InformationExtensionRef>(information_extension.clone());
489
490 let process_manager = opts.create_process_manager(Some(meta_client.clone()));
491
492 let builder = KvBackendCatalogManagerBuilder::new(
493 information_extension,
494 cached_meta_backend.clone(),
495 layered_cache_registry.clone(),
496 )
497 .with_process_manager(process_manager.clone());
498 let builder = if let Some(configurator) =
499 plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
500 {
501 let ctx = DistributedCatalogManagerConfigureContext {
502 meta_client: meta_client.clone(),
503 };
504 let ctx = CatalogManagerConfigureContext::Distributed(ctx);
505
506 configurator
507 .configure(builder, ctx)
508 .await
509 .context(OtherSnafu)?
510 } else {
511 builder
512 };
513 let catalog_manager = builder.build();
514
515 let builder = FrontendBuilder::new(
516 opts.clone(),
517 cached_meta_backend.clone(),
518 layered_cache_registry.clone(),
519 catalog_manager,
520 client,
521 meta_client.clone(),
522 process_manager,
523 );
524
525 plugins::setup_frontend_plugins_post_build(&mut plugins, &plugin_opts, &builder)
526 .await
527 .context(error::StartFrontendSnafu)?;
528
529 let instance = builder
530 .with_plugin(plugins.clone())
531 .with_local_cache_invalidator(layered_cache_registry)
532 .try_build()
533 .await
534 .context(error::StartFrontendSnafu)?;
535
536 let instance = Arc::new(instance);
537
538 let heartbeat_instance = instance.clone();
539 let heartbeat_extensions =
540 setup_and_freeze_frontend_heartbeat_extensions(&mut plugins, move |plugins| {
541 Box::pin(async move {
542 plugins::setup_frontend_heartbeat_extensions(
543 plugins,
544 &plugin_opts,
545 &heartbeat_instance,
546 )
547 .await
548 .context(error::StartFrontendSnafu)
549 })
550 })
551 .await?;
552 let heartbeat_task = Some(create_heartbeat_task_with_extensions(
553 &opts,
554 meta_client,
555 &instance,
556 heartbeat_extensions,
557 ));
558
559 let servers = Services::new(opts, instance.clone(), plugins)
560 .build()
561 .context(error::StartFrontendSnafu)?;
562
563 let frontend = Frontend {
564 instance,
565 servers,
566 heartbeat_task,
567 };
568
569 Ok(Instance::new(frontend, guard))
570 }
571}
572
573async fn prepare_frontend_plugins(
574 mut plugins: Plugins,
575 plugin_opts: &[PluginOptions],
576 opts: &frontend::frontend::FrontendOptions,
577 meta_config: Option<&[PluginOptions]>,
578) -> Result<Plugins> {
579 plugins::setup_frontend_plugins_pre_build(&mut plugins, plugin_opts, opts, meta_config)
580 .await
581 .context(error::StartFrontendSnafu)?;
582 Ok(plugins)
583}
584
585fn plugins_with_heartbeat_extensions(heartbeat_extensions: FrontendHeartbeatExtensions) -> Plugins {
586 let plugins = Plugins::new();
587 plugins.insert(heartbeat_extensions);
588 plugins
589}
590
591async fn setup_and_freeze_frontend_heartbeat_extensions(
592 plugins: &mut Plugins,
593 setup: impl for<'a> FnOnce(&'a mut Plugins) -> HeartbeatExtensionSetupFuture<'a>,
594) -> Result<FrontendHeartbeatExtensions> {
595 setup(plugins).await?;
596 let extensions = plugins
597 .get::<FrontendHeartbeatExtensions>()
598 .unwrap_or_default();
599 extensions.freeze();
600 Ok(extensions)
601}
602
603pub fn create_heartbeat_task(
604 options: &frontend::frontend::FrontendOptions,
605 meta_client: MetaClientRef,
606 instance: &frontend::instance::Instance,
607) -> HeartbeatTask {
608 create_heartbeat_task_with_extensions(options, meta_client, instance, Default::default())
609}
610
611fn create_heartbeat_task_with_extensions(
612 options: &frontend::frontend::FrontendOptions,
613 meta_client: MetaClientRef,
614 instance: &frontend::instance::Instance,
615 extensions: FrontendHeartbeatExtensions,
616) -> HeartbeatTask {
617 let executor = heartbeat_response_handler_executor(
618 &extensions,
619 instance.suspend_state(),
620 instance.cache_invalidator().clone(),
621 );
622
623 let stat = {
624 let mut stat = ResourceStatImpl::default();
625 stat.start_collect_cpu_usage();
626 Arc::new(stat)
627 };
628
629 HeartbeatTask::new(
630 instance.frontend_peer_addr().to_string(),
631 options,
632 meta_client,
633 executor,
634 stat,
635 )
636 .with_extensions(extensions)
637}
638
639#[cfg(test)]
640mod tests {
641 use std::io::Write;
642 use std::sync::Arc;
643 use std::time::Duration;
644
645 use auth::{Identity, Password, UserProviderRef};
646 use clap::{CommandFactory, Parser};
647 use common_base::readable_size::ReadableSize;
648 use common_config::ENV_VAR_SEP;
649 use common_test_util::temp_dir::create_named_temp_file;
650 use servers::grpc::GrpcOptions;
651
652 use super::*;
653 use crate::options::GlobalOptions;
654
655 #[derive(Debug)]
656 struct TestExtension(&'static str);
657
658 #[async_trait]
659 impl frontend::heartbeat::FrontendHeartbeatExtension for TestExtension {
660 fn name(&self) -> &'static str {
661 self.0
662 }
663 }
664
665 #[test]
666 fn test_command_forwards_heartbeat_extensions_to_start_build() {
667 let command = Command {
668 subcmd: SubCommand::Start(StartCommand::default()),
669 };
670 let extensions = FrontendHeartbeatExtensions::default();
671 let shared_extensions = extensions.clone();
672
673 let forwarded_plugins = command.forward_build_with_plugins(
674 FrontendOptions::default(),
675 plugins_with_heartbeat_extensions(extensions),
676 |_, _, plugins| plugins,
677 );
678 let forwarded_extensions = forwarded_plugins
679 .get::<FrontendHeartbeatExtensions>()
680 .unwrap();
681
682 assert_registry_identity(&shared_extensions, &forwarded_extensions);
683 }
684
685 #[tokio::test]
686 async fn test_prefilled_heartbeat_extensions_survive_pre_build_setup() {
687 let extensions = FrontendHeartbeatExtensions::default();
688 let shared_extensions = extensions.clone();
689
690 let options = frontend::frontend::FrontendOptions {
691 meta_client: Some(MetaClientOptions::default()),
692 ..Default::default()
693 };
694 let plugins = prepare_frontend_plugins(
695 plugins_with_heartbeat_extensions(extensions),
696 &[],
697 &options,
698 None,
699 )
700 .await
701 .unwrap();
702
703 let setup_extensions = plugins.get_or_insert(FrontendHeartbeatExtensions::default);
704 assert_registry_identity(&shared_extensions, &setup_extensions);
705 }
706
707 #[tokio::test]
708 async fn test_heartbeat_extension_setup_completes_before_freeze() {
709 let extensions = FrontendHeartbeatExtensions::default();
710 assert_eq!(
711 extensions.try_register(Arc::new(TestExtension("caller"))),
712 Ok(())
713 );
714 let mut plugins = plugins_with_heartbeat_extensions(extensions.clone());
715
716 let finalized = setup_and_freeze_frontend_heartbeat_extensions(&mut plugins, |plugins| {
717 Box::pin(async move {
718 let setup_extensions = plugins.get_or_insert(FrontendHeartbeatExtensions::default);
719 assert_eq!(
720 setup_extensions.try_register(Arc::new(TestExtension("plugin"))),
721 Ok(())
722 );
723 Ok(())
724 })
725 })
726 .await
727 .unwrap();
728
729 assert_eq!(
730 finalized
731 .extensions()
732 .iter()
733 .map(|extension| extension.name())
734 .collect::<Vec<_>>(),
735 ["caller", "plugin"]
736 );
737 assert_eq!(
738 extensions.try_register(Arc::new(TestExtension("late"))),
739 Err(frontend::heartbeat::RegistrationError::Frozen)
740 );
741 assert_eq!(finalized.len(), 2);
742 }
743
744 fn assert_registry_identity(
745 expected: &FrontendHeartbeatExtensions,
746 actual: &FrontendHeartbeatExtensions,
747 ) {
748 let extension = Arc::new(TestExtension("cmd-test-extension"));
749 assert!(expected.register(extension.clone()));
750 let registered = actual.extensions();
751 assert_eq!(registered.len(), 1);
752 assert!(Arc::ptr_eq(
753 &(extension as Arc<dyn frontend::heartbeat::FrontendHeartbeatExtension>),
754 ®istered[0]
755 ));
756 }
757
758 #[test]
759 fn test_try_from_start_command() {
760 let command = StartCommand {
761 http_addr: Some("127.0.0.1:1234".to_string()),
762 mysql_addr: Some("127.0.0.1:5678".to_string()),
763 postgres_addr: Some("127.0.0.1:5432".to_string()),
764 internal_grpc_bind_addr: Some("127.0.0.1:4010".to_string()),
765 internal_grpc_server_addr: Some("10.0.0.24:4010".to_string()),
766 influxdb_enable: Some(false),
767 disable_dashboard: Some(false),
768 ..Default::default()
769 };
770
771 let opts = command.load_options(&Default::default()).unwrap().component;
772
773 assert_eq!(opts.http.addr, "127.0.0.1:1234");
774 assert_eq!(ReadableSize::mb(64), opts.http.body_limit);
775 assert_eq!(opts.mysql.addr, "127.0.0.1:5678");
776 assert_eq!(opts.postgres.addr, "127.0.0.1:5432");
777
778 let internal_grpc = opts.internal_grpc.as_ref().unwrap();
779 assert_eq!(internal_grpc.bind_addr, "127.0.0.1:4010");
780 assert_eq!(internal_grpc.server_addr, "10.0.0.24:4010");
781
782 let default_opts = FrontendOptions::default().component;
783
784 assert_eq!(opts.grpc.bind_addr, default_opts.grpc.bind_addr);
785 assert!(opts.mysql.enable);
786 assert_eq!(opts.mysql.runtime_size, default_opts.mysql.runtime_size);
787 assert!(opts.postgres.enable);
788 assert_eq!(
789 opts.postgres.runtime_size,
790 default_opts.postgres.runtime_size
791 );
792 assert!(opts.opentsdb.enable);
793
794 assert!(!opts.influxdb.enable);
795 }
796
797 #[test]
798 fn test_read_from_config_file() {
799 let mut file = create_named_temp_file();
800 let toml_str = r#"
801 [http]
802 addr = "127.0.0.1:4000"
803 timeout = "0s"
804 body_limit = "2GB"
805
806 [opentsdb]
807 enable = false
808
809 [logging]
810 level = "debug"
811 dir = "./greptimedb_data/test/logs"
812 "#;
813 write!(file, "{}", toml_str).unwrap();
814
815 let command = StartCommand {
816 config_file: Some(file.path().to_str().unwrap().to_string()),
817 disable_dashboard: Some(false),
818 ..Default::default()
819 };
820
821 let fe_opts = command.load_options(&Default::default()).unwrap().component;
822
823 assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
824 assert_eq!(Duration::from_secs(0), fe_opts.http.timeout);
825
826 assert_eq!(ReadableSize::gb(2), fe_opts.http.body_limit);
827
828 assert_eq!("debug", fe_opts.logging.level.as_ref().unwrap());
829 assert_eq!(
830 "./greptimedb_data/test/logs".to_string(),
831 fe_opts.logging.dir
832 );
833 assert!(!fe_opts.opentsdb.enable);
834 }
835
836 #[tokio::test]
837 async fn test_user_provider_cli_redacts_passwords() {
838 let command = StartCommand::try_parse_from([
839 "frontend",
840 "--user-provider",
841 "static_user_provider:cmd:test=cli-secret",
842 ])
843 .unwrap();
844 let debug = format!("{command:#?}");
845 assert!(!debug.contains("cli-secret"));
846 assert!(debug.contains("[REDACTED]"));
847
848 let mut fe_opts = command.load_options(&Default::default()).unwrap().component;
849 fe_opts.meta_client = Some(MetaClientOptions::default());
851
852 let mut plugins = Plugins::new();
853 plugins::setup_frontend_plugins_pre_build(&mut plugins, &[], &fe_opts, None)
854 .await
855 .unwrap();
856
857 let provider = plugins.get::<UserProviderRef>().unwrap();
858 let result = provider
859 .authenticate(
860 Identity::UserId("test", None),
861 Password::PlainText("cli-secret".to_string().into()),
862 )
863 .await;
864 let _ = result.unwrap();
865 }
866
867 #[test]
868 fn test_load_log_options_from_cli() {
869 let cmd = StartCommand {
870 disable_dashboard: Some(false),
871 ..Default::default()
872 };
873
874 let options = cmd
875 .load_options(&GlobalOptions {
876 log_dir: Some("./greptimedb_data/test/logs".to_string()),
877 log_level: Some("debug".to_string()),
878
879 #[cfg(feature = "tokio-console")]
880 tokio_console_addr: None,
881 })
882 .unwrap()
883 .component;
884
885 let logging_opt = options.logging;
886 assert_eq!("./greptimedb_data/test/logs", logging_opt.dir);
887 assert_eq!("debug", logging_opt.level.as_ref().unwrap());
888 }
889
890 #[test]
891 fn test_config_precedence_order() {
892 let mut file = create_named_temp_file();
893 let toml_str = r#"
894 [http]
895 addr = "127.0.0.1:4000"
896
897 [meta_client]
898 timeout = "3s"
899 connect_timeout = "5s"
900 tcp_nodelay = true
901
902 [mysql]
903 addr = "127.0.0.1:4002"
904 "#;
905 write!(file, "{}", toml_str).unwrap();
906
907 let env_prefix = "FRONTEND_UT";
908 temp_env::with_vars(
909 [
910 (
911 [
913 env_prefix.to_string(),
914 "mysql".to_uppercase(),
915 "addr".to_uppercase(),
916 ]
917 .join(ENV_VAR_SEP),
918 Some("127.0.0.1:14002"),
919 ),
920 (
921 [
923 env_prefix.to_string(),
924 "mysql".to_uppercase(),
925 "runtime_size".to_uppercase(),
926 ]
927 .join(ENV_VAR_SEP),
928 Some("11"),
929 ),
930 (
931 [
933 env_prefix.to_string(),
934 "http".to_uppercase(),
935 "addr".to_uppercase(),
936 ]
937 .join(ENV_VAR_SEP),
938 Some("127.0.0.1:24000"),
939 ),
940 (
941 [
943 env_prefix.to_string(),
944 "meta_client".to_uppercase(),
945 "metasrv_addrs".to_uppercase(),
946 ]
947 .join(ENV_VAR_SEP),
948 Some("127.0.0.1:3001,127.0.0.1:3002,127.0.0.1:3003"),
949 ),
950 ],
951 || {
952 let command = StartCommand {
953 config_file: Some(file.path().to_str().unwrap().to_string()),
954 http_addr: Some("127.0.0.1:14000".to_string()),
955 env_prefix: env_prefix.to_string(),
956 ..Default::default()
957 };
958
959 let fe_opts = command.load_options(&Default::default()).unwrap().component;
960
961 assert_eq!(fe_opts.mysql.runtime_size, 11);
963 assert_eq!(
964 fe_opts.meta_client.unwrap().metasrv_addrs,
965 vec![
966 "127.0.0.1:3001".to_string(),
967 "127.0.0.1:3002".to_string(),
968 "127.0.0.1:3003".to_string()
969 ]
970 );
971
972 assert_eq!(fe_opts.mysql.addr, "127.0.0.1:4002");
974
975 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
977
978 assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
980 },
981 );
982 }
983
984 #[test]
985 fn test_parse_grpc_cli_aliases() {
986 let command = StartCommand::try_parse_from([
987 "frontend",
988 "--grpc-bind-addr",
989 "127.0.0.1:14001",
990 "--grpc-server-addr",
991 "10.0.0.1:14001",
992 "--internal-grpc-bind-addr",
993 "127.0.0.1:14010",
994 "--internal-grpc-server-addr",
995 "10.0.0.1:14010",
996 ])
997 .unwrap();
998 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
999 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.1:14001"));
1000 assert_eq!(
1001 command.internal_grpc_bind_addr.as_deref(),
1002 Some("127.0.0.1:14010")
1003 );
1004 assert_eq!(
1005 command.internal_grpc_server_addr.as_deref(),
1006 Some("10.0.0.1:14010")
1007 );
1008
1009 let command = StartCommand::try_parse_from([
1010 "frontend",
1011 "--rpc-bind-addr",
1012 "127.0.0.1:24001",
1013 "--rpc-server-addr",
1014 "10.0.0.2:24001",
1015 "--internal-rpc-bind-addr",
1016 "127.0.0.1:24010",
1017 "--internal-rpc-server-addr",
1018 "10.0.0.2:24010",
1019 ])
1020 .unwrap();
1021 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1022 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.2:24001"));
1023 assert_eq!(
1024 command.internal_grpc_bind_addr.as_deref(),
1025 Some("127.0.0.1:24010")
1026 );
1027 assert_eq!(
1028 command.internal_grpc_server_addr.as_deref(),
1029 Some("10.0.0.2:24010")
1030 );
1031
1032 let command = StartCommand::try_parse_from([
1033 "frontend",
1034 "--rpc-addr",
1035 "127.0.0.1:34001",
1036 "--rpc-hostname",
1037 "10.0.0.3:34001",
1038 "--internal-rpc-addr",
1039 "127.0.0.1:34010",
1040 "--internal-rpc-hostname",
1041 "10.0.0.3:34010",
1042 ])
1043 .unwrap();
1044 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1045 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.3:34001"));
1046 assert_eq!(
1047 command.internal_grpc_bind_addr.as_deref(),
1048 Some("127.0.0.1:34010")
1049 );
1050 assert_eq!(
1051 command.internal_grpc_server_addr.as_deref(),
1052 Some("10.0.0.3:34010")
1053 );
1054 }
1055
1056 #[test]
1057 fn test_help_uses_grpc_option_names() {
1058 let mut cmd = StartCommand::command();
1059 let mut help = Vec::new();
1060 cmd.write_long_help(&mut help).unwrap();
1061 let help = String::from_utf8(help).unwrap();
1062
1063 assert!(help.contains("--grpc-bind-addr"));
1064 assert!(help.contains("--grpc-server-addr"));
1065 assert!(help.contains("--internal-grpc-bind-addr"));
1066 assert!(help.contains("--internal-grpc-server-addr"));
1067 assert!(!help.contains("--rpc-bind-addr"));
1068 assert!(!help.contains("--rpc-server-addr"));
1069 assert!(!help.contains("--rpc-addr"));
1070 assert!(!help.contains("--rpc-hostname"));
1071 assert!(!help.contains("--internal-rpc-bind-addr"));
1072 assert!(!help.contains("--internal-rpc-server-addr"));
1073 assert!(!help.contains("--internal-rpc-addr"));
1074 assert!(!help.contains("--internal-rpc-hostname"));
1075 }
1076}