1use std::collections::HashMap;
16use std::fmt::Display;
17use std::fs::OpenOptions;
18use std::io;
19use std::io::Write;
20use std::path::{Path, PathBuf};
21use std::process::{Child, Command};
22use std::sync::atomic::{AtomicU32, Ordering};
23use std::sync::{Arc, Mutex};
24use std::time::Duration;
25
26use async_trait::async_trait;
27use common_error::ext::ErrorExt;
28use sqlness::{Database, EnvController, QueryContext};
29use tokio::sync::Mutex as TokioMutex;
30
31use crate::client::MultiProtocolClient;
32use crate::cmd::bare::ServerAddr;
33use crate::cmd::compat_case::try_infer_version;
34use crate::cmd::datanode_overlay::PreparedDatanodeOverlay;
35use crate::formatter::{ErrorFormatter, MysqlFormatter, OutputFormatter, PostgresqlFormatter};
36use crate::protocol_interceptor::{MYSQL, PROTOCOL_KEY};
37use crate::server_mode::{GrpcArgStyle, ServerMode};
38use crate::util;
39use crate::util::{PROGRAM, get_workspace_root, maybe_pull_binary};
40
41const SERVER_MODE_STANDALONE_IDX: usize = 0;
43const SERVER_MODE_METASRV_IDX: usize = 0;
45const SERVER_MODE_DATANODE_START_IDX: usize = 1;
46const SERVER_MODE_FRONTEND_IDX: usize = 4;
47const SERVER_MODE_FLOWNODE_IDX: usize = 5;
48const DISTRIBUTED_DATANODE_COUNT: usize = 3;
50
51#[derive(Clone)]
52pub enum WalConfig {
53 RaftEngine,
54 Kafka {
55 needs_kafka_cluster: bool,
58 broker_endpoints: Vec<String>,
59 },
60}
61
62#[derive(Debug, Clone)]
63pub(crate) enum ServiceProvider {
64 Create,
65 External(String),
66}
67
68impl From<&str> for ServiceProvider {
69 fn from(value: &str) -> Self {
70 if value.is_empty() {
71 Self::Create
72 } else {
73 Self::External(value.to_string())
74 }
75 }
76}
77
78#[derive(Clone)]
79pub struct StoreConfig {
80 pub store_addrs: Vec<String>,
81 pub setup_etcd: bool,
82 pub(crate) setup_pg: Option<ServiceProvider>,
83 pub(crate) setup_mysql: Option<ServiceProvider>,
84 pub enable_flat_format: bool,
85 pub enable_gc: bool,
86}
87
88#[derive(Clone)]
89pub struct Env {
90 sqlness_home: PathBuf,
91 server_addrs: ServerAddr,
92 wal: WalConfig,
93
94 bins_dir: Arc<Mutex<Option<PathBuf>>>,
98 versioned_bins_dirs: Arc<Mutex<HashMap<String, PathBuf>>>,
100 pull_version_on_need: bool,
102 store_config: StoreConfig,
104 extra_args: Vec<String>,
106 grpc_arg_style_cache: Arc<Mutex<HashMap<PathBuf, GrpcArgStyle>>>,
108 compat_config_stage: Arc<Mutex<CompatConfigStage>>,
109}
110
111struct ChildGuard(Option<Child>);
113
114impl ChildGuard {
115 fn new(child: Child) -> Self {
116 Self(Some(child))
117 }
118
119 fn into_inner(mut self) -> Child {
120 self.0.take().unwrap()
121 }
122}
123
124impl Drop for ChildGuard {
125 fn drop(&mut self) {
126 if let Some(child) = self.0.as_mut() {
127 Env::stop_server(child);
128 }
129 }
130}
131
132#[derive(Clone, Debug)]
134pub(crate) enum CompatConfigStage {
135 Baseline,
137 Old(Arc<PreparedDatanodeOverlay>),
139 Current,
141}
142
143#[async_trait]
144impl EnvController for Env {
145 type DB = GreptimeDB;
146
147 async fn start(&self, mode: &str, id: usize, _config: Option<&Path>) -> Self::DB {
148 if self.server_addrs.server_addr.is_some() && id > 0 {
149 panic!("Parallel test mode is not supported when server address is already set.");
150 }
151
152 unsafe {
153 std::env::set_var(
154 "SQLNESS_HOME",
155 self.sqlness_home.join("copy").display().to_string(),
156 );
157 }
158 match mode {
159 "standalone" => self.start_standalone(id).await,
160 "distributed" => self.start_distributed(id).await,
161 _ => panic!("Unexpected mode: {mode}"),
162 }
163 }
164
165 async fn stop(&self, _mode: &str, mut database: Self::DB) {
167 database.stop();
168 }
169}
170
171impl Env {
172 pub fn new(
173 data_home: PathBuf,
174 server_addrs: ServerAddr,
175 wal: WalConfig,
176 pull_version_on_need: bool,
177 bins_dir: Option<PathBuf>,
178 store_config: StoreConfig,
179 extra_args: Vec<String>,
180 ) -> Self {
181 Self {
182 sqlness_home: data_home,
183 server_addrs,
184 wal,
185 pull_version_on_need,
186 bins_dir: Arc::new(Mutex::new(bins_dir.clone())),
187 versioned_bins_dirs: Arc::new(Mutex::new(HashMap::from_iter([(
188 "latest".to_string(),
189 bins_dir.clone().unwrap_or(util::get_binary_dir("debug")),
190 )]))),
191 store_config,
192 extra_args,
193 grpc_arg_style_cache: Arc::new(Mutex::new(HashMap::new())),
194 compat_config_stage: Arc::new(Mutex::new(CompatConfigStage::Baseline)),
195 }
196 }
197
198 pub(crate) fn activate_compat_old(&self, overlay: Arc<PreparedDatanodeOverlay>) {
200 *self.compat_config_stage.lock().unwrap() = CompatConfigStage::Old(overlay);
201 }
202
203 pub(crate) fn activate_compat_current(&self) {
205 *self.compat_config_stage.lock().unwrap() = CompatConfigStage::Current;
206 }
207
208 pub(crate) fn compat_config_stage(&self) -> CompatConfigStage {
210 self.compat_config_stage.lock().unwrap().clone()
211 }
212
213 async fn start_standalone(&self, id: usize) -> GreptimeDB {
214 println!("Starting standalone instance id: {id}");
215
216 if self.server_addrs.server_addr.is_some() {
217 self.connect_db(&self.server_addrs, id).await
218 } else {
219 self.build_db();
220 self.setup_wal();
221 let mut db_ctx = GreptimeDBContext::new(self.wal.clone(), self.store_config.clone());
222
223 let server_mode = ServerMode::random_standalone();
224 db_ctx.set_server_mode(server_mode.clone(), SERVER_MODE_STANDALONE_IDX);
225 let server_addr = server_mode.server_addr().unwrap();
226 let server_process = self.start_server(server_mode, &db_ctx, id, true).await;
227
228 let mut greptimedb = self.connect_db(&server_addr, id).await;
229 greptimedb.server_processes =
230 Some(Arc::new(Mutex::new(vec![server_process.into_inner()])));
231 greptimedb.is_standalone = true;
232 greptimedb.ctx = db_ctx;
233
234 greptimedb
235 }
236 }
237
238 async fn start_distributed(&self, id: usize) -> GreptimeDB {
239 self.start_distributed_inner(id).await
240 }
241
242 async fn start_distributed_inner(&self, id: usize) -> GreptimeDB {
244 if self.server_addrs.server_addr.is_some() {
245 self.connect_db(&self.server_addrs, id).await
246 } else {
247 self.build_db();
248 self.setup_wal();
249 self.setup_etcd();
250 self.setup_pg();
251 self.setup_mysql().await;
252 let mut db_ctx = GreptimeDBContext::new(self.wal.clone(), self.store_config.clone());
253
254 let meta_server_mode = ServerMode::random_metasrv();
256 let metasrv_port = match &meta_server_mode {
257 ServerMode::Metasrv {
258 rpc_server_addr, ..
259 } => rpc_server_addr
260 .split(':')
261 .nth(1)
262 .unwrap()
263 .parse::<u16>()
264 .unwrap(),
265 _ => panic!(
266 "metasrv mode not set, maybe running in remote mode which doesn't support restart?"
267 ),
268 };
269 db_ctx.set_server_mode(meta_server_mode.clone(), SERVER_MODE_METASRV_IDX);
270 let meta_server = self.start_server(meta_server_mode, &db_ctx, id, true).await;
271
272 let mut datanodes = Vec::with_capacity(DISTRIBUTED_DATANODE_COUNT);
273 for i in 0..DISTRIBUTED_DATANODE_COUNT {
274 let datanode_mode = ServerMode::random_datanode(metasrv_port, i as u32);
275 db_ctx.set_server_mode(datanode_mode.clone(), SERVER_MODE_DATANODE_START_IDX + i);
276 let datanode = self.start_server(datanode_mode, &db_ctx, id, true).await;
277 datanodes.push(datanode);
278 }
279
280 let frontend_mode = ServerMode::random_frontend(metasrv_port);
281 let server_addr = frontend_mode.server_addr().unwrap();
282 db_ctx.set_server_mode(frontend_mode.clone(), SERVER_MODE_FRONTEND_IDX);
283 let frontend = self.start_server(frontend_mode, &db_ctx, id, true).await;
284
285 let flownode_mode = ServerMode::random_flownode(metasrv_port, 0);
286 db_ctx.set_server_mode(flownode_mode.clone(), SERVER_MODE_FLOWNODE_IDX);
287 let flownode = self.start_server(flownode_mode, &db_ctx, id, true).await;
288
289 let mut greptimedb = self.connect_db(&server_addr, id).await;
290
291 greptimedb.metasrv_process = Some(meta_server.into_inner()).into();
292 greptimedb.server_processes = Some(Arc::new(Mutex::new(
293 datanodes.into_iter().map(ChildGuard::into_inner).collect(),
294 )));
295 greptimedb.frontend_process = Some(frontend.into_inner()).into();
296 greptimedb.flownode_process = Some(flownode.into_inner()).into();
297 greptimedb.is_standalone = false;
298 greptimedb.ctx = db_ctx;
299
300 greptimedb
301 }
302 }
303
304 async fn connect_db(&self, server_addr: &ServerAddr, id: usize) -> GreptimeDB {
305 let grpc_server_addr = server_addr.server_addr.as_ref().unwrap();
306 let pg_server_addr = server_addr.pg_server_addr.as_ref().unwrap();
307 let mysql_server_addr = server_addr.mysql_server_addr.as_ref().unwrap();
308
309 let client =
310 MultiProtocolClient::connect(grpc_server_addr, pg_server_addr, mysql_server_addr).await;
311 GreptimeDB {
312 client: TokioMutex::new(client),
313 server_processes: None,
314 metasrv_process: None.into(),
315 frontend_process: None.into(),
316 flownode_process: None.into(),
317 active_bins_dir: Mutex::new(self.bins_dir.lock().unwrap().clone()),
318 ctx: GreptimeDBContext {
319 time: 0,
320 datanode_id: Default::default(),
321 wal: self.wal.clone(),
322 store_config: self.store_config.clone(),
323 server_modes: Vec::new(),
324 },
325 is_standalone: false,
326 env: self.clone(),
327 id,
328 }
329 }
330
331 fn stop_server(process: &mut Child) {
332 let _ = process.kill();
333 let _ = process.wait();
334 }
335
336 fn infer_grpc_arg_style(&self, bins_dir: &Path) -> GrpcArgStyle {
338 let cache_key = bins_dir.to_path_buf();
339
340 {
342 let cache = self.grpc_arg_style_cache.lock().unwrap();
343 if let Some(style) = cache.get(&cache_key) {
344 return *style;
345 }
346 }
347
348 let version = try_infer_version(bins_dir);
349 let style = GrpcArgStyle::for_version(version.as_ref());
350
351 {
354 let mut cache = self.grpc_arg_style_cache.lock().unwrap();
355 cache.entry(cache_key).or_insert(style);
356 }
357
358 style
359 }
360
361 async fn start_server(
362 &self,
363 mode: ServerMode,
364 db_ctx: &GreptimeDBContext,
365 id: usize,
366 truncate_log: bool,
367 ) -> ChildGuard {
368 let bins_dir = self.bins_dir.lock().unwrap().clone().expect(
369 "GreptimeDB binary is not available. Please pass in the path to the directory that contains the pre-built GreptimeDB binary. Or you may call `self.build_db()` beforehand.",
370 );
371
372 self.start_server_with_bins_dir(mode, db_ctx, id, truncate_log, bins_dir)
373 .await
374 }
375
376 async fn start_server_with_bins_dir(
377 &self,
378 mode: ServerMode,
379 db_ctx: &GreptimeDBContext,
380 id: usize,
381 truncate_log: bool,
382 bins_dir: PathBuf,
383 ) -> ChildGuard {
384 let log_file_name = match mode {
385 ServerMode::Datanode { node_id, .. } => {
386 db_ctx.incr_datanode_id();
387 format!("greptime-{}-sqlness-datanode-{}.log", id, node_id)
388 }
389 ServerMode::Flownode { .. } => format!("greptime-{}-sqlness-flownode.log", id),
390 ServerMode::Frontend { .. } => format!("greptime-{}-sqlness-frontend.log", id),
391 ServerMode::Metasrv { .. } => format!("greptime-{}-sqlness-metasrv.log", id),
392 ServerMode::Standalone { .. } => format!("greptime-{}-sqlness-standalone.log", id),
393 };
394 let stdout_file_name = self.sqlness_home.join(log_file_name).display().to_string();
395
396 println!("DB instance {id} log file at {stdout_file_name}");
397
398 let stdout_file = OpenOptions::new()
399 .create(true)
400 .write(true)
401 .truncate(truncate_log)
402 .append(!truncate_log)
403 .open(&stdout_file_name)
404 .unwrap();
405
406 let arg_style = self.infer_grpc_arg_style(&bins_dir);
407 let compat_stage = self.compat_config_stage();
408 let args = mode.get_args(
409 &self.sqlness_home,
410 self,
411 db_ctx,
412 id,
413 arg_style,
414 &compat_stage,
415 );
416 let check_ip_addrs = mode.check_addrs();
417
418 for check_ip_addr in &check_ip_addrs {
419 if util::check_port(check_ip_addr.parse().unwrap(), Duration::from_secs(1)).await {
420 panic!(
421 "Port {check_ip_addr} is already in use, please check and retry.",
422 check_ip_addr = check_ip_addr
423 );
424 }
425 }
426
427 let program = PROGRAM;
428
429 let abs_bins_dir = bins_dir
430 .canonicalize()
431 .expect("Failed to canonicalize bins_dir");
432
433 let process = Command::new(abs_bins_dir.join(program))
434 .current_dir(bins_dir.clone())
435 .env("TZ", "UTC")
436 .args(args)
437 .stdout(stdout_file)
438 .spawn()
439 .unwrap_or_else(|error| {
440 panic!(
441 "Failed to start the DB with subcommand {}, Error: {error}, path: {:?}",
442 mode.name(),
443 bins_dir.join(program)
444 );
445 });
446 let process = ChildGuard::new(process);
447
448 for check_ip_addr in &check_ip_addrs {
449 if !util::check_port(check_ip_addr.parse().unwrap(), Duration::from_secs(30)).await {
450 panic!(
451 "{} doesn't up in 30 seconds, check {} for more details.",
452 mode.name(),
453 stdout_file_name
454 )
455 }
456 }
457
458 process
459 }
460
461 pub(crate) async fn restart_server(&self, db: &GreptimeDB, is_full_restart: bool) {
463 let bins_dir = db.active_bins_dir.lock().unwrap().clone().expect(
464 "GreptimeDB binary is not available. Please pass in the path to the directory that contains the pre-built GreptimeDB binary. Or you may call `self.build_db()` beforehand.",
465 );
466
467 {
468 if let Some(server_process) = db.server_processes.clone() {
469 let mut server_processes = server_process.lock().unwrap();
470 for server_process in server_processes.iter_mut() {
471 Env::stop_server(server_process);
472 }
473 }
474
475 if is_full_restart {
476 if let Some(mut metasrv_process) =
477 db.metasrv_process.lock().expect("poisoned lock").take()
478 {
479 Env::stop_server(&mut metasrv_process);
480 }
481 if let Some(mut frontend_process) =
482 db.frontend_process.lock().expect("poisoned lock").take()
483 {
484 Env::stop_server(&mut frontend_process);
485 }
486 }
487
488 if let Some(mut flownode_process) =
490 db.flownode_process.lock().expect("poisoned lock").take()
491 {
492 Env::stop_server(&mut flownode_process);
493 }
494 }
495
496 let new_server_processes = if db.is_standalone {
498 let server_mode = db
499 .ctx
500 .get_server_mode(SERVER_MODE_STANDALONE_IDX)
501 .cloned()
502 .unwrap();
503 let server_addr = server_mode.server_addr().unwrap();
504 let new_server_process = self
505 .start_server_with_bins_dir(server_mode, &db.ctx, db.id, false, bins_dir.clone())
506 .await;
507
508 let mut client = db.client.lock().await;
509 client
510 .reconnect_mysql_client(&server_addr.mysql_server_addr.unwrap())
511 .await;
512 client
513 .reconnect_pg_client(&server_addr.pg_server_addr.unwrap())
514 .await;
515 vec![new_server_process]
516 } else {
517 db.ctx.reset_datanode_id();
518 let mut new_metasrv = None;
519 if is_full_restart {
520 let metasrv_mode = db
521 .ctx
522 .get_server_mode(SERVER_MODE_METASRV_IDX)
523 .cloned()
524 .unwrap();
525 let metasrv = self
526 .start_server_with_bins_dir(
527 metasrv_mode,
528 &db.ctx,
529 db.id,
530 false,
531 bins_dir.clone(),
532 )
533 .await;
534 new_metasrv = Some(metasrv);
535
536 tokio::time::sleep(Duration::from_secs(5)).await;
539 }
540
541 let mut processes = vec![];
542 for i in 0..DISTRIBUTED_DATANODE_COUNT {
543 let datanode_mode = db
544 .ctx
545 .get_server_mode(SERVER_MODE_DATANODE_START_IDX + i)
546 .cloned()
547 .unwrap();
548 let new_server_process = self
549 .start_server_with_bins_dir(
550 datanode_mode,
551 &db.ctx,
552 db.id,
553 false,
554 bins_dir.clone(),
555 )
556 .await;
557 processes.push(new_server_process);
558 }
559
560 let mut new_frontend = None;
561 if is_full_restart {
562 let frontend_mode = db
563 .ctx
564 .get_server_mode(SERVER_MODE_FRONTEND_IDX)
565 .cloned()
566 .unwrap();
567 let server_addr = frontend_mode.server_addr().unwrap();
568 let frontend = self
569 .start_server_with_bins_dir(
570 frontend_mode,
571 &db.ctx,
572 db.id,
573 false,
574 bins_dir.clone(),
575 )
576 .await;
577
578 let mut client = db.client.lock().await;
582 client
583 .reconnect_mysql_client(server_addr.mysql_server_addr.as_ref().unwrap())
584 .await;
585 client
586 .reconnect_pg_client(server_addr.pg_server_addr.as_ref().unwrap())
587 .await;
588 new_frontend = Some(frontend);
589 }
590
591 let mut new_flownode = None;
593 if let Some(flownode_mode) = db.ctx.get_server_mode(SERVER_MODE_FLOWNODE_IDX).cloned() {
594 let flownode = self
595 .start_server_with_bins_dir(
596 flownode_mode,
597 &db.ctx,
598 db.id,
599 false,
600 bins_dir.clone(),
601 )
602 .await;
603 new_flownode = Some(flownode);
604 }
605
606 if let Some(metasrv) = new_metasrv {
607 let mut metasrv_process = db.metasrv_process.lock().expect("lock poisoned");
608 metasrv_process.replace(metasrv.into_inner());
609 }
610 if let Some(frontend) = new_frontend {
611 let mut frontend_process = db.frontend_process.lock().expect("lock poisoned");
612 frontend_process.replace(frontend.into_inner());
613 }
614 if let Some(flownode) = new_flownode {
615 let mut flownode_process = db.flownode_process.lock().expect("lock poisoned");
616 flownode_process.replace(flownode.into_inner());
617 }
618
619 processes
620 };
621
622 if let Some(server_processes) = db.server_processes.clone() {
623 let mut server_processes = server_processes.lock().unwrap();
624 *server_processes = new_server_processes
625 .into_iter()
626 .map(ChildGuard::into_inner)
627 .collect();
628 }
629 }
630
631 fn setup_wal(&self) {
633 if matches!(self.wal, WalConfig::Kafka { needs_kafka_cluster, .. } if needs_kafka_cluster) {
634 util::setup_wal();
635 }
636 }
637
638 fn setup_etcd(&self) {
640 if self.store_config.setup_etcd {
641 let client_ports = self
642 .store_config
643 .store_addrs
644 .iter()
645 .map(|s| s.split(':').nth(1).unwrap().parse::<u16>().unwrap())
646 .collect::<Vec<_>>();
647 util::setup_etcd(client_ports, None, None);
648 }
649 }
650
651 fn setup_pg(&self) {
653 if matches!(self.store_config.setup_pg, Some(ServiceProvider::Create)) {
654 let client_ports = self
655 .store_config
656 .store_addrs
657 .iter()
658 .map(|s| s.split(':').nth(1).unwrap().parse::<u16>().unwrap())
659 .collect::<Vec<_>>();
660 let client_port = client_ports.first().unwrap_or(&5432);
661 util::setup_pg(*client_port, None);
662 }
663 }
664
665 async fn setup_mysql(&self) {
667 if matches!(self.store_config.setup_mysql, Some(ServiceProvider::Create)) {
668 let client_ports = self
669 .store_config
670 .store_addrs
671 .iter()
672 .map(|s| s.split(':').nth(1).unwrap().parse::<u16>().unwrap())
673 .collect::<Vec<_>>();
674 let client_port = client_ports.first().unwrap_or(&3306);
675 util::setup_mysql(*client_port, None);
676
677 tokio::time::sleep(Duration::from_secs(10)).await;
679 }
680 }
681
682 fn build_db(&self) {
684 let mut bins_dir = self.bins_dir.lock().unwrap();
685 if bins_dir.is_some() {
686 return;
687 }
688
689 println!("Going to build the DB...");
690 let output = Command::new("cargo")
691 .current_dir(util::get_workspace_root())
692 .args([
693 "build",
694 "--bin",
695 "greptime",
696 "--features",
697 "pg_kvbackend,mysql_kvbackend,vector_index",
698 ])
699 .output()
700 .expect("Failed to start GreptimeDB");
701 if !output.status.success() {
702 println!("Failed to build GreptimeDB, {}", output.status);
703 println!("Cargo build stdout:");
704 io::stdout().write_all(&output.stdout).unwrap();
705 println!("Cargo build stderr:");
706 io::stderr().write_all(&output.stderr).unwrap();
707 panic!();
708 }
709
710 bins_dir.replace(util::get_binary_dir("debug"));
711 }
712
713 pub(crate) fn extra_args(&self) -> &Vec<String> {
714 &self.extra_args
715 }
716
717 pub(crate) async fn compat_start_distributed(&self, id: usize) -> GreptimeDB {
719 self.start_distributed(id).await
720 }
721
722 pub(crate) async fn compat_start_standalone(&self, id: usize) -> GreptimeDB {
724 self.start_standalone(id).await
725 }
726
727 pub(crate) async fn compat_restart(&self, db: &GreptimeDB, bins_dir: PathBuf) {
729 *db.active_bins_dir.lock().unwrap() = Some(bins_dir);
730 self.restart_server(db, true).await;
731 self.wait_query_ready(db).await;
732 }
733
734 async fn wait_query_ready(&self, db: &GreptimeDB) {
736 let server_mode_idx = if db.is_standalone {
737 SERVER_MODE_STANDALONE_IDX
738 } else {
739 SERVER_MODE_FRONTEND_IDX
740 };
741 let server_mode = db.ctx.get_server_mode(server_mode_idx).cloned().unwrap();
742 if let Some(addr) = server_mode.check_addrs().first() {
743 println!("Waiting for query endpoint readiness at {addr}...");
744 crate::util::retry_with_backoff(
745 || async {
746 let mut client = db.client.lock().await;
747 match client.grpc_query("SELECT 1").await {
748 Ok(_) => Ok(()),
749 Err(e) => Err(format!("Query endpoint not ready: {e}")),
750 }
751 },
752 10,
753 std::time::Duration::from_secs(1),
754 )
755 .await
756 .unwrap_or_else(|e| panic!("Query endpoint failed to become ready: {e}"));
757 }
758 }
759}
760
761pub struct GreptimeDB {
762 server_processes: Option<Arc<Mutex<Vec<Child>>>>,
763 metasrv_process: Mutex<Option<Child>>,
764 frontend_process: Mutex<Option<Child>>,
765 flownode_process: Mutex<Option<Child>>,
766 client: TokioMutex<MultiProtocolClient>,
767 active_bins_dir: Mutex<Option<PathBuf>>,
768 ctx: GreptimeDBContext,
769 is_standalone: bool,
770 env: Env,
771 id: usize,
772}
773
774impl GreptimeDB {
775 async fn postgres_query(&self, _ctx: QueryContext, query: String) -> Box<dyn Display> {
776 let mut client = self.client.lock().await;
777
778 match client.postgres_query(&query).await {
779 Ok(rows) => Box::new(PostgresqlFormatter::from(rows)),
780 Err(e) => Box::new(e),
781 }
782 }
783
784 async fn mysql_query(&self, _ctx: QueryContext, query: String) -> Box<dyn Display> {
785 let mut client = self.client.lock().await;
786
787 match client.mysql_query(&query).await {
788 Ok(res) => Box::new(MysqlFormatter::from(res)),
789 Err(e) => Box::new(e),
790 }
791 }
792
793 async fn grpc_query(&self, _ctx: QueryContext, query: String) -> Box<dyn Display> {
794 let mut client = self.client.lock().await;
795
796 match client.grpc_query(&query).await {
797 Ok(rows) => Box::new(OutputFormatter::from(rows)),
798 Err(e) => Box::new(ErrorFormatter::from(e)),
799 }
800 }
801
802 pub(crate) async fn compat_prepare_query_context(&self, ctx: &QueryContext) {
811 if ctx.context.contains_key("restart") && self.env.server_addrs.server_addr.is_none() {
812 self.env.restart_server(self, false).await;
813 } else if let Some(version) = ctx.context.get("version") {
814 let version_bin_dir = self
815 .env
816 .versioned_bins_dirs
817 .lock()
818 .expect("lock poison")
819 .get(version.as_str())
820 .cloned();
821
822 match version_bin_dir {
823 Some(path) if path.join(PROGRAM).is_file() => {
824 *self.active_bins_dir.lock().unwrap() = Some(path);
825 }
826 _ => {
827 maybe_pull_binary(version, self.env.pull_version_on_need).await;
828 let root = get_workspace_root();
829 let new_path = PathBuf::from_iter([&root, version]);
830 *self.active_bins_dir.lock().unwrap() = Some(new_path);
831 }
832 }
833
834 self.env.restart_server(self, true).await;
835 tokio::time::sleep(Duration::from_secs(5)).await;
837 }
838 }
839
840 pub(crate) async fn compat_query(
841 &self,
842 query: &str,
843 ctx: &QueryContext,
844 ) -> Result<String, String> {
845 let mut client = self.client.lock().await;
846
847 if let Some(protocol) = ctx.context.get(PROTOCOL_KEY) {
849 if protocol == MYSQL {
850 return match client.mysql_query(query).await {
851 Ok(res) => Ok(crate::formatter::MysqlFormatter::from(res).to_string()),
852 Err(e) => Err(e),
853 };
854 } else {
855 return match client.postgres_query(query).await {
857 Ok(rows) => Ok(crate::formatter::PostgresqlFormatter::from(rows).to_string()),
858 Err(e) => Err(e),
859 };
860 }
861 }
862
863 match client.grpc_query(query).await {
865 Ok(output) => Ok(OutputFormatter::from(output).to_string()),
866 Err(e) => {
867 let status_code = e.status_code();
868 let root_cause = e.output_msg();
869 Err(format!(
870 "Error: {}({status_code}), {root_cause}",
871 status_code as u32
872 ))
873 }
874 }
875 }
876}
877
878#[async_trait]
879impl Database for GreptimeDB {
880 async fn query(&self, ctx: QueryContext, query: String) -> Box<dyn Display> {
881 if ctx.context.contains_key("restart") && self.env.server_addrs.server_addr.is_none() {
882 self.env.restart_server(self, false).await;
883 } else if let Some(version) = ctx.context.get("version") {
884 let version_bin_dir = self
885 .env
886 .versioned_bins_dirs
887 .lock()
888 .expect("lock poison")
889 .get(version.as_str())
890 .cloned();
891
892 match version_bin_dir {
893 Some(path) if path.join(PROGRAM).is_file() => {
894 *self.active_bins_dir.lock().unwrap() = Some(path);
896 }
897 _ => {
898 maybe_pull_binary(version, self.env.pull_version_on_need).await;
900 let root = get_workspace_root();
901 let new_path = PathBuf::from_iter([&root, version]);
902 *self.active_bins_dir.lock().unwrap() = Some(new_path);
903 }
904 }
905
906 self.env.restart_server(self, true).await;
907 tokio::time::sleep(Duration::from_secs(5)).await;
909 }
910
911 if let Some(protocol) = ctx.context.get(PROTOCOL_KEY) {
912 if protocol == MYSQL {
914 self.mysql_query(ctx, query).await
915 } else {
916 self.postgres_query(ctx, query).await
917 }
918 } else {
919 self.grpc_query(ctx, query).await
920 }
921 }
922}
923
924impl GreptimeDB {
925 fn stop(&mut self) {
926 if let Some(server_processes) = self.server_processes.clone() {
927 let mut server_processes = server_processes.lock().unwrap();
928 for mut server_process in server_processes.drain(..) {
929 Env::stop_server(&mut server_process);
930 println!(
931 "Standalone or Datanode (pid = {}) is stopped",
932 server_process.id()
933 );
934 }
935 }
936 if let Some(mut metasrv) = self
937 .metasrv_process
938 .lock()
939 .expect("someone else panic when holding lock")
940 .take()
941 {
942 Env::stop_server(&mut metasrv);
943 println!("Metasrv (pid = {}) is stopped", metasrv.id());
944 }
945 if let Some(mut frontend) = self
946 .frontend_process
947 .lock()
948 .expect("someone else panic when holding lock")
949 .take()
950 {
951 Env::stop_server(&mut frontend);
952 println!("Frontend (pid = {}) is stopped", frontend.id());
953 }
954 if let Some(mut flownode) = self
955 .flownode_process
956 .lock()
957 .expect("someone else panic when holding lock")
958 .take()
959 {
960 Env::stop_server(&mut flownode);
961 println!("Flownode (pid = {}) is stopped", flownode.id());
962 }
963 if matches!(self.ctx.wal, WalConfig::Kafka { needs_kafka_cluster, .. } if needs_kafka_cluster)
964 {
965 util::teardown_wal();
966 }
967 }
968
969 pub(crate) fn compat_stop(&mut self) {
971 self.stop();
972 }
973}
974
975impl Drop for GreptimeDB {
976 fn drop(&mut self) {
977 if self.env.server_addrs.server_addr.is_none() {
978 self.stop();
979 }
980 }
981}
982
983pub struct GreptimeDBContext {
984 time: i64,
986 datanode_id: AtomicU32,
987 wal: WalConfig,
988 store_config: StoreConfig,
989 server_modes: Vec<ServerMode>,
990}
991
992impl GreptimeDBContext {
993 pub fn new(wal: WalConfig, store_config: StoreConfig) -> Self {
994 Self {
995 time: common_time::util::current_time_millis(),
996 datanode_id: AtomicU32::new(0),
997 wal,
998 store_config,
999 server_modes: Vec::new(),
1000 }
1001 }
1002
1003 pub(crate) fn time(&self) -> i64 {
1004 self.time
1005 }
1006
1007 pub fn is_raft_engine(&self) -> bool {
1008 matches!(self.wal, WalConfig::RaftEngine)
1009 }
1010
1011 pub fn kafka_wal_broker_endpoints(&self) -> String {
1012 match &self.wal {
1013 WalConfig::RaftEngine => String::new(),
1014 WalConfig::Kafka {
1015 broker_endpoints, ..
1016 } => serde_json::to_string(&broker_endpoints).unwrap(),
1017 }
1018 }
1019
1020 fn incr_datanode_id(&self) {
1021 let _ = self.datanode_id.fetch_add(1, Ordering::Relaxed);
1022 }
1023
1024 fn reset_datanode_id(&self) {
1025 self.datanode_id.store(0, Ordering::Relaxed);
1026 }
1027
1028 pub(crate) fn store_config(&self) -> StoreConfig {
1029 self.store_config.clone()
1030 }
1031
1032 fn set_server_mode(&mut self, mode: ServerMode, idx: usize) {
1033 if idx >= self.server_modes.len() {
1034 self.server_modes.resize(idx + 1, mode.clone());
1035 }
1036 self.server_modes[idx] = mode;
1037 }
1038
1039 fn get_server_mode(&self, idx: usize) -> Option<&ServerMode> {
1040 self.server_modes.get(idx)
1041 }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046 use super::*;
1047 use crate::cmd::bare::ServerAddr;
1048 use crate::cmd::datanode_overlay::{DatanodeOverlay, DatanodeProtectionPolicy};
1049
1050 fn test_env(temp_dir: &Path) -> Env {
1051 Env::new(
1052 temp_dir.to_path_buf(),
1053 ServerAddr::default(),
1054 WalConfig::RaftEngine,
1055 false,
1056 None,
1057 StoreConfig {
1058 store_addrs: vec![],
1059 setup_etcd: false,
1060 setup_pg: None,
1061 setup_mysql: None,
1062 enable_flat_format: false,
1063 enable_gc: false,
1064 },
1065 vec![],
1066 )
1067 }
1068
1069 #[test]
1070 fn compat_config_stage_is_shared_by_env_clones_and_transitions_cleanly() {
1071 let temp_dir = tempfile::tempdir().unwrap();
1072 std::fs::write(temp_dir.path().join("overlay.toml"), "value = 1").unwrap();
1073 let overlay = DatanodeOverlay::load(temp_dir.path(), Path::new("overlay.toml"))
1074 .unwrap()
1075 .prepare(&DatanodeProtectionPolicy::for_wal(&WalConfig::RaftEngine))
1076 .unwrap();
1077 let env = test_env(temp_dir.path());
1078 let clone = env.clone();
1079
1080 assert!(matches!(
1081 clone.compat_config_stage(),
1082 CompatConfigStage::Baseline
1083 ));
1084 env.activate_compat_old(Arc::new(overlay));
1085 assert!(matches!(
1086 clone.compat_config_stage(),
1087 CompatConfigStage::Old(_)
1088 ));
1089 clone.activate_compat_current();
1090 assert!(matches!(
1091 env.compat_config_stage(),
1092 CompatConfigStage::Current
1093 ));
1094 }
1095
1096 #[cfg(unix)]
1097 #[test]
1098 fn child_guard_kills_untransferred_processes() {
1099 let child = std::process::Command::new("sleep")
1100 .arg("60")
1101 .spawn()
1102 .unwrap();
1103 let pid = child.id().to_string();
1104
1105 drop(ChildGuard::new(child));
1106
1107 assert!(
1108 !std::process::Command::new("kill")
1109 .args(["-0", &pid])
1110 .status()
1111 .unwrap()
1112 .success()
1113 );
1114 }
1115
1116 #[cfg(unix)]
1117 #[test]
1118 fn child_guard_kills_process_on_forced_unwind() {
1119 let child = std::process::Command::new("sleep")
1120 .arg("60")
1121 .spawn()
1122 .unwrap();
1123 let pid = child.id().to_string();
1124
1125 let unwind = std::panic::catch_unwind(|| {
1126 let _guard = ChildGuard::new(child);
1127 panic!("forced startup unwind");
1128 });
1129
1130 assert!(unwind.is_err());
1131 assert!(
1132 !std::process::Command::new("kill")
1133 .args(["-0", &pid])
1134 .status()
1135 .unwrap()
1136 .success()
1137 );
1138 }
1139}