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