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    #[serde(flatten)]
43    pub store: ObjectStoreConfig,
44    /// Object storage providers
45    pub providers: Vec<ObjectStoreConfig>,
46}
47
48impl StorageConfig {
49    /// Returns true when the default storage config is a remote object storage service such as AWS S3, etc.
50    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    /// Timeout to acquire a permit from the concurrent query limiter when
81    /// `max_concurrent_queries` is reached. Only effective when the limiter is enabled.
82    #[serde(with = "humantime_serde")]
83    pub concurrent_query_limiter_timeout: Duration,
84    /// Options for different store engines.
85    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    /// Environment variable keys to read and report in heartbeat messages.
93    /// The values of these env vars at startup will be sent to metasrv.
94    pub heartbeat_env_vars: Vec<String>,
95
96    /// Deprecated options, please use the new options instead.
97    #[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    /// Sanitize the `DatanodeOptions` to ensure the config is valid.
111    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            // Deprecated options
153            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        // Test with skip_ssl_validation = true
222        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        // Test with skip_ssl_validation = false
237        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        // Test default value (should be false)
251        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}