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 node_manager: node_manager.clone(),
685 cache_invalidator: layered_cache_registry.clone(),
686 memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
687 leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
688 table_metadata_manager: table_metadata_manager.clone(),
689 table_metadata_allocator: table_metadata_allocator.clone(),
690 flow_metadata_manager: flow_metadata_manager.clone(),
691 flow_metadata_allocator: flow_metadata_allocator.clone(),
692 region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
693 soft_drop_enabled: false,
694 soft_drop_retention: None,
695 create_database_metadata_committer: None,
696 };
697
698 let ddl_manager = DdlManager::new(
699 ddl_context,
700 procedure_manager.clone(),
701 Arc::new(StandaloneRepartitionProcedureFactory),
702 );
703
704 let ddl_manager = if let Some(configurator) =
705 plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
706 {
707 let ctx = DdlManagerConfigureContext {
708 kv_backend: kv_backend.clone(),
709 fe_client: frontend_client.clone(),
710 catalog_manager: catalog_manager.clone(),
711 };
712 configurator
713 .configure(ddl_manager, ctx)
714 .await
715 .context(OtherSnafu)?
716 } else {
717 ddl_manager
718 };
719 ddl_manager
720 .register_loaders()
721 .context(error::InitDdlManagerSnafu)?;
722
723 let procedure_executor = creator
724 .procedure_executor_creator
725 .create(Arc::new(ddl_manager), procedure_manager.clone())
726 .await?;
727
728 let fe_instance = FrontendBuilder::new(
729 fe_opts.clone(),
730 kv_backend.clone(),
731 layered_cache_registry.clone(),
732 catalog_manager.clone(),
733 node_manager.clone(),
734 procedure_executor.clone(),
735 process_manager,
736 )
737 .with_local_file_access(local_file_access);
738
739 plugins::setup_frontend_plugins_post_build(&mut plugins, &plugin_opts, &fe_instance)
740 .await
741 .context(error::StartFrontendSnafu)?;
742
743 let fe_instance = fe_instance
744 .with_plugin(plugins.clone())
745 .try_build()
746 .await
747 .context(error::StartFrontendSnafu)?;
748 let fe_instance = Arc::new(fe_instance);
749
750 event_recorder_handle.install(fe_instance.event_recorder());
751
752 let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
754 let weak_grpc_handler = Arc::downgrade(&grpc_handler);
755 frontend_instance_handler
756 .set_handler(weak_grpc_handler)
757 .await;
758
759 let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
760 .build()
761 .context(error::StartFrontendSnafu)?;
762
763 let frontend = Frontend {
764 instance: fe_instance,
765 servers,
766 heartbeat_task: None,
767 };
768 let leader_services_context = LeaderServicesContext {
769 procedure_manager: procedure_manager.clone(),
770 wal_provider: wal_provider.clone(),
771 region_server: datanode.region_server(),
772 kv_backend: kv_backend.clone(),
773 cache_registry: layered_cache_registry,
774 catalog_manager,
775 flow_engine,
776 frontend_client,
777 node_id,
778 init_regions_parallelism,
779 plugin_options: plugin_opts,
780 };
781
782 let instance = Instance {
783 datanode,
784 frontend,
785 flownode,
786 procedure_manager,
787 leader_services_controller: creator.leader_services_controller,
788 leader_services_context,
789 _guard: vec![],
790 };
791 let result = InstanceCreatorResult {
792 kv_backend,
793 node_manager,
794 table_id_allocator,
795 };
796 Ok((instance, result))
797 }
798
799 pub async fn create_table_metadata_manager(
800 kv_backend: KvBackendRef,
801 ) -> Result<TableMetadataManagerRef> {
802 let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
803
804 table_metadata_manager
805 .init()
806 .await
807 .context(error::InitMetadataSnafu)?;
808
809 Ok(table_metadata_manager)
810 }
811}
812
813#[async_trait]
814pub trait NodeManagerCreator: Send + Sync {
815 async fn create(
816 &self,
817 kv_backend: &KvBackendRef,
818 region_server: RegionServer,
819 flow_server: FlownodeRef,
820 ) -> Result<NodeManagerRef>;
821}
822
823pub struct DefaultNodeManagerCreator;
824
825#[async_trait]
826impl NodeManagerCreator for DefaultNodeManagerCreator {
827 async fn create(
828 &self,
829 _: &KvBackendRef,
830 region_server: RegionServer,
831 flow_server: FlownodeRef,
832 ) -> Result<NodeManagerRef> {
833 Ok(Arc::new(StandaloneDatanodeManager {
834 region_server,
835 flow_server,
836 }))
837 }
838}
839
840#[async_trait]
846pub trait MetadataKvBackendCreator: Send + Sync {
847 async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef>;
848}
849
850pub struct DefaultMetadataKvBackendCreator;
851
852#[async_trait]
853impl MetadataKvBackendCreator for DefaultMetadataKvBackendCreator {
854 async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef> {
855 standalone::build_metadata_kvbackend(metadata_dir, opts.metadata_store)
856 .context(error::BuildMetadataKvbackendSnafu)
857 }
858}
859
860pub trait TableIdAllocatorCreator: Send + Sync {
861 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
862}
863
864struct DefaultTableIdAllocatorCreator;
865
866impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
867 fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
868 Arc::new(
869 SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
870 .initial(MIN_USER_TABLE_ID as u64)
871 .step(10)
872 .build(),
873 )
874 }
875}
876
877#[async_trait]
878pub trait ProcedureExecutorCreator: Send + Sync {
879 async fn create(
880 &self,
881 ddl_manager: DdlManagerRef,
882 procedure_manager: ProcedureManagerRef,
883 ) -> Result<ProcedureExecutorRef>;
884}
885
886pub struct DefaultProcedureExecutorCreator;
887
888#[async_trait]
889impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
890 async fn create(
891 &self,
892 ddl_manager: DdlManagerRef,
893 procedure_manager: ProcedureManagerRef,
894 ) -> Result<ProcedureExecutorRef> {
895 Ok(Arc::new(LocalProcedureExecutor::new(
896 ddl_manager,
897 procedure_manager,
898 )))
899 }
900}
901
902#[async_trait]
903pub trait StandaloneLeaderServicesController: Send + Sync {
904 async fn start(&self, context: LeaderServicesContext) -> Result<()>;
909
910 async fn stop(
912 &self,
913 procedure_manager: ProcedureManagerRef,
914 region_server: RegionServer,
915 ) -> Result<()>;
916}
917
918#[derive(Clone)]
919pub struct LeaderServicesContext {
929 pub procedure_manager: ProcedureManagerRef,
930 pub wal_provider: WalProviderRef,
931 pub region_server: RegionServer,
932 pub kv_backend: KvBackendRef,
933 pub cache_registry: LayeredCacheRegistryRef,
934 pub catalog_manager: CatalogManagerRef,
935 pub flow_engine: FlowDualEngineRef,
936 pub frontend_client: Arc<FrontendClient>,
937 pub node_id: Option<DatanodeId>,
938 pub init_regions_parallelism: usize,
939 pub plugin_options: Vec<PluginOptions>,
940}
941
942pub struct DefaultStandaloneLeaderServicesController;
943
944#[async_trait]
945impl StandaloneLeaderServicesController for DefaultStandaloneLeaderServicesController {
946 async fn start(&self, context: LeaderServicesContext) -> Result<()> {
947 context
948 .procedure_manager
949 .start()
950 .await
951 .context(error::StartProcedureManagerSnafu)?;
952 context
953 .wal_provider
954 .start()
955 .await
956 .context(error::StartWalProviderSnafu)
957 }
958
959 async fn stop(
960 &self,
961 procedure_manager: ProcedureManagerRef,
962 _region_server: RegionServer,
963 ) -> Result<()> {
964 procedure_manager
965 .stop()
966 .await
967 .context(error::StopProcedureManagerSnafu)
968 }
969}
970
971pub struct InstanceCreator {
974 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
977 node_manager_creator: Box<dyn NodeManagerCreator>,
978 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
979 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
980 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
981 open_regions_writable_override: Option<bool>,
982}
983
984impl InstanceCreator {
985 pub fn new(
986 node_manager_creator: Box<dyn NodeManagerCreator>,
987 table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
988 procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
989 ) -> Self {
990 Self {
991 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
992 node_manager_creator,
993 table_id_allocator_creator,
994 procedure_executor_creator,
995 leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
996 open_regions_writable_override: None,
997 }
998 }
999
1000 pub fn with_metadata_kv_backend_creator(
1001 mut self,
1002 metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
1003 ) -> Self {
1004 self.metadata_kv_backend_creator = metadata_kv_backend_creator;
1005 self
1006 }
1007
1008 pub fn map_metadata_kv_backend_creator<F>(mut self, f: F) -> Self
1013 where
1014 F: FnOnce(Box<dyn MetadataKvBackendCreator>) -> Box<dyn MetadataKvBackendCreator>,
1015 {
1016 self.metadata_kv_backend_creator = f(self.metadata_kv_backend_creator);
1017 self
1018 }
1019
1020 pub fn map_node_manager_creator<F>(mut self, f: F) -> Self
1022 where
1023 F: FnOnce(Box<dyn NodeManagerCreator>) -> Box<dyn NodeManagerCreator>,
1024 {
1025 self.node_manager_creator = f(self.node_manager_creator);
1026 self
1027 }
1028
1029 pub fn map_procedure_executor_creator<F>(mut self, f: F) -> Self
1031 where
1032 F: FnOnce(Box<dyn ProcedureExecutorCreator>) -> Box<dyn ProcedureExecutorCreator>,
1033 {
1034 self.procedure_executor_creator = f(self.procedure_executor_creator);
1035 self
1036 }
1037
1038 pub fn with_leader_services_controller(
1040 mut self,
1041 leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
1042 ) -> Self {
1043 self.leader_services_controller = leader_services_controller;
1044 self
1045 }
1046
1047 pub fn with_open_regions_writable_override(mut self, writable: bool) -> Self {
1055 self.open_regions_writable_override = Some(writable);
1056 self
1057 }
1058}
1059
1060impl Default for InstanceCreator {
1061 fn default() -> Self {
1062 Self {
1063 metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
1064 node_manager_creator: Box::new(DefaultNodeManagerCreator),
1065 table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
1066 procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
1067 leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
1068 open_regions_writable_override: None,
1069 }
1070 }
1071}
1072
1073pub struct InstanceCreatorResult {
1076 pub kv_backend: KvBackendRef,
1077 pub node_manager: NodeManagerRef,
1078 pub table_id_allocator: Arc<Sequence>,
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083 use std::default::Default;
1084 use std::io::Write;
1085 use std::time::Duration;
1086
1087 use auth::{Identity, Password, UserProviderRef};
1088 use clap::{CommandFactory, Parser};
1089 use common_base::readable_size::ReadableSize;
1090 use common_config::ENV_VAR_SEP;
1091 use common_meta::ddl::allocator::wal_options::WalOptionsAllocator;
1092 use common_meta::kv_backend::memory::MemoryKvBackend;
1093 use common_options::plugin_options::StandaloneFlag;
1094 use common_test_util::temp_dir::{create_named_temp_file, create_temp_dir};
1095 use common_wal::config::object_store::ObjectStoreWalConfig;
1096 use common_wal::options::{ObjectStoreWalOptions, WalOptions};
1097 use frontend::frontend::FrontendOptions;
1098 use object_store::config::{FileConfig, GcsConfig};
1099 use servers::grpc::GrpcOptions;
1100 use servers::server::ServerHandlers;
1101 use store_api::logstore::LogStore;
1102
1103 use super::*;
1104 use crate::options::GlobalOptions;
1105
1106 struct FailingLeaderServices;
1107
1108 #[async_trait]
1109 impl StandaloneLeaderServicesController for FailingLeaderServices {
1110 async fn start(&self, _context: LeaderServicesContext) -> Result<()> {
1111 Ok(())
1112 }
1113
1114 async fn stop(
1115 &self,
1116 _procedure_manager: ProcedureManagerRef,
1117 _region_server: RegionServer,
1118 ) -> Result<()> {
1119 error::IllegalConfigSnafu {
1120 msg: "leader services failed to stop",
1121 }
1122 .fail()
1123 }
1124 }
1125
1126 struct FailingServer;
1127
1128 #[async_trait]
1129 impl servers::server::Server for FailingServer {
1130 async fn shutdown(&self) -> servers::error::Result<()> {
1131 servers::error::InternalSnafu {
1132 err_msg: "server failed to stop",
1133 }
1134 .fail()
1135 }
1136
1137 async fn start(&mut self, _listening: SocketAddr) -> servers::error::Result<()> {
1138 Ok(())
1139 }
1140
1141 fn name(&self) -> &str {
1142 "failing"
1143 }
1144
1145 fn as_any(&self) -> &dyn std::any::Any {
1146 self
1147 }
1148 }
1149
1150 #[tokio::test]
1151 async fn test_stop_runs_every_step_and_returns_the_first_error() {
1152 common_telemetry::init_default_ut_logging();
1153 let data_home = create_temp_dir("standalone-stop");
1154 let mut opts = StandaloneOptions {
1155 wal: DatanodeWalConfig::ObjectStore(ObjectStoreWalConfig::default()),
1156 ..Default::default()
1157 };
1158 opts.storage.data_home = data_home.path().to_str().unwrap().to_string();
1159 let creator = InstanceCreator::default()
1160 .with_leader_services_controller(Box::new(FailingLeaderServices));
1161 let (mut instance, _) = StartCommand::build_with(opts, vec![], creator)
1162 .await
1163 .unwrap();
1164 let log_store = instance.datanode.object_store_log_store().unwrap();
1165 let mut services = ServerHandlers::default();
1167 services.insert((Box::new(FailingServer), "127.0.0.1:0".parse().unwrap()));
1168 services.start_all().await.unwrap();
1169 instance.datanode.setup_services(services);
1170
1171 let err = instance.stop().await.unwrap_err();
1172
1173 assert!(
1174 matches!(&err, error::Error::IllegalConfig { msg, .. } if msg == "leader services failed to stop"),
1175 "unexpected error: {err:?}"
1176 );
1177 assert!(matches!(
1178 log_store.append_batch(vec![]).await,
1179 Err(log_store::error::Error::ObjectStoreWalStopped { .. })
1180 ));
1181 }
1182
1183 #[tokio::test]
1184 async fn test_build_standalone_wal_provider() {
1185 let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
1186
1187 let config = ObjectStoreWalConfig {
1188 prefix: "cluster-a/wal".to_string(),
1189 ..Default::default()
1190 };
1191 let node_prefix = config.node_prefix(0, STANDALONE_GENERATION);
1193 assert_eq!(node_prefix, "cluster-a/wal/datanodes/0/epochs/0");
1194 let provider = build_standalone_wal_provider(
1195 &DatanodeWalConfig::ObjectStore(config),
1196 Some(0),
1197 kv_backend.clone(),
1198 )
1199 .await
1200 .unwrap();
1201 assert!(matches!(
1202 &provider,
1203 WalProvider::ObjectStore { prefix } if *prefix == node_prefix
1204 ));
1205 let regions = vec![0, 1];
1206 let wal_options = provider.allocate(®ions, false).await.unwrap();
1207 for region in regions {
1208 assert_eq!(
1209 wal_options[®ion],
1210 WalOptions::ObjectStore(ObjectStoreWalOptions::new(node_prefix.clone()))
1211 );
1212 }
1213
1214 let provider =
1215 build_standalone_wal_provider(&DatanodeWalConfig::default(), Some(0), kv_backend)
1216 .await
1217 .unwrap();
1218 assert!(matches!(provider, WalProvider::RaftEngine));
1219 }
1220
1221 #[test]
1222 fn test_standalone_local_file_access_config() {
1223 let data_home = create_temp_dir("standalone_copy_root");
1224 let storage = StorageConfig {
1225 data_home: data_home.path().display().to_string(),
1226 ..Default::default()
1227 };
1228 let access = standalone_local_file_access(&storage).unwrap();
1229 assert_eq!(
1230 access.sandbox_root().unwrap(),
1231 data_home.path().join("copy").canonicalize().unwrap()
1232 );
1233
1234 let remote_data_home = StorageConfig {
1235 data_home: "s3://bucket/data".to_string(),
1236 ..Default::default()
1237 };
1238 assert!(matches!(
1239 standalone_local_file_access(&remote_data_home).unwrap(),
1240 LocalFileAccess::Disabled
1241 ));
1242
1243 let explicit_root = create_temp_dir("standalone_explicit_copy_root");
1244 let remote_with_explicit_root = StorageConfig {
1245 data_home: "s3://bucket/data".to_string(),
1246 copy_root: Some(explicit_root.path().display().to_string()),
1247 ..Default::default()
1248 };
1249 assert_eq!(
1250 standalone_local_file_access(&remote_with_explicit_root)
1251 .unwrap()
1252 .sandbox_root()
1253 .unwrap(),
1254 explicit_root.path().canonicalize().unwrap()
1255 );
1256
1257 let remote_copy_root = StorageConfig {
1258 data_home: data_home.path().display().to_string(),
1259 copy_root: Some("s3://bucket/copy".to_string()),
1260 ..Default::default()
1261 };
1262 assert!(matches!(
1263 standalone_local_file_access(&remote_copy_root),
1264 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1265 ));
1266
1267 let exposes_internal = StorageConfig {
1268 data_home: data_home.path().display().to_string(),
1269 copy_root: Some(data_home.path().join("data").display().to_string()),
1270 ..Default::default()
1271 };
1272 assert!(matches!(
1273 standalone_local_file_access(&exposes_internal),
1274 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1275 ));
1276
1277 let exposes_data_home = StorageConfig {
1278 data_home: data_home.path().display().to_string(),
1279 copy_root: Some(data_home.path().parent().unwrap().display().to_string()),
1280 ..Default::default()
1281 };
1282 assert!(matches!(
1283 standalone_local_file_access(&exposes_data_home),
1284 Err(common_datasource::error::Error::InvalidLocalFileRootConfig { .. })
1285 ));
1286 }
1287
1288 #[tokio::test]
1289 async fn test_try_from_start_command_to_anymap() {
1290 let fe_opts = FrontendOptions {
1291 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1292 ..Default::default()
1293 };
1294
1295 let mut plugins = Plugins::new();
1296 plugins.insert(StandaloneFlag);
1297 plugins::setup_frontend_plugins_pre_build(&mut plugins, &[], &fe_opts, None)
1298 .await
1299 .unwrap();
1300
1301 let provider = plugins.get::<UserProviderRef>().unwrap();
1302 let result = provider
1303 .authenticate(
1304 Identity::UserId("test", None),
1305 Password::PlainText("test".to_string().into()),
1306 )
1307 .await;
1308 let _ = result.unwrap();
1309 }
1310
1311 #[test]
1312 fn test_toml() {
1313 let opts = StandaloneOptions::default();
1314 let toml_string = toml::to_string(&opts).unwrap();
1315 let parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
1316 assert_eq!(parsed.otlp, opts.otlp);
1317 }
1318
1319 #[test]
1320 fn test_read_from_config_file() {
1321 let mut file = create_named_temp_file();
1322 let toml_str = r#"
1323 enable_memory_catalog = true
1324
1325 [wal]
1326 provider = "raft_engine"
1327 dir = "./greptimedb_data/test/wal"
1328 file_size = "1GB"
1329 purge_threshold = "50GB"
1330 purge_interval = "10m"
1331 read_batch_size = 128
1332 sync_write = false
1333
1334 [storage]
1335 data_home = "./greptimedb_data/"
1336 type = "File"
1337
1338 [[storage.providers]]
1339 type = "Gcs"
1340 bucket = "foo"
1341 endpoint = "bar"
1342
1343 [[storage.providers]]
1344 type = "S3"
1345 access_key_id = "access_key_id"
1346 secret_access_key = "secret_access_key"
1347
1348 [storage.compaction]
1349 max_inflight_tasks = 3
1350 max_files_in_level0 = 7
1351 max_purge_tasks = 32
1352
1353 [storage.manifest]
1354 checkpoint_margin = 9
1355 gc_duration = '7s'
1356
1357 [http]
1358 addr = "127.0.0.1:4000"
1359 timeout = "33s"
1360 body_limit = "128MB"
1361
1362 [opentsdb]
1363 enable = true
1364
1365 [logging]
1366 level = "debug"
1367 dir = "./greptimedb_data/test/logs"
1368 "#;
1369 write!(file, "{}", toml_str).unwrap();
1370 let cmd = StartCommand {
1371 config_file: Some(file.path().to_str().unwrap().to_string()),
1372 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1373 ..Default::default()
1374 };
1375
1376 let options = cmd
1377 .load_options(&GlobalOptions::default())
1378 .unwrap()
1379 .component;
1380 let fe_opts = options.frontend_options();
1381 let dn_opts = options.datanode_options();
1382 let logging_opts = options.logging;
1383 assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
1384 assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
1385 assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
1386 assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
1387 assert!(fe_opts.mysql.enable);
1388 assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
1389 assert_eq!(2, fe_opts.mysql.runtime_size);
1390 assert_eq!(None, fe_opts.mysql.reject_no_database);
1391 assert!(fe_opts.influxdb.enable);
1392 assert!(fe_opts.opentsdb.enable);
1393
1394 let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
1395 unreachable!()
1396 };
1397 assert_eq!(
1398 "./greptimedb_data/test/wal",
1399 raft_engine_config.dir.unwrap()
1400 );
1401
1402 assert!(matches!(
1403 &dn_opts.storage.store,
1404 object_store::config::ObjectStoreConfig::File(FileConfig { .. })
1405 ));
1406 assert_eq!(dn_opts.storage.providers.len(), 2);
1407 assert!(matches!(
1408 dn_opts.storage.providers[0],
1409 object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
1410 ));
1411 match &dn_opts.storage.providers[1] {
1412 object_store::config::ObjectStoreConfig::S3(s3_config) => {
1413 assert_eq!(
1414 "SecretBox<alloc::string::String>([REDACTED])".to_string(),
1415 format!("{:?}", s3_config.connection.access_key_id)
1416 );
1417 }
1418 _ => {
1419 unreachable!()
1420 }
1421 }
1422
1423 assert_eq!("debug", logging_opts.level.as_ref().unwrap());
1424 assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
1425 }
1426
1427 #[test]
1428 fn test_load_log_options_from_cli() {
1429 let cmd = StartCommand {
1430 user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1431 mysql_addr: Some("127.0.0.1:4002".to_string()),
1432 postgres_addr: Some("127.0.0.1:4003".to_string()),
1433 ..Default::default()
1434 };
1435
1436 let opts = cmd
1437 .load_options(&GlobalOptions {
1438 log_dir: Some("./greptimedb_data/test/logs".to_string()),
1439 log_level: Some("debug".to_string()),
1440
1441 #[cfg(feature = "tokio-console")]
1442 tokio_console_addr: None,
1443 })
1444 .unwrap()
1445 .component;
1446
1447 assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
1448 assert_eq!("debug", opts.logging.level.unwrap());
1449 }
1450
1451 #[test]
1452 fn test_config_precedence_order() {
1453 let mut file = create_named_temp_file();
1454 let toml_str = r#"
1455 [http]
1456 addr = "127.0.0.1:4000"
1457
1458 [logging]
1459 level = "debug"
1460 "#;
1461 write!(file, "{}", toml_str).unwrap();
1462
1463 let env_prefix = "STANDALONE_UT";
1464 temp_env::with_vars(
1465 [
1466 (
1467 [
1469 env_prefix.to_string(),
1470 "logging".to_uppercase(),
1471 "dir".to_uppercase(),
1472 ]
1473 .join(ENV_VAR_SEP),
1474 Some("/other/log/dir"),
1475 ),
1476 (
1477 [
1479 env_prefix.to_string(),
1480 "logging".to_uppercase(),
1481 "level".to_uppercase(),
1482 ]
1483 .join(ENV_VAR_SEP),
1484 Some("info"),
1485 ),
1486 (
1487 [
1489 env_prefix.to_string(),
1490 "http".to_uppercase(),
1491 "addr".to_uppercase(),
1492 ]
1493 .join(ENV_VAR_SEP),
1494 Some("127.0.0.1:24000"),
1495 ),
1496 ],
1497 || {
1498 let command = StartCommand {
1499 config_file: Some(file.path().to_str().unwrap().to_string()),
1500 http_addr: Some("127.0.0.1:14000".to_string()),
1501 env_prefix: env_prefix.to_string(),
1502 ..Default::default()
1503 };
1504
1505 let opts = command.load_options(&Default::default()).unwrap().component;
1506
1507 assert_eq!(opts.logging.dir, "/other/log/dir");
1509
1510 assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
1512
1513 let fe_opts = opts.frontend_options();
1515 assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
1516 assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
1517
1518 assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
1520 },
1521 );
1522 }
1523
1524 #[test]
1525 fn test_parse_grpc_bind_addr_aliases() {
1526 let command =
1527 StartCommand::try_parse_from(["standalone", "--grpc-bind-addr", "127.0.0.1:14001"])
1528 .unwrap();
1529 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
1530
1531 let command =
1532 StartCommand::try_parse_from(["standalone", "--rpc-bind-addr", "127.0.0.1:24001"])
1533 .unwrap();
1534 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1535
1536 let command =
1537 StartCommand::try_parse_from(["standalone", "--rpc-addr", "127.0.0.1:34001"]).unwrap();
1538 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1539 }
1540
1541 #[cfg(unix)]
1542 #[test]
1543 fn test_parse_daemon_flag() {
1544 let command = StartCommand::try_parse_from(["standalone", "--daemon"]).unwrap();
1545 assert!(command.is_daemon());
1546
1547 let command = StartCommand::try_parse_from(["standalone", "-d"]).unwrap();
1548 assert!(command.is_daemon());
1549
1550 let command = StartCommand::try_parse_from(["standalone"]).unwrap();
1551 assert!(!command.is_daemon());
1552 }
1553
1554 #[test]
1555 fn test_help_uses_grpc_option_names() {
1556 let mut cmd = StartCommand::command();
1557 let mut help = Vec::new();
1558 cmd.write_long_help(&mut help).unwrap();
1559 let help = String::from_utf8(help).unwrap();
1560
1561 assert!(help.contains("--grpc-bind-addr"));
1562 assert!(!help.contains("--rpc-bind-addr"));
1563 assert!(!help.contains("--rpc-addr"));
1564 }
1565
1566 #[test]
1567 fn test_load_default_standalone_options() {
1568 let options =
1569 StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1570 let default_options = StandaloneOptions::default();
1571 assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1572 assert_eq!(options.http, default_options.http);
1573 assert_eq!(options.grpc, default_options.grpc);
1574 assert_eq!(options.mysql, default_options.mysql);
1575 assert_eq!(options.postgres, default_options.postgres);
1576 assert_eq!(options.opentsdb, default_options.opentsdb);
1577 assert_eq!(options.influxdb, default_options.influxdb);
1578 assert_eq!(options.prom_store, default_options.prom_store);
1579 assert_eq!(options.wal, default_options.wal);
1580 assert_eq!(options.metadata_store, default_options.metadata_store);
1581 assert_eq!(options.procedure, default_options.procedure);
1582 assert_eq!(options.logging, default_options.logging);
1583 assert_eq!(options.region_engine, default_options.region_engine);
1584 }
1585
1586 #[test]
1587 fn test_cache_config() {
1588 let toml_str = r#"
1589 [storage]
1590 data_home = "test_data_home"
1591 type = "S3"
1592 [storage.cache_config]
1593 enable_read_cache = true
1594 "#;
1595 let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1596 opts.sanitize();
1597 assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1598 assert_eq!(
1599 opts.storage.store.cache_config().unwrap().cache_path,
1600 "test_data_home"
1601 );
1602 }
1603
1604 #[test]
1605 #[cfg(not(feature = "enterprise"))]
1606 fn test_load_options_ignores_unknown_plugin_options() {
1607 let mut file = create_named_temp_file();
1611 write!(
1612 file,
1613 r#"
1614[[plugins]]
1615SomeUnknownPlugin = {{ feature = "foo", count = 5 }}
1616
1617[[plugins]]
1618AnotherUnknownPlugin = {{}}
1619"#
1620 )
1621 .unwrap();
1622
1623 let opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1624 Some(file.path().to_str().unwrap()),
1625 "GREPTIMEDB_STANDALONE_UT",
1626 )
1627 .expect(
1628 "loading a config with unrecognized plugin options should succeed, \
1629 ignoring the unknown ones",
1630 );
1631 assert!(opts.plugins.is_empty());
1634 }
1635
1636 #[test]
1637 fn test_load_options_errors_on_malformed_known_plugin_option() {
1638 let mut file = create_named_temp_file();
1643 write!(
1644 file,
1645 r#"
1646[[plugins]]
1647Dummy = {{ unexpected = "payload" }}
1648"#
1649 )
1650 .unwrap();
1651
1652 let result = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1653 Some(file.path().to_str().unwrap()),
1654 "GREPTIMEDB_STANDALONE_UT",
1655 );
1656 assert!(
1657 result.is_err(),
1658 "a malformed payload for a known plugin variant must fail config loading"
1659 );
1660 }
1661
1662 #[test]
1663 #[cfg(not(feature = "enterprise"))]
1664 fn test_load_options_ignores_multi_key_unknown_plugin_entry() {
1665 let mut file = create_named_temp_file();
1669 write!(
1670 file,
1671 r#"
1672[[plugins]]
1673FirstUnknownPlugin = {{ a = 1 }}
1674SecondUnknownPlugin = {{ b = 2 }}
1675"#
1676 )
1677 .unwrap();
1678
1679 let opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
1680 Some(file.path().to_str().unwrap()),
1681 "GREPTIMEDB_STANDALONE_UT",
1682 )
1683 .expect(
1684 "loading a config with a multi-key unknown plugin entry should succeed, \
1685 ignoring the unknown ones",
1686 );
1687 assert!(opts.plugins.is_empty());
1688 }
1689}