1use std::fmt::Debug;
16use std::net::SocketAddr;
17use std::path::Path;
18use std::sync::Arc;
19use std::{fs, path};
20
21use async_trait::async_trait;
22use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
23use catalog::information_schema::InformationExtensionRef;
24use catalog::kvbackend::{CatalogManagerConfiguratorRef, KvBackendCatalogManagerBuilder};
25use catalog::process_manager::ProcessManager;
26use clap::Parser;
27use common_base::Plugins;
28use common_catalog::consts::{MIN_USER_FLOW_ID, MIN_USER_TABLE_ID};
29use common_config::{Configurable, metadata_store_dir};
30use common_error::ext::BoxedError;
31use common_meta::cache::LayeredCacheRegistryBuilder;
32use common_meta::ddl::flow_meta::FlowMetadataAllocator;
33use common_meta::ddl::table_meta::TableMetadataAllocator;
34use common_meta::ddl::{DdlContext, NoopRegionFailureDetectorControl};
35use common_meta::ddl_manager::{DdlManager, DdlManagerConfiguratorRef, DdlManagerRef};
36use common_meta::key::flow::FlowMetadataManager;
37use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
38use common_meta::kv_backend::KvBackendRef;
39use common_meta::node_manager::{FlownodeRef, NodeManagerRef};
40use common_meta::procedure_executor::{LocalProcedureExecutor, ProcedureExecutorRef};
41use common_meta::region_keeper::MemoryRegionKeeper;
42use common_meta::region_registry::LeaderRegionRegistry;
43use common_meta::sequence::{Sequence, SequenceBuilder};
44use common_meta::wal_provider::{WalProviderRef, build_wal_provider};
45use common_options::plugin_options::StandaloneFlag;
46use common_procedure::ProcedureManagerRef;
47use common_query::prelude::set_default_prefix;
48use common_telemetry::info;
49use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
50use common_time::timezone::set_default_timezone;
51use common_version::{short_version, verbose_version};
52use datanode::config::DatanodeOptions;
53use datanode::datanode::{Datanode, DatanodeBuilder};
54use datanode::region_server::RegionServer;
55use flow::{
56 FlownodeBuilder, FlownodeInstance, FlownodeOptions, FrontendClient, FrontendInvoker,
57 GrpcQueryHandlerWithBoxedError,
58};
59use frontend::frontend::Frontend;
60use frontend::instance::StandaloneDatanodeManager;
61use frontend::instance::builder::FrontendBuilder;
62use frontend::server::Services;
63use meta_srv::metasrv::{FLOW_ID_SEQ, TABLE_ID_SEQ};
64use plugins::PluginOptions;
65use plugins::frontend::context::{
66 CatalogManagerConfigureContext, StandaloneCatalogManagerConfigureContext,
67};
68use plugins::standalone::context::DdlManagerConfigureContext;
69use servers::tls::{TlsMode, TlsOption, merge_tls_option};
70use snafu::ResultExt;
71use standalone::options::StandaloneOptions;
72use standalone::{StandaloneInformationExtension, StandaloneRepartitionProcedureFactory};
73use tracing_appender::non_blocking::WorkerGuard;
74
75use crate::error::{OtherSnafu, Result, StartFlownodeSnafu};
76use crate::options::{GlobalOptions, GreptimeOptions};
77use crate::{App, create_resource_limit_metrics, error, log_versions, maybe_activate_heap_profile};
78
79pub const APP_NAME: &str = "greptime-standalone";
80
81#[derive(Parser)]
82pub struct Command {
83 #[clap(subcommand)]
84 subcmd: SubCommand,
85}
86
87impl Command {
88 pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
89 self.subcmd.build(opts).await
90 }
91
92 pub fn load_options(
93 &self,
94 global_options: &GlobalOptions,
95 ) -> Result<GreptimeOptions<StandaloneOptions>> {
96 self.subcmd.load_options(global_options)
97 }
98}
99
100#[derive(Parser)]
101enum SubCommand {
102 Start(StartCommand),
103}
104
105impl SubCommand {
106 async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
107 match self {
108 SubCommand::Start(cmd) => cmd.build(opts).await,
109 }
110 }
111
112 fn load_options(
113 &self,
114 global_options: &GlobalOptions,
115 ) -> Result<GreptimeOptions<StandaloneOptions>> {
116 match self {
117 SubCommand::Start(cmd) => cmd.load_options(global_options),
118 }
119 }
120}
121
122pub struct Instance {
123 datanode: Datanode,
124 frontend: Frontend,
125 flownode: FlownodeInstance,
126 procedure_manager: ProcedureManagerRef,
127 wal_provider: WalProviderRef,
128 _guard: Vec<WorkerGuard>,
130}
131
132impl Instance {
133 pub fn server_addr(&self, name: &str) -> Option<SocketAddr> {
135 self.frontend.server_handlers().addr(name)
136 }
137
138 pub fn mut_frontend(&mut self) -> &mut Frontend {
141 &mut self.frontend
142 }
143
144 pub fn datanode(&self) -> &Datanode {
147 &self.datanode
148 }
149}
150
151#[async_trait]
152impl App for Instance {
153 fn name(&self) -> &str {
154 APP_NAME
155 }
156
157 async fn start(&mut self) -> Result<()> {
158 self.datanode.start_telemetry();
159
160 self.procedure_manager
161 .start()
162 .await
163 .context(error::StartProcedureManagerSnafu)?;
164
165 self.wal_provider
166 .start()
167 .await
168 .context(error::StartWalProviderSnafu)?;
169
170 plugins::start_frontend_plugins(self.frontend.instance.plugins().clone())
171 .await
172 .context(error::StartFrontendSnafu)?;
173
174 self.frontend
175 .start()
176 .await
177 .context(error::StartFrontendSnafu)?;
178
179 self.flownode.start().await.context(StartFlownodeSnafu)?;
180
181 Ok(())
182 }
183
184 async fn stop(&mut self) -> Result<()> {
185 self.frontend
186 .shutdown()
187 .await
188 .context(error::ShutdownFrontendSnafu)?;
189
190 self.procedure_manager
191 .stop()
192 .await
193 .context(error::StopProcedureManagerSnafu)?;
194
195 self.datanode
196 .shutdown()
197 .await
198 .context(error::ShutdownDatanodeSnafu)?;
199
200 self.flownode
201 .shutdown()
202 .await
203 .context(error::ShutdownFlownodeSnafu)?;
204
205 info!("Datanode instance stopped.");
206
207 Ok(())
208 }
209}
210
211#[derive(Debug, Default, Parser)]
212pub struct StartCommand {
213 #[clap(long)]
214 http_addr: Option<String>,
215 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
216 grpc_bind_addr: Option<String>,
217 #[clap(long)]
218 mysql_addr: Option<String>,
219 #[clap(long)]
220 postgres_addr: Option<String>,
221 #[clap(short, long)]
222 influxdb_enable: bool,
223 #[clap(short, long)]
224 pub config_file: Option<String>,
225 #[clap(long)]
226 tls_mode: Option<TlsMode>,
227 #[clap(long)]
228 tls_cert_path: Option<String>,
229 #[clap(long)]
230 tls_key_path: Option<String>,
231 #[clap(long)]
232 tls_watch: bool,
233 #[clap(long)]
234 user_provider: Option<String>,
235 #[clap(long, default_value = "GREPTIMEDB_STANDALONE")]
236 pub env_prefix: String,
237 #[clap(long)]
239 data_home: Option<String>,
240}
241
242impl StartCommand {
243 pub fn load_options(
245 &self,
246 global_options: &GlobalOptions,
247 ) -> Result<GreptimeOptions<StandaloneOptions>> {
248 let mut opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
249 self.config_file.as_deref(),
250 self.env_prefix.as_ref(),
251 )
252 .context(error::LoadLayeredConfigSnafu)?;
253
254 self.merge_with_cli_options(global_options, &mut opts.component)?;
255 opts.component.sanitize();
256
257 Ok(opts)
258 }
259
260 pub fn merge_with_cli_options(
262 &self,
263 global_options: &GlobalOptions,
264 opts: &mut StandaloneOptions,
265 ) -> Result<()> {
266 if let Some(dir) = &global_options.log_dir {
267 opts.logging.dir.clone_from(dir);
268 }
269
270 if global_options.log_level.is_some() {
271 opts.logging.level.clone_from(&global_options.log_level);
272 }
273
274 opts.tracing = TracingOptions {
275 #[cfg(feature = "tokio-console")]
276 tokio_console_addr: global_options.tokio_console_addr.clone(),
277 };
278
279 let tls_opts = TlsOption::new(
280 self.tls_mode,
281 self.tls_cert_path.clone(),
282 self.tls_key_path.clone(),
283 self.tls_watch,
284 );
285
286 if let Some(addr) = &self.http_addr {
287 opts.http.addr.clone_from(addr);
288 }
289
290 if let Some(data_home) = &self.data_home {
291 opts.storage.data_home.clone_from(data_home);
292 }
293
294 if opts.logging.dir.is_empty() {
296 opts.logging.dir = Path::new(&opts.storage.data_home)
297 .join(DEFAULT_LOGGING_DIR)
298 .to_string_lossy()
299 .to_string();
300 }
301
302 if let Some(addr) = &self.grpc_bind_addr {
303 let datanode_grpc_addr = DatanodeOptions::default().grpc.bind_addr;
305 if addr.eq(&datanode_grpc_addr) {
306 return error::IllegalConfigSnafu {
307 msg: format!(
308 "gRPC listen address conflicts with datanode reserved gRPC addr: {datanode_grpc_addr}",
309 ),
310 }.fail();
311 }
312 opts.grpc.bind_addr.clone_from(addr);
313 opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
314 }
315
316 if let Some(addr) = &self.mysql_addr {
317 opts.mysql.enable = true;
318 opts.mysql.addr.clone_from(addr);
319 opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
320 }
321
322 if let Some(addr) = &self.postgres_addr {
323 opts.postgres.enable = true;
324 opts.postgres.addr.clone_from(addr);
325 opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
326 }
327
328 if self.influxdb_enable {
329 opts.influxdb.enable = self.influxdb_enable;
330 }
331
332 if let Some(user_provider) = &self.user_provider {
333 opts.user_provider = Some(user_provider.clone());
334 }
335
336 Ok(())
337 }
338
339 #[allow(unreachable_code)]
340 #[allow(unused_variables)]
341 #[allow(clippy::diverging_sub_expression)]
342 pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
344 common_runtime::init_global_runtimes(&opts.runtime);
345
346 let guard = common_telemetry::init_global_logging(
347 APP_NAME,
348 &opts.component.logging,
349 &opts.component.tracing,
350 None,
351 Some(&opts.component.slow_query),
352 );
353
354 log_versions(verbose_version(), short_version(), APP_NAME);
355 maybe_activate_heap_profile(&opts.component.memory);
356 create_resource_limit_metrics(APP_NAME);
357
358 info!("Standalone start command: {:#?}", self);
359 info!("Standalone options: {opts:#?}");
360
361 let (mut instance, _) =
362 Self::build_with(opts.component, opts.plugins, InstanceCreator::default()).await?;
363 instance._guard.extend(guard);
364 Ok(instance)
365 }
366
367 pub async fn build_with(
368 mut opts: StandaloneOptions,
369 plugin_opts: Vec<PluginOptions>,
370 creator: InstanceCreator,
371 ) -> Result<(Instance, InstanceCreatorResult)> {
372 let mut plugins = Plugins::new();
373 plugins.insert(StandaloneFlag);
374 set_default_prefix(opts.default_column_prefix.as_deref())
375 .map_err(BoxedError::new)
376 .context(error::BuildCliSnafu)?;
377
378 opts.grpc.detect_server_addr();
379 let fe_opts = opts.frontend_options();
380 let dn_opts = opts.datanode_options();
381
382 plugins::setup_frontend_plugins(&mut plugins, &plugin_opts, &fe_opts)
383 .await
384 .context(error::StartFrontendSnafu)?;
385
386 plugins::setup_datanode_plugins(&mut plugins, &plugin_opts, &dn_opts)
387 .await
388 .context(error::StartDatanodeSnafu)?;
389
390 set_default_timezone(fe_opts.default_timezone.as_deref())
391 .context(error::InitTimezoneSnafu)?;
392
393 let data_home = &dn_opts.storage.data_home;
394 fs::create_dir_all(path::Path::new(data_home))
396 .context(error::CreateDirSnafu { dir: data_home })?;
397
398 let metadata_dir = metadata_store_dir(data_home);
399 let kv_backend = creator
400 .metadata_kv_backend_creator
401 .create(metadata_dir, &opts)
402 .await?;
403 let procedure_manager =
404 standalone::build_procedure_manager(kv_backend.clone(), opts.procedure);
405
406 plugins::setup_standalone_plugins(&mut plugins, &plugin_opts, &opts, kv_backend.clone())
407 .await
408 .context(error::SetupStandalonePluginsSnafu)?;
409
410 let layered_cache_builder = LayeredCacheRegistryBuilder::default();
412 let fundamental_cache_registry = build_fundamental_cache_registry(kv_backend.clone());
413 let layered_cache_registry = Arc::new(
414 with_default_composite_cache_registry(
415 layered_cache_builder.add_cache_registry(fundamental_cache_registry),
416 )
417 .context(error::BuildCacheRegistrySnafu)?
418 .build(),
419 );
420
421 let mut builder = DatanodeBuilder::new(dn_opts, plugins.clone(), kv_backend.clone());
422 builder.with_cache_registry(layered_cache_registry.clone());
423 let datanode = builder.build().await.context(error::StartDatanodeSnafu)?;
424
425 let information_extension = Arc::new(StandaloneInformationExtension::new(
426 datanode.region_server(),
427 procedure_manager.clone(),
428 ));
429
430 plugins.insert::<InformationExtensionRef>(information_extension.clone());
431
432 let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
433
434 let (frontend_client, frontend_instance_handler) =
437 FrontendClient::from_empty_grpc_handler(opts.query.clone());
438 let frontend_client = Arc::new(frontend_client);
439
440 let builder = KvBackendCatalogManagerBuilder::new(
441 information_extension.clone(),
442 kv_backend.clone(),
443 layered_cache_registry.clone(),
444 )
445 .with_procedure_manager(procedure_manager.clone())
446 .with_process_manager(process_manager.clone());
447 let builder = if let Some(configurator) =
448 plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
449 {
450 let ctx = StandaloneCatalogManagerConfigureContext {
451 fe_client: frontend_client.clone(),
452 };
453 let ctx = CatalogManagerConfigureContext::Standalone(ctx);
454 configurator
455 .configure(builder, ctx)
456 .await
457 .context(OtherSnafu)?
458 } else {
459 builder
460 };
461 let catalog_manager = builder.build();
462
463 let table_metadata_manager =
464 Self::create_table_metadata_manager(kv_backend.clone()).await?;
465
466 let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
467 let flownode_options = FlownodeOptions {
468 flow: opts.flow.clone(),
469 ..Default::default()
470 };
471
472 let flow_builder = FlownodeBuilder::new(
473 flownode_options,
474 plugins.clone(),
475 table_metadata_manager.clone(),
476 catalog_manager.clone(),
477 flow_metadata_manager.clone(),
478 frontend_client.clone(),
479 );
480 let flownode = flow_builder
481 .build()
482 .await
483 .map_err(BoxedError::new)
484 .context(error::OtherSnafu)?;
485
486 {
488 information_extension
489 .set_flow_engine(flownode.flow_engine())
490 .await;
491 }
492
493 let node_manager = creator
494 .node_manager_creator
495 .create(
496 &kv_backend,
497 datanode.region_server(),
498 flownode.flow_engine(),
499 )
500 .await?;
501
502 let table_id_allocator = creator.table_id_allocator_creator.create(&kv_backend);
503 let flow_id_sequence = Arc::new(
504 SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone())
505 .initial(MIN_USER_FLOW_ID as u64)
506 .step(10)
507 .build(),
508 );
509 let kafka_options = opts
510 .wal
511 .clone()
512 .try_into()
513 .context(error::InvalidWalProviderSnafu)?;
514 let wal_provider = build_wal_provider(&kafka_options, kv_backend.clone())
515 .await
516 .context(error::BuildWalProviderSnafu)?;
517 let wal_provider = Arc::new(wal_provider);
518 let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
519 table_id_allocator.clone(),
520 wal_provider.clone(),
521 ));
522 let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
523 flow_id_sequence,
524 ));
525
526 let ddl_context = DdlContext {
527 node_manager: node_manager.clone(),
528 cache_invalidator: layered_cache_registry.clone(),
529 memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
530 leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
531 table_metadata_manager: table_metadata_manager.clone(),
532 table_metadata_allocator: table_metadata_allocator.clone(),
533 flow_metadata_manager: flow_metadata_manager.clone(),
534 flow_metadata_allocator: flow_metadata_allocator.clone(),
535 region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
536 };
537
538 let ddl_manager = DdlManager::try_new(
539 ddl_context,
540 procedure_manager.clone(),
541 Arc::new(StandaloneRepartitionProcedureFactory),
542 true,
543 )
544 .context(error::InitDdlManagerSnafu)?;
545
546 let ddl_manager = if let Some(configurator) =
547 plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
548 {
549 let ctx = DdlManagerConfigureContext {
550 kv_backend: kv_backend.clone(),
551 fe_client: frontend_client.clone(),
552 catalog_manager: catalog_manager.clone(),
553 };
554 configurator
555 .configure(ddl_manager, ctx)
556 .await
557 .context(OtherSnafu)?
558 } else {
559 ddl_manager
560 };
561
562 let procedure_executor = creator
563 .procedure_executor_creator
564 .create(Arc::new(ddl_manager), procedure_manager.clone())
565 .await?;
566
567 let fe_instance = FrontendBuilder::new(
568 fe_opts.clone(),
569 kv_backend.clone(),
570 layered_cache_registry.clone(),
571 catalog_manager.clone(),
572 node_manager.clone(),
573 procedure_executor.clone(),
574 process_manager,
575 )
576 .with_plugin(plugins.clone())
577 .try_build()
578 .await
579 .context(error::StartFrontendSnafu)?;
580 let fe_instance = Arc::new(fe_instance);
581
582 let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
584 let weak_grpc_handler = Arc::downgrade(&grpc_handler);
585 frontend_instance_handler
586 .set_handler(weak_grpc_handler)
587 .await;
588
589 let flow_streaming_engine = flownode.flow_engine().streaming_engine();
591 let invoker = FrontendInvoker::build_from(
593 flow_streaming_engine.clone(),
594 catalog_manager.clone(),
595 kv_backend.clone(),
596 layered_cache_registry.clone(),
597 procedure_executor,
598 node_manager.clone(),
599 )
600 .await
601 .context(StartFlownodeSnafu)?;
602 flow_streaming_engine.set_frontend_invoker(invoker).await;
603
604 let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
605 .build()
606 .context(error::StartFrontendSnafu)?;
607
608 let frontend = Frontend {
609 instance: fe_instance,
610 servers,
611 heartbeat_task: None,
612 };
613
614 let instance = Instance {
615 datanode,
616 frontend,
617 flownode,
618 procedure_manager,
619 wal_provider,
620 _guard: vec![],
621 };
622 let result = InstanceCreatorResult {
623 kv_backend,
624 node_manager,
625 table_id_allocator,
626 };
627 Ok((instance, result))
628 }
629
630 pub async fn create_table_metadata_manager(
631 kv_backend: KvBackendRef,
632 ) -> Result<TableMetadataManagerRef> {
633 let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
634
635 table_metadata_manager
636 .init()
637 .await
638 .context(error::InitMetadataSnafu)?;
639
640 Ok(table_metadata_manager)
641 }
642}
643
644#[async_trait]
645pub trait NodeManagerCreator {
646 async fn create(
647 &self,
648 kv_backend: &KvBackendRef,
649 region_server: RegionServer,
650 flow_server: FlownodeRef,
651 ) -> Result<NodeManagerRef>;
652}
653
654pub struct DefaultNodeManagerCreator;
655
656#[async_trait]
657impl NodeManagerCreator for DefaultNodeManagerCreator {
658 async fn create(
659 &self,
660 _: &KvBackendRef,
661 region_server: RegionServer,
662 flow_server: FlownodeRef,
663 ) -> Result<NodeManagerRef> {
664 Ok(Arc::new(StandaloneDatanodeManager {
665 region_server,
666 flow_server,
667 }))
668 }
669}
670
671#[async_trait]
677pub trait MetadataKvBackendCreator: Send + Sync {
678 async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef>;
679}
680
681pub struct DefaultMetadataKvBackendCreator;
682
683#[async_trait]
684impl MetadataKvBackendCreator for DefaultMetadataKvBackendCreator {
685 async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef> {
686 standalone::build_metadata_kvbackend(metadata_dir, opts.metadata_store)
687 .context(error::BuildMetadataKvbackendSnafu)
688 }
689}
690
691pub trait TableIdAllocatorCreator {
692 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
693}
694
695struct DefaultTableIdAllocatorCreator;
696
697impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
698 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
699 Arc::new(
700 SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
701 .initial(MIN_USER_TABLE_ID as u64)
702 .step(10)
703 .build(),
704 )
705 }
706}
707
708#[async_trait]
709pub trait ProcedureExecutorCreator {
710 async fn create(
711 &self,
712 ddl_manager: DdlManagerRef,
713 procedure_manager: ProcedureManagerRef,
714 ) -> Result<ProcedureExecutorRef>;
715}
716
717pub struct DefaultProcedureExecutorCreator;
718
719#[async_trait]
720impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
721 async fn create(
722 &self,
723 ddl_manager: DdlManagerRef,
724 procedure_manager: ProcedureManagerRef,
725 ) -> Result<ProcedureExecutorRef> {
726 Ok(Arc::new(LocalProcedureExecutor::new(
727 ddl_manager,
728 procedure_manager,
729 )))
730 }
731}
732
733pub struct InstanceCreator {
736 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
739 node_manager_creator: Box<dyn NodeManagerCreator>,
740 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
741 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
742}
743
744impl InstanceCreator {
745 pub fn new(
746 node_manager_creator: Box<dyn NodeManagerCreator>,
747 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
748 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
749 ) -> Self {
750 Self {
751 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
752 node_manager_creator,
753 table_id_allocator_creator,
754 procedure_executor_creator,
755 }
756 }
757
758 pub fn with_metadata_kv_backend_creator(
759 mut self,
760 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
761 ) -> Self {
762 self.metadata_kv_backend_creator = metadata_kv_backend_creator;
763 self
764 }
765}
766
767impl Default for InstanceCreator {
768 fn default() -> Self {
769 Self {
770 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
771 node_manager_creator: Box::new(DefaultNodeManagerCreator),
772 table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
773 procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
774 }
775 }
776}
777
778pub struct InstanceCreatorResult {
781 pub kv_backend: KvBackendRef,
782 pub node_manager: NodeManagerRef,
783 pub table_id_allocator: Arc<Sequence>,
784}
785
786#[cfg(test)]
787mod tests {
788 use std::default::Default;
789 use std::io::Write;
790 use std::time::Duration;
791
792 use auth::{Identity, Password, UserProviderRef};
793 use clap::{CommandFactory, Parser};
794 use common_base::readable_size::ReadableSize;
795 use common_config::ENV_VAR_SEP;
796 use common_options::plugin_options::StandaloneFlag;
797 use common_test_util::temp_dir::create_named_temp_file;
798 use common_wal::config::DatanodeWalConfig;
799 use frontend::frontend::FrontendOptions;
800 use object_store::config::{FileConfig, GcsConfig};
801 use servers::grpc::GrpcOptions;
802
803 use super::*;
804 use crate::options::GlobalOptions;
805
806 #[tokio::test]
807 async fn test_try_from_start_command_to_anymap() {
808 let fe_opts = FrontendOptions {
809 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
810 ..Default::default()
811 };
812
813 let mut plugins = Plugins::new();
814 plugins.insert(StandaloneFlag);
815 plugins::setup_frontend_plugins(&mut plugins, &[], &fe_opts)
816 .await
817 .unwrap();
818
819 let provider = plugins.get::<UserProviderRef>().unwrap();
820 let result = provider
821 .authenticate(
822 Identity::UserId("test", None),
823 Password::PlainText("test".to_string().into()),
824 )
825 .await;
826 let _ = result.unwrap();
827 }
828
829 #[test]
830 fn test_toml() {
831 let opts = StandaloneOptions::default();
832 let toml_string = toml::to_string(&opts).unwrap();
833 let _parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
834 }
835
836 #[test]
837 fn test_read_from_config_file() {
838 let mut file = create_named_temp_file();
839 let toml_str = r#"
840 enable_memory_catalog = true
841
842 [wal]
843 provider = "raft_engine"
844 dir = "./greptimedb_data/test/wal"
845 file_size = "1GB"
846 purge_threshold = "50GB"
847 purge_interval = "10m"
848 read_batch_size = 128
849 sync_write = false
850
851 [storage]
852 data_home = "./greptimedb_data/"
853 type = "File"
854
855 [[storage.providers]]
856 type = "Gcs"
857 bucket = "foo"
858 endpoint = "bar"
859
860 [[storage.providers]]
861 type = "S3"
862 access_key_id = "access_key_id"
863 secret_access_key = "secret_access_key"
864
865 [storage.compaction]
866 max_inflight_tasks = 3
867 max_files_in_level0 = 7
868 max_purge_tasks = 32
869
870 [storage.manifest]
871 checkpoint_margin = 9
872 gc_duration = '7s'
873
874 [http]
875 addr = "127.0.0.1:4000"
876 timeout = "33s"
877 body_limit = "128MB"
878
879 [opentsdb]
880 enable = true
881
882 [logging]
883 level = "debug"
884 dir = "./greptimedb_data/test/logs"
885 "#;
886 write!(file, "{}", toml_str).unwrap();
887 let cmd = StartCommand {
888 config_file: Some(file.path().to_str().unwrap().to_string()),
889 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
890 ..Default::default()
891 };
892
893 let options = cmd
894 .load_options(&GlobalOptions::default())
895 .unwrap()
896 .component;
897 let fe_opts = options.frontend_options();
898 let dn_opts = options.datanode_options();
899 let logging_opts = options.logging;
900 assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
901 assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
902 assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
903 assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
904 assert!(fe_opts.mysql.enable);
905 assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
906 assert_eq!(2, fe_opts.mysql.runtime_size);
907 assert_eq!(None, fe_opts.mysql.reject_no_database);
908 assert!(fe_opts.influxdb.enable);
909 assert!(fe_opts.opentsdb.enable);
910
911 let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
912 unreachable!()
913 };
914 assert_eq!(
915 "./greptimedb_data/test/wal",
916 raft_engine_config.dir.unwrap()
917 );
918
919 assert!(matches!(
920 &dn_opts.storage.store,
921 object_store::config::ObjectStoreConfig::File(FileConfig { .. })
922 ));
923 assert_eq!(dn_opts.storage.providers.len(), 2);
924 assert!(matches!(
925 dn_opts.storage.providers[0],
926 object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
927 ));
928 match &dn_opts.storage.providers[1] {
929 object_store::config::ObjectStoreConfig::S3(s3_config) => {
930 assert_eq!(
931 "SecretBox<alloc::string::String>([REDACTED])".to_string(),
932 format!("{:?}", s3_config.connection.access_key_id)
933 );
934 }
935 _ => {
936 unreachable!()
937 }
938 }
939
940 assert_eq!("debug", logging_opts.level.as_ref().unwrap());
941 assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
942 }
943
944 #[test]
945 fn test_load_log_options_from_cli() {
946 let cmd = StartCommand {
947 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
948 mysql_addr: Some("127.0.0.1:4002".to_string()),
949 postgres_addr: Some("127.0.0.1:4003".to_string()),
950 ..Default::default()
951 };
952
953 let opts = cmd
954 .load_options(&GlobalOptions {
955 log_dir: Some("./greptimedb_data/test/logs".to_string()),
956 log_level: Some("debug".to_string()),
957
958 #[cfg(feature = "tokio-console")]
959 tokio_console_addr: None,
960 })
961 .unwrap()
962 .component;
963
964 assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
965 assert_eq!("debug", opts.logging.level.unwrap());
966 }
967
968 #[test]
969 fn test_config_precedence_order() {
970 let mut file = create_named_temp_file();
971 let toml_str = r#"
972 [http]
973 addr = "127.0.0.1:4000"
974
975 [logging]
976 level = "debug"
977 "#;
978 write!(file, "{}", toml_str).unwrap();
979
980 let env_prefix = "STANDALONE_UT";
981 temp_env::with_vars(
982 [
983 (
984 [
986 env_prefix.to_string(),
987 "logging".to_uppercase(),
988 "dir".to_uppercase(),
989 ]
990 .join(ENV_VAR_SEP),
991 Some("/other/log/dir"),
992 ),
993 (
994 [
996 env_prefix.to_string(),
997 "logging".to_uppercase(),
998 "level".to_uppercase(),
999 ]
1000 .join(ENV_VAR_SEP),
1001 Some("info"),
1002 ),
1003 (
1004 [
1006 env_prefix.to_string(),
1007 "http".to_uppercase(),
1008 "addr".to_uppercase(),
1009 ]
1010 .join(ENV_VAR_SEP),
1011 Some("127.0.0.1:24000"),
1012 ),
1013 ],
1014 || {
1015 let command = StartCommand {
1016 config_file: Some(file.path().to_str().unwrap().to_string()),
1017 http_addr: Some("127.0.0.1:14000".to_string()),
1018 env_prefix: env_prefix.to_string(),
1019 ..Default::default()
1020 };
1021
1022 let opts = command.load_options(&Default::default()).unwrap().component;
1023
1024 assert_eq!(opts.logging.dir, "/other/log/dir");
1026
1027 assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
1029
1030 let fe_opts = opts.frontend_options();
1032 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
1033 assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
1034
1035 assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
1037 },
1038 );
1039 }
1040
1041 #[test]
1042 fn test_parse_grpc_bind_addr_aliases() {
1043 let command =
1044 StartCommand::try_parse_from(["standalone", "--grpc-bind-addr", "127.0.0.1:14001"])
1045 .unwrap();
1046 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
1047
1048 let command =
1049 StartCommand::try_parse_from(["standalone", "--rpc-bind-addr", "127.0.0.1:24001"])
1050 .unwrap();
1051 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1052
1053 let command =
1054 StartCommand::try_parse_from(["standalone", "--rpc-addr", "127.0.0.1:34001"]).unwrap();
1055 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1056 }
1057
1058 #[test]
1059 fn test_help_uses_grpc_option_names() {
1060 let mut cmd = StartCommand::command();
1061 let mut help = Vec::new();
1062 cmd.write_long_help(&mut help).unwrap();
1063 let help = String::from_utf8(help).unwrap();
1064
1065 assert!(help.contains("--grpc-bind-addr"));
1066 assert!(!help.contains("--rpc-bind-addr"));
1067 assert!(!help.contains("--rpc-addr"));
1068 }
1069
1070 #[test]
1071 fn test_load_default_standalone_options() {
1072 let options =
1073 StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1074 let default_options = StandaloneOptions::default();
1075 assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1076 assert_eq!(options.http, default_options.http);
1077 assert_eq!(options.grpc, default_options.grpc);
1078 assert_eq!(options.mysql, default_options.mysql);
1079 assert_eq!(options.postgres, default_options.postgres);
1080 assert_eq!(options.opentsdb, default_options.opentsdb);
1081 assert_eq!(options.influxdb, default_options.influxdb);
1082 assert_eq!(options.prom_store, default_options.prom_store);
1083 assert_eq!(options.wal, default_options.wal);
1084 assert_eq!(options.metadata_store, default_options.metadata_store);
1085 assert_eq!(options.procedure, default_options.procedure);
1086 assert_eq!(options.logging, default_options.logging);
1087 assert_eq!(options.region_engine, default_options.region_engine);
1088 }
1089
1090 #[test]
1091 fn test_cache_config() {
1092 let toml_str = r#"
1093 [storage]
1094 data_home = "test_data_home"
1095 type = "S3"
1096 [storage.cache_config]
1097 enable_read_cache = true
1098 "#;
1099 let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1100 opts.sanitize();
1101 assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1102 assert_eq!(
1103 opts.storage.store.cache_config().unwrap().cache_path,
1104 "test_data_home"
1105 );
1106 }
1107}