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::CatalogManagerRef;
24use catalog::information_schema::InformationExtensionRef;
25use catalog::kvbackend::{CatalogManagerConfiguratorRef, KvBackendCatalogManagerBuilder};
26use catalog::process_manager::ProcessManager;
27use clap::Parser;
28use common_base::Plugins;
29use common_base::secrets::SecretString;
30use common_catalog::consts::{MIN_USER_FLOW_ID, MIN_USER_TABLE_ID};
31use common_config::{Configurable, metadata_store_dir};
32use common_datasource::object_store::{LocalFileAccess, configured_local_path};
33use common_error::ext::BoxedError;
34use common_meta::DatanodeId;
35use common_meta::cache::{LayeredCacheRegistryBuilder, LayeredCacheRegistryRef};
36use common_meta::ddl::flow_meta::FlowMetadataAllocator;
37use common_meta::ddl::table_meta::TableMetadataAllocator;
38use common_meta::ddl::{DdlContext, NoopRegionFailureDetectorControl};
39use common_meta::ddl_manager::{DdlManager, DdlManagerConfiguratorRef, DdlManagerRef};
40use common_meta::key::flow::FlowMetadataManager;
41use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
42use common_meta::kv_backend::{ClosableKvBackendRef, KvBackendRef};
43use common_meta::node_manager::{FlownodeRef, NodeManagerRef};
44use common_meta::procedure_executor::{LocalProcedureExecutor, ProcedureExecutorRef};
45use common_meta::region_keeper::MemoryRegionKeeper;
46use common_meta::region_registry::LeaderRegionRegistry;
47use common_meta::sequence::{Sequence, SequenceBuilder};
48use common_meta::wal_provider::{WalProvider, WalProviderRef, build_wal_provider};
49use common_options::plugin_options::StandaloneFlag;
50use common_procedure::ProcedureManagerRef;
51use common_query::prelude::set_default_prefix;
52use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
53use common_telemetry::{info, warn};
54use common_time::timezone::set_default_timezone;
55use common_version::{short_version, verbose_version};
56use common_wal::config::DatanodeWalConfig;
57use common_wal::config::object_store::STANDALONE_GENERATION;
58use datanode::config::{DatanodeOptions, StorageConfig};
59use datanode::datanode::{Datanode, DatanodeBuilder};
60use datanode::region_server::RegionServer;
61use flow::{
62 FlowDualEngineRef, FlownodeBuilder, FlownodeInstance, FlownodeOptions, FrontendClient,
63 GrpcQueryHandlerWithBoxedError,
64};
65use frontend::frontend::Frontend;
66use frontend::instance::builder::FrontendBuilder;
67use frontend::server::Services;
68use meta_srv::metasrv::{FLOW_ID_SEQ, TABLE_ID_SEQ};
69use plugins::PluginOptions;
70use plugins::frontend::context::{
71 CatalogManagerConfigureContext, StandaloneCatalogManagerConfigureContext,
72};
73use plugins::standalone::context::DdlManagerConfigureContext;
74use servers::tls::{TlsMode, TlsOption, merge_tls_option};
75use snafu::{OptionExt, ResultExt};
76use standalone::options::StandaloneOptions;
77use standalone::{
78 StandaloneDatanodeManager, StandaloneInformationExtension,
79 StandaloneRepartitionProcedureFactory,
80};
81use tracing_appender::non_blocking::WorkerGuard;
82
83use crate::error::{OtherSnafu, Result, StartFlownodeSnafu};
84use crate::options::{GlobalOptions, GreptimeOptions};
85use crate::{App, create_resource_limit_metrics, error, log_versions, maybe_activate_heap_profile};
86
87pub const APP_NAME: &str = "greptime-standalone";
88
89pub async fn build_standalone_wal_provider(
95 wal: &DatanodeWalConfig,
96 node_id: Option<DatanodeId>,
97 kv_backend: KvBackendRef,
98) -> Result<WalProvider> {
99 match wal {
100 DatanodeWalConfig::ObjectStore(config) => Ok(WalProvider::ObjectStore {
101 prefix: config.node_prefix(node_id.unwrap_or(0), STANDALONE_GENERATION),
102 }),
103 DatanodeWalConfig::RaftEngine(_)
104 | DatanodeWalConfig::Kafka(_)
105 | DatanodeWalConfig::Noop => {
106 let metasrv_wal_config = wal
107 .clone()
108 .try_into()
109 .context(error::InvalidWalProviderSnafu)?;
110 build_wal_provider(&metasrv_wal_config, kv_backend)
111 .await
112 .context(error::BuildWalProviderSnafu)
113 }
114 }
115}
116
117fn standalone_local_file_access(
118 storage: &StorageConfig,
119) -> common_datasource::error::Result<LocalFileAccess> {
120 let data_home = configured_local_path(&storage.data_home)?;
121 let copy_root = match &storage.copy_root {
122 Some(root) => configured_local_path(root)?.with_context(|| {
123 common_datasource::error::InvalidLocalFileRootConfigSnafu {
124 root: root.clone(),
125 reason: "copy_root must be a local path or file URL".to_string(),
126 }
127 })?,
128 None => {
129 let Some(data_home) = &data_home else {
130 info!(
131 "SQL access to local files is disabled because storage.data_home is not a local path and storage.copy_root is unset"
132 );
133 return Ok(LocalFileAccess::Disabled);
134 };
135 data_home.join("copy")
136 }
137 };
138
139 let access = LocalFileAccess::sandboxed(©_root)?;
140 if let Some(data_home) = data_home {
141 let canonical_data_home = data_home.canonicalize().with_context(|_| {
142 common_datasource::error::InvalidLocalFileRootSnafu {
143 root: data_home.display().to_string(),
144 }
145 })?;
146 let canonical_copy_root = access.sandbox_root().with_context(|| {
147 common_datasource::error::InvalidLocalFileRootConfigSnafu {
148 root: copy_root.display().to_string(),
149 reason: "sandboxed local file access has no root".to_string(),
150 }
151 })?;
152 let default_copy_root = canonical_data_home.join("copy");
153 let exposes_internal_files = canonical_data_home.starts_with(canonical_copy_root)
154 || (canonical_copy_root.starts_with(&canonical_data_home)
155 && !canonical_copy_root.starts_with(default_copy_root));
156 if exposes_internal_files {
157 return common_datasource::error::InvalidLocalFileRootConfigSnafu {
158 root: copy_root.display().to_string(),
159 reason: "copy_root must not expose files in data_home outside data_home/copy"
160 .to_string(),
161 }
162 .fail();
163 }
164 }
165
166 Ok(access)
167}
168
169#[derive(Parser)]
170pub struct Command {
171 #[clap(subcommand)]
172 subcmd: SubCommand,
173}
174
175impl Command {
176 pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
177 self.subcmd.build(opts).await
178 }
179
180 pub fn load_options(
181 &self,
182 global_options: &GlobalOptions,
183 ) -> Result<GreptimeOptions<StandaloneOptions>> {
184 self.subcmd.load_options(global_options)
185 }
186
187 pub fn is_daemon(&self) -> bool {
189 self.subcmd.is_daemon()
190 }
191}
192
193#[derive(Parser)]
194enum SubCommand {
195 Start(StartCommand),
196}
197
198impl SubCommand {
199 async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
200 match self {
201 SubCommand::Start(cmd) => cmd.build(opts).await,
202 }
203 }
204
205 fn load_options(
206 &self,
207 global_options: &GlobalOptions,
208 ) -> Result<GreptimeOptions<StandaloneOptions>> {
209 match self {
210 SubCommand::Start(cmd) => cmd.load_options(global_options),
211 }
212 }
213
214 fn is_daemon(&self) -> bool {
215 match self {
216 SubCommand::Start(cmd) => cmd.is_daemon(),
217 }
218 }
219}
220
221pub struct Instance {
222 datanode: Datanode,
223 frontend: Frontend,
224 flownode: FlownodeInstance,
225 procedure_manager: ProcedureManagerRef,
226 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
227 leader_services_context: LeaderServicesContext,
228 metadata_kv_backend: ClosableKvBackendRef,
230 _guard: Vec<WorkerGuard>,
232}
233
234impl Instance {
235 pub fn server_addr(&self, name: &str) -> Option<SocketAddr> {
237 self.frontend.server_handlers().addr(name)
238 }
239
240 pub fn mut_frontend(&mut self) -> &mut Frontend {
243 &mut self.frontend
244 }
245
246 pub fn datanode(&self) -> &Datanode {
249 &self.datanode
250 }
251}
252
253#[async_trait]
254impl App for Instance {
255 fn name(&self) -> &str {
256 APP_NAME
257 }
258
259 async fn start(&mut self) -> Result<()> {
260 self.datanode.start_telemetry();
261
262 self.leader_services_controller
263 .start(self.leader_services_context.clone())
264 .await?;
265
266 plugins::start_frontend_plugins(&self.frontend.instance)
267 .await
268 .context(error::StartFrontendSnafu)?;
269
270 self.frontend
271 .start()
272 .await
273 .context(error::StartFrontendSnafu)?;
274
275 self.flownode.start().await.context(StartFlownodeSnafu)?;
276
277 Ok(())
278 }
279
280 async fn stop(&mut self) -> Result<()> {
283 let mut first_error = None;
284 let mut record_shutdown_error = |result: Result<()>| {
285 if let Err(err) = result {
286 if first_error.is_none() {
287 first_error = Some(err);
288 } else {
289 warn!(err; "Ignored a later shutdown error");
290 }
291 }
292 };
293
294 record_shutdown_error(
295 self.frontend
296 .shutdown()
297 .await
298 .context(error::ShutdownFrontendSnafu),
299 );
300
301 record_shutdown_error(
302 self.leader_services_controller
303 .stop(
304 self.procedure_manager.clone(),
305 self.datanode.region_server(),
306 )
307 .await,
308 );
309
310 record_shutdown_error(
311 self.datanode
312 .shutdown()
313 .await
314 .context(error::ShutdownDatanodeSnafu),
315 );
316
317 record_shutdown_error(
318 self.flownode
319 .shutdown()
320 .await
321 .context(error::ShutdownFlownodeSnafu),
322 );
323
324 record_shutdown_error(
326 self.metadata_kv_backend
327 .close()
328 .await
329 .context(error::CloseMetadataKvBackendSnafu),
330 );
331
332 info!("Datanode instance stopped.");
333
334 first_error.map_or(Ok(()), Err)
335 }
336}
337
338#[derive(Debug, Default, Parser)]
339pub struct StartCommand {
340 #[clap(long)]
341 http_addr: Option<String>,
342 #[clap(long)]
345 http_api_server_addr: Option<String>,
346 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
347 grpc_bind_addr: Option<String>,
348 #[clap(long)]
349 mysql_addr: Option<String>,
350 #[clap(long)]
351 postgres_addr: Option<String>,
352 #[clap(short, long)]
353 influxdb_enable: bool,
354 #[clap(short, long)]
355 pub config_file: Option<String>,
356 #[clap(long)]
357 tls_mode: Option<TlsMode>,
358 #[clap(long)]
359 tls_cert_path: Option<String>,
360 #[clap(long)]
361 tls_key_path: Option<String>,
362 #[clap(long)]
363 tls_watch: bool,
364 #[clap(long)]
365 user_provider: Option<SecretString>,
366 #[clap(long, default_value = "GREPTIMEDB_STANDALONE")]
367 pub env_prefix: String,
368 #[clap(long)]
370 data_home: Option<String>,
371 #[cfg(unix)]
373 #[clap(short, long)]
374 daemon: bool,
375}
376
377impl StartCommand {
378 #[cfg(unix)]
380 pub(crate) fn is_daemon(&self) -> bool {
381 self.daemon
382 }
383
384 #[cfg(not(unix))]
385 pub(crate) fn is_daemon(&self) -> bool {
386 false
387 }
388
389 pub fn load_options(
391 &self,
392 global_options: &GlobalOptions,
393 ) -> Result<GreptimeOptions<StandaloneOptions>> {
394 let mut opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
395 self.config_file.as_deref(),
396 self.env_prefix.as_ref(),
397 )
398 .context(error::LoadLayeredConfigSnafu)?;
399
400 self.merge_with_cli_options(global_options, &mut opts.component)?;
401 opts.component.sanitize();
402
403 Ok(opts)
404 }
405
406 pub fn merge_with_cli_options(
408 &self,
409 global_options: &GlobalOptions,
410 opts: &mut StandaloneOptions,
411 ) -> Result<()> {
412 if let Some(dir) = &global_options.log_dir {
413 opts.logging.dir.clone_from(dir);
414 }
415
416 if global_options.log_level.is_some() {
417 opts.logging.level.clone_from(&global_options.log_level);
418 }
419
420 opts.tracing = TracingOptions {
421 #[cfg(feature = "tokio-console")]
422 tokio_console_addr: global_options.tokio_console_addr.clone(),
423 };
424
425 let tls_opts = TlsOption::new(
426 self.tls_mode,
427 self.tls_cert_path.clone(),
428 self.tls_key_path.clone(),
429 self.tls_watch,
430 );
431
432 if let Some(addr) = &self.http_addr {
433 opts.http.addr.clone_from(addr);
434 }
435
436 if let Some(addr) = &self.http_api_server_addr {
437 opts.http.api_server_addr.clone_from(addr);
438 opts.http.enable_api_server = true;
439 }
440
441 if let Some(data_home) = &self.data_home {
442 opts.storage.data_home.clone_from(data_home);
443 }
444
445 if opts.logging.dir.is_empty() {
447 opts.logging.dir = Path::new(&opts.storage.data_home)
448 .join(DEFAULT_LOGGING_DIR)
449 .to_string_lossy()
450 .to_string();
451 }
452
453 if let Some(addr) = &self.grpc_bind_addr {
454 let datanode_grpc_addr = DatanodeOptions::default().grpc.bind_addr;
456 if addr.eq(&datanode_grpc_addr) {
457 return error::IllegalConfigSnafu {
458 msg: format!(
459 "gRPC listen address conflicts with datanode reserved gRPC addr: {datanode_grpc_addr}",
460 ),
461 }.fail();
462 }
463 opts.grpc.bind_addr.clone_from(addr);
464 opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
465 }
466
467 if let Some(addr) = &self.mysql_addr {
468 opts.mysql.enable = true;
469 opts.mysql.addr.clone_from(addr);
470 opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
471 }
472
473 if let Some(addr) = &self.postgres_addr {
474 opts.postgres.enable = true;
475 opts.postgres.addr.clone_from(addr);
476 opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
477 }
478
479 if self.influxdb_enable {
480 opts.influxdb.enable = self.influxdb_enable;
481 }
482
483 if let Some(user_provider) = &self.user_provider {
484 opts.user_provider = Some(user_provider.clone());
485 }
486
487 Ok(())
488 }
489
490 #[allow(unreachable_code)]
491 #[allow(unused_variables)]
492 #[allow(clippy::diverging_sub_expression)]
493 pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
495 let guard = common_telemetry::init_global_logging(
496 APP_NAME,
497 &opts.component.logging,
498 &opts.component.tracing,
499 None,
500 Some(&opts.component.slow_query),
501 );
502
503 common_runtime::init_standalone_runtimes(&opts.runtime);
504
505 crate::options::flush_dropped_plugin_warnings();
506 log_versions(verbose_version(), short_version(), APP_NAME);
507 maybe_activate_heap_profile(&opts.component.memory);
508 create_resource_limit_metrics(APP_NAME);
509
510 info!("Standalone start command: {:#?}", self);
511 info!("Standalone options: {opts:#?}");
512
513 let (mut instance, _) =
514 Self::build_with(opts.component, opts.plugins, InstanceCreator::default()).await?;
515 instance._guard.extend(guard);
516 Ok(instance)
517 }
518
519 pub async fn build_with(
520 mut opts: StandaloneOptions,
521 plugin_opts: Vec<PluginOptions>,
522 creator: InstanceCreator,
523 ) -> Result<(Instance, InstanceCreatorResult)> {
524 let mut plugins = Plugins::new();
525 plugins.insert(StandaloneFlag);
526 set_default_prefix(opts.default_column_prefix.as_deref())
527 .map_err(BoxedError::new)
528 .context(error::BuildCliSnafu)?;
529
530 opts.grpc.detect_server_addr();
531 let fe_opts = opts.frontend_options();
532 let dn_opts = opts.datanode_options();
533 let node_id = dn_opts.node_id;
534 let init_regions_parallelism = dn_opts.init_regions_parallelism;
535
536 plugins::setup_frontend_plugins_pre_build(&mut plugins, &plugin_opts, &fe_opts, None)
537 .await
538 .context(error::StartFrontendSnafu)?;
539
540 plugins::setup_datanode_plugins_pre_build(&mut plugins, &plugin_opts, &dn_opts)
541 .await
542 .context(error::StartDatanodeSnafu)?;
543
544 set_default_timezone(fe_opts.default_timezone.as_deref())
545 .context(error::InitTimezoneSnafu)?;
546
547 let data_home = &dn_opts.storage.data_home;
548 fs::create_dir_all(path::Path::new(data_home))
550 .context(error::CreateDirSnafu { dir: data_home })?;
551 let local_file_access = standalone_local_file_access(&dn_opts.storage)
552 .map_err(BoxedError::new)
553 .context(OtherSnafu)?;
554
555 let metadata_dir = metadata_store_dir(data_home);
556 let metadata_kv_backend = creator
557 .metadata_kv_backend_creator
558 .create(metadata_dir, &opts)
559 .await?;
560 let kv_backend: KvBackendRef = metadata_kv_backend.clone();
561 let (procedure_manager, event_recorder_handle) =
562 standalone::build_procedure_manager(kv_backend.clone(), opts.procedure);
563
564 plugins::setup_standalone_plugins(&mut plugins, &plugin_opts, &opts, kv_backend.clone())
565 .await
566 .context(error::SetupStandalonePluginsSnafu)?;
567
568 let layered_cache_builder = LayeredCacheRegistryBuilder::default();
570 let fundamental_cache_registry = build_fundamental_cache_registry(kv_backend.clone());
571 let mut layered_cache_builder = with_default_composite_cache_registry(
572 layered_cache_builder.add_cache_registry(fundamental_cache_registry),
573 )
574 .context(error::BuildCacheRegistrySnafu)?;
575
576 if let Some(plugin_cache_builder) = plugins::standalone::configure_cache_registry(&plugins)
577 {
578 layered_cache_builder =
579 layered_cache_builder.add_cache_registry(plugin_cache_builder.build());
580 }
581
582 let layered_cache_registry = Arc::new(layered_cache_builder.build());
583
584 let mut builder = DatanodeBuilder::new(dn_opts, plugins.clone(), kv_backend.clone());
585 builder.with_cache_registry(layered_cache_registry.clone());
586 builder.with_local_file_access(local_file_access.clone());
587 if let Some(writable) = creator.open_regions_writable_override {
588 builder.with_open_regions_writable_override(writable);
589 }
590
591 plugins::setup_datanode_plugins_post_build(&mut plugins, &plugin_opts, &builder)
592 .await
593 .context(error::StartDatanodeSnafu)?;
594 builder.set_plugins(plugins.clone());
595
596 let datanode = builder.build().await.context(error::StartDatanodeSnafu)?;
597
598 let information_extension = Arc::new(StandaloneInformationExtension::new(
599 datanode.region_server(),
600 procedure_manager.clone(),
601 ));
602
603 plugins.insert::<InformationExtensionRef>(information_extension.clone());
604
605 let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
606
607 let (frontend_client, frontend_instance_handler) =
610 FrontendClient::from_empty_grpc_handler(opts.query.clone());
611 let frontend_client = Arc::new(frontend_client);
612
613 let builder = KvBackendCatalogManagerBuilder::new(
614 information_extension.clone(),
615 kv_backend.clone(),
616 layered_cache_registry.clone(),
617 )
618 .with_procedure_manager(procedure_manager.clone())
619 .with_process_manager(process_manager.clone());
620 let builder = if let Some(configurator) =
621 plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
622 {
623 let ctx = StandaloneCatalogManagerConfigureContext {
624 fe_client: frontend_client.clone(),
625 };
626 let ctx = CatalogManagerConfigureContext::Standalone(ctx);
627 configurator
628 .configure(builder, ctx)
629 .await
630 .context(OtherSnafu)?
631 } else {
632 builder
633 };
634 let catalog_manager = builder.build();
635
636 let table_metadata_manager =
637 Self::create_table_metadata_manager(kv_backend.clone()).await?;
638
639 let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
640 let flownode_options = FlownodeOptions {
641 flow: opts.flow.clone(),
642 ..Default::default()
643 };
644
645 let mut flow_builder = FlownodeBuilder::new(
646 flownode_options,
647 plugins.clone(),
648 table_metadata_manager.clone(),
649 catalog_manager.clone(),
650 flow_metadata_manager.clone(),
651 frontend_client.clone(),
652 );
653
654 plugins::setup_flownode_plugins_post_build(&mut plugins, &plugin_opts, &flow_builder)
655 .await
656 .context(error::StartFlownodeSnafu)?;
657 flow_builder.set_plugins(plugins.clone());
658
659 let flownode = flow_builder
660 .build()
661 .await
662 .map_err(BoxedError::new)
663 .context(error::OtherSnafu)?;
664 let flow_engine = flownode.flow_engine();
665
666 {
668 information_extension
669 .set_flow_engine(flow_engine.clone())
670 .await;
671 }
672
673 let node_manager = creator
674 .node_manager_creator
675 .create(&kv_backend, datanode.region_server(), flow_engine.clone())
676 .await?;
677
678 let table_id_allocator = creator.table_id_allocator_creator.create(&kv_backend);
679 let flow_id_sequence = Arc::new(
680 SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone())
681 .initial(MIN_USER_FLOW_ID as u64)
682 .step(10)
683 .build(),
684 );
685 let wal_provider =
686 Arc::new(build_standalone_wal_provider(&opts.wal, node_id, kv_backend.clone()).await?);
687 let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
688 table_id_allocator.clone(),
689 wal_provider.clone(),
690 ));
691 let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
692 flow_id_sequence,
693 ));
694
695 let ddl_context = DdlContext {
696 affinity_resolver: None,
697 affinity_fallback: Default::default(),
698 peer_alloc_context_provider: None,
699 node_manager: node_manager.clone(),
700 cache_invalidator: layered_cache_registry.clone(),
701 memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
702 leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
703 table_metadata_manager: table_metadata_manager.clone(),
704 table_metadata_allocator: table_metadata_allocator.clone(),
705 flow_metadata_manager: flow_metadata_manager.clone(),
706 flow_metadata_allocator: flow_metadata_allocator.clone(),
707 region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
708 soft_drop_enabled: false,
709 soft_drop_retention: None,
710 create_database_metadata_committer: None,
711 };
712
713 let ddl_manager = DdlManager::new(
714 ddl_context,
715 procedure_manager.clone(),
716 Arc::new(StandaloneRepartitionProcedureFactory),
717 );
718
719 let ddl_manager = if let Some(configurator) =
720 plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
721 {
722 let ctx = DdlManagerConfigureContext {
723 kv_backend: kv_backend.clone(),
724 fe_client: frontend_client.clone(),
725 catalog_manager: catalog_manager.clone(),
726 };
727 configurator
728 .configure(ddl_manager, ctx)
729 .await
730 .context(OtherSnafu)?
731 } else {
732 ddl_manager
733 };
734 ddl_manager
735 .register_loaders()
736 .context(error::InitDdlManagerSnafu)?;
737
738 let procedure_executor = creator
739 .procedure_executor_creator
740 .create(Arc::new(ddl_manager), procedure_manager.clone())
741 .await?;
742
743 let fe_instance = FrontendBuilder::new(
744 fe_opts.clone(),
745 kv_backend.clone(),
746 layered_cache_registry.clone(),
747 catalog_manager.clone(),
748 node_manager.clone(),
749 procedure_executor.clone(),
750 process_manager,
751 )
752 .with_local_file_access(local_file_access);
753
754 plugins::setup_frontend_plugins_post_build(&mut plugins, &plugin_opts, &fe_instance)
755 .await
756 .context(error::StartFrontendSnafu)?;
757
758 let fe_instance = fe_instance
759 .with_plugin(plugins.clone())
760 .try_build()
761 .await
762 .context(error::StartFrontendSnafu)?;
763 let fe_instance = Arc::new(fe_instance);
764
765 event_recorder_handle.install(fe_instance.event_recorder());
766
767 let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
769 let weak_grpc_handler = Arc::downgrade(&grpc_handler);
770 frontend_instance_handler
771 .set_handler(weak_grpc_handler)
772 .await;
773
774 let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
775 .build()
776 .context(error::StartFrontendSnafu)?;
777
778 let frontend = Frontend {
779 instance: fe_instance,
780 servers,
781 heartbeat_task: None,
782 };
783 let leader_services_context = LeaderServicesContext {
784 procedure_manager: procedure_manager.clone(),
785 wal_provider: wal_provider.clone(),
786 region_server: datanode.region_server(),
787 kv_backend: kv_backend.clone(),
788 cache_registry: layered_cache_registry,
789 catalog_manager,
790 flow_engine,
791 frontend_client,
792 node_id,
793 init_regions_parallelism,
794 plugin_options: plugin_opts,
795 };
796
797 let instance = Instance {
798 datanode,
799 frontend,
800 flownode,
801 procedure_manager,
802 leader_services_controller: creator.leader_services_controller,
803 leader_services_context,
804 metadata_kv_backend,
805 _guard: vec![],
806 };
807 let result = InstanceCreatorResult {
808 kv_backend,
809 node_manager,
810 table_id_allocator,
811 };
812 Ok((instance, result))
813 }
814
815 pub async fn create_table_metadata_manager(
816 kv_backend: KvBackendRef,
817 ) -> Result<TableMetadataManagerRef> {
818 let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
819
820 table_metadata_manager
821 .init()
822 .await
823 .context(error::InitMetadataSnafu)?;
824
825 Ok(table_metadata_manager)
826 }
827}
828
829#[async_trait]
830pub trait NodeManagerCreator: Send + Sync {
831 async fn create(
832 &self,
833 kv_backend: &KvBackendRef,
834 region_server: RegionServer,
835 flow_server: FlownodeRef,
836 ) -> Result<NodeManagerRef>;
837}
838
839pub struct DefaultNodeManagerCreator;
840
841#[async_trait]
842impl NodeManagerCreator for DefaultNodeManagerCreator {
843 async fn create(
844 &self,
845 _: &KvBackendRef,
846 region_server: RegionServer,
847 flow_server: FlownodeRef,
848 ) -> Result<NodeManagerRef> {
849 Ok(Arc::new(StandaloneDatanodeManager {
850 region_server,
851 flow_server,
852 }))
853 }
854}
855
856#[async_trait]
862pub trait MetadataKvBackendCreator: Send + Sync {
863 async fn create(
866 &self,
867 metadata_dir: String,
868 opts: &StandaloneOptions,
869 ) -> Result<ClosableKvBackendRef>;
870}
871
872pub struct DefaultMetadataKvBackendCreator;
873
874#[async_trait]
875impl MetadataKvBackendCreator for DefaultMetadataKvBackendCreator {
876 async fn create(
877 &self,
878 metadata_dir: String,
879 opts: &StandaloneOptions,
880 ) -> Result<ClosableKvBackendRef> {
881 standalone::open_metadata_kvbackend(metadata_dir, opts.metadata_store)
882 .context(error::BuildMetadataKvbackendSnafu)
883 }
884}
885
886pub trait TableIdAllocatorCreator: Send + Sync {
887 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
888}
889
890struct DefaultTableIdAllocatorCreator;
891
892impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
893 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
894 Arc::new(
895 SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
896 .initial(MIN_USER_TABLE_ID as u64)
897 .step(10)
898 .build(),
899 )
900 }
901}
902
903#[async_trait]
904pub trait ProcedureExecutorCreator: Send + Sync {
905 async fn create(
906 &self,
907 ddl_manager: DdlManagerRef,
908 procedure_manager: ProcedureManagerRef,
909 ) -> Result<ProcedureExecutorRef>;
910}
911
912pub struct DefaultProcedureExecutorCreator;
913
914#[async_trait]
915impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
916 async fn create(
917 &self,
918 ddl_manager: DdlManagerRef,
919 procedure_manager: ProcedureManagerRef,
920 ) -> Result<ProcedureExecutorRef> {
921 Ok(Arc::new(LocalProcedureExecutor::new(
922 ddl_manager,
923 procedure_manager,
924 )))
925 }
926}
927
928#[async_trait]
929pub trait StandaloneLeaderServicesController: Send + Sync {
930 async fn start(&self, context: LeaderServicesContext) -> Result<()>;
935
936 async fn stop(
938 &self,
939 procedure_manager: ProcedureManagerRef,
940 region_server: RegionServer,
941 ) -> Result<()>;
942}
943
944#[derive(Clone)]
945pub struct LeaderServicesContext {
955 pub procedure_manager: ProcedureManagerRef,
956 pub wal_provider: WalProviderRef,
957 pub region_server: RegionServer,
958 pub kv_backend: KvBackendRef,
959 pub cache_registry: LayeredCacheRegistryRef,
960 pub catalog_manager: CatalogManagerRef,
961 pub flow_engine: FlowDualEngineRef,
962 pub frontend_client: Arc<FrontendClient>,
963 pub node_id: Option<DatanodeId>,
964 pub init_regions_parallelism: usize,
965 pub plugin_options: Vec<PluginOptions>,
966}
967
968pub struct DefaultStandaloneLeaderServicesController;
969
970#[async_trait]
971impl StandaloneLeaderServicesController for DefaultStandaloneLeaderServicesController {
972 async fn start(&self, context: LeaderServicesContext) -> Result<()> {
973 context
974 .procedure_manager
975 .start()
976 .await
977 .context(error::StartProcedureManagerSnafu)?;
978 context
979 .wal_provider
980 .start()
981 .await
982 .context(error::StartWalProviderSnafu)
983 }
984
985 async fn stop(
986 &self,
987 procedure_manager: ProcedureManagerRef,
988 _region_server: RegionServer,
989 ) -> Result<()> {
990 procedure_manager
991 .stop()
992 .await
993 .context(error::StopProcedureManagerSnafu)
994 }
995}
996
997pub struct InstanceCreator {
1000 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
1003 node_manager_creator: Box<dyn NodeManagerCreator>,
1004 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
1005 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
1006 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
1007 open_regions_writable_override: Option<bool>,
1008}
1009
1010impl InstanceCreator {
1011 pub fn new(
1012 node_manager_creator: Box<dyn NodeManagerCreator>,
1013 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
1014 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
1015 ) -> Self {
1016 Self {
1017 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
1018 node_manager_creator,
1019 table_id_allocator_creator,
1020 procedure_executor_creator,
1021 leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
1022 open_regions_writable_override: None,
1023 }
1024 }
1025
1026 pub fn with_metadata_kv_backend_creator(
1027 mut self,
1028 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
1029 ) -> Self {
1030 self.metadata_kv_backend_creator = metadata_kv_backend_creator;
1031 self
1032 }
1033
1034 pub fn map_metadata_kv_backend_creator<F>(mut self, f: F) -> Self
1039 where
1040 F: FnOnce(Box<dyn MetadataKvBackendCreator>) -> Box<dyn MetadataKvBackendCreator>,
1041 {
1042 self.metadata_kv_backend_creator = f(self.metadata_kv_backend_creator);
1043 self
1044 }
1045
1046 pub fn map_node_manager_creator<F>(mut self, f: F) -> Self
1048 where
1049 F: FnOnce(Box<dyn NodeManagerCreator>) -> Box<dyn NodeManagerCreator>,
1050 {
1051 self.node_manager_creator = f(self.node_manager_creator);
1052 self
1053 }
1054
1055 pub fn map_procedure_executor_creator<F>(mut self, f: F) -> Self
1057 where
1058 F: FnOnce(Box<dyn ProcedureExecutorCreator>) -> Box<dyn ProcedureExecutorCreator>,
1059 {
1060 self.procedure_executor_creator = f(self.procedure_executor_creator);
1061 self
1062 }
1063
1064 pub fn with_leader_services_controller(
1066 mut self,
1067 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
1068 ) -> Self {
1069 self.leader_services_controller = leader_services_controller;
1070 self
1071 }
1072
1073 pub fn with_open_regions_writable_override(mut self, writable: bool) -> Self {
1081 self.open_regions_writable_override = Some(writable);
1082 self
1083 }
1084}
1085
1086impl Default for InstanceCreator {
1087 fn default() -> Self {
1088 Self {
1089 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
1090 node_manager_creator: Box::new(DefaultNodeManagerCreator),
1091 table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
1092 procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
1093 leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
1094 open_regions_writable_override: None,
1095 }
1096 }
1097}
1098
1099pub struct InstanceCreatorResult {
1102 pub kv_backend: KvBackendRef,
1103 pub node_manager: NodeManagerRef,
1104 pub table_id_allocator: Arc<Sequence>,
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109 use std::default::Default;
1110 use std::io::Write;
1111 use std::time::Duration;
1112
1113 use auth::{Identity, Password, UserProviderRef};
1114 use clap::{CommandFactory, Parser};
1115 use common_base::readable_size::ReadableSize;
1116 use common_config::ENV_VAR_SEP;
1117 use common_meta::ddl::allocator::wal_options::WalOptionsAllocator;
1118 use common_meta::kv_backend::memory::MemoryKvBackend;
1119 use common_options::plugin_options::StandaloneFlag;
1120 use common_test_util::temp_dir::{create_named_temp_file, create_temp_dir};
1121 use common_wal::config::object_store::ObjectStoreWalConfig;
1122 use common_wal::options::{ObjectStoreWalOptions, WalOptions};
1123 use object_store::config::{FileConfig, GcsConfig};
1124 use servers::grpc::GrpcOptions;
1125 use servers::server::ServerHandlers;
1126 use store_api::logstore::LogStore;
1127
1128 use super::*;
1129 use crate::options::GlobalOptions;
1130
1131 struct FailingLeaderServices;
1132
1133 #[async_trait]
1134 impl StandaloneLeaderServicesController for FailingLeaderServices {
1135 async fn start(&self, _context: LeaderServicesContext) -> Result<()> {
1136 Ok(())
1137 }
1138
1139 async fn stop(
1140 &self,
1141 _procedure_manager: ProcedureManagerRef,
1142 _region_server: RegionServer,
1143 ) -> Result<()> {
1144 error::IllegalConfigSnafu {
1145 msg: "leader services failed to stop",
1146 }
1147 .fail()
1148 }
1149 }
1150
1151 struct FailingServer;
1152
1153 #[async_trait]
1154 impl servers::server::Server for FailingServer {
1155 async fn shutdown(&self) -> servers::error::Result<()> {
1156 servers::error::InternalSnafu {
1157 err_msg: "server failed to stop",
1158 }
1159 .fail()
1160 }
1161
1162 async fn start(&mut self, _listening: SocketAddr) -> servers::error::Result<()> {
1163 Ok(())
1164 }
1165
1166 fn name(&self) -> &str {
1167 "failing"
1168 }
1169
1170 fn as_any(&self) -> &dyn std::any::Any {
1171 self
1172 }
1173 }
1174
1175 #[tokio::test]
1176 async fn test_stop_runs_every_step_and_returns_the_first_error() {
1177 common_telemetry::init_default_ut_logging();
1178 let data_home = create_temp_dir("standalone-stop");
1179 let mut opts = StandaloneOptions {
1180 wal: DatanodeWalConfig::ObjectStore(ObjectStoreWalConfig::default()),
1181 ..Default::default()
1182 };
1183 opts.storage.data_home = data_home.path().to_str().unwrap().to_string();
1184 let creator = InstanceCreator::default()
1185 .with_leader_services_controller(Box::new(FailingLeaderServices));
1186 let metadata_dir = metadata_store_dir(&opts.storage.data_home);
1187 let (mut instance, _) = StartCommand::build_with(opts, vec![], creator)
1188 .await
1189 .unwrap();
1190 let log_store = instance.datanode.object_store_log_store().unwrap();
1191 let mut services = ServerHandlers::default();
1193 services.insert((Box::new(FailingServer), "127.0.0.1:0".parse().unwrap()));
1194 services.start_all().await.unwrap();
1195 instance.datanode.setup_services(services);
1196
1197 let err = instance.stop().await.unwrap_err();
1198
1199 assert!(
1200 matches!(&err, error::Error::IllegalConfig { msg, .. } if msg == "leader services failed to stop"),
1201 "unexpected error: {err:?}"
1202 );
1203 assert!(matches!(
1204 log_store.append_batch(vec![]).await,
1205 Err(log_store::error::Error::ObjectStoreWalStopped { .. })
1206 ));
1207 let metadata =
1210 standalone::open_metadata_kvbackend(metadata_dir, Default::default()).unwrap();
1211 metadata.close().await.unwrap();
1212 }
1213
1214 #[tokio::test]
1215 async fn test_build_standalone_wal_provider() {
1216 let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
1217
1218 let config = ObjectStoreWalConfig {
1219 prefix: "cluster-a/wal".to_string(),
1220 ..Default::default()
1221 };
1222 let node_prefix = config.node_prefix(0, STANDALONE_GENERATION);
1224 assert_eq!(node_prefix, "cluster-a/wal/datanodes/0/epochs/0");
1225 let provider = build_standalone_wal_provider(
1226 &DatanodeWalConfig::ObjectStore(config),
1227 Some(0),
1228 kv_backend.clone(),
1229 )
1230 .await
1231 .unwrap();
1232 assert!(matches!(
1233 &provider,
1234 WalProvider::ObjectStore { prefix } if *prefix == node_prefix
1235 ));
1236 let regions = vec![0, 1];
1237 let wal_options = provider.allocate(®ions, false).await.unwrap();
1238 for region in regions {
1239 assert_eq!(
1240 wal_options[®ion],
1241 WalOptions::ObjectStore(ObjectStoreWalOptions::new(node_prefix.clone()))
1242 );
1243 }
1244
1245 let provider =
1246 build_standalone_wal_provider(&DatanodeWalConfig::default(), Some(0), kv_backend)
1247 .await
1248 .unwrap();
1249 assert!(matches!(provider, WalProvider::RaftEngine));
1250 }
1251
1252 #[test]
1253 fn test_standalone_local_file_access_config() {
1254 let data_home = create_temp_dir("standalone_copy_root");
1255 let storage = StorageConfig {
1256 data_home: data_home.path().display().to_string(),
1257 ..Default::default()
1258 };
1259 let access = standalone_local_file_access(&storage).unwrap();
1260 assert_eq!(
1261 access.sandbox_root().unwrap(),
1262 data_home.path().join("copy").canonicalize().unwrap()
1263 );
1264
1265 let remote_data_home = StorageConfig {
1266 data_home: "s3://bucket/data".to_string(),
1267 ..Default::default()
1268 };
1269 assert!(matches!(
1270 standalone_local_file_access(&remote_data_home).unwrap(),
1271 LocalFileAccess::Disabled
1272 ));
1273
1274 let explicit_root = create_temp_dir("standalone_explicit_copy_root");
1275 let remote_with_explicit_root = StorageConfig {
1276 data_home: "s3://bucket/data".to_string(),
1277 copy_root: Some(explicit_root.path().display().to_string()),
1278 ..Default::default()
1279 };
1280 assert_eq!(
1281 standalone_local_file_access(&remote_with_explicit_root)
1282 .unwrap()
1283 .sandbox_root()
1284 .unwrap(),
1285 explicit_root.path().canonicalize().unwrap()
1286 );
1287
1288 let remote_copy_root = StorageConfig {
1289 data_home: data_home.path().display().to_string(),
1290 copy_root: Some("s3://bucket/copy".to_string()),
1291 ..Default::default()
1292 };
1293 assert!(matches!(
1294 standalone_local_file_access(&remote_copy_root),
1295 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1296 ));
1297
1298 let exposes_internal = StorageConfig {
1299 data_home: data_home.path().display().to_string(),
1300 copy_root: Some(data_home.path().join("data").display().to_string()),
1301 ..Default::default()
1302 };
1303 assert!(matches!(
1304 standalone_local_file_access(&exposes_internal),
1305 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1306 ));
1307
1308 let exposes_data_home = StorageConfig {
1309 data_home: data_home.path().display().to_string(),
1310 copy_root: Some(data_home.path().parent().unwrap().display().to_string()),
1311 ..Default::default()
1312 };
1313 assert!(matches!(
1314 standalone_local_file_access(&exposes_data_home),
1315 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1316 ));
1317 }
1318
1319 #[tokio::test]
1320 async fn test_user_provider_cli_redacts_passwords() {
1321 let command = StartCommand::try_parse_from([
1322 "standalone",
1323 "--user-provider",
1324 "static_user_provider:cmd:test=cli-secret",
1325 ])
1326 .unwrap();
1327 let debug = format!("{command:#?}");
1328 assert!(!debug.contains("cli-secret"));
1329 assert!(debug.contains("[REDACTED]"));
1330
1331 let fe_opts = command
1332 .load_options(&Default::default())
1333 .unwrap()
1334 .component
1335 .frontend_options();
1336
1337 let mut plugins = Plugins::new();
1338 plugins.insert(StandaloneFlag);
1339 plugins::setup_frontend_plugins_pre_build(&mut plugins, &[], &fe_opts, None)
1340 .await
1341 .unwrap();
1342
1343 let provider = plugins.get::<UserProviderRef>().unwrap();
1344 let result = provider
1345 .authenticate(
1346 Identity::UserId("test", None),
1347 Password::PlainText("cli-secret".to_string().into()),
1348 )
1349 .await;
1350 let _ = result.unwrap();
1351 }
1352
1353 #[test]
1354 fn test_toml() {
1355 let opts = StandaloneOptions::default();
1356 let toml_string = toml::to_string(&opts).unwrap();
1357 let parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
1358 assert_eq!(parsed.otlp, opts.otlp);
1359 }
1360
1361 #[test]
1362 fn test_read_from_config_file() {
1363 let mut file = create_named_temp_file();
1364 let toml_str = r#"
1365 enable_memory_catalog = true
1366 max_concurrent_queries = 8
1367 concurrent_query_limiter_timeout = "250ms"
1368
1369 [wal]
1370 provider = "raft_engine"
1371 dir = "./greptimedb_data/test/wal"
1372 file_size = "1GB"
1373 purge_threshold = "50GB"
1374 purge_interval = "10m"
1375 read_batch_size = 128
1376 sync_write = false
1377
1378 [storage]
1379 data_home = "./greptimedb_data/"
1380 type = "File"
1381
1382 [[storage.providers]]
1383 type = "Gcs"
1384 bucket = "foo"
1385 endpoint = "bar"
1386
1387 [[storage.providers]]
1388 type = "S3"
1389 access_key_id = "access_key_id"
1390 secret_access_key = "secret_access_key"
1391
1392 [storage.compaction]
1393 max_inflight_tasks = 3
1394 max_files_in_level0 = 7
1395 max_purge_tasks = 32
1396
1397 [storage.manifest]
1398 checkpoint_margin = 9
1399 gc_duration = '7s'
1400
1401 [http]
1402 addr = "127.0.0.1:4000"
1403 timeout = "33s"
1404 body_limit = "128MB"
1405
1406 [opentsdb]
1407 enable = true
1408
1409 [logging]
1410 level = "debug"
1411 dir = "./greptimedb_data/test/logs"
1412 "#;
1413 write!(file, "{}", toml_str).unwrap();
1414 let cmd = StartCommand {
1415 config_file: Some(file.path().to_str().unwrap().to_string()),
1416 user_provider: Some("static_user_provider:cmd:test=test".to_string().into()),
1417 ..Default::default()
1418 };
1419
1420 let options = cmd
1421 .load_options(&GlobalOptions::default())
1422 .unwrap()
1423 .component;
1424 let fe_opts = options.frontend_options();
1425 let dn_opts = options.datanode_options();
1426 assert_eq!(dn_opts.max_concurrent_queries, 8);
1427 assert_eq!(
1428 dn_opts.concurrent_query_limiter_timeout,
1429 Duration::from_millis(250)
1430 );
1431 let logging_opts = options.logging;
1432 assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
1433 assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
1434 assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
1435 assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
1436 assert!(fe_opts.mysql.enable);
1437 assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
1438 assert_eq!(2, fe_opts.mysql.runtime_size);
1439 assert_eq!(None, fe_opts.mysql.reject_no_database);
1440 assert!(fe_opts.influxdb.enable);
1441 assert!(fe_opts.opentsdb.enable);
1442
1443 let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
1444 unreachable!()
1445 };
1446 assert_eq!(
1447 "./greptimedb_data/test/wal",
1448 raft_engine_config.dir.unwrap()
1449 );
1450
1451 assert!(matches!(
1452 &dn_opts.storage.store,
1453 object_store::config::ObjectStoreConfig::File(FileConfig { .. })
1454 ));
1455 assert_eq!(dn_opts.storage.providers.len(), 2);
1456 assert!(matches!(
1457 dn_opts.storage.providers[0],
1458 object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
1459 ));
1460 match &dn_opts.storage.providers[1] {
1461 object_store::config::ObjectStoreConfig::S3(s3_config) => {
1462 assert_eq!(
1463 "SecretBox<alloc::string::String>([REDACTED])".to_string(),
1464 format!("{:?}", s3_config.connection.access_key_id)
1465 );
1466 }
1467 _ => {
1468 unreachable!()
1469 }
1470 }
1471
1472 assert_eq!("debug", logging_opts.level.as_ref().unwrap());
1473 assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
1474 }
1475
1476 #[test]
1477 fn test_load_log_options_from_cli() {
1478 let cmd = StartCommand {
1479 user_provider: Some("static_user_provider:cmd:test=test".to_string().into()),
1480 mysql_addr: Some("127.0.0.1:4002".to_string()),
1481 postgres_addr: Some("127.0.0.1:4003".to_string()),
1482 ..Default::default()
1483 };
1484
1485 let opts = cmd
1486 .load_options(&GlobalOptions {
1487 log_dir: Some("./greptimedb_data/test/logs".to_string()),
1488 log_level: Some("debug".to_string()),
1489
1490 #[cfg(feature = "tokio-console")]
1491 tokio_console_addr: None,
1492 })
1493 .unwrap()
1494 .component;
1495
1496 assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
1497 assert_eq!("debug", opts.logging.level.unwrap());
1498 }
1499
1500 #[test]
1501 fn test_config_precedence_order() {
1502 let mut file = create_named_temp_file();
1503 let toml_str = r#"
1504 [http]
1505 addr = "127.0.0.1:4000"
1506
1507 [logging]
1508 level = "debug"
1509 "#;
1510 write!(file, "{}", toml_str).unwrap();
1511
1512 let env_prefix = "STANDALONE_UT";
1513 temp_env::with_vars(
1514 [
1515 (
1516 [
1518 env_prefix.to_string(),
1519 "logging".to_uppercase(),
1520 "dir".to_uppercase(),
1521 ]
1522 .join(ENV_VAR_SEP),
1523 Some("/other/log/dir"),
1524 ),
1525 (
1526 [
1528 env_prefix.to_string(),
1529 "logging".to_uppercase(),
1530 "level".to_uppercase(),
1531 ]
1532 .join(ENV_VAR_SEP),
1533 Some("info"),
1534 ),
1535 (
1536 [
1538 env_prefix.to_string(),
1539 "http".to_uppercase(),
1540 "addr".to_uppercase(),
1541 ]
1542 .join(ENV_VAR_SEP),
1543 Some("127.0.0.1:24000"),
1544 ),
1545 ],
1546 || {
1547 let command = StartCommand {
1548 config_file: Some(file.path().to_str().unwrap().to_string()),
1549 http_addr: Some("127.0.0.1:14000".to_string()),
1550 env_prefix: env_prefix.to_string(),
1551 ..Default::default()
1552 };
1553
1554 let opts = command.load_options(&Default::default()).unwrap().component;
1555
1556 assert_eq!(opts.logging.dir, "/other/log/dir");
1558
1559 assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
1561
1562 let fe_opts = opts.frontend_options();
1564 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
1565 assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
1566
1567 assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
1569 },
1570 );
1571 }
1572
1573 #[test]
1574 fn test_parse_grpc_bind_addr_aliases() {
1575 let command =
1576 StartCommand::try_parse_from(["standalone", "--grpc-bind-addr", "127.0.0.1:14001"])
1577 .unwrap();
1578 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
1579
1580 let command =
1581 StartCommand::try_parse_from(["standalone", "--rpc-bind-addr", "127.0.0.1:24001"])
1582 .unwrap();
1583 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1584
1585 let command =
1586 StartCommand::try_parse_from(["standalone", "--rpc-addr", "127.0.0.1:34001"]).unwrap();
1587 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1588 }
1589
1590 #[cfg(unix)]
1591 #[test]
1592 fn test_parse_daemon_flag() {
1593 let command = StartCommand::try_parse_from(["standalone", "--daemon"]).unwrap();
1594 assert!(command.is_daemon());
1595
1596 let command = StartCommand::try_parse_from(["standalone", "-d"]).unwrap();
1597 assert!(command.is_daemon());
1598
1599 let command = StartCommand::try_parse_from(["standalone"]).unwrap();
1600 assert!(!command.is_daemon());
1601 }
1602
1603 #[test]
1604 fn test_help_uses_grpc_option_names() {
1605 let mut cmd = StartCommand::command();
1606 let mut help = Vec::new();
1607 cmd.write_long_help(&mut help).unwrap();
1608 let help = String::from_utf8(help).unwrap();
1609
1610 assert!(help.contains("--grpc-bind-addr"));
1611 assert!(!help.contains("--rpc-bind-addr"));
1612 assert!(!help.contains("--rpc-addr"));
1613 }
1614
1615 #[test]
1616 fn test_load_default_standalone_options() {
1617 let options =
1618 StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1619 let default_options = StandaloneOptions::default();
1620 assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1621 assert_eq!(options.http, default_options.http);
1622 assert_eq!(options.grpc, default_options.grpc);
1623 assert_eq!(options.mysql, default_options.mysql);
1624 assert_eq!(options.postgres, default_options.postgres);
1625 assert_eq!(options.opentsdb, default_options.opentsdb);
1626 assert_eq!(options.influxdb, default_options.influxdb);
1627 assert_eq!(options.prom_store, default_options.prom_store);
1628 assert_eq!(options.wal, default_options.wal);
1629 assert_eq!(options.metadata_store, default_options.metadata_store);
1630 assert_eq!(options.procedure, default_options.procedure);
1631 assert_eq!(options.logging, default_options.logging);
1632 assert_eq!(options.region_engine, default_options.region_engine);
1633 }
1634
1635 #[test]
1636 fn test_cache_config() {
1637 let toml_str = r#"
1638 [storage]
1639 data_home = "test_data_home"
1640 type = "S3"
1641 [storage.cache_config]
1642 enable_read_cache = true
1643 "#;
1644 let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1645 opts.sanitize();
1646 assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1647 assert_eq!(
1648 opts.storage.store.cache_config().unwrap().cache_path,
1649 "test_data_home"
1650 );
1651 }
1652
1653 #[test]
1654 #[cfg(not(feature = "enterprise"))]
1655 fn test_load_options_ignores_unknown_plugin_options() {
1656 let mut file = create_named_temp_file();
1660 write!(
1661 file,
1662 r#"
1663[[plugins]]
1664SomeUnknownPlugin = {{ feature = "foo", count = 5 }}
1665
1666[[plugins]]
1667AnotherUnknownPlugin = {{}}
1668"#
1669 )
1670 .unwrap();
1671
1672 let opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1673 Some(file.path().to_str().unwrap()),
1674 "GREPTIMEDB_STANDALONE_UT",
1675 )
1676 .expect(
1677 "loading a config with unrecognized plugin options should succeed, \
1678 ignoring the unknown ones",
1679 );
1680 assert!(opts.plugins.is_empty());
1683 }
1684
1685 #[test]
1686 fn test_load_options_errors_on_malformed_known_plugin_option() {
1687 let mut file = create_named_temp_file();
1692 write!(
1693 file,
1694 r#"
1695[[plugins]]
1696Dummy = {{ unexpected = "payload" }}
1697"#
1698 )
1699 .unwrap();
1700
1701 let result = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1702 Some(file.path().to_str().unwrap()),
1703 "GREPTIMEDB_STANDALONE_UT",
1704 );
1705 assert!(
1706 result.is_err(),
1707 "a malformed payload for a known plugin variant must fail config loading"
1708 );
1709 }
1710
1711 #[test]
1712 #[cfg(not(feature = "enterprise"))]
1713 fn test_load_options_ignores_multi_key_unknown_plugin_entry() {
1714 let mut file = create_named_temp_file();
1718 write!(
1719 file,
1720 r#"
1721[[plugins]]
1722FirstUnknownPlugin = {{ a = 1 }}
1723SecondUnknownPlugin = {{ b = 2 }}
1724"#
1725 )
1726 .unwrap();
1727
1728 let opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1729 Some(file.path().to_str().unwrap()),
1730 "GREPTIMEDB_STANDALONE_UT",
1731 )
1732 .expect(
1733 "loading a config with a multi-key unknown plugin entry should succeed, \
1734 ignoring the unknown ones",
1735 );
1736 assert!(opts.plugins.is_empty());
1737 }
1738}