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_procedure::ProcedureManagerRef;
46use common_query::prelude::set_default_prefix;
47use common_telemetry::info;
48use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
49use common_time::timezone::set_default_timezone;
50use common_version::{short_version, verbose_version};
51use const_format::concatcp;
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 = concatcp!(common_version::product_name(), "-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, alias = "rpc-addr")]
216    rpc_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.rpc_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        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
381        plugins::setup_frontend_plugins(&mut plugins, &plugin_opts, &fe_opts)
382            .await
383            .context(error::StartFrontendSnafu)?;
384
385        plugins::setup_datanode_plugins(&mut plugins, &plugin_opts, &dn_opts)
386            .await
387            .context(error::StartDatanodeSnafu)?;
388
389        set_default_timezone(fe_opts.default_timezone.as_deref())
390            .context(error::InitTimezoneSnafu)?;
391
392        let data_home = &dn_opts.storage.data_home;
393        // Ensure the data_home directory exists.
394        fs::create_dir_all(path::Path::new(data_home))
395            .context(error::CreateDirSnafu { dir: data_home })?;
396
397        let metadata_dir = metadata_store_dir(data_home);
398        let kv_backend = standalone::build_metadata_kvbackend(metadata_dir, opts.metadata_store)
399            .context(error::BuildMetadataKvbackendSnafu)?;
400        let procedure_manager =
401            standalone::build_procedure_manager(kv_backend.clone(), opts.procedure);
402
403        plugins::setup_standalone_plugins(&mut plugins, &plugin_opts, &opts, kv_backend.clone())
404            .await
405            .context(error::SetupStandalonePluginsSnafu)?;
406
407        // Builds cache registry
408        let layered_cache_builder = LayeredCacheRegistryBuilder::default();
409        let fundamental_cache_registry = build_fundamental_cache_registry(kv_backend.clone());
410        let layered_cache_registry = Arc::new(
411            with_default_composite_cache_registry(
412                layered_cache_builder.add_cache_registry(fundamental_cache_registry),
413            )
414            .context(error::BuildCacheRegistrySnafu)?
415            .build(),
416        );
417
418        let mut builder = DatanodeBuilder::new(dn_opts, plugins.clone(), kv_backend.clone());
419        builder.with_cache_registry(layered_cache_registry.clone());
420        let datanode = builder.build().await.context(error::StartDatanodeSnafu)?;
421
422        let information_extension = Arc::new(StandaloneInformationExtension::new(
423            datanode.region_server(),
424            procedure_manager.clone(),
425        ));
426
427        plugins.insert::<InformationExtensionRef>(information_extension.clone());
428
429        let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
430
431        // for standalone not use grpc, but get a handler to frontend grpc client without
432        // actually make a connection
433        let (frontend_client, frontend_instance_handler) =
434            FrontendClient::from_empty_grpc_handler(opts.query.clone());
435        let frontend_client = Arc::new(frontend_client);
436
437        let builder = KvBackendCatalogManagerBuilder::new(
438            information_extension.clone(),
439            kv_backend.clone(),
440            layered_cache_registry.clone(),
441        )
442        .with_procedure_manager(procedure_manager.clone())
443        .with_process_manager(process_manager.clone());
444        let builder = if let Some(configurator) =
445            plugins.get::<CatalogManagerConfiguratorRef<CatalogManagerConfigureContext>>()
446        {
447            let ctx = StandaloneCatalogManagerConfigureContext {
448                fe_client: frontend_client.clone(),
449            };
450            let ctx = CatalogManagerConfigureContext::Standalone(ctx);
451            configurator
452                .configure(builder, ctx)
453                .await
454                .context(OtherSnafu)?
455        } else {
456            builder
457        };
458        let catalog_manager = builder.build();
459
460        let table_metadata_manager =
461            Self::create_table_metadata_manager(kv_backend.clone()).await?;
462
463        let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone()));
464        let flownode_options = FlownodeOptions {
465            flow: opts.flow.clone(),
466            ..Default::default()
467        };
468
469        let flow_builder = FlownodeBuilder::new(
470            flownode_options,
471            plugins.clone(),
472            table_metadata_manager.clone(),
473            catalog_manager.clone(),
474            flow_metadata_manager.clone(),
475            frontend_client.clone(),
476        );
477        let flownode = flow_builder
478            .build()
479            .await
480            .map_err(BoxedError::new)
481            .context(error::OtherSnafu)?;
482
483        // set the ref to query for the local flow state
484        {
485            information_extension
486                .set_flow_engine(flownode.flow_engine())
487                .await;
488        }
489
490        let node_manager = creator
491            .node_manager_creator
492            .create(
493                &kv_backend,
494                datanode.region_server(),
495                flownode.flow_engine(),
496            )
497            .await?;
498
499        let table_id_allocator = creator.table_id_allocator_creator.create(&kv_backend);
500        let flow_id_sequence = Arc::new(
501            SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone())
502                .initial(MIN_USER_FLOW_ID as u64)
503                .step(10)
504                .build(),
505        );
506        let kafka_options = opts
507            .wal
508            .clone()
509            .try_into()
510            .context(error::InvalidWalProviderSnafu)?;
511        let wal_provider = build_wal_provider(&kafka_options, kv_backend.clone())
512            .await
513            .context(error::BuildWalProviderSnafu)?;
514        let wal_provider = Arc::new(wal_provider);
515        let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
516            table_id_allocator.clone(),
517            wal_provider.clone(),
518        ));
519        let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
520            flow_id_sequence,
521        ));
522
523        let ddl_context = DdlContext {
524            node_manager: node_manager.clone(),
525            cache_invalidator: layered_cache_registry.clone(),
526            memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
527            leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
528            table_metadata_manager: table_metadata_manager.clone(),
529            table_metadata_allocator: table_metadata_allocator.clone(),
530            flow_metadata_manager: flow_metadata_manager.clone(),
531            flow_metadata_allocator: flow_metadata_allocator.clone(),
532            region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
533        };
534
535        let ddl_manager = DdlManager::try_new(
536            ddl_context,
537            procedure_manager.clone(),
538            Arc::new(StandaloneRepartitionProcedureFactory),
539            true,
540        )
541        .context(error::InitDdlManagerSnafu)?;
542
543        let ddl_manager = if let Some(configurator) =
544            plugins.get::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>()
545        {
546            let ctx = DdlManagerConfigureContext {
547                kv_backend: kv_backend.clone(),
548                fe_client: frontend_client.clone(),
549                catalog_manager: catalog_manager.clone(),
550            };
551            configurator
552                .configure(ddl_manager, ctx)
553                .await
554                .context(OtherSnafu)?
555        } else {
556            ddl_manager
557        };
558
559        let procedure_executor = creator
560            .procedure_executor_creator
561            .create(Arc::new(ddl_manager), procedure_manager.clone())
562            .await?;
563
564        let fe_instance = FrontendBuilder::new(
565            fe_opts.clone(),
566            kv_backend.clone(),
567            layered_cache_registry.clone(),
568            catalog_manager.clone(),
569            node_manager.clone(),
570            procedure_executor.clone(),
571            process_manager,
572        )
573        .with_plugin(plugins.clone())
574        .try_build()
575        .await
576        .context(error::StartFrontendSnafu)?;
577        let fe_instance = Arc::new(fe_instance);
578
579        // set the frontend client for flownode
580        let grpc_handler = fe_instance.clone() as Arc<dyn GrpcQueryHandlerWithBoxedError>;
581        let weak_grpc_handler = Arc::downgrade(&grpc_handler);
582        frontend_instance_handler
583            .set_handler(weak_grpc_handler)
584            .await;
585
586        // set the frontend invoker for flownode
587        let flow_streaming_engine = flownode.flow_engine().streaming_engine();
588        // flow server need to be able to use frontend to write insert requests back
589        let invoker = FrontendInvoker::build_from(
590            flow_streaming_engine.clone(),
591            catalog_manager.clone(),
592            kv_backend.clone(),
593            layered_cache_registry.clone(),
594            procedure_executor,
595            node_manager.clone(),
596        )
597        .await
598        .context(StartFlownodeSnafu)?;
599        flow_streaming_engine.set_frontend_invoker(invoker).await;
600
601        let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
602            .build()
603            .context(error::StartFrontendSnafu)?;
604
605        let frontend = Frontend {
606            instance: fe_instance,
607            servers,
608            heartbeat_task: None,
609        };
610
611        let instance = Instance {
612            datanode,
613            frontend,
614            flownode,
615            procedure_manager,
616            wal_provider,
617            _guard: vec![],
618        };
619        let result = InstanceCreatorResult {
620            kv_backend,
621            node_manager,
622            table_id_allocator,
623        };
624        Ok((instance, result))
625    }
626
627    pub async fn create_table_metadata_manager(
628        kv_backend: KvBackendRef,
629    ) -> Result<TableMetadataManagerRef> {
630        let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend));
631
632        table_metadata_manager
633            .init()
634            .await
635            .context(error::InitMetadataSnafu)?;
636
637        Ok(table_metadata_manager)
638    }
639}
640
641#[async_trait]
642pub trait NodeManagerCreator {
643    async fn create(
644        &self,
645        kv_backend: &KvBackendRef,
646        region_server: RegionServer,
647        flow_server: FlownodeRef,
648    ) -> Result<NodeManagerRef>;
649}
650
651pub struct DefaultNodeManagerCreator;
652
653#[async_trait]
654impl NodeManagerCreator for DefaultNodeManagerCreator {
655    async fn create(
656        &self,
657        _: &KvBackendRef,
658        region_server: RegionServer,
659        flow_server: FlownodeRef,
660    ) -> Result<NodeManagerRef> {
661        Ok(Arc::new(StandaloneDatanodeManager {
662            region_server,
663            flow_server,
664        }))
665    }
666}
667
668pub trait TableIdAllocatorCreator {
669    fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence>;
670}
671
672struct DefaultTableIdAllocatorCreator;
673
674impl TableIdAllocatorCreator for DefaultTableIdAllocatorCreator {
675    fn create(&self, kv_backend: &KvBackendRef) -> Arc<Sequence> {
676        Arc::new(
677            SequenceBuilder::new(TABLE_ID_SEQ, kv_backend.clone())
678                .initial(MIN_USER_TABLE_ID as u64)
679                .step(10)
680                .build(),
681        )
682    }
683}
684
685#[async_trait]
686pub trait ProcedureExecutorCreator {
687    async fn create(
688        &self,
689        ddl_manager: DdlManagerRef,
690        procedure_manager: ProcedureManagerRef,
691    ) -> Result<ProcedureExecutorRef>;
692}
693
694pub struct DefaultProcedureExecutorCreator;
695
696#[async_trait]
697impl ProcedureExecutorCreator for DefaultProcedureExecutorCreator {
698    async fn create(
699        &self,
700        ddl_manager: DdlManagerRef,
701        procedure_manager: ProcedureManagerRef,
702    ) -> Result<ProcedureExecutorRef> {
703        Ok(Arc::new(LocalProcedureExecutor::new(
704            ddl_manager,
705            procedure_manager,
706        )))
707    }
708}
709
710/// `InstanceCreator` is used for grouping various component creators for building the
711/// Standalone instance, suitable for customizing how the instance can be built.
712pub struct InstanceCreator {
713    node_manager_creator: Box<dyn NodeManagerCreator>,
714    table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
715    procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
716}
717
718impl InstanceCreator {
719    pub fn new(
720        node_manager_creator: Box<dyn NodeManagerCreator>,
721        table_id_allocator_creator: Box<dyn TableIdAllocatorCreator>,
722        procedure_executor_creator: Box<dyn ProcedureExecutorCreator>,
723    ) -> Self {
724        Self {
725            node_manager_creator,
726            table_id_allocator_creator,
727            procedure_executor_creator,
728        }
729    }
730}
731
732impl Default for InstanceCreator {
733    fn default() -> Self {
734        Self {
735            node_manager_creator: Box::new(DefaultNodeManagerCreator),
736            table_id_allocator_creator: Box::new(DefaultTableIdAllocatorCreator),
737            procedure_executor_creator: Box::new(DefaultProcedureExecutorCreator),
738        }
739    }
740}
741
742/// `InstanceCreatorResult` is expected to be used paired with [InstanceCreator].
743/// It stores the created and other important components for further reusing.
744pub struct InstanceCreatorResult {
745    pub kv_backend: KvBackendRef,
746    pub node_manager: NodeManagerRef,
747    pub table_id_allocator: Arc<Sequence>,
748}
749
750#[cfg(test)]
751mod tests {
752    use std::default::Default;
753    use std::io::Write;
754    use std::time::Duration;
755
756    use auth::{Identity, Password, UserProviderRef};
757    use common_base::readable_size::ReadableSize;
758    use common_config::ENV_VAR_SEP;
759    use common_test_util::temp_dir::create_named_temp_file;
760    use common_wal::config::DatanodeWalConfig;
761    use frontend::frontend::FrontendOptions;
762    use object_store::config::{FileConfig, GcsConfig};
763    use servers::grpc::GrpcOptions;
764
765    use super::*;
766    use crate::options::GlobalOptions;
767
768    #[tokio::test]
769    async fn test_try_from_start_command_to_anymap() {
770        let fe_opts = FrontendOptions {
771            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
772            ..Default::default()
773        };
774
775        let mut plugins = Plugins::new();
776        plugins::setup_frontend_plugins(&mut plugins, &[], &fe_opts)
777            .await
778            .unwrap();
779
780        let provider = plugins.get::<UserProviderRef>().unwrap();
781        let result = provider
782            .authenticate(
783                Identity::UserId("test", None),
784                Password::PlainText("test".to_string().into()),
785            )
786            .await;
787        let _ = result.unwrap();
788    }
789
790    #[test]
791    fn test_toml() {
792        let opts = StandaloneOptions::default();
793        let toml_string = toml::to_string(&opts).unwrap();
794        let _parsed: StandaloneOptions = toml::from_str(&toml_string).unwrap();
795    }
796
797    #[test]
798    fn test_read_from_config_file() {
799        let mut file = create_named_temp_file();
800        let toml_str = r#"
801            enable_memory_catalog = true
802
803            [wal]
804            provider = "raft_engine"
805            dir = "./greptimedb_data/test/wal"
806            file_size = "1GB"
807            purge_threshold = "50GB"
808            purge_interval = "10m"
809            read_batch_size = 128
810            sync_write = false
811
812            [storage]
813            data_home = "./greptimedb_data/"
814            type = "File"
815
816            [[storage.providers]]
817            type = "Gcs"
818            bucket = "foo"
819            endpoint = "bar"
820
821            [[storage.providers]]
822            type = "S3"
823            access_key_id = "access_key_id"
824            secret_access_key = "secret_access_key"
825
826            [storage.compaction]
827            max_inflight_tasks = 3
828            max_files_in_level0 = 7
829            max_purge_tasks = 32
830
831            [storage.manifest]
832            checkpoint_margin = 9
833            gc_duration = '7s'
834
835            [http]
836            addr = "127.0.0.1:4000"
837            timeout = "33s"
838            body_limit = "128MB"
839
840            [opentsdb]
841            enable = true
842
843            [logging]
844            level = "debug"
845            dir = "./greptimedb_data/test/logs"
846        "#;
847        write!(file, "{}", toml_str).unwrap();
848        let cmd = StartCommand {
849            config_file: Some(file.path().to_str().unwrap().to_string()),
850            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
851            ..Default::default()
852        };
853
854        let options = cmd
855            .load_options(&GlobalOptions::default())
856            .unwrap()
857            .component;
858        let fe_opts = options.frontend_options();
859        let dn_opts = options.datanode_options();
860        let logging_opts = options.logging;
861        assert_eq!("127.0.0.1:4000".to_string(), fe_opts.http.addr);
862        assert_eq!(Duration::from_secs(33), fe_opts.http.timeout);
863        assert_eq!(ReadableSize::mb(128), fe_opts.http.body_limit);
864        assert_eq!("127.0.0.1:4001".to_string(), fe_opts.grpc.bind_addr);
865        assert!(fe_opts.mysql.enable);
866        assert_eq!("127.0.0.1:4002", fe_opts.mysql.addr);
867        assert_eq!(2, fe_opts.mysql.runtime_size);
868        assert_eq!(None, fe_opts.mysql.reject_no_database);
869        assert!(fe_opts.influxdb.enable);
870        assert!(fe_opts.opentsdb.enable);
871
872        let DatanodeWalConfig::RaftEngine(raft_engine_config) = dn_opts.wal else {
873            unreachable!()
874        };
875        assert_eq!(
876            "./greptimedb_data/test/wal",
877            raft_engine_config.dir.unwrap()
878        );
879
880        assert!(matches!(
881            &dn_opts.storage.store,
882            object_store::config::ObjectStoreConfig::File(FileConfig { .. })
883        ));
884        assert_eq!(dn_opts.storage.providers.len(), 2);
885        assert!(matches!(
886            dn_opts.storage.providers[0],
887            object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
888        ));
889        match &dn_opts.storage.providers[1] {
890            object_store::config::ObjectStoreConfig::S3(s3_config) => {
891                assert_eq!(
892                    "SecretBox<alloc::string::String>([REDACTED])".to_string(),
893                    format!("{:?}", s3_config.connection.access_key_id)
894                );
895            }
896            _ => {
897                unreachable!()
898            }
899        }
900
901        assert_eq!("debug", logging_opts.level.as_ref().unwrap());
902        assert_eq!("./greptimedb_data/test/logs".to_string(), logging_opts.dir);
903    }
904
905    #[test]
906    fn test_load_log_options_from_cli() {
907        let cmd = StartCommand {
908            user_provider: Some("static_user_provider:cmd:test=test".to_string()),
909            mysql_addr: Some("127.0.0.1:4002".to_string()),
910            postgres_addr: Some("127.0.0.1:4003".to_string()),
911            ..Default::default()
912        };
913
914        let opts = cmd
915            .load_options(&GlobalOptions {
916                log_dir: Some("./greptimedb_data/test/logs".to_string()),
917                log_level: Some("debug".to_string()),
918
919                #[cfg(feature = "tokio-console")]
920                tokio_console_addr: None,
921            })
922            .unwrap()
923            .component;
924
925        assert_eq!("./greptimedb_data/test/logs", opts.logging.dir);
926        assert_eq!("debug", opts.logging.level.unwrap());
927    }
928
929    #[test]
930    fn test_config_precedence_order() {
931        let mut file = create_named_temp_file();
932        let toml_str = r#"
933            [http]
934            addr = "127.0.0.1:4000"
935
936            [logging]
937            level = "debug"
938        "#;
939        write!(file, "{}", toml_str).unwrap();
940
941        let env_prefix = "STANDALONE_UT";
942        temp_env::with_vars(
943            [
944                (
945                    // logging.dir = /other/log/dir
946                    [
947                        env_prefix.to_string(),
948                        "logging".to_uppercase(),
949                        "dir".to_uppercase(),
950                    ]
951                    .join(ENV_VAR_SEP),
952                    Some("/other/log/dir"),
953                ),
954                (
955                    // logging.level = info
956                    [
957                        env_prefix.to_string(),
958                        "logging".to_uppercase(),
959                        "level".to_uppercase(),
960                    ]
961                    .join(ENV_VAR_SEP),
962                    Some("info"),
963                ),
964                (
965                    // http.addr = 127.0.0.1:24000
966                    [
967                        env_prefix.to_string(),
968                        "http".to_uppercase(),
969                        "addr".to_uppercase(),
970                    ]
971                    .join(ENV_VAR_SEP),
972                    Some("127.0.0.1:24000"),
973                ),
974            ],
975            || {
976                let command = StartCommand {
977                    config_file: Some(file.path().to_str().unwrap().to_string()),
978                    http_addr: Some("127.0.0.1:14000".to_string()),
979                    env_prefix: env_prefix.to_string(),
980                    ..Default::default()
981                };
982
983                let opts = command.load_options(&Default::default()).unwrap().component;
984
985                // Should be read from env, env > default values.
986                assert_eq!(opts.logging.dir, "/other/log/dir");
987
988                // Should be read from config file, config file > env > default values.
989                assert_eq!(opts.logging.level.as_ref().unwrap(), "debug");
990
991                // Should be read from cli, cli > config file > env > default values.
992                let fe_opts = opts.frontend_options();
993                assert_eq!(fe_opts.http.addr, "127.0.0.1:14000");
994                assert_eq!(ReadableSize::mb(64), fe_opts.http.body_limit);
995
996                // Should be default value.
997                assert_eq!(fe_opts.grpc.bind_addr, GrpcOptions::default().bind_addr);
998            },
999        );
1000    }
1001
1002    #[test]
1003    fn test_load_default_standalone_options() {
1004        let options =
1005            StandaloneOptions::load_layered_options(None, "GREPTIMEDB_STANDALONE").unwrap();
1006        let default_options = StandaloneOptions::default();
1007        assert_eq!(options.enable_telemetry, default_options.enable_telemetry);
1008        assert_eq!(options.http, default_options.http);
1009        assert_eq!(options.grpc, default_options.grpc);
1010        assert_eq!(options.mysql, default_options.mysql);
1011        assert_eq!(options.postgres, default_options.postgres);
1012        assert_eq!(options.opentsdb, default_options.opentsdb);
1013        assert_eq!(options.influxdb, default_options.influxdb);
1014        assert_eq!(options.prom_store, default_options.prom_store);
1015        assert_eq!(options.wal, default_options.wal);
1016        assert_eq!(options.metadata_store, default_options.metadata_store);
1017        assert_eq!(options.procedure, default_options.procedure);
1018        assert_eq!(options.logging, default_options.logging);
1019        assert_eq!(options.region_engine, default_options.region_engine);
1020    }
1021
1022    #[test]
1023    fn test_cache_config() {
1024        let toml_str = r#"
1025            [storage]
1026            data_home = "test_data_home"
1027            type = "S3"
1028            [storage.cache_config]
1029            enable_read_cache = true
1030        "#;
1031        let mut opts: StandaloneOptions = toml::from_str(toml_str).unwrap();
1032        opts.sanitize();
1033        assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
1034        assert_eq!(
1035            opts.storage.store.cache_config().unwrap().cache_path,
1036            "test_data_home"
1037        );
1038    }
1039}