cli/common/
store.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::sync::Arc;
16
17use clap::Parser;
18use common_error::ext::BoxedError;
19use common_meta::kv_backend::KvBackendRef;
20use common_meta::kv_backend::chroot::ChrootKvBackend;
21use common_meta::kv_backend::etcd::EtcdStore;
22use meta_srv::metasrv::{BackendClientOptions, BackendImpl};
23use meta_srv::utils::etcd::create_etcd_client_with_tls;
24use servers::tls::{TlsMode, TlsOption};
25
26use crate::error::EmptyStoreAddrsSnafu;
27
28#[derive(Debug, Default, Parser)]
29pub struct StoreConfig {
30    /// The endpoint of store. one of etcd, postgres or mysql.
31    ///
32    /// For postgres store, the format is:
33    /// "password=password dbname=postgres user=postgres host=localhost port=5432"
34    ///
35    /// For etcd store, the format is:
36    /// "127.0.0.1:2379"
37    ///
38    /// For mysql store, the format is:
39    /// "mysql://user:password@ip:port/dbname"
40    #[clap(long, alias = "store-addr", value_delimiter = ',', num_args = 1..)]
41    pub store_addrs: Vec<String>,
42
43    /// The maximum number of operations in a transaction. Only used when using [etcd-store].
44    #[clap(long, default_value = "128")]
45    pub max_txn_ops: usize,
46
47    /// The metadata store backend.
48    #[clap(long, value_enum, default_value = "etcd-store")]
49    pub backend: BackendImpl,
50
51    /// The key prefix of the metadata store.
52    #[clap(long, default_value = "")]
53    pub store_key_prefix: String,
54
55    /// The table name in RDS to store metadata. Only used when using [postgres-store] or [mysql-store].
56    #[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))]
57    #[clap(long, default_value = common_meta::kv_backend::DEFAULT_META_TABLE_NAME)]
58    pub meta_table_name: String,
59
60    /// Optional PostgreSQL schema for metadata table (defaults to current search_path if unset).
61    #[cfg(feature = "pg_kvbackend")]
62    #[clap(long)]
63    pub meta_schema_name: Option<String>,
64
65    /// Automatically create PostgreSQL schema if it doesn't exist (default: true).
66    #[cfg(feature = "pg_kvbackend")]
67    #[clap(long, default_value_t = true)]
68    pub auto_create_schema: bool,
69
70    /// TLS mode for backend store connections (etcd, PostgreSQL, MySQL)
71    #[clap(long = "backend-tls-mode", value_enum, default_value = "disable")]
72    pub backend_tls_mode: TlsMode,
73
74    /// Path to TLS certificate file for backend store connections
75    #[clap(long = "backend-tls-cert-path", default_value = "")]
76    pub backend_tls_cert_path: String,
77
78    /// Path to TLS private key file for backend store connections
79    #[clap(long = "backend-tls-key-path", default_value = "")]
80    pub backend_tls_key_path: String,
81
82    /// Path to TLS CA certificate file for backend store connections
83    #[clap(long = "backend-tls-ca-cert-path", default_value = "")]
84    pub backend_tls_ca_cert_path: String,
85
86    /// Enable watching TLS certificate files for changes
87    #[clap(long = "backend-tls-watch")]
88    pub backend_tls_watch: bool,
89}
90
91impl StoreConfig {
92    pub fn tls_config(&self) -> Option<TlsOption> {
93        if self.backend_tls_mode != TlsMode::Disable {
94            Some(TlsOption {
95                mode: self.backend_tls_mode.clone(),
96                cert_path: self.backend_tls_cert_path.clone(),
97                key_path: self.backend_tls_key_path.clone(),
98                ca_cert_path: self.backend_tls_ca_cert_path.clone(),
99                watch: self.backend_tls_watch,
100            })
101        } else {
102            None
103        }
104    }
105
106    /// Builds a [`KvBackendRef`] from the store configuration.
107    pub async fn build(&self) -> Result<KvBackendRef, BoxedError> {
108        let max_txn_ops = self.max_txn_ops;
109        let store_addrs = &self.store_addrs;
110        if store_addrs.is_empty() {
111            EmptyStoreAddrsSnafu.fail().map_err(BoxedError::new)
112        } else {
113            common_telemetry::info!(
114                "Building kvbackend with store addrs: {:?}, backend: {:?}",
115                store_addrs,
116                self.backend
117            );
118            let kvbackend = match self.backend {
119                BackendImpl::EtcdStore => {
120                    let tls_config = self.tls_config();
121                    let etcd_client = create_etcd_client_with_tls(
122                        store_addrs,
123                        &BackendClientOptions::default(),
124                        tls_config.as_ref(),
125                    )
126                    .await
127                    .map_err(BoxedError::new)?;
128                    Ok(EtcdStore::with_etcd_client(etcd_client, max_txn_ops))
129                }
130                #[cfg(feature = "pg_kvbackend")]
131                BackendImpl::PostgresStore => {
132                    let table_name = &self.meta_table_name;
133                    let tls_config = self.tls_config();
134                    let pool = meta_srv::utils::postgres::create_postgres_pool(
135                        store_addrs,
136                        None,
137                        tls_config,
138                    )
139                    .await
140                    .map_err(BoxedError::new)?;
141                    let schema_name = self.meta_schema_name.as_deref();
142                    Ok(common_meta::kv_backend::rds::PgStore::with_pg_pool(
143                        pool,
144                        schema_name,
145                        table_name,
146                        max_txn_ops,
147                        self.auto_create_schema,
148                    )
149                    .await
150                    .map_err(BoxedError::new)?)
151                }
152                #[cfg(feature = "mysql_kvbackend")]
153                BackendImpl::MysqlStore => {
154                    let table_name = &self.meta_table_name;
155                    let tls_config = self.tls_config();
156                    let pool =
157                        meta_srv::utils::mysql::create_mysql_pool(store_addrs, tls_config.as_ref())
158                            .await
159                            .map_err(BoxedError::new)?;
160                    Ok(common_meta::kv_backend::rds::MySqlStore::with_mysql_pool(
161                        pool,
162                        table_name,
163                        max_txn_ops,
164                    )
165                    .await
166                    .map_err(BoxedError::new)?)
167                }
168                #[cfg(not(test))]
169                BackendImpl::MemoryStore => {
170                    use crate::error::UnsupportedMemoryBackendSnafu;
171
172                    UnsupportedMemoryBackendSnafu
173                        .fail()
174                        .map_err(BoxedError::new)
175                }
176                #[cfg(test)]
177                BackendImpl::MemoryStore => {
178                    use common_meta::kv_backend::memory::MemoryKvBackend;
179
180                    Ok(Arc::new(MemoryKvBackend::default()) as _)
181                }
182            };
183            if self.store_key_prefix.is_empty() {
184                kvbackend
185            } else {
186                let chroot_kvbackend =
187                    ChrootKvBackend::new(self.store_key_prefix.as_bytes().to_vec(), kvbackend?);
188                Ok(Arc::new(chroot_kvbackend))
189            }
190        }
191    }
192}