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    // Keep the logging guard to prevent the worker from being dropped.
129    _guard: Vec<WorkerGuard>,
130}
131
132impl Instance {
133    /// Find the socket addr of a server by its `name`.
134    pub fn server_addr(&self, name: &str) -> Option<SocketAddr> {
135        self.frontend.server_handlers().addr(name)
136    }
137
138    /// Get the mutable Frontend component of this Standalone instance for externally modification
139    /// by others (might not be in this code base, so don't delete this function).
140    pub fn mut_frontend(&mut self) -> &mut Frontend {
141        &mut self.frontend
142    }
143
144    /// Get the Datanode component of this Standalone instance for externally usage
145    /// by others (might not be in this code base, so don't delete this function).
146    pub fn datanode(&self) -> &Datanode {
147        &self.datanode
148    }
149}
150
151#[async_trait]
152impl App for Instance {
153    fn name(&self) -> &str {
154        APP_NAME
155    }
156
157    async fn start(&mut self) -> Result<()> {
158        self.datanode.start_telemetry();
159
160        self.procedure_manager
161            .start()
162            .await
163            .context(error::StartProcedureManagerSnafu)?;
164
165        self.wal_provider
166            .start()
167            .await
168            .context(error::StartWalProviderSnafu)?;
169
170        plugins::start_frontend_plugins(self.frontend.instance.plugins().clone())
171            .await
172            .context(error::StartFrontendSnafu)?;
173
174        self.frontend
175            .start()
176            .await
177            .context(error::StartFrontendSnafu)?;
178
179        self.flownode.start().await.context(StartFlownodeSnafu)?;
180
181        Ok(())
182    }
183
184    async fn stop(&mut self) -> Result<()> {
185        self.frontend
186            .shutdown()
187            .await
188            .context(error::ShutdownFrontendSnafu)?;
189
190        self.procedure_manager
191            .stop()
192            .await
193            .context(error::StopProcedureManagerSnafu)?;
194
195        self.datanode
196            .shutdown()
197            .await
198            .context(error::ShutdownDatanodeSnafu)?;
199
200        self.flownode
201            .shutdown()
202            .await
203            .context(error::ShutdownFlownodeSnafu)?;
204
205        info!("Datanode instance stopped.");
206
207        Ok(())
208    }
209}
210
211#[derive(Debug, Default, Parser)]
212pub struct StartCommand {
213    #[clap(long)]
214    http_addr: Option<String>,
215    #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
216    grpc_bind_addr: Option<String>,
217    #[clap(long)]
218    mysql_addr: Option<String>,
219    #[clap(long)]
220    postgres_addr: Option<String>,
221    #[clap(short, long)]
222    influxdb_enable: bool,
223    #[clap(short, long)]
224    pub config_file: Option<String>,
225    #[clap(long)]
226    tls_mode: Option<TlsMode>,
227    #[clap(long)]
228    tls_cert_path: Option<String>,
229    #[clap(long)]
230    tls_key_path: Option<String>,
231    #[clap(long)]
232    tls_watch: bool,
233    #[clap(long)]
234    user_provider: Option<String>,
235    #[clap(long, default_value = "GREPTIMEDB_STANDALONE")]
236    pub env_prefix: String,
237    /// The working home directory of this standalone instance.
238    #[clap(long)]
239    data_home: Option<String>,
240}
241
242impl StartCommand {
243    /// Load the GreptimeDB options from various sources (command line, config file or env).
244    pub fn load_options(
245        &self,
246        global_options: &GlobalOptions,
247    ) -> Result<GreptimeOptions<StandaloneOptions>> {
248        let mut opts = GreptimeOptions::<StandaloneOptions>::load_layered_options(
249            self.config_file.as_deref(),
250            self.env_prefix.as_ref(),
251        )
252        .context(error::LoadLayeredConfigSnafu)?;
253
254        self.merge_with_cli_options(global_options, &mut opts.component)?;
255        opts.component.sanitize();
256
257        Ok(opts)
258    }
259
260    // The precedence order is: cli > config file > environment variables > default values.
261    pub fn merge_with_cli_options(
262        &self,
263        global_options: &GlobalOptions,
264        opts: &mut StandaloneOptions,
265    ) -> Result<()> {
266        if let Some(dir) = &global_options.log_dir {
267            opts.logging.dir.clone_from(dir);
268        }
269
270        if global_options.log_level.is_some() {
271            opts.logging.level.clone_from(&global_options.log_level);
272        }
273
274        opts.tracing = TracingOptions {
275            #[cfg(feature = "tokio-console")]
276            tokio_console_addr: global_options.tokio_console_addr.clone(),
277        };
278
279        let tls_opts = TlsOption::new(
280            self.tls_mode,
281            self.tls_cert_path.clone(),
282            self.tls_key_path.clone(),
283            self.tls_watch,
284        );
285
286        if let Some(addr) = &self.http_addr {
287            opts.http.addr.clone_from(addr);
288        }
289
290        if let Some(data_home) = &self.data_home {
291            opts.storage.data_home.clone_from(data_home);
292        }
293
294        // If the logging dir is not set, use the default logs dir in the data home.
295        if opts.logging.dir.is_empty() {
296            opts.logging.dir = Path::new(&opts.storage.data_home)
297                .join(DEFAULT_LOGGING_DIR)
298                .to_string_lossy()
299                .to_string();
300        }
301
302        if let Some(addr) = &self.grpc_bind_addr {
303            // frontend grpc addr conflict with datanode default grpc addr
304            let datanode_grpc_addr = DatanodeOptions::default().grpc.bind_addr;
305            if addr.eq(&datanode_grpc_addr) {
306                return error::IllegalConfigSnafu {
307                    msg: format!(
308                        "gRPC listen address conflicts with datanode reserved gRPC addr: {datanode_grpc_addr}",
309                    ),
310                }.fail();
311            }
312            opts.grpc.bind_addr.clone_from(addr);
313            opts.grpc.tls = merge_tls_option(&opts.grpc.tls, tls_opts.clone());
314        }
315
316        if let Some(addr) = &self.mysql_addr {
317            opts.mysql.enable = true;
318            opts.mysql.addr.clone_from(addr);
319            opts.mysql.tls = merge_tls_option(&opts.mysql.tls, tls_opts.clone());
320        }
321
322        if let Some(addr) = &self.postgres_addr {
323            opts.postgres.enable = true;
324            opts.postgres.addr.clone_from(addr);
325            opts.postgres.tls = merge_tls_option(&opts.postgres.tls, tls_opts.clone());
326        }
327
328        if self.influxdb_enable {
329            opts.influxdb.enable = self.influxdb_enable;
330        }
331
332        if let Some(user_provider) = &self.user_provider {
333            opts.user_provider = Some(user_provider.clone());
334        }
335
336        Ok(())
337    }
338
339    #[allow(unreachable_code)]
340    #[allow(unused_variables)]
341    #[allow(clippy::diverging_sub_expression)]
342    /// Build GreptimeDB instance with the loaded options.
343    pub async fn build(&self, opts: GreptimeOptions<StandaloneOptions>) -> Result<Instance> {
344        common_runtime::init_global_runtimes(&opts.runtime);
345
346        let guard = common_telemetry::init_global_logging(
347            APP_NAME,
348            &opts.component.logging,
349            &opts.component.tracing,
350            None,
351            Some(&opts.component.slow_query),
352        );
353
354        log_versions(verbose_version(), short_version(), APP_NAME);
355        maybe_activate_heap_profile(&opts.component.memory);
356        create_resource_limit_metrics(APP_NAME);
357
358        info!("Standalone start command: {:#?}", self);
359        info!("Standalone options: {opts:#?}");
360
361        let (mut instance, _) =
362            Self::build_with(opts.component, opts.plugins, InstanceCreator::default()).await?;
363        instance._guard.extend(guard);
364        Ok(instance)
365    }
366
367    pub async fn build_with(
368        mut opts: StandaloneOptions,
369        plugin_opts: Vec<PluginOptions>,
370        creator: InstanceCreator,
371    ) -> Result<(Instance, InstanceCreatorResult)> {
372        let mut plugins = Plugins::new();
373        plugins.insert(StandaloneFlag);
374        set_default_prefix(opts.default_column_prefix.as_deref())
375            .map_err(BoxedError::new)
376            .context(error::BuildCliSnafu)?;
377
378        opts.grpc.detect_server_addr();
379        let fe_opts = opts.frontend_options();
380        let dn_opts = opts.datanode_options();
381
382        plugins::setup_frontend_plugins(&mut plugins, &plugin_opts, &fe_opts)
383            .await
384            .context(error::StartFrontendSnafu)?;
385
386        plugins::setup_datanode_plugins(&mut plugins, &plugin_opts, &dn_opts)
387            .await
388            .context(error::StartDatanodeSnafu)?;
389
390        set_default_timezone(fe_opts.default_timezone.as_deref())
391            .context(error::InitTimezoneSnafu)?;
392
393        let data_home = &dn_opts.storage.data_home;
394        // Ensure the data_home directory exists.
395        fs::create_dir_all(path::Path::new(data_home))
396            .context(error::CreateDirSnafu { dir: data_home })?;
397
398        let metadata_dir = metadata_store_dir(data_home);
399        let kv_backend = creator
400            .metadata_kv_backend_creator
401            .create(metadata_dir, &opts)
402            .await?;
403        let procedure_manager =
404            standalone::build_procedure_manager(kv_backend.clone(), opts.procedure);
405
406        plugins::setup_standalone_plugins(&mut plugins, &plugin_opts, &opts, kv_backend.clone())
407            .await
408            .context(error::SetupStandalonePluginsSnafu)?;
409
410        // Builds cache registry
411        let layered_cache_builder = LayeredCacheRegistryBuilder::default();
412        let fundamental_cache_registry = build_fundamental_cache_registry(kv_backend.clone());
413        let layered_cache_registry = Arc::new(
414            with_default_composite_cache_registry(
415                layered_cache_builder.add_cache_registry(fundamental_cache_registry),
416            )
417            .context(error::BuildCacheRegistrySnafu)?
418            .build(),
419        );
420
421        let mut builder = DatanodeBuilder::new(dn_opts, plugins.clone(), kv_backend.clone());
422        builder.with_cache_registry(layered_cache_registry.clone());
423        let datanode = builder.build().await.context(error::StartDatanodeSnafu)?;
424
425        let information_extension = Arc::new(StandaloneInformationExtension::new(
426            datanode.region_server(),
427            procedure_manager.clone(),
428        ));
429
430        plugins.insert::<InformationExtensionRef>(information_extension.clone());
431
432        let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
433
434        // for standalone not use grpc, but get a handler to frontend grpc client without
435        // actually make a connection
436        let (frontend_client, frontend_instance_handler) =
437            FrontendClient::from_empty_grpc_handler(opts.query.clone());
438        let frontend_client = Arc::new(frontend_client);
439
440        let builder = KvBackendCatalogManagerBuilder::new(
441            information_extension.clone(),
442            kv_backend.clone(),
443            layered_cache_registry.clone(),
444        )
445        .with_procedure_manager(procedure_manager.clone())
446        .with_process_manager(process_manager.clone());
447        let builder = if let Some(configurator) =
448            plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
449        {
450            let ctx = StandaloneCatalogManagerConfigureContext {
451                fe_client: frontend_client.clone(),
452            };
453            let ctx = CatalogManagerConfigureContext::Standalone(ctx);
454            configurator
455                .configure(builder, ctx)
456                .await
457                .context(OtherSnafu)?
458        } else {
459            builder
460        };
461        let catalog_manager = builder.build();
462
463        let table_metadata_manager =
464            Self::create_table_metadata_manager(kv_backend.clone()).await?;
465
466        let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
467        let flownode_options = FlownodeOptions {
468            flow: opts.flow.clone(),
469            ..Default::default()
470        };
471
472        let flow_builder = FlownodeBuilder::new(
473            flownode_options,
474            plugins.clone(),
475            table_metadata_manager.clone(),
476            catalog_manager.clone(),
477            flow_metadata_manager.clone(),
478            frontend_client.clone(),
479        );
480        let flownode = flow_builder
481            .build()
482            .await
483            .map_err(BoxedError::new)
484            .context(error::OtherSnafu)?;
485
486        // set the ref to query for the local flow state
487        {
488            information_extension
489                .set_flow_engine(flownode.flow_engine())
490                .await;
491        }
492
493        let node_manager = creator
494            .node_manager_creator
495            .create(
496                &kv_backend,
497                datanode.region_server(),
498                flownode.flow_engine(),
499            )
500            .await?;
501
502        let table_id_allocator = creator.table_id_allocator_creator.create(&kv_backend);
503        let flow_id_sequence = Arc::new(
504            SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone())
505                .initial(MIN_USER_FLOW_ID as u64)
506                .step(10)
507                .build(),
508        );
509        let kafka_options = opts
510            .wal
511            .clone()
512            .try_into()
513            .context(error::InvalidWalProviderSnafu)?;
514        let wal_provider = build_wal_provider(&kafka_options, kv_backend.clone())
515            .await
516            .context(error::BuildWalProviderSnafu)?;
517        let wal_provider = Arc::new(wal_provider);
518        let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
519            table_id_allocator.clone(),
520            wal_provider.clone(),
521        ));
522        let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
523            flow_id_sequence,
524        ));
525
526        let ddl_context = DdlContext {
527            node_manager: node_manager.clone(),
528            cache_invalidator: layered_cache_registry.clone(),
529            memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
530            leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
531            table_metadata_manager: table_metadata_manager.clone(),
532            table_metadata_allocator: table_metadata_allocator.clone(),
533            flow_metadata_manager: flow_metadata_manager.clone(),
534            flow_metadata_allocator: flow_metadata_allocator.clone(),
535            region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
536        };
537
538        let ddl_manager = DdlManager::try_new(
539            ddl_context,
540            procedure_manager.clone(),
541            Arc::new(StandaloneRepartitionProcedureFactory),
542            true,
543        )
544        .context(error::InitDdlManagerSnafu)?;
545
546        let ddl_manager = if let Some(configurator) =
547            plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
548        {
549            let ctx = DdlManagerConfigureContext {
550                kv_backend: kv_backend.clone(),
551                fe_client: frontend_client.clone(),
552                catalog_manager: catalog_manager.clone(),
553            };
554            configurator
555                .configure(ddl_manager, ctx)
556                .await
557                .context(OtherSnafu)?
558        } else {
559            ddl_manager
560        };
561
562        let procedure_executor = creator
563            .procedure_executor_creator
564            .create(Arc::new(ddl_manager), procedure_manager.clone())
565            .await?;
566
567        let fe_instance = FrontendBuilder::new(
568            fe_opts.clone(),
569            kv_backend.clone(),
570            layered_cache_registry.clone(),
571            catalog_manager.clone(),
572            node_manager.clone(),
573            procedure_executor.clone(),
574            process_manager,
575        )
576        .with_plugin(plugins.clone())
577        .try_build()
578        .await
579        .context(error::StartFrontendSnafu)?;
580        let fe_instance = Arc::new(fe_instance);
581
582        // set the frontend client for flownode
583        let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
584        let weak_grpc_handler = Arc::downgrade(&grpc_handler);
585        frontend_instance_handler
586            .set_handler(weak_grpc_handler)
587            .await;
588
589        // set the frontend invoker for flownode
590        let flow_streaming_engine = flownode.flow_engine().streaming_engine();
591        // flow server need to be able to use frontend to write insert requests back
592        let invoker = FrontendInvoker::build_from(
593            flow_streaming_engine.clone(),
594            catalog_manager.clone(),
595            kv_backend.clone(),
596            layered_cache_registry.clone(),
597            procedure_executor,
598            node_manager.clone(),
599        )
600        .await
601        .context(StartFlownodeSnafu)?;
602        flow_streaming_engine.set_frontend_invoker(invoker).await;
603
604        let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
605            .build()
606            .context(error::StartFrontendSnafu)?;
607
608        let frontend = Frontend {
609            instance: fe_instance,
610            servers,
611            heartbeat_task: None,
612        };
613
614        let instance = Instance {
615            datanode,
616            frontend,
617            flownode,
618            procedure_manager,
619            wal_provider,
620            _guard: vec![],
621        };
622        let result = InstanceCreatorResult {
623            kv_backend,
624            node_manager,
625            table_id_allocator,
626        };
627        Ok((instance, result))
628    }
629
630    pub async fn create_table_metadata_manager(
631        kv_backend: KvBackendRef,
632    ) -> Result<TableMetadataManagerRef> {
633        let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
634
635        table_metadata_manager
636            .init()
637            .await
638            .context(error::InitMetadataSnafu)?;
639
640        Ok(table_metadata_manager)
641    }
642}
643
644#[async_trait]
645pub trait NodeManagerCreator {
646    async fn create(
647        &self,
648        kv_backend: &KvBackendRef,
649        region_server: RegionServer,
650        flow_server: FlownodeRef,
651    ) -> Result<NodeManagerRef>;
652}
653
654pub struct DefaultNodeManagerCreator;
655
656#[async_trait]
657impl NodeManagerCreator for DefaultNodeManagerCreator {
658    async fn create(
659        &self,
660        _: &KvBackendRef,
661        region_server: RegionServer,
662        flow_server: FlownodeRef,
663    ) -> Result<NodeManagerRef> {
664        Ok(Arc::new(StandaloneDatanodeManager {
665            region_server,
666            flow_server,
667        }))
668    }
669}
670
671/// Customizes how standalone opens its metadata KV backend.
672///
673/// The default implementation preserves the built-in raft-engine path. Other
674/// callers can provide a custom implementation without changing standalone
675/// configuration types.
676#[async_trait]
677pub trait MetadataKvBackendCreator: Send + Sync {
678    async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef>;
679}
680
681pub struct DefaultMetadataKvBackendCreator;
682
683#[async_trait]
684impl MetadataKvBackendCreator for DefaultMetadataKvBackendCreator {
685    async fn create(&self, metadata_dir: String, opts: &StandaloneOptions) -> Result<KvBackendRef> {
686        standalone::build_metadata_kvbackend(metadata_dir, opts.metadata_store)
687            .context(error::BuildMetadataKvbackendSnafu)
688    }
689}
690
691pub trait TableIdAllocatorCreator {
692    fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
693}
694
695struct DefaultTableIdAllocatorCreator;
696
697impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
698    fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
699        Arc::new(
700            SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
701                .initial(MIN_USER_TABLE_ID as u64)
702                .step(10)
703                .build(),
704        )
705    }
706}
707
708#[async_trait]
709pub trait ProcedureExecutorCreator {
710    async fn create(
711        &self,
712        ddl_manager: DdlManagerRef,
713        procedure_manager: ProcedureManagerRef,
714    ) -> Result<ProcedureExecutorRef>;
715}
716
717pub struct DefaultProcedureExecutorCreator;
718
719#[async_trait]
720impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
721    async fn create(
722        &self,
723        ddl_manager: DdlManagerRef,
724        procedure_manager: ProcedureManagerRef,
725    ) -> Result<ProcedureExecutorRef> {
726        Ok(Arc::new(LocalProcedureExecutor::new(
727            ddl_manager,
728            procedure_manager,
729        )))
730    }
731}
732
733/// `InstanceCreator` is used for grouping various component creators for building the
734/// Standalone instance, suitable for customizing how the instance can be built.
735pub struct InstanceCreator {
736    /// Hook for replacing metadata KV construction while reusing the rest of the
737    /// standalone build flow.
738    metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
739    node_manager_creator: Box<dyn NodeManagerCreator>,
740    table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
741    procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
742}
743
744impl InstanceCreator {
745    pub fn new(
746        node_manager_creator: Box<dyn NodeManagerCreator>,
747        table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
748        procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
749    ) -> Self {
750        Self {
751            metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
752            node_manager_creator,
753            table_id_allocator_creator,
754            procedure_executor_creator,
755        }
756    }
757
758    pub fn with_metadata_kv_backend_creator(
759        mut self,
760        metadata_kv_backend_creator: Box<dyn MetadataKvBackendCreator>,
761    ) -> Self {
762        self.metadata_kv_backend_creator = metadata_kv_backend_creator;
763        self
764    }
765}
766
767impl Default for InstanceCreator {
768    fn default() -> Self {
769        Self {
770            metadata_kv_backend_creator: Box::new(DefaultMetadataKvBackendCreator),
771            node_manager_creator: Box::new(DefaultNodeManagerCreator),
772            table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
773            procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
774        }
775    }
776}
777
778/// `InstanceCreatorResult` is expected to be used paired with [InstanceCreator].
779/// It stores the created and other important components for further reusing.
780pub struct InstanceCreatorResult {
781    pub kv_backend: KvBackendRef,
782    pub node_manager: NodeManagerRef,
783    pub table_id_allocator: Arc<Sequence>,
784}
785
786#[cfg(test)]
787mod tests {
788    use std::default::Default;
789    use std::io::Write;
790    use std::time::Duration;
791
792    use auth::{Identity, Password, UserProviderRef};
793    use clap::{CommandFactory, Parser};
794    use common_base::readable_size::ReadableSize;
795    use common_config::ENV_VAR_SEP;
796    use common_options::plugin_options::StandaloneFlag;
797    use common_test_util::temp_dir::create_named_temp_file;
798    use common_wal::config::DatanodeWalConfig;
799    use frontend::frontend::FrontendOptions;
800    use object_store::config::{FileConfig, GcsConfig};
801    use servers::grpc::GrpcOptions;
802
803    use super::*;
804    use crate::options::GlobalOptions;
805
806    #[tokio::test]
807    async fn test_try_from_start_command_to_anymap() {
808        let fe_opts = FrontendOptions {
809            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
810            ..Default::default()
811        };
812
813        let mut plugins = Plugins::new();
814        plugins.insert(StandaloneFlag);
815        plugins::setup_frontend_plugins(&mut plugins, &[], &fe_opts)
816            .await
817            .unwrap();
818
819        let provider = plugins.get::<UserProviderRef>().unwrap();
820        let result = provider
821            .authenticate(
822                Identity::UserId("test", None),
823                Password::PlainText("test".to_string().into()),
824            )
825            .await;
826        let _ = result.unwrap();
827    }
828
829    #[test]
830    fn test_toml() {
831        let opts = StandaloneOptions::default();
832        let toml_string = toml::to_string(&opts).unwrap();
833        let _parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
834    }
835
836    #[test]
837    fn test_read_from_config_file() {
838        let mut file = create_named_temp_file();
839        let toml_str = r#"
840            enable_memory_catalog = true
841
842            [wal]
843            provider = "raft_engine"
844            dir = "./greptimedb_data/test/wal"
845            file_size = "1GB"
846            purge_threshold = "50GB"
847            purge_interval = "10m"
848            read_batch_size = 128
849            sync_write = false
850
851            [storage]
852            data_home = "./greptimedb_data/"
853            type = "File"
854
855            [[storage.providers]]
856            type = "Gcs"
857            bucket = "foo"
858            endpoint = "bar"
859
860            [[storage.providers]]
861            type = "S3"
862            access_key_id = "access_key_id"
863            secret_access_key = "secret_access_key"
864
865            [storage.compaction]
866            max_inflight_tasks = 3
867            max_files_in_level0 = 7
868            max_purge_tasks = 32
869
870            [storage.manifest]
871            checkpoint_margin = 9
872            gc_duration = '7s'
873
874            [http]
875            addr = "127.0.0.1:4000"
876            timeout = "33s"
877            body_limit = "128MB"
878
879            [opentsdb]
880            enable = true
881
882            [logging]
883            level = "debug"
884            dir = "./greptimedb_data/test/logs"
885        "#;
886        write!(file, "{}", toml_str).unwrap();
887        let cmd = StartCommand {
888            config_file: Some(file.path().to_str().unwrap().to_string()),
889            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
890            ..Default::default()
891        };
892
893        let options = cmd
894            .load_options(&GlobalOptions::default())
895            .unwrap()
896            .component;
897        let fe_opts = options.frontend_options();
898        let dn_opts = options.datanode_options();
899        let logging_opts = options.logging;
900        assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
901        assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
902        assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
903        assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
904        assert!(fe_opts.mysql.enable);
905        assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
906        assert_eq!(2, fe_opts.mysql.runtime_size);
907        assert_eq!(None, fe_opts.mysql.reject_no_database);
908        assert!(fe_opts.influxdb.enable);
909        assert!(fe_opts.opentsdb.enable);
910
911        let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
912            unreachable!()
913        };
914        assert_eq!(
915            "./greptimedb_data/test/wal",
916            raft_engine_config.dir.unwrap()
917        );
918
919        assert!(matches!(
920            &dn_opts.storage.store,
921            object_store::config::ObjectStoreConfig::File(FileConfig { .. })
922        ));
923        assert_eq!(dn_opts.storage.providers.len(), 2);
924        assert!(matches!(
925            dn_opts.storage.providers[0],
926            object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
927        ));
928        match &dn_opts.storage.providers[1] {
929            object_store::config::ObjectStoreConfig::S3(s3_config) => {
930                assert_eq!(
931                    "SecretBox<alloc::string::String>([REDACTED])".to_string(),
932                    format!("{:?}", s3_config.connection.access_key_id)
933                );
934            }
935            _ => {
936                unreachable!()
937            }
938        }
939
940        assert_eq!("debug", logging_opts.level.as_ref().unwrap());
941        assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
942    }
943
944    #[test]
945    fn test_load_log_options_from_cli() {
946        let cmd = StartCommand {
947            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
948            mysql_addr: Some("127.0.0.1:4002".to_string()),
949            postgres_addr: Some("127.0.0.1:4003".to_string()),
950            ..Default::default()
951        };
952
953        let opts = cmd
954            .load_options(&GlobalOptions {
955                log_dir: Some("./greptimedb_data/test/logs".to_string()),
956                log_level: Some("debug".to_string()),
957
958                #[cfg(feature = "tokio-console")]
959                tokio_console_addr: None,
960            })
961            .unwrap()
962            .component;
963
964        assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
965        assert_eq!("debug", opts.logging.level.unwrap());
966    }
967
968    #[test]
969    fn test_config_precedence_order() {
970        let mut file = create_named_temp_file();
971        let toml_str = r#"
972            [http]
973            addr = "127.0.0.1:4000"
974
975            [logging]
976            level = "debug"
977        "#;
978        write!(file, "{}", toml_str).unwrap();
979
980        let env_prefix = "STANDALONE_UT";
981        temp_env::with_vars(
982            [
983                (
984                    // logging.dir = /other/log/dir
985                    [
986                        env_prefix.to_string(),
987                        "logging".to_uppercase(),
988                        "dir".to_uppercase(),
989                    ]
990                    .join(ENV_VAR_SEP),
991                    Some("/other/log/dir"),
992                ),
993                (
994                    // logging.level = info
995                    [
996                        env_prefix.to_string(),
997                        "logging".to_uppercase(),
998                        "level".to_uppercase(),
999                    ]
1000                    .join(ENV_VAR_SEP),
1001                    Some("info"),
1002                ),
1003                (
1004                    // http.addr = 127.0.0.1:24000
1005                    [
1006                        env_prefix.to_string(),
1007                        "http".to_uppercase(),
1008                        "addr".to_uppercase(),
1009                    ]
1010                    .join(ENV_VAR_SEP),
1011                    Some("127.0.0.1:24000"),
1012                ),
1013            ],
1014            || {
1015                let command = StartCommand {
1016                    config_file: Some(file.path().to_str().unwrap().to_string()),
1017                    http_addr: Some("127.0.0.1:14000".to_string()),
1018                    env_prefix: env_prefix.to_string(),
1019                    ..Default::default()
1020                };
1021
1022                let opts = command.load_options(&Default::default()).unwrap().component;
1023
1024                // Should be read from env, env > default values.
1025                assert_eq!(opts.logging.dir, "/other/log/dir");
1026
1027                // Should be read from config file, config file > env > default values.
1028                assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
1029
1030                // Should be read from cli, cli > config file > env > default values.
1031                let fe_opts = opts.frontend_options();
1032                assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
1033                assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
1034
1035                // Should be default value.
1036                assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
1037            },
1038        );
1039    }
1040
1041    #[test]
1042    fn test_parse_grpc_bind_addr_aliases() {
1043        let command =
1044            StartCommand::try_parse_from(["standalone", "--grpc-bind-addr", "127.0.0.1:14001"])
1045                .unwrap();
1046        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14001"));
1047
1048        let command =
1049            StartCommand::try_parse_from(["standalone", "--rpc-bind-addr", "127.0.0.1:24001"])
1050                .unwrap();
1051        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24001"));
1052
1053        let command =
1054            StartCommand::try_parse_from(["standalone", "--rpc-addr", "127.0.0.1:34001"]).unwrap();
1055        assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34001"));
1056    }
1057
1058    #[test]
1059    fn test_help_uses_grpc_option_names() {
1060        let mut cmd = StartCommand::command();
1061        let mut help = Vec::new();
1062        cmd.write_long_help(&mut help).unwrap();
1063        let help = String::from_utf8(help).unwrap();
1064
1065        assert!(help.contains("--grpc-bind-addr"));
1066        assert!(!help.contains("--rpc-bind-addr"));
1067        assert!(!help.contains("--rpc-addr"));
1068    }
1069
1070    #[test]
1071    fn test_load_default_standalone_options() {
1072        let options =
1073            StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1074        let default_options = StandaloneOptions::default();
1075        assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1076        assert_eq!(options.http, default_options.http);
1077        assert_eq!(options.grpc, default_options.grpc);
1078        assert_eq!(options.mysql, default_options.mysql);
1079        assert_eq!(options.postgres, default_options.postgres);
1080        assert_eq!(options.opentsdb, default_options.opentsdb);
1081        assert_eq!(options.influxdb, default_options.influxdb);
1082        assert_eq!(options.prom_store, default_options.prom_store);
1083        assert_eq!(options.wal, default_options.wal);
1084        assert_eq!(options.metadata_store, default_options.metadata_store);
1085        assert_eq!(options.procedure, default_options.procedure);
1086        assert_eq!(options.logging, default_options.logging);
1087        assert_eq!(options.region_engine, default_options.region_engine);
1088    }
1089
1090    #[test]
1091    fn test_cache_config() {
1092        let toml_str = r#"
1093            [storage]
1094            data_home = "test_data_home"
1095            type = "S3"
1096            [storage.cache_config]
1097            enable_read_cache = true
1098        "#;
1099        let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1100        opts.sanitize();
1101        assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1102        assert_eq!(
1103            opts.storage.store.cache_config().unwrap().cache_path,
1104            "test_data_home"
1105        );
1106    }
1107}