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::noop::log_store::NoopLogStore;
50use log_store::raft_engine::log_store::RaftEngineLogStore;
51use meta_client::MetaClientRef;
52use metric_engine::engine::MetricEngine;
53use mito2::config::MitoConfig;
54use mito2::engine::{MitoEngine, MitoEngineBuilder};
55use mito2::region::opener::PartitionExprFetcherRef;
56use mito2::sst::file_ref::{FileReferenceManager, FileReferenceManagerRef};
57use object_store::ObjectStore;
58use object_store::manager::{ObjectStoreManager, ObjectStoreManagerRef};
59use object_store::util::normalize_dir;
60use query::QueryEngineFactory;
61use query::dummy_catalog::TableProviderFactoryRef;
62use query::region_query::RegionQueryHandlerFactoryRef;
63use servers::server::ServerHandlers;
64use snafu::{OptionExt, ResultExt, ensure};
65use store_api::logstore::LogStore;
66use store_api::path_utils::WAL_DIR;
67use store_api::region_engine::{
68    RegionEngineRef, RegionRole, SetRegionRoleStateResponse, SettableRegionRoleState,
69};
70use tokio::fs;
71use tokio::sync::Notify;
72
73use crate::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
74use crate::error::{
75    self, BuildDatanodeSnafu, BuildMetricEngineSnafu, BuildMitoEngineSnafu, CreateDirSnafu,
76    DataFusionSnafu, DuplicateRegionEngineConfigSnafu, GetMetadataSnafu,
77    InvalidObjectStoreWalConfigSnafu, MissingCacheSnafu, MissingNodeIdSnafu,
78    ObjectStoreWalNotStandaloneSnafu, OpenLogStoreSnafu, Result, ShutdownInstanceSnafu,
79    ShutdownServerSnafu, StartServerSnafu,
80};
81use crate::event_listener::{
82    NoopRegionServerEventListener, RegionServerEventListenerRef, RegionServerEventReceiver,
83    new_region_server_event_channel,
84};
85use crate::greptimedb_telemetry::get_greptimedb_telemetry_task;
86use crate::heartbeat::HeartbeatTask;
87use crate::partition_expr_fetcher::MetaPartitionExprFetcher;
88use crate::region_query::DatanodeRegionQueryHandler;
89use crate::region_server::{DummyTableProviderFactory, RegionServer};
90use crate::store;
91use crate::utils::{RegionOpenRequests, build_region_open_requests};
92
93/// Datanode service.
94pub struct Datanode {
95    services: ServerHandlers,
96    heartbeat_task: Option<HeartbeatTask>,
97    region_event_receiver: Option<RegionServerEventReceiver>,
98    region_server: RegionServer,
99    greptimedb_telemetry_task: Arc<GreptimeDBTelemetryTask>,
100    leases_notifier: Option<Arc<Notify>>,
101    plugins: Plugins,
102    object_store_log_store: Option<Arc<ObjectStoreLogStore>>,
103}
104
105impl Datanode {
106    pub async fn start(&mut self) -> Result<()> {
107        info!("Starting datanode instance...");
108
109        self.start_heartbeat().await?;
110        self.wait_coordinated().await;
111
112        self.start_telemetry();
113
114        self.services.start_all().await.context(StartServerSnafu)
115    }
116
117    pub fn server_handlers(&self) -> &ServerHandlers {
118        &self.services
119    }
120
121    pub fn start_telemetry(&self) {
122        if let Err(e) = self.greptimedb_telemetry_task.start() {
123            warn!(e; "Failed to start telemetry task!");
124        }
125    }
126
127    pub async fn start_heartbeat(&mut self) -> Result<()> {
128        if let Some(task) = &self.heartbeat_task {
129            // Safety: The event_receiver must exist.
130            let receiver = self.region_event_receiver.take().unwrap();
131
132            task.start(receiver, self.leases_notifier.clone()).await?;
133        }
134        Ok(())
135    }
136
137    /// If `leases_notifier` exists, it waits until leases have been obtained in all regions.
138    pub async fn wait_coordinated(&mut self) {
139        if let Some(notifier) = self.leases_notifier.take() {
140            notifier.notified().await;
141        }
142    }
143
144    pub fn setup_services(&mut self, services: ServerHandlers) {
145        self.services = services;
146    }
147
148    /// Shuts down every component in order. A failed step does not skip the
149    /// later ones; the first error is returned and the rest are logged.
150    pub async fn shutdown(&mut self) -> Result<()> {
151        let mut first_error = None;
152        record_shutdown_error(
153            &mut first_error,
154            self.services
155                .shutdown_all()
156                .await
157                .context(ShutdownServerSnafu),
158        );
159
160        let _ = self.greptimedb_telemetry_task.stop().await;
161        if let Some(heartbeat_task) = &self.heartbeat_task {
162            record_shutdown_error(
163                &mut first_error,
164                heartbeat_task
165                    .close()
166                    .map_err(BoxedError::new)
167                    .context(ShutdownInstanceSnafu),
168            );
169        }
170        // The object store WAL stops before the regions close: its stop
171        // uploads the backlog or drops it, and answers every wait for its
172        // entries, so a flush that waits for the WAL, queued or run by the
173        // close of a region, cannot hold up the shutdown.
174        if let Some(log_store) = &self.object_store_log_store {
175            record_shutdown_error(
176                &mut first_error,
177                log_store
178                    .stop()
179                    .await
180                    .map_err(BoxedError::new)
181                    .context(ShutdownInstanceSnafu),
182            );
183        }
184        record_shutdown_error(&mut first_error, self.region_server.stop().await);
185        first_error.map_or(Ok(()), Err)
186    }
187
188    pub fn region_server(&self) -> RegionServer {
189        self.region_server.clone()
190    }
191
192    /// Returns the object store log store when the datanode runs on the object
193    /// store WAL.
194    #[cfg(any(test, feature = "testing"))]
195    pub fn object_store_log_store(&self) -> Option<Arc<ObjectStoreLogStore>> {
196        self.object_store_log_store.clone()
197    }
198
199    pub fn plugins(&self) -> Plugins {
200        self.plugins.clone()
201    }
202}
203
204pub struct DatanodeBuilder {
205    opts: DatanodeOptions,
206    table_provider_factory: Option<TableProviderFactoryRef>,
207    plugins: Plugins,
208    meta_client: Option<MetaClientRef>,
209    kv_backend: KvBackendRef,
210    cache_registry: Option<Arc<LayeredCacheRegistry>>,
211    topic_stats_reporter: Option<Box<dyn TopicStatsReporter>>,
212    open_regions_writable_override: Option<bool>,
213    local_file_access: LocalFileAccess,
214    #[cfg(feature = "enterprise")]
215    extension_range_provider_factory: Option<mito2::extension::BoxedExtensionRangeProviderFactory>,
216    /// Set as soon as the object store log store exists so that a failed build can stop it.
217    object_store_log_store: Option<Arc<ObjectStoreLogStore>>,
218}
219
220impl DatanodeBuilder {
221    pub fn new(opts: DatanodeOptions, plugins: Plugins, kv_backend: KvBackendRef) -> Self {
222        Self {
223            opts,
224            table_provider_factory: None,
225            plugins,
226            meta_client: None,
227            kv_backend,
228            cache_registry: None,
229            open_regions_writable_override: None,
230            local_file_access: LocalFileAccess::Disabled,
231            #[cfg(feature = "enterprise")]
232            extension_range_provider_factory: None,
233            topic_stats_reporter: None,
234            object_store_log_store: None,
235        }
236    }
237
238    pub fn options(&self) -> &DatanodeOptions {
239        &self.opts
240    }
241
242    pub fn with_meta_client(&mut self, client: MetaClientRef) -> &mut Self {
243        self.meta_client = Some(client);
244        self
245    }
246
247    pub fn with_cache_registry(&mut self, registry: Arc<LayeredCacheRegistry>) -> &mut Self {
248        self.cache_registry = Some(registry);
249        self
250    }
251
252    pub fn with_local_file_access(&mut self, local_file_access: LocalFileAccess) -> &mut Self {
253        self.local_file_access = local_file_access;
254        self
255    }
256
257    pub fn kv_backend(&self) -> &KvBackendRef {
258        &self.kv_backend
259    }
260
261    pub fn meta_client(&self) -> Option<&MetaClientRef> {
262        self.meta_client.as_ref()
263    }
264
265    pub fn cache_registry(&self) -> Option<&Arc<LayeredCacheRegistry>> {
266        self.cache_registry.as_ref()
267    }
268
269    pub fn set_plugins(&mut self, plugins: Plugins) {
270        self.plugins = plugins;
271    }
272
273    pub fn with_table_provider_factory(&mut self, factory: TableProviderFactoryRef) -> &mut Self {
274        self.table_provider_factory = Some(factory);
275        self
276    }
277
278    /// Overrides whether regions opened during datanode startup should become writable.
279    ///
280    /// When unset, the builder uses its default writable policy for reopened regions
281    /// (writable only when no metasrv client is configured).
282    ///
283    /// Warning: setting this to `true` on a metasrv-controlled datanode (one built
284    /// with `with_meta_client`) will promote regions to Leader before heartbeat and
285    /// lease coordination begin, bypassing the metasrv safety contract and creating a
286    /// potential split-brain window during startup.
287    pub fn with_open_regions_writable_override(&mut self, writable: bool) -> &mut Self {
288        self.open_regions_writable_override = Some(writable);
289        self
290    }
291
292    #[cfg(feature = "enterprise")]
293    pub fn with_extension_range_provider(
294        &mut self,
295        extension_range_provider_factory: mito2::extension::BoxedExtensionRangeProviderFactory,
296    ) -> &mut Self {
297        self.extension_range_provider_factory = Some(extension_range_provider_factory);
298        self
299    }
300
301    pub async fn build(mut self) -> Result<Datanode> {
302        self.try_build().await
303    }
304
305    /// Builds the datanode and stops the object store log store if a later step fails.
306    async fn try_build(&mut self) -> Result<Datanode> {
307        let result = self.build_inner().await;
308        if result.is_err()
309            && let Some(log_store) = &self.object_store_log_store
310            && let Err(err) = log_store.stop().await
311        {
312            warn!(err; "Failed to stop the object store log store after a failed build");
313        }
314        result
315    }
316
317    async fn build_inner(&mut self) -> Result<Datanode> {
318        validate_region_engine_config(&self.opts.region_engine)?;
319        let node_id = self.opts.node_id.context(MissingNodeIdSnafu)?;
320        set_default_prefix(self.opts.default_column_prefix.as_deref())
321            .map_err(BoxedError::new)
322            .context(BuildDatanodeSnafu)?;
323
324        let meta_client = self.meta_client.take();
325
326        // If metasrv client is provided, we will use it to control the region server.
327        // Otherwise the region server is self-controlled, meaning no heartbeat and immediately
328        // writable upon open.
329        let controlled_by_metasrv = meta_client.is_some();
330        if let DatanodeWalConfig::ObjectStore(config) = &self.opts.wal {
331            ensure!(!controlled_by_metasrv, ObjectStoreWalNotStandaloneSnafu);
332            validate_object_store_wal_config(config)?;
333        }
334
335        // build and initialize region server
336        let (region_event_listener, region_event_receiver) = if controlled_by_metasrv {
337            let (tx, rx) = new_region_server_event_channel();
338            (Box::new(tx) as _, Some(rx))
339        } else {
340            (Box::new(NoopRegionServerEventListener) as _, None)
341        };
342
343        let cache_registry = self.cache_registry.take().context(MissingCacheSnafu)?;
344        let schema_cache: SchemaCacheRef = cache_registry.get().context(MissingCacheSnafu)?;
345        let table_id_schema_cache: TableSchemaCacheRef =
346            cache_registry.get().context(MissingCacheSnafu)?;
347
348        let schema_metadata_manager = Arc::new(SchemaMetadataManager::new(
349            table_id_schema_cache,
350            schema_cache,
351        ));
352
353        let gc_enabled = self.opts.region_engine.iter().any(|engine| {
354            if let RegionEngineConfig::Mito(config) = engine {
355                config.gc.enable
356            } else {
357                false
358            }
359        });
360
361        let file_ref_manager = Arc::new(FileReferenceManager::with_gc_enabled(
362            Some(node_id),
363            gc_enabled,
364        ));
365        let region_server = self
366            .new_region_server(
367                schema_metadata_manager,
368                region_event_listener,
369                file_ref_manager,
370                cache_registry.clone(),
371            )
372            .await?;
373
374        // TODO(weny): Considering introducing a readonly kv_backend trait.
375        let runtime_switch_manager = RuntimeSwitchManager::new(self.kv_backend.clone());
376        let is_recovery_mode = runtime_switch_manager
377            .recovery_mode()
378            .await
379            .context(GetMetadataSnafu)?;
380
381        let region_open_requests =
382            build_region_open_requests(node_id, self.kv_backend.clone()).await?;
383        let open_with_writable = self
384            .open_regions_writable_override
385            .unwrap_or(!controlled_by_metasrv);
386        let open_all_regions = open_all_regions(
387            region_server.clone(),
388            region_open_requests,
389            open_with_writable,
390            self.opts.init_regions_parallelism,
391            // Ignore nonexistent regions in recovery mode.
392            is_recovery_mode,
393        );
394
395        if self.opts.init_regions_in_background {
396            // Opens regions in background.
397            common_runtime::spawn_global(async move {
398                if let Err(err) = open_all_regions.await {
399                    error!(err; "Failed to open regions during the startup.");
400                }
401            });
402        } else {
403            open_all_regions.await?;
404        }
405
406        let heartbeat_task = if let Some(meta_client) = meta_client {
407            let task = self
408                .create_heartbeat_task(&region_server, meta_client, cache_registry)
409                .await?;
410            Some(task)
411        } else {
412            None
413        };
414
415        let is_standalone = heartbeat_task.is_none();
416        let greptimedb_telemetry_task = get_greptimedb_telemetry_task(
417            Some(self.opts.storage.data_home.clone()),
418            is_standalone && self.opts.enable_telemetry,
419        )
420        .await;
421
422        let leases_notifier = if self.opts.require_lease_before_startup && !is_standalone {
423            Some(Arc::new(Notify::new()))
424        } else {
425            None
426        };
427
428        Ok(Datanode {
429            services: ServerHandlers::default(),
430            heartbeat_task,
431            region_server,
432            greptimedb_telemetry_task,
433            region_event_receiver,
434            leases_notifier,
435            plugins: self.plugins.clone(),
436            object_store_log_store: self.object_store_log_store.take(),
437        })
438    }
439
440    async fn create_heartbeat_task(
441        &self,
442        region_server: &RegionServer,
443        meta_client: MetaClientRef,
444        cache_invalidator: CacheInvalidatorRef,
445    ) -> Result<HeartbeatTask> {
446        let stat = {
447            let mut stat = ResourceStatImpl::default();
448            stat.start_collect_cpu_usage();
449            Arc::new(stat)
450        };
451
452        HeartbeatTask::try_new(
453            &self.opts,
454            region_server.clone(),
455            meta_client,
456            self.kv_backend.clone(),
457            cache_invalidator,
458            self.plugins.clone(),
459            stat,
460        )
461        .await
462    }
463
464    /// Builds [ObjectStoreManager] from [StorageConfig].
465    pub async fn build_object_store_manager(cfg: &StorageConfig) -> Result<ObjectStoreManagerRef> {
466        let object_store = store::new_object_store(cfg.store.clone(), &cfg.data_home).await?;
467        let default_name = cfg.store.config_name();
468        let mut object_store_manager = ObjectStoreManager::new(default_name, object_store);
469        for store in &cfg.providers {
470            object_store_manager.add(
471                store.config_name(),
472                store::new_object_store(store.clone(), &cfg.data_home).await?,
473            );
474        }
475        Ok(Arc::new(object_store_manager))
476    }
477
478    #[cfg(test)]
479    /// Open all regions belong to this datanode.
480    async fn initialize_region_server(
481        &self,
482        region_server: &RegionServer,
483        open_with_writable: bool,
484    ) -> Result<()> {
485        let node_id = self.opts.node_id.context(MissingNodeIdSnafu)?;
486
487        // TODO(weny): Considering introducing a readonly kv_backend trait.
488        let runtime_switch_manager = RuntimeSwitchManager::new(self.kv_backend.clone());
489        let is_recovery_mode = runtime_switch_manager
490            .recovery_mode()
491            .await
492            .context(GetMetadataSnafu)?;
493        let region_open_requests =
494            build_region_open_requests(node_id, self.kv_backend.clone()).await?;
495
496        open_all_regions(
497            region_server.clone(),
498            region_open_requests,
499            open_with_writable,
500            self.opts.init_regions_parallelism,
501            is_recovery_mode,
502        )
503        .await
504    }
505
506    async fn new_region_server(
507        &mut self,
508        schema_metadata_manager: SchemaMetadataManagerRef,
509        event_listener: RegionServerEventListenerRef,
510        file_ref_manager: FileReferenceManagerRef,
511        cache_registry: Arc<LayeredCacheRegistry>,
512    ) -> Result<RegionServer> {
513        let opts: &DatanodeOptions = &self.opts;
514
515        // Keep `with_dist_planner` false: datanodes execute received MergeScan plans.
516        let catalog_manager = KvBackendCatalogManagerBuilder::new(
517            Arc::new(NoopInformationExtension),
518            self.kv_backend.clone(),
519            cache_registry.clone(),
520        )
521        .build();
522        let partition_manager = catalog_manager.partition_manager();
523
524        // MergeScan region requests stream until completion, so they have no client timeout.
525        let mut channel_config = ChannelConfig {
526            timeout: None,
527            ..Default::default()
528        };
529        if opts.grpc.flight_compression.transport_compression() {
530            channel_config.accept_compression = true;
531            channel_config.send_compression = true;
532        }
533        let node_manager: NodeManagerRef = Arc::new(NodeClients::new(channel_config));
534        let region_query_handler =
535            if let Some(factory) = self.plugins.get::<RegionQueryHandlerFactoryRef>() {
536                factory.build(partition_manager.clone(), node_manager)
537            } else {
538                DatanodeRegionQueryHandler::arc(partition_manager.clone(), node_manager)
539            };
540
541        let query_engine_factory = QueryEngineFactory::try_new_with_plugins(
542            // query engine in datanode only executes plan with resolved table source.
543            catalog_manager,
544            Some(partition_manager),
545            Some(region_query_handler),
546            None,
547            None,
548            None,
549            false,
550            self.plugins.clone(),
551            opts.query.clone(),
552        )
553        .context(DataFusionSnafu)?;
554        let query_engine = query_engine_factory.query_engine();
555
556        let table_provider_factory = self
557            .table_provider_factory
558            .clone()
559            .unwrap_or_else(|| Arc::new(DummyTableProviderFactory));
560        let mut region_server = RegionServer::with_table_provider(
561            query_engine,
562            common_runtime::global_runtime(),
563            event_listener,
564            table_provider_factory,
565            opts.max_concurrent_queries,
566            opts.concurrent_query_limiter_timeout,
567            opts.grpc.flight_compression,
568        );
569        region_server.install_remote_dyn_filter_receiver_injector(&self.plugins);
570
571        let object_store_manager = Self::build_object_store_manager(&opts.storage).await?;
572        let engines = self
573            .build_store_engines(
574                object_store_manager,
575                schema_metadata_manager,
576                file_ref_manager,
577                self.plugins.clone(),
578            )
579            .await?;
580        for engine in engines {
581            region_server.register_engine(engine);
582        }
583        if let Some(topic_stats_reporter) = self.topic_stats_reporter.take() {
584            region_server.set_topic_stats_reporter(topic_stats_reporter);
585        }
586
587        Ok(region_server)
588    }
589
590    // internal utils
591
592    /// Builds [RegionEngineRef] from `store_engine` section in `opts`
593    async fn build_store_engines(
594        &mut self,
595        object_store_manager: ObjectStoreManagerRef,
596        schema_metadata_manager: SchemaMetadataManagerRef,
597        file_ref_manager: FileReferenceManagerRef,
598        plugins: Plugins,
599    ) -> Result<Vec<RegionEngineRef>> {
600        let mut metric_engine_config = metric_engine::config::EngineConfig::default();
601        let mut mito_engine_config = MitoConfig::default();
602        let mut file_engine_config = file_engine::config::EngineConfig::default();
603
604        for engine in &self.opts.region_engine {
605            match engine {
606                RegionEngineConfig::Mito(config) => {
607                    mito_engine_config = config.clone();
608                }
609                RegionEngineConfig::File(config) => {
610                    file_engine_config = config.clone();
611                }
612                RegionEngineConfig::Metric(metric_config) => {
613                    metric_engine_config = metric_config.clone();
614                }
615            }
616        }
617
618        // Build a fetcher to backfill partition_expr on open.
619        let fetcher = Arc::new(MetaPartitionExprFetcher::new(self.kv_backend.clone()));
620        let mito_engine = self
621            .build_mito_engine(
622                object_store_manager.clone(),
623                mito_engine_config,
624                schema_metadata_manager.clone(),
625                file_ref_manager.clone(),
626                fetcher.clone(),
627                plugins.clone(),
628            )
629            .await?;
630
631        let metric_engine = MetricEngine::try_new(mito_engine.clone(), metric_engine_config)
632            .context(BuildMetricEngineSnafu)?;
633
634        let file_engine = FileRegionEngine::new(
635            file_engine_config,
636            object_store_manager.default_object_store().clone(), // TODO: implement custom storage for file engine
637            self.local_file_access.clone(),
638        );
639
640        Ok(vec![
641            Arc::new(mito_engine) as _,
642            Arc::new(metric_engine) as _,
643            Arc::new(file_engine) as _,
644        ])
645    }
646
647    /// Builds [MitoEngine] according to options.
648    async fn build_mito_engine(
649        &mut self,
650        object_store_manager: ObjectStoreManagerRef,
651        mut config: MitoConfig,
652        schema_metadata_manager: SchemaMetadataManagerRef,
653        file_ref_manager: FileReferenceManagerRef,
654        partition_expr_fetcher: PartitionExprFetcherRef,
655        plugins: Plugins,
656    ) -> Result<MitoEngine> {
657        let opts = &self.opts;
658        if opts.storage.is_object_storage() {
659            // Enable the write cache when setting object storage
660            config.enable_write_cache = true;
661            info!("Configured 'enable_write_cache=true' for mito engine.");
662        }
663
664        let mito_engine = match &opts.wal {
665            DatanodeWalConfig::RaftEngine(raft_engine_config) => {
666                let log_store =
667                    Self::build_raft_engine_log_store(&opts.storage.data_home, raft_engine_config)
668                        .await?;
669
670                let builder = MitoEngineBuilder::new(
671                    &opts.storage.data_home,
672                    config,
673                    log_store,
674                    object_store_manager,
675                    schema_metadata_manager,
676                    file_ref_manager,
677                    partition_expr_fetcher.clone(),
678                    plugins,
679                );
680
681                #[cfg(feature = "enterprise")]
682                let builder = builder.with_extension_range_provider_factory(
683                    self.extension_range_provider_factory.take(),
684                );
685
686                builder.try_build().await.context(BuildMitoEngineSnafu)?
687            }
688            DatanodeWalConfig::Kafka(kafka_config) => {
689                let log_store = Self::build_kafka_log_store(kafka_config).await?;
690                self.topic_stats_reporter = Some(log_store.topic_stats_reporter());
691                let builder = MitoEngineBuilder::new(
692                    &opts.storage.data_home,
693                    config,
694                    log_store,
695                    object_store_manager,
696                    schema_metadata_manager,
697                    file_ref_manager,
698                    partition_expr_fetcher,
699                    plugins,
700                );
701
702                #[cfg(feature = "enterprise")]
703                let builder = builder.with_extension_range_provider_factory(
704                    self.extension_range_provider_factory.take(),
705                );
706
707                builder.try_build().await.context(BuildMitoEngineSnafu)?
708            }
709            DatanodeWalConfig::Noop => {
710                let log_store = Arc::new(NoopLogStore);
711
712                let builder = MitoEngineBuilder::new(
713                    &opts.storage.data_home,
714                    config,
715                    log_store,
716                    object_store_manager,
717                    schema_metadata_manager,
718                    file_ref_manager,
719                    partition_expr_fetcher.clone(),
720                    plugins,
721                );
722
723                #[cfg(feature = "enterprise")]
724                let builder = builder.with_extension_range_provider_factory(
725                    self.extension_range_provider_factory.take(),
726                );
727
728                builder.try_build().await.context(BuildMitoEngineSnafu)?
729            }
730            DatanodeWalConfig::ObjectStore(object_store_config) => {
731                let object_store = wal_object_store(
732                    &object_store_manager,
733                    &opts.storage,
734                    &object_store_config.storage_provider,
735                )?;
736                let node_id = opts.node_id.context(MissingNodeIdSnafu)?;
737                let log_store =
738                    Self::build_object_store_log_store(object_store, object_store_config, node_id)
739                        .await?;
740                self.object_store_log_store = Some(log_store.clone());
741
742                let builder = MitoEngineBuilder::new(
743                    &opts.storage.data_home,
744                    config,
745                    log_store,
746                    object_store_manager,
747                    schema_metadata_manager,
748                    file_ref_manager,
749                    partition_expr_fetcher,
750                    plugins,
751                );
752
753                #[cfg(feature = "enterprise")]
754                let builder = builder.with_extension_range_provider_factory(
755                    self.extension_range_provider_factory.take(),
756                );
757
758                builder.try_build().await.context(BuildMitoEngineSnafu)?
759            }
760        };
761        Ok(mito_engine)
762    }
763
764    /// Builds [ObjectStoreLogStore] under the node prefix of `node_id` in the
765    /// standalone generation, the prefix the regions persist in their WAL options.
766    async fn build_object_store_log_store(
767        object_store: ObjectStore,
768        config: &ObjectStoreWalConfig,
769        node_id: u64,
770    ) -> Result<Arc<ObjectStoreLogStore>> {
771        info!(
772            "Creating object store logstore with config: {:?}, node id: {}",
773            config, node_id
774        );
775        ObjectStoreLogStore::try_new(object_store, config, node_id, STANDALONE_GENERATION)
776            .await
777            .map_err(Box::new)
778            .context(OpenLogStoreSnafu)
779    }
780
781    /// Builds [RaftEngineLogStore].
782    async fn build_raft_engine_log_store(
783        data_home: &str,
784        config: &RaftEngineConfig,
785    ) -> Result<Arc<RaftEngineLogStore>> {
786        let data_home = normalize_dir(data_home);
787        let wal_dir = match &config.dir {
788            Some(dir) => dir.clone(),
789            None => format!("{}{WAL_DIR}", data_home),
790        };
791
792        // create WAL directory
793        fs::create_dir_all(Path::new(&wal_dir))
794            .await
795            .context(CreateDirSnafu { dir: &wal_dir })?;
796        info!(
797            "Creating raft-engine logstore with config: {:?} and storage path: {}",
798            config, &wal_dir
799        );
800        let logstore = RaftEngineLogStore::try_new(wal_dir, config)
801            .await
802            .map_err(Box::new)
803            .context(OpenLogStoreSnafu)?;
804
805        Ok(Arc::new(logstore))
806    }
807
808    /// Builds [`KafkaLogStore`].
809    async fn build_kafka_log_store(config: &DatanodeKafkaConfig) -> Result<Arc<KafkaLogStore>> {
810        KafkaLogStore::try_new(config)
811            .await
812            .map_err(Box::new)
813            .context(OpenLogStoreSnafu)
814            .map(Arc::new)
815    }
816}
817
818/// Keeps the first shutdown error and logs the later ones.
819pub(crate) fn record_shutdown_error(first_error: &mut Option<error::Error>, result: Result<()>) {
820    if let Err(err) = result {
821        if first_error.is_none() {
822            *first_error = Some(err);
823        } else {
824            warn!(err; "Ignored a later shutdown error");
825        }
826    }
827}
828
829/// Returns the object store that holds the WAL objects: the default one for an
830/// empty `storage_provider`, otherwise the configured provider with that name.
831fn wal_object_store(
832    object_store_manager: &ObjectStoreManager,
833    storage: &StorageConfig,
834    storage_provider: &str,
835) -> Result<ObjectStore> {
836    if storage_provider.is_empty() {
837        return Ok(object_store_manager.default_object_store().clone());
838    }
839    object_store_manager
840        .find(storage_provider)
841        .cloned()
842        .with_context(|| {
843            let configured = std::iter::once(&storage.store)
844                .chain(storage.providers.iter())
845                .map(|store| store.config_name())
846                .collect::<Vec<_>>();
847            InvalidObjectStoreWalConfigSnafu {
848                field: "storage_provider",
849                value: storage_provider,
850                reason: format!("must name a configured storage provider, one of {configured:?}"),
851            }
852        })
853}
854
855/// Rejects repeated engine types before their configs can silently overwrite each other.
856fn validate_region_engine_config(configs: &[RegionEngineConfig]) -> Result<()> {
857    let mut engine_indices = HashMap::new();
858    for (index, config) in configs.iter().enumerate() {
859        let engine = match config {
860            RegionEngineConfig::Mito(_) => MITO_ENGINE,
861            RegionEngineConfig::File(_) => FILE_ENGINE,
862            RegionEngineConfig::Metric(_) => METRIC_ENGINE,
863        };
864        if let Some(first_index) = engine_indices.insert(engine, index) {
865            return DuplicateRegionEngineConfigSnafu {
866                engine,
867                first_index,
868                duplicate_index: index,
869            }
870            .fail();
871        }
872    }
873    Ok(())
874}
875
876/// Rejects an object store WAL config the log store cannot run on.
877fn validate_object_store_wal_config(config: &ObjectStoreWalConfig) -> Result<()> {
878    let prefix = config.prefix.trim();
879    ensure!(
880        !prefix.is_empty(),
881        InvalidObjectStoreWalConfigSnafu {
882            field: "prefix",
883            value: &config.prefix,
884            reason: "must not be empty",
885        }
886    );
887    ensure!(
888        !prefix.starts_with('/'),
889        InvalidObjectStoreWalConfigSnafu {
890            field: "prefix",
891            value: &config.prefix,
892            reason: "must be a relative path",
893        }
894    );
895    ensure!(
896        !prefix
897            .split('/')
898            .any(|component| component.is_empty() || component == ".."),
899        InvalidObjectStoreWalConfigSnafu {
900            field: "prefix",
901            value: &config.prefix,
902            reason: "must not contain empty or `..` components",
903        }
904    );
905    ensure!(
906        config.flush_interval >= Duration::from_millis(10),
907        InvalidObjectStoreWalConfigSnafu {
908            field: "flush_interval",
909            value: format!("{:?}", config.flush_interval),
910            reason: "must be at least 10ms",
911        }
912    );
913    ensure!(
914        config.max_batch_bytes.as_bytes() > 0,
915        InvalidObjectStoreWalConfigSnafu {
916            field: "max_batch_bytes",
917            value: config.max_batch_bytes.to_string(),
918            reason: "must be greater than 0",
919        }
920    );
921    ensure!(
922        config.max_unpersisted_bytes.as_bytes() > 0,
923        InvalidObjectStoreWalConfigSnafu {
924            field: "max_unpersisted_bytes",
925            value: config.max_unpersisted_bytes.to_string(),
926            reason: "must be greater than 0",
927        }
928    );
929    ensure!(
930        config.max_unpersisted_age > Duration::ZERO,
931        InvalidObjectStoreWalConfigSnafu {
932            field: "max_unpersisted_age",
933            value: format!("{:?}", config.max_unpersisted_age),
934            reason: "must be greater than 0",
935        }
936    );
937    Ok(())
938}
939
940/// Open all regions belong to this datanode.
941async fn open_all_regions(
942    region_server: RegionServer,
943    region_open_requests: RegionOpenRequests,
944    open_with_writable: bool,
945    init_regions_parallelism: usize,
946    ignore_nonexistent_region: bool,
947) -> Result<()> {
948    let RegionOpenRequests {
949        leader_regions,
950        #[cfg(feature = "enterprise")]
951        follower_regions,
952    } = region_open_requests;
953
954    let leader_region_num = leader_regions.len();
955    info!("going to open {} region(s)", leader_region_num);
956    let now = Instant::now();
957    let open_regions = region_server
958        .handle_batch_open_requests(
959            init_regions_parallelism,
960            leader_regions,
961            ignore_nonexistent_region,
962        )
963        .await?;
964    info!(
965        "Opened {} regions in {:?}",
966        open_regions.len(),
967        now.elapsed()
968    );
969    if !ignore_nonexistent_region {
970        ensure!(
971            open_regions.len() == leader_region_num,
972            error::UnexpectedSnafu {
973                violated: format!(
974                    "Expected to open {} of regions, only {} of regions has opened",
975                    leader_region_num,
976                    open_regions.len()
977                )
978            }
979        );
980    } else if open_regions.len() != leader_region_num {
981        warn!(
982            "ignore nonexistent region, expected to open {} of regions, only {} of regions has opened",
983            leader_region_num,
984            open_regions.len()
985        );
986    }
987
988    for region_id in open_regions {
989        if open_with_writable {
990            let res = region_server.set_region_role(region_id, RegionRole::Leader);
991            match res {
992                Ok(_) => {
993                    // Finalize leadership: persist backfilled metadata.
994                    if let SetRegionRoleStateResponse::InvalidTransition(err) = region_server
995                        .set_region_role_state_gracefully(
996                            region_id,
997                            SettableRegionRoleState::Leader,
998                        )
999                        .await?
1000                    {
1001                        error!(err; "failed to convert region {region_id} to leader");
1002                    }
1003                }
1004                Err(e) => {
1005                    error!(e; "failed to convert region {region_id} to leader");
1006                }
1007            }
1008        }
1009    }
1010
1011    #[cfg(feature = "enterprise")]
1012    if !follower_regions.is_empty() {
1013        use tokio::time::Instant;
1014
1015        let follower_region_num = follower_regions.len();
1016        info!("going to open {} follower region(s)", follower_region_num);
1017
1018        let now = Instant::now();
1019        let open_regions = region_server
1020            .handle_batch_open_requests(
1021                init_regions_parallelism,
1022                follower_regions,
1023                ignore_nonexistent_region,
1024            )
1025            .await?;
1026        info!(
1027            "Opened {} follower regions in {:?}",
1028            open_regions.len(),
1029            now.elapsed()
1030        );
1031
1032        if !ignore_nonexistent_region {
1033            ensure!(
1034                open_regions.len() == follower_region_num,
1035                error::UnexpectedSnafu {
1036                    violated: format!(
1037                        "Expected to open {} of follower regions, only {} of regions has opened",
1038                        follower_region_num,
1039                        open_regions.len()
1040                    )
1041                }
1042            );
1043        } else if open_regions.len() != follower_region_num {
1044            warn!(
1045                "ignore nonexistent region, expected to open {} of follower regions, only {} of regions has opened",
1046                follower_region_num,
1047                open_regions.len()
1048            );
1049        }
1050    }
1051
1052    info!("all regions are opened");
1053
1054    Ok(())
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use std::assert_matches;
1060    use std::collections::{BTreeMap, HashMap};
1061    use std::io::Write;
1062    use std::ops::Range;
1063    use std::path::Path;
1064    use std::sync::Arc;
1065    use std::sync::atomic::{AtomicUsize, Ordering};
1066    use std::time::Duration;
1067
1068    use api::v1::{ColumnSchema, Rows};
1069    use cache::build_datanode_layered_cache_registry;
1070    use common_base::Plugins;
1071    use common_base::readable_size::ReadableSize;
1072    use common_config::Configurable;
1073    use common_error::ext::ErrorExt;
1074    use common_error::status_code::StatusCode;
1075    use common_meta::key::RegionRoleSet;
1076    use common_meta::key::datanode_table::DatanodeTableManager;
1077    use common_meta::kv_backend::KvBackendRef;
1078    use common_meta::kv_backend::memory::MemoryKvBackend;
1079    use common_meta::node_manager::NodeManagerRef;
1080    use common_test_util::temp_dir::{create_named_temp_file, create_temp_dir};
1081    use common_wal::config::DatanodeWalConfig;
1082    use common_wal::config::object_store::{AckMode, ObjectStoreWalConfig, STANDALONE_GENERATION};
1083    use common_wal::options::{ObjectStoreWalOptions, WAL_OPTIONS_KEY, WalOptions};
1084    use log_store::ObjectStoreLogStore;
1085    use log_store::error::Error as LogStoreError;
1086    use meta_client::client::MetaClientBuilder;
1087    use mito2::engine::MITO_ENGINE_NAME;
1088    use mito2::test_util::{CreateRequestBuilder, build_rows, rows_schema};
1089    use object_store::ObjectStore;
1090    use object_store::manager::ObjectStoreManager;
1091    use partition::manager::PartitionRuleManagerRef;
1092    use query::region_query::{RegionQueryHandlerFactory, RegionQueryHandlerFactoryRef};
1093    use store_api::logstore::LogStore;
1094    use store_api::logstore::provider::Provider;
1095    use store_api::region_request::{PathType, RegionOpenRequest, RegionPutRequest, RegionRequest};
1096    use store_api::storage::RegionId;
1097
1098    use crate::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
1099    use crate::datanode::{
1100        Datanode, DatanodeBuilder, validate_object_store_wal_config, validate_region_engine_config,
1101        wal_object_store,
1102    };
1103    use crate::error::{self, Error};
1104    use crate::region_query::DatanodeRegionQueryHandler;
1105    use crate::region_server::RegionServer;
1106    use crate::tests::{MockRegionEngine, mock_region_server};
1107
1108    const WAIT: Duration = Duration::from_secs(30);
1109
1110    async fn setup_table_datanode(kv: &KvBackendRef) {
1111        let mgr = DatanodeTableManager::new(kv.clone());
1112        let txn = mgr
1113            .build_create_txn(
1114                1028,
1115                MITO_ENGINE_NAME,
1116                "foo/bar/weny",
1117                HashMap::from([("foo".to_string(), "bar".to_string())]),
1118                HashMap::default(),
1119                BTreeMap::from([(0, RegionRoleSet::new(vec![0, 1, 2], vec![]))]),
1120            )
1121            .unwrap();
1122
1123        let r = kv.txn(txn).await.unwrap();
1124        assert!(r.succeeded);
1125    }
1126
1127    #[tokio::test]
1128    async fn test_initialize_region_server() {
1129        common_telemetry::init_default_ut_logging();
1130        let mut mock_region_server = mock_region_server();
1131        let (mock_region, mut mock_region_handler) = MockRegionEngine::new(MITO_ENGINE_NAME);
1132
1133        mock_region_server.register_engine(mock_region.clone());
1134
1135        let kv_backend = Arc::new(MemoryKvBackend::new());
1136        let layered_cache_registry =
1137            Arc::new(build_datanode_layered_cache_registry(kv_backend.clone()));
1138
1139        let mut builder = DatanodeBuilder::new(
1140            DatanodeOptions {
1141                node_id: Some(0),
1142                ..Default::default()
1143            },
1144            Plugins::default(),
1145            kv_backend.clone(),
1146        );
1147        builder.with_cache_registry(layered_cache_registry);
1148        setup_table_datanode(&(kv_backend as _)).await;
1149
1150        builder
1151            .initialize_region_server(&mock_region_server, false)
1152            .await
1153            .unwrap();
1154
1155        for i in 0..3 {
1156            let (region_id, req) = mock_region_handler.recv().await.unwrap();
1157            assert_eq!(region_id, RegionId::new(1028, i));
1158            if let RegionRequest::Open(req) = req {
1159                assert_eq!(
1160                    req.options,
1161                    HashMap::from([("foo".to_string(), "bar".to_string())])
1162                )
1163            } else {
1164                unreachable!()
1165            }
1166        }
1167
1168        assert_matches!(
1169            mock_region_handler.try_recv(),
1170            Err(tokio::sync::mpsc::error::TryRecvError::Empty)
1171        );
1172    }
1173
1174    fn datanode_builder(
1175        data_home: &str,
1176        wal: DatanodeWalConfig,
1177        kv_backend: KvBackendRef,
1178    ) -> DatanodeBuilder {
1179        let layered_cache_registry =
1180            Arc::new(build_datanode_layered_cache_registry(kv_backend.clone()));
1181        let mut opts = DatanodeOptions {
1182            node_id: Some(0),
1183            wal,
1184            enable_telemetry: false,
1185            ..Default::default()
1186        };
1187        opts.storage.data_home = data_home.to_string();
1188
1189        let mut builder = DatanodeBuilder::new(opts, Plugins::default(), kv_backend);
1190        builder.with_cache_registry(layered_cache_registry);
1191        builder
1192    }
1193
1194    fn object_store_wal_builder(data_home: &str, config: ObjectStoreWalConfig) -> DatanodeBuilder {
1195        datanode_builder(
1196            data_home,
1197            DatanodeWalConfig::ObjectStore(config),
1198            Arc::new(MemoryKvBackend::new()),
1199        )
1200    }
1201
1202    async fn build_err(builder: DatanodeBuilder) -> Error {
1203        match builder.build().await {
1204            Ok(_) => panic!("build must fail"),
1205            Err(err) => err,
1206        }
1207    }
1208
1209    fn is_empty_dir(dir: &Path) -> bool {
1210        std::fs::read_dir(dir).unwrap().next().is_none()
1211    }
1212
1213    /// Returns true if the store rejects even an empty append, which it does only once stopped.
1214    async fn is_stopped(log_store: &ObjectStoreLogStore) -> bool {
1215        matches!(
1216            log_store.append_batch(vec![]).await,
1217            Err(LogStoreError::ObjectStoreWalStopped { .. })
1218        )
1219    }
1220
1221    #[test]
1222    fn test_validate_unique_region_engine_config() {
1223        let mut opts = DatanodeOptions::default();
1224        validate_region_engine_config(&opts.region_engine).unwrap();
1225
1226        opts.region_engine
1227            .push(RegionEngineConfig::Metric(Default::default()));
1228        validate_region_engine_config(&opts.region_engine).unwrap();
1229
1230        // Omitted engines still use their defaults during engine construction.
1231        validate_region_engine_config(&[]).unwrap();
1232    }
1233
1234    #[tokio::test]
1235    async fn test_build_rejects_duplicate_region_engine_config() {
1236        for (engine, first_index) in [("mito", 0), ("file", 1), ("metric", 2)] {
1237            let mut file = create_named_temp_file();
1238            write!(
1239                file,
1240                r#"
1241                [[region_engine]]
1242                [region_engine.mito]
1243                global_write_buffer_size = "4GiB"
1244                min_compaction_interval = "600s"
1245
1246                [[region_engine]]
1247                [region_engine.file]
1248
1249                [[region_engine]]
1250                [region_engine.metric]
1251
1252                [[region_engine]]
1253                [region_engine.{engine}]
1254                "#
1255            )
1256            .unwrap();
1257            let opts = DatanodeOptions::load_layered_options(
1258                Some(file.path().to_str().unwrap()),
1259                "DATANODE_DUPLICATE_REGION_ENGINE_UT",
1260            )
1261            .unwrap();
1262            let data_home = create_temp_dir("duplicate-region-engine-config");
1263            let mut builder = datanode_builder(
1264                data_home.path().to_str().unwrap(),
1265                DatanodeWalConfig::default(),
1266                Arc::new(MemoryKvBackend::new()),
1267            );
1268            builder.opts.region_engine = opts.region_engine;
1269            let err = build_err(builder).await;
1270
1271            let Error::DuplicateRegionEngineConfig {
1272                engine: actual_engine,
1273                first_index: actual_first_index,
1274                duplicate_index,
1275                ..
1276            } = &err
1277            else {
1278                panic!("unexpected error for {engine}: {err:?}");
1279            };
1280            assert_eq!(*actual_engine, engine);
1281            assert_eq!(*actual_first_index, first_index);
1282            assert_eq!(*duplicate_index, 3);
1283            assert_eq!(err.status_code(), StatusCode::InvalidArguments);
1284            // Rejected before the builder creates any storage or log store.
1285            assert!(is_empty_dir(data_home.path()));
1286        }
1287    }
1288
1289    #[tokio::test]
1290    async fn test_build_rejects_invalid_object_store_wal_config() {
1291        let cases = [
1292            (
1293                ObjectStoreWalConfig {
1294                    prefix: String::new(),
1295                    ..Default::default()
1296                },
1297                "prefix",
1298            ),
1299            (
1300                ObjectStoreWalConfig {
1301                    prefix: "/wal".to_string(),
1302                    ..Default::default()
1303                },
1304                "prefix",
1305            ),
1306            (
1307                ObjectStoreWalConfig {
1308                    prefix: "wal/../other".to_string(),
1309                    ..Default::default()
1310                },
1311                "prefix",
1312            ),
1313            (
1314                ObjectStoreWalConfig {
1315                    prefix: "wal//objects".to_string(),
1316                    ..Default::default()
1317                },
1318                "prefix",
1319            ),
1320            (
1321                ObjectStoreWalConfig {
1322                    prefix: "wal/".to_string(),
1323                    ..Default::default()
1324                },
1325                "prefix",
1326            ),
1327            (
1328                ObjectStoreWalConfig {
1329                    flush_interval: Duration::from_millis(9),
1330                    ..Default::default()
1331                },
1332                "flush_interval",
1333            ),
1334            (
1335                ObjectStoreWalConfig {
1336                    max_batch_bytes: ReadableSize(0),
1337                    ..Default::default()
1338                },
1339                "max_batch_bytes",
1340            ),
1341            (
1342                ObjectStoreWalConfig {
1343                    max_unpersisted_bytes: ReadableSize(0),
1344                    ..Default::default()
1345                },
1346                "max_unpersisted_bytes",
1347            ),
1348            (
1349                ObjectStoreWalConfig {
1350                    max_unpersisted_age: Duration::ZERO,
1351                    ..Default::default()
1352                },
1353                "max_unpersisted_age",
1354            ),
1355        ];
1356
1357        for (config, expected_field) in cases {
1358            let expected_value = match expected_field {
1359                "prefix" => config.prefix.clone(),
1360                "flush_interval" => format!("{:?}", config.flush_interval),
1361                "max_unpersisted_bytes" => config.max_unpersisted_bytes.to_string(),
1362                "max_unpersisted_age" => format!("{:?}", config.max_unpersisted_age),
1363                _ => config.max_batch_bytes.to_string(),
1364            };
1365            let data_home = create_temp_dir("object-store-wal-invalid-config");
1366            let builder = object_store_wal_builder(data_home.path().to_str().unwrap(), config);
1367            let err = build_err(builder).await;
1368
1369            let Error::InvalidObjectStoreWalConfig { field, value, .. } = &err else {
1370                panic!("unexpected error for {expected_field}: {err:?}");
1371            };
1372            assert_eq!(*field, expected_field);
1373            assert_eq!(*value, expected_value);
1374            assert_eq!(StatusCode::InvalidArguments, err.status_code());
1375            // The builder stops before it creates any storage or log store.
1376            assert!(is_empty_dir(data_home.path()));
1377        }
1378    }
1379
1380    #[tokio::test]
1381    async fn test_build_rejects_object_store_wal_with_meta_client() {
1382        let data_home = create_temp_dir("object-store-wal-meta-client");
1383        let meta_client = Arc::new(MetaClientBuilder::datanode_default_options(0).build());
1384
1385        let mut builder = object_store_wal_builder(
1386            data_home.path().to_str().unwrap(),
1387            ObjectStoreWalConfig::default(),
1388        );
1389        builder.with_meta_client(meta_client);
1390        let err = build_err(builder).await;
1391
1392        assert_matches!(err, Error::ObjectStoreWalNotStandalone { .. });
1393        assert_eq!(StatusCode::InvalidArguments, err.status_code());
1394        // The builder stops before it creates any storage or log store.
1395        assert!(is_empty_dir(data_home.path()));
1396    }
1397
1398    #[tokio::test]
1399    async fn test_build_rejects_unknown_wal_storage_provider() {
1400        let data_home = create_temp_dir("object-store-wal-unknown-provider");
1401        let builder = object_store_wal_builder(
1402            data_home.path().to_str().unwrap(),
1403            ObjectStoreWalConfig {
1404                storage_provider: "s3".to_string(),
1405                ..Default::default()
1406            },
1407        );
1408        let err = build_err(builder).await;
1409
1410        let Error::InvalidObjectStoreWalConfig {
1411            field,
1412            value,
1413            reason,
1414            ..
1415        } = &err
1416        else {
1417            panic!("unexpected error: {err:?}");
1418        };
1419        assert_eq!(*field, "storage_provider");
1420        assert_eq!(value, "s3");
1421        assert!(reason.contains(r#"["File"]"#), "{reason}");
1422        assert_eq!(StatusCode::InvalidArguments, err.status_code());
1423        // No log store is created under the prefix.
1424        assert!(!data_home.path().join("wal").exists());
1425    }
1426
1427    #[test]
1428    fn test_wal_object_store_selects_provider() {
1429        fn fs_object_store(dir: &Path) -> ObjectStore {
1430            let builder = object_store::services::Fs::default().root(dir.to_str().unwrap());
1431            ObjectStore::new(builder).unwrap()
1432        }
1433
1434        let default_dir = create_temp_dir("wal-object-store-default");
1435        let named_dir = create_temp_dir("wal-object-store-named");
1436        let mut manager = ObjectStoreManager::new("File", fs_object_store(default_dir.path()));
1437        manager.add("Wal-Store", fs_object_store(named_dir.path()));
1438        let storage = StorageConfig::default();
1439
1440        let root = |store: ObjectStore| store.info().root().clone();
1441        assert_eq!(
1442            root(wal_object_store(&manager, &storage, "").unwrap()),
1443            root(manager.default_object_store().clone())
1444        );
1445        assert_eq!(
1446            root(wal_object_store(&manager, &storage, "wal-store").unwrap()),
1447            root(manager.find("Wal-Store").unwrap().clone())
1448        );
1449        assert_matches!(
1450            wal_object_store(&manager, &storage, "missing").unwrap_err(),
1451            Error::InvalidObjectStoreWalConfig {
1452                field: "storage_provider",
1453                ..
1454            }
1455        );
1456    }
1457
1458    struct CountingRegionQueryHandlerFactory(Arc<AtomicUsize>);
1459
1460    impl RegionQueryHandlerFactory for CountingRegionQueryHandlerFactory {
1461        fn build(
1462            &self,
1463            partition_manager: PartitionRuleManagerRef,
1464            node_manager: NodeManagerRef,
1465        ) -> query::region_query::RegionQueryHandlerRef {
1466            self.0.fetch_add(1, Ordering::SeqCst);
1467            DatanodeRegionQueryHandler::arc(partition_manager, node_manager)
1468        }
1469    }
1470
1471    #[tokio::test]
1472    async fn test_build_uses_installed_region_query_handler_factory() {
1473        let data_home = create_temp_dir("region-query-handler-factory");
1474        let builds = Arc::new(AtomicUsize::new(0));
1475        let plugins = Plugins::new();
1476        plugins.insert(Arc::new(CountingRegionQueryHandlerFactory(builds.clone()))
1477            as RegionQueryHandlerFactoryRef);
1478        let mut builder = datanode_builder(
1479            data_home.path().to_str().unwrap(),
1480            DatanodeWalConfig::default(),
1481            Arc::new(MemoryKvBackend::new()),
1482        );
1483        builder.set_plugins(plugins);
1484
1485        let mut datanode = builder.build().await.unwrap();
1486        assert_eq!(builds.load(Ordering::SeqCst), 1);
1487        datanode.shutdown().await.unwrap();
1488    }
1489
1490    #[tokio::test]
1491    async fn test_build_and_shutdown_with_raft_engine_wal() {
1492        let data_home = create_temp_dir("raft-engine-wal");
1493        let builder = datanode_builder(
1494            data_home.path().to_str().unwrap(),
1495            DatanodeWalConfig::default(),
1496            Arc::new(MemoryKvBackend::new()),
1497        );
1498
1499        let mut datanode = builder.build().await.unwrap();
1500        assert!(datanode.object_store_log_store.is_none());
1501        assert!(data_home.path().join("wal").exists());
1502        datanode.shutdown().await.unwrap();
1503    }
1504
1505    #[tokio::test]
1506    async fn test_build_and_shutdown_with_object_store_wal() {
1507        common_telemetry::init_default_ut_logging();
1508        let data_home = create_temp_dir("object-store-wal");
1509        let builder = object_store_wal_builder(
1510            data_home.path().to_str().unwrap(),
1511            ObjectStoreWalConfig::default(),
1512        );
1513
1514        let mut datanode = builder.build().await.unwrap();
1515        let log_store = datanode.object_store_log_store.clone().unwrap();
1516        assert!(!is_stopped(&log_store).await);
1517        // The store runs under the node prefix derived from the configured root.
1518        assert!(format!("{log_store:?}").contains(r#"prefix: "wal/datanodes/0/epochs/0""#));
1519
1520        datanode.shutdown().await.unwrap();
1521        assert!(is_stopped(&log_store).await);
1522    }
1523
1524    #[tokio::test]
1525    async fn test_build_with_named_wal_storage_provider() {
1526        let data_home = create_temp_dir("object-store-wal-named-provider");
1527        // The default file store is registered under its provider name.
1528        let builder = object_store_wal_builder(
1529            data_home.path().to_str().unwrap(),
1530            ObjectStoreWalConfig {
1531                storage_provider: "file".to_string(),
1532                ..Default::default()
1533            },
1534        );
1535
1536        let mut datanode = builder.build().await.unwrap();
1537        assert!(datanode.object_store_log_store.is_some());
1538        datanode.shutdown().await.unwrap();
1539    }
1540
1541    #[tokio::test]
1542    async fn test_shutdown_stops_every_engine_and_the_log_store_after_engine_failures() {
1543        common_telemetry::init_default_ut_logging();
1544        let data_home = create_temp_dir("object-store-wal-shutdown-failure");
1545        let builder = object_store_wal_builder(
1546            data_home.path().to_str().unwrap(),
1547            ObjectStoreWalConfig::default(),
1548        );
1549        let mut datanode = builder.build().await.unwrap();
1550        let log_store = datanode.object_store_log_store.clone().unwrap();
1551        // Both engines fail to stop, so whichever the region server stops first fails.
1552        let stop_calls = Arc::new(AtomicUsize::new(0));
1553        for name in ["failing-a", "failing-b"] {
1554            let stop_calls = stop_calls.clone();
1555            let (engine, _) = MockRegionEngine::with_custom_apply_fn(name, move |engine| {
1556                engine.handle_stop_mock_fn = Some(Box::new(move || {
1557                    stop_calls.fetch_add(1, Ordering::Relaxed);
1558                    error::UnexpectedSnafu {
1559                        violated: "stop failed",
1560                    }
1561                    .fail()
1562                }));
1563            });
1564            datanode.region_server().register_engine(engine);
1565        }
1566
1567        let err = datanode.shutdown().await.unwrap_err();
1568
1569        assert_matches!(err, Error::StopRegionEngine { .. });
1570        assert_eq!(2, stop_calls.load(Ordering::Relaxed));
1571        assert!(is_stopped(&log_store).await);
1572    }
1573
1574    #[tokio::test]
1575    async fn test_failed_build_stops_object_store_log_store() {
1576        common_telemetry::init_default_ut_logging();
1577        let data_home = create_temp_dir("object-store-wal-failed-build");
1578        let kv_backend = Arc::new(MemoryKvBackend::new());
1579        // Regions registered in the metadata but missing on disk fail the region open
1580        // that runs after the log store and the engines are built.
1581        setup_table_datanode(&(kv_backend.clone() as _)).await;
1582        let mut builder = datanode_builder(
1583            data_home.path().to_str().unwrap(),
1584            DatanodeWalConfig::ObjectStore(ObjectStoreWalConfig::default()),
1585            kv_backend,
1586        );
1587
1588        assert!(builder.try_build().await.is_err());
1589
1590        let log_store = builder.object_store_log_store.as_ref().unwrap();
1591        assert!(is_stopped(log_store).await);
1592    }
1593
1594    const REGION: RegionId = RegionId::new(1024, 1);
1595
1596    fn enqueued_wal_config(flush_interval: Duration) -> ObjectStoreWalConfig {
1597        ObjectStoreWalConfig {
1598            ack_mode: AckMode::Enqueued,
1599            flush_interval,
1600            ..Default::default()
1601        }
1602    }
1603
1604    /// The options that put a region on the WAL of a datanode with `config`.
1605    fn region_wal_options(config: &ObjectStoreWalConfig) -> HashMap<String, String> {
1606        let prefix = config.node_prefix(0, STANDALONE_GENERATION);
1607        let wal_options =
1608            serde_json::to_string(&WalOptions::ObjectStore(ObjectStoreWalOptions::new(prefix)))
1609                .unwrap();
1610        HashMap::from([(WAL_OPTIONS_KEY.to_string(), wal_options)])
1611    }
1612
1613    async fn put_rows(
1614        region_server: &RegionServer,
1615        schema: &[ColumnSchema],
1616        rows: Range<usize>,
1617        skip_wal: bool,
1618    ) {
1619        if rows.is_empty() {
1620            return;
1621        }
1622        region_server
1623            .handle_request(
1624                REGION,
1625                RegionRequest::Put(RegionPutRequest {
1626                    rows: Rows {
1627                        schema: schema.to_vec(),
1628                        rows: build_rows(rows.start, rows.end),
1629                    },
1630                    skip_wal,
1631                    hint: None,
1632                    partition_expr_version: None,
1633                }),
1634            )
1635            .await
1636            .unwrap();
1637    }
1638
1639    /// Builds a datanode on an `enqueued` object store WAL with
1640    /// `flush_interval` and creates [`REGION`] on it with `durable_rows`
1641    /// written through the WAL and uploaded. Then makes every later create
1642    /// of the WAL fail with a transient error if `uploads_fail`, and writes
1643    /// `rows` through the WAL and `skipped_rows` past it. Returns the datanode
1644    /// and its log store.
1645    async fn datanode_with_object_store_wal(
1646        data_home: &str,
1647        flush_interval: Duration,
1648        durable_rows: usize,
1649        uploads_fail: bool,
1650        rows: usize,
1651        skipped_rows: usize,
1652    ) -> (Datanode, Arc<ObjectStoreLogStore>) {
1653        let config = enqueued_wal_config(flush_interval);
1654        let prefix = config.node_prefix(0, STANDALONE_GENERATION);
1655        let mut request = CreateRequestBuilder::new().build();
1656        request.options.extend(region_wal_options(&config));
1657        let datanode = object_store_wal_builder(data_home, config)
1658            .build()
1659            .await
1660            .unwrap();
1661        let log_store = datanode.object_store_log_store.clone().unwrap();
1662        let region_server = datanode.region_server();
1663        let schema = rows_schema(&request);
1664        region_server
1665            .handle_request(REGION, RegionRequest::Create(request))
1666            .await
1667            .unwrap();
1668
1669        put_rows(&region_server, &schema, 0..durable_rows, false).await;
1670        if durable_rows > 0 {
1671            let provider = Provider::object_store_provider(REGION, prefix);
1672            tokio::time::timeout(WAIT, async {
1673                while log_store.latest_entry_id(&provider).unwrap() == 0 {
1674                    tokio::time::sleep(Duration::from_millis(10)).await;
1675                }
1676            })
1677            .await
1678            .expect("the rows must become durable");
1679        }
1680        if uploads_fail {
1681            log_store.fail_creates();
1682        }
1683        let skipped = durable_rows + rows;
1684        put_rows(&region_server, &schema, durable_rows..skipped, false).await;
1685        put_rows(
1686            &region_server,
1687            &schema,
1688            skipped..skipped + skipped_rows,
1689            true,
1690        )
1691        .await;
1692        (datanode, log_store)
1693    }
1694
1695    /// Restarts a datanode on the files of `data_home`, opens [`REGION`] and
1696    /// returns the number of rows it holds after replaying the WAL.
1697    async fn rows_after_restart(data_home: &str) -> u64 {
1698        let config = enqueued_wal_config(ObjectStoreWalConfig::default().flush_interval);
1699        let options = region_wal_options(&config);
1700        let mut datanode = object_store_wal_builder(data_home, config)
1701            .build()
1702            .await
1703            .unwrap();
1704        let region_server = datanode.region_server();
1705        region_server
1706            .handle_request(
1707                REGION,
1708                RegionRequest::Open(RegionOpenRequest {
1709                    engine: MITO_ENGINE_NAME.to_string(),
1710                    table_dir: CreateRequestBuilder::new().build().table_dir,
1711                    path_type: PathType::Bare,
1712                    options,
1713                    skip_wal_replay: false,
1714                    checkpoint: None,
1715                    requirements: Default::default(),
1716                }),
1717            )
1718            .await
1719            .unwrap();
1720        let rows = region_server.region_statistic(REGION).unwrap().num_rows;
1721        datanode.shutdown().await.unwrap();
1722        rows
1723    }
1724
1725    /// Returns the Parquet files under `dir` and its subdirectories.
1726    fn sst_files(dir: &Path) -> Vec<String> {
1727        let mut files = Vec::new();
1728        for entry in std::fs::read_dir(dir).unwrap() {
1729            let path = entry.unwrap().path();
1730            if path.is_dir() {
1731                files.extend(sst_files(&path));
1732            } else if path.extension().is_some_and(|ext| ext == "parquet") {
1733                files.push(path.display().to_string());
1734            }
1735        }
1736        files
1737    }
1738
1739    /// A flush that waits for WAL uploads that keep failing does not hold up
1740    /// the shutdown: the WAL stops first and drops its backlog, which fails
1741    /// the flush with the stopped error before it writes its manifest edit,
1742    /// and the shutdown reports the dropped backlog. After a restart the rows
1743    /// that were durable before the outage replay once.
1744    #[tokio::test]
1745    async fn test_shutdown_while_a_flush_waits_for_failing_wal_uploads() {
1746        common_telemetry::init_default_ut_logging();
1747        let data_home = create_temp_dir("object-store-wal-shutdown-flush");
1748        let data_home = data_home.path().to_str().unwrap();
1749        let (mut datanode, log_store) = datanode_with_object_store_wal(
1750            data_home,
1751            ObjectStoreWalConfig::default().flush_interval,
1752            3,
1753            true,
1754            2,
1755            0,
1756        )
1757        .await;
1758        let flush = {
1759            let region_server = datanode.region_server();
1760            tokio::spawn(async move {
1761                region_server
1762                    .handle_request(REGION, RegionRequest::Flush(Default::default()))
1763                    .await
1764            })
1765        };
1766        tokio::time::timeout(WAIT, log_store.wait_for_durability_waits(1))
1767            .await
1768            .unwrap()
1769            .unwrap();
1770
1771        let err = tokio::time::timeout(WAIT, datanode.shutdown())
1772            .await
1773            .expect("the shutdown must finish")
1774            .unwrap_err();
1775
1776        assert_matches!(err, Error::ShutdownInstance { .. });
1777        assert!(
1778            format!("{err:?}").contains("injected create failure"),
1779            "{err:?}"
1780        );
1781        let flush_err = flush.await.unwrap().unwrap_err();
1782        assert!(
1783            format!("{flush_err:?}").contains("Object store WAL log store is stopped"),
1784            "{flush_err:?}"
1785        );
1786        assert_eq!(Vec::<String>::new(), sst_files(Path::new(data_home)));
1787        assert!(is_stopped(&log_store).await);
1788        drop(datanode);
1789
1790        assert_eq!(3, rows_after_restart(data_home).await);
1791    }
1792
1793    /// A region that holds writes that skipped the WAL is flushed when it
1794    /// closes. When the WAL's uploads keep failing, the WAL stops first and
1795    /// drops its backlog, so that flush fails at once instead of waiting: the
1796    /// close fails, the writes that skipped the WAL are lost, and the shutdown
1797    /// returns the dropped backlog.
1798    #[tokio::test]
1799    async fn test_shutdown_closes_a_region_after_the_wal_dropped_its_backlog() {
1800        common_telemetry::init_default_ut_logging();
1801        let data_home = create_temp_dir("object-store-wal-shutdown-closing-flush");
1802        let (mut datanode, log_store) = datanode_with_object_store_wal(
1803            data_home.path().to_str().unwrap(),
1804            ObjectStoreWalConfig::default().flush_interval,
1805            0,
1806            true,
1807            3,
1808            2,
1809        )
1810        .await;
1811
1812        let err = tokio::time::timeout(WAIT, datanode.shutdown())
1813            .await
1814            .expect("the shutdown must finish")
1815            .unwrap_err();
1816
1817        assert_matches!(err, Error::ShutdownInstance { .. });
1818        assert!(
1819            format!("{err:?}").contains("injected create failure"),
1820            "{err:?}"
1821        );
1822        assert_eq!(Vec::<String>::new(), sst_files(data_home.path()));
1823        assert!(is_stopped(&log_store).await);
1824    }
1825
1826    /// With a healthy WAL whose seal interval is longer than any test, the
1827    /// close-time flush of a region that holds writes that skipped the WAL
1828    /// commits: the WAL's stop seals and uploads its open batch before the
1829    /// region closes, so the flush finds its entries durable, and every row
1830    /// survives a restart.
1831    #[tokio::test]
1832    async fn test_shutdown_flushes_a_region_while_the_wal_is_healthy() {
1833        common_telemetry::init_default_ut_logging();
1834        let data_home = create_temp_dir("object-store-wal-shutdown-healthy-flush");
1835        let data_home = data_home.path().to_str().unwrap();
1836        let (mut datanode, log_store) =
1837            datanode_with_object_store_wal(data_home, Duration::from_secs(3600), 0, false, 3, 2)
1838                .await;
1839
1840        tokio::time::timeout(WAIT, datanode.shutdown())
1841            .await
1842            .expect("the shutdown must finish")
1843            .unwrap();
1844        assert_eq!(1, sst_files(Path::new(data_home)).len());
1845        assert!(is_stopped(&log_store).await);
1846        drop(datanode);
1847
1848        assert_eq!(5, rows_after_restart(data_home).await);
1849    }
1850
1851    #[test]
1852    fn test_node_prefix_passes_validation() {
1853        let config = ObjectStoreWalConfig::default();
1854        let derived = ObjectStoreWalConfig {
1855            prefix: config.node_prefix(0, STANDALONE_GENERATION),
1856            ..config
1857        };
1858        validate_object_store_wal_config(&derived).unwrap();
1859    }
1860}