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