1use 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
46pub 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 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 pub(crate) fn contains(&self, region_id: RegionId) -> bool {
79 self.running_procedures
80 .read()
81 .unwrap()
82 .contains_key(®ion_id)
83 }
84}
85
86pub(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#[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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
120#[strum(serialize_all = "PascalCase")]
121pub enum RegionMigrationTriggerReason {
122 #[default]
123 Unknown,
125 Manual,
127 AutoRebalance,
129 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#[derive(Debug, Default, PartialEq, Eq)]
163pub struct SubmitRegionMigrationTaskResult {
164 pub migrated: Vec<RegionId>,
166 pub leader_changed: Vec<RegionId>,
168 pub peer_conflict: Vec<RegionId>,
170 pub table_not_found: Vec<RegionId>,
172 pub region_not_found: Vec<RegionId>,
174 pub migrating: Vec<RegionId>,
176 pub submitted: Vec<RegionId>,
178 pub procedure_id: Option<ProcedureId>,
180}
181
182impl RegionMigrationManager {
183 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 pub fn tracker(&self) -> &RegionMigrationProcedureTracker {
197 &self.tracker
198 }
199
200 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 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 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 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 task.from_peer = leader_peer.clone();
339 }
340
341 Ok(())
342 }
343
344 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 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 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 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 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 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(®ion_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(®ion_route, &mut task)?;
581 self.verify_region_follower_peers(®ion_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 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 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 ®ion_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 ®ion_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 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 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}