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