1use std::collections::HashMap;
18use std::path::Path;
19use std::sync::Arc;
20use std::time::{Duration, Instant};
21
22use catalog::information_schema::NoopInformationExtension;
23use catalog::kvbackend::KvBackendCatalogManagerBuilder;
24use client::client_manager::NodeClients;
25use common_base::Plugins;
26use common_catalog::consts::{FILE_ENGINE, METRIC_ENGINE, MITO_ENGINE};
27use common_datasource::object_store::LocalFileAccess;
28use common_error::ext::BoxedError;
29use common_greptimedb_telemetry::GreptimeDBTelemetryTask;
30use common_grpc::channel_manager::ChannelConfig;
31use common_meta::cache::{LayeredCacheRegistry, SchemaCacheRef, TableSchemaCacheRef};
32use common_meta::cache_invalidator::CacheInvalidatorRef;
33use common_meta::datanode::TopicStatsReporter;
34use common_meta::key::runtime_switch::RuntimeSwitchManager;
35use common_meta::key::{SchemaMetadataManager, SchemaMetadataManagerRef};
36use common_meta::kv_backend::KvBackendRef;
37use common_meta::node_manager::NodeManagerRef;
38pub use common_procedure::options::ProcedureConfig;
39use common_query::prelude::set_default_prefix;
40use common_stat::ResourceStatImpl;
41use common_telemetry::{error, info, warn};
42use common_wal::config::DatanodeWalConfig;
43use common_wal::config::kafka::DatanodeKafkaConfig;
44use common_wal::config::object_store::{ObjectStoreWalConfig, STANDALONE_GENERATION};
45use common_wal::config::raft_engine::RaftEngineConfig;
46use file_engine::engine::FileRegionEngine;
47use log_store::ObjectStoreLogStore;
48use log_store::kafka::log_store::KafkaLogStore;
49use log_store::kafka::{GlobalIndexCollector, default_index_file};
50use log_store::noop::log_store::NoopLogStore;
51use log_store::raft_engine::log_store::RaftEngineLogStore;
52use meta_client::MetaClientRef;
53use metric_engine::engine::MetricEngine;
54use mito2::config::MitoConfig;
55use mito2::engine::{MitoEngine, MitoEngineBuilder};
56use mito2::region::opener::PartitionExprFetcherRef;
57use mito2::sst::file_ref::{FileReferenceManager, FileReferenceManagerRef};
58use object_store::ObjectStore;
59use object_store::manager::{ObjectStoreManager, ObjectStoreManagerRef};
60use object_store::util::normalize_dir;
61use query::QueryEngineFactory;
62use query::dummy_catalog::TableProviderFactoryRef;
63use query::region_query::RegionQueryHandlerFactoryRef;
64use servers::server::ServerHandlers;
65use snafu::{OptionExt, ResultExt, ensure};
66use store_api::logstore::LogStore;
67use store_api::path_utils::WAL_DIR;
68use store_api::region_engine::{
69 RegionEngineRef, RegionRole, SetRegionRoleStateResponse, SettableRegionRoleState,
70};
71use tokio::fs;
72use tokio::sync::Notify;
73
74use crate::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
75use crate::error::{
76 self, BuildDatanodeSnafu, BuildMetricEngineSnafu, BuildMitoEngineSnafu, CreateDirSnafu,
77 DataFusionSnafu, DuplicateRegionEngineConfigSnafu, GetMetadataSnafu,
78 InvalidObjectStoreWalConfigSnafu, MissingCacheSnafu, MissingNodeIdSnafu,
79 ObjectStoreWalNotStandaloneSnafu, OpenLogStoreSnafu, Result, ShutdownInstanceSnafu,
80 ShutdownServerSnafu, StartServerSnafu,
81};
82use crate::event_listener::{
83 NoopRegionServerEventListener, RegionServerEventListenerRef, RegionServerEventReceiver,
84 new_region_server_event_channel,
85};
86use crate::greptimedb_telemetry::get_greptimedb_telemetry_task;
87use crate::heartbeat::HeartbeatTask;
88use crate::partition_expr_fetcher::MetaPartitionExprFetcher;
89use crate::region_query::DatanodeRegionQueryHandler;
90use crate::region_server::{DummyTableProviderFactory, RegionServer};
91use crate::store::{self, new_object_store_without_cache};
92use crate::utils::{RegionOpenRequests, build_region_open_requests};
93
94pub struct Datanode {
96 services: ServerHandlers,
97 heartbeat_task: Option<HeartbeatTask>,
98 region_event_receiver: Option<RegionServerEventReceiver>,
99 region_server: RegionServer,
100 greptimedb_telemetry_task: Arc<GreptimeDBTelemetryTask>,
101 leases_notifier: Option<Arc<Notify>>,
102 plugins: Plugins,
103 object_store_log_store: Option<Arc<ObjectStoreLogStore>>,
104}
105
106impl Datanode {
107 pub async fn start(&mut self) -> Result<()> {
108 info!("Starting datanode instance...");
109
110 self.start_heartbeat().await?;
111 self.wait_coordinated().await;
112
113 self.start_telemetry();
114
115 self.services.start_all().await.context(StartServerSnafu)
116 }
117
118 pub fn server_handlers(&self) -> &ServerHandlers {
119 &self.services
120 }
121
122 pub fn start_telemetry(&self) {
123 if let Err(e) = self.greptimedb_telemetry_task.start() {
124 warn!(e; "Failed to start telemetry task!");
125 }
126 }
127
128 pub async fn start_heartbeat(&mut self) -> Result<()> {
129 if let Some(task) = &self.heartbeat_task {
130 let receiver = self.region_event_receiver.take().unwrap();
132
133 task.start(receiver, self.leases_notifier.clone()).await?;
134 }
135 Ok(())
136 }
137
138 pub async fn wait_coordinated(&mut self) {
140 if let Some(notifier) = self.leases_notifier.take() {
141 notifier.notified().await;
142 }
143 }
144
145 pub fn setup_services(&mut self, services: ServerHandlers) {
146 self.services = services;
147 }
148
149 pub async fn shutdown(&mut self) -> Result<()> {
152 let mut first_error = None;
153 record_shutdown_error(
154 &mut first_error,
155 self.services
156 .shutdown_all()
157 .await
158 .context(ShutdownServerSnafu),
159 );
160
161 let _ = self.greptimedb_telemetry_task.stop().await;
162 if let Some(heartbeat_task) = &self.heartbeat_task {
163 record_shutdown_error(
164 &mut first_error,
165 heartbeat_task
166 .close()
167 .map_err(BoxedError::new)
168 .context(ShutdownInstanceSnafu),
169 );
170 }
171 record_shutdown_error(&mut first_error, self.region_server.stop().await);
172 if let Some(log_store) = &self.object_store_log_store {
173 record_shutdown_error(
174 &mut first_error,
175 log_store
176 .stop()
177 .await
178 .map_err(BoxedError::new)
179 .context(ShutdownInstanceSnafu),
180 );
181 }
182 first_error.map_or(Ok(()), Err)
183 }
184
185 pub fn region_server(&self) -> RegionServer {
186 self.region_server.clone()
187 }
188
189 #[cfg(any(test, feature = "testing"))]
192 pub fn object_store_log_store(&self) -> Option<Arc<ObjectStoreLogStore>> {
193 self.object_store_log_store.clone()
194 }
195
196 pub fn plugins(&self) -> Plugins {
197 self.plugins.clone()
198 }
199}
200
201pub struct DatanodeBuilder {
202 opts: DatanodeOptions,
203 table_provider_factory: Option<TableProviderFactoryRef>,
204 plugins: Plugins,
205 meta_client: Option<MetaClientRef>,
206 kv_backend: KvBackendRef,
207 cache_registry: Option<Arc<LayeredCacheRegistry>>,
208 topic_stats_reporter: Option<Box<dyn TopicStatsReporter>>,
209 open_regions_writable_override: Option<bool>,
210 local_file_access: LocalFileAccess,
211 #[cfg(feature = "enterprise")]
212 extension_range_provider_factory: Option<mito2::extension::BoxedExtensionRangeProviderFactory>,
213 object_store_log_store: Option<Arc<ObjectStoreLogStore>>,
215}
216
217impl DatanodeBuilder {
218 pub fn new(opts: DatanodeOptions, plugins: Plugins, kv_backend: KvBackendRef) -> Self {
219 Self {
220 opts,
221 table_provider_factory: None,
222 plugins,
223 meta_client: None,
224 kv_backend,
225 cache_registry: None,
226 open_regions_writable_override: None,
227 local_file_access: LocalFileAccess::Disabled,
228 #[cfg(feature = "enterprise")]
229 extension_range_provider_factory: None,
230 topic_stats_reporter: None,
231 object_store_log_store: None,
232 }
233 }
234
235 pub fn options(&self) -> &DatanodeOptions {
236 &self.opts
237 }
238
239 pub fn with_meta_client(&mut self, client: MetaClientRef) -> &mut Self {
240 self.meta_client = Some(client);
241 self
242 }
243
244 pub fn with_cache_registry(&mut self, registry: Arc<LayeredCacheRegistry>) -> &mut Self {
245 self.cache_registry = Some(registry);
246 self
247 }
248
249 pub fn with_local_file_access(&mut self, local_file_access: LocalFileAccess) -> &mut Self {
250 self.local_file_access = local_file_access;
251 self
252 }
253
254 pub fn kv_backend(&self) -> &KvBackendRef {
255 &self.kv_backend
256 }
257
258 pub fn meta_client(&self) -> Option<&MetaClientRef> {
259 self.meta_client.as_ref()
260 }
261
262 pub fn cache_registry(&self) -> Option<&Arc<LayeredCacheRegistry>> {
263 self.cache_registry.as_ref()
264 }
265
266 pub fn set_plugins(&mut self, plugins: Plugins) {
267 self.plugins = plugins;
268 }
269
270 pub fn with_table_provider_factory(&mut self, factory: TableProviderFactoryRef) -> &mut Self {
271 self.table_provider_factory = Some(factory);
272 self
273 }
274
275 pub fn with_open_regions_writable_override(&mut self, writable: bool) -> &mut Self {
285 self.open_regions_writable_override = Some(writable);
286 self
287 }
288
289 #[cfg(feature = "enterprise")]
290 pub fn with_extension_range_provider(
291 &mut self,
292 extension_range_provider_factory: mito2::extension::BoxedExtensionRangeProviderFactory,
293 ) -> &mut Self {
294 self.extension_range_provider_factory = Some(extension_range_provider_factory);
295 self
296 }
297
298 pub async fn build(mut self) -> Result<Datanode> {
299 self.try_build().await
300 }
301
302 async fn try_build(&mut self) -> Result<Datanode> {
304 let result = self.build_inner().await;
305 if result.is_err()
306 && let Some(log_store) = &self.object_store_log_store
307 && let Err(err) = log_store.stop().await
308 {
309 warn!(err; "Failed to stop the object store log store after a failed build");
310 }
311 result
312 }
313
314 async fn build_inner(&mut self) -> Result<Datanode> {
315 validate_region_engine_config(&self.opts.region_engine)?;
316 let node_id = self.opts.node_id.context(MissingNodeIdSnafu)?;
317 set_default_prefix(self.opts.default_column_prefix.as_deref())
318 .map_err(BoxedError::new)
319 .context(BuildDatanodeSnafu)?;
320
321 let meta_client = self.meta_client.take();
322
323 let controlled_by_metasrv = meta_client.is_some();
327 if let DatanodeWalConfig::ObjectStore(config) = &self.opts.wal {
328 ensure!(!controlled_by_metasrv, ObjectStoreWalNotStandaloneSnafu);
329 validate_object_store_wal_config(config)?;
330 }
331
332 let (region_event_listener, region_event_receiver) = if controlled_by_metasrv {
334 let (tx, rx) = new_region_server_event_channel();
335 (Box::new(tx) as _, Some(rx))
336 } else {
337 (Box::new(NoopRegionServerEventListener) as _, None)
338 };
339
340 let cache_registry = self.cache_registry.take().context(MissingCacheSnafu)?;
341 let schema_cache: SchemaCacheRef = cache_registry.get().context(MissingCacheSnafu)?;
342 let table_id_schema_cache: TableSchemaCacheRef =
343 cache_registry.get().context(MissingCacheSnafu)?;
344
345 let schema_metadata_manager = Arc::new(SchemaMetadataManager::new(
346 table_id_schema_cache,
347 schema_cache,
348 ));
349
350 let gc_enabled = self.opts.region_engine.iter().any(|engine| {
351 if let RegionEngineConfig::Mito(config) = engine {
352 config.gc.enable
353 } else {
354 false
355 }
356 });
357
358 let file_ref_manager = Arc::new(FileReferenceManager::with_gc_enabled(
359 Some(node_id),
360 gc_enabled,
361 ));
362 let region_server = self
363 .new_region_server(
364 schema_metadata_manager,
365 region_event_listener,
366 file_ref_manager,
367 cache_registry.clone(),
368 )
369 .await?;
370
371 let runtime_switch_manager = RuntimeSwitchManager::new(self.kv_backend.clone());
373 let is_recovery_mode = runtime_switch_manager
374 .recovery_mode()
375 .await
376 .context(GetMetadataSnafu)?;
377
378 let region_open_requests =
379 build_region_open_requests(node_id, self.kv_backend.clone()).await?;
380 let open_with_writable = self
381 .open_regions_writable_override
382 .unwrap_or(!controlled_by_metasrv);
383 let open_all_regions = open_all_regions(
384 region_server.clone(),
385 region_open_requests,
386 open_with_writable,
387 self.opts.init_regions_parallelism,
388 is_recovery_mode,
390 );
391
392 if self.opts.init_regions_in_background {
393 common_runtime::spawn_global(async move {
395 if let Err(err) = open_all_regions.await {
396 error!(err; "Failed to open regions during the startup.");
397 }
398 });
399 } else {
400 open_all_regions.await?;
401 }
402
403 let heartbeat_task = if let Some(meta_client) = meta_client {
404 let task = self
405 .create_heartbeat_task(®ion_server, meta_client, cache_registry)
406 .await?;
407 Some(task)
408 } else {
409 None
410 };
411
412 let is_standalone = heartbeat_task.is_none();
413 let greptimedb_telemetry_task = get_greptimedb_telemetry_task(
414 Some(self.opts.storage.data_home.clone()),
415 is_standalone && self.opts.enable_telemetry,
416 )
417 .await;
418
419 let leases_notifier = if self.opts.require_lease_before_startup && !is_standalone {
420 Some(Arc::new(Notify::new()))
421 } else {
422 None
423 };
424
425 Ok(Datanode {
426 services: ServerHandlers::default(),
427 heartbeat_task,
428 region_server,
429 greptimedb_telemetry_task,
430 region_event_receiver,
431 leases_notifier,
432 plugins: self.plugins.clone(),
433 object_store_log_store: self.object_store_log_store.take(),
434 })
435 }
436
437 async fn create_heartbeat_task(
438 &self,
439 region_server: &RegionServer,
440 meta_client: MetaClientRef,
441 cache_invalidator: CacheInvalidatorRef,
442 ) -> Result<HeartbeatTask> {
443 let stat = {
444 let mut stat = ResourceStatImpl::default();
445 stat.start_collect_cpu_usage();
446 Arc::new(stat)
447 };
448
449 HeartbeatTask::try_new(
450 &self.opts,
451 region_server.clone(),
452 meta_client,
453 self.kv_backend.clone(),
454 cache_invalidator,
455 self.plugins.clone(),
456 stat,
457 )
458 .await
459 }
460
461 pub async fn build_object_store_manager(cfg: &StorageConfig) -> Result<ObjectStoreManagerRef> {
463 let object_store = store::new_object_store(cfg.store.clone(), &cfg.data_home).await?;
464 let default_name = cfg.store.config_name();
465 let mut object_store_manager = ObjectStoreManager::new(default_name, object_store);
466 for store in &cfg.providers {
467 object_store_manager.add(
468 store.config_name(),
469 store::new_object_store(store.clone(), &cfg.data_home).await?,
470 );
471 }
472 Ok(Arc::new(object_store_manager))
473 }
474
475 #[cfg(test)]
476 async fn initialize_region_server(
478 &self,
479 region_server: &RegionServer,
480 open_with_writable: bool,
481 ) -> Result<()> {
482 let node_id = self.opts.node_id.context(MissingNodeIdSnafu)?;
483
484 let runtime_switch_manager = RuntimeSwitchManager::new(self.kv_backend.clone());
486 let is_recovery_mode = runtime_switch_manager
487 .recovery_mode()
488 .await
489 .context(GetMetadataSnafu)?;
490 let region_open_requests =
491 build_region_open_requests(node_id, self.kv_backend.clone()).await?;
492
493 open_all_regions(
494 region_server.clone(),
495 region_open_requests,
496 open_with_writable,
497 self.opts.init_regions_parallelism,
498 is_recovery_mode,
499 )
500 .await
501 }
502
503 async fn new_region_server(
504 &mut self,
505 schema_metadata_manager: SchemaMetadataManagerRef,
506 event_listener: RegionServerEventListenerRef,
507 file_ref_manager: FileReferenceManagerRef,
508 cache_registry: Arc<LayeredCacheRegistry>,
509 ) -> Result<RegionServer> {
510 let opts: &DatanodeOptions = &self.opts;
511
512 let catalog_manager = KvBackendCatalogManagerBuilder::new(
514 Arc::new(NoopInformationExtension),
515 self.kv_backend.clone(),
516 cache_registry.clone(),
517 )
518 .build();
519 let partition_manager = catalog_manager.partition_manager();
520
521 let mut channel_config = ChannelConfig {
523 timeout: None,
524 ..Default::default()
525 };
526 if opts.grpc.flight_compression.transport_compression() {
527 channel_config.accept_compression = true;
528 channel_config.send_compression = true;
529 }
530 let node_manager: NodeManagerRef = Arc::new(NodeClients::new(channel_config));
531 let region_query_handler =
532 if let Some(factory) = self.plugins.get::<RegionQueryHandlerFactoryRef>() {
533 factory.build(partition_manager.clone(), node_manager)
534 } else {
535 DatanodeRegionQueryHandler::arc(partition_manager.clone(), node_manager)
536 };
537
538 let query_engine_factory = QueryEngineFactory::try_new_with_plugins(
539 catalog_manager,
541 Some(partition_manager),
542 Some(region_query_handler),
543 None,
544 None,
545 None,
546 false,
547 self.plugins.clone(),
548 opts.query.clone(),
549 )
550 .context(DataFusionSnafu)?;
551 let query_engine = query_engine_factory.query_engine();
552
553 let table_provider_factory = self
554 .table_provider_factory
555 .clone()
556 .unwrap_or_else(|| Arc::new(DummyTableProviderFactory));
557 let mut region_server = RegionServer::with_table_provider(
558 query_engine,
559 common_runtime::global_runtime(),
560 event_listener,
561 table_provider_factory,
562 opts.max_concurrent_queries,
563 opts.concurrent_query_limiter_timeout,
564 opts.grpc.flight_compression,
565 );
566 region_server.install_remote_dyn_filter_receiver_injector(&self.plugins);
567
568 let object_store_manager = Self::build_object_store_manager(&opts.storage).await?;
569 let engines = self
570 .build_store_engines(
571 object_store_manager,
572 schema_metadata_manager,
573 file_ref_manager,
574 self.plugins.clone(),
575 )
576 .await?;
577 for engine in engines {
578 region_server.register_engine(engine);
579 }
580 if let Some(topic_stats_reporter) = self.topic_stats_reporter.take() {
581 region_server.set_topic_stats_reporter(topic_stats_reporter);
582 }
583
584 Ok(region_server)
585 }
586
587 async fn build_store_engines(
591 &mut self,
592 object_store_manager: ObjectStoreManagerRef,
593 schema_metadata_manager: SchemaMetadataManagerRef,
594 file_ref_manager: FileReferenceManagerRef,
595 plugins: Plugins,
596 ) -> Result<Vec<RegionEngineRef>> {
597 let mut metric_engine_config = metric_engine::config::EngineConfig::default();
598 let mut mito_engine_config = MitoConfig::default();
599 let mut file_engine_config = file_engine::config::EngineConfig::default();
600
601 for engine in &self.opts.region_engine {
602 match engine {
603 RegionEngineConfig::Mito(config) => {
604 mito_engine_config = config.clone();
605 }
606 RegionEngineConfig::File(config) => {
607 file_engine_config = config.clone();
608 }
609 RegionEngineConfig::Metric(metric_config) => {
610 metric_engine_config = metric_config.clone();
611 }
612 }
613 }
614
615 let fetcher = Arc::new(MetaPartitionExprFetcher::new(self.kv_backend.clone()));
617 let mito_engine = self
618 .build_mito_engine(
619 object_store_manager.clone(),
620 mito_engine_config,
621 schema_metadata_manager.clone(),
622 file_ref_manager.clone(),
623 fetcher.clone(),
624 plugins.clone(),
625 )
626 .await?;
627
628 let metric_engine = MetricEngine::try_new(mito_engine.clone(), metric_engine_config)
629 .context(BuildMetricEngineSnafu)?;
630
631 let file_engine = FileRegionEngine::new(
632 file_engine_config,
633 object_store_manager.default_object_store().clone(), self.local_file_access.clone(),
635 );
636
637 Ok(vec![
638 Arc::new(mito_engine) as _,
639 Arc::new(metric_engine) as _,
640 Arc::new(file_engine) as _,
641 ])
642 }
643
644 async fn build_mito_engine(
646 &mut self,
647 object_store_manager: ObjectStoreManagerRef,
648 mut config: MitoConfig,
649 schema_metadata_manager: SchemaMetadataManagerRef,
650 file_ref_manager: FileReferenceManagerRef,
651 partition_expr_fetcher: PartitionExprFetcherRef,
652 plugins: Plugins,
653 ) -> Result<MitoEngine> {
654 let opts = &self.opts;
655 if opts.storage.is_object_storage() {
656 config.enable_write_cache = true;
658 info!("Configured 'enable_write_cache=true' for mito engine.");
659 }
660
661 let mito_engine = match &opts.wal {
662 DatanodeWalConfig::RaftEngine(raft_engine_config) => {
663 let log_store =
664 Self::build_raft_engine_log_store(&opts.storage.data_home, raft_engine_config)
665 .await?;
666
667 let builder = MitoEngineBuilder::new(
668 &opts.storage.data_home,
669 config,
670 log_store,
671 object_store_manager,
672 schema_metadata_manager,
673 file_ref_manager,
674 partition_expr_fetcher.clone(),
675 plugins,
676 );
677
678 #[cfg(feature = "enterprise")]
679 let builder = builder.with_extension_range_provider_factory(
680 self.extension_range_provider_factory.take(),
681 );
682
683 builder.try_build().await.context(BuildMitoEngineSnafu)?
684 }
685 DatanodeWalConfig::Kafka(kafka_config) => {
686 if kafka_config.create_index && opts.node_id.is_none() {
687 warn!("The WAL index creation only available in distributed mode.")
688 }
689 let global_index_collector = if kafka_config.create_index
690 && let Some(node_id) = opts.node_id
691 {
692 let operator = new_object_store_without_cache(
693 &opts.storage.store,
694 &opts.storage.data_home,
695 )
696 .await?;
697 let path = default_index_file(node_id);
698 Some(Self::build_global_index_collector(
699 kafka_config.dump_index_interval,
700 operator,
701 path,
702 ))
703 } else {
704 None
705 };
706
707 let log_store =
708 Self::build_kafka_log_store(kafka_config, global_index_collector).await?;
709 self.topic_stats_reporter = Some(log_store.topic_stats_reporter());
710 let builder = MitoEngineBuilder::new(
711 &opts.storage.data_home,
712 config,
713 log_store,
714 object_store_manager,
715 schema_metadata_manager,
716 file_ref_manager,
717 partition_expr_fetcher,
718 plugins,
719 );
720
721 #[cfg(feature = "enterprise")]
722 let builder = builder.with_extension_range_provider_factory(
723 self.extension_range_provider_factory.take(),
724 );
725
726 builder.try_build().await.context(BuildMitoEngineSnafu)?
727 }
728 DatanodeWalConfig::Noop => {
729 let log_store = Arc::new(NoopLogStore);
730
731 let builder = MitoEngineBuilder::new(
732 &opts.storage.data_home,
733 config,
734 log_store,
735 object_store_manager,
736 schema_metadata_manager,
737 file_ref_manager,
738 partition_expr_fetcher.clone(),
739 plugins,
740 );
741
742 #[cfg(feature = "enterprise")]
743 let builder = builder.with_extension_range_provider_factory(
744 self.extension_range_provider_factory.take(),
745 );
746
747 builder.try_build().await.context(BuildMitoEngineSnafu)?
748 }
749 DatanodeWalConfig::ObjectStore(object_store_config) => {
750 let object_store = wal_object_store(
751 &object_store_manager,
752 &opts.storage,
753 &object_store_config.storage_provider,
754 )?;
755 let node_id = opts.node_id.context(MissingNodeIdSnafu)?;
756 let log_store =
757 Self::build_object_store_log_store(object_store, object_store_config, node_id)
758 .await?;
759 self.object_store_log_store = Some(log_store.clone());
760
761 let builder = MitoEngineBuilder::new(
762 &opts.storage.data_home,
763 config,
764 log_store,
765 object_store_manager,
766 schema_metadata_manager,
767 file_ref_manager,
768 partition_expr_fetcher,
769 plugins,
770 );
771
772 #[cfg(feature = "enterprise")]
773 let builder = builder.with_extension_range_provider_factory(
774 self.extension_range_provider_factory.take(),
775 );
776
777 builder.try_build().await.context(BuildMitoEngineSnafu)?
778 }
779 };
780 Ok(mito_engine)
781 }
782
783 async fn build_object_store_log_store(
786 object_store: ObjectStore,
787 config: &ObjectStoreWalConfig,
788 node_id: u64,
789 ) -> Result<Arc<ObjectStoreLogStore>> {
790 info!(
791 "Creating object store logstore with config: {:?}, node id: {}",
792 config, node_id
793 );
794 ObjectStoreLogStore::try_new(object_store, config, node_id, STANDALONE_GENERATION)
795 .await
796 .map_err(Box::new)
797 .context(OpenLogStoreSnafu)
798 }
799
800 async fn build_raft_engine_log_store(
802 data_home: &str,
803 config: &RaftEngineConfig,
804 ) -> Result<Arc<RaftEngineLogStore>> {
805 let data_home = normalize_dir(data_home);
806 let wal_dir = match &config.dir {
807 Some(dir) => dir.clone(),
808 None => format!("{}{WAL_DIR}", data_home),
809 };
810
811 fs::create_dir_all(Path::new(&wal_dir))
813 .await
814 .context(CreateDirSnafu { dir: &wal_dir })?;
815 info!(
816 "Creating raft-engine logstore with config: {:?} and storage path: {}",
817 config, &wal_dir
818 );
819 let logstore = RaftEngineLogStore::try_new(wal_dir, config)
820 .await
821 .map_err(Box::new)
822 .context(OpenLogStoreSnafu)?;
823
824 Ok(Arc::new(logstore))
825 }
826
827 async fn build_kafka_log_store(
829 config: &DatanodeKafkaConfig,
830 global_index_collector: Option<GlobalIndexCollector>,
831 ) -> Result<Arc<KafkaLogStore>> {
832 KafkaLogStore::try_new(config, global_index_collector)
833 .await
834 .map_err(Box::new)
835 .context(OpenLogStoreSnafu)
836 .map(Arc::new)
837 }
838
839 fn build_global_index_collector(
841 dump_index_interval: Duration,
842 operator: object_store::ObjectStore,
843 path: String,
844 ) -> GlobalIndexCollector {
845 GlobalIndexCollector::new(dump_index_interval, operator, path)
846 }
847}
848
849pub(crate) fn record_shutdown_error(first_error: &mut Option<error::Error>, result: Result<()>) {
851 if let Err(err) = result {
852 if first_error.is_none() {
853 *first_error = Some(err);
854 } else {
855 warn!(err; "Ignored a later shutdown error");
856 }
857 }
858}
859
860fn wal_object_store(
863 object_store_manager: &ObjectStoreManager,
864 storage: &StorageConfig,
865 storage_provider: &str,
866) -> Result<ObjectStore> {
867 if storage_provider.is_empty() {
868 return Ok(object_store_manager.default_object_store().clone());
869 }
870 object_store_manager
871 .find(storage_provider)
872 .cloned()
873 .with_context(|| {
874 let configured = std::iter::once(&storage.store)
875 .chain(storage.providers.iter())
876 .map(|store| store.config_name())
877 .collect::<Vec<_>>();
878 InvalidObjectStoreWalConfigSnafu {
879 field: "storage_provider",
880 value: storage_provider,
881 reason: format!("must name a configured storage provider, one of {configured:?}"),
882 }
883 })
884}
885
886fn validate_region_engine_config(configs: &[RegionEngineConfig]) -> Result<()> {
888 let mut engine_indices = HashMap::new();
889 for (index, config) in configs.iter().enumerate() {
890 let engine = match config {
891 RegionEngineConfig::Mito(_) => MITO_ENGINE,
892 RegionEngineConfig::File(_) => FILE_ENGINE,
893 RegionEngineConfig::Metric(_) => METRIC_ENGINE,
894 };
895 if let Some(first_index) = engine_indices.insert(engine, index) {
896 return DuplicateRegionEngineConfigSnafu {
897 engine,
898 first_index,
899 duplicate_index: index,
900 }
901 .fail();
902 }
903 }
904 Ok(())
905}
906
907fn validate_object_store_wal_config(config: &ObjectStoreWalConfig) -> Result<()> {
909 let prefix = config.prefix.trim();
910 ensure!(
911 !prefix.is_empty(),
912 InvalidObjectStoreWalConfigSnafu {
913 field: "prefix",
914 value: &config.prefix,
915 reason: "must not be empty",
916 }
917 );
918 ensure!(
919 !prefix.starts_with('/'),
920 InvalidObjectStoreWalConfigSnafu {
921 field: "prefix",
922 value: &config.prefix,
923 reason: "must be a relative path",
924 }
925 );
926 ensure!(
927 !prefix
928 .split('/')
929 .any(|component| component.is_empty() || component == ".."),
930 InvalidObjectStoreWalConfigSnafu {
931 field: "prefix",
932 value: &config.prefix,
933 reason: "must not contain empty or `..` components",
934 }
935 );
936 ensure!(
937 config.flush_interval >= Duration::from_millis(10),
938 InvalidObjectStoreWalConfigSnafu {
939 field: "flush_interval",
940 value: format!("{:?}", config.flush_interval),
941 reason: "must be at least 10ms",
942 }
943 );
944 ensure!(
945 config.max_batch_bytes.as_bytes() > 0,
946 InvalidObjectStoreWalConfigSnafu {
947 field: "max_batch_bytes",
948 value: config.max_batch_bytes.to_string(),
949 reason: "must be greater than 0",
950 }
951 );
952 ensure!(
953 config.max_unpersisted_bytes.as_bytes() > 0,
954 InvalidObjectStoreWalConfigSnafu {
955 field: "max_unpersisted_bytes",
956 value: config.max_unpersisted_bytes.to_string(),
957 reason: "must be greater than 0",
958 }
959 );
960 ensure!(
961 config.max_unpersisted_age > Duration::ZERO,
962 InvalidObjectStoreWalConfigSnafu {
963 field: "max_unpersisted_age",
964 value: format!("{:?}", config.max_unpersisted_age),
965 reason: "must be greater than 0",
966 }
967 );
968 Ok(())
969}
970
971async fn open_all_regions(
973 region_server: RegionServer,
974 region_open_requests: RegionOpenRequests,
975 open_with_writable: bool,
976 init_regions_parallelism: usize,
977 ignore_nonexistent_region: bool,
978) -> Result<()> {
979 let RegionOpenRequests {
980 leader_regions,
981 #[cfg(feature = "enterprise")]
982 follower_regions,
983 } = region_open_requests;
984
985 let leader_region_num = leader_regions.len();
986 info!("going to open {} region(s)", leader_region_num);
987 let now = Instant::now();
988 let open_regions = region_server
989 .handle_batch_open_requests(
990 init_regions_parallelism,
991 leader_regions,
992 ignore_nonexistent_region,
993 )
994 .await?;
995 info!(
996 "Opened {} regions in {:?}",
997 open_regions.len(),
998 now.elapsed()
999 );
1000 if !ignore_nonexistent_region {
1001 ensure!(
1002 open_regions.len() == leader_region_num,
1003 error::UnexpectedSnafu {
1004 violated: format!(
1005 "Expected to open {} of regions, only {} of regions has opened",
1006 leader_region_num,
1007 open_regions.len()
1008 )
1009 }
1010 );
1011 } else if open_regions.len() != leader_region_num {
1012 warn!(
1013 "ignore nonexistent region, expected to open {} of regions, only {} of regions has opened",
1014 leader_region_num,
1015 open_regions.len()
1016 );
1017 }
1018
1019 for region_id in open_regions {
1020 if open_with_writable {
1021 let res = region_server.set_region_role(region_id, RegionRole::Leader);
1022 match res {
1023 Ok(_) => {
1024 if let SetRegionRoleStateResponse::InvalidTransition(err) = region_server
1026 .set_region_role_state_gracefully(
1027 region_id,
1028 SettableRegionRoleState::Leader,
1029 )
1030 .await?
1031 {
1032 error!(err; "failed to convert region {region_id} to leader");
1033 }
1034 }
1035 Err(e) => {
1036 error!(e; "failed to convert region {region_id} to leader");
1037 }
1038 }
1039 }
1040 }
1041
1042 #[cfg(feature = "enterprise")]
1043 if !follower_regions.is_empty() {
1044 use tokio::time::Instant;
1045
1046 let follower_region_num = follower_regions.len();
1047 info!("going to open {} follower region(s)", follower_region_num);
1048
1049 let now = Instant::now();
1050 let open_regions = region_server
1051 .handle_batch_open_requests(
1052 init_regions_parallelism,
1053 follower_regions,
1054 ignore_nonexistent_region,
1055 )
1056 .await?;
1057 info!(
1058 "Opened {} follower regions in {:?}",
1059 open_regions.len(),
1060 now.elapsed()
1061 );
1062
1063 if !ignore_nonexistent_region {
1064 ensure!(
1065 open_regions.len() == follower_region_num,
1066 error::UnexpectedSnafu {
1067 violated: format!(
1068 "Expected to open {} of follower regions, only {} of regions has opened",
1069 follower_region_num,
1070 open_regions.len()
1071 )
1072 }
1073 );
1074 } else if open_regions.len() != follower_region_num {
1075 warn!(
1076 "ignore nonexistent region, expected to open {} of follower regions, only {} of regions has opened",
1077 follower_region_num,
1078 open_regions.len()
1079 );
1080 }
1081 }
1082
1083 info!("all regions are opened");
1084
1085 Ok(())
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090 use std::assert_matches;
1091 use std::collections::{BTreeMap, HashMap};
1092 use std::io::Write;
1093 use std::path::Path;
1094 use std::sync::Arc;
1095 use std::sync::atomic::{AtomicUsize, Ordering};
1096 use std::time::Duration;
1097
1098 use cache::build_datanode_layered_cache_registry;
1099 use common_base::Plugins;
1100 use common_base::readable_size::ReadableSize;
1101 use common_config::Configurable;
1102 use common_error::ext::ErrorExt;
1103 use common_error::status_code::StatusCode;
1104 use common_meta::key::RegionRoleSet;
1105 use common_meta::key::datanode_table::DatanodeTableManager;
1106 use common_meta::kv_backend::KvBackendRef;
1107 use common_meta::kv_backend::memory::MemoryKvBackend;
1108 use common_meta::node_manager::NodeManagerRef;
1109 use common_test_util::temp_dir::{create_named_temp_file, create_temp_dir};
1110 use common_wal::config::DatanodeWalConfig;
1111 use common_wal::config::object_store::{ObjectStoreWalConfig, STANDALONE_GENERATION};
1112 use log_store::ObjectStoreLogStore;
1113 use log_store::error::Error as LogStoreError;
1114 use meta_client::client::MetaClientBuilder;
1115 use mito2::engine::MITO_ENGINE_NAME;
1116 use object_store::ObjectStore;
1117 use object_store::manager::ObjectStoreManager;
1118 use partition::manager::PartitionRuleManagerRef;
1119 use query::region_query::{RegionQueryHandlerFactory, RegionQueryHandlerFactoryRef};
1120 use store_api::logstore::LogStore;
1121 use store_api::region_request::RegionRequest;
1122 use store_api::storage::RegionId;
1123
1124 use crate::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
1125 use crate::datanode::{
1126 DatanodeBuilder, validate_object_store_wal_config, validate_region_engine_config,
1127 wal_object_store,
1128 };
1129 use crate::error::{self, Error};
1130 use crate::region_query::DatanodeRegionQueryHandler;
1131 use crate::tests::{MockRegionEngine, mock_region_server};
1132
1133 async fn setup_table_datanode(kv: &KvBackendRef) {
1134 let mgr = DatanodeTableManager::new(kv.clone());
1135 let txn = mgr
1136 .build_create_txn(
1137 1028,
1138 MITO_ENGINE_NAME,
1139 "foo/bar/weny",
1140 HashMap::from([("foo".to_string(), "bar".to_string())]),
1141 HashMap::default(),
1142 BTreeMap::from([(0, RegionRoleSet::new(vec![0, 1, 2], vec![]))]),
1143 )
1144 .unwrap();
1145
1146 let r = kv.txn(txn).await.unwrap();
1147 assert!(r.succeeded);
1148 }
1149
1150 #[tokio::test]
1151 async fn test_initialize_region_server() {
1152 common_telemetry::init_default_ut_logging();
1153 let mut mock_region_server = mock_region_server();
1154 let (mock_region, mut mock_region_handler) = MockRegionEngine::new(MITO_ENGINE_NAME);
1155
1156 mock_region_server.register_engine(mock_region.clone());
1157
1158 let kv_backend = Arc::new(MemoryKvBackend::new());
1159 let layered_cache_registry =
1160 Arc::new(build_datanode_layered_cache_registry(kv_backend.clone()));
1161
1162 let mut builder = DatanodeBuilder::new(
1163 DatanodeOptions {
1164 node_id: Some(0),
1165 ..Default::default()
1166 },
1167 Plugins::default(),
1168 kv_backend.clone(),
1169 );
1170 builder.with_cache_registry(layered_cache_registry);
1171 setup_table_datanode(&(kv_backend as _)).await;
1172
1173 builder
1174 .initialize_region_server(&mock_region_server, false)
1175 .await
1176 .unwrap();
1177
1178 for i in 0..3 {
1179 let (region_id, req) = mock_region_handler.recv().await.unwrap();
1180 assert_eq!(region_id, RegionId::new(1028, i));
1181 if let RegionRequest::Open(req) = req {
1182 assert_eq!(
1183 req.options,
1184 HashMap::from([("foo".to_string(), "bar".to_string())])
1185 )
1186 } else {
1187 unreachable!()
1188 }
1189 }
1190
1191 assert_matches!(
1192 mock_region_handler.try_recv(),
1193 Err(tokio::sync::mpsc::error::TryRecvError::Empty)
1194 );
1195 }
1196
1197 fn datanode_builder(
1198 data_home: &str,
1199 wal: DatanodeWalConfig,
1200 kv_backend: KvBackendRef,
1201 ) -> DatanodeBuilder {
1202 let layered_cache_registry =
1203 Arc::new(build_datanode_layered_cache_registry(kv_backend.clone()));
1204 let mut opts = DatanodeOptions {
1205 node_id: Some(0),
1206 wal,
1207 enable_telemetry: false,
1208 ..Default::default()
1209 };
1210 opts.storage.data_home = data_home.to_string();
1211
1212 let mut builder = DatanodeBuilder::new(opts, Plugins::default(), kv_backend);
1213 builder.with_cache_registry(layered_cache_registry);
1214 builder
1215 }
1216
1217 fn object_store_wal_builder(data_home: &str, config: ObjectStoreWalConfig) -> DatanodeBuilder {
1218 datanode_builder(
1219 data_home,
1220 DatanodeWalConfig::ObjectStore(config),
1221 Arc::new(MemoryKvBackend::new()),
1222 )
1223 }
1224
1225 async fn build_err(builder: DatanodeBuilder) -> Error {
1226 match builder.build().await {
1227 Ok(_) => panic!("build must fail"),
1228 Err(err) => err,
1229 }
1230 }
1231
1232 fn is_empty_dir(dir: &Path) -> bool {
1233 std::fs::read_dir(dir).unwrap().next().is_none()
1234 }
1235
1236 async fn is_stopped(log_store: &ObjectStoreLogStore) -> bool {
1238 matches!(
1239 log_store.append_batch(vec![]).await,
1240 Err(LogStoreError::ObjectStoreWalStopped { .. })
1241 )
1242 }
1243
1244 #[test]
1245 fn test_validate_unique_region_engine_config() {
1246 let mut opts = DatanodeOptions::default();
1247 validate_region_engine_config(&opts.region_engine).unwrap();
1248
1249 opts.region_engine
1250 .push(RegionEngineConfig::Metric(Default::default()));
1251 validate_region_engine_config(&opts.region_engine).unwrap();
1252
1253 validate_region_engine_config(&[]).unwrap();
1255 }
1256
1257 #[tokio::test]
1258 async fn test_build_rejects_duplicate_region_engine_config() {
1259 for (engine, first_index) in [("mito", 0), ("file", 1), ("metric", 2)] {
1260 let mut file = create_named_temp_file();
1261 write!(
1262 file,
1263 r#"
1264 [[region_engine]]
1265 [region_engine.mito]
1266 global_write_buffer_size = "4GiB"
1267 min_compaction_interval = "600s"
1268
1269 [[region_engine]]
1270 [region_engine.file]
1271
1272 [[region_engine]]
1273 [region_engine.metric]
1274
1275 [[region_engine]]
1276 [region_engine.{engine}]
1277 "#
1278 )
1279 .unwrap();
1280 let opts = DatanodeOptions::load_layered_options(
1281 Some(file.path().to_str().unwrap()),
1282 "DATANODE_DUPLICATE_REGION_ENGINE_UT",
1283 )
1284 .unwrap();
1285 let data_home = create_temp_dir("duplicate-region-engine-config");
1286 let mut builder = datanode_builder(
1287 data_home.path().to_str().unwrap(),
1288 DatanodeWalConfig::default(),
1289 Arc::new(MemoryKvBackend::new()),
1290 );
1291 builder.opts.region_engine = opts.region_engine;
1292 let err = build_err(builder).await;
1293
1294 let Error::DuplicateRegionEngineConfig {
1295 engine: actual_engine,
1296 first_index: actual_first_index,
1297 duplicate_index,
1298 ..
1299 } = &err
1300 else {
1301 panic!("unexpected error for {engine}: {err:?}");
1302 };
1303 assert_eq!(*actual_engine, engine);
1304 assert_eq!(*actual_first_index, first_index);
1305 assert_eq!(*duplicate_index, 3);
1306 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
1307 assert!(is_empty_dir(data_home.path()));
1309 }
1310 }
1311
1312 #[tokio::test]
1313 async fn test_build_rejects_invalid_object_store_wal_config() {
1314 let cases = [
1315 (
1316 ObjectStoreWalConfig {
1317 prefix: String::new(),
1318 ..Default::default()
1319 },
1320 "prefix",
1321 ),
1322 (
1323 ObjectStoreWalConfig {
1324 prefix: "/wal".to_string(),
1325 ..Default::default()
1326 },
1327 "prefix",
1328 ),
1329 (
1330 ObjectStoreWalConfig {
1331 prefix: "wal/../other".to_string(),
1332 ..Default::default()
1333 },
1334 "prefix",
1335 ),
1336 (
1337 ObjectStoreWalConfig {
1338 prefix: "wal//objects".to_string(),
1339 ..Default::default()
1340 },
1341 "prefix",
1342 ),
1343 (
1344 ObjectStoreWalConfig {
1345 prefix: "wal/".to_string(),
1346 ..Default::default()
1347 },
1348 "prefix",
1349 ),
1350 (
1351 ObjectStoreWalConfig {
1352 flush_interval: Duration::from_millis(9),
1353 ..Default::default()
1354 },
1355 "flush_interval",
1356 ),
1357 (
1358 ObjectStoreWalConfig {
1359 max_batch_bytes: ReadableSize(0),
1360 ..Default::default()
1361 },
1362 "max_batch_bytes",
1363 ),
1364 (
1365 ObjectStoreWalConfig {
1366 max_unpersisted_bytes: ReadableSize(0),
1367 ..Default::default()
1368 },
1369 "max_unpersisted_bytes",
1370 ),
1371 (
1372 ObjectStoreWalConfig {
1373 max_unpersisted_age: Duration::ZERO,
1374 ..Default::default()
1375 },
1376 "max_unpersisted_age",
1377 ),
1378 ];
1379
1380 for (config, expected_field) in cases {
1381 let expected_value = match expected_field {
1382 "prefix" => config.prefix.clone(),
1383 "flush_interval" => format!("{:?}", config.flush_interval),
1384 "max_unpersisted_bytes" => config.max_unpersisted_bytes.to_string(),
1385 "max_unpersisted_age" => format!("{:?}", config.max_unpersisted_age),
1386 _ => config.max_batch_bytes.to_string(),
1387 };
1388 let data_home = create_temp_dir("object-store-wal-invalid-config");
1389 let builder = object_store_wal_builder(data_home.path().to_str().unwrap(), config);
1390 let err = build_err(builder).await;
1391
1392 let Error::InvalidObjectStoreWalConfig { field, value, .. } = &err else {
1393 panic!("unexpected error for {expected_field}: {err:?}");
1394 };
1395 assert_eq!(*field, expected_field);
1396 assert_eq!(*value, expected_value);
1397 assert_eq!(StatusCode::InvalidArguments, err.status_code());
1398 assert!(is_empty_dir(data_home.path()));
1400 }
1401 }
1402
1403 #[tokio::test]
1404 async fn test_build_rejects_object_store_wal_with_meta_client() {
1405 let data_home = create_temp_dir("object-store-wal-meta-client");
1406 let meta_client = Arc::new(MetaClientBuilder::datanode_default_options(0).build());
1407
1408 let mut builder = object_store_wal_builder(
1409 data_home.path().to_str().unwrap(),
1410 ObjectStoreWalConfig::default(),
1411 );
1412 builder.with_meta_client(meta_client);
1413 let err = build_err(builder).await;
1414
1415 assert_matches!(err, Error::ObjectStoreWalNotStandalone { .. });
1416 assert_eq!(StatusCode::InvalidArguments, err.status_code());
1417 assert!(is_empty_dir(data_home.path()));
1419 }
1420
1421 #[tokio::test]
1422 async fn test_build_rejects_unknown_wal_storage_provider() {
1423 let data_home = create_temp_dir("object-store-wal-unknown-provider");
1424 let builder = object_store_wal_builder(
1425 data_home.path().to_str().unwrap(),
1426 ObjectStoreWalConfig {
1427 storage_provider: "s3".to_string(),
1428 ..Default::default()
1429 },
1430 );
1431 let err = build_err(builder).await;
1432
1433 let Error::InvalidObjectStoreWalConfig {
1434 field,
1435 value,
1436 reason,
1437 ..
1438 } = &err
1439 else {
1440 panic!("unexpected error: {err:?}");
1441 };
1442 assert_eq!(*field, "storage_provider");
1443 assert_eq!(value, "s3");
1444 assert!(reason.contains(r#"["File"]"#), "{reason}");
1445 assert_eq!(StatusCode::InvalidArguments, err.status_code());
1446 assert!(!data_home.path().join("wal").exists());
1448 }
1449
1450 #[test]
1451 fn test_wal_object_store_selects_provider() {
1452 fn fs_object_store(dir: &Path) -> ObjectStore {
1453 let builder = object_store::services::Fs::default().root(dir.to_str().unwrap());
1454 ObjectStore::new(builder).unwrap()
1455 }
1456
1457 let default_dir = create_temp_dir("wal-object-store-default");
1458 let named_dir = create_temp_dir("wal-object-store-named");
1459 let mut manager = ObjectStoreManager::new("File", fs_object_store(default_dir.path()));
1460 manager.add("Wal-Store", fs_object_store(named_dir.path()));
1461 let storage = StorageConfig::default();
1462
1463 let root = |store: ObjectStore| store.info().root().clone();
1464 assert_eq!(
1465 root(wal_object_store(&manager, &storage, "").unwrap()),
1466 root(manager.default_object_store().clone())
1467 );
1468 assert_eq!(
1469 root(wal_object_store(&manager, &storage, "wal-store").unwrap()),
1470 root(manager.find("Wal-Store").unwrap().clone())
1471 );
1472 assert_matches!(
1473 wal_object_store(&manager, &storage, "missing").unwrap_err(),
1474 Error::InvalidObjectStoreWalConfig {
1475 field: "storage_provider",
1476 ..
1477 }
1478 );
1479 }
1480
1481 struct CountingRegionQueryHandlerFactory(Arc<AtomicUsize>);
1482
1483 impl RegionQueryHandlerFactory for CountingRegionQueryHandlerFactory {
1484 fn build(
1485 &self,
1486 partition_manager: PartitionRuleManagerRef,
1487 node_manager: NodeManagerRef,
1488 ) -> query::region_query::RegionQueryHandlerRef {
1489 self.0.fetch_add(1, Ordering::SeqCst);
1490 DatanodeRegionQueryHandler::arc(partition_manager, node_manager)
1491 }
1492 }
1493
1494 #[tokio::test]
1495 async fn test_build_uses_installed_region_query_handler_factory() {
1496 let data_home = create_temp_dir("region-query-handler-factory");
1497 let builds = Arc::new(AtomicUsize::new(0));
1498 let plugins = Plugins::new();
1499 plugins.insert(Arc::new(CountingRegionQueryHandlerFactory(builds.clone()))
1500 as RegionQueryHandlerFactoryRef);
1501 let mut builder = datanode_builder(
1502 data_home.path().to_str().unwrap(),
1503 DatanodeWalConfig::default(),
1504 Arc::new(MemoryKvBackend::new()),
1505 );
1506 builder.set_plugins(plugins);
1507
1508 let mut datanode = builder.build().await.unwrap();
1509 assert_eq!(builds.load(Ordering::SeqCst), 1);
1510 datanode.shutdown().await.unwrap();
1511 }
1512
1513 #[tokio::test]
1514 async fn test_build_and_shutdown_with_raft_engine_wal() {
1515 let data_home = create_temp_dir("raft-engine-wal");
1516 let builder = datanode_builder(
1517 data_home.path().to_str().unwrap(),
1518 DatanodeWalConfig::default(),
1519 Arc::new(MemoryKvBackend::new()),
1520 );
1521
1522 let mut datanode = builder.build().await.unwrap();
1523 assert!(datanode.object_store_log_store.is_none());
1524 assert!(data_home.path().join("wal").exists());
1525 datanode.shutdown().await.unwrap();
1526 }
1527
1528 #[tokio::test]
1529 async fn test_build_and_shutdown_with_object_store_wal() {
1530 common_telemetry::init_default_ut_logging();
1531 let data_home = create_temp_dir("object-store-wal");
1532 let builder = object_store_wal_builder(
1533 data_home.path().to_str().unwrap(),
1534 ObjectStoreWalConfig::default(),
1535 );
1536
1537 let mut datanode = builder.build().await.unwrap();
1538 let log_store = datanode.object_store_log_store.clone().unwrap();
1539 assert!(!is_stopped(&log_store).await);
1540 assert!(format!("{log_store:?}").contains(r#"prefix: "wal/datanodes/0/epochs/0""#));
1542
1543 datanode.shutdown().await.unwrap();
1544 assert!(is_stopped(&log_store).await);
1545 }
1546
1547 #[tokio::test]
1548 async fn test_build_with_named_wal_storage_provider() {
1549 let data_home = create_temp_dir("object-store-wal-named-provider");
1550 let builder = object_store_wal_builder(
1552 data_home.path().to_str().unwrap(),
1553 ObjectStoreWalConfig {
1554 storage_provider: "file".to_string(),
1555 ..Default::default()
1556 },
1557 );
1558
1559 let mut datanode = builder.build().await.unwrap();
1560 assert!(datanode.object_store_log_store.is_some());
1561 datanode.shutdown().await.unwrap();
1562 }
1563
1564 #[tokio::test]
1565 async fn test_shutdown_stops_every_engine_and_the_log_store_after_engine_failures() {
1566 common_telemetry::init_default_ut_logging();
1567 let data_home = create_temp_dir("object-store-wal-shutdown-failure");
1568 let builder = object_store_wal_builder(
1569 data_home.path().to_str().unwrap(),
1570 ObjectStoreWalConfig::default(),
1571 );
1572 let mut datanode = builder.build().await.unwrap();
1573 let log_store = datanode.object_store_log_store.clone().unwrap();
1574 let stop_calls = Arc::new(AtomicUsize::new(0));
1576 for name in ["failing-a", "failing-b"] {
1577 let stop_calls = stop_calls.clone();
1578 let (engine, _) = MockRegionEngine::with_custom_apply_fn(name, move |engine| {
1579 engine.handle_stop_mock_fn = Some(Box::new(move || {
1580 stop_calls.fetch_add(1, Ordering::Relaxed);
1581 error::UnexpectedSnafu {
1582 violated: "stop failed",
1583 }
1584 .fail()
1585 }));
1586 });
1587 datanode.region_server().register_engine(engine);
1588 }
1589
1590 let err = datanode.shutdown().await.unwrap_err();
1591
1592 assert_matches!(err, Error::StopRegionEngine { .. });
1593 assert_eq!(2, stop_calls.load(Ordering::Relaxed));
1594 assert!(is_stopped(&log_store).await);
1595 }
1596
1597 #[tokio::test]
1598 async fn test_failed_build_stops_object_store_log_store() {
1599 common_telemetry::init_default_ut_logging();
1600 let data_home = create_temp_dir("object-store-wal-failed-build");
1601 let kv_backend = Arc::new(MemoryKvBackend::new());
1602 setup_table_datanode(&(kv_backend.clone() as _)).await;
1605 let mut builder = datanode_builder(
1606 data_home.path().to_str().unwrap(),
1607 DatanodeWalConfig::ObjectStore(ObjectStoreWalConfig::default()),
1608 kv_backend,
1609 );
1610
1611 assert!(builder.try_build().await.is_err());
1612
1613 let log_store = builder.object_store_log_store.as_ref().unwrap();
1614 assert!(is_stopped(log_store).await);
1615 }
1616
1617 #[test]
1618 fn test_node_prefix_passes_validation() {
1619 let config = ObjectStoreWalConfig::default();
1620 let derived = ObjectStoreWalConfig {
1621 prefix: config.node_prefix(0, STANDALONE_GENERATION),
1622 ..config
1623 };
1624 validate_object_store_wal_config(&derived).unwrap();
1625 }
1626}