Skip to main content

datanode/
datanode.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Datanode implementation.
16
17use 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
94/// Datanode service.
95pub 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            // Safety: The event_receiver must exist.
131            let receiver = self.region_event_receiver.take().unwrap();
132
133            task.start(receiver, self.leases_notifier.clone()).await?;
134        }
135        Ok(())
136    }
137
138    /// If `leases_notifier` exists, it waits until leases have been obtained in all regions.
139    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    /// Shuts down every component in order. A failed step does not skip the
150    /// later ones; the first error is returned and the rest are logged.
151    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    /// Returns the object store log store when the datanode runs on the object
190    /// store WAL.
191    #[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    /// Set as soon as the object store log store exists so that a failed build can stop it.
214    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    /// Overrides whether regions opened during datanode startup should become writable.
276    ///
277    /// When unset, the builder uses its default writable policy for reopened regions
278    /// (writable only when no metasrv client is configured).
279    ///
280    /// Warning: setting this to `true` on a metasrv-controlled datanode (one built
281    /// with `with_meta_client`) will promote regions to Leader before heartbeat and
282    /// lease coordination begin, bypassing the metasrv safety contract and creating a
283    /// potential split-brain window during startup.
284    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    /// Builds the datanode and stops the object store log store if a later step fails.
303    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        // If metasrv client is provided, we will use it to control the region server.
324        // Otherwise the region server is self-controlled, meaning no heartbeat and immediately
325        // writable upon open.
326        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        // build and initialize region server
333        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        // TODO(weny): Considering introducing a readonly kv_backend trait.
372        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            // Ignore nonexistent regions in recovery mode.
389            is_recovery_mode,
390        );
391
392        if self.opts.init_regions_in_background {
393            // Opens regions in background.
394            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(&region_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    /// Builds [ObjectStoreManager] from [StorageConfig].
462    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    /// Open all regions belong to this datanode.
477    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        // TODO(weny): Considering introducing a readonly kv_backend trait.
485        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        // Keep `with_dist_planner` false: datanodes execute received MergeScan plans.
513        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        // MergeScan region requests stream until completion, so they have no client timeout.
522        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            // query engine in datanode only executes plan with resolved table source.
540            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    // internal utils
588
589    /// Builds [RegionEngineRef] from `store_engine` section in `opts`
590    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        // Build a fetcher to backfill partition_expr on open.
616        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(), // TODO: implement custom storage for file engine
634            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    /// Builds [MitoEngine] according to options.
645    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            // Enable the write cache when setting object storage
657            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    /// Builds [ObjectStoreLogStore] under the node prefix of `node_id` in the
784    /// standalone generation, the prefix the regions persist in their WAL options.
785    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    /// Builds [RaftEngineLogStore].
801    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        // create WAL directory
812        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    /// Builds [`KafkaLogStore`].
828    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    /// Builds [`GlobalIndexCollector`]
840    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
849/// Keeps the first shutdown error and logs the later ones.
850pub(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
860/// Returns the object store that holds the WAL objects: the default one for an
861/// empty `storage_provider`, otherwise the configured provider with that name.
862fn 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
886/// Rejects repeated engine types before their configs can silently overwrite each other.
887fn 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
907/// Rejects an object store WAL config the log store cannot run on.
908fn 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
971/// Open all regions belong to this datanode.
972async 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                    // Finalize leadership: persist backfilled metadata.
1025                    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    /// Returns true if the store rejects even an empty append, which it does only once stopped.
1237    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        // Omitted engines still use their defaults during engine construction.
1254        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            // Rejected before the builder creates any storage or log store.
1308            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            // The builder stops before it creates any storage or log store.
1399            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        // The builder stops before it creates any storage or log store.
1418        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        // No log store is created under the prefix.
1447        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        // The store runs under the node prefix derived from the configured root.
1541        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        // The default file store is registered under its provider name.
1551        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        // Both engines fail to stop, so whichever the region server stops first fails.
1575        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        // Regions registered in the metadata but missing on disk fail the region open
1603        // that runs after the log store and the engines are built.
1604        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}