Skip to main content

datanode/
config.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
15//! Datanode configurations
16
17use 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/// Storage engine config
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38#[serde(default)]
39pub struct StorageConfig {
40    /// The working directory of database
41    pub data_home: String,
42    /// Root directory for standalone SQL access to local files.
43    ///
44    /// Defaults to `<data_home>/copy` when `data_home` is a local path.
45    /// Distributed deployments always disable SQL access to local files.
46    pub copy_root: Option<String>,
47    /// Whether to probe every remote object storage when the datanode starts.
48    ///
49    /// The probe writes, reads, overwrites, lists and deletes an object under
50    /// `.greptime_probe/`, and a failure aborts startup. The probe lives at the
51    /// storage root, so the credentials must allow writes and deletes below the root.
52    /// Disable it when the credentials are scoped to narrower prefixes or when the
53    /// storage may be unavailable at startup.
54    pub probe_on_startup: bool,
55    #[serde(flatten)]
56    pub store: ObjectStoreConfig,
57    /// Object storage providers
58    pub providers: Vec<ObjectStoreConfig>,
59}
60
61impl StorageConfig {
62    /// Returns true when the default storage config is a remote object storage service such as AWS S3, etc.
63    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    /// Timeout to acquire a permit from the concurrent query limiter when
96    /// `max_concurrent_queries` is reached. Only effective when the limiter is enabled.
97    #[serde(with = "humantime_serde")]
98    pub concurrent_query_limiter_timeout: Duration,
99    /// Options for different store engines.
100    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    /// Environment variable keys to read and report in heartbeat messages.
108    /// The values of these env vars at startup will be sent to metasrv.
109    pub heartbeat_env_vars: Vec<String>,
110
111    /// Deprecated options, please use the new options instead.
112    #[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    /// Sanitize the `DatanodeOptions` to ensure the config is valid.
126    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            // Deprecated options
168            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        // Test with skip_ssl_validation = true
254        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        // Test with skip_ssl_validation = false
269        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        // Test default value (should be false)
283        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}