Skip to main content

sqlness_runner/
server_mode.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
15use std::collections::{HashMap, HashSet};
16use std::path::Path;
17use std::sync::{Mutex, OnceLock};
18
19use serde::Serialize;
20use tinytemplate::TinyTemplate;
21
22use crate::cmd::bare::ServerAddr;
23use crate::cmd::compat_case::Version;
24use crate::env::bare::{Env, GreptimeDBContext, ServiceProvider};
25use crate::util;
26
27const DEFAULT_LOG_LEVEL: &str = "--log-level=debug,hyper=warn,tower=warn,datafusion=warn,reqwest=warn,sqlparser=warn,h2=info,opendal=info";
28
29/// Which set of gRPC CLI argument names to use when spawning a GreptimeDB binary.
30///
31/// The CLI rename from `rpc-*` to `grpc-*` landed before v1.1.0.  Older release
32/// binaries (e.g. v1.0.0) only recognize `--rpc-bind-addr` and
33/// `--rpc-server-addr`; v1.1.0+ and current binaries use `--grpc-*` names while
34/// keeping the old names as hidden aliases.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum GrpcArgStyle {
37    /// Current-style: `--grpc-bind-addr` / `--grpc-server-addr`
38    Grpc,
39    /// Legacy-style: `--rpc-bind-addr` / `--rpc-server-addr`
40    Rpc,
41}
42
43impl GrpcArgStyle {
44    /// Chooses the argument style from an inferred GreptimeDB binary version.
45    ///
46    /// Unknown versions are treated as current/development binaries and use the
47    /// official `grpc-*` names.
48    pub(crate) fn for_version(version: Option<&Version>) -> Self {
49        const GRPC_ARG_RENAME_VERSION: Version = Version {
50            major: 1,
51            minor: 1,
52            patch: 0,
53        };
54
55        if version.is_some_and(|version| version < &GRPC_ARG_RENAME_VERSION) {
56            GrpcArgStyle::Rpc
57        } else {
58            GrpcArgStyle::Grpc
59        }
60    }
61
62    /// Returns the CLI flag name for the gRPC bind address.
63    pub fn bind_addr_arg(self) -> &'static str {
64        match self {
65            GrpcArgStyle::Grpc => "--grpc-bind-addr",
66            GrpcArgStyle::Rpc => "--rpc-bind-addr",
67        }
68    }
69
70    /// Returns the CLI flag name for the gRPC server (advertised) address.
71    pub fn server_addr_arg(self) -> &'static str {
72        match self {
73            GrpcArgStyle::Grpc => "--grpc-server-addr",
74            GrpcArgStyle::Rpc => "--rpc-server-addr",
75        }
76    }
77}
78
79static USED_PORTS: OnceLock<Mutex<HashSet<u16>>> = OnceLock::new();
80
81fn get_used_ports() -> &'static Mutex<HashSet<u16>> {
82    USED_PORTS.get_or_init(|| Mutex::new(HashSet::new()))
83}
84
85fn get_unique_random_port() -> u16 {
86    // Tricky loop 100 times to find an unused port instead of infinite loop.
87    const MAX_ATTEMPTS: usize = 100;
88
89    for _ in 0..MAX_ATTEMPTS {
90        let p = util::get_random_port();
91        let mut used = get_used_ports().lock().unwrap();
92        if !used.contains(&p) {
93            used.insert(p);
94            return p;
95        }
96    }
97
98    panic!(
99        "Failed to find an unused port after {} attempts",
100        MAX_ATTEMPTS
101    );
102}
103
104#[derive(Clone)]
105pub enum ServerMode {
106    Standalone {
107        http_addr: String,
108        rpc_bind_addr: String,
109        mysql_addr: String,
110        postgres_addr: String,
111    },
112    Frontend {
113        http_addr: String,
114        rpc_bind_addr: String,
115        mysql_addr: String,
116        postgres_addr: String,
117        metasrv_addr: String,
118    },
119    Metasrv {
120        rpc_bind_addr: String,
121        rpc_server_addr: String,
122        http_addr: String,
123    },
124    Datanode {
125        rpc_bind_addr: String,
126        rpc_server_addr: String,
127        http_addr: String,
128        metasrv_addr: String,
129        node_id: u32,
130    },
131    Flownode {
132        rpc_bind_addr: String,
133        rpc_server_addr: String,
134        http_addr: String,
135        metasrv_addr: String,
136        node_id: u32,
137    },
138}
139
140#[derive(Serialize)]
141struct ConfigContext {
142    wal_dir: String,
143    data_home: String,
144    copy_root: String,
145    procedure_dir: String,
146    is_raft_engine: bool,
147    kafka_wal_broker_endpoints: String,
148    use_etcd: bool,
149    store_addrs: String,
150    instance_id: usize,
151    addrs: HashMap<String, String>,
152    // enable flat format for storage engine
153    enable_flat_format: bool,
154    // enable garbage collection in metasrv and datanodes
155    enable_gc: bool,
156}
157
158impl ServerMode {
159    pub fn random_standalone() -> Self {
160        let http_port = get_unique_random_port();
161        let rpc_port = get_unique_random_port();
162        let mysql_port = get_unique_random_port();
163        let postgres_port = get_unique_random_port();
164
165        ServerMode::Standalone {
166            http_addr: format!("127.0.0.1:{http_port}"),
167            rpc_bind_addr: format!("127.0.0.1:{rpc_port}"),
168            mysql_addr: format!("127.0.0.1:{mysql_port}"),
169            postgres_addr: format!("127.0.0.1:{postgres_port}"),
170        }
171    }
172
173    pub fn random_frontend(metasrv_port: u16) -> Self {
174        let http_port = get_unique_random_port();
175        let rpc_port = get_unique_random_port();
176        let mysql_port = get_unique_random_port();
177        let postgres_port = get_unique_random_port();
178
179        ServerMode::Frontend {
180            http_addr: format!("127.0.0.1:{http_port}"),
181            rpc_bind_addr: format!("127.0.0.1:{rpc_port}"),
182            mysql_addr: format!("127.0.0.1:{mysql_port}"),
183            postgres_addr: format!("127.0.0.1:{postgres_port}"),
184            metasrv_addr: format!("127.0.0.1:{metasrv_port}"),
185        }
186    }
187
188    pub fn random_metasrv() -> Self {
189        let bind_port = get_unique_random_port();
190        let http_port = get_unique_random_port();
191
192        ServerMode::Metasrv {
193            rpc_bind_addr: format!("127.0.0.1:{bind_port}"),
194            rpc_server_addr: format!("127.0.0.1:{bind_port}"),
195            http_addr: format!("127.0.0.1:{http_port}"),
196        }
197    }
198
199    pub fn random_datanode(metasrv_port: u16, node_id: u32) -> Self {
200        let rpc_port = get_unique_random_port();
201        let http_port = get_unique_random_port();
202
203        ServerMode::Datanode {
204            rpc_bind_addr: format!("127.0.0.1:{rpc_port}"),
205            rpc_server_addr: format!("127.0.0.1:{rpc_port}"),
206            http_addr: format!("127.0.0.1:{http_port}"),
207            metasrv_addr: format!("127.0.0.1:{metasrv_port}"),
208            node_id,
209        }
210    }
211
212    pub fn random_flownode(metasrv_port: u16, node_id: u32) -> Self {
213        let rpc_port = get_unique_random_port();
214        let http_port = get_unique_random_port();
215
216        ServerMode::Flownode {
217            rpc_bind_addr: format!("127.0.0.1:{rpc_port}"),
218            rpc_server_addr: format!("127.0.0.1:{rpc_port}"),
219            http_addr: format!("127.0.0.1:{http_port}"),
220            metasrv_addr: format!("127.0.0.1:{metasrv_port}"),
221            node_id,
222        }
223    }
224
225    pub fn name(&self) -> &'static str {
226        match self {
227            ServerMode::Standalone { .. } => "standalone",
228            ServerMode::Frontend { .. } => "frontend",
229            ServerMode::Metasrv { .. } => "metasrv",
230            ServerMode::Datanode { .. } => "datanode",
231            ServerMode::Flownode { .. } => "flownode",
232        }
233    }
234
235    /// Returns the addresses of the server that needed to be checked.
236    pub fn check_addrs(&self) -> Vec<String> {
237        match self {
238            ServerMode::Standalone {
239                rpc_bind_addr,
240                mysql_addr,
241                postgres_addr,
242                http_addr,
243                ..
244            } => {
245                vec![
246                    rpc_bind_addr.clone(),
247                    mysql_addr.clone(),
248                    postgres_addr.clone(),
249                    http_addr.clone(),
250                ]
251            }
252            ServerMode::Frontend {
253                rpc_bind_addr,
254                mysql_addr,
255                postgres_addr,
256                ..
257            } => {
258                vec![
259                    rpc_bind_addr.clone(),
260                    mysql_addr.clone(),
261                    postgres_addr.clone(),
262                ]
263            }
264            ServerMode::Metasrv { rpc_bind_addr, .. } => {
265                vec![rpc_bind_addr.clone()]
266            }
267            ServerMode::Datanode { rpc_bind_addr, .. } => {
268                vec![rpc_bind_addr.clone()]
269            }
270            ServerMode::Flownode { rpc_bind_addr, .. } => {
271                vec![rpc_bind_addr.clone()]
272            }
273        }
274    }
275
276    /// Returns the server addresses to connect. Only standalone and frontend mode have this.
277    pub fn server_addr(&self) -> Option<ServerAddr> {
278        match self {
279            ServerMode::Standalone {
280                rpc_bind_addr,
281                mysql_addr,
282                postgres_addr,
283                ..
284            } => Some(ServerAddr {
285                server_addr: Some(rpc_bind_addr.clone()),
286                pg_server_addr: Some(postgres_addr.clone()),
287                mysql_server_addr: Some(mysql_addr.clone()),
288            }),
289            ServerMode::Frontend {
290                rpc_bind_addr,
291                mysql_addr,
292                postgres_addr,
293                ..
294            } => Some(ServerAddr {
295                server_addr: Some(rpc_bind_addr.clone()),
296                pg_server_addr: Some(postgres_addr.clone()),
297                mysql_server_addr: Some(mysql_addr.clone()),
298            }),
299            _ => None,
300        }
301    }
302
303    pub fn generate_config_file(
304        &self,
305        sqlness_home: &Path,
306        db_ctx: &GreptimeDBContext,
307        id: usize,
308    ) -> String {
309        let mut tt = TinyTemplate::new();
310
311        let mut path = util::sqlness_conf_path();
312        path.push(format!("{}-test.toml.template", self.name()));
313        let template = std::fs::read_to_string(&path)
314            .unwrap_or_else(|e| panic!("read file '{}' error: {e}", path.display()));
315        tt.add_template(self.name(), &template).unwrap();
316
317        let data_home = sqlness_home.join(format!("greptimedb-{}-{}", id, self.name()));
318        std::fs::create_dir_all(data_home.as_path()).unwrap();
319
320        let wal_dir = data_home.join("wal").display().to_string();
321        let procedure_dir = data_home.join("procedure").display().to_string();
322
323        // Get the required addresses based on server mode
324        let addrs: HashMap<String, String> = match self {
325            ServerMode::Standalone {
326                rpc_bind_addr,
327                mysql_addr,
328                postgres_addr,
329                http_addr,
330            } => [
331                ("http_addr".to_string(), http_addr.clone()),
332                ("grpc_addr".to_string(), rpc_bind_addr.clone()),
333                ("mysql_addr".to_string(), mysql_addr.clone()),
334                ("postgres_addr".to_string(), postgres_addr.clone()),
335            ]
336            .into(),
337            ServerMode::Frontend { rpc_bind_addr, .. } => {
338                [("grpc_addr".to_string(), rpc_bind_addr.clone())].into()
339            }
340            ServerMode::Datanode { metasrv_addr, .. } => {
341                [("metasrv_addr".to_string(), metasrv_addr.clone())].into()
342            }
343            _ => HashMap::new(),
344        };
345
346        let ctx = ConfigContext {
347            wal_dir,
348            data_home: data_home.display().to_string(),
349            copy_root: sqlness_home.join("copy").display().to_string(),
350            procedure_dir,
351            is_raft_engine: db_ctx.is_raft_engine(),
352            kafka_wal_broker_endpoints: db_ctx.kafka_wal_broker_endpoints(),
353            use_etcd: !db_ctx.store_config().store_addrs.is_empty(),
354            store_addrs: db_ctx
355                .store_config()
356                .store_addrs
357                .iter()
358                .map(|p| format!("\"{p}\""))
359                .collect::<Vec<_>>()
360                .join(","),
361            instance_id: id,
362            addrs,
363            enable_flat_format: db_ctx.store_config().enable_flat_format,
364            enable_gc: db_ctx.store_config().enable_gc,
365        };
366
367        let rendered = tt.render(self.name(), &ctx).unwrap();
368
369        let conf_file = data_home
370            .join(format!("{}-{}-{}.toml", self.name(), id, db_ctx.time()))
371            .display()
372            .to_string();
373        println!(
374            "Generating id {}, {} config file in {conf_file}",
375            id,
376            self.name()
377        );
378        std::fs::write(&conf_file, rendered).unwrap();
379
380        conf_file
381    }
382
383    pub fn get_args(
384        &self,
385        sqlness_home: &Path,
386        env: &Env,
387        db_ctx: &GreptimeDBContext,
388        id: usize,
389        arg_style: GrpcArgStyle,
390    ) -> Vec<String> {
391        let mut args = env
392            .extra_args()
393            .iter()
394            .map(String::as_str)
395            .chain([DEFAULT_LOG_LEVEL, self.name(), "start"])
396            .map(ToString::to_string)
397            .collect::<Vec<String>>();
398
399        match self {
400            ServerMode::Standalone {
401                http_addr,
402                rpc_bind_addr,
403                mysql_addr,
404                postgres_addr,
405            } => {
406                args.extend([
407                    format!(
408                        "--log-dir={}/greptimedb-{}-standalone/logs",
409                        sqlness_home.display(),
410                        id
411                    ),
412                    "-c".to_string(),
413                    self.generate_config_file(sqlness_home, db_ctx, id),
414                    format!("--http-addr={http_addr}"),
415                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
416                    format!("--mysql-addr={mysql_addr}"),
417                    format!("--postgres-addr={postgres_addr}"),
418                ]);
419            }
420            ServerMode::Frontend {
421                http_addr,
422                rpc_bind_addr,
423                mysql_addr,
424                postgres_addr,
425                metasrv_addr,
426            } => {
427                args.extend([
428                    format!("--metasrv-addrs={metasrv_addr}"),
429                    format!("--http-addr={http_addr}"),
430                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
431                    // since sqlness run on local, bind addr is the same as server addr
432                    // this is needed so that `cluster_info`'s server addr column can be correct
433                    format!("{}={rpc_bind_addr}", arg_style.server_addr_arg()),
434                    format!("--mysql-addr={mysql_addr}"),
435                    format!("--postgres-addr={postgres_addr}"),
436                    format!(
437                        "--log-dir={}/greptimedb-{}-frontend/logs",
438                        sqlness_home.display(),
439                        id
440                    ),
441                    "-c".to_string(),
442                    self.generate_config_file(sqlness_home, db_ctx, id),
443                ]);
444            }
445            ServerMode::Metasrv {
446                rpc_bind_addr,
447                rpc_server_addr,
448                http_addr,
449            } => {
450                args.extend([
451                    arg_style.bind_addr_arg().to_string(),
452                    rpc_bind_addr.clone(),
453                    arg_style.server_addr_arg().to_string(),
454                    rpc_server_addr.clone(),
455                    "--enable-region-failover".to_string(),
456                    "false".to_string(),
457                    format!("--http-addr={http_addr}"),
458                    format!(
459                        "--log-dir={}/greptimedb-{}-metasrv/logs",
460                        sqlness_home.display(),
461                        id
462                    ),
463                    "-c".to_string(),
464                    self.generate_config_file(sqlness_home, db_ctx, id),
465                ]);
466
467                if matches!(
468                    db_ctx.store_config().setup_pg,
469                    Some(ServiceProvider::Create)
470                ) {
471                    let client_ports = db_ctx
472                        .store_config()
473                        .store_addrs
474                        .iter()
475                        .map(|s| s.split(':').nth(1).unwrap().parse::<u16>().unwrap())
476                        .collect::<Vec<_>>();
477                    let client_port = client_ports.first().unwrap_or(&5432);
478                    let pg_server_addr = format!(
479                        "postgresql://greptimedb:admin@127.0.0.1:{}/postgres",
480                        client_port
481                    );
482                    args.extend(vec!["--backend".to_string(), "postgres-store".to_string()]);
483                    args.extend(vec!["--store-addrs".to_string(), pg_server_addr]);
484                } else if let Some(ServiceProvider::External(connection_string)) =
485                    db_ctx.store_config().setup_pg
486                {
487                    println!("Using external PostgreSQL '{connection_string}' as Kvbackend");
488                    args.extend([
489                        "--backend".to_string(),
490                        "postgres-store".to_string(),
491                        "--store-addrs".to_string(),
492                        connection_string,
493                    ]);
494                } else if matches!(
495                    db_ctx.store_config().setup_mysql,
496                    Some(ServiceProvider::Create)
497                ) {
498                    let client_ports = db_ctx
499                        .store_config()
500                        .store_addrs
501                        .iter()
502                        .map(|s| s.split(':').nth(1).unwrap().parse::<u16>().unwrap())
503                        .collect::<Vec<_>>();
504                    let client_port = client_ports.first().unwrap_or(&3306);
505                    let mysql_server_addr =
506                        format!("mysql://greptimedb:admin@127.0.0.1:{}/mysql", client_port);
507                    args.extend(vec!["--backend".to_string(), "mysql-store".to_string()]);
508                    args.extend(vec!["--store-addrs".to_string(), mysql_server_addr]);
509                } else if let Some(ServiceProvider::External(connection_string)) =
510                    db_ctx.store_config().setup_mysql
511                {
512                    println!("Using external MySQL '{connection_string}' as Kvbackend");
513                    args.extend([
514                        "--backend".to_string(),
515                        "mysql-store".to_string(),
516                        "--store-addrs".to_string(),
517                        connection_string,
518                    ]);
519                } else if db_ctx.store_config().store_addrs.is_empty() {
520                    args.extend(vec!["--backend".to_string(), "memory-store".to_string()])
521                }
522            }
523            ServerMode::Datanode {
524                rpc_bind_addr,
525                rpc_server_addr,
526                http_addr,
527                metasrv_addr,
528                node_id,
529            } => {
530                let data_home = sqlness_home.join(format!(
531                    "greptimedb_{}_datanode_{}_{node_id}",
532                    id,
533                    db_ctx.time()
534                ));
535                args.extend([
536                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
537                    format!("{}={rpc_server_addr}", arg_style.server_addr_arg()),
538                    format!("--http-addr={http_addr}"),
539                    format!("--data-home={}", data_home.display()),
540                    format!("--log-dir={}/logs", data_home.display()),
541                    format!("--node-id={node_id}"),
542                    "-c".to_string(),
543                    self.generate_config_file(sqlness_home, db_ctx, id),
544                    format!("--metasrv-addrs={metasrv_addr}"),
545                ]);
546            }
547            ServerMode::Flownode {
548                rpc_bind_addr,
549                rpc_server_addr,
550                http_addr,
551                metasrv_addr,
552                node_id,
553            } => {
554                args.extend([
555                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
556                    format!("{}={rpc_server_addr}", arg_style.server_addr_arg()),
557                    format!("--node-id={node_id}"),
558                    format!(
559                        "--log-dir={}/greptimedb-{}-flownode/logs",
560                        sqlness_home.display(),
561                        id
562                    ),
563                    format!("--metasrv-addrs={metasrv_addr}"),
564                    format!("--http-addr={http_addr}"),
565                ]);
566            }
567        }
568
569        args
570    }
571}
572
573#[cfg(test)]
574mod tests {
575    use std::path::PathBuf;
576
577    use super::*;
578    use crate::env::bare::{StoreConfig, WalConfig};
579
580    fn test_env(sqlness_home: &Path) -> (Env, GreptimeDBContext) {
581        let store_config = StoreConfig {
582            store_addrs: vec!["127.0.0.1:2379".to_string()],
583            setup_etcd: true,
584            setup_pg: None,
585            setup_mysql: None,
586            enable_flat_format: false,
587            enable_gc: false,
588        };
589        let env = Env::new(
590            sqlness_home.to_path_buf(),
591            ServerAddr::default(),
592            WalConfig::RaftEngine,
593            false,
594            Some(PathBuf::from(".")),
595            store_config.clone(),
596            vec![],
597        );
598        let db_ctx = GreptimeDBContext::new(WalConfig::RaftEngine, store_config);
599
600        (env, db_ctx)
601    }
602
603    fn has_arg(args: &[String], name: &str) -> bool {
604        let prefix = format!("{name}=");
605        args.iter()
606            .any(|arg| arg == name || arg.starts_with(&prefix))
607    }
608
609    fn assert_uses_style(args: &[String], style: GrpcArgStyle, expect_server_addr: bool) {
610        let bind = style.bind_addr_arg();
611        let server = style.server_addr_arg();
612        let other_bind = match style {
613            GrpcArgStyle::Grpc => "--rpc-bind-addr",
614            GrpcArgStyle::Rpc => "--grpc-bind-addr",
615        };
616        let other_server = match style {
617            GrpcArgStyle::Grpc => "--rpc-server-addr",
618            GrpcArgStyle::Rpc => "--grpc-server-addr",
619        };
620
621        assert!(has_arg(args, bind), "missing {bind} in args: {args:?}");
622        assert!(
623            !args.iter().any(|arg| arg.contains(other_bind)),
624            "unexpected {other_bind} in args: {args:?}"
625        );
626
627        if expect_server_addr {
628            assert!(has_arg(args, server), "missing {server} in args: {args:?}");
629            assert!(
630                !args.iter().any(|arg| arg.contains(other_server)),
631                "unexpected {other_server} in args: {args:?}"
632            );
633        }
634    }
635
636    fn test_all_modes(env: &Env, db_ctx: &GreptimeDBContext, temp_dir: &Path, style: GrpcArgStyle) {
637        let standalone = ServerMode::Standalone {
638            http_addr: "127.0.0.1:4000".to_string(),
639            rpc_bind_addr: "127.0.0.1:4001".to_string(),
640            mysql_addr: "127.0.0.1:4002".to_string(),
641            postgres_addr: "127.0.0.1:4003".to_string(),
642        };
643        assert_uses_style(
644            &standalone.get_args(temp_dir, env, db_ctx, 0, style),
645            style,
646            false,
647        );
648
649        let frontend = ServerMode::Frontend {
650            http_addr: "127.0.0.1:4100".to_string(),
651            rpc_bind_addr: "127.0.0.1:4101".to_string(),
652            mysql_addr: "127.0.0.1:4102".to_string(),
653            postgres_addr: "127.0.0.1:4103".to_string(),
654            metasrv_addr: "127.0.0.1:4001".to_string(),
655        };
656        assert_uses_style(
657            &frontend.get_args(temp_dir, env, db_ctx, 0, style),
658            style,
659            true,
660        );
661
662        let metasrv = ServerMode::Metasrv {
663            rpc_bind_addr: "127.0.0.1:4201".to_string(),
664            rpc_server_addr: "127.0.0.1:4201".to_string(),
665            http_addr: "127.0.0.1:4200".to_string(),
666        };
667        assert_uses_style(
668            &metasrv.get_args(temp_dir, env, db_ctx, 0, style),
669            style,
670            true,
671        );
672
673        let datanode = ServerMode::Datanode {
674            rpc_bind_addr: "127.0.0.1:4301".to_string(),
675            rpc_server_addr: "127.0.0.1:4301".to_string(),
676            http_addr: "127.0.0.1:4300".to_string(),
677            metasrv_addr: "127.0.0.1:4001".to_string(),
678            node_id: 0,
679        };
680        assert_uses_style(
681            &datanode.get_args(temp_dir, env, db_ctx, 0, style),
682            style,
683            true,
684        );
685
686        let flownode = ServerMode::Flownode {
687            rpc_bind_addr: "127.0.0.1:4401".to_string(),
688            rpc_server_addr: "127.0.0.1:4401".to_string(),
689            http_addr: "127.0.0.1:4400".to_string(),
690            metasrv_addr: "127.0.0.1:4001".to_string(),
691            node_id: 0,
692        };
693        assert_uses_style(
694            &flownode.get_args(temp_dir, env, db_ctx, 0, style),
695            style,
696            true,
697        );
698    }
699
700    #[test]
701    fn test_get_args_with_grpc_style() {
702        let temp_dir = tempfile::tempdir().unwrap();
703        let (env, db_ctx) = test_env(temp_dir.path());
704        test_all_modes(&env, &db_ctx, temp_dir.path(), GrpcArgStyle::Grpc);
705    }
706
707    #[test]
708    fn test_get_args_with_rpc_style() {
709        let temp_dir = tempfile::tempdir().unwrap();
710        let (env, db_ctx) = test_env(temp_dir.path());
711        test_all_modes(&env, &db_ctx, temp_dir.path(), GrpcArgStyle::Rpc);
712    }
713
714    #[test]
715    fn test_arg_style_for_unknown_version_defaults_to_grpc() {
716        assert_eq!(GrpcArgStyle::for_version(None), GrpcArgStyle::Grpc);
717    }
718
719    #[test]
720    fn test_arg_style_for_legacy_versions_uses_rpc() {
721        let v1_0_0 = Version::parse("v1.0.0").unwrap();
722        let v1_0_9 = Version::parse("v1.0.9").unwrap();
723
724        assert_eq!(GrpcArgStyle::for_version(Some(&v1_0_0)), GrpcArgStyle::Rpc);
725        assert_eq!(GrpcArgStyle::for_version(Some(&v1_0_9)), GrpcArgStyle::Rpc);
726    }
727
728    #[test]
729    fn test_arg_style_for_current_versions_uses_grpc() {
730        let v1_1_0 = Version::parse("v1.1.0").unwrap();
731        let v1_2_0 = Version::parse("v1.2.0").unwrap();
732
733        assert_eq!(GrpcArgStyle::for_version(Some(&v1_1_0)), GrpcArgStyle::Grpc);
734        assert_eq!(GrpcArgStyle::for_version(Some(&v1_2_0)), GrpcArgStyle::Grpc);
735    }
736
737    #[test]
738    fn test_generate_distributed_gc_config_when_enabled() {
739        let temp_dir = tempfile::tempdir().unwrap();
740        let store_config = StoreConfig {
741            store_addrs: vec![],
742            setup_etcd: false,
743            setup_pg: None,
744            setup_mysql: None,
745            enable_flat_format: false,
746            enable_gc: true,
747        };
748        let db_ctx = GreptimeDBContext::new(WalConfig::RaftEngine, store_config);
749        let metasrv = ServerMode::Metasrv {
750            rpc_bind_addr: "127.0.0.1:4201".to_string(),
751            rpc_server_addr: "127.0.0.1:4201".to_string(),
752            http_addr: "127.0.0.1:4200".to_string(),
753        };
754        let datanode = ServerMode::Datanode {
755            rpc_bind_addr: "127.0.0.1:4301".to_string(),
756            rpc_server_addr: "127.0.0.1:4301".to_string(),
757            http_addr: "127.0.0.1:4300".to_string(),
758            metasrv_addr: "127.0.0.1:4201".to_string(),
759            node_id: 0,
760        };
761
762        let metasrv_config =
763            std::fs::read_to_string(metasrv.generate_config_file(temp_dir.path(), &db_ctx, 0))
764                .unwrap();
765        let datanode_config =
766            std::fs::read_to_string(datanode.generate_config_file(temp_dir.path(), &db_ctx, 0))
767                .unwrap();
768
769        assert!(metasrv_config.contains("[gc]\nenable = true"));
770        assert!(metasrv_config.contains("[gc.experimental_soft_drop]\nenable = true"));
771        assert!(datanode_config.contains("[region_engine.mito.gc]\nenable = true"));
772    }
773}