1use std::sync::Arc;
16
17use clap::{Parser, ValueEnum};
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;
23use meta_srv::utils::etcd::create_etcd_client_with_tls;
24use serde::{Deserialize, Serialize};
25use servers::tls::{TlsMode, TlsOption};
26use snafu::OptionExt;
27
28use crate::error::{EmptyStoreAddrsSnafu, InvalidArgumentsSnafu};
29
30#[derive(Clone, Debug, PartialEq, Serialize, Default, Deserialize, ValueEnum)]
32#[serde(rename_all = "snake_case")]
33#[allow(clippy::enum_variant_names)]
34pub enum BackendImpl {
35 #[default]
37 EtcdStore,
38 MemoryStore,
40 #[cfg(feature = "pg_kvbackend")]
41 PostgresStore,
43 #[cfg(feature = "mysql_kvbackend")]
44 MysqlStore,
46 RaftEngineStore,
48}
49
50#[derive(Debug, Default, Parser)]
51pub struct StoreConfig {
52 #[clap(long, alias = "store-addr", value_delimiter = ',', num_args = 1..)]
63 pub store_addrs: Vec<String>,
64
65 #[clap(long, default_value = "128")]
67 pub max_txn_ops: usize,
68
69 #[clap(long, value_enum, default_value = "etcd-store")]
71 pub backend: BackendImpl,
72
73 #[clap(long, default_value = "")]
75 pub store_key_prefix: String,
76
77 #[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))]
79 #[clap(long, default_value = common_meta::kv_backend::DEFAULT_META_TABLE_NAME)]
80 pub meta_table_name: String,
81
82 #[cfg(feature = "pg_kvbackend")]
84 #[clap(long)]
85 pub meta_schema_name: Option<String>,
86
87 #[cfg(feature = "pg_kvbackend")]
89 #[clap(
90 long,
91 default_value_t = true,
92 default_missing_value = "true",
93 num_args = 0..=1,
94 action = clap::ArgAction::Set
95 )]
96 pub auto_create_schema: bool,
97
98 #[clap(long = "backend-tls-mode", value_enum, default_value = "disable")]
100 pub backend_tls_mode: TlsMode,
101
102 #[clap(long = "backend-tls-cert-path", default_value = "")]
104 pub backend_tls_cert_path: String,
105
106 #[clap(long = "backend-tls-key-path", default_value = "")]
108 pub backend_tls_key_path: String,
109
110 #[clap(long = "backend-tls-ca-cert-path", default_value = "")]
112 pub backend_tls_ca_cert_path: String,
113
114 #[clap(long = "backend-tls-watch")]
116 pub backend_tls_watch: bool,
117}
118
119impl StoreConfig {
120 pub fn tls_config(&self) -> Option<TlsOption> {
121 if self.backend_tls_mode != TlsMode::Disable {
122 Some(TlsOption {
123 mode: self.backend_tls_mode,
124 cert_path: self.backend_tls_cert_path.clone(),
125 key_path: self.backend_tls_key_path.clone(),
126 ca_cert_path: self.backend_tls_ca_cert_path.clone(),
127 watch: self.backend_tls_watch,
128 })
129 } else {
130 None
131 }
132 }
133
134 fn sanitize_store_addrs(&self) -> Vec<String> {
136 self.store_addrs
137 .iter()
138 .map(|addr| common_meta::kv_backend::util::sanitize_connection_string(addr))
139 .collect()
140 }
141
142 pub async fn build(&self) -> Result<KvBackendRef, BoxedError> {
144 let max_txn_ops = self.max_txn_ops;
145 let store_addrs = &self.store_addrs;
146 if store_addrs.is_empty() {
147 EmptyStoreAddrsSnafu.fail().map_err(BoxedError::new)
148 } else {
149 common_telemetry::info!(
150 "Building kvbackend with store addrs: {:?}, backend: {:?}",
151 &self.sanitize_store_addrs(),
152 self.backend
153 );
154 let kvbackend = match self.backend {
155 BackendImpl::EtcdStore => {
156 let tls_config = self.tls_config();
157 let etcd_client = create_etcd_client_with_tls(
158 store_addrs,
159 &BackendClientOptions::default(),
160 tls_config.as_ref(),
161 )
162 .await
163 .map_err(BoxedError::new)?;
164 Ok(EtcdStore::with_etcd_client(etcd_client, max_txn_ops))
165 }
166 #[cfg(feature = "pg_kvbackend")]
167 BackendImpl::PostgresStore => {
168 let table_name = &self.meta_table_name;
169 let tls_config = self.tls_config();
170 Ok(meta_srv::utils::postgres::build_postgres_kv_backend(
171 store_addrs,
172 None,
173 tls_config,
174 self.meta_schema_name.as_deref(),
175 table_name,
176 max_txn_ops,
177 self.auto_create_schema,
178 )
179 .await
180 .map_err(BoxedError::new)?)
181 }
182 #[cfg(feature = "mysql_kvbackend")]
183 BackendImpl::MysqlStore => {
184 let table_name = &self.meta_table_name;
185 let tls_config = self.tls_config();
186 Ok(meta_srv::utils::mysql::build_mysql_kv_backend(
187 store_addrs,
188 tls_config.as_ref(),
189 table_name,
190 max_txn_ops,
191 )
192 .await
193 .map_err(BoxedError::new)?)
194 }
195 #[cfg(not(test))]
196 BackendImpl::MemoryStore => {
197 use crate::error::UnsupportedMemoryBackendSnafu;
198
199 UnsupportedMemoryBackendSnafu
200 .fail()
201 .map_err(BoxedError::new)
202 }
203 #[cfg(test)]
204 BackendImpl::MemoryStore => {
205 use common_meta::kv_backend::memory::MemoryKvBackend;
206
207 Ok(Arc::new(MemoryKvBackend::default()) as _)
208 }
209 BackendImpl::RaftEngineStore => {
210 let url = store_addrs
211 .first()
212 .context(InvalidArgumentsSnafu {
213 msg: "empty store addresses".to_string(),
214 })
215 .map_err(BoxedError::new)?;
216 let kvbackend =
217 standalone::build_metadata_kv_from_url(url).map_err(BoxedError::new)?;
218
219 Ok(kvbackend)
220 }
221 };
222 if self.store_key_prefix.is_empty() {
223 kvbackend
224 } else {
225 let chroot_kvbackend =
226 ChrootKvBackend::new(self.store_key_prefix.as_bytes().to_vec(), kvbackend?);
227 Ok(Arc::new(chroot_kvbackend))
228 }
229 }
230 }
231}
232
233#[cfg(all(test, feature = "pg_kvbackend"))]
234mod tests {
235 use clap::Parser;
236
237 use super::StoreConfig;
238
239 #[test]
240 fn test_auto_create_schema_accepts_explicit_value() {
241 let config =
242 StoreConfig::try_parse_from(["store", "--auto-create-schema", "false"]).unwrap();
243 assert!(!config.auto_create_schema);
244
245 let config =
246 StoreConfig::try_parse_from(["store", "--auto-create-schema", "true"]).unwrap();
247 assert!(config.auto_create_schema);
248 }
249
250 #[test]
251 fn test_auto_create_schema_defaults_to_true() {
252 let config = StoreConfig::try_parse_from(["store"]).unwrap();
253 assert!(config.auto_create_schema);
254 }
255}