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 #[serde(flatten)]
43 pub store: ObjectStoreConfig,
44 pub providers: Vec<ObjectStoreConfig>,
46}
47
48impl StorageConfig {
49 pub fn is_object_storage(&self) -> bool {
51 self.store.is_object_storage()
52 }
53}
54
55impl Default for StorageConfig {
56 fn default() -> Self {
57 Self {
58 data_home: DEFAULT_DATA_HOME.to_string(),
59 store: ObjectStoreConfig::default(),
60 providers: vec![],
61 }
62 }
63}
64
65#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
66#[serde(default)]
67pub struct DatanodeOptions {
68 pub node_id: Option<u64>,
69 pub default_column_prefix: Option<String>,
70 pub workload_types: Vec<DatanodeWorkloadType>,
71 pub require_lease_before_startup: bool,
72 pub init_regions_in_background: bool,
73 pub init_regions_parallelism: usize,
74 pub grpc: GrpcOptions,
75 pub http: HttpOptions,
76 pub meta_client: Option<MetaClientOptions>,
77 pub wal: DatanodeWalConfig,
78 pub storage: StorageConfig,
79 pub max_concurrent_queries: usize,
80 #[serde(with = "humantime_serde")]
83 pub concurrent_query_limiter_timeout: Duration,
84 pub region_engine: Vec<RegionEngineConfig>,
86 pub logging: LoggingOptions,
87 pub enable_telemetry: bool,
88 pub tracing: TracingOptions,
89 pub query: QueryOptions,
90 pub memory: MemoryOptions,
91
92 pub heartbeat_env_vars: Vec<String>,
95
96 #[deprecated(note = "Please use `grpc.bind_addr` instead.")]
98 pub rpc_addr: Option<String>,
99 #[deprecated(note = "Please use `grpc.server_addr` instead.")]
100 pub rpc_hostname: Option<String>,
101 #[deprecated(note = "Please use `grpc.runtime_size` instead.")]
102 pub rpc_runtime_size: Option<usize>,
103 #[deprecated(note = "Please use `grpc.max_recv_message_size` instead.")]
104 pub rpc_max_recv_message_size: Option<ReadableSize>,
105 #[deprecated(note = "Please use `grpc.max_send_message_size` instead.")]
106 pub rpc_max_send_message_size: Option<ReadableSize>,
107}
108
109impl DatanodeOptions {
110 pub fn sanitize(&mut self) {
112 sanitize_workload_types(&mut self.workload_types);
113
114 if self.storage.is_object_storage() {
115 self.storage
116 .store
117 .cache_config_mut()
118 .unwrap()
119 .sanitize(&self.storage.data_home);
120 }
121 }
122}
123
124impl Default for DatanodeOptions {
125 #[allow(deprecated)]
126 fn default() -> Self {
127 Self {
128 node_id: None,
129 default_column_prefix: None,
130 workload_types: vec![DatanodeWorkloadType::Hybrid],
131 require_lease_before_startup: false,
132 init_regions_in_background: false,
133 init_regions_parallelism: 16,
134 grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:3001"),
135 http: HttpOptions::default(),
136 meta_client: None,
137 wal: DatanodeWalConfig::default(),
138 storage: StorageConfig::default(),
139 max_concurrent_queries: 0,
140 concurrent_query_limiter_timeout: Duration::from_millis(100),
141 region_engine: vec![
142 RegionEngineConfig::Mito(MitoConfig::default()),
143 RegionEngineConfig::File(FileEngineConfig::default()),
144 ],
145 logging: LoggingOptions::default(),
146 enable_telemetry: true,
147 tracing: TracingOptions::default(),
148 query: QueryOptions::default(),
149 memory: MemoryOptions::default(),
150 heartbeat_env_vars: vec![],
151
152 rpc_addr: None,
154 rpc_hostname: None,
155 rpc_runtime_size: None,
156 rpc_max_recv_message_size: None,
157 rpc_max_send_message_size: None,
158 }
159 }
160}
161
162impl Configurable for DatanodeOptions {
163 fn env_list_keys() -> Option<&'static [&'static str]> {
164 Some(&[
165 "heartbeat_env_vars",
166 "meta_client.metasrv_addrs",
167 "wal.broker_endpoints",
168 ])
169 }
170}
171
172#[allow(clippy::large_enum_variant)]
173#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
174pub enum RegionEngineConfig {
175 #[serde(rename = "mito")]
176 Mito(MitoConfig),
177 #[serde(rename = "file")]
178 File(FileEngineConfig),
179 #[serde(rename = "metric")]
180 Metric(MetricEngineConfig),
181}
182
183#[cfg(test)]
184mod tests {
185 use common_base::secrets::ExposeSecret;
186
187 use super::*;
188
189 #[test]
190 fn test_toml() {
191 let opts = DatanodeOptions::default();
192 let toml_string = toml::to_string(&opts).unwrap();
193 let _parsed: DatanodeOptions = toml::from_str(&toml_string).unwrap();
194 }
195
196 #[test]
197 fn test_secstr() {
198 let toml_str = r#"
199 [storage]
200 type = "S3"
201 access_key_id = "access_key_id"
202 secret_access_key = "secret_access_key"
203 "#;
204 let opts: DatanodeOptions = toml::from_str(toml_str).unwrap();
205 match &opts.storage.store {
206 ObjectStoreConfig::S3(cfg) => {
207 assert_eq!(
208 "SecretBox<alloc::string::String>([REDACTED])".to_string(),
209 format!("{:?}", cfg.connection.access_key_id)
210 );
211 assert_eq!(
212 "access_key_id",
213 cfg.connection.access_key_id.expose_secret()
214 );
215 }
216 _ => unreachable!(),
217 }
218 }
219 #[test]
220 fn test_skip_ssl_validation_config() {
221 let toml_str_true = r#"
223 [storage]
224 type = "S3"
225 [storage.http_client]
226 skip_ssl_validation = true
227 "#;
228 let opts: DatanodeOptions = toml::from_str(toml_str_true).unwrap();
229 match &opts.storage.store {
230 ObjectStoreConfig::S3(cfg) => {
231 assert!(cfg.http_client.skip_ssl_validation);
232 }
233 _ => panic!("Expected S3 config"),
234 }
235
236 let toml_str_false = r#"
238 [storage]
239 type = "S3"
240 [storage.http_client]
241 skip_ssl_validation = false
242 "#;
243 let opts: DatanodeOptions = toml::from_str(toml_str_false).unwrap();
244 match &opts.storage.store {
245 ObjectStoreConfig::S3(cfg) => {
246 assert!(!cfg.http_client.skip_ssl_validation);
247 }
248 _ => panic!("Expected S3 config"),
249 }
250 let toml_str_default = r#"
252 [storage]
253 type = "S3"
254 "#;
255 let opts: DatanodeOptions = toml::from_str(toml_str_default).unwrap();
256 match &opts.storage.store {
257 ObjectStoreConfig::S3(cfg) => {
258 assert!(!cfg.http_client.skip_ssl_validation);
259 }
260 _ => panic!("Expected S3 config"),
261 }
262 }
263
264 #[test]
265 fn test_cache_config() {
266 let toml_str = r#"
267 [storage]
268 data_home = "test_data_home"
269 type = "S3"
270 [storage.cache_config]
271 enable_read_cache = true
272 "#;
273 let mut opts: DatanodeOptions = toml::from_str(toml_str).unwrap();
274 opts.sanitize();
275 assert!(opts.storage.store.cache_config().unwrap().enable_read_cache);
276 assert_eq!(
277 opts.storage.store.cache_config().unwrap().cache_path,
278 "test_data_home"
279 );
280 }
281}