Skip to main content

meta_srv/procedure/region_migration/
manager.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::hash_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::fmt::Display;
18use std::sync::{Arc, RwLock};
19use std::time::Duration;
20
21use common_event_recorder::PersistentEventContext;
22use common_meta::key::table_info::TableInfoValue;
23use common_meta::key::table_route::TableRouteValue;
24use common_meta::peer::Peer;
25use common_meta::rpc::router::RegionRoute;
26use common_procedure::{
27    ProcedureContext, ProcedureId, ProcedureManagerRef, ProcedureWithId, watcher,
28};
29use common_telemetry::{error, info, warn};
30use serde::{Deserialize, Serialize};
31use snafu::{OptionExt, ResultExt, ensure};
32use store_api::storage::RegionId;
33use table::table_name::TableName;
34
35use crate::error::{self, Result};
36use crate::metrics::{METRIC_META_REGION_MIGRATION_DATANODES, METRIC_META_REGION_MIGRATION_FAIL};
37use crate::procedure::region_migration::utils::{
38    RegionMigrationAnalysis, RegionMigrationTaskBatch, analyze_region_migration_task,
39};
40use crate::procedure::region_migration::{
41    DefaultContextFactory, PersistentContext, RegionMigrationProcedure,
42};
43
44pub type RegionMigrationManagerRef = Arc<RegionMigrationManager>;
45
46/// Manager of region migration procedure.
47pub struct RegionMigrationManager {
48    procedure_manager: ProcedureManagerRef,
49    context_factory: DefaultContextFactory,
50    tracker: RegionMigrationProcedureTracker,
51}
52
53#[derive(Default, Clone)]
54pub struct RegionMigrationProcedureTracker {
55    running_procedures: Arc<RwLock<HashMap<RegionId, RegionMigrationProcedureTask>>>,
56}
57
58impl RegionMigrationProcedureTracker {
59    /// Returns the [RegionMigrationProcedureGuard] if current region isn't migrating.
60    pub(crate) fn insert_running_procedure(
61        &self,
62        task: &RegionMigrationProcedureTask,
63    ) -> Option<RegionMigrationProcedureGuard> {
64        let mut procedures = self.running_procedures.write().unwrap();
65        match procedures.entry(task.region_id) {
66            Entry::Occupied(_) => None,
67            Entry::Vacant(v) => {
68                v.insert(task.clone());
69                Some(RegionMigrationProcedureGuard {
70                    region_id: task.region_id,
71                    running_procedures: self.running_procedures.clone(),
72                })
73            }
74        }
75    }
76
77    /// Returns true if it contains the specific region(`region_id`).
78    pub(crate) fn contains(&self, region_id: RegionId) -> bool {
79        self.running_procedures
80            .read()
81            .unwrap()
82            .contains_key(&region_id)
83    }
84}
85
86/// The guard of running [RegionMigrationProcedureTask].
87pub(crate) struct RegionMigrationProcedureGuard {
88    region_id: RegionId,
89    running_procedures: Arc<RwLock<HashMap<RegionId, RegionMigrationProcedureTask>>>,
90}
91
92impl Drop for RegionMigrationProcedureGuard {
93    fn drop(&mut self) {
94        let exists = self
95            .running_procedures
96            .read()
97            .unwrap()
98            .contains_key(&self.region_id);
99        if exists {
100            self.running_procedures
101                .write()
102                .unwrap()
103                .remove(&self.region_id);
104        }
105    }
106}
107
108/// A task of region migration procedure.
109#[derive(Debug, Clone)]
110pub struct RegionMigrationProcedureTask {
111    pub(crate) region_id: RegionId,
112    pub(crate) from_peer: Peer,
113    pub(crate) to_peer: Peer,
114    pub(crate) timeout: Duration,
115    pub(crate) trigger_reason: RegionMigrationTriggerReason,
116}
117
118/// The reason why the region migration procedure is triggered.
119#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
120#[strum(serialize_all = "PascalCase")]
121pub enum RegionMigrationTriggerReason {
122    #[default]
123    /// The region migration procedure is triggered by unknown reason.
124    Unknown,
125    /// The region migration procedure is triggered by administrator.
126    Manual,
127    /// The region migration procedure is triggered by auto rebalance.
128    AutoRebalance,
129    /// The region migration procedure is triggered by failover.
130    Failover,
131}
132
133impl RegionMigrationProcedureTask {
134    pub fn new(
135        region_id: RegionId,
136        from_peer: Peer,
137        to_peer: Peer,
138        timeout: Duration,
139        trigger_reason: RegionMigrationTriggerReason,
140    ) -> Self {
141        Self {
142            region_id,
143            from_peer,
144            to_peer,
145            timeout,
146            trigger_reason,
147        }
148    }
149}
150
151impl Display for RegionMigrationProcedureTask {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        write!(
154            f,
155            "region: {}, from_peer: {}, to_peer: {}, trigger_reason: {}",
156            self.region_id, self.from_peer, self.to_peer, self.trigger_reason
157        )
158    }
159}
160
161/// The result of submitting a region migration task.
162#[derive(Debug, Default, PartialEq, Eq)]
163pub struct SubmitRegionMigrationTaskResult {
164    /// Regions already migrated to the `to_peer`.
165    pub migrated: Vec<RegionId>,
166    /// Regions where the leader peer has changed.
167    pub leader_changed: Vec<RegionId>,
168    /// Regions where `to_peer` is already a follower (conflict).
169    pub peer_conflict: Vec<RegionId>,
170    /// Regions whose table is not found.
171    pub table_not_found: Vec<RegionId>,
172    /// Regions whose table exists but region route is not found (e.g., removed after repartition).
173    pub region_not_found: Vec<RegionId>,
174    /// Regions still pending migration.
175    pub migrating: Vec<RegionId>,
176    /// Regions that have been submitted for migration.
177    pub submitted: Vec<RegionId>,
178    /// The procedure id of the region migration procedure.
179    pub procedure_id: Option<ProcedureId>,
180}
181
182impl RegionMigrationManager {
183    /// Returns new [`RegionMigrationManager`]
184    pub(crate) fn new(
185        procedure_manager: ProcedureManagerRef,
186        context_factory: DefaultContextFactory,
187    ) -> Self {
188        Self {
189            procedure_manager,
190            context_factory,
191            tracker: RegionMigrationProcedureTracker::default(),
192        }
193    }
194
195    /// Returns the [`RegionMigrationProcedureTracker`].
196    pub fn tracker(&self) -> &RegionMigrationProcedureTracker {
197        &self.tracker
198    }
199
200    /// Registers the loader of [RegionMigrationProcedure] to the `ProcedureManager`.
201    pub(crate) fn try_start(&self) -> Result<()> {
202        let context_factory = self.context_factory.clone();
203        let tracker = self.tracker.clone();
204        self.procedure_manager
205            .register_loader(
206                RegionMigrationProcedure::TYPE_NAME,
207                Box::new(move |json| {
208                    let context_factory = context_factory.clone();
209                    let tracker = tracker.clone();
210                    RegionMigrationProcedure::from_json(json, context_factory, tracker)
211                        .map(|p| Box::new(p) as _)
212                }),
213            )
214            .context(error::RegisterProcedureLoaderSnafu {
215                type_name: RegionMigrationProcedure::TYPE_NAME,
216            })
217    }
218
219    fn insert_running_procedure(
220        &self,
221        task: &RegionMigrationProcedureTask,
222    ) -> Option<RegionMigrationProcedureGuard> {
223        self.tracker.insert_running_procedure(task)
224    }
225
226    fn verify_task(&self, task: &RegionMigrationProcedureTask) -> Result<()> {
227        if task.to_peer.id == task.from_peer.id {
228            return error::InvalidArgumentsSnafu {
229                err_msg: "The `from_peer_id` can't equal `to_peer_id`",
230            }
231            .fail();
232        }
233
234        Ok(())
235    }
236
237    async fn retrieve_table_route(&self, region_id: RegionId) -> Result<TableRouteValue> {
238        let table_route = self
239            .context_factory
240            .table_metadata_manager
241            .table_route_manager()
242            .table_route_storage()
243            .get(region_id.table_id())
244            .await
245            .context(error::TableMetadataManagerSnafu)?
246            .context(error::TableRouteNotFoundSnafu {
247                table_id: region_id.table_id(),
248            })?;
249
250        Ok(table_route)
251    }
252
253    async fn retrieve_table_info(&self, region_id: RegionId) -> Result<TableInfoValue> {
254        let table_route = self
255            .context_factory
256            .table_metadata_manager
257            .table_info_manager()
258            .get(region_id.table_id())
259            .await
260            .context(error::TableMetadataManagerSnafu)?
261            .context(error::TableInfoNotFoundSnafu {
262                table_id: region_id.table_id(),
263            })?
264            .into_inner();
265
266        Ok(table_route)
267    }
268
269    /// Verifies the type of region migration table route.
270    fn verify_table_route(
271        &self,
272        table_route: &TableRouteValue,
273        task: &RegionMigrationProcedureTask,
274    ) -> Result<()> {
275        if !table_route.is_physical() {
276            return error::UnexpectedSnafu {
277                violated: format!(
278                    "Trying to execute region migration on the logical table, task {task}"
279                ),
280            }
281            .fail();
282        }
283
284        Ok(())
285    }
286
287    /// Returns true if the region has been migrated.
288    fn has_migrated(
289        &self,
290        region_route: &RegionRoute,
291        task: &RegionMigrationProcedureTask,
292    ) -> Result<bool> {
293        if region_route.is_leader_downgrading() {
294            return Ok(false);
295        }
296
297        let leader_peer = region_route
298            .leader_peer
299            .as_ref()
300            .context(error::UnexpectedSnafu {
301                violated: "Region route leader peer is not found",
302            })?;
303
304        Ok(leader_peer.id == task.to_peer.id)
305    }
306
307    /// Throws an error if `leader_peer` is not the `from_peer`.
308    ///
309    /// If `from_peer` is unknown, use the leader peer as the `from_peer`.
310    fn verify_region_leader_peer(
311        &self,
312        region_route: &RegionRoute,
313        task: &mut RegionMigrationProcedureTask,
314    ) -> Result<()> {
315        let leader_peer = region_route
316            .leader_peer
317            .as_ref()
318            .context(error::UnexpectedSnafu {
319                violated: "Region route leader peer is not found",
320            })?;
321
322        ensure!(
323            leader_peer.id == task.from_peer.id,
324            error::LeaderPeerChangedSnafu {
325                msg: format!(
326                    "Region's leader peer({}) is not the `from_peer`({}), region: {}",
327                    leader_peer.id, task.from_peer.id, task.region_id
328                ),
329            }
330        );
331
332        if task.from_peer.addr.is_empty() {
333            warn!(
334                "The `from_peer` is unknown, use the leader peer({}) as the `from_peer`, region: {}",
335                leader_peer, task.region_id
336            );
337            // The peer id is the same as the leader peer id.
338            task.from_peer = leader_peer.clone();
339        }
340
341        Ok(())
342    }
343
344    /// Throws an error if `to_peer` is already has a region follower.
345    fn verify_region_follower_peers(
346        &self,
347        region_route: &RegionRoute,
348        task: &RegionMigrationProcedureTask,
349    ) -> Result<()> {
350        ensure!(
351            !region_route.follower_peers.contains(&task.to_peer),
352            error::InvalidArgumentsSnafu {
353                err_msg: format!(
354                    "The `to_peer`({}) is already has a region follower, region: {}",
355                    task.to_peer.id, task.region_id
356                ),
357            },
358        );
359
360        Ok(())
361    }
362
363    /// Extracts regions from the migration task that are already running migration procedures.
364    ///
365    /// Returns a tuple containing those region ids that are already running and the newly created procedure guards.
366    /// The regions that are already running will be removed from the [`RegionMigrationTask`].
367    fn extract_running_regions(
368        &self,
369        task: &mut RegionMigrationTaskBatch,
370    ) -> (Vec<RegionId>, Vec<RegionMigrationProcedureGuard>) {
371        let mut migrating_region_ids = Vec::new();
372        let mut procedure_guards = Vec::with_capacity(task.region_ids.len());
373
374        for region_id in &task.region_ids {
375            let Some(guard) = self.insert_running_procedure(&RegionMigrationProcedureTask::new(
376                *region_id,
377                task.from_peer.clone(),
378                task.to_peer.clone(),
379                task.timeout,
380                task.trigger_reason,
381            )) else {
382                migrating_region_ids.push(*region_id);
383                continue;
384            };
385            procedure_guards.push(guard);
386        }
387
388        let migrating_set = migrating_region_ids.iter().cloned().collect::<HashSet<_>>();
389        task.region_ids.retain(|id| !migrating_set.contains(id));
390
391        (migrating_region_ids, procedure_guards)
392    }
393
394    pub async fn submit_region_migration_task(
395        &self,
396        mut task: RegionMigrationTaskBatch,
397    ) -> Result<SubmitRegionMigrationTaskResult> {
398        let (migrating_region_ids, procedure_guards) = self.extract_running_regions(&mut task);
399        let RegionMigrationAnalysis {
400            migrated,
401            leader_changed,
402            peer_conflict,
403            mut table_not_found,
404            region_not_found,
405            pending,
406        } = analyze_region_migration_task(&task, &self.context_factory.table_metadata_manager)
407            .await?;
408        if pending.is_empty() {
409            return Ok(SubmitRegionMigrationTaskResult {
410                migrated,
411                leader_changed,
412                peer_conflict,
413                table_not_found,
414                region_not_found,
415                migrating: migrating_region_ids,
416                submitted: vec![],
417                procedure_id: None,
418            });
419        }
420
421        // Updates the region ids to the pending region ids.
422        task.region_ids = pending;
423        let table_regions = task.table_regions();
424        let table_ids = table_regions.keys().cloned().collect::<Vec<_>>();
425        let table_info_values = self
426            .context_factory
427            .table_metadata_manager
428            .table_info_manager()
429            .batch_get(&table_ids)
430            .await
431            .context(error::TableMetadataManagerSnafu)?;
432        let mut catalog_and_schema = Vec::with_capacity(table_info_values.len());
433        for (table_id, regions) in table_regions {
434            match table_info_values.get(&table_id) {
435                Some(table_info) => {
436                    let TableName {
437                        catalog_name,
438                        schema_name,
439                        ..
440                    } = table_info.table_name();
441                    catalog_and_schema.push((catalog_name, schema_name));
442                }
443                None => {
444                    task.region_ids.retain(|id| id.table_id() != table_id);
445                    table_not_found.extend(regions);
446                }
447            }
448        }
449        if task.region_ids.is_empty() {
450            return Ok(SubmitRegionMigrationTaskResult {
451                migrated,
452                leader_changed,
453                peer_conflict,
454                table_not_found,
455                region_not_found,
456                migrating: migrating_region_ids,
457                submitted: vec![],
458                procedure_id: None,
459            });
460        }
461
462        let submitting_region_ids = task.region_ids.clone();
463        // TODO(weny): Pass the actor when region migration task submission propagates it.
464        let procedure_context = ProcedureContext {
465            actor: None,
466            event_context: Some(PersistentEventContext::new(
467                task.trigger_reason.to_trigger_reason(),
468            )),
469        };
470        let procedure_id = self
471            .submit_procedure_inner(
472                procedure_context,
473                task,
474                procedure_guards,
475                catalog_and_schema,
476            )
477            .await?;
478        Ok(SubmitRegionMigrationTaskResult {
479            migrated,
480            leader_changed,
481            peer_conflict,
482            table_not_found,
483            region_not_found,
484            migrating: migrating_region_ids,
485            submitted: submitting_region_ids,
486            procedure_id: Some(procedure_id),
487        })
488    }
489
490    async fn submit_procedure_inner(
491        &self,
492        procedure_context: ProcedureContext,
493        task: RegionMigrationTaskBatch,
494        procedure_guards: Vec<RegionMigrationProcedureGuard>,
495        catalog_and_schema: Vec<(String, String)>,
496    ) -> Result<ProcedureId> {
497        let procedure = RegionMigrationProcedure::new(
498            PersistentContext::new(
499                catalog_and_schema,
500                task.from_peer.clone(),
501                task.to_peer.clone(),
502                task.region_ids.clone(),
503                task.timeout,
504                task.trigger_reason,
505            ),
506            self.context_factory.clone(),
507            procedure_guards,
508        );
509        let procedure_with_id =
510            ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
511        let procedure_id = procedure_with_id.id;
512        info!("Starting region migration procedure {procedure_id} for {task}");
513        let mut watcher = self
514            .procedure_manager
515            .submit(procedure_with_id)
516            .await
517            .context(error::SubmitProcedureSnafu)?;
518        let num_region = task.region_ids.len();
519
520        common_runtime::spawn_global(async move {
521            METRIC_META_REGION_MIGRATION_DATANODES
522                .with_label_values(&["src", &task.from_peer.id.to_string()])
523                .inc_by(num_region as u64);
524            METRIC_META_REGION_MIGRATION_DATANODES
525                .with_label_values(&["desc", &task.to_peer.id.to_string()])
526                .inc_by(num_region as u64);
527
528            if let Err(e) = watcher::wait(&mut watcher).await {
529                error!(e; "Failed to wait region migration procedure {procedure_id} for {task}");
530                METRIC_META_REGION_MIGRATION_FAIL.inc();
531                return;
532            }
533
534            info!("Region migration procedure {procedure_id} for {task} is finished successfully!");
535        });
536
537        Ok(procedure_id)
538    }
539
540    /// Builds a new region migration procedure for `task` without submitting it.
541    ///
542    /// Returns the built [`ProcedureWithId`] so that a parent procedure can run it as a
543    /// child procedure.
544    pub async fn build_subprocedure(
545        &self,
546        procedure_context: ProcedureContext,
547        mut task: RegionMigrationProcedureTask,
548    ) -> Result<ProcedureWithId> {
549        let Some(guard) = self.insert_running_procedure(&task) else {
550            return error::MigrationRunningSnafu {
551                region_id: task.region_id,
552            }
553            .fail();
554        };
555
556        self.verify_task(&task)?;
557
558        let region_id = task.region_id;
559
560        let table_route = self.retrieve_table_route(region_id).await?;
561        self.verify_table_route(&table_route, &task)?;
562
563        // Safety: checked before.
564        let region_route = table_route
565            .region_route(region_id)
566            .context(error::UnexpectedLogicalRouteTableSnafu {
567                err_msg: format!("{table_route:?} is a non-physical TableRouteValue."),
568            })?
569            .context(error::RegionRouteNotFoundSnafu { region_id })?;
570
571        if self.has_migrated(&region_route, &task)? {
572            info!("Skipping region migration task: {task}");
573            return error::RegionMigratedSnafu {
574                region_id,
575                target_peer_id: task.to_peer.id,
576            }
577            .fail();
578        }
579
580        self.verify_region_leader_peer(&region_route, &mut task)?;
581        self.verify_region_follower_peers(&region_route, &task)?;
582        let table_info = self.retrieve_table_info(region_id).await?;
583        let TableName {
584            catalog_name,
585            schema_name,
586            ..
587        } = table_info.table_name();
588        let RegionMigrationProcedureTask {
589            region_id,
590            from_peer,
591            to_peer,
592            timeout,
593            trigger_reason,
594        } = task.clone();
595        let procedure = RegionMigrationProcedure::new(
596            PersistentContext::new(
597                vec![(catalog_name, schema_name)],
598                from_peer,
599                to_peer,
600                vec![region_id],
601                timeout,
602                trigger_reason,
603            ),
604            self.context_factory.clone(),
605            vec![guard],
606        );
607        let procedure_with_id =
608            ProcedureWithId::with_random_id(Box::new(procedure)).with_context(procedure_context);
609        Ok(procedure_with_id)
610    }
611
612    /// Submits a new region migration procedure.
613    pub async fn submit_procedure(
614        &self,
615        procedure_context: ProcedureContext,
616        mut task: RegionMigrationProcedureTask,
617    ) -> Result<Option<ProcedureId>> {
618        if let Some(event_context) = procedure_context.event_context.as_ref() {
619            task.trigger_reason =
620                RegionMigrationTriggerReason::from_trigger_reason(event_context.reason);
621        }
622
623        let procedure_with_id = self
624            .build_subprocedure(procedure_context, task.clone())
625            .await?;
626        let procedure_id = procedure_with_id.id;
627        info!("Starting region migration procedure {procedure_id} for {task}");
628        let mut watcher = self
629            .procedure_manager
630            .submit(procedure_with_id)
631            .await
632            .context(error::SubmitProcedureSnafu)?;
633        common_runtime::spawn_global(async move {
634            METRIC_META_REGION_MIGRATION_DATANODES
635                .with_label_values(&["src", &task.from_peer.id.to_string()])
636                .inc();
637            METRIC_META_REGION_MIGRATION_DATANODES
638                .with_label_values(&["desc", &task.to_peer.id.to_string()])
639                .inc();
640
641            if let Err(e) = watcher::wait(&mut watcher).await {
642                error!(e; "Failed to wait region migration procedure {procedure_id} for {task}");
643                METRIC_META_REGION_MIGRATION_FAIL.inc();
644                return;
645            }
646
647            info!("Region migration procedure {procedure_id} for {task} is finished successfully!");
648        });
649
650        Ok(Some(procedure_id))
651    }
652}
653
654#[cfg(test)]
655mod test {
656    use std::assert_matches;
657
658    use common_meta::key::table_route::LogicalTableRouteValue;
659    use common_meta::key::test_utils::new_test_table_info;
660    use common_meta::rpc::router::Region;
661    use common_meta::state_store::KvStateStore;
662    use common_procedure::ProcedureManager;
663    use common_procedure::local::{LocalManager, ManagerConfig, PauseAware};
664    use common_procedure::test_util::InMemoryPoisonStore;
665    use tokio::sync::Notify;
666
667    use super::*;
668    use crate::procedure::region_migration::test_util::TestingEnv;
669
670    #[derive(Default)]
671    struct SubmissionGate {
672        entered: Notify,
673        release: Notify,
674    }
675
676    #[async_trait::async_trait]
677    impl PauseAware for SubmissionGate {
678        async fn is_paused(&self) -> std::result::Result<bool, common_error::ext::BoxedError> {
679            self.entered.notify_one();
680            self.release.notified().await;
681            Ok(false)
682        }
683    }
684
685    async fn check_submission_registration(batch: bool) {
686        let env = TestingEnv::new();
687        let gate = Arc::new(SubmissionGate::default());
688        let procedure_manager = Arc::new(LocalManager::new(
689            ManagerConfig::default(),
690            Arc::new(KvStateStore::new(env.kv_backend())),
691            Arc::new(InMemoryPoisonStore::default()),
692            Some(gate.clone()),
693            None,
694        ));
695        let manager = RegionMigrationManager::new(procedure_manager.clone(), env.context_factory());
696        let region_ids = if batch {
697            vec![RegionId::new(1024, 1), RegionId::new(1024, 2)]
698        } else {
699            vec![RegionId::new(1024, 1)]
700        };
701        let region_routes = region_ids
702            .iter()
703            .map(|region_id| RegionRoute {
704                region: Region::new_test(*region_id),
705                leader_peer: Some(Peer::empty(1)),
706                ..Default::default()
707            })
708            .collect();
709        env.create_physical_table_metadata(new_test_table_info(1024), region_routes)
710            .await;
711
712        // A stopped manager must reject submission and release all migration guards.
713        // Starting it then allows the same regions to be submitted successfully.
714        for started in [false, true] {
715            if started {
716                procedure_manager.start().await.unwrap();
717            }
718            let submission = async {
719                if batch {
720                    let result = manager
721                        .submit_region_migration_task(RegionMigrationTaskBatch {
722                            region_ids: region_ids.clone(),
723                            from_peer: Peer::empty(1),
724                            to_peer: Peer::empty(2),
725                            timeout: Duration::from_secs(10),
726                            trigger_reason: RegionMigrationTriggerReason::Manual,
727                        })
728                        .await?;
729                    assert_eq!(result.submitted, region_ids);
730                    Ok(result.procedure_id)
731                } else {
732                    manager
733                        .submit_procedure(
734                            ProcedureContext::default(),
735                            RegionMigrationProcedureTask::new(
736                                region_ids[0],
737                                Peer::empty(1),
738                                Peer::empty(2),
739                                Duration::from_secs(10),
740                                RegionMigrationTriggerReason::Manual,
741                            ),
742                        )
743                        .await
744                }
745            };
746            tokio::pin!(submission);
747            tokio::select! {
748                biased;
749                result = &mut submission => panic!("Submission returned before registration: {result:?}"),
750                _ = gate.entered.notified() => {}
751            }
752            assert!(futures::poll!(&mut submission).is_pending());
753            assert!(
754                procedure_manager
755                    .list_procedures()
756                    .await
757                    .unwrap()
758                    .is_empty()
759            );
760            for region_id in &region_ids {
761                assert!(manager.tracker.contains(*region_id));
762            }
763
764            gate.release.notify_one();
765            let result = tokio::time::timeout(Duration::from_secs(5), &mut submission)
766                .await
767                .expect("Submission should return after registration");
768            if started {
769                let procedure_id = result.unwrap().unwrap();
770                assert!(
771                    procedure_manager
772                        .procedure_state(procedure_id)
773                        .await
774                        .unwrap()
775                        .is_some()
776                );
777            } else {
778                assert_matches!(
779                    result.unwrap_err(),
780                    error::Error::SubmitProcedure {
781                        source: common_procedure::Error::ManagerNotStart { .. },
782                        ..
783                    }
784                );
785                for region_id in &region_ids {
786                    assert!(!manager.tracker.contains(*region_id));
787                }
788            }
789        }
790        procedure_manager.stop().await.unwrap();
791    }
792
793    #[tokio::test]
794    async fn test_submit_procedure_registration() {
795        check_submission_registration(false).await;
796    }
797
798    #[tokio::test]
799    async fn test_submit_region_migration_task_registration() {
800        check_submission_registration(true).await;
801    }
802
803    #[tokio::test]
804    async fn test_insert_running_procedure() {
805        let env = TestingEnv::new();
806        let context_factory = env.context_factory();
807        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
808        let region_id = RegionId::new(1024, 1);
809        let task = RegionMigrationProcedureTask {
810            region_id,
811            from_peer: Peer::empty(2),
812            to_peer: Peer::empty(1),
813            timeout: Duration::from_millis(1000),
814            trigger_reason: RegionMigrationTriggerReason::Manual,
815        };
816        // Inserts one
817        manager
818            .tracker
819            .running_procedures
820            .write()
821            .unwrap()
822            .insert(region_id, task.clone());
823
824        let err = manager
825            .submit_procedure(
826                ProcedureContext::from_event_context(PersistentEventContext::new(
827                    task.trigger_reason.to_trigger_reason(),
828                )),
829                task,
830            )
831            .await
832            .unwrap_err();
833        assert_matches!(err, error::Error::MigrationRunning { .. });
834    }
835
836    #[tokio::test]
837    async fn test_submit_procedure_invalid_task() {
838        let env = TestingEnv::new();
839        let context_factory = env.context_factory();
840        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
841        let region_id = RegionId::new(1024, 1);
842        let task = RegionMigrationProcedureTask {
843            region_id,
844            from_peer: Peer::empty(1),
845            to_peer: Peer::empty(1),
846            timeout: Duration::from_millis(1000),
847            trigger_reason: RegionMigrationTriggerReason::Manual,
848        };
849
850        let err = manager
851            .submit_procedure(
852                ProcedureContext::from_event_context(PersistentEventContext::new(
853                    task.trigger_reason.to_trigger_reason(),
854                )),
855                task,
856            )
857            .await
858            .unwrap_err();
859        assert_matches!(err, error::Error::InvalidArguments { .. });
860    }
861
862    #[tokio::test]
863    async fn test_submit_procedure_table_not_found() {
864        let env = TestingEnv::new();
865        let context_factory = env.context_factory();
866        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
867        let region_id = RegionId::new(1024, 1);
868        let task = RegionMigrationProcedureTask {
869            region_id,
870            from_peer: Peer::empty(1),
871            to_peer: Peer::empty(2),
872            timeout: Duration::from_millis(1000),
873            trigger_reason: RegionMigrationTriggerReason::Manual,
874        };
875
876        let err = manager
877            .submit_procedure(
878                ProcedureContext::from_event_context(PersistentEventContext::new(
879                    task.trigger_reason.to_trigger_reason(),
880                )),
881                task,
882            )
883            .await
884            .unwrap_err();
885        assert_matches!(err, error::Error::TableRouteNotFound { .. });
886    }
887
888    #[tokio::test]
889    async fn test_submit_procedure_region_route_not_found() {
890        let env = TestingEnv::new();
891        let context_factory = env.context_factory();
892        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
893        let region_id = RegionId::new(1024, 1);
894        let task = RegionMigrationProcedureTask {
895            region_id,
896            from_peer: Peer::empty(1),
897            to_peer: Peer::empty(2),
898            timeout: Duration::from_millis(1000),
899            trigger_reason: RegionMigrationTriggerReason::Manual,
900        };
901
902        let table_info = new_test_table_info(1024);
903        let region_routes = vec![RegionRoute {
904            region: Region::new_test(RegionId::new(1024, 2)),
905            leader_peer: Some(Peer::empty(3)),
906            ..Default::default()
907        }];
908
909        env.create_physical_table_metadata(table_info, region_routes)
910            .await;
911
912        let err = manager
913            .submit_procedure(
914                ProcedureContext::from_event_context(PersistentEventContext::new(
915                    task.trigger_reason.to_trigger_reason(),
916                )),
917                task,
918            )
919            .await
920            .unwrap_err();
921        assert_matches!(err, error::Error::RegionRouteNotFound { .. });
922    }
923
924    #[tokio::test]
925    async fn test_submit_procedure_incorrect_from_peer() {
926        let env = TestingEnv::new();
927        let context_factory = env.context_factory();
928        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
929        let region_id = RegionId::new(1024, 1);
930        let task = RegionMigrationProcedureTask {
931            region_id,
932            from_peer: Peer::empty(1),
933            to_peer: Peer::empty(2),
934            timeout: Duration::from_millis(1000),
935            trigger_reason: RegionMigrationTriggerReason::Manual,
936        };
937
938        let table_info = new_test_table_info(1024);
939        let region_routes = vec![RegionRoute {
940            region: Region::new_test(RegionId::new(1024, 1)),
941            leader_peer: Some(Peer::empty(3)),
942            ..Default::default()
943        }];
944
945        env.create_physical_table_metadata(table_info, region_routes)
946            .await;
947
948        let err = manager
949            .submit_procedure(
950                ProcedureContext::from_event_context(PersistentEventContext::new(
951                    task.trigger_reason.to_trigger_reason(),
952                )),
953                task,
954            )
955            .await
956            .unwrap_err();
957        assert_matches!(err, error::Error::LeaderPeerChanged { .. });
958        assert_eq!(
959            err.to_string(),
960            "Region's leader peer changed: Region's leader peer(3) is not the `from_peer`(1), region: 4398046511105(1024, 1)"
961        );
962    }
963
964    #[tokio::test]
965    async fn test_submit_procedure_region_follower_on_to_peer() {
966        let env = TestingEnv::new();
967        let context_factory = env.context_factory();
968        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
969        let region_id = RegionId::new(1024, 1);
970        let task = RegionMigrationProcedureTask {
971            region_id,
972            from_peer: Peer::empty(3),
973            to_peer: Peer::empty(2),
974            timeout: Duration::from_millis(1000),
975            trigger_reason: RegionMigrationTriggerReason::Manual,
976        };
977
978        let table_info = new_test_table_info(1024);
979        let region_routes = vec![RegionRoute {
980            region: Region::new_test(region_id),
981            leader_peer: Some(Peer::empty(3)),
982            follower_peers: vec![Peer::empty(2)],
983            ..Default::default()
984        }];
985
986        env.create_physical_table_metadata(table_info, region_routes)
987            .await;
988
989        let err = manager
990            .submit_procedure(
991                ProcedureContext::from_event_context(PersistentEventContext::new(
992                    task.trigger_reason.to_trigger_reason(),
993                )),
994                task,
995            )
996            .await
997            .unwrap_err();
998        assert_matches!(err, error::Error::InvalidArguments { .. });
999        assert_eq!(
1000            err.to_string(),
1001            "Invalid arguments: The `to_peer`(2) is already has a region follower, region: 4398046511105(1024, 1)"
1002        );
1003    }
1004
1005    #[tokio::test]
1006    async fn test_submit_procedure_has_migrated() {
1007        common_telemetry::init_default_ut_logging();
1008        let env = TestingEnv::new();
1009        let context_factory = env.context_factory();
1010        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1011        let region_id = RegionId::new(1024, 1);
1012        let task = RegionMigrationProcedureTask {
1013            region_id,
1014            from_peer: Peer::empty(1),
1015            to_peer: Peer::empty(2),
1016            timeout: Duration::from_millis(1000),
1017            trigger_reason: RegionMigrationTriggerReason::Manual,
1018        };
1019
1020        let table_info = new_test_table_info(1024);
1021        let region_routes = vec![RegionRoute {
1022            region: Region::new_test(RegionId::new(1024, 1)),
1023            leader_peer: Some(Peer::empty(2)),
1024            ..Default::default()
1025        }];
1026
1027        env.create_physical_table_metadata(table_info, region_routes)
1028            .await;
1029
1030        let err = manager
1031            .submit_procedure(
1032                ProcedureContext::from_event_context(PersistentEventContext::new(
1033                    task.trigger_reason.to_trigger_reason(),
1034                )),
1035                task,
1036            )
1037            .await
1038            .unwrap_err();
1039        assert_matches!(err, error::Error::RegionMigrated { .. });
1040    }
1041
1042    #[tokio::test]
1043    async fn test_verify_table_route_error() {
1044        let env = TestingEnv::new();
1045        let context_factory = env.context_factory();
1046        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1047        let region_id = RegionId::new(1024, 1);
1048        let task = RegionMigrationProcedureTask {
1049            region_id,
1050            from_peer: Peer::empty(1),
1051            to_peer: Peer::empty(2),
1052            timeout: Duration::from_millis(1000),
1053            trigger_reason: RegionMigrationTriggerReason::Manual,
1054        };
1055
1056        let err = manager
1057            .verify_table_route(
1058                &TableRouteValue::Logical(LogicalTableRouteValue::new(0)),
1059                &task,
1060            )
1061            .unwrap_err();
1062
1063        assert_matches!(err, error::Error::Unexpected { .. });
1064    }
1065
1066    #[tokio::test]
1067    async fn test_submit_procedure_with_multiple_regions_invalid_task() {
1068        let env = TestingEnv::new();
1069        let context_factory = env.context_factory();
1070        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1071        let task = RegionMigrationTaskBatch {
1072            region_ids: vec![RegionId::new(1024, 1)],
1073            from_peer: Peer::empty(1),
1074            to_peer: Peer::empty(1),
1075            timeout: Duration::from_millis(1000),
1076            trigger_reason: RegionMigrationTriggerReason::Manual,
1077        };
1078
1079        let err = manager
1080            .submit_region_migration_task(task)
1081            .await
1082            .unwrap_err();
1083        assert_matches!(err, error::Error::InvalidArguments { .. });
1084    }
1085
1086    #[tokio::test]
1087    async fn test_submit_procedure_with_multiple_regions_no_region_to_migrate() {
1088        common_telemetry::init_default_ut_logging();
1089        let env = TestingEnv::new();
1090        let context_factory = env.context_factory();
1091        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1092        let region_id = RegionId::new(1024, 1);
1093        let task = RegionMigrationTaskBatch {
1094            region_ids: vec![region_id],
1095            from_peer: Peer::empty(1),
1096            to_peer: Peer::empty(2),
1097            timeout: Duration::from_millis(1000),
1098            trigger_reason: RegionMigrationTriggerReason::Manual,
1099        };
1100        let table_info = new_test_table_info(1024);
1101        let region_routes = vec![RegionRoute {
1102            region: Region::new_test(region_id),
1103            leader_peer: Some(Peer::empty(2)),
1104            ..Default::default()
1105        }];
1106        env.create_physical_table_metadata(table_info, region_routes)
1107            .await;
1108        let result = manager.submit_region_migration_task(task).await.unwrap();
1109
1110        assert_eq!(
1111            result,
1112            SubmitRegionMigrationTaskResult {
1113                migrated: vec![region_id],
1114                ..Default::default()
1115            }
1116        );
1117    }
1118
1119    #[tokio::test]
1120    async fn test_submit_procedure_with_multiple_regions_leader_peer_changed() {
1121        let env = TestingEnv::new();
1122        let context_factory = env.context_factory();
1123        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1124        let region_id = RegionId::new(1024, 1);
1125        let task = RegionMigrationTaskBatch {
1126            region_ids: vec![region_id],
1127            from_peer: Peer::empty(1),
1128            to_peer: Peer::empty(2),
1129            timeout: Duration::from_millis(1000),
1130            trigger_reason: RegionMigrationTriggerReason::Manual,
1131        };
1132
1133        let table_info = new_test_table_info(1024);
1134        let region_routes = vec![RegionRoute {
1135            region: Region::new_test(RegionId::new(1024, 1)),
1136            leader_peer: Some(Peer::empty(3)),
1137            ..Default::default()
1138        }];
1139
1140        env.create_physical_table_metadata(table_info, region_routes)
1141            .await;
1142        let result = manager.submit_region_migration_task(task).await.unwrap();
1143        assert_eq!(
1144            result,
1145            SubmitRegionMigrationTaskResult {
1146                leader_changed: vec![region_id],
1147                ..Default::default()
1148            }
1149        );
1150    }
1151
1152    #[tokio::test]
1153    async fn test_submit_procedure_with_multiple_regions_peer_conflict() {
1154        let env = TestingEnv::new();
1155        let context_factory = env.context_factory();
1156        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1157        let region_id = RegionId::new(1024, 1);
1158        let task = RegionMigrationTaskBatch {
1159            region_ids: vec![region_id],
1160            from_peer: Peer::empty(3),
1161            to_peer: Peer::empty(2),
1162            timeout: Duration::from_millis(1000),
1163            trigger_reason: RegionMigrationTriggerReason::Manual,
1164        };
1165
1166        let table_info = new_test_table_info(1024);
1167        let region_routes = vec![RegionRoute {
1168            region: Region::new_test(region_id),
1169            leader_peer: Some(Peer::empty(3)),
1170            follower_peers: vec![Peer::empty(2)],
1171            ..Default::default()
1172        }];
1173
1174        env.create_physical_table_metadata(table_info, region_routes)
1175            .await;
1176        let result = manager.submit_region_migration_task(task).await.unwrap();
1177        assert_eq!(
1178            result,
1179            SubmitRegionMigrationTaskResult {
1180                peer_conflict: vec![region_id],
1181                ..Default::default()
1182            }
1183        );
1184    }
1185
1186    #[tokio::test]
1187    async fn test_running_regions() {
1188        let env = TestingEnv::new();
1189        env.procedure_manager().start().await.unwrap();
1190        let context_factory = env.context_factory();
1191        let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory);
1192        let region_id = RegionId::new(1024, 1);
1193        let task = RegionMigrationTaskBatch {
1194            region_ids: vec![region_id, RegionId::new(1024, 2)],
1195            from_peer: Peer::empty(1),
1196            to_peer: Peer::empty(2),
1197            timeout: Duration::from_millis(1000),
1198            trigger_reason: RegionMigrationTriggerReason::Manual,
1199        };
1200        // Inserts one
1201        manager.tracker.running_procedures.write().unwrap().insert(
1202            region_id,
1203            RegionMigrationProcedureTask::new(
1204                region_id,
1205                task.from_peer.clone(),
1206                task.to_peer.clone(),
1207                task.timeout,
1208                task.trigger_reason,
1209            ),
1210        );
1211        let table_info = new_test_table_info(1024);
1212        let region_routes = vec![RegionRoute {
1213            region: Region::new_test(RegionId::new(1024, 2)),
1214            leader_peer: Some(Peer::empty(1)),
1215            ..Default::default()
1216        }];
1217        env.create_physical_table_metadata(table_info, region_routes)
1218            .await;
1219        let result = manager.submit_region_migration_task(task).await.unwrap();
1220        assert_eq!(result.migrating, vec![region_id]);
1221        assert_eq!(result.submitted, vec![RegionId::new(1024, 2)]);
1222        assert!(result.procedure_id.is_some());
1223        env.procedure_manager().stop().await.unwrap();
1224    }
1225}