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::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
52use common_telemetry::{info, warn};
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<()> {
280 let mut first_error = None;
281 let mut record = |result: Result<()>| {
282 if let Err(err) = result {
283 if first_error.is_none() {
284 first_error = Some(err);
285 } else {
286 warn!(err; "Ignored a later shutdown error");
287 }
288 }
289 };
290
291 record(
292 self.frontend
293 .shutdown()
294 .await
295 .context(error::ShutdownFrontendSnafu),
296 );
297
298 record(
299 self.leader_services_controller
300 .stop(
301 self.procedure_manager.clone(),
302 self.datanode.region_server(),
303 )
304 .await,
305 );
306
307 record(
308 self.datanode
309 .shutdown()
310 .await
311 .context(error::ShutdownDatanodeSnafu),
312 );
313
314 record(
315 self.flownode
316 .shutdown()
317 .await
318 .context(error::ShutdownFlownodeSnafu),
319 );
320
321 info!("Datanode instance stopped.");
322
323 first_error.map_or(Ok(()), Err)
324 }
325}
326
327#[derive(Debug, Default, Parser)]
328pub struct StartCommand {
329 #[clap(long)]
330 http_addr: Option<String>,
331 #[clap(long)]
334 http_api_server_addr: Option<String>,
335 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
336 grpc_bind_addr: Option<String>,
337 #[clap(long)]
338 mysql_addr: Option<String>,
339 #[clap(long)]
340 postgres_addr: Option<String>,
341 #[clap(short, long)]
342 influxdb_enable: bool,
343 #[clap(short, long)]
344 pub config_file: Option<String>,
345 #[clap(long)]
346 tls_mode: Option<TlsMode>,
347 #[clap(long)]
348 tls_cert_path: Option<String>,
349 #[clap(long)]
350 tls_key_path: Option<String>,
351 #[clap(long)]
352 tls_watch: bool,
353 #[clap(long)]
354 user_provider: Option<String>,
355 #[clap(long, default_value = "GREPTIMEDB_STANDALONE")]
356 pub env_prefix: String,
357 #[clap(long)]
359 data_home: Option<String>,
360 #[cfg(unix)]
362 #[clap(short, long)]
363 daemon: bool,
364}
365
366impl StartCommand {
367 #[cfg(unix)]
369 pub(crate) fn is_daemon(&self) -> bool {
370 self.daemon
371 }
372
373 #[cfg(not(unix))]
374 pub(crate) fn is_daemon(&self) -> bool {
375 false
376 }
377
378 pub fn load_options(
380 &self,
381 global_options: &GlobalOptions,
382 ) -> Result<GreptimeOptions<StandaloneOptions>> {
383 let mut opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
384 self.config_file.as_deref(),
385 self.env_prefix.as_ref(),
386 )
387 .context(error::LoadLayeredConfigSnafu)?;
388
389 self.merge_with_cli_options(global_options, &mut opts.component)?;
390 opts.component.sanitize();
391
392 Ok(opts)
393 }
394
395 pub fn merge_with_cli_options(
397 &self,
398 global_options: &GlobalOptions,
399 opts: &mut StandaloneOptions,
400 ) -> Result<()> {
401 if let Some(dir) = &global_options.log_dir {
402 opts.logging.dir.clone_from(dir);
403 }
404
405 if global_options.log_level.is_some() {
406 opts.logging.level.clone_from(&global_options.log_level);
407 }
408
409 opts.tracing = TracingOptions {
410 #[cfg(feature = "tokio-console")]
411 tokio_console_addr: global_options.tokio_console_addr.clone(),
412 };
413
414 let tls_opts = TlsOption::new(
415 self.tls_mode,
416 self.tls_cert_path.clone(),
417 self.tls_key_path.clone(),
418 self.tls_watch,
419 );
420
421 if let Some(addr) = &self.http_addr {
422 opts.http.addr.clone_from(addr);
423 }
424
425 if let Some(addr) = &self.http_api_server_addr {
426 opts.http.api_server_addr.clone_from(addr);
427 opts.http.enable_api_server = true;
428 }
429
430 if let Some(data_home) = &self.data_home {
431 opts.storage.data_home.clone_from(data_home);
432 }
433
434 if opts.logging.dir.is_empty() {
436 opts.logging.dir = Path::new(&opts.storage.data_home)
437 .join(DEFAULT_LOGGING_DIR)
438 .to_string_lossy()
439 .to_string();
440 }
441
442 if let Some(addr) = &self.grpc_bind_addr {
443 let datanode_grpc_addr = DatanodeOptions::default().grpc.bind_addr;
445 if addr.eq(&datanode_grpc_addr) {
446 return error::IllegalConfigSnafu {
447 msg: format!(
448 "gRPC listen address conflicts with datanode reserved gRPC addr: {datanode_grpc_addr}",
449 ),
450 }.fail();
451 }
452 opts.grpc.bind_addr.clone_from(addr);
453 opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
454 }
455
456 if let Some(addr) = &self.mysql_addr {
457 opts.mysql.enable = true;
458 opts.mysql.addr.clone_from(addr);
459 opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
460 }
461
462 if let Some(addr) = &self.postgres_addr {
463 opts.postgres.enable = true;
464 opts.postgres.addr.clone_from(addr);
465 opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
466 }
467
468 if self.influxdb_enable {
469 opts.influxdb.enable = self.influxdb_enable;
470 }
471
472 if let Some(user_provider) = &self.user_provider {
473 opts.user_provider = Some(user_provider.clone());
474 }
475
476 Ok(())
477 }
478
479 #[allow(unreachable_code)]
480 #[allow(unused_variables)]
481 #[allow(clippy::diverging_sub_expression)]
482 pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
484 let guard = common_telemetry::init_global_logging(
485 APP_NAME,
486 &opts.component.logging,
487 &opts.component.tracing,
488 None,
489 Some(&opts.component.slow_query),
490 );
491
492 common_runtime::init_standalone_runtimes(&opts.runtime);
493
494 crate::options::flush_dropped_plugin_warnings();
495 log_versions(verbose_version(), short_version(), APP_NAME);
496 maybe_activate_heap_profile(&opts.component.memory);
497 create_resource_limit_metrics(APP_NAME);
498
499 info!("Standalone start command: {:#?}", self);
500 info!("Standalone options: {opts:#?}");
501
502 let (mut instance, _) =
503 Self::build_with(opts.component, opts.plugins, InstanceCreator::default()).await?;
504 instance._guard.extend(guard);
505 Ok(instance)
506 }
507
508 pub async fn build_with(
509 mut opts: StandaloneOptions,
510 plugin_opts: Vec<PluginOptions>,
511 creator: InstanceCreator,
512 ) -> Result<(Instance, InstanceCreatorResult)> {
513 let mut plugins = Plugins::new();
514 plugins.insert(StandaloneFlag);
515 set_default_prefix(opts.default_column_prefix.as_deref())
516 .map_err(BoxedError::new)
517 .context(error::BuildCliSnafu)?;
518
519 opts.grpc.detect_server_addr();
520 let fe_opts = opts.frontend_options();
521 let dn_opts = opts.datanode_options();
522 let node_id = dn_opts.node_id;
523 let init_regions_parallelism = dn_opts.init_regions_parallelism;
524
525 plugins::setup_frontend_plugins_pre_build(&mut plugins, &plugin_opts, &fe_opts, None)
526 .await
527 .context(error::StartFrontendSnafu)?;
528
529 plugins::setup_datanode_plugins_pre_build(&mut plugins, &plugin_opts, &dn_opts)
530 .await
531 .context(error::StartDatanodeSnafu)?;
532
533 set_default_timezone(fe_opts.default_timezone.as_deref())
534 .context(error::InitTimezoneSnafu)?;
535
536 let data_home = &dn_opts.storage.data_home;
537 fs::create_dir_all(path::Path::new(data_home))
539 .context(error::CreateDirSnafu { dir: data_home })?;
540 let local_file_access = standalone_local_file_access(&dn_opts.storage)
541 .map_err(BoxedError::new)
542 .context(OtherSnafu)?;
543
544 let metadata_dir = metadata_store_dir(data_home);
545 let kv_backend = creator
546 .metadata_kv_backend_creator
547 .create(metadata_dir, &opts)
548 .await?;
549 let (procedure_manager, event_recorder_handle) =
550 standalone::build_procedure_manager(kv_backend.clone(), opts.procedure);
551
552 plugins::setup_standalone_plugins(&mut plugins, &plugin_opts, &opts, kv_backend.clone())
553 .await
554 .context(error::SetupStandalonePluginsSnafu)?;
555
556 let layered_cache_builder = LayeredCacheRegistryBuilder::default();
558 let fundamental_cache_registry = build_fundamental_cache_registry(kv_backend.clone());
559 let mut layered_cache_builder = with_default_composite_cache_registry(
560 layered_cache_builder.add_cache_registry(fundamental_cache_registry),
561 )
562 .context(error::BuildCacheRegistrySnafu)?;
563
564 if let Some(plugin_cache_builder) = plugins::standalone::configure_cache_registry(&plugins)
565 {
566 layered_cache_builder =
567 layered_cache_builder.add_cache_registry(plugin_cache_builder.build());
568 }
569
570 let layered_cache_registry = Arc::new(layered_cache_builder.build());
571
572 let mut builder = DatanodeBuilder::new(dn_opts, plugins.clone(), kv_backend.clone());
573 builder.with_cache_registry(layered_cache_registry.clone());
574 builder.with_local_file_access(local_file_access.clone());
575 if let Some(writable) = creator.open_regions_writable_override {
576 builder.with_open_regions_writable_override(writable);
577 }
578
579 plugins::setup_datanode_plugins_post_build(&mut plugins, &plugin_opts, &builder)
580 .await
581 .context(error::StartDatanodeSnafu)?;
582 builder.set_plugins(plugins.clone());
583
584 let datanode = builder.build().await.context(error::StartDatanodeSnafu)?;
585
586 let information_extension = Arc::new(StandaloneInformationExtension::new(
587 datanode.region_server(),
588 procedure_manager.clone(),
589 ));
590
591 plugins.insert::<InformationExtensionRef>(information_extension.clone());
592
593 let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
594
595 let (frontend_client, frontend_instance_handler) =
598 FrontendClient::from_empty_grpc_handler(opts.query.clone());
599 let frontend_client = Arc::new(frontend_client);
600
601 let builder = KvBackendCatalogManagerBuilder::new(
602 information_extension.clone(),
603 kv_backend.clone(),
604 layered_cache_registry.clone(),
605 )
606 .with_procedure_manager(procedure_manager.clone())
607 .with_process_manager(process_manager.clone());
608 let builder = if let Some(configurator) =
609 plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
610 {
611 let ctx = StandaloneCatalogManagerConfigureContext {
612 fe_client: frontend_client.clone(),
613 };
614 let ctx = CatalogManagerConfigureContext::Standalone(ctx);
615 configurator
616 .configure(builder, ctx)
617 .await
618 .context(OtherSnafu)?
619 } else {
620 builder
621 };
622 let catalog_manager = builder.build();
623
624 let table_metadata_manager =
625 Self::create_table_metadata_manager(kv_backend.clone()).await?;
626
627 let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
628 let flownode_options = FlownodeOptions {
629 flow: opts.flow.clone(),
630 ..Default::default()
631 };
632
633 let mut flow_builder = FlownodeBuilder::new(
634 flownode_options,
635 plugins.clone(),
636 table_metadata_manager.clone(),
637 catalog_manager.clone(),
638 flow_metadata_manager.clone(),
639 frontend_client.clone(),
640 );
641
642 plugins::setup_flownode_plugins_post_build(&mut plugins, &plugin_opts, &flow_builder)
643 .await
644 .context(error::StartFlownodeSnafu)?;
645 flow_builder.set_plugins(plugins.clone());
646
647 let flownode = flow_builder
648 .build()
649 .await
650 .map_err(BoxedError::new)
651 .context(error::OtherSnafu)?;
652 let flow_engine = flownode.flow_engine();
653
654 {
656 information_extension
657 .set_flow_engine(flow_engine.clone())
658 .await;
659 }
660
661 let node_manager = creator
662 .node_manager_creator
663 .create(&kv_backend, datanode.region_server(), flow_engine.clone())
664 .await?;
665
666 let table_id_allocator = creator.table_id_allocator_creator.create(&kv_backend);
667 let flow_id_sequence = Arc::new(
668 SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone())
669 .initial(MIN_USER_FLOW_ID as u64)
670 .step(10)
671 .build(),
672 );
673 let wal_provider =
674 Arc::new(build_standalone_wal_provider(&opts.wal, node_id, kv_backend.clone()).await?);
675 let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
676 table_id_allocator.clone(),
677 wal_provider.clone(),
678 ));
679 let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
680 flow_id_sequence,
681 ));
682
683 let ddl_context = DdlContext {
684 affinity_resolver: None,
685 affinity_fallback: Default::default(),
686 peer_alloc_context_provider: None,
687 node_manager: node_manager.clone(),
688 cache_invalidator: layered_cache_registry.clone(),
689 memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
690 leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
691 table_metadata_manager: table_metadata_manager.clone(),
692 table_metadata_allocator: table_metadata_allocator.clone(),
693 flow_metadata_manager: flow_metadata_manager.clone(),
694 flow_metadata_allocator: flow_metadata_allocator.clone(),
695 region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
696 soft_drop_enabled: false,
697 soft_drop_retention: None,
698 create_database_metadata_committer: None,
699 };
700
701 let ddl_manager = DdlManager::new(
702 ddl_context,
703 procedure_manager.clone(),
704 Arc::new(StandaloneRepartitionProcedureFactory),
705 );
706
707 let ddl_manager = if let Some(configurator) =
708 plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
709 {
710 let ctx = DdlManagerConfigureContext {
711 kv_backend: kv_backend.clone(),
712 fe_client: frontend_client.clone(),
713 catalog_manager: catalog_manager.clone(),
714 };
715 configurator
716 .configure(ddl_manager, ctx)
717 .await
718 .context(OtherSnafu)?
719 } else {
720 ddl_manager
721 };
722 ddl_manager
723 .register_loaders()
724 .context(error::InitDdlManagerSnafu)?;
725
726 let procedure_executor = creator
727 .procedure_executor_creator
728 .create(Arc::new(ddl_manager), procedure_manager.clone())
729 .await?;
730
731 let fe_instance = FrontendBuilder::new(
732 fe_opts.clone(),
733 kv_backend.clone(),
734 layered_cache_registry.clone(),
735 catalog_manager.clone(),
736 node_manager.clone(),
737 procedure_executor.clone(),
738 process_manager,
739 )
740 .with_local_file_access(local_file_access);
741
742 plugins::setup_frontend_plugins_post_build(&mut plugins, &plugin_opts, &fe_instance)
743 .await
744 .context(error::StartFrontendSnafu)?;
745
746 let fe_instance = fe_instance
747 .with_plugin(plugins.clone())
748 .try_build()
749 .await
750 .context(error::StartFrontendSnafu)?;
751 let fe_instance = Arc::new(fe_instance);
752
753 event_recorder_handle.install(fe_instance.event_recorder());
754
755 let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
757 let weak_grpc_handler = Arc::downgrade(&grpc_handler);
758 frontend_instance_handler
759 .set_handler(weak_grpc_handler)
760 .await;
761
762 let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
763 .build()
764 .context(error::StartFrontendSnafu)?;
765
766 let frontend = Frontend {
767 instance: fe_instance,
768 servers,
769 heartbeat_task: None,
770 };
771 let leader_services_context = LeaderServicesContext {
772 procedure_manager: procedure_manager.clone(),
773 wal_provider: wal_provider.clone(),
774 region_server: datanode.region_server(),
775 kv_backend: kv_backend.clone(),
776 cache_registry: layered_cache_registry,
777 catalog_manager,
778 flow_engine,
779 frontend_client,
780 node_id,
781 init_regions_parallelism,
782 plugin_options: plugin_opts,
783 };
784
785 let instance = Instance {
786 datanode,
787 frontend,
788 flownode,
789 procedure_manager,
790 leader_services_controller: creator.leader_services_controller,
791 leader_services_context,
792 _guard: vec![],
793 };
794 let result = InstanceCreatorResult {
795 kv_backend,
796 node_manager,
797 table_id_allocator,
798 };
799 Ok((instance, result))
800 }
801
802 pub async fn create_table_metadata_manager(
803 kv_backend: KvBackendRef,
804 ) -> Result<TableMetadataManagerRef> {
805 let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
806
807 table_metadata_manager
808 .init()
809 .await
810 .context(error::InitMetadataSnafu)?;
811
812 Ok(table_metadata_manager)
813 }
814}
815
816#[async_trait]
817pub trait NodeManagerCreator: Send + Sync {
818 async fn create(
819 &self,
820 kv_backend: &KvBackendRef,
821 region_server: RegionServer,
822 flow_server: FlownodeRef,
823 ) -> Result<NodeManagerRef>;
824}
825
826pub struct DefaultNodeManagerCreator;
827
828#[async_trait]
829impl NodeManagerCreator for DefaultNodeManagerCreator {
830 async fn create(
831 &self,
832 _: &KvBackendRef,
833 region_server: RegionServer,
834 flow_server: FlownodeRef,
835 ) -> Result<NodeManagerRef> {
836 Ok(Arc::new(StandaloneDatanodeManager {
837 region_server,
838 flow_server,
839 }))
840 }
841}
842
843#[async_trait]
849pub trait MetadataKvBackendCreator: Send + Sync {
850 async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef>;
851}
852
853pub struct DefaultMetadataKvBackendCreator;
854
855#[async_trait]
856impl MetadataKvBackendCreator for DefaultMetadataKvBackendCreator {
857 async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef> {
858 standalone::build_metadata_kvbackend(metadata_dir, opts.metadata_store)
859 .context(error::BuildMetadataKvbackendSnafu)
860 }
861}
862
863pub trait TableIdAllocatorCreator: Send + Sync {
864 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
865}
866
867struct DefaultTableIdAllocatorCreator;
868
869impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
870 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
871 Arc::new(
872 SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
873 .initial(MIN_USER_TABLE_ID as u64)
874 .step(10)
875 .build(),
876 )
877 }
878}
879
880#[async_trait]
881pub trait ProcedureExecutorCreator: Send + Sync {
882 async fn create(
883 &self,
884 ddl_manager: DdlManagerRef,
885 procedure_manager: ProcedureManagerRef,
886 ) -> Result<ProcedureExecutorRef>;
887}
888
889pub struct DefaultProcedureExecutorCreator;
890
891#[async_trait]
892impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
893 async fn create(
894 &self,
895 ddl_manager: DdlManagerRef,
896 procedure_manager: ProcedureManagerRef,
897 ) -> Result<ProcedureExecutorRef> {
898 Ok(Arc::new(LocalProcedureExecutor::new(
899 ddl_manager,
900 procedure_manager,
901 )))
902 }
903}
904
905#[async_trait]
906pub trait StandaloneLeaderServicesController: Send + Sync {
907 async fn start(&self, context: LeaderServicesContext) -> Result<()>;
912
913 async fn stop(
915 &self,
916 procedure_manager: ProcedureManagerRef,
917 region_server: RegionServer,
918 ) -> Result<()>;
919}
920
921#[derive(Clone)]
922pub struct LeaderServicesContext {
932 pub procedure_manager: ProcedureManagerRef,
933 pub wal_provider: WalProviderRef,
934 pub region_server: RegionServer,
935 pub kv_backend: KvBackendRef,
936 pub cache_registry: LayeredCacheRegistryRef,
937 pub catalog_manager: CatalogManagerRef,
938 pub flow_engine: FlowDualEngineRef,
939 pub frontend_client: Arc<FrontendClient>,
940 pub node_id: Option<DatanodeId>,
941 pub init_regions_parallelism: usize,
942 pub plugin_options: Vec<PluginOptions>,
943}
944
945pub struct DefaultStandaloneLeaderServicesController;
946
947#[async_trait]
948impl StandaloneLeaderServicesController for DefaultStandaloneLeaderServicesController {
949 async fn start(&self, context: LeaderServicesContext) -> Result<()> {
950 context
951 .procedure_manager
952 .start()
953 .await
954 .context(error::StartProcedureManagerSnafu)?;
955 context
956 .wal_provider
957 .start()
958 .await
959 .context(error::StartWalProviderSnafu)
960 }
961
962 async fn stop(
963 &self,
964 procedure_manager: ProcedureManagerRef,
965 _region_server: RegionServer,
966 ) -> Result<()> {
967 procedure_manager
968 .stop()
969 .await
970 .context(error::StopProcedureManagerSnafu)
971 }
972}
973
974pub struct InstanceCreator {
977 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
980 node_manager_creator: Box<dyn NodeManagerCreator>,
981 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
982 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
983 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
984 open_regions_writable_override: Option<bool>,
985}
986
987impl InstanceCreator {
988 pub fn new(
989 node_manager_creator: Box<dyn NodeManagerCreator>,
990 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
991 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
992 ) -> Self {
993 Self {
994 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
995 node_manager_creator,
996 table_id_allocator_creator,
997 procedure_executor_creator,
998 leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
999 open_regions_writable_override: None,
1000 }
1001 }
1002
1003 pub fn with_metadata_kv_backend_creator(
1004 mut self,
1005 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
1006 ) -> Self {
1007 self.metadata_kv_backend_creator = metadata_kv_backend_creator;
1008 self
1009 }
1010
1011 pub fn map_metadata_kv_backend_creator<F>(mut self, f: F) -> Self
1016 where
1017 F: FnOnce(Box<dyn MetadataKvBackendCreator>) -> Box<dyn MetadataKvBackendCreator>,
1018 {
1019 self.metadata_kv_backend_creator = f(self.metadata_kv_backend_creator);
1020 self
1021 }
1022
1023 pub fn map_node_manager_creator<F>(mut self, f: F) -> Self
1025 where
1026 F: FnOnce(Box<dyn NodeManagerCreator>) -> Box<dyn NodeManagerCreator>,
1027 {
1028 self.node_manager_creator = f(self.node_manager_creator);
1029 self
1030 }
1031
1032 pub fn map_procedure_executor_creator<F>(mut self, f: F) -> Self
1034 where
1035 F: FnOnce(Box<dyn ProcedureExecutorCreator>) -> Box<dyn ProcedureExecutorCreator>,
1036 {
1037 self.procedure_executor_creator = f(self.procedure_executor_creator);
1038 self
1039 }
1040
1041 pub fn with_leader_services_controller(
1043 mut self,
1044 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
1045 ) -> Self {
1046 self.leader_services_controller = leader_services_controller;
1047 self
1048 }
1049
1050 pub fn with_open_regions_writable_override(mut self, writable: bool) -> Self {
1058 self.open_regions_writable_override = Some(writable);
1059 self
1060 }
1061}
1062
1063impl Default for InstanceCreator {
1064 fn default() -> Self {
1065 Self {
1066 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
1067 node_manager_creator: Box::new(DefaultNodeManagerCreator),
1068 table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
1069 procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
1070 leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
1071 open_regions_writable_override: None,
1072 }
1073 }
1074}
1075
1076pub struct InstanceCreatorResult {
1079 pub kv_backend: KvBackendRef,
1080 pub node_manager: NodeManagerRef,
1081 pub table_id_allocator: Arc<Sequence>,
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use std::default::Default;
1087 use std::io::Write;
1088 use std::time::Duration;
1089
1090 use auth::{Identity, Password, UserProviderRef};
1091 use clap::{CommandFactory, Parser};
1092 use common_base::readable_size::ReadableSize;
1093 use common_config::ENV_VAR_SEP;
1094 use common_meta::ddl::allocator::wal_options::WalOptionsAllocator;
1095 use common_meta::kv_backend::memory::MemoryKvBackend;
1096 use common_options::plugin_options::StandaloneFlag;
1097 use common_test_util::temp_dir::{create_named_temp_file, create_temp_dir};
1098 use common_wal::config::object_store::ObjectStoreWalConfig;
1099 use common_wal::options::{ObjectStoreWalOptions, WalOptions};
1100 use frontend::frontend::FrontendOptions;
1101 use object_store::config::{FileConfig, GcsConfig};
1102 use servers::grpc::GrpcOptions;
1103 use servers::server::ServerHandlers;
1104 use store_api::logstore::LogStore;
1105
1106 use super::*;
1107 use crate::options::GlobalOptions;
1108
1109 struct FailingLeaderServices;
1110
1111 #[async_trait]
1112 impl StandaloneLeaderServicesController for FailingLeaderServices {
1113 async fn start(&self, _context: LeaderServicesContext) -> Result<()> {
1114 Ok(())
1115 }
1116
1117 async fn stop(
1118 &self,
1119 _procedure_manager: ProcedureManagerRef,
1120 _region_server: RegionServer,
1121 ) -> Result<()> {
1122 error::IllegalConfigSnafu {
1123 msg: "leader services failed to stop",
1124 }
1125 .fail()
1126 }
1127 }
1128
1129 struct FailingServer;
1130
1131 #[async_trait]
1132 impl servers::server::Server for FailingServer {
1133 async fn shutdown(&self) -> servers::error::Result<()> {
1134 servers::error::InternalSnafu {
1135 err_msg: "server failed to stop",
1136 }
1137 .fail()
1138 }
1139
1140 async fn start(&mut self, _listening: SocketAddr) -> servers::error::Result<()> {
1141 Ok(())
1142 }
1143
1144 fn name(&self) -> &str {
1145 "failing"
1146 }
1147
1148 fn as_any(&self) -> &dyn std::any::Any {
1149 self
1150 }
1151 }
1152
1153 #[tokio::test]
1154 async fn test_stop_runs_every_step_and_returns_the_first_error() {
1155 common_telemetry::init_default_ut_logging();
1156 let data_home = create_temp_dir("standalone-stop");
1157 let mut opts = StandaloneOptions {
1158 wal: DatanodeWalConfig::ObjectStore(ObjectStoreWalConfig::default()),
1159 ..Default::default()
1160 };
1161 opts.storage.data_home = data_home.path().to_str().unwrap().to_string();
1162 let creator = InstanceCreator::default()
1163 .with_leader_services_controller(Box::new(FailingLeaderServices));
1164 let (mut instance, _) = StartCommand::build_with(opts, vec![], creator)
1165 .await
1166 .unwrap();
1167 let log_store = instance.datanode.object_store_log_store().unwrap();
1168 let mut services = ServerHandlers::default();
1170 services.insert((Box::new(FailingServer), "127.0.0.1:0".parse().unwrap()));
1171 services.start_all().await.unwrap();
1172 instance.datanode.setup_services(services);
1173
1174 let err = instance.stop().await.unwrap_err();
1175
1176 assert!(
1177 matches!(&err, error::Error::IllegalConfig { msg, .. } if msg == "leader services failed to stop"),
1178 "unexpected error: {err:?}"
1179 );
1180 assert!(matches!(
1181 log_store.append_batch(vec![]).await,
1182 Err(log_store::error::Error::ObjectStoreWalStopped { .. })
1183 ));
1184 }
1185
1186 #[tokio::test]
1187 async fn test_build_standalone_wal_provider() {
1188 let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
1189
1190 let config = ObjectStoreWalConfig {
1191 prefix: "cluster-a/wal".to_string(),
1192 ..Default::default()
1193 };
1194 let node_prefix = config.node_prefix(0, STANDALONE_GENERATION);
1196 assert_eq!(node_prefix, "cluster-a/wal/datanodes/0/epochs/0");
1197 let provider = build_standalone_wal_provider(
1198 &DatanodeWalConfig::ObjectStore(config),
1199 Some(0),
1200 kv_backend.clone(),
1201 )
1202 .await
1203 .unwrap();
1204 assert!(matches!(
1205 &provider,
1206 WalProvider::ObjectStore { prefix } if *prefix == node_prefix
1207 ));
1208 let regions = vec![0, 1];
1209 let wal_options = provider.allocate(®ions, false).await.unwrap();
1210 for region in regions {
1211 assert_eq!(
1212 wal_options[®ion],
1213 WalOptions::ObjectStore(ObjectStoreWalOptions::new(node_prefix.clone()))
1214 );
1215 }
1216
1217 let provider =
1218 build_standalone_wal_provider(&DatanodeWalConfig::default(), Some(0), kv_backend)
1219 .await
1220 .unwrap();
1221 assert!(matches!(provider, WalProvider::RaftEngine));
1222 }
1223
1224 #[test]
1225 fn test_standalone_local_file_access_config() {
1226 let data_home = create_temp_dir("standalone_copy_root");
1227 let storage = StorageConfig {
1228 data_home: data_home.path().display().to_string(),
1229 ..Default::default()
1230 };
1231 let access = standalone_local_file_access(&storage).unwrap();
1232 assert_eq!(
1233 access.sandbox_root().unwrap(),
1234 data_home.path().join("copy").canonicalize().unwrap()
1235 );
1236
1237 let remote_data_home = StorageConfig {
1238 data_home: "s3://bucket/data".to_string(),
1239 ..Default::default()
1240 };
1241 assert!(matches!(
1242 standalone_local_file_access(&remote_data_home).unwrap(),
1243 LocalFileAccess::Disabled
1244 ));
1245
1246 let explicit_root = create_temp_dir("standalone_explicit_copy_root");
1247 let remote_with_explicit_root = StorageConfig {
1248 data_home: "s3://bucket/data".to_string(),
1249 copy_root: Some(explicit_root.path().display().to_string()),
1250 ..Default::default()
1251 };
1252 assert_eq!(
1253 standalone_local_file_access(&remote_with_explicit_root)
1254 .unwrap()
1255 .sandbox_root()
1256 .unwrap(),
1257 explicit_root.path().canonicalize().unwrap()
1258 );
1259
1260 let remote_copy_root = StorageConfig {
1261 data_home: data_home.path().display().to_string(),
1262 copy_root: Some("s3://bucket/copy".to_string()),
1263 ..Default::default()
1264 };
1265 assert!(matches!(
1266 standalone_local_file_access(&remote_copy_root),
1267 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1268 ));
1269
1270 let exposes_internal = StorageConfig {
1271 data_home: data_home.path().display().to_string(),
1272 copy_root: Some(data_home.path().join("data").display().to_string()),
1273 ..Default::default()
1274 };
1275 assert!(matches!(
1276 standalone_local_file_access(&exposes_internal),
1277 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1278 ));
1279
1280 let exposes_data_home = StorageConfig {
1281 data_home: data_home.path().display().to_string(),
1282 copy_root: Some(data_home.path().parent().unwrap().display().to_string()),
1283 ..Default::default()
1284 };
1285 assert!(matches!(
1286 standalone_local_file_access(&exposes_data_home),
1287 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1288 ));
1289 }
1290
1291 #[tokio::test]
1292 async fn test_try_from_start_command_to_anymap() {
1293 let fe_opts = FrontendOptions {
1294 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1295 ..Default::default()
1296 };
1297
1298 let mut plugins = Plugins::new();
1299 plugins.insert(StandaloneFlag);
1300 plugins::setup_frontend_plugins_pre_build(&mut plugins, &[], &fe_opts, None)
1301 .await
1302 .unwrap();
1303
1304 let provider = plugins.get::<UserProviderRef>().unwrap();
1305 let result = provider
1306 .authenticate(
1307 Identity::UserId("test", None),
1308 Password::PlainText("test".to_string().into()),
1309 )
1310 .await;
1311 let _ = result.unwrap();
1312 }
1313
1314 #[test]
1315 fn test_toml() {
1316 let opts = StandaloneOptions::default();
1317 let toml_string = toml::to_string(&opts).unwrap();
1318 let parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
1319 assert_eq!(parsed.otlp, opts.otlp);
1320 }
1321
1322 #[test]
1323 fn test_read_from_config_file() {
1324 let mut file = create_named_temp_file();
1325 let toml_str = r#"
1326 enable_memory_catalog = true
1327
1328 [wal]
1329 provider = "raft_engine"
1330 dir = "./greptimedb_data/test/wal"
1331 file_size = "1GB"
1332 purge_threshold = "50GB"
1333 purge_interval = "10m"
1334 read_batch_size = 128
1335 sync_write = false
1336
1337 [storage]
1338 data_home = "./greptimedb_data/"
1339 type = "File"
1340
1341 [[storage.providers]]
1342 type = "Gcs"
1343 bucket = "foo"
1344 endpoint = "bar"
1345
1346 [[storage.providers]]
1347 type = "S3"
1348 access_key_id = "access_key_id"
1349 secret_access_key = "secret_access_key"
1350
1351 [storage.compaction]
1352 max_inflight_tasks = 3
1353 max_files_in_level0 = 7
1354 max_purge_tasks = 32
1355
1356 [storage.manifest]
1357 checkpoint_margin = 9
1358 gc_duration = '7s'
1359
1360 [http]
1361 addr = "127.0.0.1:4000"
1362 timeout = "33s"
1363 body_limit = "128MB"
1364
1365 [opentsdb]
1366 enable = true
1367
1368 [logging]
1369 level = "debug"
1370 dir = "./greptimedb_data/test/logs"
1371 "#;
1372 write!(file, "{}", toml_str).unwrap();
1373 let cmd = StartCommand {
1374 config_file: Some(file.path().to_str().unwrap().to_string()),
1375 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1376 ..Default::default()
1377 };
1378
1379 let options = cmd
1380 .load_options(&GlobalOptions::default())
1381 .unwrap()
1382 .component;
1383 let fe_opts = options.frontend_options();
1384 let dn_opts = options.datanode_options();
1385 let logging_opts = options.logging;
1386 assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
1387 assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
1388 assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
1389 assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
1390 assert!(fe_opts.mysql.enable);
1391 assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
1392 assert_eq!(2, fe_opts.mysql.runtime_size);
1393 assert_eq!(None, fe_opts.mysql.reject_no_database);
1394 assert!(fe_opts.influxdb.enable);
1395 assert!(fe_opts.opentsdb.enable);
1396
1397 let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
1398 unreachable!()
1399 };
1400 assert_eq!(
1401 "./greptimedb_data/test/wal",
1402 raft_engine_config.dir.unwrap()
1403 );
1404
1405 assert!(matches!(
1406 &dn_opts.storage.store,
1407 object_store::config::ObjectStoreConfig::File(FileConfig { .. })
1408 ));
1409 assert_eq!(dn_opts.storage.providers.len(), 2);
1410 assert!(matches!(
1411 dn_opts.storage.providers[0],
1412 object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
1413 ));
1414 match &dn_opts.storage.providers[1] {
1415 object_store::config::ObjectStoreConfig::S3(s3_config) => {
1416 assert_eq!(
1417 "SecretBox<alloc::string::String>([REDACTED])".to_string(),
1418 format!("{:?}", s3_config.connection.access_key_id)
1419 );
1420 }
1421 _ => {
1422 unreachable!()
1423 }
1424 }
1425
1426 assert_eq!("debug", logging_opts.level.as_ref().unwrap());
1427 assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
1428 }
1429
1430 #[test]
1431 fn test_load_log_options_from_cli() {
1432 let cmd = StartCommand {
1433 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1434 mysql_addr: Some("127.0.0.1:4002".to_string()),
1435 postgres_addr: Some("127.0.0.1:4003".to_string()),
1436 ..Default::default()
1437 };
1438
1439 let opts = cmd
1440 .load_options(&GlobalOptions {
1441 log_dir: Some("./greptimedb_data/test/logs".to_string()),
1442 log_level: Some("debug".to_string()),
1443
1444 #[cfg(feature = "tokio-console")]
1445 tokio_console_addr: None,
1446 })
1447 .unwrap()
1448 .component;
1449
1450 assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
1451 assert_eq!("debug", opts.logging.level.unwrap());
1452 }
1453
1454 #[test]
1455 fn test_config_precedence_order() {
1456 let mut file = create_named_temp_file();
1457 let toml_str = r#"
1458 [http]
1459 addr = "127.0.0.1:4000"
1460
1461 [logging]
1462 level = "debug"
1463 "#;
1464 write!(file, "{}", toml_str).unwrap();
1465
1466 let env_prefix = "STANDALONE_UT";
1467 temp_env::with_vars(
1468 [
1469 (
1470 [
1472 env_prefix.to_string(),
1473 "logging".to_uppercase(),
1474 "dir".to_uppercase(),
1475 ]
1476 .join(ENV_VAR_SEP),
1477 Some("/other/log/dir"),
1478 ),
1479 (
1480 [
1482 env_prefix.to_string(),
1483 "logging".to_uppercase(),
1484 "level".to_uppercase(),
1485 ]
1486 .join(ENV_VAR_SEP),
1487 Some("info"),
1488 ),
1489 (
1490 [
1492 env_prefix.to_string(),
1493 "http".to_uppercase(),
1494 "addr".to_uppercase(),
1495 ]
1496 .join(ENV_VAR_SEP),
1497 Some("127.0.0.1:24000"),
1498 ),
1499 ],
1500 || {
1501 let command = StartCommand {
1502 config_file: Some(file.path().to_str().unwrap().to_string()),
1503 http_addr: Some("127.0.0.1:14000".to_string()),
1504 env_prefix: env_prefix.to_string(),
1505 ..Default::default()
1506 };
1507
1508 let opts = command.load_options(&Default::default()).unwrap().component;
1509
1510 assert_eq!(opts.logging.dir, "/other/log/dir");
1512
1513 assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
1515
1516 let fe_opts = opts.frontend_options();
1518 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
1519 assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
1520
1521 assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
1523 },
1524 );
1525 }
1526
1527 #[test]
1528 fn test_parse_grpc_bind_addr_aliases() {
1529 let command =
1530 StartCommand::try_parse_from(["standalone", "--grpc-bind-addr", "127.0.0.1:14001"])
1531 .unwrap();
1532 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
1533
1534 let command =
1535 StartCommand::try_parse_from(["standalone", "--rpc-bind-addr", "127.0.0.1:24001"])
1536 .unwrap();
1537 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1538
1539 let command =
1540 StartCommand::try_parse_from(["standalone", "--rpc-addr", "127.0.0.1:34001"]).unwrap();
1541 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1542 }
1543
1544 #[cfg(unix)]
1545 #[test]
1546 fn test_parse_daemon_flag() {
1547 let command = StartCommand::try_parse_from(["standalone", "--daemon"]).unwrap();
1548 assert!(command.is_daemon());
1549
1550 let command = StartCommand::try_parse_from(["standalone", "-d"]).unwrap();
1551 assert!(command.is_daemon());
1552
1553 let command = StartCommand::try_parse_from(["standalone"]).unwrap();
1554 assert!(!command.is_daemon());
1555 }
1556
1557 #[test]
1558 fn test_help_uses_grpc_option_names() {
1559 let mut cmd = StartCommand::command();
1560 let mut help = Vec::new();
1561 cmd.write_long_help(&mut help).unwrap();
1562 let help = String::from_utf8(help).unwrap();
1563
1564 assert!(help.contains("--grpc-bind-addr"));
1565 assert!(!help.contains("--rpc-bind-addr"));
1566 assert!(!help.contains("--rpc-addr"));
1567 }
1568
1569 #[test]
1570 fn test_load_default_standalone_options() {
1571 let options =
1572 StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1573 let default_options = StandaloneOptions::default();
1574 assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1575 assert_eq!(options.http, default_options.http);
1576 assert_eq!(options.grpc, default_options.grpc);
1577 assert_eq!(options.mysql, default_options.mysql);
1578 assert_eq!(options.postgres, default_options.postgres);
1579 assert_eq!(options.opentsdb, default_options.opentsdb);
1580 assert_eq!(options.influxdb, default_options.influxdb);
1581 assert_eq!(options.prom_store, default_options.prom_store);
1582 assert_eq!(options.wal, default_options.wal);
1583 assert_eq!(options.metadata_store, default_options.metadata_store);
1584 assert_eq!(options.procedure, default_options.procedure);
1585 assert_eq!(options.logging, default_options.logging);
1586 assert_eq!(options.region_engine, default_options.region_engine);
1587 }
1588
1589 #[test]
1590 fn test_cache_config() {
1591 let toml_str = r#"
1592 [storage]
1593 data_home = "test_data_home"
1594 type = "S3"
1595 [storage.cache_config]
1596 enable_read_cache = true
1597 "#;
1598 let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1599 opts.sanitize();
1600 assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1601 assert_eq!(
1602 opts.storage.store.cache_config().unwrap().cache_path,
1603 "test_data_home"
1604 );
1605 }
1606
1607 #[test]
1608 #[cfg(not(feature = "enterprise"))]
1609 fn test_load_options_ignores_unknown_plugin_options() {
1610 let mut file = create_named_temp_file();
1614 write!(
1615 file,
1616 r#"
1617[[plugins]]
1618SomeUnknownPlugin = {{ feature = "foo", count = 5 }}
1619
1620[[plugins]]
1621AnotherUnknownPlugin = {{}}
1622"#
1623 )
1624 .unwrap();
1625
1626 let opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1627 Some(file.path().to_str().unwrap()),
1628 "GREPTIMEDB_STANDALONE_UT",
1629 )
1630 .expect(
1631 "loading a config with unrecognized plugin options should succeed, \
1632 ignoring the unknown ones",
1633 );
1634 assert!(opts.plugins.is_empty());
1637 }
1638
1639 #[test]
1640 fn test_load_options_errors_on_malformed_known_plugin_option() {
1641 let mut file = create_named_temp_file();
1646 write!(
1647 file,
1648 r#"
1649[[plugins]]
1650Dummy = {{ unexpected = "payload" }}
1651"#
1652 )
1653 .unwrap();
1654
1655 let result = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1656 Some(file.path().to_str().unwrap()),
1657 "GREPTIMEDB_STANDALONE_UT",
1658 );
1659 assert!(
1660 result.is_err(),
1661 "a malformed payload for a known plugin variant must fail config loading"
1662 );
1663 }
1664
1665 #[test]
1666 #[cfg(not(feature = "enterprise"))]
1667 fn test_load_options_ignores_multi_key_unknown_plugin_entry() {
1668 let mut file = create_named_temp_file();
1672 write!(
1673 file,
1674 r#"
1675[[plugins]]
1676FirstUnknownPlugin = {{ a = 1 }}
1677SecondUnknownPlugin = {{ b = 2 }}
1678"#
1679 )
1680 .unwrap();
1681
1682 let opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1683 Some(file.path().to_str().unwrap()),
1684 "GREPTIMEDB_STANDALONE_UT",
1685 )
1686 .expect(
1687 "loading a config with a multi-key unknown plugin entry should succeed, \
1688 ignoring the unknown ones",
1689 );
1690 assert!(opts.plugins.is_empty());
1691 }
1692}