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