1use std::path::Path;
16use std::sync::Arc;
17use std::time::Duration;
18
19use async_trait::async_trait;
20use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
21use catalog::information_extension::DistributedInformationExtension;
22use catalog::kvbackend::{CachedKvBackendBuilder, KvBackendCatalogManagerBuilder, MetaKvBackend};
23use catalog::process_manager::ProcessManager;
24use clap::Parser;
25use client::client_manager::NodeClients;
26use common_base::Plugins;
27use common_config::{Configurable, DEFAULT_DATA_HOME};
28use common_grpc::channel_manager::ChannelConfig;
29use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder};
30use common_meta::heartbeat::handler::invalidate_table_cache::InvalidateCacheHandler;
31use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler;
32use common_meta::heartbeat::handler::HandlerGroupExecutor;
33use common_telemetry::info;
34use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
35use common_time::timezone::set_default_timezone;
36use common_version::{short_version, verbose_version};
37use frontend::frontend::Frontend;
38use frontend::heartbeat::HeartbeatTask;
39use frontend::instance::builder::FrontendBuilder;
40use frontend::server::Services;
41use meta_client::{MetaClientOptions, MetaClientType};
42use servers::addrs;
43use servers::export_metrics::ExportMetricsTask;
44use servers::grpc::GrpcOptions;
45use servers::tls::{TlsMode, TlsOption};
46use snafu::{OptionExt, ResultExt};
47use tracing_appender::non_blocking::WorkerGuard;
48
49use crate::error::{self, Result};
50use crate::options::{GlobalOptions, GreptimeOptions};
51use crate::{create_resource_limit_metrics, log_versions, maybe_activate_heap_profile, App};
52
53type FrontendOptions = GreptimeOptions<frontend::frontend::FrontendOptions>;
54
55pub struct Instance {
56 frontend: Frontend,
57 _guard: Vec<WorkerGuard>,
59}
60
61pub const APP_NAME: &str = "greptime-frontend";
62
63impl Instance {
64 pub fn new(frontend: Frontend, _guard: Vec<WorkerGuard>) -> Self {
65 Self { frontend, _guard }
66 }
67
68 pub fn inner(&self) -> &Frontend {
69 &self.frontend
70 }
71
72 pub fn mut_inner(&mut self) -> &mut Frontend {
73 &mut self.frontend
74 }
75}
76
77#[async_trait]
78impl App for Instance {
79 fn name(&self) -> &str {
80 APP_NAME
81 }
82
83 async fn start(&mut self) -> Result<()> {
84 let plugins = self.frontend.instance.plugins().clone();
85 plugins::start_frontend_plugins(plugins)
86 .await
87 .context(error::StartFrontendSnafu)?;
88
89 self.frontend
90 .start()
91 .await
92 .context(error::StartFrontendSnafu)
93 }
94
95 async fn stop(&mut self) -> Result<()> {
96 self.frontend
97 .shutdown()
98 .await
99 .context(error::ShutdownFrontendSnafu)
100 }
101}
102
103#[derive(Parser)]
104pub struct Command {
105 #[clap(subcommand)]
106 pub subcmd: SubCommand,
107}
108
109impl Command {
110 pub async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
111 self.subcmd.build(opts).await
112 }
113
114 pub fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
115 self.subcmd.load_options(global_options)
116 }
117}
118
119#[derive(Parser)]
120pub enum SubCommand {
121 Start(StartCommand),
122}
123
124impl SubCommand {
125 async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
126 match self {
127 SubCommand::Start(cmd) => cmd.build(opts).await,
128 }
129 }
130
131 fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
132 match self {
133 SubCommand::Start(cmd) => cmd.load_options(global_options),
134 }
135 }
136}
137
138#[derive(Debug, Default, Parser)]
139pub struct StartCommand {
140 #[clap(long, alias = "rpc-addr")]
142 rpc_bind_addr: Option<String>,
143 #[clap(long, alias = "rpc-hostname")]
147 rpc_server_addr: Option<String>,
148 #[clap(long, alias = "internal-rpc-addr")]
150 internal_rpc_bind_addr: Option<String>,
151 #[clap(long, alias = "internal-rpc-hostname")]
155 internal_rpc_server_addr: Option<String>,
156 #[clap(long)]
157 http_addr: Option<String>,
158 #[clap(long)]
159 http_timeout: Option<u64>,
160 #[clap(long)]
161 mysql_addr: Option<String>,
162 #[clap(long)]
163 postgres_addr: Option<String>,
164 #[clap(short, long)]
165 pub config_file: Option<String>,
166 #[clap(short, long)]
167 influxdb_enable: Option<bool>,
168 #[clap(long, value_delimiter = ',', num_args = 1..)]
169 metasrv_addrs: Option<Vec<String>>,
170 #[clap(long)]
171 tls_mode: Option<TlsMode>,
172 #[clap(long)]
173 tls_cert_path: Option<String>,
174 #[clap(long)]
175 tls_key_path: Option<String>,
176 #[clap(long)]
177 user_provider: Option<String>,
178 #[clap(long)]
179 disable_dashboard: Option<bool>,
180 #[clap(long, default_value = "GREPTIMEDB_FRONTEND")]
181 pub env_prefix: String,
182}
183
184impl StartCommand {
185 fn load_options(&self, global_options: &GlobalOptions) -> Result<FrontendOptions> {
186 let mut opts = FrontendOptions::load_layered_options(
187 self.config_file.as_deref(),
188 self.env_prefix.as_ref(),
189 )
190 .context(error::LoadLayeredConfigSnafu)?;
191
192 self.merge_with_cli_options(global_options, &mut opts)?;
193
194 Ok(opts)
195 }
196
197 fn merge_with_cli_options(
199 &self,
200 global_options: &GlobalOptions,
201 opts: &mut FrontendOptions,
202 ) -> Result<()> {
203 let opts = &mut opts.component;
204
205 if let Some(dir) = &global_options.log_dir {
206 opts.logging.dir.clone_from(dir);
207 }
208
209 if opts.logging.dir.is_empty() {
211 opts.logging.dir = Path::new(DEFAULT_DATA_HOME)
212 .join(DEFAULT_LOGGING_DIR)
213 .to_string_lossy()
214 .to_string();
215 }
216
217 if global_options.log_level.is_some() {
218 opts.logging.level.clone_from(&global_options.log_level);
219 }
220
221 opts.tracing = TracingOptions {
222 #[cfg(feature = "tokio-console")]
223 tokio_console_addr: global_options.tokio_console_addr.clone(),
224 };
225
226 let tls_opts = TlsOption::new(
227 self.tls_mode.clone(),
228 self.tls_cert_path.clone(),
229 self.tls_key_path.clone(),
230 );
231
232 if let Some(addr) = &self.http_addr {
233 opts.http.addr.clone_from(addr);
234 }
235
236 if let Some(http_timeout) = self.http_timeout {
237 opts.http.timeout = Duration::from_secs(http_timeout)
238 }
239
240 if let Some(disable_dashboard) = self.disable_dashboard {
241 opts.http.disable_dashboard = disable_dashboard;
242 }
243
244 if let Some(addr) = &self.rpc_bind_addr {
245 opts.grpc.bind_addr.clone_from(addr);
246 opts.grpc.tls = tls_opts.clone();
247 }
248
249 if let Some(addr) = &self.rpc_server_addr {
250 opts.grpc.server_addr.clone_from(addr);
251 }
252
253 if let Some(addr) = &self.internal_rpc_bind_addr {
254 if let Some(internal_grpc) = &mut opts.internal_grpc {
255 internal_grpc.bind_addr = addr.to_string();
256 } else {
257 let grpc_options = GrpcOptions {
258 bind_addr: addr.to_string(),
259 ..Default::default()
260 };
261
262 opts.internal_grpc = Some(grpc_options);
263 }
264 }
265
266 if let Some(addr) = &self.internal_rpc_server_addr {
267 if let Some(internal_grpc) = &mut opts.internal_grpc {
268 internal_grpc.server_addr = addr.to_string();
269 } else {
270 let grpc_options = GrpcOptions {
271 server_addr: addr.to_string(),
272 ..Default::default()
273 };
274 opts.internal_grpc = Some(grpc_options);
275 }
276 }
277
278 if let Some(addr) = &self.mysql_addr {
279 opts.mysql.enable = true;
280 opts.mysql.addr.clone_from(addr);
281 opts.mysql.tls = tls_opts.clone();
282 }
283
284 if let Some(addr) = &self.postgres_addr {
285 opts.postgres.enable = true;
286 opts.postgres.addr.clone_from(addr);
287 opts.postgres.tls = tls_opts;
288 }
289
290 if let Some(enable) = self.influxdb_enable {
291 opts.influxdb.enable = enable;
292 }
293
294 if let Some(metasrv_addrs) = &self.metasrv_addrs {
295 opts.meta_client
296 .get_or_insert_with(MetaClientOptions::default)
297 .metasrv_addrs
298 .clone_from(metasrv_addrs);
299 }
300
301 if let Some(user_provider) = &self.user_provider {
302 opts.user_provider = Some(user_provider.clone());
303 }
304
305 Ok(())
306 }
307
308 async fn build(&self, opts: FrontendOptions) -> Result<Instance> {
309 common_runtime::init_global_runtimes(&opts.runtime);
310
311 let guard = common_telemetry::init_global_logging(
312 APP_NAME,
313 &opts.component.logging,
314 &opts.component.tracing,
315 opts.component.node_id.clone(),
316 Some(&opts.component.slow_query),
317 );
318
319 log_versions(verbose_version(), short_version(), APP_NAME);
320 maybe_activate_heap_profile(&opts.component.memory);
321 create_resource_limit_metrics(APP_NAME);
322
323 info!("Frontend start command: {:#?}", self);
324 info!("Frontend options: {:#?}", opts);
325
326 let plugin_opts = opts.plugins;
327 let mut opts = opts.component;
328 opts.grpc.detect_server_addr();
329 let mut plugins = Plugins::new();
330 plugins::setup_frontend_plugins(&mut plugins, &plugin_opts, &opts)
331 .await
332 .context(error::StartFrontendSnafu)?;
333
334 set_default_timezone(opts.default_timezone.as_deref()).context(error::InitTimezoneSnafu)?;
335
336 let meta_client_options = opts
337 .meta_client
338 .as_ref()
339 .context(error::MissingConfigSnafu {
340 msg: "'meta_client'",
341 })?;
342
343 let cache_max_capacity = meta_client_options.metadata_cache_max_capacity;
344 let cache_ttl = meta_client_options.metadata_cache_ttl;
345 let cache_tti = meta_client_options.metadata_cache_tti;
346
347 let meta_client = meta_client::create_meta_client(
348 MetaClientType::Frontend,
349 meta_client_options,
350 Some(&plugins),
351 None,
352 )
353 .await
354 .context(error::MetaClientInitSnafu)?;
355
356 let cached_meta_backend =
358 CachedKvBackendBuilder::new(Arc::new(MetaKvBackend::new(meta_client.clone())))
359 .cache_max_capacity(cache_max_capacity)
360 .cache_ttl(cache_ttl)
361 .cache_tti(cache_tti)
362 .build();
363 let cached_meta_backend = Arc::new(cached_meta_backend);
364
365 let layered_cache_builder = LayeredCacheRegistryBuilder::default().add_cache_registry(
367 CacheRegistryBuilder::default()
368 .add_cache(cached_meta_backend.clone())
369 .build(),
370 );
371 let fundamental_cache_registry =
372 build_fundamental_cache_registry(Arc::new(MetaKvBackend::new(meta_client.clone())));
373 let layered_cache_registry = Arc::new(
374 with_default_composite_cache_registry(
375 layered_cache_builder.add_cache_registry(fundamental_cache_registry),
376 )
377 .context(error::BuildCacheRegistrySnafu)?
378 .build(),
379 );
380
381 let information_extension =
382 Arc::new(DistributedInformationExtension::new(meta_client.clone()));
383
384 let process_manager = Arc::new(ProcessManager::new(
385 addrs::resolve_addr(&opts.grpc.bind_addr, Some(&opts.grpc.server_addr)),
386 Some(meta_client.clone()),
387 ));
388
389 let builder = KvBackendCatalogManagerBuilder::new(
390 information_extension,
391 cached_meta_backend.clone(),
392 layered_cache_registry.clone(),
393 )
394 .with_process_manager(process_manager.clone());
395 #[cfg(feature = "enterprise")]
396 let builder = if let Some(factories) = plugins.get() {
397 builder.with_extra_information_table_factories(factories)
398 } else {
399 builder
400 };
401 let catalog_manager = builder.build();
402
403 let executor = HandlerGroupExecutor::new(vec![
404 Arc::new(ParseMailboxMessageHandler),
405 Arc::new(InvalidateCacheHandler::new(layered_cache_registry.clone())),
406 ]);
407
408 let heartbeat_task = HeartbeatTask::new(
409 &opts,
410 meta_client.clone(),
411 opts.heartbeat.clone(),
412 Arc::new(executor),
413 );
414 let heartbeat_task = Some(heartbeat_task);
415
416 let mut channel_config = ChannelConfig {
419 timeout: None,
420 tcp_nodelay: opts.datanode.client.tcp_nodelay,
421 connect_timeout: Some(opts.datanode.client.connect_timeout),
422 ..Default::default()
423 };
424 if opts.grpc.flight_compression.transport_compression() {
425 channel_config.accept_compression = true;
426 channel_config.send_compression = true;
427 }
428 let client = NodeClients::new(channel_config);
429
430 let instance = FrontendBuilder::new(
431 opts.clone(),
432 cached_meta_backend.clone(),
433 layered_cache_registry.clone(),
434 catalog_manager,
435 Arc::new(client),
436 meta_client,
437 process_manager,
438 )
439 .with_plugin(plugins.clone())
440 .with_local_cache_invalidator(layered_cache_registry)
441 .try_build()
442 .await
443 .context(error::StartFrontendSnafu)?;
444 let instance = Arc::new(instance);
445
446 let export_metrics_task = ExportMetricsTask::try_new(&opts.export_metrics, Some(&plugins))
447 .context(error::ServersSnafu)?;
448
449 let servers = Services::new(opts, instance.clone(), plugins)
450 .build()
451 .context(error::StartFrontendSnafu)?;
452
453 let frontend = Frontend {
454 instance,
455 servers,
456 heartbeat_task,
457 export_metrics_task,
458 };
459
460 Ok(Instance::new(frontend, guard))
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use std::io::Write;
467 use std::time::Duration;
468
469 use auth::{Identity, Password, UserProviderRef};
470 use common_base::readable_size::ReadableSize;
471 use common_config::ENV_VAR_SEP;
472 use common_test_util::temp_dir::create_named_temp_file;
473 use servers::grpc::GrpcOptions;
474 use servers::http::HttpOptions;
475
476 use super::*;
477 use crate::options::GlobalOptions;
478
479 #[test]
480 fn test_try_from_start_command() {
481 let command = StartCommand {
482 http_addr: Some("127.0.0.1:1234".to_string()),
483 mysql_addr: Some("127.0.0.1:5678".to_string()),
484 postgres_addr: Some("127.0.0.1:5432".to_string()),
485 internal_rpc_bind_addr: Some("127.0.0.1:4010".to_string()),
486 internal_rpc_server_addr: Some("10.0.0.24:4010".to_string()),
487 influxdb_enable: Some(false),
488 disable_dashboard: Some(false),
489 ..Default::default()
490 };
491
492 let opts = command.load_options(&Default::default()).unwrap().component;
493
494 assert_eq!(opts.http.addr, "127.0.0.1:1234");
495 assert_eq!(ReadableSize::mb(64), opts.http.body_limit);
496 assert_eq!(opts.mysql.addr, "127.0.0.1:5678");
497 assert_eq!(opts.postgres.addr, "127.0.0.1:5432");
498
499 let internal_grpc = opts.internal_grpc.as_ref().unwrap();
500 assert_eq!(internal_grpc.bind_addr, "127.0.0.1:4010");
501 assert_eq!(internal_grpc.server_addr, "10.0.0.24:4010");
502
503 let default_opts = FrontendOptions::default().component;
504
505 assert_eq!(opts.grpc.bind_addr, default_opts.grpc.bind_addr);
506 assert!(opts.mysql.enable);
507 assert_eq!(opts.mysql.runtime_size, default_opts.mysql.runtime_size);
508 assert!(opts.postgres.enable);
509 assert_eq!(
510 opts.postgres.runtime_size,
511 default_opts.postgres.runtime_size
512 );
513 assert!(opts.opentsdb.enable);
514
515 assert!(!opts.influxdb.enable);
516 }
517
518 #[test]
519 fn test_read_from_config_file() {
520 let mut file = create_named_temp_file();
521 let toml_str = r#"
522 [http]
523 addr = "127.0.0.1:4000"
524 timeout = "0s"
525 body_limit = "2GB"
526
527 [opentsdb]
528 enable = false
529
530 [logging]
531 level = "debug"
532 dir = "./greptimedb_data/test/logs"
533 "#;
534 write!(file, "{}", toml_str).unwrap();
535
536 let command = StartCommand {
537 config_file: Some(file.path().to_str().unwrap().to_string()),
538 disable_dashboard: Some(false),
539 ..Default::default()
540 };
541
542 let fe_opts = command.load_options(&Default::default()).unwrap().component;
543
544 assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
545 assert_eq!(Duration::from_secs(0), fe_opts.http.timeout);
546
547 assert_eq!(ReadableSize::gb(2), fe_opts.http.body_limit);
548
549 assert_eq!("debug", fe_opts.logging.level.as_ref().unwrap());
550 assert_eq!(
551 "./greptimedb_data/test/logs".to_string(),
552 fe_opts.logging.dir
553 );
554 assert!(!fe_opts.opentsdb.enable);
555 }
556
557 #[tokio::test]
558 async fn test_try_from_start_command_to_anymap() {
559 let fe_opts = frontend::frontend::FrontendOptions {
560 http: HttpOptions {
561 disable_dashboard: false,
562 ..Default::default()
563 },
564 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
565 ..Default::default()
566 };
567
568 let mut plugins = Plugins::new();
569 plugins::setup_frontend_plugins(&mut plugins, &[], &fe_opts)
570 .await
571 .unwrap();
572
573 let provider = plugins.get::<UserProviderRef>().unwrap();
574 let result = provider
575 .authenticate(
576 Identity::UserId("test", None),
577 Password::PlainText("test".to_string().into()),
578 )
579 .await;
580 let _ = result.unwrap();
581 }
582
583 #[test]
584 fn test_load_log_options_from_cli() {
585 let cmd = StartCommand {
586 disable_dashboard: Some(false),
587 ..Default::default()
588 };
589
590 let options = cmd
591 .load_options(&GlobalOptions {
592 log_dir: Some("./greptimedb_data/test/logs".to_string()),
593 log_level: Some("debug".to_string()),
594
595 #[cfg(feature = "tokio-console")]
596 tokio_console_addr: None,
597 })
598 .unwrap()
599 .component;
600
601 let logging_opt = options.logging;
602 assert_eq!("./greptimedb_data/test/logs", logging_opt.dir);
603 assert_eq!("debug", logging_opt.level.as_ref().unwrap());
604 }
605
606 #[test]
607 fn test_config_precedence_order() {
608 let mut file = create_named_temp_file();
609 let toml_str = r#"
610 [http]
611 addr = "127.0.0.1:4000"
612
613 [meta_client]
614 timeout = "3s"
615 connect_timeout = "5s"
616 tcp_nodelay = true
617
618 [mysql]
619 addr = "127.0.0.1:4002"
620 "#;
621 write!(file, "{}", toml_str).unwrap();
622
623 let env_prefix = "FRONTEND_UT";
624 temp_env::with_vars(
625 [
626 (
627 [
629 env_prefix.to_string(),
630 "mysql".to_uppercase(),
631 "addr".to_uppercase(),
632 ]
633 .join(ENV_VAR_SEP),
634 Some("127.0.0.1:14002"),
635 ),
636 (
637 [
639 env_prefix.to_string(),
640 "mysql".to_uppercase(),
641 "runtime_size".to_uppercase(),
642 ]
643 .join(ENV_VAR_SEP),
644 Some("11"),
645 ),
646 (
647 [
649 env_prefix.to_string(),
650 "http".to_uppercase(),
651 "addr".to_uppercase(),
652 ]
653 .join(ENV_VAR_SEP),
654 Some("127.0.0.1:24000"),
655 ),
656 (
657 [
659 env_prefix.to_string(),
660 "meta_client".to_uppercase(),
661 "metasrv_addrs".to_uppercase(),
662 ]
663 .join(ENV_VAR_SEP),
664 Some("127.0.0.1:3001,127.0.0.1:3002,127.0.0.1:3003"),
665 ),
666 ],
667 || {
668 let command = StartCommand {
669 config_file: Some(file.path().to_str().unwrap().to_string()),
670 http_addr: Some("127.0.0.1:14000".to_string()),
671 env_prefix: env_prefix.to_string(),
672 ..Default::default()
673 };
674
675 let fe_opts = command.load_options(&Default::default()).unwrap().component;
676
677 assert_eq!(fe_opts.mysql.runtime_size, 11);
679 assert_eq!(
680 fe_opts.meta_client.unwrap().metasrv_addrs,
681 vec![
682 "127.0.0.1:3001".to_string(),
683 "127.0.0.1:3002".to_string(),
684 "127.0.0.1:3003".to_string()
685 ]
686 );
687
688 assert_eq!(fe_opts.mysql.addr, "127.0.0.1:4002");
690
691 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
693
694 assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
696 },
697 );
698 }
699}