1use std::fmt::Debug;
16use std::path::Path;
17use std::sync::Arc;
18use std::time::Duration;
19
20use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
21use catalog::information_extension::DistributedInformationExtension;
22use catalog::kvbackend::{
23 CachedKvBackendBuilder, KvBackendCatalogManagerBuilder, new_read_only_meta_kv_backend,
24};
25use clap::Parser;
26use client::client_manager::NodeClients;
27use common_base::Plugins;
28use common_config::{Configurable, DEFAULT_DATA_HOME};
29use common_grpc::channel_manager::ChannelConfig;
30use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder};
31use common_meta::heartbeat::handler::HandlerGroupExecutor;
32use common_meta::heartbeat::handler::invalidate_table_cache::InvalidateCacheHandler;
33use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler;
34use common_meta::key::TableMetadataManager;
35use common_meta::key::flow::FlowMetadataManager;
36use common_stat::ResourceStatImpl;
37use common_telemetry::info;
38use common_telemetry::logging::{DEFAULT_LOGGING_DIR, TracingOptions};
39use common_version::{short_version, verbose_version};
40use flow::{
41 FlownodeBuilder, FlownodeInstance, FlownodeServiceBuilder, FrontendClient, FrontendInvoker,
42};
43use meta_client::{MetaClientOptions, MetaClientType};
44use plugins::flownode::context::GrpcConfigureContext;
45use servers::addrs;
46use servers::configurator::GrpcBuilderConfiguratorRef;
47use snafu::{OptionExt, ResultExt, ensure};
48use tracing_appender::non_blocking::WorkerGuard;
49
50use crate::error::{
51 BuildCacheRegistrySnafu, LoadLayeredConfigSnafu, MetaClientInitSnafu, MissingConfigSnafu,
52 OtherSnafu, Result, ShutdownFlownodeSnafu, StartFlownodeSnafu,
53};
54use crate::options::{GlobalOptions, GreptimeOptions};
55use crate::{App, create_resource_limit_metrics, log_versions, maybe_activate_heap_profile};
56
57pub const APP_NAME: &str = "greptime-flownode";
58
59type FlownodeOptions = GreptimeOptions<flow::FlownodeOptions>;
60
61pub struct Instance {
62 flownode: FlownodeInstance,
63 _guard: Vec<WorkerGuard>,
65}
66
67impl Instance {
68 pub fn new(flownode: FlownodeInstance, guard: Vec<WorkerGuard>) -> Self {
69 Self {
70 flownode,
71 _guard: guard,
72 }
73 }
74
75 pub fn flownode(&self) -> &FlownodeInstance {
76 &self.flownode
77 }
78
79 pub fn flownode_mut(&mut self) -> &mut FlownodeInstance {
81 &mut self.flownode
82 }
83}
84
85#[async_trait::async_trait]
86impl App for Instance {
87 fn name(&self) -> &str {
88 APP_NAME
89 }
90
91 async fn start(&mut self) -> Result<()> {
92 plugins::start_flownode_plugins(&self.flownode)
93 .await
94 .context(StartFlownodeSnafu)?;
95
96 self.flownode.start().await.context(StartFlownodeSnafu)
97 }
98
99 async fn stop(&mut self) -> Result<()> {
100 self.flownode
101 .shutdown()
102 .await
103 .context(ShutdownFlownodeSnafu)
104 }
105}
106
107#[derive(Parser)]
108pub struct Command {
109 #[clap(subcommand)]
110 subcmd: SubCommand,
111}
112
113impl Command {
114 pub async fn build(&self, opts: FlownodeOptions) -> Result<Instance> {
115 self.subcmd.build(opts).await
116 }
117
118 pub fn load_options(&self, global_options: &GlobalOptions) -> Result<FlownodeOptions> {
119 match &self.subcmd {
120 SubCommand::Start(cmd) => cmd.load_options(global_options),
121 }
122 }
123}
124
125#[derive(Parser)]
126enum SubCommand {
127 Start(StartCommand),
128}
129
130impl SubCommand {
131 async fn build(&self, opts: FlownodeOptions) -> Result<Instance> {
132 match self {
133 SubCommand::Start(cmd) => cmd.build(opts).await,
134 }
135 }
136}
137
138#[derive(Debug, Parser, Default)]
139struct StartCommand {
140 #[clap(long)]
142 node_id: Option<u64>,
143 #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
145 grpc_bind_addr: Option<String>,
146 #[clap(
150 long = "grpc-server-addr",
151 alias = "rpc-server-addr",
152 alias = "rpc-hostname"
153 )]
154 grpc_server_addr: Option<String>,
155 #[clap(long, value_delimiter = ',', num_args = 1..)]
157 metasrv_addrs: Option<Vec<String>>,
158 #[clap(short, long)]
160 config_file: Option<String>,
161 #[clap(long, default_value = "GREPTIMEDB_FLOWNODE")]
163 env_prefix: String,
164 #[clap(long)]
165 http_addr: Option<String>,
166 #[clap(long)]
168 http_timeout: Option<u64>,
169}
170
171impl StartCommand {
172 fn load_options(&self, global_options: &GlobalOptions) -> Result<FlownodeOptions> {
173 let mut opts = FlownodeOptions::load_layered_options(
174 self.config_file.as_deref(),
175 self.env_prefix.as_ref(),
176 )
177 .context(LoadLayeredConfigSnafu)?;
178
179 self.merge_with_cli_options(global_options, &mut opts)?;
180
181 Ok(opts)
182 }
183
184 fn merge_with_cli_options(
186 &self,
187 global_options: &GlobalOptions,
188 opts: &mut FlownodeOptions,
189 ) -> Result<()> {
190 let opts = &mut opts.component;
191
192 if let Some(dir) = &global_options.log_dir {
193 opts.logging.dir.clone_from(dir);
194 }
195
196 if opts.logging.dir.is_empty() {
198 opts.logging.dir = Path::new(DEFAULT_DATA_HOME)
199 .join(DEFAULT_LOGGING_DIR)
200 .to_string_lossy()
201 .to_string();
202 }
203
204 if global_options.log_level.is_some() {
205 opts.logging.level.clone_from(&global_options.log_level);
206 }
207
208 opts.tracing = TracingOptions {
209 #[cfg(feature = "tokio-console")]
210 tokio_console_addr: global_options.tokio_console_addr.clone(),
211 };
212
213 if let Some(addr) = &self.grpc_bind_addr {
214 opts.grpc.bind_addr.clone_from(addr);
215 }
216
217 if let Some(server_addr) = &self.grpc_server_addr {
218 opts.grpc.server_addr.clone_from(server_addr);
219 }
220
221 if let Some(node_id) = self.node_id {
222 opts.node_id = Some(node_id);
223 }
224
225 if let Some(metasrv_addrs) = &self.metasrv_addrs {
226 opts.meta_client
227 .get_or_insert_with(MetaClientOptions::default)
228 .metasrv_addrs
229 .clone_from(metasrv_addrs);
230 }
231
232 if let Some(http_addr) = &self.http_addr {
233 opts.http.addr.clone_from(http_addr);
234 }
235
236 if let Some(http_timeout) = self.http_timeout {
237 opts.http.timeout = Duration::from_secs(http_timeout);
238 }
239
240 ensure!(
241 opts.node_id.is_some(),
242 MissingConfigSnafu {
243 msg: "Missing node id option"
244 }
245 );
246
247 Ok(())
248 }
249
250 async fn build(&self, opts: FlownodeOptions) -> Result<Instance> {
251 let guard = common_telemetry::init_global_logging(
252 APP_NAME,
253 &opts.component.logging,
254 &opts.component.tracing,
255 opts.component.node_id.map(|x| x.to_string()),
256 None,
257 );
258
259 common_runtime::init_global_runtimes(&opts.runtime);
260
261 crate::options::flush_dropped_plugin_warnings();
262 log_versions(verbose_version(), short_version(), APP_NAME);
263 maybe_activate_heap_profile(&opts.component.memory);
264 create_resource_limit_metrics(APP_NAME);
265
266 info!("Flownode start command: {:#?}", self);
267 info!("Flownode options: {:#?}", opts);
268
269 let plugin_opts = opts.plugins;
270 let mut opts = opts.component;
271 opts.grpc.detect_server_addr();
272
273 let mut plugins = Plugins::new();
274 plugins::setup_flownode_plugins_pre_build(&mut plugins, &plugin_opts, &opts)
275 .await
276 .context(StartFlownodeSnafu)?;
277
278 let member_id = opts
279 .node_id
280 .context(MissingConfigSnafu { msg: "'node_id'" })?;
281
282 let meta_config = opts.meta_client.as_ref().context(MissingConfigSnafu {
283 msg: "'meta_client_options'",
284 })?;
285
286 let meta_client = meta_client::create_meta_client(
287 MetaClientType::Flownode { member_id },
288 meta_config,
289 None,
290 None,
291 )
292 .await
293 .context(MetaClientInitSnafu)?;
294
295 let cache_max_capacity = meta_config.metadata_cache_max_capacity;
296 let cache_ttl = meta_config.metadata_cache_ttl;
297 let cache_tti = meta_config.metadata_cache_tti;
298
299 let readonly_meta_backend = new_read_only_meta_kv_backend(meta_client.clone());
300
301 let cached_meta_backend = CachedKvBackendBuilder::new(readonly_meta_backend.clone())
303 .cache_max_capacity(cache_max_capacity)
304 .cache_ttl(cache_ttl)
305 .cache_tti(cache_tti)
306 .build();
307 let cached_meta_backend = Arc::new(cached_meta_backend);
308
309 let layered_cache_builder = LayeredCacheRegistryBuilder::default().add_cache_registry(
311 CacheRegistryBuilder::default()
312 .add_cache(cached_meta_backend.clone())
313 .build(),
314 );
315 let fundamental_cache_registry =
316 build_fundamental_cache_registry(readonly_meta_backend.clone());
317 let layered_cache_registry = Arc::new(
318 with_default_composite_cache_registry(
319 layered_cache_builder.add_cache_registry(fundamental_cache_registry),
320 )
321 .context(BuildCacheRegistrySnafu)?
322 .build(),
323 );
324
325 let channel_config = ChannelConfig {
328 timeout: None,
329 ..Default::default()
330 };
331 let client = Arc::new(NodeClients::new(channel_config));
332
333 let information_extension = Arc::new(DistributedInformationExtension::new(
334 meta_client.clone(),
335 client.clone(),
336 ));
337 let catalog_manager = KvBackendCatalogManagerBuilder::new(
338 information_extension,
339 cached_meta_backend.clone(),
340 layered_cache_registry.clone(),
341 )
342 .build();
343
344 let table_metadata_manager =
345 Arc::new(TableMetadataManager::new(cached_meta_backend.clone()));
346
347 let executor = HandlerGroupExecutor::new(vec![
348 Arc::new(ParseMailboxMessageHandler),
349 Arc::new(InvalidateCacheHandler::new(layered_cache_registry.clone())),
350 ]);
351
352 let mut resource_stat = ResourceStatImpl::default();
353 resource_stat.start_collect_cpu_usage();
354
355 let heartbeat_task = flow::heartbeat::HeartbeatTask::new(
356 &opts,
357 meta_client.clone(),
358 Arc::new(executor),
359 Arc::new(resource_stat),
360 );
361
362 let flow_metadata_manager = Arc::new(FlowMetadataManager::new(cached_meta_backend.clone()));
363 let frontend_client = FrontendClient::from_meta_client(
364 meta_client.clone(),
365 opts.query.clone(),
366 opts.flow.batching_mode.clone(),
367 )
368 .context(StartFlownodeSnafu)?;
369 let frontend_client = Arc::new(frontend_client);
370 let mut flownode_builder = FlownodeBuilder::new(
371 opts.clone(),
372 plugins.clone(),
373 table_metadata_manager,
374 catalog_manager.clone(),
375 flow_metadata_manager,
376 frontend_client.clone(),
377 )
378 .with_heartbeat_task(heartbeat_task);
379
380 plugins::setup_flownode_plugins_post_build(&mut plugins, &plugin_opts, &flownode_builder)
381 .await
382 .context(StartFlownodeSnafu)?;
383 flownode_builder.set_plugins(plugins.clone());
384
385 let mut flownode = flownode_builder.build().await.context(StartFlownodeSnafu)?;
386
387 let builder =
388 FlownodeServiceBuilder::grpc_server_builder(&opts, flownode.flownode_server());
389 let builder = if let Some(configurator) =
390 plugins.get::<GrpcBuilderConfiguratorRef<GrpcConfigureContext>>()
391 {
392 let context = GrpcConfigureContext {
393 kv_backend: cached_meta_backend.clone(),
394 fe_client: frontend_client.clone(),
395 flownode_id: member_id,
396 catalog_manager: catalog_manager.clone(),
397 };
398 configurator
399 .configure(builder, context)
400 .await
401 .context(OtherSnafu)?
402 } else {
403 builder
404 };
405 let grpc_server = builder.build();
406
407 let services = FlownodeServiceBuilder::new(&opts)
408 .with_grpc_server(grpc_server)
409 .enable_http_service()
410 .build()
411 .context(StartFlownodeSnafu)?;
412 flownode.setup_services(services);
413 let flownode = flownode;
414
415 let invoker = FrontendInvoker::build_from(
416 flownode.flow_engine().streaming_engine(),
417 catalog_manager.clone(),
418 cached_meta_backend.clone(),
419 layered_cache_registry.clone(),
420 meta_client.clone(),
421 client,
422 addrs::resolve_addr(&opts.grpc.bind_addr, Some(&opts.grpc.server_addr)),
423 )
424 .await
425 .context(StartFlownodeSnafu)?;
426 flownode
427 .flow_engine()
428 .streaming_engine()
429 .set_frontend_invoker(invoker)
431 .await;
432
433 Ok(Instance::new(flownode, guard))
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use clap::{CommandFactory, Parser};
440
441 use super::*;
442
443 #[test]
444 fn test_parse_grpc_cli_aliases() {
445 let command = StartCommand::try_parse_from([
446 "flownode",
447 "--grpc-bind-addr",
448 "127.0.0.1:14004",
449 "--grpc-server-addr",
450 "10.0.0.1:14004",
451 ])
452 .unwrap();
453 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:14004"));
454 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.1:14004"));
455
456 let command = StartCommand::try_parse_from([
457 "flownode",
458 "--rpc-bind-addr",
459 "127.0.0.1:24004",
460 "--rpc-server-addr",
461 "10.0.0.2:24004",
462 ])
463 .unwrap();
464 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:24004"));
465 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.2:24004"));
466
467 let command = StartCommand::try_parse_from([
468 "flownode",
469 "--rpc-addr",
470 "127.0.0.1:34004",
471 "--rpc-hostname",
472 "10.0.0.3:34004",
473 ])
474 .unwrap();
475 assert_eq!(command.grpc_bind_addr.as_deref(), Some("127.0.0.1:34004"));
476 assert_eq!(command.grpc_server_addr.as_deref(), Some("10.0.0.3:34004"));
477 }
478
479 #[test]
480 fn test_help_uses_grpc_option_names() {
481 let mut cmd = StartCommand::command();
482 let mut help = Vec::new();
483 cmd.write_long_help(&mut help).unwrap();
484 let help = String::from_utf8(help).unwrap();
485
486 assert!(help.contains("--grpc-bind-addr"));
487 assert!(help.contains("--grpc-server-addr"));
488 assert!(!help.contains("--rpc-bind-addr"));
489 assert!(!help.contains("--rpc-server-addr"));
490 assert!(!help.contains("--rpc-addr"));
491 assert!(!help.contains("--rpc-hostname"));
492 assert!(!help.contains("--user-provider"));
493 }
494
495 #[test]
496 fn test_user_provider_cli_option_is_removed() {
497 let command = StartCommand::try_parse_from([
498 "flownode",
499 "--node-id",
500 "14",
501 "--user-provider",
502 "static_user_provider:cmd:test=test",
503 ]);
504 assert!(command.is_err());
505 }
506}