Skip to main content

tests_integration/
cluster.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::collections::{BTreeSet, HashMap};
16use std::env;
17use std::net::TcpListener;
18use std::ops::RangeInclusive;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicUsize, Ordering};
21use std::task::{Context, Poll};
22use std::time::Duration;
23
24use api::v1::health_check_server::HealthCheckServer;
25use api::v1::region::region_server::RegionServer;
26use arrow_flight::flight_service_server::FlightServiceServer;
27use cache::{
28    build_datanode_layered_cache_registry, build_fundamental_cache_registry,
29    with_default_composite_cache_registry,
30};
31use catalog::information_extension::DistributedInformationExtension;
32use catalog::kvbackend::{
33    CachedKvBackendBuilder, KvBackendCatalogManagerBuilder, new_read_only_meta_kv_backend,
34};
35use catalog::process_manager::ProcessManager;
36use client::Client;
37use client::client_manager::NodeClients;
38use cmd::frontend::create_heartbeat_task;
39use common_base::Plugins;
40use common_datasource::object_store::LocalFileAccess;
41use common_event_recorder::EventRecorderOptions;
42use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
43use common_meta::DatanodeId;
44use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder};
45use common_meta::kv_backend::KvBackendRef;
46use common_meta::kv_backend::chroot::ChrootKvBackend;
47use common_meta::kv_backend::etcd::EtcdStore;
48use common_meta::kv_backend::memory::MemoryKvBackend;
49use common_meta::peer::Peer;
50use common_runtime::Builder as RuntimeBuilder;
51use common_runtime::runtime::BuilderBuild;
52use common_test_util::temp_dir::create_temp_dir;
53use common_time::util::DefaultSystemTimer;
54use common_wal::config::{DatanodeWalConfig, MetasrvWalConfig};
55use datanode::config::DatanodeOptions;
56use datanode::datanode::{Datanode, DatanodeBuilder, ProcedureConfig};
57use frontend::frontend::{Frontend, FrontendOptions};
58use frontend::instance::Instance as FeInstance;
59use frontend::instance::builder::FrontendBuilder;
60use frontend::server::Services;
61use futures::TryStreamExt;
62use hyper_util::rt::TokioIo;
63use meta_client::client::MetaClientBuilder;
64use meta_srv::cluster::MetaPeerClientRef;
65use meta_srv::discovery;
66use meta_srv::gc::GcSchedulerOptions;
67use meta_srv::metasrv::{Metasrv, MetasrvOptions, SelectorRef};
68use meta_srv::mocks::MockInfo;
69use mito2::gc::GcConfig;
70use mito2::region::MitoRegionRef;
71use object_store::config::ObjectStoreConfig;
72use rand::Rng;
73use servers::grpc::flight::FlightCraftWrapper;
74use servers::grpc::region_server::RegionServerRequestHandler;
75use servers::grpc::{GrpcOptions, HealthCheckHandler};
76use servers::server::ServerHandlers;
77use store_api::storage::RegionId;
78use tempfile::TempDir;
79use tokio::io::{AsyncRead, AsyncWrite};
80use tonic::codec::CompressionEncoding;
81use tonic::transport::Server;
82use tonic::transport::server::Connected;
83use tower::{Layer, Service, service_fn};
84use uuid::Uuid;
85
86use crate::test_util::{
87    self, FileDirGuard, StorageType, TestGuard, create_datanode_opts,
88    create_tmp_dir_and_datanode_opts,
89};
90
91/// Flight `DoGet` requests another node sent to a datanode over its network address (see
92/// [`GreptimeDbClusterBuilder::with_real_datanode_grpc_addr`]).
93///
94/// The frontend and datanodes inside the test process use in-process clients, so counted requests
95/// come from remote nodes querying regions owned by this datanode.
96#[derive(Debug, Default)]
97pub struct DatanodeRpcStats {
98    do_get_requests: AtomicUsize,
99}
100
101impl DatanodeRpcStats {
102    /// The number of Flight `DoGet` requests another node sent to the datanode.
103    pub fn requests(&self) -> usize {
104        self.do_get_requests.load(Ordering::Relaxed)
105    }
106}
107
108pub struct GreptimeDbCluster {
109    pub guards: Vec<TestGuard>,
110    pub datanode_options: Vec<DatanodeOptions>,
111
112    pub datanode_instances: HashMap<DatanodeId, Datanode>,
113    /// The stats of the network address of every datanode, empty unless the cluster serves the
114    /// datanodes at their own addresses.
115    pub datanode_rpc_stats: HashMap<DatanodeId, Arc<DatanodeRpcStats>>,
116    pub kv_backend: KvBackendRef,
117    pub metasrv: Arc<Metasrv>,
118    pub frontend: Arc<Frontend>,
119}
120
121impl GreptimeDbCluster {
122    pub fn fe_instance(&self) -> &Arc<FeInstance> {
123        &self.frontend.instance
124    }
125
126    /// The stats of the network address of the datanode `datanode_id`, if the cluster serves the
127    /// datanodes at their own addresses (see
128    /// [`GreptimeDbClusterBuilder::with_real_datanode_grpc_addr`]).
129    pub fn datanode_rpc_stats(&self, datanode_id: DatanodeId) -> Option<&Arc<DatanodeRpcStats>> {
130        self.datanode_rpc_stats.get(&datanode_id)
131    }
132
133    /// List all SST files from all datanodes.
134    pub async fn list_sst_files_from_all_datanodes(&self) -> BTreeSet<String> {
135        let mut sst_files = BTreeSet::new();
136
137        for datanode in self.datanode_instances.values() {
138            let region_server = datanode.region_server();
139            let mito = region_server.mito_engine().unwrap();
140            let all_files = mito
141                .all_ssts_from_storage()
142                .try_collect::<Vec<_>>()
143                .await
144                .unwrap()
145                .into_iter()
146                .map(|e| e.file_path)
147                .collect::<Vec<_>>();
148            sst_files.extend(all_files);
149        }
150
151        sst_files
152    }
153
154    /// List all SST files from the manifests of all datanodes.
155    pub async fn list_sst_files_from_manifests(&self) -> BTreeSet<String> {
156        let mut sst_files = BTreeSet::new();
157
158        for datanode in self.datanode_instances.values() {
159            let region_server = datanode.region_server();
160            let mito = region_server.mito_engine().unwrap();
161            let all_files = mito
162                .all_ssts_from_manifest()
163                .await
164                .into_iter()
165                .flat_map(|e| {
166                    if let Some(index_file_path) = e.index_file_path {
167                        vec![e.file_path, index_file_path]
168                    } else {
169                        vec![e.file_path]
170                    }
171                })
172                .collect::<BTreeSet<_>>();
173
174            sst_files.extend(all_files);
175        }
176
177        sst_files
178    }
179
180    pub async fn list_all_regions(&self) -> HashMap<RegionId, MitoRegionRef> {
181        let mut regions = HashMap::new();
182
183        for datanode in self.datanode_instances.values() {
184            let region_server = datanode.region_server();
185            let mito = region_server.mito_engine().unwrap();
186            for region in mito.regions() {
187                regions.insert(region.region_id(), region);
188            }
189        }
190
191        regions
192    }
193}
194
195pub struct GreptimeDbClusterBuilder {
196    cluster_name: String,
197    kv_backend: KvBackendRef,
198    store_config: Option<ObjectStoreConfig>,
199    store_providers: Option<Vec<StorageType>>,
200    datanodes: Option<u32>,
201    datanode_wal_config: DatanodeWalConfig,
202    metasrv_wal_config: MetasrvWalConfig,
203    datanode_gc_config: GcConfig,
204    metasrv_gc_config: GcSchedulerOptions,
205    frontend_auto_create_table: bool,
206    shared_home_dir: Option<Arc<TempDir>>,
207    meta_selector: Option<SelectorRef>,
208    plugins: Plugins,
209    local_file_access: LocalFileAccess,
210    event_recorder_options: EventRecorderOptions,
211    real_datanode_grpc_addr: bool,
212}
213
214impl GreptimeDbClusterBuilder {
215    pub async fn new(cluster_name: &str) -> Self {
216        let endpoints = env::var("GT_ETCD_ENDPOINTS").unwrap_or_default();
217
218        let kv_backend: KvBackendRef = if endpoints.is_empty() {
219            Arc::new(MemoryKvBackend::new())
220        } else {
221            let endpoints = endpoints
222                .split(',')
223                .map(|s| s.to_string())
224                .collect::<Vec<String>>();
225            let backend = EtcdStore::with_endpoints(endpoints, 128)
226                .await
227                .expect("malformed endpoints");
228            // Each retry requires a new isolation namespace.
229            let chroot = format!("{}{}", cluster_name, Uuid::new_v4());
230            Arc::new(ChrootKvBackend::new(chroot.into(), backend))
231        };
232
233        Self {
234            cluster_name: cluster_name.to_string(),
235            kv_backend,
236            store_config: None,
237            store_providers: None,
238            datanodes: None,
239            datanode_wal_config: DatanodeWalConfig::default(),
240            metasrv_wal_config: MetasrvWalConfig::default(),
241            datanode_gc_config: GcConfig::default(),
242            metasrv_gc_config: GcSchedulerOptions::default(),
243            frontend_auto_create_table: true,
244            shared_home_dir: None,
245            meta_selector: None,
246            plugins: Plugins::default(),
247            local_file_access: LocalFileAccess::default(),
248            event_recorder_options: EventRecorderOptions::default(),
249            real_datanode_grpc_addr: false,
250        }
251    }
252
253    #[must_use]
254    pub fn with_store_config(mut self, store_config: ObjectStoreConfig) -> Self {
255        self.store_config = Some(store_config);
256        self
257    }
258
259    #[must_use]
260    pub fn with_store_providers(mut self, store_providers: Vec<StorageType>) -> Self {
261        self.store_providers = Some(store_providers);
262        self
263    }
264
265    #[must_use]
266    pub fn with_datanodes(mut self, datanodes: u32) -> Self {
267        self.datanodes = Some(datanodes);
268        self
269    }
270
271    #[must_use]
272    pub fn with_datanode_wal_config(mut self, datanode_wal_config: DatanodeWalConfig) -> Self {
273        self.datanode_wal_config = datanode_wal_config;
274        self
275    }
276
277    #[must_use]
278    pub fn with_metasrv_wal_config(mut self, metasrv_wal_config: MetasrvWalConfig) -> Self {
279        self.metasrv_wal_config = metasrv_wal_config;
280        self
281    }
282
283    #[must_use]
284    pub fn with_datanode_gc_config(mut self, datanode_gc_config: GcConfig) -> Self {
285        self.datanode_gc_config = datanode_gc_config;
286        self
287    }
288
289    pub fn with_metasrv_gc_config(mut self, metasrv_gc_config: GcSchedulerOptions) -> Self {
290        self.metasrv_gc_config = metasrv_gc_config;
291        self
292    }
293
294    #[must_use]
295    pub fn with_event_recorder_options(
296        mut self,
297        event_recorder_options: EventRecorderOptions,
298    ) -> Self {
299        self.event_recorder_options = event_recorder_options;
300        self
301    }
302
303    /// Sets whether the test frontend automatically creates tables on write.
304    #[must_use]
305    pub fn with_frontend_auto_create_table(mut self, auto_create_table: bool) -> Self {
306        self.frontend_auto_create_table = auto_create_table;
307        self
308    }
309
310    #[must_use]
311    pub fn with_shared_home_dir(mut self, shared_home_dir: Arc<TempDir>) -> Self {
312        self.shared_home_dir = Some(shared_home_dir);
313        self
314    }
315
316    #[must_use]
317    pub fn with_meta_selector(mut self, selector: SelectorRef) -> Self {
318        self.meta_selector = Some(selector);
319        self
320    }
321
322    /// Sets the [Plugins] used by the metasrv.
323    #[must_use]
324    pub fn with_plugins(mut self, plugins: Plugins) -> Self {
325        self.plugins = plugins;
326        self
327    }
328
329    /// Configure the frontend COPY sandbox for filesystem integration tests.
330    pub fn with_local_file_access(mut self, access: LocalFileAccess) -> Self {
331        self.local_file_access = access;
332        self
333    }
334
335    /// Serves every datanode on its own real localhost TCP port and registers that
336    /// address in metasrv, instead of the default in-process duplex placeholder address
337    /// that all datanodes share.
338    ///
339    /// Datanodes serve the region queries of the plans they execute by querying the
340    /// leader of every region of the plan (see `DatanodeRegionQueryHandler`), so a test
341    /// that exercises a datanode-to-datanode region query needs each datanode to be
342    /// reachable at the address recorded in the table route, i.e. the address the
343    /// datanode registers with its heartbeat.
344    #[must_use]
345    pub fn with_real_datanode_grpc_addr(mut self, real_datanode_grpc_addr: bool) -> Self {
346        self.real_datanode_grpc_addr = real_datanode_grpc_addr;
347        self
348    }
349
350    pub async fn build_with(
351        &self,
352        datanode_options: Vec<DatanodeOptions>,
353        start_frontend_servers: bool,
354        guards: Vec<TestGuard>,
355    ) -> GreptimeDbCluster {
356        let datanodes = datanode_options.len();
357
358        let channel_config = ChannelConfig::new().timeout(Some(Duration::from_secs(20)));
359        let datanode_clients = Arc::new(NodeClients::new(channel_config));
360
361        let opt = MetasrvOptions {
362            procedure: ProcedureConfig {
363                // Due to large network delay during cross data-center.
364                // We only make max_retry_times and retry_delay large than the default in tests.
365                max_retry_times: 5,
366                retry_delay: Duration::from_secs(1),
367                max_metadata_value_size: None,
368                max_running_procedures: 128,
369            },
370            wal: self.metasrv_wal_config.clone(),
371            grpc: GrpcOptions {
372                server_addr: "127.0.0.1:3002".to_string(),
373                ..Default::default()
374            },
375            gc: self.metasrv_gc_config.clone(),
376            event_recorder: self.event_recorder_options.clone(),
377            ..Default::default()
378        };
379
380        test_util::prepare_another_catalog_and_schema_with_kv_backend(self.kv_backend.clone())
381            .await;
382
383        let metasrv = meta_srv::mocks::mock_with_plugins(
384            opt,
385            self.kv_backend.clone(),
386            self.meta_selector.clone(),
387            Some(datanode_clients.clone()),
388            None,
389            self.plugins.clone(),
390        )
391        .await;
392
393        let datanode_instances = self
394            .build_datanodes_with_options(&metasrv, &datanode_options)
395            .await;
396
397        let datanode_rpc_stats = if self.real_datanode_grpc_addr {
398            datanode_options
399                .iter()
400                .map(|opts| (opts.node_id.unwrap(), Arc::new(DatanodeRpcStats::default())))
401                .collect::<HashMap<_, _>>()
402        } else {
403            HashMap::new()
404        };
405
406        build_datanode_clients(
407            datanode_clients.clone(),
408            &datanode_instances,
409            &datanode_options,
410            &datanode_rpc_stats,
411        )
412        .await;
413
414        self.wait_datanodes_alive(metasrv.metasrv.meta_peer_client(), datanodes)
415            .await;
416
417        let mut frontend = self
418            .build_frontend(
419                metasrv.clone(),
420                datanode_clients.clone(),
421                start_frontend_servers,
422            )
423            .await;
424
425        frontend.start().await.unwrap();
426
427        GreptimeDbCluster {
428            datanode_options,
429            guards,
430            datanode_instances,
431            datanode_rpc_stats,
432            kv_backend: self.kv_backend.clone(),
433            metasrv: metasrv.metasrv,
434            frontend: Arc::new(frontend),
435        }
436    }
437
438    pub async fn build(&self, start_frontend_servers: bool) -> GreptimeDbCluster {
439        let datanodes = self.datanodes.unwrap_or(4);
440        let (datanode_options, guards) = self.build_datanode_options_and_guards(datanodes).await;
441        self.build_with(datanode_options, start_frontend_servers, guards)
442            .await
443    }
444
445    async fn build_datanode_options_and_guards(
446        &self,
447        datanodes: u32,
448    ) -> (Vec<DatanodeOptions>, Vec<TestGuard>) {
449        let mut options = Vec::with_capacity(datanodes as usize);
450        let mut guards = Vec::with_capacity(datanodes as usize);
451
452        for i in 0..datanodes {
453            let datanode_id = i as u64 + 1;
454            let mut opts = if let Some(store_config) = &self.store_config {
455                let home_dir = if let Some(home_dir) = &self.shared_home_dir {
456                    home_dir.path().to_str().unwrap().to_string()
457                } else {
458                    let home_tmp_dir = create_temp_dir(&format!("gt_home_{}", self.cluster_name));
459                    let home_dir = home_tmp_dir.path().to_str().unwrap().to_string();
460                    guards.push(TestGuard {
461                        home_guard: FileDirGuard::new(home_tmp_dir),
462                        storage_guards: Vec::new(),
463                    });
464
465                    home_dir
466                };
467
468                create_datanode_opts(
469                    store_config.clone(),
470                    vec![],
471                    home_dir,
472                    self.datanode_wal_config.clone(),
473                    self.datanode_gc_config.clone(),
474                )
475            } else {
476                let (opts, guard) = create_tmp_dir_and_datanode_opts(
477                    StorageType::File,
478                    self.store_providers.clone().unwrap_or_default(),
479                    &format!("{}-dn-{}", self.cluster_name, datanode_id),
480                    self.datanode_wal_config.clone(),
481                    self.datanode_gc_config.clone(),
482                );
483                guards.push(guard);
484
485                opts
486            };
487            opts.node_id = Some(datanode_id);
488            if self.real_datanode_grpc_addr {
489                // Give every datanode its own address, so that the other datanodes can
490                // reach it (see `with_real_datanode_grpc_addr`).
491                let addr = self.choose_datanode_grpc_addr();
492                opts.grpc.bind_addr = addr.clone();
493                opts.grpc.server_addr = addr;
494            }
495            options.push(opts);
496        }
497        (options, guards)
498    }
499
500    /// Chooses a free localhost port for a datanode.
501    ///
502    /// Uses a port range disjoint from the one of the frontend servers
503    /// (see [`Self::build_frontend_options`]).
504    fn choose_datanode_grpc_addr(&self) -> String {
505        let localhost = "127.0.0.1";
506        let port = self.choose_random_unused_port(24000..=34000, 20, localhost);
507        format!("{}:{}", localhost, port)
508    }
509
510    async fn build_datanodes_with_options(
511        &self,
512        metasrv: &MockInfo,
513        options: &[DatanodeOptions],
514    ) -> HashMap<DatanodeId, Datanode> {
515        let mut instances = HashMap::with_capacity(options.len());
516
517        for opts in options {
518            let datanode = self.create_datanode(opts.clone(), metasrv.clone()).await;
519            let datanode_id = opts.node_id.unwrap();
520            instances.insert(datanode_id, datanode);
521        }
522
523        instances
524    }
525
526    async fn wait_datanodes_alive(
527        &self,
528        meta_peer_client: &MetaPeerClientRef,
529        expected_datanodes: usize,
530    ) {
531        for _ in 0..100 {
532            let alive_datanodes = discovery::utils::alive_datanode_infos(
533                &DefaultSystemTimer,
534                meta_peer_client.as_ref(),
535                Duration::from_secs(u64::MAX),
536                None,
537            )
538            .await
539            .unwrap()
540            .len();
541            if alive_datanodes == expected_datanodes {
542                return;
543            }
544            tokio::time::sleep(Duration::from_micros(100)).await
545        }
546        panic!("Some Datanodes are not alive in 10 seconds!")
547    }
548
549    async fn create_datanode(&self, opts: DatanodeOptions, metasrv: MockInfo) -> Datanode {
550        let mut meta_client = MetaClientBuilder::datanode_default_options(opts.node_id.unwrap())
551            .channel_manager(metasrv.channel_manager)
552            .build();
553        meta_client.start(&[&metasrv.server_addr]).await.unwrap();
554        let meta_client = Arc::new(meta_client);
555
556        let meta_backend = new_read_only_meta_kv_backend(meta_client.clone());
557
558        let layered_cache_registry =
559            Arc::new(build_datanode_layered_cache_registry(meta_backend.clone()));
560
561        let mut builder = DatanodeBuilder::new(opts.clone(), Plugins::default(), meta_backend);
562        builder
563            .with_cache_registry(layered_cache_registry.clone())
564            .with_meta_client(meta_client);
565        let mut datanode = builder.build().await.unwrap();
566
567        datanode.start_heartbeat().await.unwrap();
568
569        datanode
570    }
571
572    async fn build_frontend(
573        &self,
574        metasrv: MockInfo,
575        datanode_clients: Arc<NodeClients>,
576        start_frontend_servers: bool,
577    ) -> Frontend {
578        let mut meta_client = MetaClientBuilder::frontend_default_options()
579            .channel_manager(metasrv.channel_manager)
580            .enable_access_cluster_info()
581            .build();
582        meta_client.start(&[&metasrv.server_addr]).await.unwrap();
583        let meta_client = Arc::new(meta_client);
584
585        let readonly_meta_backend = new_read_only_meta_kv_backend(meta_client.clone());
586        let cached_meta_backend =
587            Arc::new(CachedKvBackendBuilder::new(readonly_meta_backend.clone()).build());
588
589        let layered_cache_builder = LayeredCacheRegistryBuilder::default().add_cache_registry(
590            CacheRegistryBuilder::default()
591                .add_cache(cached_meta_backend.clone())
592                .build(),
593        );
594        let fundamental_cache_registry =
595            build_fundamental_cache_registry(readonly_meta_backend.clone());
596        let cache_registry = Arc::new(
597            with_default_composite_cache_registry(
598                layered_cache_builder.add_cache_registry(fundamental_cache_registry),
599            )
600            .unwrap()
601            .build(),
602        );
603
604        let information_extension = Arc::new(DistributedInformationExtension::new(
605            meta_client.clone(),
606            datanode_clients.clone(),
607        ));
608        let catalog_manager = KvBackendCatalogManagerBuilder::new(
609            information_extension,
610            cached_meta_backend.clone(),
611            cache_registry.clone(),
612        )
613        .build();
614
615        let fe_opts = self.build_frontend_options();
616
617        let instance = FrontendBuilder::new(
618            fe_opts.clone(),
619            cached_meta_backend.clone(),
620            cache_registry.clone(),
621            catalog_manager,
622            datanode_clients,
623            meta_client.clone(),
624            Arc::new(ProcessManager::new(fe_opts.grpc.server_addr.clone(), None)),
625        )
626        .with_local_file_access(self.local_file_access.clone())
627        .with_local_cache_invalidator(cache_registry)
628        .try_build()
629        .await
630        .unwrap();
631
632        let heartbeat_task = create_heartbeat_task(&fe_opts, meta_client, &instance);
633
634        let instance = Arc::new(instance);
635
636        // Build the servers for the frontend.
637        let servers = if start_frontend_servers {
638            Services::new(fe_opts, instance.clone(), Plugins::default())
639                .build()
640                .unwrap()
641        } else {
642            ServerHandlers::default()
643        };
644
645        Frontend {
646            instance,
647            servers,
648            heartbeat_task: Some(heartbeat_task),
649        }
650    }
651
652    fn build_frontend_options(&self) -> FrontendOptions {
653        let mut fe_opts = FrontendOptions {
654            auto_create_table: self.frontend_auto_create_table,
655            event_recorder: self.event_recorder_options.clone(),
656            ..Default::default()
657        };
658
659        // Choose a random unused port between [14000, 24000] for local test to avoid conflicts.
660        let port_range = 14000..=24000;
661        let max_attempts = 10;
662        let localhost = "127.0.0.1";
663        let construct_addr = |port: u16| format!("{}:{}", localhost, port);
664
665        fe_opts.http.addr = construct_addr(self.choose_random_unused_port(
666            port_range.clone(),
667            max_attempts,
668            localhost,
669        ));
670
671        let grpc_port = self.choose_random_unused_port(port_range.clone(), max_attempts, localhost);
672        fe_opts.grpc.bind_addr = construct_addr(grpc_port);
673        fe_opts.grpc.server_addr = construct_addr(grpc_port);
674
675        fe_opts.mysql.addr = construct_addr(self.choose_random_unused_port(
676            port_range.clone(),
677            max_attempts,
678            localhost,
679        ));
680        fe_opts.postgres.addr =
681            construct_addr(self.choose_random_unused_port(port_range, max_attempts, localhost));
682
683        fe_opts
684    }
685
686    // Choose a random unused port between [start, end].
687    fn choose_random_unused_port(
688        &self,
689        port_range: RangeInclusive<u16>,
690        max_attempts: u16,
691        addr: &str,
692    ) -> u16 {
693        let mut rng = rand::rng();
694
695        let mut attempts = 0;
696        while attempts < max_attempts {
697            let port = rng.random_range(port_range.clone());
698            if TcpListener::bind(format!("{}:{}", addr, port)).is_ok() {
699                return port;
700            }
701            attempts += 1;
702        }
703
704        panic!("No unused port found");
705    }
706}
707
708async fn build_datanode_clients(
709    clients: Arc<NodeClients>,
710    instances: &HashMap<DatanodeId, Datanode>,
711    options: &[DatanodeOptions],
712    rpc_stats: &HashMap<DatanodeId, Arc<DatanodeRpcStats>>,
713) {
714    for opts in options {
715        let datanode_id = opts.node_id.unwrap();
716        let instance = &instances[&datanode_id];
717        let (addr, client) = create_datanode_client(
718            instance,
719            &opts.grpc.server_addr,
720            rpc_stats.get(&datanode_id).cloned(),
721        )
722        .await;
723        clients
724            .insert_client(Peer::new(datanode_id, addr), client)
725            .await;
726    }
727}
728
729/// Runs a gRPC server that serves the region server of `datanode` over `incoming` until the
730/// cluster is dropped.
731fn serve_datanode_region_server<I, IO>(
732    datanode: &Datanode,
733    incoming: I,
734    rpc_stats: Option<Arc<DatanodeRpcStats>>,
735) where
736    I: futures::Stream<Item = Result<IO, std::io::Error>> + Send + 'static,
737    IO: AsyncRead + AsyncWrite + Connected + Unpin + Send + 'static,
738{
739    let runtime = RuntimeBuilder::default()
740        .worker_threads(2)
741        .thread_name("grpc-handlers")
742        .build()
743        .unwrap();
744
745    let flight_handler = FlightCraftWrapper(datanode.region_server());
746    let region_server_handler =
747        RegionServerRequestHandler::new(Arc::new(datanode.region_server()), runtime);
748
749    tokio::spawn(async move {
750        Server::builder()
751            .layer(CountRequestsLayer { rpc_stats })
752            .add_service(
753                FlightServiceServer::new(flight_handler)
754                    .accept_compressed(CompressionEncoding::Gzip)
755                    .accept_compressed(CompressionEncoding::Zstd)
756                    .send_compressed(CompressionEncoding::Gzip)
757                    .send_compressed(CompressionEncoding::Zstd),
758            )
759            .add_service(
760                RegionServer::new(region_server_handler)
761                    .accept_compressed(CompressionEncoding::Gzip)
762                    .accept_compressed(CompressionEncoding::Zstd)
763                    .send_compressed(CompressionEncoding::Gzip)
764                    .send_compressed(CompressionEncoding::Zstd),
765            )
766            .add_service(HealthCheckServer::new(HealthCheckHandler))
767            .serve_with_incoming(incoming)
768            .await
769    });
770}
771
772/// Serves the region server of `datanode` and returns the address and the client of the address.
773///
774/// The returned client is always an in-process client: the frontend and the datanodes of the test
775/// process reach the region server through a duplex stream, no matter which address they use. This
776/// keeps the in-process traffic out of `rpc_stats`.
777///
778/// [`test_util::PEER_PLACEHOLDER_ADDR`] is not a real address, so it cannot be served over the
779/// network at all. Any other address is a real one: the region server is additionally served on
780/// its local TCP port (`rpc_stats` counts the requests of the remote nodes that reach it, e.g. the
781/// region query that a datanode sends for the regions of another datanode).
782async fn create_datanode_client(
783    datanode: &Datanode,
784    addr: &str,
785    rpc_stats: Option<Arc<DatanodeRpcStats>>,
786) -> (String, Client) {
787    let (client, server) = tokio::io::duplex(1024);
788    serve_datanode_region_server(
789        datanode,
790        futures::stream::iter(vec![Ok::<_, std::io::Error>(server)]),
791        None,
792    );
793
794    if addr != test_util::PEER_PLACEHOLDER_ADDR {
795        let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
796        let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener);
797        serve_datanode_region_server(datanode, incoming, rpc_stats);
798    }
799
800    let mut client = Some(client);
801    let channel_manager = ChannelManager::new();
802    channel_manager
803        .reset_with_connector(
804            addr,
805            service_fn(move |_| {
806                let client = client.take();
807
808                async move {
809                    client
810                        .map(TokioIo::new)
811                        .ok_or_else(|| std::io::Error::other("Client already taken"))
812                }
813            }),
814        )
815        .unwrap();
816
817    (
818        addr.to_string(),
819        // The mock connector pool lives on this single manager, so both lanes
820        // intentionally share it (same semantics as the legacy constructor).
821        Client::with_query_and_control_managers(channel_manager.clone(), channel_manager, [addr]),
822    )
823}
824
825/// Counts Flight `DoGet` requests received by the wrapped server.
826#[derive(Clone)]
827struct CountRequests<S> {
828    inner: S,
829    rpc_stats: Option<Arc<DatanodeRpcStats>>,
830}
831
832impl<S, ReqBody> Service<http::Request<ReqBody>> for CountRequests<S>
833where
834    S: Service<http::Request<ReqBody>>,
835{
836    type Response = S::Response;
837    type Error = S::Error;
838    type Future = S::Future;
839
840    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
841        self.inner.poll_ready(cx)
842    }
843
844    fn call(&mut self, request: http::Request<ReqBody>) -> Self::Future {
845        if request.uri().path() == "/arrow.flight.protocol.FlightService/DoGet"
846            && let Some(rpc_stats) = &self.rpc_stats
847        {
848            rpc_stats.do_get_requests.fetch_add(1, Ordering::Relaxed);
849        }
850
851        self.inner.call(request)
852    }
853}
854
855#[derive(Clone)]
856struct CountRequestsLayer {
857    rpc_stats: Option<Arc<DatanodeRpcStats>>,
858}
859
860impl<S> Layer<S> for CountRequestsLayer {
861    type Service = CountRequests<S>;
862
863    fn layer(&self, inner: S) -> Self::Service {
864        CountRequests {
865            inner,
866            rpc_stats: self.rpc_stats.clone(),
867        }
868    }
869}
870
871// Mock connectors are registered on a shared channel manager.
872#[cfg(test)]
873#[allow(deprecated)]
874mod tests {
875    use std::sync::atomic::{AtomicBool, Ordering};
876
877    use api::v1::flow::FlowRequest;
878    use api::v1::region::{
879        ListMetadataRequest, RegionRequest, RegionRequestHeader, region_request,
880    };
881    use catalog::memory::new_memory_catalog_manager;
882    use client::Client;
883    use common_error::ext::ErrorExt;
884    use common_error::status_code::StatusCode;
885    use common_meta::ddl_manager::{DdlManager, DdlManagerConfigurator, DdlManagerConfiguratorRef};
886    use common_meta::key::TableMetadataManager;
887    use common_meta::key::flow::FlowMetadataManager;
888    use common_meta::kv_backend::memory::MemoryKvBackend;
889    use common_meta::node_manager::{DatanodeManager, FlownodeManager};
890    use common_meta::peer::Peer;
891    use flow::{FlownodeBuilder, FlownodeOptions, FlownodeServiceBuilder, FrontendClient};
892    use meta_srv::metasrv::builder::DdlManagerConfigureContext;
893
894    use super::*;
895
896    const HEALTH_REFRESH_INTERVAL: Duration = Duration::from_millis(10);
897    const HEALTH_REFRESH_TIMEOUT: Duration = Duration::from_secs(5);
898
899    async fn wait_for_active_route(client: &Client, active_addr: &str) {
900        client.find_channel().unwrap();
901        tokio::time::timeout(HEALTH_REFRESH_TIMEOUT, async {
902            let mut interval = tokio::time::interval(HEALTH_REFRESH_INTERVAL);
903            loop {
904                interval.tick().await;
905                let (active, inactive) = client.peer_addresses_by_state();
906                if active.len() == 1 && active[0] == active_addr && inactive.len() == 1 {
907                    return;
908                }
909            }
910        })
911        .await
912        .expect("background health routing did not select the active peer");
913    }
914
915    async fn create_region_requester_health_client(datanode: &Datanode) -> (Peer, Peer, Client) {
916        const ACTIVE_ADDR: &str = "127.0.0.1:3001";
917        const INACTIVE_ADDR: &str = "127.0.0.1:3002";
918
919        let (active_client, server) = tokio::io::duplex(1024);
920        let runtime = RuntimeBuilder::default()
921            .worker_threads(2)
922            .thread_name("grpc-handlers")
923            .build()
924            .unwrap();
925        let region_server_handler =
926            RegionServerRequestHandler::new(Arc::new(datanode.region_server()), runtime);
927
928        tokio::spawn(async move {
929            Server::builder()
930                .add_service(RegionServer::new(region_server_handler))
931                .add_service(HealthCheckServer::new(HealthCheckHandler))
932                .serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(server)]))
933                .await
934        });
935
936        let channel_manager = ChannelManager::new();
937        let mut active_client = Some(active_client);
938        channel_manager
939            .reset_with_connector(
940                ACTIVE_ADDR,
941                service_fn(move |_| {
942                    let client = active_client.take();
943
944                    async move {
945                        client
946                            .map(TokioIo::new)
947                            .ok_or_else(|| std::io::Error::other("Active client already taken"))
948                    }
949                }),
950            )
951            .unwrap();
952        channel_manager
953            .reset_with_connector(
954                INACTIVE_ADDR,
955                service_fn(|_| async {
956                    Err::<TokioIo<tokio::io::DuplexStream>, _>(std::io::Error::other(
957                        "Inactive synthetic peer",
958                    ))
959                }),
960            )
961            .unwrap();
962
963        let client = Client::with_manager_and_urls_and_options(
964            channel_manager,
965            [INACTIVE_ADDR, ACTIVE_ADDR],
966            client::ClientOptions {
967                health_check_interval: HEALTH_REFRESH_INTERVAL,
968                ..Default::default()
969            },
970        );
971        (
972            Peer::new(1, ACTIVE_ADDR),
973            Peer::new(2, INACTIVE_ADDR),
974            client,
975        )
976    }
977
978    async fn create_flow_requester_health_client(
979        flownode: &flow::FlownodeInstance,
980    ) -> (Peer, Peer, Client) {
981        const ACTIVE_ADDR: &str = "127.0.0.1:3003";
982        const INACTIVE_ADDR: &str = "127.0.0.1:3004";
983
984        let (active_client, server) = tokio::io::duplex(1024);
985        let flow_service = flownode.flownode_server().create_flow_service();
986
987        tokio::spawn(async move {
988            Server::builder()
989                .add_service(flow_service)
990                .add_service(HealthCheckServer::new(HealthCheckHandler))
991                .serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(server)]))
992                .await
993        });
994
995        let channel_manager = ChannelManager::new();
996        let mut active_client = Some(active_client);
997        channel_manager
998            .reset_with_connector(
999                ACTIVE_ADDR,
1000                service_fn(move |_| {
1001                    let client = active_client.take();
1002
1003                    async move {
1004                        client
1005                            .map(TokioIo::new)
1006                            .ok_or_else(|| std::io::Error::other("Active client already taken"))
1007                    }
1008                }),
1009            )
1010            .unwrap();
1011        channel_manager
1012            .reset_with_connector(
1013                INACTIVE_ADDR,
1014                service_fn(|_| async {
1015                    Err::<TokioIo<tokio::io::DuplexStream>, _>(std::io::Error::other(
1016                        "Inactive synthetic peer",
1017                    ))
1018                }),
1019            )
1020            .unwrap();
1021
1022        let client = Client::with_manager_and_urls_and_options(
1023            channel_manager,
1024            [INACTIVE_ADDR, ACTIVE_ADDR],
1025            client::ClientOptions {
1026                health_check_interval: HEALTH_REFRESH_INTERVAL,
1027                ..Default::default()
1028            },
1029        );
1030        (
1031            Peer::new(1, ACTIVE_ADDR),
1032            Peer::new(2, INACTIVE_ADDR),
1033            client,
1034        )
1035    }
1036
1037    async fn start_flownode(addr: String) -> flow::FlownodeInstance {
1038        let kv_backend = Arc::new(MemoryKvBackend::new());
1039        let table_meta = Arc::new(TableMetadataManager::new(kv_backend.clone()));
1040        table_meta.init().await.unwrap();
1041        let flow_meta = Arc::new(FlowMetadataManager::new(kv_backend));
1042        let catalog_manager = new_memory_catalog_manager().unwrap();
1043        let (frontend_client, _handler) =
1044            FrontendClient::from_empty_grpc_handler(Default::default());
1045        let mut opts = FlownodeOptions::default();
1046        opts.grpc.bind_addr = addr.clone();
1047        opts.grpc.server_addr = addr;
1048
1049        let mut flownode = FlownodeBuilder::new(
1050            opts.clone(),
1051            Plugins::default(),
1052            table_meta,
1053            catalog_manager,
1054            flow_meta,
1055            Arc::new(frontend_client),
1056        )
1057        .build()
1058        .await
1059        .unwrap();
1060        let services = FlownodeServiceBuilder::new(&opts)
1061            .with_default_grpc_server(flownode.flownode_server())
1062            .build()
1063            .unwrap();
1064        flownode.setup_services(services);
1065        flownode.start().await.unwrap();
1066        flownode
1067    }
1068
1069    struct TestDdlManagerConfigurator(Arc<AtomicBool>);
1070
1071    #[async_trait::async_trait]
1072    impl DdlManagerConfigurator<DdlManagerConfigureContext> for TestDdlManagerConfigurator {
1073        async fn configure(
1074            &self,
1075            ddl_manager: DdlManager,
1076            _ctx: DdlManagerConfigureContext,
1077        ) -> std::result::Result<DdlManager, common_error::ext::BoxedError> {
1078            self.0.store(true, Ordering::SeqCst);
1079            Ok(ddl_manager)
1080        }
1081    }
1082
1083    #[tokio::test(flavor = "multi_thread")]
1084    async fn test_build_cluster_with_plugins() {
1085        let configured = Arc::new(AtomicBool::new(false));
1086        let plugins = Plugins::default();
1087        plugins.insert::<DdlManagerConfiguratorRef<DdlManagerConfigureContext>>(Arc::new(
1088            TestDdlManagerConfigurator(configured.clone()),
1089        ));
1090
1091        let _cluster = GreptimeDbClusterBuilder::new("test_cluster_with_plugins")
1092            .await
1093            .with_datanodes(1)
1094            .with_plugins(plugins)
1095            .build(false)
1096            .await;
1097
1098        assert!(configured.load(Ordering::SeqCst));
1099    }
1100
1101    #[tokio::test(flavor = "multi_thread")]
1102    async fn test_region_requester_health_check_and_list_metadata() {
1103        // Arrange: configure active and inactive synthetic peers over the duplex harness.
1104        let cluster = GreptimeDbClusterBuilder::new("region_requester_health_check")
1105            .await
1106            .with_datanodes(1)
1107            .build(false)
1108            .await;
1109        let (active_peer, inactive_peer, client) =
1110            create_region_requester_health_client(cluster.datanode_instances.get(&1).unwrap())
1111                .await;
1112        let clients = NodeClients::default();
1113        clients
1114            .insert_client(active_peer.clone(), client.clone())
1115            .await;
1116        clients
1117            .insert_client(inactive_peer.clone(), client.clone())
1118            .await;
1119
1120        // Act: wait for background health routing, then send a real Region RPC.
1121        wait_for_active_route(&client, &active_peer.addr).await;
1122        let requester = clients.datanode(&active_peer).await;
1123        let response = requester
1124            .handle(RegionRequest {
1125                header: Some(RegionRequestHeader::default()),
1126                body: Some(region_request::Body::ListMetadata(ListMetadataRequest {
1127                    region_ids: vec![],
1128                })),
1129            })
1130            .await
1131            .unwrap();
1132
1133        // Assert: the inactive peer was rejected and the RegionRequester reached the active service.
1134        assert_eq!(
1135            (vec![active_peer.addr], vec![inactive_peer.addr]),
1136            client.peer_addresses_by_state()
1137        );
1138        assert_eq!(b"[]", response.metadata.as_slice());
1139    }
1140
1141    #[tokio::test(flavor = "multi_thread")]
1142    async fn test_flow_requester_health_check_and_handle_request() {
1143        // Arrange: start a flownode and expose its production flow service through a duplex harness.
1144        let mut flownode = start_flownode("127.0.0.1:0".to_string()).await;
1145        let (active_peer, inactive_peer, client) =
1146            create_flow_requester_health_client(&flownode).await;
1147        let clients = NodeClients::default();
1148        clients
1149            .insert_client(active_peer.clone(), client.clone())
1150            .await;
1151        clients
1152            .insert_client(inactive_peer.clone(), client.clone())
1153            .await;
1154
1155        // Act: wait for background health routing, then send a real Flow RPC.
1156        wait_for_active_route(&client, &active_peer.addr).await;
1157        let response = clients
1158            .flownode(&active_peer)
1159            .await
1160            .handle(FlowRequest::default())
1161            .await;
1162
1163        // Assert: the inactive peer was rejected and the production Flow service handled the RPC.
1164        assert_eq!(
1165            (vec![active_peer.addr.clone()], vec![inactive_peer.addr]),
1166            client.peer_addresses_by_state()
1167        );
1168        assert_eq!(
1169            StatusCode::InvalidArguments,
1170            response.unwrap_err().status_code()
1171        );
1172
1173        for value in ["", "unknown", "follower"] {
1174            let mut query_context =
1175                api::v1::QueryContext::from(session::context::QueryContext::arc().as_ref());
1176            query_context.extensions.insert(
1177                session::hints::READ_PREFERENCE_EXTENSION_KEY.to_string(),
1178                value.to_string(),
1179            );
1180            let error = clients
1181                .flownode(&active_peer)
1182                .await
1183                .handle(FlowRequest {
1184                    header: Some(api::v1::flow::FlowRequestHeader {
1185                        tracing_context: Default::default(),
1186                        query_context: Some(query_context),
1187                    }),
1188                    body: None,
1189                })
1190                .await
1191                .unwrap_err();
1192            assert_eq!(StatusCode::InvalidArguments, error.status_code());
1193            let message = error.output_msg();
1194            assert!(message.contains("read_preference"), "{message}");
1195            if !value.is_empty() {
1196                assert!(message.contains(value), "{message}");
1197            }
1198        }
1199
1200        flownode.shutdown().await.unwrap();
1201    }
1202}