Skip to main content

cmd/
standalone.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
15use std::fmt::Debug;
16use std::net::SocketAddr;
17use std::path::Path;
18use std::sync::Arc;
19use std::{fs, path};
20
21use async_trait::async_trait;
22use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
23use catalog::CatalogManagerRef;
24use catalog::information_schema::InformationExtensionRef;
25use catalog::kvbackend::{CatalogManagerConfiguratorRef, KvBackendCatalogManagerBuilder};
26use catalog::process_manager::ProcessManager;
27use clap::Parser;
28use common_base::Plugins;
29use common_catalog::consts::{MIN_USER_FLOW_ID, MIN_USER_TABLE_ID};
30use common_config::{Configurable, metadata_store_dir};
31use common_error::ext::BoxedError;
32use common_meta::DatanodeId;
33use common_meta::cache::{LayeredCacheRegistryBuilder, LayeredCacheRegistryRef};
34use common_meta::ddl::flow_meta::FlowMetadataAllocator;
35use common_meta::ddl::table_meta::TableMetadataAllocator;
36use common_meta::ddl::{DdlContext, NoopRegionFailureDetectorControl};
37use common_meta::ddl_manager::{DdlManager, DdlManagerConfiguratorRef, DdlManagerRef};
38use common_meta::key::flow::FlowMetadataManager;
39use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
40use common_meta::kv_backend::KvBackendRef;
41use common_meta::node_manager::{FlownodeRef, NodeManagerRef};
42use common_meta::procedure_executor::{LocalProcedureExecutor, ProcedureExecutorRef};
43use common_meta::region_keeper::MemoryRegionKeeper;
44use common_meta::region_registry::LeaderRegionRegistry;
45use common_meta::sequence::{Sequence, SequenceBuilder};
46use common_meta::wal_provider::{WalProviderRef, build_wal_provider};
47use common_options::plugin_options::StandaloneFlag;
48use common_procedure::ProcedureManagerRef;
49use common_query::prelude::set_default_prefix;
50use common_telemetry::info;
51use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
52use common_time::timezone::set_default_timezone;
53use common_version::{short_version, verbose_version};
54use datanode::config::DatanodeOptions;
55use datanode::datanode::{Datanode, DatanodeBuilder};
56use datanode::region_server::RegionServer;
57use flow::{
58    FlowDualEngineRef, FlownodeBuilder, FlownodeInstance, FlownodeOptions, FrontendClient,
59    FrontendInvoker, GrpcQueryHandlerWithBoxedError,
60};
61use frontend::frontend::Frontend;
62use frontend::instance::StandaloneDatanodeManager;
63use frontend::instance::builder::FrontendBuilder;
64use frontend::server::Services;
65use meta_srv::metasrv::{FLOW_ID_SEQ, TABLE_ID_SEQ};
66use plugins::PluginOptions;
67use plugins::frontend::context::{
68    CatalogManagerConfigureContext, StandaloneCatalogManagerConfigureContext,
69};
70use plugins::standalone::context::DdlManagerConfigureContext;
71use servers::tls::{TlsMode, TlsOption, merge_tls_option};
72use snafu::ResultExt;
73use standalone::options::StandaloneOptions;
74use standalone::{StandaloneInformationExtension, StandaloneRepartitionProcedureFactory};
75use tracing_appender::non_blocking::WorkerGuard;
76
77use crate::error::{OtherSnafu, Result, StartFlownodeSnafu};
78use crate::options::{GlobalOptions, GreptimeOptions};
79use crate::{App, create_resource_limit_metrics, error, log_versions, maybe_activate_heap_profile};
80
81pub const APP_NAME: &str = "greptime-standalone";
82
83#[derive(Parser)]
84pub struct Command {
85    #[clap(subcommand)]
86    subcmd: SubCommand,
87}
88
89impl Command {
90    pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
91        self.subcmd.build(opts).await
92    }
93
94    pub fn load_options(
95        &self,
96        global_options: &GlobalOptions,
97    ) -> Result<GreptimeOptions<StandaloneOptions>> {
98        self.subcmd.load_options(global_options)
99    }
100}
101
102#[derive(Parser)]
103enum SubCommand {
104    Start(StartCommand),
105}
106
107impl SubCommand {
108    async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
109        match self {
110            SubCommand::Start(cmd) => cmd.build(opts).await,
111        }
112    }
113
114    fn load_options(
115        &self,
116        global_options: &GlobalOptions,
117    ) -> Result<GreptimeOptions<StandaloneOptions>> {
118        match self {
119            SubCommand::Start(cmd) => cmd.load_options(global_options),
120        }
121    }
122}
123
124pub struct Instance {
125    datanode: Datanode,
126    frontend: Frontend,
127    flownode: FlownodeInstance,
128    procedure_manager: ProcedureManagerRef,
129    leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
130    leader_services_context: LeaderServicesContext,
131    // Keep the logging guard to prevent the worker from being dropped.
132    _guard: Vec<WorkerGuard>,
133}
134
135impl Instance {
136    /// Find the socket addr of a server by its `name`.
137    pub fn server_addr(&self, name: &str) -> Option<SocketAddr> {
138        self.frontend.server_handlers().addr(name)
139    }
140
141    /// Get the mutable Frontend component of this Standalone instance for externally modification
142    /// by others (might not be in this code base, so don't delete this function).
143    pub fn mut_frontend(&mut self) -> &mut Frontend {
144        &mut self.frontend
145    }
146
147    /// Get the Datanode component of this Standalone instance for externally usage
148    /// by others (might not be in this code base, so don't delete this function).
149    pub fn datanode(&self) -> &Datanode {
150        &self.datanode
151    }
152}
153
154#[async_trait]
155impl App for Instance {
156    fn name(&self) -> &str {
157        APP_NAME
158    }
159
160    async fn start(&mut self) -> Result<()> {
161        self.datanode.start_telemetry();
162
163        self.leader_services_controller
164            .start(self.leader_services_context.clone())
165            .await?;
166
167        plugins::start_frontend_plugins(&self.frontend.instance)
168            .await
169            .context(error::StartFrontendSnafu)?;
170
171        self.frontend
172            .start()
173            .await
174            .context(error::StartFrontendSnafu)?;
175
176        self.flownode.start().await.context(StartFlownodeSnafu)?;
177
178        Ok(())
179    }
180
181    async fn stop(&mut self) -> Result<()> {
182        self.frontend
183            .shutdown()
184            .await
185            .context(error::ShutdownFrontendSnafu)?;
186
187        self.leader_services_controller
188            .stop(
189                self.procedure_manager.clone(),
190                self.datanode.region_server(),
191            )
192            .await?;
193
194        self.datanode
195            .shutdown()
196            .await
197            .context(error::ShutdownDatanodeSnafu)?;
198
199        self.flownode
200            .shutdown()
201            .await
202            .context(error::ShutdownFlownodeSnafu)?;
203
204        info!("Datanode instance stopped.");
205
206        Ok(())
207    }
208}
209
210#[derive(Debug, Default, Parser)]
211pub struct StartCommand {
212    #[clap(long)]
213    http_addr: Option<String>,
214    #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
215    grpc_bind_addr: Option<String>,
216    #[clap(long)]
217    mysql_addr: Option<String>,
218    #[clap(long)]
219    postgres_addr: Option<String>,
220    #[clap(short, long)]
221    influxdb_enable: bool,
222    #[clap(short, long)]
223    pub config_file: Option<String>,
224    #[clap(long)]
225    tls_mode: Option<TlsMode>,
226    #[clap(long)]
227    tls_cert_path: Option<String>,
228    #[clap(long)]
229    tls_key_path: Option<String>,
230    #[clap(long)]
231    tls_watch: bool,
232    #[clap(long)]
233    user_provider: Option<String>,
234    #[clap(long, default_value = "GREPTIMEDB_STANDALONE")]
235    pub env_prefix: String,
236    /// The working home directory of this standalone instance.
237    #[clap(long)]
238    data_home: Option<String>,
239}
240
241impl StartCommand {
242    /// Load the GreptimeDB options from various sources (command line, config file or env).
243    pub fn load_options(
244        &self,
245        global_options: &GlobalOptions,
246    ) -> Result<GreptimeOptions<StandaloneOptions>> {
247        let mut opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
248            self.config_file.as_deref(),
249            self.env_prefix.as_ref(),
250        )
251        .context(error::LoadLayeredConfigSnafu)?;
252
253        self.merge_with_cli_options(global_options, &mut opts.component)?;
254        opts.component.sanitize();
255
256        Ok(opts)
257    }
258
259    // The precedence order is: cli > config file > environment variables > default values.
260    pub fn merge_with_cli_options(
261        &self,
262        global_options: &GlobalOptions,
263        opts: &mut StandaloneOptions,
264    ) -> Result<()> {
265        if let Some(dir) = &global_options.log_dir {
266            opts.logging.dir.clone_from(dir);
267        }
268
269        if global_options.log_level.is_some() {
270            opts.logging.level.clone_from(&global_options.log_level);
271        }
272
273        opts.tracing = TracingOptions {
274            #[cfg(feature = "tokio-console")]
275            tokio_console_addr: global_options.tokio_console_addr.clone(),
276        };
277
278        let tls_opts = TlsOption::new(
279            self.tls_mode,
280            self.tls_cert_path.clone(),
281            self.tls_key_path.clone(),
282            self.tls_watch,
283        );
284
285        if let Some(addr) = &self.http_addr {
286            opts.http.addr.clone_from(addr);
287        }
288
289        if let Some(data_home) = &self.data_home {
290            opts.storage.data_home.clone_from(data_home);
291        }
292
293        // If the logging dir is not set, use the default logs dir in the data home.
294        if opts.logging.dir.is_empty() {
295            opts.logging.dir = Path::new(&opts.storage.data_home)
296                .join(DEFAULT_LOGGING_DIR)
297                .to_string_lossy()
298                .to_string();
299        }
300
301        if let Some(addr) = &self.grpc_bind_addr {
302            // frontend grpc addr conflict with datanode default grpc addr
303            let datanode_grpc_addr = DatanodeOptions::default().grpc.bind_addr;
304            if addr.eq(&datanode_grpc_addr) {
305                return error::IllegalConfigSnafu {
306                    msg: format!(
307                        "gRPC listen address conflicts with datanode reserved gRPC addr: {datanode_grpc_addr}",
308                    ),
309                }.fail();
310            }
311            opts.grpc.bind_addr.clone_from(addr);
312            opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
313        }
314
315        if let Some(addr) = &self.mysql_addr {
316            opts.mysql.enable = true;
317            opts.mysql.addr.clone_from(addr);
318            opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
319        }
320
321        if let Some(addr) = &self.postgres_addr {
322            opts.postgres.enable = true;
323            opts.postgres.addr.clone_from(addr);
324            opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
325        }
326
327        if self.influxdb_enable {
328            opts.influxdb.enable = self.influxdb_enable;
329        }
330
331        if let Some(user_provider) = &self.user_provider {
332            opts.user_provider = Some(user_provider.clone());
333        }
334
335        Ok(())
336    }
337
338    #[allow(unreachable_code)]
339    #[allow(unused_variables)]
340    #[allow(clippy::diverging_sub_expression)]
341    /// Build GreptimeDB instance with the loaded options.
342    pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
343        common_runtime::init_global_runtimes(&opts.runtime);
344
345        let guard = common_telemetry::init_global_logging(
346            APP_NAME,
347            &opts.component.logging,
348            &opts.component.tracing,
349            None,
350            Some(&opts.component.slow_query),
351        );
352
353        log_versions(verbose_version(), short_version(), APP_NAME);
354        maybe_activate_heap_profile(&opts.component.memory);
355        create_resource_limit_metrics(APP_NAME);
356
357        info!("Standalone start command: {:#?}", self);
358        info!("Standalone options: {opts:#?}");
359
360        let (mut instance, _) =
361            Self::build_with(opts.component, opts.plugins, InstanceCreator::default()).await?;
362        instance._guard.extend(guard);
363        Ok(instance)
364    }
365
366    pub async fn build_with(
367        mut opts: StandaloneOptions,
368        plugin_opts: Vec<PluginOptions>,
369        creator: InstanceCreator,
370    ) -> Result<(Instance, InstanceCreatorResult)> {
371        let mut plugins = Plugins::new();
372        plugins.insert(StandaloneFlag);
373        set_default_prefix(opts.default_column_prefix.as_deref())
374            .map_err(BoxedError::new)
375            .context(error::BuildCliSnafu)?;
376
377        opts.grpc.detect_server_addr();
378        let fe_opts = opts.frontend_options();
379        let dn_opts = opts.datanode_options();
380        let node_id = dn_opts.node_id;
381        let init_regions_parallelism = dn_opts.init_regions_parallelism;
382
383        plugins::setup_frontend_plugins_pre_build(&mut plugins, &plugin_opts, &fe_opts, None)
384            .await
385            .context(error::StartFrontendSnafu)?;
386
387        plugins::setup_datanode_plugins_pre_build(&mut plugins, &plugin_opts, &dn_opts)
388            .await
389            .context(error::StartDatanodeSnafu)?;
390
391        set_default_timezone(fe_opts.default_timezone.as_deref())
392            .context(error::InitTimezoneSnafu)?;
393
394        let data_home = &dn_opts.storage.data_home;
395        // Ensure the data_home directory exists.
396        fs::create_dir_all(path::Path::new(data_home))
397            .context(error::CreateDirSnafu { dir: data_home })?;
398
399        let metadata_dir = metadata_store_dir(data_home);
400        let kv_backend = creator
401            .metadata_kv_backend_creator
402            .create(metadata_dir, &opts)
403            .await?;
404        let procedure_manager =
405            standalone::build_procedure_manager(kv_backend.clone(), opts.procedure);
406
407        plugins::setup_standalone_plugins(&mut plugins, &plugin_opts, &opts, kv_backend.clone())
408            .await
409            .context(error::SetupStandalonePluginsSnafu)?;
410
411        // Builds cache registry
412        let layered_cache_builder = LayeredCacheRegistryBuilder::default();
413        let fundamental_cache_registry = build_fundamental_cache_registry(kv_backend.clone());
414        let mut layered_cache_builder = with_default_composite_cache_registry(
415            layered_cache_builder.add_cache_registry(fundamental_cache_registry),
416        )
417        .context(error::BuildCacheRegistrySnafu)?;
418
419        if let Some(plugin_cache_builder) = plugins::standalone::configure_cache_registry(&plugins)
420        {
421            layered_cache_builder =
422                layered_cache_builder.add_cache_registry(plugin_cache_builder.build());
423        }
424
425        let layered_cache_registry = Arc::new(layered_cache_builder.build());
426
427        let mut builder = DatanodeBuilder::new(dn_opts, plugins.clone(), kv_backend.clone());
428        builder.with_cache_registry(layered_cache_registry.clone());
429        if let Some(writable) = creator.open_regions_writable_override {
430            builder.with_open_regions_writable_override(writable);
431        }
432
433        plugins::setup_datanode_plugins_post_build(&mut plugins, &plugin_opts, &builder)
434            .await
435            .context(error::StartDatanodeSnafu)?;
436        builder.set_plugins(plugins.clone());
437
438        let datanode = builder.build().await.context(error::StartDatanodeSnafu)?;
439
440        let information_extension = Arc::new(StandaloneInformationExtension::new(
441            datanode.region_server(),
442            procedure_manager.clone(),
443        ));
444
445        plugins.insert::<InformationExtensionRef>(information_extension.clone());
446
447        let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
448
449        // for standalone not use grpc, but get a handler to frontend grpc client without
450        // actually make a connection
451        let (frontend_client, frontend_instance_handler) =
452            FrontendClient::from_empty_grpc_handler(opts.query.clone());
453        let frontend_client = Arc::new(frontend_client);
454
455        let builder = KvBackendCatalogManagerBuilder::new(
456            information_extension.clone(),
457            kv_backend.clone(),
458            layered_cache_registry.clone(),
459        )
460        .with_procedure_manager(procedure_manager.clone())
461        .with_process_manager(process_manager.clone());
462        let builder = if let Some(configurator) =
463            plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
464        {
465            let ctx = StandaloneCatalogManagerConfigureContext {
466                fe_client: frontend_client.clone(),
467            };
468            let ctx = CatalogManagerConfigureContext::Standalone(ctx);
469            configurator
470                .configure(builder, ctx)
471                .await
472                .context(OtherSnafu)?
473        } else {
474            builder
475        };
476        let catalog_manager = builder.build();
477
478        let table_metadata_manager =
479            Self::create_table_metadata_manager(kv_backend.clone()).await?;
480
481        let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
482        let flownode_options = FlownodeOptions {
483            flow: opts.flow.clone(),
484            ..Default::default()
485        };
486
487        let mut flow_builder = FlownodeBuilder::new(
488            flownode_options,
489            plugins.clone(),
490            table_metadata_manager.clone(),
491            catalog_manager.clone(),
492            flow_metadata_manager.clone(),
493            frontend_client.clone(),
494        );
495
496        plugins::setup_flownode_plugins_post_build(&mut plugins, &plugin_opts, &flow_builder)
497            .await
498            .context(error::StartFlownodeSnafu)?;
499        flow_builder.set_plugins(plugins.clone());
500
501        let flownode = flow_builder
502            .build()
503            .await
504            .map_err(BoxedError::new)
505            .context(error::OtherSnafu)?;
506        let flow_engine = flownode.flow_engine();
507
508        // set the ref to query for the local flow state
509        {
510            information_extension
511                .set_flow_engine(flow_engine.clone())
512                .await;
513        }
514
515        let node_manager = creator
516            .node_manager_creator
517            .create(&kv_backend, datanode.region_server(), flow_engine.clone())
518            .await?;
519
520        let table_id_allocator = creator.table_id_allocator_creator.create(&kv_backend);
521        let flow_id_sequence = Arc::new(
522            SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone())
523                .initial(MIN_USER_FLOW_ID as u64)
524                .step(10)
525                .build(),
526        );
527        let kafka_options = opts
528            .wal
529            .clone()
530            .try_into()
531            .context(error::InvalidWalProviderSnafu)?;
532        let wal_provider = build_wal_provider(&kafka_options, kv_backend.clone())
533            .await
534            .context(error::BuildWalProviderSnafu)?;
535        let wal_provider = Arc::new(wal_provider);
536        let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
537            table_id_allocator.clone(),
538            wal_provider.clone(),
539        ));
540        let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
541            flow_id_sequence,
542        ));
543
544        let ddl_context = DdlContext {
545            node_manager: node_manager.clone(),
546            cache_invalidator: layered_cache_registry.clone(),
547            memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
548            leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
549            table_metadata_manager: table_metadata_manager.clone(),
550            table_metadata_allocator: table_metadata_allocator.clone(),
551            flow_metadata_manager: flow_metadata_manager.clone(),
552            flow_metadata_allocator: flow_metadata_allocator.clone(),
553            region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
554            soft_drop_enabled: false,
555        };
556
557        let ddl_manager = DdlManager::try_new(
558            ddl_context,
559            procedure_manager.clone(),
560            Arc::new(StandaloneRepartitionProcedureFactory),
561            true,
562        )
563        .context(error::InitDdlManagerSnafu)?;
564
565        let ddl_manager = if let Some(configurator) =
566            plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
567        {
568            let ctx = DdlManagerConfigureContext {
569                kv_backend: kv_backend.clone(),
570                fe_client: frontend_client.clone(),
571                catalog_manager: catalog_manager.clone(),
572            };
573            configurator
574                .configure(ddl_manager, ctx)
575                .await
576                .context(OtherSnafu)?
577        } else {
578            ddl_manager
579        };
580
581        let procedure_executor = creator
582            .procedure_executor_creator
583            .create(Arc::new(ddl_manager), procedure_manager.clone())
584            .await?;
585
586        let fe_instance = FrontendBuilder::new(
587            fe_opts.clone(),
588            kv_backend.clone(),
589            layered_cache_registry.clone(),
590            catalog_manager.clone(),
591            node_manager.clone(),
592            procedure_executor.clone(),
593            process_manager,
594        );
595
596        plugins::setup_frontend_plugins_post_build(&mut plugins, &plugin_opts, &fe_instance)
597            .await
598            .context(error::StartFrontendSnafu)?;
599
600        let fe_instance = fe_instance
601            .with_plugin(plugins.clone())
602            .try_build()
603            .await
604            .context(error::StartFrontendSnafu)?;
605        let fe_instance = Arc::new(fe_instance);
606
607        // set the frontend client for flownode
608        let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
609        let weak_grpc_handler = Arc::downgrade(&grpc_handler);
610        frontend_instance_handler
611            .set_handler(weak_grpc_handler)
612            .await;
613
614        // set the frontend invoker for flownode
615        let flow_streaming_engine = flow_engine.streaming_engine();
616        // flow server need to be able to use frontend to write insert requests back
617        let invoker = FrontendInvoker::build_from(
618            flow_streaming_engine.clone(),
619            catalog_manager.clone(),
620            kv_backend.clone(),
621            layered_cache_registry.clone(),
622            procedure_executor,
623            node_manager.clone(),
624            fe_instance.frontend_peer_addr().to_string(),
625        )
626        .await
627        .context(StartFlownodeSnafu)?;
628        flow_streaming_engine.set_frontend_invoker(invoker).await;
629
630        let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
631            .build()
632            .context(error::StartFrontendSnafu)?;
633
634        let frontend = Frontend {
635            instance: fe_instance,
636            servers,
637            heartbeat_task: None,
638        };
639        let leader_services_context = LeaderServicesContext {
640            procedure_manager: procedure_manager.clone(),
641            wal_provider: wal_provider.clone(),
642            region_server: datanode.region_server(),
643            kv_backend: kv_backend.clone(),
644            cache_registry: layered_cache_registry,
645            catalog_manager,
646            flow_engine,
647            frontend_client,
648            node_id,
649            init_regions_parallelism,
650            plugin_options: plugin_opts,
651        };
652
653        let instance = Instance {
654            datanode,
655            frontend,
656            flownode,
657            procedure_manager,
658            leader_services_controller: creator.leader_services_controller,
659            leader_services_context,
660            _guard: vec![],
661        };
662        let result = InstanceCreatorResult {
663            kv_backend,
664            node_manager,
665            table_id_allocator,
666        };
667        Ok((instance, result))
668    }
669
670    pub async fn create_table_metadata_manager(
671        kv_backend: KvBackendRef,
672    ) -> Result<TableMetadataManagerRef> {
673        let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
674
675        table_metadata_manager
676            .init()
677            .await
678            .context(error::InitMetadataSnafu)?;
679
680        Ok(table_metadata_manager)
681    }
682}
683
684#[async_trait]
685pub trait NodeManagerCreator: Send + Sync {
686    async fn create(
687        &self,
688        kv_backend: &KvBackendRef,
689        region_server: RegionServer,
690        flow_server: FlownodeRef,
691    ) -> Result<NodeManagerRef>;
692}
693
694pub struct DefaultNodeManagerCreator;
695
696#[async_trait]
697impl NodeManagerCreator for DefaultNodeManagerCreator {
698    async fn create(
699        &self,
700        _: &KvBackendRef,
701        region_server: RegionServer,
702        flow_server: FlownodeRef,
703    ) -> Result<NodeManagerRef> {
704        Ok(Arc::new(StandaloneDatanodeManager {
705            region_server,
706            flow_server,
707        }))
708    }
709}
710
711/// Customizes how standalone opens its metadata KV backend.
712///
713/// The default implementation preserves the built-in raft-engine path. Other
714/// callers can provide a custom implementation without changing standalone
715/// configuration types.
716#[async_trait]
717pub trait MetadataKvBackendCreator: Send + Sync {
718    async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef>;
719}
720
721pub struct DefaultMetadataKvBackendCreator;
722
723#[async_trait]
724impl MetadataKvBackendCreator for DefaultMetadataKvBackendCreator {
725    async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef> {
726        standalone::build_metadata_kvbackend(metadata_dir, opts.metadata_store)
727            .context(error::BuildMetadataKvbackendSnafu)
728    }
729}
730
731pub trait TableIdAllocatorCreator: Send + Sync {
732    fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
733}
734
735struct DefaultTableIdAllocatorCreator;
736
737impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
738    fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
739        Arc::new(
740            SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
741                .initial(MIN_USER_TABLE_ID as u64)
742                .step(10)
743                .build(),
744        )
745    }
746}
747
748#[async_trait]
749pub trait ProcedureExecutorCreator: Send + Sync {
750    async fn create(
751        &self,
752        ddl_manager: DdlManagerRef,
753        procedure_manager: ProcedureManagerRef,
754    ) -> Result<ProcedureExecutorRef>;
755}
756
757pub struct DefaultProcedureExecutorCreator;
758
759#[async_trait]
760impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
761    async fn create(
762        &self,
763        ddl_manager: DdlManagerRef,
764        procedure_manager: ProcedureManagerRef,
765    ) -> Result<ProcedureExecutorRef> {
766        Ok(Arc::new(LocalProcedureExecutor::new(
767            ddl_manager,
768            procedure_manager,
769        )))
770    }
771}
772
773#[async_trait]
774pub trait StandaloneLeaderServicesController: Send + Sync {
775    /// Starts leader services that manage standalone metadata or WAL state.
776    ///
777    /// The default implementation starts the procedure manager and WAL provider
778    /// during instance startup.
779    async fn start(&self, context: LeaderServicesContext) -> Result<()>;
780
781    /// Stops services started by [`StandaloneLeaderServicesController::start`].
782    async fn stop(
783        &self,
784        procedure_manager: ProcedureManagerRef,
785        region_server: RegionServer,
786    ) -> Result<()>;
787}
788
789#[derive(Clone)]
790/// Additional runtime handles for custom leader-service controllers.
791///
792/// The default standalone startup only needs to start/stop the procedure
793/// manager and WAL provider. Some embedders need to do more work around
794/// leader-service startup, for example reconciling metadata-backed runtime
795/// state before publishing writable leadership. Grouping those handles here
796/// keeps `Instance` small and avoids expanding
797/// [`StandaloneLeaderServicesController::start`] every time a custom lifecycle
798/// needs one more standalone component.
799pub struct LeaderServicesContext {
800    pub procedure_manager: ProcedureManagerRef,
801    pub wal_provider: WalProviderRef,
802    pub region_server: RegionServer,
803    pub kv_backend: KvBackendRef,
804    pub cache_registry: LayeredCacheRegistryRef,
805    pub catalog_manager: CatalogManagerRef,
806    pub flow_engine: FlowDualEngineRef,
807    pub frontend_client: Arc<FrontendClient>,
808    pub node_id: Option<DatanodeId>,
809    pub init_regions_parallelism: usize,
810    pub plugin_options: Vec<PluginOptions>,
811}
812
813pub struct DefaultStandaloneLeaderServicesController;
814
815#[async_trait]
816impl StandaloneLeaderServicesController for DefaultStandaloneLeaderServicesController {
817    async fn start(&self, context: LeaderServicesContext) -> Result<()> {
818        context
819            .procedure_manager
820            .start()
821            .await
822            .context(error::StartProcedureManagerSnafu)?;
823        context
824            .wal_provider
825            .start()
826            .await
827            .context(error::StartWalProviderSnafu)
828    }
829
830    async fn stop(
831        &self,
832        procedure_manager: ProcedureManagerRef,
833        _region_server: RegionServer,
834    ) -> Result<()> {
835        procedure_manager
836            .stop()
837            .await
838            .context(error::StopProcedureManagerSnafu)
839    }
840}
841
842/// `InstanceCreator` is used for grouping various component creators for building the
843/// Standalone instance, suitable for customizing how the instance can be built.
844pub struct InstanceCreator {
845    /// Hook for replacing metadata KV construction while reusing the rest of the
846    /// standalone build flow.
847    metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
848    node_manager_creator: Box<dyn NodeManagerCreator>,
849    table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
850    procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
851    leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
852    open_regions_writable_override: Option<bool>,
853}
854
855impl InstanceCreator {
856    pub fn new(
857        node_manager_creator: Box<dyn NodeManagerCreator>,
858        table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
859        procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
860    ) -> Self {
861        Self {
862            metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
863            node_manager_creator,
864            table_id_allocator_creator,
865            procedure_executor_creator,
866            leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
867            open_regions_writable_override: None,
868        }
869    }
870
871    pub fn with_metadata_kv_backend_creator(
872        mut self,
873        metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
874    ) -> Self {
875        self.metadata_kv_backend_creator = metadata_kv_backend_creator;
876        self
877    }
878
879    /// Wraps the metadata backend creator while retaining the default creator.
880    ///
881    /// This is useful for callers that need to add runtime behavior around
882    /// metadata access without reimplementing backend selection.
883    pub fn map_metadata_kv_backend_creator<F>(mut self, f: F) -> Self
884    where
885        F: FnOnce(Box<dyn MetadataKvBackendCreator>) -> Box<dyn MetadataKvBackendCreator>,
886    {
887        self.metadata_kv_backend_creator = f(self.metadata_kv_backend_creator);
888        self
889    }
890
891    /// Wraps node-manager creation while preserving the selected standalone node manager.
892    pub fn map_node_manager_creator<F>(mut self, f: F) -> Self
893    where
894        F: FnOnce(Box<dyn NodeManagerCreator>) -> Box<dyn NodeManagerCreator>,
895    {
896        self.node_manager_creator = f(self.node_manager_creator);
897        self
898    }
899
900    /// Wraps procedure-executor creation while preserving the current setup.
901    pub fn map_procedure_executor_creator<F>(mut self, f: F) -> Self
902    where
903        F: FnOnce(Box<dyn ProcedureExecutorCreator>) -> Box<dyn ProcedureExecutorCreator>,
904    {
905        self.procedure_executor_creator = f(self.procedure_executor_creator);
906        self
907    }
908
909    /// Replaces startup/shutdown ownership for procedure manager and WAL provider.
910    pub fn with_leader_services_controller(
911        mut self,
912        leader_services_controller: Box<dyn StandaloneLeaderServicesController>,
913    ) -> Self {
914        self.leader_services_controller = leader_services_controller;
915        self
916    }
917
918    /// Overrides whether regions opened during startup should become writable.
919    ///
920    /// `None` keeps the default startup behavior (regions open writable).
921    ///
922    /// Warning: setting this to `false` in standalone mode will leave reopened regions
923    /// permanently read-only. Standalone has no metasrv heartbeat or region-role
924    /// reconciliation, so there is no path to promote regions to Leader after startup.
925    pub fn with_open_regions_writable_override(mut self, writable: bool) -> Self {
926        self.open_regions_writable_override = Some(writable);
927        self
928    }
929}
930
931impl Default for InstanceCreator {
932    fn default() -> Self {
933        Self {
934            metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
935            node_manager_creator: Box::new(DefaultNodeManagerCreator),
936            table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
937            procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
938            leader_services_controller: Box::new(DefaultStandaloneLeaderServicesController),
939            open_regions_writable_override: None,
940        }
941    }
942}
943
944/// `InstanceCreatorResult` is expected to be used paired with [InstanceCreator].
945/// It stores the created and other important components for further reusing.
946pub struct InstanceCreatorResult {
947    pub kv_backend: KvBackendRef,
948    pub node_manager: NodeManagerRef,
949    pub table_id_allocator: Arc<Sequence>,
950}
951
952#[cfg(test)]
953mod tests {
954    use std::default::Default;
955    use std::io::Write;
956    use std::time::Duration;
957
958    use auth::{Identity, Password, UserProviderRef};
959    use clap::{CommandFactory, Parser};
960    use common_base::readable_size::ReadableSize;
961    use common_config::ENV_VAR_SEP;
962    use common_options::plugin_options::StandaloneFlag;
963    use common_test_util::temp_dir::create_named_temp_file;
964    use common_wal::config::DatanodeWalConfig;
965    use frontend::frontend::FrontendOptions;
966    use object_store::config::{FileConfig, GcsConfig};
967    use servers::grpc::GrpcOptions;
968
969    use super::*;
970    use crate::options::GlobalOptions;
971
972    #[tokio::test]
973    async fn test_try_from_start_command_to_anymap() {
974        let fe_opts = FrontendOptions {
975            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
976            ..Default::default()
977        };
978
979        let mut plugins = Plugins::new();
980        plugins.insert(StandaloneFlag);
981        plugins::setup_frontend_plugins_pre_build(&mut plugins, &[], &fe_opts, None)
982            .await
983            .unwrap();
984
985        let provider = plugins.get::<UserProviderRef>().unwrap();
986        let result = provider
987            .authenticate(
988                Identity::UserId("test", None),
989                Password::PlainText("test".to_string().into()),
990            )
991            .await;
992        let _ = result.unwrap();
993    }
994
995    #[test]
996    fn test_toml() {
997        let opts = StandaloneOptions::default();
998        let toml_string = toml::to_string(&opts).unwrap();
999        let _parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
1000    }
1001
1002    #[test]
1003    fn test_read_from_config_file() {
1004        let mut file = create_named_temp_file();
1005        let toml_str = r#"
1006            enable_memory_catalog = true
1007
1008            [wal]
1009            provider = "raft_engine"
1010            dir = "./greptimedb_data/test/wal"
1011            file_size = "1GB"
1012            purge_threshold = "50GB"
1013            purge_interval = "10m"
1014            read_batch_size = 128
1015            sync_write = false
1016
1017            [storage]
1018            data_home = "./greptimedb_data/"
1019            type = "File"
1020
1021            [[storage.providers]]
1022            type = "Gcs"
1023            bucket = "foo"
1024            endpoint = "bar"
1025
1026            [[storage.providers]]
1027            type = "S3"
1028            access_key_id = "access_key_id"
1029            secret_access_key = "secret_access_key"
1030
1031            [storage.compaction]
1032            max_inflight_tasks = 3
1033            max_files_in_level0 = 7
1034            max_purge_tasks = 32
1035
1036            [storage.manifest]
1037            checkpoint_margin = 9
1038            gc_duration = '7s'
1039
1040            [http]
1041            addr = "127.0.0.1:4000"
1042            timeout = "33s"
1043            body_limit = "128MB"
1044
1045            [opentsdb]
1046            enable = true
1047
1048            [logging]
1049            level = "debug"
1050            dir = "./greptimedb_data/test/logs"
1051        "#;
1052        write!(file, "{}", toml_str).unwrap();
1053        let cmd = StartCommand {
1054            config_file: Some(file.path().to_str().unwrap().to_string()),
1055            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1056            ..Default::default()
1057        };
1058
1059        let options = cmd
1060            .load_options(&GlobalOptions::default())
1061            .unwrap()
1062            .component;
1063        let fe_opts = options.frontend_options();
1064        let dn_opts = options.datanode_options();
1065        let logging_opts = options.logging;
1066        assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
1067        assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
1068        assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
1069        assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
1070        assert!(fe_opts.mysql.enable);
1071        assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
1072        assert_eq!(2, fe_opts.mysql.runtime_size);
1073        assert_eq!(None, fe_opts.mysql.reject_no_database);
1074        assert!(fe_opts.influxdb.enable);
1075        assert!(fe_opts.opentsdb.enable);
1076
1077        let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
1078            unreachable!()
1079        };
1080        assert_eq!(
1081            "./greptimedb_data/test/wal",
1082            raft_engine_config.dir.unwrap()
1083        );
1084
1085        assert!(matches!(
1086            &dn_opts.storage.store,
1087            object_store::config::ObjectStoreConfig::File(FileConfig { .. })
1088        ));
1089        assert_eq!(dn_opts.storage.providers.len(), 2);
1090        assert!(matches!(
1091            dn_opts.storage.providers[0],
1092            object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
1093        ));
1094        match &dn_opts.storage.providers[1] {
1095            object_store::config::ObjectStoreConfig::S3(s3_config) => {
1096                assert_eq!(
1097                    "SecretBox<alloc::string::String>([REDACTED])".to_string(),
1098                    format!("{:?}", s3_config.connection.access_key_id)
1099                );
1100            }
1101            _ => {
1102                unreachable!()
1103            }
1104        }
1105
1106        assert_eq!("debug", logging_opts.level.as_ref().unwrap());
1107        assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
1108    }
1109
1110    #[test]
1111    fn test_load_log_options_from_cli() {
1112        let cmd = StartCommand {
1113            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
1114            mysql_addr: Some("127.0.0.1:4002".to_string()),
1115            postgres_addr: Some("127.0.0.1:4003".to_string()),
1116            ..Default::default()
1117        };
1118
1119        let opts = cmd
1120            .load_options(&GlobalOptions {
1121                log_dir: Some("./greptimedb_data/test/logs".to_string()),
1122                log_level: Some("debug".to_string()),
1123
1124                #[cfg(feature = "tokio-console")]
1125                tokio_console_addr: None,
1126            })
1127            .unwrap()
1128            .component;
1129
1130        assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
1131        assert_eq!("debug", opts.logging.level.unwrap());
1132    }
1133
1134    #[test]
1135    fn test_config_precedence_order() {
1136        let mut file = create_named_temp_file();
1137        let toml_str = r#"
1138            [http]
1139            addr = "127.0.0.1:4000"
1140
1141            [logging]
1142            level = "debug"
1143        "#;
1144        write!(file, "{}", toml_str).unwrap();
1145
1146        let env_prefix = "STANDALONE_UT";
1147        temp_env::with_vars(
1148            [
1149                (
1150                    // logging.dir = /other/log/dir
1151                    [
1152                        env_prefix.to_string(),
1153                        "logging".to_uppercase(),
1154                        "dir".to_uppercase(),
1155                    ]
1156                    .join(ENV_VAR_SEP),
1157                    Some("/other/log/dir"),
1158                ),
1159                (
1160                    // logging.level = info
1161                    [
1162                        env_prefix.to_string(),
1163                        "logging".to_uppercase(),
1164                        "level".to_uppercase(),
1165                    ]
1166                    .join(ENV_VAR_SEP),
1167                    Some("info"),
1168                ),
1169                (
1170                    // http.addr = 127.0.0.1:24000
1171                    [
1172                        env_prefix.to_string(),
1173                        "http".to_uppercase(),
1174                        "addr".to_uppercase(),
1175                    ]
1176                    .join(ENV_VAR_SEP),
1177                    Some("127.0.0.1:24000"),
1178                ),
1179            ],
1180            || {
1181                let command = StartCommand {
1182                    config_file: Some(file.path().to_str().unwrap().to_string()),
1183                    http_addr: Some("127.0.0.1:14000".to_string()),
1184                    env_prefix: env_prefix.to_string(),
1185                    ..Default::default()
1186                };
1187
1188                let opts = command.load_options(&Default::default()).unwrap().component;
1189
1190                // Should be read from env, env > default values.
1191                assert_eq!(opts.logging.dir, "/other/log/dir");
1192
1193                // Should be read from config file, config file > env > default values.
1194                assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
1195
1196                // Should be read from cli, cli > config file > env > default values.
1197                let fe_opts = opts.frontend_options();
1198                assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
1199                assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
1200
1201                // Should be default value.
1202                assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
1203            },
1204        );
1205    }
1206
1207    #[test]
1208    fn test_parse_grpc_bind_addr_aliases() {
1209        let command =
1210            StartCommand::try_parse_from(["standalone", "--grpc-bind-addr", "127.0.0.1:14001"])
1211                .unwrap();
1212        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
1213
1214        let command =
1215            StartCommand::try_parse_from(["standalone", "--rpc-bind-addr", "127.0.0.1:24001"])
1216                .unwrap();
1217        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1218
1219        let command =
1220            StartCommand::try_parse_from(["standalone", "--rpc-addr", "127.0.0.1:34001"]).unwrap();
1221        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1222    }
1223
1224    #[test]
1225    fn test_help_uses_grpc_option_names() {
1226        let mut cmd = StartCommand::command();
1227        let mut help = Vec::new();
1228        cmd.write_long_help(&mut help).unwrap();
1229        let help = String::from_utf8(help).unwrap();
1230
1231        assert!(help.contains("--grpc-bind-addr"));
1232        assert!(!help.contains("--rpc-bind-addr"));
1233        assert!(!help.contains("--rpc-addr"));
1234    }
1235
1236    #[test]
1237    fn test_load_default_standalone_options() {
1238        let options =
1239            StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1240        let default_options = StandaloneOptions::default();
1241        assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1242        assert_eq!(options.http, default_options.http);
1243        assert_eq!(options.grpc, default_options.grpc);
1244        assert_eq!(options.mysql, default_options.mysql);
1245        assert_eq!(options.postgres, default_options.postgres);
1246        assert_eq!(options.opentsdb, default_options.opentsdb);
1247        assert_eq!(options.influxdb, default_options.influxdb);
1248        assert_eq!(options.prom_store, default_options.prom_store);
1249        assert_eq!(options.wal, default_options.wal);
1250        assert_eq!(options.metadata_store, default_options.metadata_store);
1251        assert_eq!(options.procedure, default_options.procedure);
1252        assert_eq!(options.logging, default_options.logging);
1253        assert_eq!(options.region_engine, default_options.region_engine);
1254    }
1255
1256    #[test]
1257    fn test_cache_config() {
1258        let toml_str = r#"
1259            [storage]
1260            data_home = "test_data_home"
1261            type = "S3"
1262            [storage.cache_config]
1263            enable_read_cache = true
1264        "#;
1265        let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1266        opts.sanitize();
1267        assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1268        assert_eq!(
1269            opts.storage.store.cache_config().unwrap().cache_path,
1270            "test_data_home"
1271        );
1272    }
1273}