1pub mod builder;
16#[allow(clippy::print_stdout)]
17pub(crate) mod objbench;
18#[allow(clippy::print_stdout)]
19pub mod parquetbench;
20#[allow(clippy::print_stdout)]
21pub mod scanbench;
22
23use std::path::Path;
24use std::time::Duration;
25
26use async_trait::async_trait;
27use clap::Parser;
28use common_config::Configurable;
29use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
30use common_telemetry::{info, warn};
31use common_wal::config::DatanodeWalConfig;
32use datanode::config::RegionEngineConfig;
33use datanode::datanode::Datanode;
34use meta_client::MetaClientOptions;
35use serde::{Deserialize, Serialize};
36use snafu::{ResultExt, ensure};
37use tracing_appender::non_blocking::WorkerGuard;
38
39use crate::App;
40use crate::datanode::builder::InstanceBuilder;
41use crate::datanode::objbench::ObjbenchCommand;
42use crate::datanode::parquetbench::ParquetbenchCommand;
43use crate::datanode::scanbench::ScanbenchCommand;
44use crate::error::{
45 LoadLayeredConfigSnafu, MissingConfigSnafu, Result, ShutdownDatanodeSnafu, StartDatanodeSnafu,
46};
47use crate::options::{GlobalOptions, GreptimeOptions};
48
49pub const APP_NAME: &str = "greptime-datanode";
50
51type DatanodeOptions = GreptimeOptions<datanode::config::DatanodeOptions>;
52
53pub struct Instance {
54 datanode: Datanode,
55
56 _guard: Vec<WorkerGuard>,
58}
59
60impl Instance {
61 pub fn new(datanode: Datanode, guard: Vec<WorkerGuard>) -> Self {
62 Self {
63 datanode,
64 _guard: guard,
65 }
66 }
67
68 pub fn datanode(&self) -> &Datanode {
69 &self.datanode
70 }
71
72 pub fn datanode_mut(&mut self) -> &mut Datanode {
74 &mut self.datanode
75 }
76}
77
78#[async_trait]
79impl App for Instance {
80 fn name(&self) -> &str {
81 APP_NAME
82 }
83
84 async fn start(&mut self) -> Result<()> {
85 plugins::start_datanode_plugins(&self.datanode)
86 .await
87 .context(StartDatanodeSnafu)?;
88
89 self.datanode.start().await.context(StartDatanodeSnafu)
90 }
91
92 async fn stop(&mut self) -> Result<()> {
93 self.datanode
94 .shutdown()
95 .await
96 .context(ShutdownDatanodeSnafu)
97 }
98}
99
100#[derive(Parser)]
101pub struct Command {
102 #[clap(subcommand)]
103 pub subcmd: SubCommand,
104}
105
106impl Command {
107 pub async fn build_with(&self, builder: InstanceBuilder) -> Result<Instance> {
108 self.subcmd.build_with(builder).await
109 }
110
111 pub fn load_options(&self, global_options: &GlobalOptions) -> Result<DatanodeOptions> {
112 match &self.subcmd {
113 SubCommand::Start(cmd) => cmd.load_options(global_options),
114 SubCommand::Objbench(_) | SubCommand::Scanbench(_) => Self::default_bench_options(),
116 SubCommand::Parquetbench(_) => Self::default_bench_options(),
117 }
118 }
119
120 fn default_bench_options() -> Result<DatanodeOptions> {
123 let mut opts = datanode::config::DatanodeOptions::default();
124 opts.sanitize();
125 Ok(DatanodeOptions {
126 runtime: Default::default(),
127 plugins: Default::default(),
128 component: opts,
129 })
130 }
131}
132
133#[derive(Parser)]
134pub enum SubCommand {
135 Start(StartCommand),
136 Objbench(ObjbenchCommand),
138 Scanbench(ScanbenchCommand),
140 Parquetbench(ParquetbenchCommand),
142}
143
144impl SubCommand {
145 async fn build_with(&self, builder: InstanceBuilder) -> Result<Instance> {
146 match self {
147 SubCommand::Start(cmd) => {
148 info!("Building datanode with {:#?}", cmd);
149 builder.build().await
150 }
151 SubCommand::Objbench(cmd) => {
152 cmd.run().await?;
153 std::process::exit(0);
154 }
155 SubCommand::Scanbench(cmd) => {
156 cmd.run().await?;
157 std::process::exit(0);
158 }
159 SubCommand::Parquetbench(cmd) => {
160 cmd.run().await?;
161 std::process::exit(0);
162 }
163 }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
169#[serde(default)]
170pub struct StorageConfig {
171 pub data_home: String,
173 #[serde(flatten)]
174 pub store: object_store::config::ObjectStoreConfig,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
178#[serde(default)]
179struct StorageConfigWrapper {
180 storage: StorageConfig,
181 region_engine: Vec<RegionEngineConfig>,
182 #[serde(default, deserialize_with = "deserialize_wal_config")]
183 wal: DatanodeWalConfig,
184}
185
186fn deserialize_wal_config<'de, D>(
191 deserializer: D,
192) -> std::result::Result<DatanodeWalConfig, D::Error>
193where
194 D: serde::Deserializer<'de>,
195{
196 use serde::de::Error as _;
197
198 let mut table = <toml::value::Table as serde::Deserialize>::deserialize(deserializer)?;
199 if !table.contains_key("provider") {
200 table.insert(
201 "provider".to_string(),
202 toml::Value::String("raft_engine".to_string()),
203 );
204 }
205 DatanodeWalConfig::deserialize(toml::Value::Table(table)).map_err(D::Error::custom)
206}
207
208#[derive(Debug, Parser, Default)]
209pub struct StartCommand {
210 #[clap(long)]
211 node_id: Option<u64>,
212 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
214 grpc_bind_addr: Option<String>,
215 #[clap(
219 long = "grpc-server-addr",
220 alias = "rpc-server-addr",
221 alias = "rpc-hostname"
222 )]
223 grpc_server_addr: Option<String>,
224 #[clap(long, value_delimiter = ',', num_args = 1..)]
225 metasrv_addrs: Option<Vec<String>>,
226 #[clap(short, long)]
227 config_file: Option<String>,
228 #[clap(long)]
229 data_home: Option<String>,
230 #[clap(long)]
231 wal_dir: Option<String>,
232 #[clap(long)]
233 http_addr: Option<String>,
234 #[clap(long)]
235 http_timeout: Option<u64>,
236 #[clap(long, default_value = "GREPTIMEDB_DATANODE")]
237 env_prefix: String,
238}
239
240impl StartCommand {
241 pub fn load_options(&self, global_options: &GlobalOptions) -> Result<DatanodeOptions> {
242 let mut opts = DatanodeOptions::load_layered_options(
243 self.config_file.as_deref(),
244 self.env_prefix.as_ref(),
245 )
246 .context(LoadLayeredConfigSnafu)?;
247
248 self.merge_with_cli_options(global_options, &mut opts)?;
249 opts.component.sanitize();
250
251 Ok(opts)
252 }
253
254 #[allow(deprecated)]
256 fn merge_with_cli_options(
257 &self,
258 global_options: &GlobalOptions,
259 opts: &mut DatanodeOptions,
260 ) -> Result<()> {
261 let opts = &mut opts.component;
262
263 if let Some(dir) = &global_options.log_dir {
264 opts.logging.dir.clone_from(dir);
265 }
266
267 if global_options.log_level.is_some() {
268 opts.logging.level.clone_from(&global_options.log_level);
269 }
270
271 opts.tracing = TracingOptions {
272 #[cfg(feature = "tokio-console")]
273 tokio_console_addr: global_options.tokio_console_addr.clone(),
274 };
275
276 if let Some(addr) = &self.grpc_bind_addr {
277 opts.grpc.bind_addr.clone_from(addr);
278 } else if let Some(addr) = &opts.rpc_addr {
279 warn!(
280 "Use the deprecated attribute `DatanodeOptions.rpc_addr`, please use `grpc.bind_addr` instead."
281 );
282 opts.grpc.bind_addr.clone_from(addr);
283 }
284
285 if let Some(server_addr) = &self.grpc_server_addr {
286 opts.grpc.server_addr.clone_from(server_addr);
287 } else if let Some(server_addr) = &opts.rpc_hostname {
288 warn!(
289 "Use the deprecated attribute `DatanodeOptions.rpc_hostname`, please use `grpc.server_addr` instead."
290 );
291 opts.grpc.server_addr.clone_from(server_addr);
292 }
293
294 if let Some(runtime_size) = opts.rpc_runtime_size {
295 warn!(
296 "Use the deprecated attribute `DatanodeOptions.rpc_runtime_size`, please use `grpc.runtime_size` instead."
297 );
298 opts.grpc.runtime_size = runtime_size;
299 }
300
301 if let Some(max_recv_message_size) = opts.rpc_max_recv_message_size {
302 warn!(
303 "Use the deprecated attribute `DatanodeOptions.rpc_max_recv_message_size`, please use `grpc.max_recv_message_size` instead."
304 );
305 opts.grpc.max_recv_message_size = max_recv_message_size;
306 }
307
308 if let Some(max_send_message_size) = opts.rpc_max_send_message_size {
309 warn!(
310 "Use the deprecated attribute `DatanodeOptions.rpc_max_send_message_size`, please use `grpc.max_send_message_size` instead."
311 );
312 opts.grpc.max_send_message_size = max_send_message_size;
313 }
314
315 if let Some(node_id) = self.node_id {
316 opts.node_id = Some(node_id);
317 }
318
319 if let Some(metasrv_addrs) = &self.metasrv_addrs {
320 opts.meta_client
321 .get_or_insert_with(MetaClientOptions::default)
322 .metasrv_addrs
323 .clone_from(metasrv_addrs);
324 }
325
326 ensure!(
327 opts.node_id.is_some(),
328 MissingConfigSnafu {
329 msg: "Missing node id option"
330 }
331 );
332
333 if let Some(data_home) = &self.data_home {
334 opts.storage.data_home.clone_from(data_home);
335 }
336
337 if let Some(wal_dir) = &self.wal_dir
339 && let DatanodeWalConfig::RaftEngine(raft_engine_config) = &mut opts.wal
340 {
341 if raft_engine_config
342 .dir
343 .as_ref()
344 .is_some_and(|original_dir| original_dir != wal_dir)
345 {
346 info!("The wal dir of raft-engine is altered to {wal_dir}");
347 }
348 raft_engine_config.dir.replace(wal_dir.clone());
349 }
350
351 if opts.logging.dir.is_empty() {
353 opts.logging.dir = Path::new(&opts.storage.data_home)
354 .join(DEFAULT_LOGGING_DIR)
355 .to_string_lossy()
356 .to_string();
357 }
358
359 if let Some(http_addr) = &self.http_addr {
360 opts.http.addr.clone_from(http_addr);
361 }
362
363 if let Some(http_timeout) = self.http_timeout {
364 opts.http.timeout = Duration::from_secs(http_timeout)
365 }
366
367 opts.http.disable_dashboard = true;
369
370 Ok(())
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use std::assert_matches;
377 use std::io::Write;
378 use std::time::Duration;
379
380 use clap::{CommandFactory, Parser};
381 use common_config::ENV_VAR_SEP;
382 use common_test_util::temp_dir::create_named_temp_file;
383 use object_store::config::{FileConfig, GcsConfig, ObjectStoreConfig, S3Config};
384
385 use super::*;
386 use crate::options::GlobalOptions;
387
388 #[test]
389 fn test_deprecated_cli_options() {
390 common_telemetry::init_default_ut_logging();
391 let mut file = create_named_temp_file();
392 let toml_str = r#"
393 enable_memory_catalog = false
394 node_id = 42
395
396 rpc_addr = "127.0.0.1:4001"
397 rpc_hostname = "192.168.0.1"
398 [grpc]
399 bind_addr = "127.0.0.1:3001"
400 server_addr = "127.0.0.1"
401 runtime_size = 8
402 "#;
403 write!(file, "{}", toml_str).unwrap();
404
405 let cmd = StartCommand {
406 config_file: Some(file.path().to_str().unwrap().to_string()),
407 ..Default::default()
408 };
409
410 let options = cmd.load_options(&Default::default()).unwrap().component;
411 assert_eq!("127.0.0.1:4001".to_string(), options.grpc.bind_addr);
412 assert_eq!("192.168.0.1".to_string(), options.grpc.server_addr);
413 }
414
415 #[test]
416 fn test_read_from_config_file() {
417 let mut file = create_named_temp_file();
418 let toml_str = r#"
419 enable_memory_catalog = false
420 node_id = 42
421
422 [grpc]
423 bind_addr = "127.0.0.1:3001"
424 server_addr = "127.0.0.1"
425 runtime_size = 8
426
427 [meta_client]
428 metasrv_addrs = ["127.0.0.1:3002"]
429 timeout = "3s"
430 connect_timeout = "5s"
431 ddl_timeout = "10s"
432 tcp_nodelay = true
433
434 [wal]
435 provider = "raft_engine"
436 dir = "/other/wal"
437 file_size = "1GB"
438 purge_threshold = "50GB"
439 purge_interval = "10m"
440 read_batch_size = 128
441 sync_write = false
442
443 [storage]
444 data_home = "./greptimedb_data/"
445 type = "File"
446
447 [[storage.providers]]
448 type = "Gcs"
449 bucket = "foo"
450 endpoint = "bar"
451
452 [[storage.providers]]
453 type = "S3"
454 bucket = "foo"
455
456 [logging]
457 level = "debug"
458 dir = "./greptimedb_data/test/logs"
459 "#;
460 write!(file, "{}", toml_str).unwrap();
461
462 let cmd = StartCommand {
463 config_file: Some(file.path().to_str().unwrap().to_string()),
464 ..Default::default()
465 };
466
467 let options = cmd.load_options(&Default::default()).unwrap().component;
468
469 assert_eq!("127.0.0.1:3001".to_string(), options.grpc.bind_addr);
470 assert_eq!("127.0.0.1".to_string(), options.grpc.server_addr);
471 assert_eq!(Some(42), options.node_id);
472
473 let DatanodeWalConfig::RaftEngine(raft_engine_config) = options.wal else {
474 unreachable!()
475 };
476 assert_eq!("/other/wal", raft_engine_config.dir.unwrap());
477 assert_eq!(Duration::from_secs(600), raft_engine_config.purge_interval);
478 assert_eq!(1024 * 1024 * 1024, raft_engine_config.file_size.0);
479 assert_eq!(
480 1024 * 1024 * 1024 * 50,
481 raft_engine_config.purge_threshold.0
482 );
483 assert!(!raft_engine_config.sync_write);
484
485 let MetaClientOptions {
486 metasrv_addrs: metasrv_addr,
487 timeout,
488 connect_timeout,
489 ddl_timeout,
490 tcp_nodelay,
491 ..
492 } = options.meta_client.unwrap();
493
494 assert_eq!(vec!["127.0.0.1:3002".to_string()], metasrv_addr);
495 assert_eq!(5000, connect_timeout.as_millis());
496 assert_eq!(10000, ddl_timeout.as_millis());
497 assert_eq!(3000, timeout.as_millis());
498 assert!(tcp_nodelay);
499 assert_eq!("./greptimedb_data/", options.storage.data_home);
500 assert!(matches!(
501 &options.storage.store,
502 ObjectStoreConfig::File(FileConfig { .. })
503 ));
504 assert_eq!(options.storage.providers.len(), 2);
505 assert!(matches!(
506 options.storage.providers[0],
507 ObjectStoreConfig::Gcs(GcsConfig { .. })
508 ));
509 assert!(matches!(
510 options.storage.providers[1],
511 ObjectStoreConfig::S3(S3Config { .. })
512 ));
513
514 assert_eq!("debug", options.logging.level.unwrap());
515 assert_eq!(
516 "./greptimedb_data/test/logs".to_string(),
517 options.logging.dir
518 );
519 }
520
521 #[test]
522 fn test_try_from_cmd() {
523 assert!(
524 (StartCommand {
525 metasrv_addrs: Some(vec!["127.0.0.1:3002".to_string()]),
526 ..Default::default()
527 })
528 .load_options(&GlobalOptions::default())
529 .is_err()
530 );
531
532 assert!(
534 (StartCommand {
535 node_id: Some(42),
536 ..Default::default()
537 })
538 .load_options(&GlobalOptions::default())
539 .is_ok()
540 );
541 }
542
543 #[test]
544 fn test_load_log_options_from_cli() {
545 let mut cmd = StartCommand::default();
546
547 let result = cmd.load_options(&GlobalOptions {
548 log_dir: Some("./greptimedb_data/test/logs".to_string()),
549 log_level: Some("debug".to_string()),
550
551 #[cfg(feature = "tokio-console")]
552 tokio_console_addr: None,
553 });
554 assert_matches!(result, Err(crate::error::Error::MissingConfig { .. }));
556
557 cmd.node_id = Some(42);
558
559 let options = cmd
560 .load_options(&GlobalOptions {
561 log_dir: Some("./greptimedb_data/test/logs".to_string()),
562 log_level: Some("debug".to_string()),
563
564 #[cfg(feature = "tokio-console")]
565 tokio_console_addr: None,
566 })
567 .unwrap()
568 .component;
569
570 let logging_opt = options.logging;
571 assert_eq!("./greptimedb_data/test/logs", logging_opt.dir);
572 assert_eq!("debug", logging_opt.level.as_ref().unwrap());
573 }
574
575 #[test]
576 fn test_config_precedence_order() {
577 let mut file = create_named_temp_file();
578 let toml_str = r#"
579 enable_memory_catalog = false
580 node_id = 42
581 rpc_addr = "127.0.0.1:3001"
582 rpc_runtime_size = 8
583 rpc_hostname = "10.103.174.219"
584
585 [meta_client]
586 timeout = "3s"
587 connect_timeout = "5s"
588 tcp_nodelay = true
589
590 [wal]
591 provider = "raft_engine"
592 file_size = "1GB"
593 purge_threshold = "50GB"
594 purge_interval = "5m"
595 sync_write = false
596
597 [storage]
598 type = "File"
599 data_home = "./greptimedb_data/"
600
601 [logging]
602 level = "debug"
603 dir = "./greptimedb_data/test/logs"
604 "#;
605 write!(file, "{}", toml_str).unwrap();
606
607 let env_prefix = "DATANODE_UT";
608 temp_env::with_vars(
609 [
610 (
611 [
613 env_prefix.to_string(),
614 "wal".to_uppercase(),
615 "purge_interval".to_uppercase(),
616 ]
617 .join(ENV_VAR_SEP),
618 Some("1m"),
619 ),
620 (
621 [
623 env_prefix.to_string(),
624 "wal".to_uppercase(),
625 "read_batch_size".to_uppercase(),
626 ]
627 .join(ENV_VAR_SEP),
628 Some("100"),
629 ),
630 (
631 [
633 env_prefix.to_string(),
634 "meta_client".to_uppercase(),
635 "metasrv_addrs".to_uppercase(),
636 ]
637 .join(ENV_VAR_SEP),
638 Some("127.0.0.1:3001,127.0.0.1:3002,127.0.0.1:3003"),
639 ),
640 ],
641 || {
642 let command = StartCommand {
643 config_file: Some(file.path().to_str().unwrap().to_string()),
644 wal_dir: Some("/other/wal/dir".to_string()),
645 env_prefix: env_prefix.to_string(),
646 ..Default::default()
647 };
648
649 let opts = command.load_options(&Default::default()).unwrap().component;
650
651 let DatanodeWalConfig::RaftEngine(raft_engine_config) = opts.wal else {
653 unreachable!()
654 };
655 assert_eq!(raft_engine_config.read_batch_size, 100);
656 assert_eq!(
657 opts.meta_client.unwrap().metasrv_addrs,
658 vec![
659 "127.0.0.1:3001".to_string(),
660 "127.0.0.1:3002".to_string(),
661 "127.0.0.1:3003".to_string()
662 ]
663 );
664
665 assert_eq!(
667 raft_engine_config.purge_interval,
668 Duration::from_secs(60 * 5)
669 );
670
671 assert_eq!(raft_engine_config.dir.unwrap(), "/other/wal/dir");
673
674 assert_eq!(
676 opts.http.addr,
677 DatanodeOptions::default().component.http.addr
678 );
679 assert_eq!(opts.grpc.server_addr, "10.103.174.219");
680 },
681 );
682 }
683
684 #[test]
685 fn test_parse_grpc_cli_aliases() {
686 let command = StartCommand::try_parse_from([
687 "datanode",
688 "--grpc-bind-addr",
689 "127.0.0.1:13001",
690 "--grpc-server-addr",
691 "10.0.0.1:13001",
692 ])
693 .unwrap();
694 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:13001"));
695 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.1:13001"));
696
697 let command = StartCommand::try_parse_from([
698 "datanode",
699 "--rpc-bind-addr",
700 "127.0.0.1:23001",
701 "--rpc-server-addr",
702 "10.0.0.2:23001",
703 ])
704 .unwrap();
705 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:23001"));
706 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.2:23001"));
707
708 let command = StartCommand::try_parse_from([
709 "datanode",
710 "--rpc-addr",
711 "127.0.0.1:33001",
712 "--rpc-hostname",
713 "10.0.0.3:33001",
714 ])
715 .unwrap();
716 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:33001"));
717 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.3:33001"));
718 }
719
720 #[test]
721 fn test_help_uses_grpc_option_names() {
722 let mut cmd = StartCommand::command();
723 let mut help = Vec::new();
724 cmd.write_long_help(&mut help).unwrap();
725 let help = String::from_utf8(help).unwrap();
726
727 assert!(help.contains("--grpc-bind-addr"));
728 assert!(help.contains("--grpc-server-addr"));
729 assert!(!help.contains("--rpc-bind-addr"));
730 assert!(!help.contains("--rpc-server-addr"));
731 assert!(!help.contains("--rpc-addr"));
732 assert!(!help.contains("--rpc-hostname"));
733 }
734}