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