Skip to main content

cmd/
flownode.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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    // Keep the logging guard to prevent the worker from being dropped.
64    _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    /// allow customizing flownode for downstream projects
80    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    /// Flownode's id
141    #[clap(long)]
142    node_id: Option<u64>,
143    /// Bind address for the gRPC server.
144    #[clap(long = "grpc-bind-addr", alias = "rpc-bind-addr", alias = "rpc-addr")]
145    grpc_bind_addr: Option<String>,
146    /// The address advertised to the metasrv, and used for connections from outside the host.
147    /// If left empty or unset, the server will automatically use the IP address of the first network interface
148    /// on the host, with the same port number as the one specified in `grpc_bind_addr`.
149    #[clap(
150        long = "grpc-server-addr",
151        alias = "rpc-server-addr",
152        alias = "rpc-hostname"
153    )]
154    grpc_server_addr: Option<String>,
155    /// Metasrv address list;
156    #[clap(long, value_delimiter = ',', num_args = 1..)]
157    metasrv_addrs: Option<Vec<String>>,
158    /// The configuration file for flownode
159    #[clap(short, long)]
160    config_file: Option<String>,
161    /// The prefix of environment variables, default is `GREPTIMEDB_FLOWNODE`;
162    #[clap(long, default_value = "GREPTIMEDB_FLOWNODE")]
163    env_prefix: String,
164    #[clap(long)]
165    http_addr: Option<String>,
166    /// HTTP request timeout in seconds.
167    #[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    // The precedence order is: cli > config file > environment variables > default values.
185    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 the logging dir is not set, use the default logs dir in the data home.
197        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        // TODO(discord9): add helper function to ease the creation of cache registry&such
302        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        // Builds cache registry
310        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        // flownode's frontend to datanode need not timeout.
326        // Some queries are expected to take long time.
327        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            // TODO(discord9): refactor and avoid circular reference
430            .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}