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 query::options::QueryOptions;
26use serde::{Deserialize, Serialize};
27use servers::grpc::GrpcOptions;
28use servers::http::HttpOptions;
29use servers::server::ServerHandlers;
30use snafu::ResultExt;
31
32use crate::error;
33use crate::error::Result;
34use crate::heartbeat::HeartbeatTask;
35use crate::instance::Instance;
36use crate::service_config::{
37 InfluxdbOptions, JaegerOptions, MysqlOptions, OpentsdbOptions, OtlpOptions, PostgresOptions,
38 PromStoreOptions,
39};
40
41#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
42#[serde(default)]
43pub struct FrontendOptions {
44 pub node_id: Option<String>,
45 pub default_timezone: Option<String>,
46 pub default_column_prefix: Option<String>,
47 pub max_in_flight_write_bytes: ReadableSize,
50 pub write_bytes_exhausted_policy: OnExhaustedPolicy,
53 pub http: HttpOptions,
54 pub grpc: GrpcOptions,
55 pub internal_grpc: Option<GrpcOptions>,
58 pub mysql: MysqlOptions,
59 pub postgres: PostgresOptions,
60 pub opentsdb: OpentsdbOptions,
61 pub influxdb: InfluxdbOptions,
62 pub prom_store: PromStoreOptions,
63 pub jaeger: JaegerOptions,
64 pub otlp: OtlpOptions,
65 pub meta_client: Option<MetaClientOptions>,
66 pub logging: LoggingOptions,
67 pub datanode: DatanodeClientOptions,
68 pub user_provider: Option<String>,
69 pub tracing: TracingOptions,
70 pub query: QueryOptions,
71 pub slow_query: SlowQueryOptions,
72 pub memory: MemoryOptions,
73 pub event_recorder: EventRecorderOptions,
75 pub heartbeat_env_vars: Vec<String>,
77}
78
79impl Default for FrontendOptions {
80 fn default() -> Self {
81 Self {
82 node_id: None,
83 default_timezone: None,
84 default_column_prefix: None,
85 max_in_flight_write_bytes: ReadableSize(0),
86 write_bytes_exhausted_policy: OnExhaustedPolicy::default(),
87 http: HttpOptions::default(),
88 grpc: GrpcOptions::default(),
89 internal_grpc: None,
90 mysql: MysqlOptions::default(),
91 postgres: PostgresOptions::default(),
92 opentsdb: OpentsdbOptions::default(),
93 influxdb: InfluxdbOptions::default(),
94 jaeger: JaegerOptions::default(),
95 prom_store: PromStoreOptions::default(),
96 otlp: OtlpOptions::default(),
97 meta_client: None,
98 logging: LoggingOptions::default(),
99 datanode: DatanodeClientOptions::default(),
100 user_provider: None,
101 tracing: TracingOptions::default(),
102 query: QueryOptions::default(),
103 slow_query: SlowQueryOptions::default(),
104 memory: MemoryOptions::default(),
105 event_recorder: EventRecorderOptions::default(),
106 heartbeat_env_vars: vec![],
107 }
108 }
109}
110
111impl Configurable for FrontendOptions {
112 fn env_list_keys() -> Option<&'static [&'static str]> {
113 Some(&["heartbeat_env_vars", "meta_client.metasrv_addrs"])
114 }
115}
116
117pub struct Frontend {
120 pub instance: Arc<Instance>,
121 pub servers: ServerHandlers,
122 pub heartbeat_task: Option<HeartbeatTask>,
123}
124
125impl Frontend {
126 pub async fn start(&mut self) -> Result<()> {
127 if let Some(t) = &self.heartbeat_task {
128 t.start().await?;
129 }
130
131 self.servers
132 .start_all()
133 .await
134 .context(error::StartServerSnafu)
135 }
136
137 pub async fn shutdown(&mut self) -> Result<()> {
138 self.servers
139 .shutdown_all()
140 .await
141 .context(error::ShutdownServerSnafu)
142 }
143
144 pub fn server_handlers(&self) -> &ServerHandlers {
145 &self.servers
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use std::sync::atomic::{AtomicBool, Ordering};
152 use std::time::Duration;
153
154 use api::v1::meta::heartbeat_server::HeartbeatServer;
155 use api::v1::meta::mailbox_message::Payload;
156 use api::v1::meta::{
157 AskLeaderRequest, AskLeaderResponse, HeartbeatRequest, HeartbeatResponse, MailboxMessage,
158 Peer, ResponseHeader, Role, heartbeat_server,
159 };
160 use async_trait::async_trait;
161 use client::{Client, Database};
162 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
163 use common_error::ext::ErrorExt;
164 use common_error::from_header_to_err_code_msg;
165 use common_error::status_code::StatusCode;
166 use common_grpc::channel_manager::ChannelManager;
167 use common_meta::heartbeat::handler::HandlerGroupExecutor;
168 use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler;
169 use common_meta::heartbeat::handler::suspend::SuspendHandler;
170 use common_meta::instruction::Instruction;
171 use common_stat::ResourceStatImpl;
172 use meta_client::MetaClientRef;
173 use meta_client::client::MetaClientBuilder;
174 use meta_srv::service::GrpcStream;
175 use servers::grpc::{FlightCompression, GRPC_SERVER};
176 use servers::http::HTTP_SERVER;
177 use servers::http::result::greptime_result_v1::GreptimedbV1Response;
178 use tokio::sync::mpsc;
179 use tonic::codec::CompressionEncoding;
180 use tonic::codegen::tokio_stream::StreamExt;
181 use tonic::codegen::tokio_stream::wrappers::ReceiverStream;
182 use tonic::{Request, Response, Status, Streaming};
183
184 use super::*;
185 use crate::instance::builder::FrontendBuilder;
186 use crate::server::Services;
187
188 #[test]
189 fn test_toml() {
190 let opts = FrontendOptions::default();
191 let toml_string = toml::to_string(&opts).unwrap();
192 let _parsed: FrontendOptions = toml::from_str(&toml_string).unwrap();
193 }
194
195 struct SuspendableHeartbeatServer {
196 suspend: Arc<AtomicBool>,
197 }
198
199 #[async_trait]
200 impl heartbeat_server::Heartbeat for SuspendableHeartbeatServer {
201 type HeartbeatStream = GrpcStream<HeartbeatResponse>;
202
203 async fn heartbeat(
204 &self,
205 request: Request<Streaming<HeartbeatRequest>>,
206 ) -> std::result::Result<Response<Self::HeartbeatStream>, Status> {
207 let (tx, rx) = mpsc::channel(4);
208
209 common_runtime::spawn_global({
210 let mut requests = request.into_inner();
211 let suspend = self.suspend.clone();
212 async move {
213 let heartbeat_interval_ms = Duration::from_millis(200).as_millis() as u64;
216 let mut is_handshake = true;
217 while let Some(request) = requests.next().await {
218 if let Err(e) = request {
219 let _ = tx.send(Err(e)).await;
220 return;
221 }
222
223 let mailbox_message =
224 suspend.load(Ordering::Relaxed).then(|| MailboxMessage {
225 payload: Some(Payload::Json(
226 serde_json::to_string(&Instruction::Suspend).unwrap(),
227 )),
228 ..Default::default()
229 });
230 let heartbeat_config =
231 is_handshake.then_some(api::v1::meta::HeartbeatConfig {
232 heartbeat_interval_ms,
233 retry_interval_ms: heartbeat_interval_ms,
234 });
235 is_handshake = false;
236 let response = HeartbeatResponse {
237 header: Some(ResponseHeader::success()),
238 mailbox_message,
239 heartbeat_config,
240 ..Default::default()
241 };
242
243 let _ = tx.send(Ok(response)).await;
244 }
245 }
246 });
247
248 Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
249 }
250
251 async fn ask_leader(
252 &self,
253 _: Request<AskLeaderRequest>,
254 ) -> std::result::Result<Response<AskLeaderResponse>, Status> {
255 Ok(Response::new(AskLeaderResponse {
256 header: Some(ResponseHeader::success()),
257 leader: Some(Peer {
258 addr: "localhost:0".to_string(),
259 ..Default::default()
260 }),
261 }))
262 }
263 }
264
265 async fn create_meta_client(
266 options: &MetaClientOptions,
267 heartbeat_server: Arc<SuspendableHeartbeatServer>,
268 ) -> MetaClientRef {
269 let (client, server) = tokio::io::duplex(1024);
270
271 common_runtime::spawn_global(async move {
273 let mut router = tonic::transport::Server::builder();
274 let router = router.add_service(
275 HeartbeatServer::from_arc(heartbeat_server)
276 .accept_compressed(CompressionEncoding::Zstd)
277 .send_compressed(CompressionEncoding::Zstd),
278 );
279 router
280 .serve_with_incoming(futures::stream::iter([Ok::<_, std::io::Error>(server)]))
281 .await
282 });
283
284 let mut client = Some(client);
287 let connector = tower::service_fn(move |_| {
288 let client = client.take();
289 async move {
290 if let Some(client) = client {
291 Ok(hyper_util::rt::TokioIo::new(client))
292 } else {
293 Err(std::io::Error::other("client already taken"))
294 }
295 }
296 });
297 let manager = ChannelManager::new();
298 manager
299 .reset_with_connector("localhost:0", connector)
300 .unwrap();
301
302 let mut client = MetaClientBuilder::new(0, Role::Frontend)
304 .enable_heartbeat()
305 .heartbeat_channel_manager(manager)
306 .build();
307 client.start(&options.metasrv_addrs).await.unwrap();
308 Arc::new(client)
309 }
310
311 async fn create_frontend(
312 options: &FrontendOptions,
313 meta_client: MetaClientRef,
314 ) -> Result<Frontend> {
315 let instance = Arc::new(
316 FrontendBuilder::new_test(options, meta_client.clone())
317 .try_build()
318 .await?,
319 );
320
321 let servers =
322 Services::new(options.clone(), instance.clone(), Default::default()).build()?;
323
324 let executor = Arc::new(HandlerGroupExecutor::new(vec![
325 Arc::new(ParseMailboxMessageHandler),
326 Arc::new(SuspendHandler::new(instance.suspend_state())),
327 ]));
328 let heartbeat_task = Some(HeartbeatTask::new(
329 options,
330 meta_client,
331 executor,
332 Arc::new(ResourceStatImpl::default()),
333 ));
334
335 let mut frontend = Frontend {
336 instance,
337 servers,
338 heartbeat_task,
339 };
340 frontend.start().await?;
341 Ok(frontend)
342 }
343
344 async fn verify_suspend_state_by_http(
345 frontend: &Frontend,
346 expected: std::result::Result<&str, (StatusCode, &str)>,
347 ) {
348 let addr = frontend.server_handlers().addr(HTTP_SERVER).unwrap();
349 let response = reqwest::get(format!("http://{}/v1/sql?sql=SELECT 1", addr))
350 .await
351 .unwrap();
352
353 let headers = response.headers();
354 let response = if let Some((code, error)) = from_header_to_err_code_msg(headers) {
355 Err((code, error))
356 } else {
357 Ok(response.text().await.unwrap())
358 };
359
360 match (response, expected) {
361 (Ok(response), Ok(expected)) => {
362 let response: GreptimedbV1Response = serde_json::from_str(&response).unwrap();
363 let response = serde_json::to_string(response.output()).unwrap();
364 assert_eq!(&response, expected);
365 }
366 (Err(actual), Err(expected)) => assert_eq!(actual, expected),
367 _ => unreachable!(),
368 }
369 }
370
371 async fn verify_suspend_state_by_grpc(
372 frontend: &Frontend,
373 expected: std::result::Result<&str, (StatusCode, &str)>,
374 ) {
375 let addr = frontend.server_handlers().addr(GRPC_SERVER).unwrap();
376 let client = Client::with_urls([addr.to_string()]);
377 let client = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client);
378 let response = client.sql("SELECT 1").await;
379
380 match (response, expected) {
381 (Ok(response), Ok(expected)) => {
382 let response = response.data.pretty_print().await;
383 assert_eq!(&response, expected.trim());
384 }
385 (Err(actual), Err(expected)) => {
386 assert_eq!(actual.status_code(), expected.0);
387 assert_eq!(actual.output_msg(), expected.1);
388 }
389 _ => unreachable!(),
390 }
391 }
392
393 async fn wait_for_suspend_state(frontend: &Frontend, expected: bool) {
394 let check = || frontend.instance.is_suspended() == expected;
395 if check() {
396 return;
397 }
398
399 tokio::time::timeout(Duration::from_secs(5), async move {
400 while !check() {
401 tokio::time::sleep(Duration::from_millis(20)).await;
402 }
403 })
404 .await
405 .unwrap();
406 }
407
408 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
409 async fn test_suspend_frontend() -> Result<()> {
410 common_telemetry::init_default_ut_logging();
411
412 let meta_client_options = MetaClientOptions {
413 metasrv_addrs: vec!["localhost:0".to_string()],
414 ..Default::default()
415 };
416 let options = FrontendOptions {
417 http: HttpOptions {
418 addr: "127.0.0.1:0".to_string(),
419 ..Default::default()
420 },
421 grpc: GrpcOptions {
422 bind_addr: "127.0.0.1:0".to_string(),
423 flight_compression: FlightCompression::None,
424 ..Default::default()
425 },
426 mysql: MysqlOptions {
427 enable: false,
428 ..Default::default()
429 },
430 postgres: PostgresOptions {
431 enable: false,
432 ..Default::default()
433 },
434 meta_client: Some(meta_client_options.clone()),
435 ..Default::default()
436 };
437
438 let server = Arc::new(SuspendableHeartbeatServer {
439 suspend: Arc::new(AtomicBool::new(false)),
440 });
441 let meta_client = create_meta_client(&meta_client_options, server.clone()).await;
442 let frontend = create_frontend(&options, meta_client).await?;
443
444 assert!(!frontend.instance.is_suspended());
446 verify_suspend_state_by_http(&frontend, Ok(r#"[{"records":{"schema":{"column_schemas":[{"name":"Int64(1)","data_type":"Int64"}]},"rows":[[1]],"total_rows":1}}]"#)).await;
447 verify_suspend_state_by_grpc(
448 &frontend,
449 Ok(r#"
450+----------+
451| Int64(1) |
452+----------+
453| 1 |
454+----------+"#),
455 )
456 .await;
457
458 server.suspend.store(true, Ordering::Relaxed);
460 wait_for_suspend_state(&frontend, true).await;
461 assert!(frontend.instance.is_suspended());
463 verify_suspend_state_by_http(
464 &frontend,
465 Err((
466 StatusCode::Suspended,
467 "error: Service suspended, execution_time_ms: 0",
468 )),
469 )
470 .await;
471 verify_suspend_state_by_grpc(&frontend, Err((StatusCode::Suspended, "Service suspended")))
472 .await;
473
474 server.suspend.store(false, Ordering::Relaxed);
476 wait_for_suspend_state(&frontend, false).await;
477 assert!(!frontend.instance.is_suspended());
479 verify_suspend_state_by_http(&frontend, Ok(r#"[{"records":{"schema":{"column_schemas":[{"name":"Int64(1)","data_type":"Int64"}]},"rows":[[1]],"total_rows":1}}]"#)).await;
480 verify_suspend_state_by_grpc(
481 &frontend,
482 Ok(r#"
483+----------+
484| Int64(1) |
485+----------+
486| 1 |
487+----------+"#),
488 )
489 .await;
490 Ok(())
491 }
492}