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