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::{CompatConfigStage, 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        compat_stage: &CompatConfigStage,
309    ) -> String {
310        let mut tt = TinyTemplate::new();
311
312        let mut path = util::sqlness_conf_path();
313        path.push(format!("{}-test.toml.template", self.name()));
314        let template = std::fs::read_to_string(&path)
315            .unwrap_or_else(|e| panic!("read file '{}' error: {e}", path.display()));
316        tt.add_template(self.name(), &template).unwrap();
317
318        let data_home = sqlness_home.join(format!("greptimedb-{}-{}", id, self.name()));
319        std::fs::create_dir_all(data_home.as_path()).unwrap();
320
321        let wal_dir = data_home.join("wal").display().to_string();
322        let procedure_dir = data_home.join("procedure").display().to_string();
323
324        // Get the required addresses based on server mode
325        let addrs: HashMap<String, String> = match self {
326            ServerMode::Standalone {
327                rpc_bind_addr,
328                mysql_addr,
329                postgres_addr,
330                http_addr,
331            } => [
332                ("http_addr".to_string(), http_addr.clone()),
333                ("grpc_addr".to_string(), rpc_bind_addr.clone()),
334                ("mysql_addr".to_string(), mysql_addr.clone()),
335                ("postgres_addr".to_string(), postgres_addr.clone()),
336            ]
337            .into(),
338            ServerMode::Frontend { rpc_bind_addr, .. } => {
339                [("grpc_addr".to_string(), rpc_bind_addr.clone())].into()
340            }
341            ServerMode::Datanode { metasrv_addr, .. } => {
342                [("metasrv_addr".to_string(), metasrv_addr.clone())].into()
343            }
344            _ => HashMap::new(),
345        };
346
347        let ctx = ConfigContext {
348            wal_dir,
349            data_home: data_home.display().to_string(),
350            copy_root: sqlness_home.join("copy").display().to_string(),
351            procedure_dir,
352            is_raft_engine: db_ctx.is_raft_engine(),
353            kafka_wal_broker_endpoints: db_ctx.kafka_wal_broker_endpoints(),
354            use_etcd: !db_ctx.store_config().store_addrs.is_empty(),
355            store_addrs: db_ctx
356                .store_config()
357                .store_addrs
358                .iter()
359                .map(|p| format!("\"{p}\""))
360                .collect::<Vec<_>>()
361                .join(","),
362            instance_id: id,
363            addrs,
364            enable_flat_format: db_ctx.store_config().enable_flat_format,
365            enable_gc: db_ctx.store_config().enable_gc,
366        };
367
368        let rendered = tt.render(self.name(), &ctx).unwrap();
369        let rendered = match (self, compat_stage) {
370            (ServerMode::Datanode { .. }, CompatConfigStage::Old(overlay)) => overlay
371                .apply_to_rendered_baseline(&rendered)
372                .unwrap_or_else(|error| {
373                    panic!(
374                        "Failed to apply old datanode overlay {}: {error}",
375                        overlay.source().display()
376                    )
377                }),
378            _ => rendered,
379        };
380
381        let stage_suffix = match compat_stage {
382            CompatConfigStage::Baseline => "baseline",
383            CompatConfigStage::Old(_) => "old",
384            CompatConfigStage::Current => "current",
385        };
386        let conf_file = data_home
387            .join(if matches!(self, ServerMode::Datanode { .. }) {
388                format!(
389                    "{}-{}-{}-{stage_suffix}.toml",
390                    self.name(),
391                    id,
392                    db_ctx.time()
393                )
394            } else {
395                format!("{}-{}-{}.toml", self.name(), id, db_ctx.time())
396            })
397            .display()
398            .to_string();
399        println!(
400            "Generating id {}, {} config file in {conf_file}",
401            id,
402            self.name()
403        );
404        std::fs::write(&conf_file, rendered).unwrap();
405
406        conf_file
407    }
408
409    pub fn get_args(
410        &self,
411        sqlness_home: &Path,
412        env: &Env,
413        db_ctx: &GreptimeDBContext,
414        id: usize,
415        arg_style: GrpcArgStyle,
416        compat_stage: &CompatConfigStage,
417    ) -> Vec<String> {
418        let mut args = env
419            .extra_args()
420            .iter()
421            .map(String::as_str)
422            .chain([DEFAULT_LOG_LEVEL, self.name(), "start"])
423            .map(ToString::to_string)
424            .collect::<Vec<String>>();
425
426        match self {
427            ServerMode::Standalone {
428                http_addr,
429                rpc_bind_addr,
430                mysql_addr,
431                postgres_addr,
432            } => {
433                args.extend([
434                    format!(
435                        "--log-dir={}/greptimedb-{}-standalone/logs",
436                        sqlness_home.display(),
437                        id
438                    ),
439                    "-c".to_string(),
440                    self.generate_config_file(sqlness_home, db_ctx, id, compat_stage),
441                    format!("--http-addr={http_addr}"),
442                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
443                    format!("--mysql-addr={mysql_addr}"),
444                    format!("--postgres-addr={postgres_addr}"),
445                ]);
446            }
447            ServerMode::Frontend {
448                http_addr,
449                rpc_bind_addr,
450                mysql_addr,
451                postgres_addr,
452                metasrv_addr,
453            } => {
454                args.extend([
455                    format!("--metasrv-addrs={metasrv_addr}"),
456                    format!("--http-addr={http_addr}"),
457                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
458                    // since sqlness run on local, bind addr is the same as server addr
459                    // this is needed so that `cluster_info`'s server addr column can be correct
460                    format!("{}={rpc_bind_addr}", arg_style.server_addr_arg()),
461                    format!("--mysql-addr={mysql_addr}"),
462                    format!("--postgres-addr={postgres_addr}"),
463                    format!(
464                        "--log-dir={}/greptimedb-{}-frontend/logs",
465                        sqlness_home.display(),
466                        id
467                    ),
468                    "-c".to_string(),
469                    self.generate_config_file(sqlness_home, db_ctx, id, compat_stage),
470                ]);
471            }
472            ServerMode::Metasrv {
473                rpc_bind_addr,
474                rpc_server_addr,
475                http_addr,
476            } => {
477                args.extend([
478                    arg_style.bind_addr_arg().to_string(),
479                    rpc_bind_addr.clone(),
480                    arg_style.server_addr_arg().to_string(),
481                    rpc_server_addr.clone(),
482                    "--enable-region-failover".to_string(),
483                    "false".to_string(),
484                    format!("--http-addr={http_addr}"),
485                    format!(
486                        "--log-dir={}/greptimedb-{}-metasrv/logs",
487                        sqlness_home.display(),
488                        id
489                    ),
490                    "-c".to_string(),
491                    self.generate_config_file(sqlness_home, db_ctx, id, compat_stage),
492                ]);
493
494                if matches!(
495                    db_ctx.store_config().setup_pg,
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(&5432);
505                    let pg_server_addr = format!(
506                        "postgresql://greptimedb:admin@127.0.0.1:{}/postgres",
507                        client_port
508                    );
509                    args.extend(vec!["--backend".to_string(), "postgres-store".to_string()]);
510                    args.extend(vec!["--store-addrs".to_string(), pg_server_addr]);
511                } else if let Some(ServiceProvider::External(connection_string)) =
512                    db_ctx.store_config().setup_pg
513                {
514                    println!("Using external PostgreSQL '{connection_string}' as Kvbackend");
515                    args.extend([
516                        "--backend".to_string(),
517                        "postgres-store".to_string(),
518                        "--store-addrs".to_string(),
519                        connection_string,
520                    ]);
521                } else if matches!(
522                    db_ctx.store_config().setup_mysql,
523                    Some(ServiceProvider::Create)
524                ) {
525                    let client_ports = db_ctx
526                        .store_config()
527                        .store_addrs
528                        .iter()
529                        .map(|s| s.split(':').nth(1).unwrap().parse::<u16>().unwrap())
530                        .collect::<Vec<_>>();
531                    let client_port = client_ports.first().unwrap_or(&3306);
532                    let mysql_server_addr =
533                        format!("mysql://greptimedb:admin@127.0.0.1:{}/mysql", client_port);
534                    args.extend(vec!["--backend".to_string(), "mysql-store".to_string()]);
535                    args.extend(vec!["--store-addrs".to_string(), mysql_server_addr]);
536                } else if let Some(ServiceProvider::External(connection_string)) =
537                    db_ctx.store_config().setup_mysql
538                {
539                    println!("Using external MySQL '{connection_string}' as Kvbackend");
540                    args.extend([
541                        "--backend".to_string(),
542                        "mysql-store".to_string(),
543                        "--store-addrs".to_string(),
544                        connection_string,
545                    ]);
546                } else if db_ctx.store_config().store_addrs.is_empty() {
547                    args.extend(vec!["--backend".to_string(), "memory-store".to_string()])
548                }
549            }
550            ServerMode::Datanode {
551                rpc_bind_addr,
552                rpc_server_addr,
553                http_addr,
554                metasrv_addr,
555                node_id,
556            } => {
557                let data_home = sqlness_home.join(format!(
558                    "greptimedb_{}_datanode_{}_{node_id}",
559                    id,
560                    db_ctx.time()
561                ));
562                args.extend([
563                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
564                    format!("{}={rpc_server_addr}", arg_style.server_addr_arg()),
565                    format!("--http-addr={http_addr}"),
566                    format!("--data-home={}", data_home.display()),
567                    format!("--log-dir={}/logs", data_home.display()),
568                    format!("--node-id={node_id}"),
569                    "-c".to_string(),
570                    self.generate_config_file(sqlness_home, db_ctx, id, compat_stage),
571                    format!("--metasrv-addrs={metasrv_addr}"),
572                ]);
573            }
574            ServerMode::Flownode {
575                rpc_bind_addr,
576                rpc_server_addr,
577                http_addr,
578                metasrv_addr,
579                node_id,
580            } => {
581                args.extend([
582                    format!("{}={rpc_bind_addr}", arg_style.bind_addr_arg()),
583                    format!("{}={rpc_server_addr}", arg_style.server_addr_arg()),
584                    format!("--node-id={node_id}"),
585                    format!(
586                        "--log-dir={}/greptimedb-{}-flownode/logs",
587                        sqlness_home.display(),
588                        id
589                    ),
590                    format!("--metasrv-addrs={metasrv_addr}"),
591                    format!("--http-addr={http_addr}"),
592                ]);
593            }
594        }
595
596        args
597    }
598}
599
600#[cfg(test)]
601mod tests {
602    use std::path::PathBuf;
603    use std::sync::Arc;
604
605    use super::*;
606    use crate::cmd::datanode_overlay::{DatanodeOverlay, DatanodeProtectionPolicy};
607    use crate::env::bare::{StoreConfig, WalConfig};
608
609    fn test_env(sqlness_home: &Path) -> (Env, GreptimeDBContext) {
610        let store_config = StoreConfig {
611            store_addrs: vec!["127.0.0.1:2379".to_string()],
612            setup_etcd: true,
613            setup_pg: None,
614            setup_mysql: None,
615            enable_flat_format: false,
616            enable_gc: false,
617        };
618        let env = Env::new(
619            sqlness_home.to_path_buf(),
620            ServerAddr::default(),
621            WalConfig::RaftEngine,
622            false,
623            Some(PathBuf::from(".")),
624            store_config.clone(),
625            vec![],
626        );
627        let db_ctx = GreptimeDBContext::new(WalConfig::RaftEngine, store_config);
628
629        (env, db_ctx)
630    }
631
632    fn old_stage(temp_dir: &Path) -> CompatConfigStage {
633        std::fs::write(
634            temp_dir.join("old-datanode.toml"),
635            "old_only = \"applied\"\nmode = \"standalone\"\n",
636        )
637        .unwrap();
638        let overlay = DatanodeOverlay::load(temp_dir, Path::new("old-datanode.toml"))
639            .unwrap()
640            .prepare(&DatanodeProtectionPolicy::for_wal(&WalConfig::RaftEngine))
641            .unwrap();
642        CompatConfigStage::Old(Arc::new(overlay))
643    }
644
645    fn has_arg(args: &[String], name: &str) -> bool {
646        let prefix = format!("{name}=");
647        args.iter()
648            .any(|arg| arg == name || arg.starts_with(&prefix))
649    }
650
651    fn assert_uses_style(args: &[String], style: GrpcArgStyle, expect_server_addr: bool) {
652        let bind = style.bind_addr_arg();
653        let server = style.server_addr_arg();
654        let other_bind = match style {
655            GrpcArgStyle::Grpc => "--rpc-bind-addr",
656            GrpcArgStyle::Rpc => "--grpc-bind-addr",
657        };
658        let other_server = match style {
659            GrpcArgStyle::Grpc => "--rpc-server-addr",
660            GrpcArgStyle::Rpc => "--grpc-server-addr",
661        };
662
663        assert!(has_arg(args, bind), "missing {bind} in args: {args:?}");
664        assert!(
665            !args.iter().any(|arg| arg.contains(other_bind)),
666            "unexpected {other_bind} in args: {args:?}"
667        );
668
669        if expect_server_addr {
670            assert!(has_arg(args, server), "missing {server} in args: {args:?}");
671            assert!(
672                !args.iter().any(|arg| arg.contains(other_server)),
673                "unexpected {other_server} in args: {args:?}"
674            );
675        }
676    }
677
678    fn test_all_modes(env: &Env, db_ctx: &GreptimeDBContext, temp_dir: &Path, style: GrpcArgStyle) {
679        let standalone = ServerMode::Standalone {
680            http_addr: "127.0.0.1:4000".to_string(),
681            rpc_bind_addr: "127.0.0.1:4001".to_string(),
682            mysql_addr: "127.0.0.1:4002".to_string(),
683            postgres_addr: "127.0.0.1:4003".to_string(),
684        };
685        assert_uses_style(
686            &standalone.get_args(
687                temp_dir,
688                env,
689                db_ctx,
690                0,
691                style,
692                &CompatConfigStage::Baseline,
693            ),
694            style,
695            false,
696        );
697
698        let frontend = ServerMode::Frontend {
699            http_addr: "127.0.0.1:4100".to_string(),
700            rpc_bind_addr: "127.0.0.1:4101".to_string(),
701            mysql_addr: "127.0.0.1:4102".to_string(),
702            postgres_addr: "127.0.0.1:4103".to_string(),
703            metasrv_addr: "127.0.0.1:4001".to_string(),
704        };
705        assert_uses_style(
706            &frontend.get_args(
707                temp_dir,
708                env,
709                db_ctx,
710                0,
711                style,
712                &CompatConfigStage::Baseline,
713            ),
714            style,
715            true,
716        );
717
718        let metasrv = ServerMode::Metasrv {
719            rpc_bind_addr: "127.0.0.1:4201".to_string(),
720            rpc_server_addr: "127.0.0.1:4201".to_string(),
721            http_addr: "127.0.0.1:4200".to_string(),
722        };
723        assert_uses_style(
724            &metasrv.get_args(
725                temp_dir,
726                env,
727                db_ctx,
728                0,
729                style,
730                &CompatConfigStage::Baseline,
731            ),
732            style,
733            true,
734        );
735
736        let datanode = ServerMode::Datanode {
737            rpc_bind_addr: "127.0.0.1:4301".to_string(),
738            rpc_server_addr: "127.0.0.1:4301".to_string(),
739            http_addr: "127.0.0.1:4300".to_string(),
740            metasrv_addr: "127.0.0.1:4001".to_string(),
741            node_id: 0,
742        };
743        assert_uses_style(
744            &datanode.get_args(
745                temp_dir,
746                env,
747                db_ctx,
748                0,
749                style,
750                &CompatConfigStage::Baseline,
751            ),
752            style,
753            true,
754        );
755
756        let flownode = ServerMode::Flownode {
757            rpc_bind_addr: "127.0.0.1:4401".to_string(),
758            rpc_server_addr: "127.0.0.1:4401".to_string(),
759            http_addr: "127.0.0.1:4400".to_string(),
760            metasrv_addr: "127.0.0.1:4001".to_string(),
761            node_id: 0,
762        };
763        assert_uses_style(
764            &flownode.get_args(
765                temp_dir,
766                env,
767                db_ctx,
768                0,
769                style,
770                &CompatConfigStage::Baseline,
771            ),
772            style,
773            true,
774        );
775    }
776
777    #[test]
778    fn test_get_args_with_grpc_style() {
779        let temp_dir = tempfile::tempdir().unwrap();
780        let (env, db_ctx) = test_env(temp_dir.path());
781        test_all_modes(&env, &db_ctx, temp_dir.path(), GrpcArgStyle::Grpc);
782    }
783
784    #[test]
785    fn test_get_args_with_rpc_style() {
786        let temp_dir = tempfile::tempdir().unwrap();
787        let (env, db_ctx) = test_env(temp_dir.path());
788        test_all_modes(&env, &db_ctx, temp_dir.path(), GrpcArgStyle::Rpc);
789    }
790
791    #[test]
792    fn test_arg_style_for_unknown_version_defaults_to_grpc() {
793        assert_eq!(GrpcArgStyle::for_version(None), GrpcArgStyle::Grpc);
794    }
795
796    #[test]
797    fn test_arg_style_for_legacy_versions_uses_rpc() {
798        let v1_0_0 = Version::parse("v1.0.0").unwrap();
799        let v1_0_9 = Version::parse("v1.0.9").unwrap();
800
801        assert_eq!(GrpcArgStyle::for_version(Some(&v1_0_0)), GrpcArgStyle::Rpc);
802        assert_eq!(GrpcArgStyle::for_version(Some(&v1_0_9)), GrpcArgStyle::Rpc);
803    }
804
805    #[test]
806    fn test_arg_style_for_current_versions_uses_grpc() {
807        let v1_1_0 = Version::parse("v1.1.0").unwrap();
808        let v1_2_0 = Version::parse("v1.2.0").unwrap();
809
810        assert_eq!(GrpcArgStyle::for_version(Some(&v1_1_0)), GrpcArgStyle::Grpc);
811        assert_eq!(GrpcArgStyle::for_version(Some(&v1_2_0)), GrpcArgStyle::Grpc);
812    }
813
814    #[test]
815    fn test_generate_distributed_gc_config_when_enabled() {
816        let temp_dir = tempfile::tempdir().unwrap();
817        let store_config = StoreConfig {
818            store_addrs: vec![],
819            setup_etcd: false,
820            setup_pg: None,
821            setup_mysql: None,
822            enable_flat_format: false,
823            enable_gc: true,
824        };
825        let db_ctx = GreptimeDBContext::new(WalConfig::RaftEngine, store_config);
826        let metasrv = ServerMode::Metasrv {
827            rpc_bind_addr: "127.0.0.1:4201".to_string(),
828            rpc_server_addr: "127.0.0.1:4201".to_string(),
829            http_addr: "127.0.0.1:4200".to_string(),
830        };
831        let datanode = ServerMode::Datanode {
832            rpc_bind_addr: "127.0.0.1:4301".to_string(),
833            rpc_server_addr: "127.0.0.1:4301".to_string(),
834            http_addr: "127.0.0.1:4300".to_string(),
835            metasrv_addr: "127.0.0.1:4201".to_string(),
836            node_id: 0,
837        };
838
839        let metasrv_config = std::fs::read_to_string(metasrv.generate_config_file(
840            temp_dir.path(),
841            &db_ctx,
842            0,
843            &CompatConfigStage::Baseline,
844        ))
845        .unwrap();
846        let datanode_config = std::fs::read_to_string(datanode.generate_config_file(
847            temp_dir.path(),
848            &db_ctx,
849            0,
850            &CompatConfigStage::Baseline,
851        ))
852        .unwrap();
853
854        assert!(metasrv_config.contains("[gc]\nenable = true"));
855        assert!(datanode_config.contains("[region_engine.mito.gc]\nenable = true"));
856    }
857
858    #[test]
859    fn test_datanode_rendering_applies_only_old_stage_with_distinct_filenames() {
860        let temp_dir = tempfile::tempdir().unwrap();
861        let (_, db_ctx) = test_env(temp_dir.path());
862        let datanode = ServerMode::Datanode {
863            rpc_bind_addr: "127.0.0.1:4301".to_string(),
864            rpc_server_addr: "127.0.0.1:4301".to_string(),
865            http_addr: "127.0.0.1:4300".to_string(),
866            metasrv_addr: "127.0.0.1:4201".to_string(),
867            node_id: 0,
868        };
869        let baseline_path = datanode.generate_config_file(
870            temp_dir.path(),
871            &db_ctx,
872            0,
873            &CompatConfigStage::Baseline,
874        );
875        let old_path =
876            datanode.generate_config_file(temp_dir.path(), &db_ctx, 0, &old_stage(temp_dir.path()));
877        let current_path =
878            datanode.generate_config_file(temp_dir.path(), &db_ctx, 0, &CompatConfigStage::Current);
879
880        let baseline = std::fs::read_to_string(&baseline_path).unwrap();
881        let old = std::fs::read_to_string(&old_path).unwrap();
882        let current = std::fs::read_to_string(&current_path).unwrap();
883        assert!(baseline_path.ends_with("-baseline.toml"));
884        assert!(old_path.ends_with("-old.toml"));
885        assert!(current_path.ends_with("-current.toml"));
886        assert!(!baseline.contains("old_only"));
887        assert!(old.contains("old_only = \"applied\""));
888        assert!(!current.contains("old_only"));
889        assert_eq!(
890            toml::from_str::<toml::Value>(&old).unwrap()["mode"].as_str(),
891            toml::from_str::<toml::Value>(&baseline).unwrap()["mode"].as_str()
892        );
893    }
894
895    #[test]
896    fn test_non_datanode_ignores_old_overlay() {
897        let temp_dir = tempfile::tempdir().unwrap();
898        let (_, db_ctx) = test_env(temp_dir.path());
899        let metasrv = ServerMode::Metasrv {
900            rpc_bind_addr: "127.0.0.1:4201".to_string(),
901            rpc_server_addr: "127.0.0.1:4201".to_string(),
902            http_addr: "127.0.0.1:4200".to_string(),
903        };
904        let config_path =
905            metasrv.generate_config_file(temp_dir.path(), &db_ctx, 0, &old_stage(temp_dir.path()));
906
907        assert!(!config_path.ends_with("-old.toml"));
908        assert!(
909            !std::fs::read_to_string(config_path)
910                .unwrap()
911                .contains("old_only")
912        );
913    }
914}