1use std::sync::Arc;
16
17use common_base::readable_size::ReadableSize;
18use common_config::config::Configurable;
19use common_event_recorder::EventRecorderOptions;
20use common_memory_manager::OnExhaustedPolicy;
21use common_options::datanode::DatanodeClientOptions;
22use common_options::memory::MemoryOptions;
23use common_telemetry::logging::{LoggingOptions, SlowQueryOptions, TracingOptions};
24use meta_client::MetaClientOptions;
25use pipeline::PipelineOptions;
26use query::options::QueryOptions;
27use serde::{Deserialize, Serialize};
28use servers::grpc::GrpcOptions;
29use servers::http::HttpOptions;
30use servers::server::ServerHandlers;
31use snafu::ResultExt;
32
33use crate::error;
34use crate::error::Result;
35use crate::heartbeat::HeartbeatTask;
36use crate::instance::Instance;
37use crate::service_config::{
38 InfluxdbOptions, JaegerOptions, MysqlOptions, OpentsdbOptions, OtlpOptions,
39 PendingRowsBatcherOptions, PostgresOptions, PromStoreOptions, WriteRetryOptions,
40};
41
42#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
44#[serde(default)]
45pub struct FrontendOptions {
46 pub node_id: Option<String>,
47 pub default_timezone: Option<String>,
48 pub default_column_prefix: Option<String>,
49 pub auto_create_table: bool,
54 pub max_in_flight_write_bytes: ReadableSize,
57 pub write_bytes_exhausted_policy: OnExhaustedPolicy,
60 pub http: HttpOptions,
61 pub grpc: GrpcOptions,
62 pub internal_grpc: Option<GrpcOptions>,
66 pub mysql: MysqlOptions,
67 pub postgres: PostgresOptions,
68 pub opentsdb: OpentsdbOptions,
69 pub influxdb: InfluxdbOptions,
70 pub pending_rows_batcher: PendingRowsBatcherOptions,
72 pub prom_store: PromStoreOptions,
73 pub jaeger: JaegerOptions,
74 pub otlp: OtlpOptions,
75 pub meta_client: Option<MetaClientOptions>,
76 pub logging: LoggingOptions,
77 pub datanode: DatanodeClientOptions,
78 pub write_retry: WriteRetryOptions,
80 pub user_provider: Option<String>,
81 pub tracing: TracingOptions,
82 pub query: QueryOptions,
83 pub slow_query: SlowQueryOptions,
84 pub memory: MemoryOptions,
85 pub pipeline: PipelineOptions,
87 pub event_recorder: EventRecorderOptions,
89 pub heartbeat_env_vars: Vec<String>,
91}
92
93impl Default for FrontendOptions {
94 fn default() -> Self {
95 Self {
96 node_id: None,
97 default_timezone: None,
98 default_column_prefix: None,
99 auto_create_table: true,
100 max_in_flight_write_bytes: ReadableSize(0),
101 write_bytes_exhausted_policy: OnExhaustedPolicy::default(),
102 http: HttpOptions::default(),
103 grpc: GrpcOptions::default(),
104 internal_grpc: None,
105 mysql: MysqlOptions::default(),
106 postgres: PostgresOptions::default(),
107 opentsdb: OpentsdbOptions::default(),
108 influxdb: InfluxdbOptions::default(),
109 pending_rows_batcher: PendingRowsBatcherOptions::default(),
110 jaeger: JaegerOptions::default(),
111 prom_store: PromStoreOptions::default(),
112 otlp: OtlpOptions::default(),
113 meta_client: None,
114 logging: LoggingOptions::default(),
115 datanode: DatanodeClientOptions::default(),
116 write_retry: WriteRetryOptions::default(),
117 user_provider: None,
118 tracing: TracingOptions::default(),
119 query: QueryOptions::default(),
120 slow_query: SlowQueryOptions::default(),
121 memory: MemoryOptions::default(),
122 pipeline: PipelineOptions::default(),
123 event_recorder: EventRecorderOptions::default(),
124 heartbeat_env_vars: vec![],
125 }
126 }
127}
128
129impl Configurable for FrontendOptions {
130 fn env_list_keys() -> Option<&'static [&'static str]> {
131 Some(&[
132 "heartbeat_env_vars",
133 "meta_client.metasrv_addrs",
134 "event_recorder.event_types",
135 "pending_rows_batcher.protocols",
136 "pending_rows_batcher.logical_table.protocols",
137 ])
138 }
139}
140
141pub struct Frontend {
144 pub instance: Arc<Instance>,
145 pub servers: ServerHandlers,
146 pub heartbeat_task: Option<HeartbeatTask>,
147}
148
149impl Frontend {
150 pub async fn start(&mut self) -> Result<()> {
151 if let Some(t) = &self.heartbeat_task
152 && let Err(error) = t.start().await
153 {
154 t.shutdown().await;
155 return Err(error);
156 }
157
158 if let Err(source) = self.servers.start_all().await {
159 if let Some(t) = &self.heartbeat_task {
160 t.shutdown().await;
161 }
162 return Err(source).context(error::StartServerSnafu);
163 }
164 Ok(())
165 }
166
167 pub async fn shutdown(&mut self) -> Result<()> {
168 if let Some(t) = &self.heartbeat_task {
169 t.shutdown().await;
170 }
171 self.servers
172 .shutdown_all()
173 .await
174 .context(error::ShutdownServerSnafu)
175 }
176
177 pub fn server_handlers(&self) -> &ServerHandlers {
178 &self.servers
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use std::any::Any;
185 use std::net::SocketAddr;
186 use std::pin::Pin;
187 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
188 use std::time::Duration;
189
190 use api::v1::meta::heartbeat_server::HeartbeatServer;
191 use api::v1::meta::mailbox_message::Payload;
192 use api::v1::meta::{
193 AskLeaderRequest, AskLeaderResponse, HeartbeatRequest, HeartbeatResponse, MailboxMessage,
194 Peer, ResponseHeader, Role, heartbeat_server,
195 };
196 use async_trait::async_trait;
197 use client::{Client, Database};
198 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
199 use common_error::ext::ErrorExt;
200 use common_error::from_header_to_err_code_msg;
201 use common_error::status_code::StatusCode;
202 use common_grpc::channel_manager::ChannelManager;
203 use common_meta::heartbeat::handler::HandlerGroupExecutor;
204 use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler;
205 use common_meta::heartbeat::handler::suspend::SuspendHandler;
206 use common_meta::instruction::Instruction;
207 use common_stat::ResourceStatImpl;
208 use futures::Stream;
209 use meta_client::MetaClientRef;
210 use meta_client::client::MetaClientBuilder;
211 use servers::batcher::BatchingProtocol;
212 use servers::grpc::{FlightCompression, GRPC_SERVER};
213 use servers::http::HTTP_SERVER;
214 use servers::http::result::greptime_result_v1::GreptimedbV1Response;
215 use servers::server::Server;
216 use tokio::sync::mpsc;
217 use tonic::codec::CompressionEncoding;
218 use tonic::codegen::tokio_stream::StreamExt;
219 use tonic::codegen::tokio_stream::wrappers::ReceiverStream;
220 use tonic::{Request, Response, Status, Streaming};
221
222 use crate::frontend::*;
223 use crate::heartbeat::{
224 FrontendHeartbeatExtension, FrontendHeartbeatExtensionResult, FrontendHeartbeatExtensions,
225 };
226 use crate::instance::builder::FrontendBuilder;
227 use crate::server::Services;
228
229 type GrpcStream<T> =
230 Pin<Box<dyn Stream<Item = std::result::Result<T, Status>> + Send + Sync + 'static>>;
231
232 #[test]
233 fn test_pipeline_host_access_config() {
234 let defaults: FrontendOptions = toml::from_str("").unwrap();
235 assert!(!defaults.pipeline.allow_vrl_host_access);
236 let options: FrontendOptions =
237 toml::from_str("[pipeline]\nallow_vrl_host_access = true").unwrap();
238 assert!(options.pipeline.allow_vrl_host_access);
239 let serialized = toml::to_string(&options).unwrap();
240 let restored: FrontendOptions = toml::from_str(&serialized).unwrap();
241 assert_eq!(options.pipeline, restored.pipeline);
242 }
243
244 #[test]
245 fn test_write_retry_config() {
246 let defaults: FrontendOptions = toml::from_str("").unwrap();
247 assert_eq!(defaults.write_retry, WriteRetryOptions::default());
248 let options: FrontendOptions = toml::from_str(
249 r#"
250[write_retry]
251max_retries = 5
252initial_backoff = "250ms"
253max_backoff = "5s"
254backoff_multiplier = 3.0
255"#,
256 )
257 .unwrap();
258 assert_eq!(
259 options.write_retry,
260 WriteRetryOptions {
261 max_retries: 5,
262 initial_backoff: Duration::from_millis(250),
263 max_backoff: Duration::from_secs(5),
264 backoff_multiplier: 3.0,
265 }
266 );
267 options.write_retry.validate().unwrap();
268 let serialized = toml::to_string(&options).unwrap();
269 let parsed: FrontendOptions = toml::from_str(&serialized).unwrap();
270 assert_eq!(options.write_retry, parsed.write_retry);
271 let disabled: FrontendOptions = toml::from_str("[write_retry]\nmax_retries = 0").unwrap();
272 assert_eq!(disabled.write_retry.max_retries, 0);
273 assert_eq!(
274 disabled.write_retry.initial_backoff,
275 defaults.write_retry.initial_backoff
276 );
277 }
278
279 #[test]
280 fn test_write_retry_config_from_env() {
281 temp_env::with_vars(
282 [
283 ("FRONTEND_RETRY_TEST__WRITE_RETRY__MAX_RETRIES", Some("4")),
284 (
285 "FRONTEND_RETRY_TEST__WRITE_RETRY__INITIAL_BACKOFF",
286 Some("500ms"),
287 ),
288 ("FRONTEND_RETRY_TEST__WRITE_RETRY__MAX_BACKOFF", Some("8s")),
289 (
290 "FRONTEND_RETRY_TEST__WRITE_RETRY__BACKOFF_MULTIPLIER",
291 Some("1.5"),
292 ),
293 ],
294 || {
295 let options =
296 FrontendOptions::load_layered_options(None, "FRONTEND_RETRY_TEST").unwrap();
297 assert_eq!(
298 options.write_retry,
299 WriteRetryOptions {
300 max_retries: 4,
301 initial_backoff: Duration::from_millis(500),
302 max_backoff: Duration::from_secs(8),
303 backoff_multiplier: 1.5,
304 }
305 );
306 },
307 );
308 }
309
310 #[test]
311 fn test_logical_batcher_protocols_from_env() {
312 temp_env::with_vars(
313 [
314 (
315 "FRONTEND_LOGICAL_TEST__PENDING_ROWS_BATCHER__PROTOCOLS",
316 Some("otlp,influxdb"),
317 ),
318 (
319 "FRONTEND_LOGICAL_TEST__PENDING_ROWS_BATCHER__LOGICAL_TABLE__PROTOCOLS",
320 Some("prom,otlp"),
321 ),
322 ],
323 || {
324 let options =
325 FrontendOptions::load_layered_options(None, "FRONTEND_LOGICAL_TEST").unwrap();
326 assert_eq!(options.pending_rows_batcher.table.protocols.len(), 2);
327 assert_eq!(
328 options
329 .pending_rows_batcher
330 .logical_table
331 .unwrap()
332 .protocols,
333 vec![BatchingProtocol::Prom, BatchingProtocol::Otlp]
334 );
335 },
336 );
337 }
338
339 #[test]
340 fn test_batcher_protocols_from_env() {
341 temp_env::with_vars(
342 [(
343 "FRONTEND_BATCHER_TEST__PENDING_ROWS_BATCHER__PROTOCOLS",
344 Some("influxdb,http_sql"),
345 )],
346 || {
347 let options =
348 FrontendOptions::load_layered_options(None, "FRONTEND_BATCHER_TEST").unwrap();
349 assert_eq!(
350 options.pending_rows_batcher.table.protocols,
351 vec![BatchingProtocol::Influxdb, BatchingProtocol::HttpSql]
352 );
353 },
354 );
355 }
356
357 #[test]
358 fn test_protocol_pending_rows_batcher_config() {
359 let defaults: FrontendOptions = toml::from_str("").unwrap();
360 assert!(
361 !defaults
362 .pending_rows_batcher
363 .table
364 .pending_rows_batching_enabled()
365 );
366 let options: FrontendOptions = toml::from_str(
367 r#"
368[pending_rows_batcher]
369protocols = ["influxdb", "http_sql"]
370pending_rows_flush_interval = "5ms"
371max_batch_rows = 25
372"#,
373 )
374 .unwrap();
375 assert_eq!(options.pending_rows_batcher.table.max_batch_rows, 25);
376 assert_eq!(options.pending_rows_batcher.table.protocols.len(), 2);
377 assert!(
378 options
379 .pending_rows_batcher
380 .table
381 .pending_rows_batching_enabled()
382 );
383 let serialized = toml::to_string(&options).unwrap();
384 let parsed: FrontendOptions = toml::from_str(&serialized).unwrap();
385 assert_eq!(options.influxdb, parsed.influxdb);
386 assert_eq!(options.opentsdb, parsed.opentsdb);
387 assert_eq!(options.pending_rows_batcher, parsed.pending_rows_batcher);
388 }
389
390 #[test]
391 fn test_toml() {
392 let opts = FrontendOptions::default();
393 let toml_string = toml::to_string(&opts).unwrap();
394 let parsed: FrontendOptions = toml::from_str(&toml_string).unwrap();
395 assert_eq!(parsed.otlp, opts.otlp);
396 assert_eq!(parsed.influxdb, opts.influxdb);
397 assert_eq!(parsed.opentsdb, opts.opentsdb);
398 }
399
400 #[test]
401 fn test_http_api_server_defaults_on_when_absent() {
402 let parsed: FrontendOptions = toml::from_str("").unwrap();
405 assert!(!parsed.http.enable_api_server);
406 assert_eq!(parsed.http.api_server_addr, "127.0.0.1:4006");
407 }
408
409 struct SuspendableHeartbeatServer {
410 suspend: Arc<AtomicBool>,
411 fail_heartbeat: bool,
412 }
413
414 struct FailingServer;
415
416 struct ShutdownTrackingExtension {
417 shutdown_calls: AtomicUsize,
418 }
419
420 #[async_trait]
421 impl FrontendHeartbeatExtension for ShutdownTrackingExtension {
422 fn name(&self) -> &str {
423 "shutdown-tracking"
424 }
425
426 async fn shutdown(&self) -> FrontendHeartbeatExtensionResult<()> {
427 self.shutdown_calls.fetch_add(1, Ordering::AcqRel);
428 Ok(())
429 }
430 }
431
432 #[async_trait]
433 impl Server for FailingServer {
434 async fn shutdown(&self) -> servers::error::Result<()> {
435 Ok(())
436 }
437
438 async fn start(&mut self, _listening: SocketAddr) -> servers::error::Result<()> {
439 Err(servers::error::Error::Internal {
440 err_msg: "mock server start failure".to_string(),
441 })
442 }
443
444 fn name(&self) -> &str {
445 "FAILING_SERVER"
446 }
447
448 fn as_any(&self) -> &dyn Any {
449 self
450 }
451 }
452
453 #[async_trait]
454 impl heartbeat_server::Heartbeat for SuspendableHeartbeatServer {
455 type HeartbeatStream = GrpcStream<HeartbeatResponse>;
456
457 async fn heartbeat(
458 &self,
459 request: Request<Streaming<HeartbeatRequest>>,
460 ) -> std::result::Result<Response<Self::HeartbeatStream>, Status> {
461 if self.fail_heartbeat {
462 return Err(Status::unavailable("mock initial heartbeat failure"));
463 }
464
465 let (tx, rx) = mpsc::channel(4);
466
467 common_runtime::spawn_global({
468 let mut requests = request.into_inner();
469 let suspend = self.suspend.clone();
470 async move {
471 let heartbeat_interval_ms = Duration::from_millis(200).as_millis() as u64;
474 let mut is_handshake = true;
475 while let Some(request) = requests.next().await {
476 if let Err(e) = request {
477 let _ = tx.send(Err(e)).await;
478 return;
479 }
480
481 let mailbox_message =
482 suspend.load(Ordering::Relaxed).then(|| MailboxMessage {
483 payload: Some(Payload::Json(
484 serde_json::to_string(&Instruction::Suspend).unwrap(),
485 )),
486 ..Default::default()
487 });
488 let heartbeat_config =
489 is_handshake.then_some(api::v1::meta::HeartbeatConfig {
490 heartbeat_interval_ms,
491 retry_interval_ms: heartbeat_interval_ms,
492 gc_enabled: false,
493 });
494 is_handshake = false;
495 let response = HeartbeatResponse {
496 header: Some(ResponseHeader::success()),
497 mailbox_message,
498 heartbeat_config,
499 ..Default::default()
500 };
501
502 let _ = tx.send(Ok(response)).await;
503 }
504 }
505 });
506
507 Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
508 }
509
510 async fn ask_leader(
511 &self,
512 _: Request<AskLeaderRequest>,
513 ) -> std::result::Result<Response<AskLeaderResponse>, Status> {
514 Ok(Response::new(AskLeaderResponse {
515 header: Some(ResponseHeader::success()),
516 leader: Some(Peer {
517 addr: "localhost:0".to_string(),
518 ..Default::default()
519 }),
520 }))
521 }
522 }
523
524 async fn create_meta_client(
525 options: &MetaClientOptions,
526 heartbeat_server: Arc<SuspendableHeartbeatServer>,
527 ) -> MetaClientRef {
528 let (client, server) = tokio::io::duplex(1024);
529
530 common_runtime::spawn_global(async move {
532 let mut router = tonic::transport::Server::builder();
533 let router = router.add_service(
534 HeartbeatServer::from_arc(heartbeat_server)
535 .accept_compressed(CompressionEncoding::Zstd)
536 .send_compressed(CompressionEncoding::Zstd),
537 );
538 router
539 .serve_with_incoming(futures::stream::iter([Ok::<_, std::io::Error>(server)]))
540 .await
541 });
542
543 let mut client = Some(client);
546 let connector = tower::service_fn(move |_| {
547 let client = client.take();
548 async move {
549 if let Some(client) = client {
550 Ok(hyper_util::rt::TokioIo::new(client))
551 } else {
552 Err(std::io::Error::other("client already taken"))
553 }
554 }
555 });
556 let manager = ChannelManager::new();
557 manager
558 .reset_with_connector("localhost:0", connector)
559 .unwrap();
560
561 let mut client = MetaClientBuilder::new(0, Role::Frontend)
563 .enable_heartbeat()
564 .heartbeat_channel_manager(manager)
565 .build();
566 client.start(&options.metasrv_addrs).await.unwrap();
567 Arc::new(client)
568 }
569
570 async fn create_frontend(
571 options: &FrontendOptions,
572 meta_client: MetaClientRef,
573 ) -> Result<Frontend> {
574 let instance = Arc::new(
575 FrontendBuilder::new_test(options, meta_client.clone())
576 .try_build()
577 .await?,
578 );
579
580 let servers =
581 Services::new(options.clone(), instance.clone(), Default::default()).build()?;
582
583 let executor = Arc::new(HandlerGroupExecutor::new(vec![
584 Arc::new(ParseMailboxMessageHandler),
585 Arc::new(SuspendHandler::new(instance.suspend_state())),
586 ]));
587 let heartbeat_task = Some(HeartbeatTask::new(
588 instance.frontend_peer_addr().to_string(),
589 options,
590 meta_client,
591 executor,
592 Arc::new(ResourceStatImpl::default()),
593 ));
594
595 let mut frontend = Frontend {
596 instance,
597 servers,
598 heartbeat_task,
599 };
600 frontend.start().await?;
601 Ok(frontend)
602 }
603
604 #[tokio::test]
605 async fn test_server_start_failure_shuts_down_heartbeat() {
606 let meta_client_options = MetaClientOptions {
607 metasrv_addrs: vec!["localhost:0".to_string()],
608 ..Default::default()
609 };
610 let options = FrontendOptions {
611 meta_client: Some(meta_client_options.clone()),
612 ..Default::default()
613 };
614 let heartbeat_server = Arc::new(SuspendableHeartbeatServer {
615 suspend: Arc::new(AtomicBool::new(false)),
616 fail_heartbeat: false,
617 });
618 let meta_client = create_meta_client(&meta_client_options, heartbeat_server).await;
619 let instance = Arc::new(
620 FrontendBuilder::new_test(&options, meta_client.clone())
621 .try_build()
622 .await
623 .unwrap(),
624 );
625 let heartbeat_task = HeartbeatTask::new(
626 instance.frontend_peer_addr().to_string(),
627 &options,
628 meta_client,
629 Arc::new(HandlerGroupExecutor::new(vec![])),
630 Arc::new(ResourceStatImpl::default()),
631 );
632 let heartbeat_probe = heartbeat_task.clone();
633 let servers = ServerHandlers::default();
634 servers.insert((Box::new(FailingServer), "127.0.0.1:0".parse().unwrap()));
635 let mut frontend = Frontend {
636 instance,
637 servers,
638 heartbeat_task: Some(heartbeat_task),
639 };
640
641 assert!(frontend.start().await.is_err());
642 assert!(heartbeat_probe.is_shutdown());
643 }
644
645 #[tokio::test]
646 async fn test_heartbeat_start_failure_shuts_down_extensions() {
647 let meta_client_options = MetaClientOptions {
648 metasrv_addrs: vec!["localhost:0".to_string()],
649 ..Default::default()
650 };
651 let options = FrontendOptions {
652 meta_client: Some(meta_client_options.clone()),
653 ..Default::default()
654 };
655 let heartbeat_server = Arc::new(SuspendableHeartbeatServer {
656 suspend: Arc::new(AtomicBool::new(false)),
657 fail_heartbeat: true,
658 });
659 let meta_client = create_meta_client(&meta_client_options, heartbeat_server).await;
660 let instance = Arc::new(
661 FrontendBuilder::new_test(&options, meta_client.clone())
662 .try_build()
663 .await
664 .unwrap(),
665 );
666 let extension = Arc::new(ShutdownTrackingExtension {
667 shutdown_calls: AtomicUsize::new(0),
668 });
669 let extensions = FrontendHeartbeatExtensions::default();
670 assert!(extensions.register(extension.clone()));
671 let heartbeat_task = HeartbeatTask::new(
672 instance.frontend_peer_addr().to_string(),
673 &options,
674 meta_client,
675 Arc::new(HandlerGroupExecutor::new(vec![])),
676 Arc::new(ResourceStatImpl::default()),
677 )
678 .with_extensions(extensions);
679 let heartbeat_probe = heartbeat_task.clone();
680 let mut frontend = Frontend {
681 instance,
682 servers: ServerHandlers::default(),
683 heartbeat_task: Some(heartbeat_task),
684 };
685
686 assert!(frontend.start().await.is_err());
687 assert!(heartbeat_probe.is_shutdown());
688 assert_eq!(extension.shutdown_calls.load(Ordering::Acquire), 1);
689 }
690
691 async fn verify_suspend_state_by_http(
692 frontend: &Frontend,
693 expected: std::result::Result<&str, (StatusCode, &str)>,
694 ) {
695 let addr = frontend.server_handlers().addr(HTTP_SERVER).unwrap();
696 let response = reqwest::get(format!("http://{}/v1/sql?sql=SELECT 1", addr))
697 .await
698 .unwrap();
699
700 let headers = response.headers();
701 let response = if let Some((code, error)) = from_header_to_err_code_msg(headers) {
702 Err((code, error))
703 } else {
704 Ok(response.text().await.unwrap())
705 };
706
707 match (response, expected) {
708 (Ok(response), Ok(expected)) => {
709 let response: GreptimedbV1Response = serde_json::from_str(&response).unwrap();
710 let response = serde_json::to_string(response.output()).unwrap();
711 assert_eq!(&response, expected);
712 }
713 (Err(actual), Err(expected)) => assert_eq!(actual, expected),
714 _ => unreachable!(),
715 }
716 }
717
718 async fn verify_suspend_state_by_grpc(
719 frontend: &Frontend,
720 expected: std::result::Result<&str, (StatusCode, &str)>,
721 ) {
722 let addr = frontend.server_handlers().addr(GRPC_SERVER).unwrap();
723 let client = Client::with_query_and_control_managers(
724 ChannelManager::new(),
725 ChannelManager::new(),
726 [addr.to_string()],
727 );
728 let client = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client);
729 let response = client.sql("SELECT 1").await;
730
731 match (response, expected) {
732 (Ok(response), Ok(expected)) => {
733 let response = response.data.pretty_print().await;
734 assert_eq!(&response, expected.trim());
735 }
736 (Err(actual), Err(expected)) => {
737 assert_eq!(actual.status_code(), expected.0);
738 assert_eq!(actual.output_msg(), expected.1);
739 }
740 _ => unreachable!(),
741 }
742 }
743
744 async fn wait_for_suspend_state(frontend: &Frontend, expected: bool) {
745 let check = || frontend.instance.is_suspended() == expected;
746 if check() {
747 return;
748 }
749
750 tokio::time::timeout(Duration::from_secs(5), async move {
751 while !check() {
752 tokio::time::sleep(Duration::from_millis(20)).await;
753 }
754 })
755 .await
756 .unwrap();
757 }
758
759 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
760 async fn test_suspend_frontend() -> Result<()> {
761 common_telemetry::init_default_ut_logging();
762
763 let meta_client_options = MetaClientOptions {
764 metasrv_addrs: vec!["localhost:0".to_string()],
765 ..Default::default()
766 };
767 let options = FrontendOptions {
768 http: HttpOptions {
769 addr: "127.0.0.1:0".to_string(),
770 ..Default::default()
771 },
772 grpc: GrpcOptions {
773 bind_addr: "127.0.0.1:0".to_string(),
774 flight_compression: FlightCompression::None,
775 ..Default::default()
776 },
777 mysql: MysqlOptions {
778 enable: false,
779 ..Default::default()
780 },
781 postgres: PostgresOptions {
782 enable: false,
783 ..Default::default()
784 },
785 meta_client: Some(meta_client_options.clone()),
786 ..Default::default()
787 };
788
789 let server = Arc::new(SuspendableHeartbeatServer {
790 suspend: Arc::new(AtomicBool::new(false)),
791 fail_heartbeat: false,
792 });
793 let meta_client = create_meta_client(&meta_client_options, server.clone()).await;
794 let frontend = create_frontend(&options, meta_client).await?;
795
796 assert!(!frontend.instance.is_suspended());
798 verify_suspend_state_by_http(&frontend, Ok(r#"[{"records":{"schema":{"column_schemas":[{"name":"Int64(1)","data_type":"Int64"}]},"rows":[[1]],"total_rows":1}}]"#)).await;
799 verify_suspend_state_by_grpc(
800 &frontend,
801 Ok(r#"
802+----------+
803| Int64(1) |
804+----------+
805| 1 |
806+----------+"#),
807 )
808 .await;
809
810 server.suspend.store(true, Ordering::Relaxed);
812 wait_for_suspend_state(&frontend, true).await;
813 assert!(frontend.instance.is_suspended());
815 verify_suspend_state_by_http(
816 &frontend,
817 Err((
818 StatusCode::Suspended,
819 "error: Service suspended, execution_time_ms: 0",
820 )),
821 )
822 .await;
823 verify_suspend_state_by_grpc(&frontend, Err((StatusCode::Suspended, "Service suspended")))
824 .await;
825
826 server.suspend.store(false, Ordering::Relaxed);
828 wait_for_suspend_state(&frontend, false).await;
829 assert!(!frontend.instance.is_suspended());
831 verify_suspend_state_by_http(&frontend, Ok(r#"[{"records":{"schema":{"column_schemas":[{"name":"Int64(1)","data_type":"Int64"}]},"rows":[[1]],"total_rows":1}}]"#)).await;
832 verify_suspend_state_by_grpc(
833 &frontend,
834 Ok(r#"
835+----------+
836| Int64(1) |
837+----------+
838| 1 |
839+----------+"#),
840 )
841 .await;
842 Ok(())
843 }
844}