1use 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 _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 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
151 grpc_bind_addr: Option<String>,
152 #[clap(
156 long = "grpc-server-addr",
157 alias = "rpc-server-addr",
158 alias = "rpc-hostname"
159 )]
160 grpc_server_addr: Option<String>,
161 #[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 #[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 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 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 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 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 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 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 [
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 [
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 [
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 [
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 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 assert_eq!(fe_opts.mysql.addr, "127.0.0.1:4002");
791
792 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
794
795 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}