1use std::time::Duration;
18
19use common_base::readable_size::ReadableSize;
20use common_config::{Configurable, DEFAULT_DATA_HOME};
21use common_options::memory::MemoryOptions;
22pub use common_procedure::options::ProcedureConfig;
23use common_telemetry::logging::{LoggingOptions, TracingOptions};
24use common_wal::config::DatanodeWalConfig;
25use common_workload::{DatanodeWorkloadType, sanitize_workload_types};
26use file_engine::config::EngineConfig as FileEngineConfig;
27use meta_client::MetaClientOptions;
28use metric_engine::config::EngineConfig as MetricEngineConfig;
29use mito2::config::MitoConfig;
30pub(crate) use object_store::config::ObjectStoreConfig;
31use query::options::QueryOptions;
32use serde::{Deserialize, Serialize};
33use servers::grpc::GrpcOptions;
34use servers::http::HttpOptions;
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38#[serde(default)]
39pub struct StorageConfig {
40 pub data_home: String,
42 pub copy_root: Option<String>,
47 pub probe_on_startup: bool,
55 #[serde(flatten)]
56 pub store: ObjectStoreConfig,
57 pub providers: Vec<ObjectStoreConfig>,
59}
60
61impl StorageConfig {
62 pub fn is_object_storage(&self) -> bool {
64 self.store.is_object_storage()
65 }
66}
67
68impl Default for StorageConfig {
69 fn default() -> Self {
70 Self {
71 data_home: DEFAULT_DATA_HOME.to_string(),
72 copy_root: None,
73 probe_on_startup: true,
74 store: ObjectStoreConfig::default(),
75 providers: vec![],
76 }
77 }
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
81#[serde(default)]
82pub struct DatanodeOptions {
83 pub node_id: Option<u64>,
84 pub default_column_prefix: Option<String>,
85 pub workload_types: Vec<DatanodeWorkloadType>,
86 pub require_lease_before_startup: bool,
87 pub init_regions_in_background: bool,
88 pub init_regions_parallelism: usize,
89 pub grpc: GrpcOptions,
90 pub http: HttpOptions,
91 pub meta_client: Option<MetaClientOptions>,
92 pub wal: DatanodeWalConfig,
93 pub storage: StorageConfig,
94 pub max_concurrent_queries: usize,
95 #[serde(with = "humantime_serde")]
98 pub concurrent_query_limiter_timeout: Duration,
99 pub region_engine: Vec<RegionEngineConfig>,
101 pub logging: LoggingOptions,
102 pub enable_telemetry: bool,
103 pub tracing: TracingOptions,
104 pub query: QueryOptions,
105 pub memory: MemoryOptions,
106
107 pub heartbeat_env_vars: Vec<String>,
110
111 #[deprecated(note = "Please use `grpc.bind_addr` instead.")]
113 pub rpc_addr: Option<String>,
114 #[deprecated(note = "Please use `grpc.server_addr` instead.")]
115 pub rpc_hostname: Option<String>,
116 #[deprecated(note = "Please use `grpc.runtime_size` instead.")]
117 pub rpc_runtime_size: Option<usize>,
118 #[deprecated(note = "Please use `grpc.max_recv_message_size` instead.")]
119 pub rpc_max_recv_message_size: Option<ReadableSize>,
120 #[deprecated(note = "Please use `grpc.max_send_message_size` instead.")]
121 pub rpc_max_send_message_size: Option<ReadableSize>,
122}
123
124impl DatanodeOptions {
125 pub fn sanitize(&mut self) {
127 sanitize_workload_types(&mut self.workload_types);
128
129 if self.storage.is_object_storage() {
130 self.storage
131 .store
132 .cache_config_mut()
133 .unwrap()
134 .sanitize(&self.storage.data_home);
135 }
136 }
137}
138
139impl Default for DatanodeOptions {
140 #[allow(deprecated)]
141 fn default() -> Self {
142 Self {
143 node_id: None,
144 default_column_prefix: None,
145 workload_types: vec![DatanodeWorkloadType::Hybrid],
146 require_lease_before_startup: false,
147 init_regions_in_background: false,
148 init_regions_parallelism: 16,
149 grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:3001"),
150 http: HttpOptions::default(),
151 meta_client: None,
152 wal: DatanodeWalConfig::default(),
153 storage: StorageConfig::default(),
154 max_concurrent_queries: 0,
155 concurrent_query_limiter_timeout: Duration::from_millis(100),
156 region_engine: vec![
157 RegionEngineConfig::Mito(MitoConfig::default()),
158 RegionEngineConfig::File(FileEngineConfig::default()),
159 ],
160 logging: LoggingOptions::default(),
161 enable_telemetry: true,
162 tracing: TracingOptions::default(),
163 query: QueryOptions::default(),
164 memory: MemoryOptions::default(),
165 heartbeat_env_vars: vec![],
166
167 rpc_addr: None,
169 rpc_hostname: None,
170 rpc_runtime_size: None,
171 rpc_max_recv_message_size: None,
172 rpc_max_send_message_size: None,
173 }
174 }
175}
176
177impl Configurable for DatanodeOptions {
178 fn env_list_keys() -> Option<&'static [&'static str]> {
179 Some(&[
180 "heartbeat_env_vars",
181 "meta_client.metasrv_addrs",
182 "wal.broker_endpoints",
183 ])
184 }
185
186 fn env_tagged_seq_keys() -> Option<&'static [&'static str]> {
187 Some(&["region_engine"])
188 }
189}
190
191#[allow(clippy::large_enum_variant)]
192#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
193pub enum RegionEngineConfig {
194 #[serde(rename = "mito")]
195 Mito(MitoConfig),
196 #[serde(rename = "file")]
197 File(FileEngineConfig),
198 #[serde(rename = "metric")]
199 Metric(MetricEngineConfig),
200}
201
202#[cfg(test)]
203mod tests {
204 use common_base::secrets::ExposeSecret;
205
206 use super::*;
207
208 #[test]
209 fn test_toml() {
210 let opts = DatanodeOptions::default();
211 let toml_string = toml::to_string(&opts).unwrap();
212 let _parsed: DatanodeOptions = toml::from_str(&toml_string).unwrap();
213 }
214
215 #[test]
216 fn test_probe_on_startup() {
217 assert!(DatanodeOptions::default().storage.probe_on_startup);
218
219 let toml_str = r#"
220 [storage]
221 type = "File"
222 probe_on_startup = false
223 "#;
224 let opts: DatanodeOptions = toml::from_str(toml_str).unwrap();
225 assert!(!opts.storage.probe_on_startup);
226 }
227
228 #[test]
229 fn test_secstr() {
230 let toml_str = r#"
231 [storage]
232 type = "S3"
233 access_key_id = "access_key_id"
234 secret_access_key = "secret_access_key"
235 "#;
236 let opts: DatanodeOptions = toml::from_str(toml_str).unwrap();
237 match &opts.storage.store {
238 ObjectStoreConfig::S3(cfg) => {
239 assert_eq!(
240 "SecretBox<alloc::string::String>([REDACTED])".to_string(),
241 format!("{:?}", cfg.connection.access_key_id)
242 );
243 assert_eq!(
244 "access_key_id",
245 cfg.connection.access_key_id.expose_secret()
246 );
247 }
248 _ => unreachable!(),
249 }
250 }
251 #[test]
252 fn test_skip_ssl_validation_config() {
253 let toml_str_true = r#"
255 [storage]
256 type = "S3"
257 [storage.http_client]
258 skip_ssl_validation = true
259 "#;
260 let opts: DatanodeOptions = toml::from_str(toml_str_true).unwrap();
261 match &opts.storage.store {
262 ObjectStoreConfig::S3(cfg) => {
263 assert!(cfg.http_client.skip_ssl_validation);
264 }
265 _ => panic!("Expected S3 config"),
266 }
267
268 let toml_str_false = r#"
270 [storage]
271 type = "S3"
272 [storage.http_client]
273 skip_ssl_validation = false
274 "#;
275 let opts: DatanodeOptions = toml::from_str(toml_str_false).unwrap();
276 match &opts.storage.store {
277 ObjectStoreConfig::S3(cfg) => {
278 assert!(!cfg.http_client.skip_ssl_validation);
279 }
280 _ => panic!("Expected S3 config"),
281 }
282 let toml_str_default = r#"
284 [storage]
285 type = "S3"
286 "#;
287 let opts: DatanodeOptions = toml::from_str(toml_str_default).unwrap();
288 match &opts.storage.store {
289 ObjectStoreConfig::S3(cfg) => {
290 assert!(!cfg.http_client.skip_ssl_validation);
291 }
292 _ => panic!("Expected S3 config"),
293 }
294 }
295
296 #[test]
297 fn test_cache_config() {
298 let toml_str = r#"
299 [storage]
300 data_home = "test_data_home"
301 type = "S3"
302 [storage.cache_config]
303 enable_read_cache = true
304 "#;
305 let mut opts: DatanodeOptions = toml::from_str(toml_str).unwrap();
306 opts.sanitize();
307 assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
308 assert_eq!(
309 opts.storage.store.cache_config().unwrap().cache_path,
310 "test_data_home"
311 );
312 }
313}