1pub(crate) mod close_downgraded_region;
16pub(crate) mod downgrade_leader_region;
17pub(crate) mod flush_leader_region;
18pub(crate) mod manager;
19pub(crate) mod migration_abort;
20pub(crate) mod migration_end;
21pub(crate) mod migration_start;
22pub(crate) mod open_candidate_region;
23#[cfg(test)]
24pub mod test_util;
25pub(crate) mod update_metadata;
26pub(crate) mod upgrade_candidate_region;
27pub(crate) mod utils;
28
29use std::any::Any;
30use std::collections::{HashMap, HashSet};
31use std::fmt::{Debug, Display};
32use std::time::Duration;
33
34use common_error::ext::BoxedError;
35use common_event_recorder::{Event, PersistentEventContext};
36use common_meta::cache_invalidator::CacheInvalidatorRef;
37use common_meta::ddl::RegionFailureDetectorControllerRef;
38use common_meta::instruction::CacheIdent;
39use common_meta::key::datanode_table::{DatanodeTableKey, DatanodeTableValue};
40use common_meta::key::table_route::TableRouteValue;
41use common_meta::key::topic_name::TopicNameKey;
42use common_meta::key::topic_region::{ReplayCheckpoint, TopicRegionKey};
43use common_meta::key::{DeserializedValueWithBytes, TableMetadataManagerRef};
44use common_meta::kv_backend::{KvBackendRef, ResettableKvBackendRef};
45use common_meta::lock_key::{CatalogLock, RegionLock, SchemaLock, TableLock};
46use common_meta::peer::Peer;
47use common_meta::region_keeper::{MemoryRegionKeeperRef, OperatingRegionGuard};
48use common_meta::rpc::ddl::TriggerReason;
49use common_procedure::error::{
50 Error as ProcedureError, FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu,
51};
52use common_procedure::{
53 Context as ProcedureContext, EventContext, LockKey, Procedure, Status, StringKey,
54};
55use common_telemetry::{debug, error, info};
56use manager::RegionMigrationProcedureGuard;
57pub use manager::{
58 RegionMigrationManagerRef, RegionMigrationProcedureTask, RegionMigrationProcedureTracker,
59 RegionMigrationTriggerReason,
60};
61use serde::{Deserialize, Deserializer, Serialize};
62use snafu::{OptionExt, ResultExt};
63use store_api::storage::{RegionId, TableId};
64use tokio::time::Instant;
65
66use self::migration_start::RegionMigrationStart;
67use crate::error::{self, Result};
68use crate::event::region_migration::{REGION_MIGRATION_EVENT_TYPE, RegionMigrationEvent};
69use crate::metrics::{
70 METRIC_META_REGION_MIGRATION_ERROR, METRIC_META_REGION_MIGRATION_EXECUTE,
71 METRIC_META_REGION_MIGRATION_STAGE_ELAPSED,
72};
73use crate::service::mailbox::MailboxRef;
74
75pub const DEFAULT_REGION_MIGRATION_TIMEOUT: Duration = Duration::from_secs(120);
77
78#[derive(Debug, Deserialize)]
79#[serde(untagged)]
80enum SingleOrMultiple<T> {
81 Single(T),
82 Multiple(Vec<T>),
83}
84
85fn single_or_multiple_from<'de, D, T>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
86where
87 D: Deserializer<'de>,
88 T: Deserialize<'de>,
89{
90 let helper = SingleOrMultiple::<T>::deserialize(deserializer)?;
91 Ok(match helper {
92 SingleOrMultiple::Single(x) => vec![x],
93 SingleOrMultiple::Multiple(xs) => xs,
94 })
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103pub struct PersistentContext {
104 #[deprecated(note = "use `catalog_and_schema` instead")]
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub(crate) catalog: Option<String>,
108 #[deprecated(note = "use `catalog_and_schema` instead")]
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub(crate) schema: Option<String>,
112 #[serde(default, skip_serializing_if = "Vec::is_empty")]
114 pub(crate) catalog_and_schema: Vec<(String, String)>,
115 pub(crate) from_peer: Peer,
117 pub(crate) to_peer: Peer,
119 #[serde(deserialize_with = "single_or_multiple_from", alias = "region_id")]
121 pub(crate) region_ids: Vec<RegionId>,
122 #[serde(with = "humantime_serde", default = "default_timeout")]
124 pub(crate) timeout: Duration,
125 #[serde(default)]
127 trigger_reason: RegionMigrationTriggerReason,
128}
129
130impl PersistentContext {
131 pub fn new(
132 catalog_and_schema: Vec<(String, String)>,
133 from_peer: Peer,
134 to_peer: Peer,
135 region_ids: Vec<RegionId>,
136 timeout: Duration,
137 trigger_reason: RegionMigrationTriggerReason,
138 ) -> Self {
139 #[allow(deprecated)]
140 Self {
141 catalog: None,
142 schema: None,
143 catalog_and_schema,
144 from_peer,
145 to_peer,
146 region_ids,
147 timeout,
148 trigger_reason,
149 }
150 }
151}
152
153impl RegionMigrationTriggerReason {
154 fn to_trigger_reason(self) -> TriggerReason {
155 match self {
156 Self::Manual => TriggerReason::Manual,
157 Self::AutoRebalance => TriggerReason::AutoRebalance,
158 Self::Failover => TriggerReason::RegionFailover,
159 Self::Unknown => TriggerReason::Unknown,
160 }
161 }
162
163 pub(crate) fn from_trigger_reason(reason: TriggerReason) -> Self {
164 match reason {
165 TriggerReason::Manual => Self::Manual,
166 TriggerReason::AutoRebalance => Self::AutoRebalance,
167 TriggerReason::RegionFailover => Self::Failover,
168 _ => Self::Unknown,
169 }
170 }
171}
172
173fn default_timeout() -> Duration {
174 Duration::from_secs(10)
175}
176
177impl PersistentContext {
178 pub fn lock_key(&self) -> Vec<StringKey> {
179 let mut lock_keys =
180 Vec::with_capacity(self.region_ids.len() + 2 + self.catalog_and_schema.len() * 2);
181 #[allow(deprecated)]
182 if let (Some(catalog), Some(schema)) = (&self.catalog, &self.schema) {
183 lock_keys.push(CatalogLock::Read(catalog).into());
184 lock_keys.push(SchemaLock::read(catalog, schema).into());
185 }
186 for (catalog, schema) in self.catalog_and_schema.iter() {
187 lock_keys.push(CatalogLock::Read(catalog).into());
188 lock_keys.push(SchemaLock::read(catalog, schema).into());
189 }
190
191 let mut region_ids = self.region_ids.clone();
193 region_ids.sort_unstable();
194 for region_id in region_ids {
195 lock_keys.push(RegionLock::Write(region_id).into());
196 }
197 lock_keys
198 }
199
200 pub fn region_table_ids(&self) -> Vec<TableId> {
204 self.region_ids
205 .iter()
206 .map(|region_id| region_id.table_id())
207 .collect::<HashSet<_>>()
208 .into_iter()
209 .collect()
210 }
211
212 pub fn table_regions(&self) -> HashMap<TableId, Vec<RegionId>> {
216 let mut table_regions = HashMap::new();
217 for region_id in &self.region_ids {
218 table_regions
219 .entry(region_id.table_id())
220 .or_insert_with(Vec::new)
221 .push(*region_id);
222 }
223 table_regions
224 }
225}
226
227#[derive(Debug, Clone, Default)]
229pub struct Metrics {
230 operations_elapsed: Duration,
232 flush_leader_region_elapsed: Duration,
234 downgrade_leader_region_elapsed: Duration,
236 open_candidate_region_elapsed: Duration,
238 upgrade_candidate_region_elapsed: Duration,
240}
241
242impl Display for Metrics {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 let total = self.flush_leader_region_elapsed
245 + self.downgrade_leader_region_elapsed
246 + self.open_candidate_region_elapsed
247 + self.upgrade_candidate_region_elapsed;
248 write!(
249 f,
250 "total: {:?}, flush_leader_region_elapsed: {:?}, downgrade_leader_region_elapsed: {:?}, open_candidate_region_elapsed: {:?}, upgrade_candidate_region_elapsed: {:?}",
251 total,
252 self.flush_leader_region_elapsed,
253 self.downgrade_leader_region_elapsed,
254 self.open_candidate_region_elapsed,
255 self.upgrade_candidate_region_elapsed
256 )
257 }
258}
259
260impl Metrics {
261 pub fn update_operations_elapsed(&mut self, elapsed: Duration) {
263 self.operations_elapsed += elapsed;
264 }
265
266 pub fn update_flush_leader_region_elapsed(&mut self, elapsed: Duration) {
268 self.flush_leader_region_elapsed += elapsed;
269 }
270
271 pub fn update_downgrade_leader_region_elapsed(&mut self, elapsed: Duration) {
273 self.downgrade_leader_region_elapsed += elapsed;
274 }
275
276 pub fn update_open_candidate_region_elapsed(&mut self, elapsed: Duration) {
278 self.open_candidate_region_elapsed += elapsed;
279 }
280
281 pub fn update_upgrade_candidate_region_elapsed(&mut self, elapsed: Duration) {
283 self.upgrade_candidate_region_elapsed += elapsed;
284 }
285}
286
287impl Drop for Metrics {
288 fn drop(&mut self) {
289 let total = self.flush_leader_region_elapsed
290 + self.downgrade_leader_region_elapsed
291 + self.open_candidate_region_elapsed
292 + self.upgrade_candidate_region_elapsed;
293 METRIC_META_REGION_MIGRATION_STAGE_ELAPSED
294 .with_label_values(&["total"])
295 .observe(total.as_secs_f64());
296
297 if !self.flush_leader_region_elapsed.is_zero() {
298 METRIC_META_REGION_MIGRATION_STAGE_ELAPSED
299 .with_label_values(&["flush_leader_region"])
300 .observe(self.flush_leader_region_elapsed.as_secs_f64());
301 }
302
303 if !self.downgrade_leader_region_elapsed.is_zero() {
304 METRIC_META_REGION_MIGRATION_STAGE_ELAPSED
305 .with_label_values(&["downgrade_leader_region"])
306 .observe(self.downgrade_leader_region_elapsed.as_secs_f64());
307 }
308
309 if !self.open_candidate_region_elapsed.is_zero() {
310 METRIC_META_REGION_MIGRATION_STAGE_ELAPSED
311 .with_label_values(&["open_candidate_region"])
312 .observe(self.open_candidate_region_elapsed.as_secs_f64());
313 }
314
315 if !self.upgrade_candidate_region_elapsed.is_zero() {
316 METRIC_META_REGION_MIGRATION_STAGE_ELAPSED
317 .with_label_values(&["upgrade_candidate_region"])
318 .observe(self.upgrade_candidate_region_elapsed.as_secs_f64());
319 }
320 }
321}
322
323#[derive(Debug, Clone, Default)]
329pub struct VolatileContext {
330 opening_region_guards: Vec<OperatingRegionGuard>,
337 leader_region_lease_deadline: Option<Instant>,
339 from_peer_datanode_table_values: Option<HashMap<TableId, DatanodeTableValue>>,
341 leader_region_last_entry_ids: HashMap<RegionId, u64>,
343 leader_region_metadata_last_entry_ids: HashMap<RegionId, u64>,
345 metrics: Metrics,
347}
348
349impl VolatileContext {
350 pub fn set_leader_region_lease_deadline(&mut self, lease_timeout: Duration) {
352 if self.leader_region_lease_deadline.is_none() {
353 self.leader_region_lease_deadline = Some(Instant::now() + lease_timeout);
354 }
355 }
356
357 pub fn reset_leader_region_lease_deadline(&mut self) {
359 self.leader_region_lease_deadline = None;
360 }
361
362 pub fn set_last_entry_id(&mut self, region_id: RegionId, last_entry_id: u64) {
364 self.leader_region_last_entry_ids
365 .insert(region_id, last_entry_id);
366 }
367
368 pub fn set_metadata_last_entry_id(&mut self, region_id: RegionId, last_entry_id: u64) {
370 self.leader_region_metadata_last_entry_ids
371 .insert(region_id, last_entry_id);
372 }
373}
374
375pub trait ContextFactory {
377 fn new_context(self, persistent_ctx: PersistentContext) -> Context;
378}
379
380#[derive(Clone)]
382pub struct DefaultContextFactory {
383 volatile_ctx: VolatileContext,
384 in_memory_key: ResettableKvBackendRef,
385 table_metadata_manager: TableMetadataManagerRef,
386 opening_region_keeper: MemoryRegionKeeperRef,
387 region_failure_detector_controller: RegionFailureDetectorControllerRef,
388 mailbox: MailboxRef,
389 server_addr: String,
390 cache_invalidator: CacheInvalidatorRef,
391}
392
393impl DefaultContextFactory {
394 pub fn new(
396 in_memory_key: ResettableKvBackendRef,
397 table_metadata_manager: TableMetadataManagerRef,
398 opening_region_keeper: MemoryRegionKeeperRef,
399 region_failure_detector_controller: RegionFailureDetectorControllerRef,
400 mailbox: MailboxRef,
401 server_addr: String,
402 cache_invalidator: CacheInvalidatorRef,
403 ) -> Self {
404 Self {
405 volatile_ctx: VolatileContext::default(),
406 in_memory_key,
407 table_metadata_manager,
408 opening_region_keeper,
409 region_failure_detector_controller,
410 mailbox,
411 server_addr,
412 cache_invalidator,
413 }
414 }
415}
416
417impl ContextFactory for DefaultContextFactory {
418 fn new_context(self, persistent_ctx: PersistentContext) -> Context {
419 Context {
420 persistent_ctx,
421 volatile_ctx: self.volatile_ctx,
422 in_memory: self.in_memory_key,
423 table_metadata_manager: self.table_metadata_manager,
424 opening_region_keeper: self.opening_region_keeper,
425 region_failure_detector_controller: self.region_failure_detector_controller,
426 mailbox: self.mailbox,
427 server_addr: self.server_addr,
428 cache_invalidator: self.cache_invalidator,
429 }
430 }
431}
432
433pub struct Context {
435 persistent_ctx: PersistentContext,
436 volatile_ctx: VolatileContext,
437 in_memory: KvBackendRef,
438 table_metadata_manager: TableMetadataManagerRef,
439 opening_region_keeper: MemoryRegionKeeperRef,
440 region_failure_detector_controller: RegionFailureDetectorControllerRef,
441 mailbox: MailboxRef,
442 server_addr: String,
443 cache_invalidator: CacheInvalidatorRef,
444}
445
446impl Context {
447 pub(crate) fn trigger_reason(
448 &self,
449 event_context: Option<&PersistentEventContext>,
450 ) -> RegionMigrationTriggerReason {
451 event_context
452 .map(|ctx| RegionMigrationTriggerReason::from_trigger_reason(ctx.reason))
453 .unwrap_or(self.persistent_ctx.trigger_reason)
454 }
455 pub fn next_operation_timeout(&self) -> Option<Duration> {
457 self.persistent_ctx
458 .timeout
459 .checked_sub(self.volatile_ctx.metrics.operations_elapsed)
460 }
461
462 pub fn update_operations_elapsed(&mut self, instant: Instant) {
464 self.volatile_ctx
465 .metrics
466 .update_operations_elapsed(instant.elapsed());
467 }
468
469 pub fn update_flush_leader_region_elapsed(&mut self, instant: Instant) {
471 self.volatile_ctx
472 .metrics
473 .update_flush_leader_region_elapsed(instant.elapsed());
474 }
475
476 pub fn update_downgrade_leader_region_elapsed(&mut self, instant: Instant) {
478 self.volatile_ctx
479 .metrics
480 .update_downgrade_leader_region_elapsed(instant.elapsed());
481 }
482
483 pub fn update_open_candidate_region_elapsed(&mut self, instant: Instant) {
485 self.volatile_ctx
486 .metrics
487 .update_open_candidate_region_elapsed(instant.elapsed());
488 }
489
490 pub fn update_upgrade_candidate_region_elapsed(&mut self, instant: Instant) {
492 self.volatile_ctx
493 .metrics
494 .update_upgrade_candidate_region_elapsed(instant.elapsed());
495 }
496
497 pub fn server_addr(&self) -> &str {
499 &self.server_addr
500 }
501
502 pub fn region_table_ids(&self) -> Vec<TableId> {
504 self.persistent_ctx
505 .region_ids
506 .iter()
507 .map(|region_id| region_id.table_id())
508 .collect::<HashSet<_>>()
509 .into_iter()
510 .collect()
511 }
512
513 pub async fn get_table_route_values(
519 &self,
520 ) -> Result<HashMap<TableId, DeserializedValueWithBytes<TableRouteValue>>> {
521 let table_ids = self.persistent_ctx.region_table_ids();
522 let table_routes = self
523 .table_metadata_manager
524 .table_route_manager()
525 .table_route_storage()
526 .batch_get_with_raw_bytes(&table_ids)
527 .await
528 .context(error::TableMetadataManagerSnafu)
529 .map_err(BoxedError::new)
530 .with_context(|_| error::RetryLaterWithSourceSnafu {
531 reason: format!("Failed to get table routes: {table_ids:?}"),
532 })?;
533 let table_routes = table_ids
534 .into_iter()
535 .zip(table_routes)
536 .filter_map(|(table_id, table_route)| {
537 table_route.map(|table_route| (table_id, table_route))
538 })
539 .collect::<HashMap<_, _>>();
540 Ok(table_routes)
541 }
542
543 pub async fn get_table_route_value(
549 &self,
550 table_id: TableId,
551 ) -> Result<DeserializedValueWithBytes<TableRouteValue>> {
552 let table_route_value = self
553 .table_metadata_manager
554 .table_route_manager()
555 .table_route_storage()
556 .get_with_raw_bytes(table_id)
557 .await
558 .context(error::TableMetadataManagerSnafu)
559 .map_err(BoxedError::new)
560 .with_context(|_| error::RetryLaterWithSourceSnafu {
561 reason: format!("Failed to get table routes: {table_id:}"),
562 })?
563 .context(error::TableRouteNotFoundSnafu { table_id })?;
564 Ok(table_route_value)
565 }
566
567 pub async fn get_from_peer_datanode_table_values(
573 &mut self,
574 ) -> Result<&HashMap<TableId, DatanodeTableValue>> {
575 let from_peer_datanode_table_values =
576 &mut self.volatile_ctx.from_peer_datanode_table_values;
577 if from_peer_datanode_table_values.is_none() {
578 let table_ids = self.persistent_ctx.region_table_ids();
579 let datanode_table_keys = table_ids
580 .iter()
581 .map(|table_id| DatanodeTableKey {
582 datanode_id: self.persistent_ctx.from_peer.id,
583 table_id: *table_id,
584 })
585 .collect::<Vec<_>>();
586 let datanode_table_values = self
587 .table_metadata_manager
588 .datanode_table_manager()
589 .batch_get(&datanode_table_keys)
590 .await
591 .context(error::TableMetadataManagerSnafu)
592 .map_err(BoxedError::new)
593 .with_context(|_| error::RetryLaterWithSourceSnafu {
594 reason: format!("Failed to get DatanodeTable: {table_ids:?}"),
595 })?
596 .into_iter()
597 .map(|(k, v)| (k.table_id, v))
598 .collect();
599 *from_peer_datanode_table_values = Some(datanode_table_values);
600 }
601 Ok(from_peer_datanode_table_values.as_ref().unwrap())
602 }
603
604 pub async fn get_from_peer_datanode_table_value(
610 &self,
611 table_id: TableId,
612 ) -> Result<DatanodeTableValue> {
613 let datanode_table_value = self
614 .table_metadata_manager
615 .datanode_table_manager()
616 .get(&DatanodeTableKey {
617 datanode_id: self.persistent_ctx.from_peer.id,
618 table_id,
619 })
620 .await
621 .context(error::TableMetadataManagerSnafu)
622 .map_err(BoxedError::new)
623 .with_context(|_| error::RetryLaterWithSourceSnafu {
624 reason: format!("Failed to get DatanodeTable: {table_id}"),
625 })?
626 .context(error::DatanodeTableNotFoundSnafu {
627 table_id,
628 datanode_id: self.persistent_ctx.from_peer.id,
629 })?;
630 Ok(datanode_table_value)
631 }
632
633 pub async fn register_failure_detectors(&self, region_ids: &[RegionId]) {
639 let datanode_id = self.persistent_ctx.from_peer.id;
640 let detecting_regions = region_ids
641 .iter()
642 .map(|region_id| (datanode_id, *region_id))
643 .collect::<Vec<_>>();
644 self.region_failure_detector_controller
645 .register_failure_detectors(detecting_regions)
646 .await;
647 info!(
648 "Registered failure detectors after migration failures for datanode {}, regions {:?}",
649 datanode_id, region_ids
650 );
651 }
652
653 pub async fn reset_failure_detectors(&self, region_ids: &[RegionId]) {
656 let datanode_id = self.persistent_ctx.to_peer.id;
657 let detecting_regions = region_ids
658 .iter()
659 .map(|region_id| (datanode_id, *region_id))
660 .collect::<Vec<_>>();
661 self.region_failure_detector_controller
662 .reset_failure_detectors(detecting_regions)
663 .await;
664 info!(
665 "Reset failure detectors of regions for datanode {}, regions {:?}",
666 datanode_id, region_ids
667 );
668 }
669
670 pub async fn deregister_failure_detectors(&self) {
675 let datanode_id = self.persistent_ctx.from_peer.id;
676 let region_ids = &self.persistent_ctx.region_ids;
677 let detecting_regions = region_ids
678 .iter()
679 .map(|region_id| (datanode_id, *region_id))
680 .collect::<Vec<_>>();
681
682 self.region_failure_detector_controller
683 .deregister_failure_detectors(detecting_regions)
684 .await;
685 }
686
687 pub async fn deregister_failure_detectors_for_candidate_regions(&self) {
692 let to_peer_id = self.persistent_ctx.to_peer.id;
693 let region_ids = &self.persistent_ctx.region_ids;
694 let detecting_regions = region_ids
695 .iter()
696 .map(|region_id| (to_peer_id, *region_id))
697 .collect::<Vec<_>>();
698
699 self.region_failure_detector_controller
700 .deregister_failure_detectors(detecting_regions)
701 .await;
702 }
703
704 pub async fn get_replay_checkpoints_with_topic_pruned_entry_ids(
706 &self,
707 region_topics: &[(RegionId, String, bool)],
708 ) -> Result<HashMap<RegionId, ReplayCheckpoint>> {
709 let topic_region_keys = region_topics
710 .iter()
711 .map(|(region_id, topic, _)| TopicRegionKey::new(*region_id, topic))
712 .collect::<Vec<_>>();
713 let topic_region_values = self
714 .table_metadata_manager
715 .topic_region_manager()
716 .batch_get(topic_region_keys)
717 .await
718 .context(error::TableMetadataManagerSnafu)?;
719
720 let topic_name_keys = region_topics
721 .iter()
722 .map(|(_, topic, _)| topic.as_str())
723 .collect::<HashSet<_>>()
724 .into_iter()
725 .map(TopicNameKey::new)
726 .collect::<Vec<_>>();
727 let topic_name_values = self
728 .table_metadata_manager
729 .topic_name_manager()
730 .batch_get(topic_name_keys)
731 .await
732 .context(error::TableMetadataManagerSnafu)?;
733 debug!(
734 "Fetched topic region values: {:?}, topic name values: {:?}",
735 topic_region_values, topic_name_values
736 );
737
738 let replay_checkpoints = region_topics
739 .iter()
740 .filter_map(|(region_id, topic, is_metric_engine)| {
741 let checkpoint = topic_region_values
742 .get(region_id)
743 .and_then(|value| value.checkpoint);
744 let pruned_entry_id = topic_name_values
745 .get(topic)
746 .map(|value| value.pruned_entry_id);
747
748 ReplayCheckpoint::merge_with_topic_pruned_entry_id(
749 checkpoint,
750 pruned_entry_id,
751 *is_metric_engine,
752 )
753 .map(|checkpoint| (*region_id, checkpoint))
754 })
755 .collect::<HashMap<_, _>>();
756
757 Ok(replay_checkpoints)
758 }
759
760 pub async fn invalidate_table_cache(&self) -> Result<()> {
762 let table_ids = self.region_table_ids();
763 let mut cache_idents = Vec::with_capacity(table_ids.len());
764 for table_id in &table_ids {
765 cache_idents.push(CacheIdent::TableId(*table_id));
766 }
767 let ctx = common_meta::cache_invalidator::Context::default();
769 let _ = self.cache_invalidator.invalidate(&ctx, &cache_idents).await;
770 Ok(())
771 }
772
773 pub fn persistent_ctx(&self) -> PersistentContext {
775 self.persistent_ctx.clone()
776 }
777}
778
779#[async_trait::async_trait]
780#[typetag::serde(tag = "region_migration_state")]
781pub(crate) trait State: Sync + Send + Debug {
782 fn name(&self) -> &'static str {
783 let type_name = std::any::type_name::<Self>();
784 type_name.split("::").last().unwrap_or(type_name)
786 }
787
788 async fn next(
790 &mut self,
791 ctx: &mut Context,
792 procedure_ctx: &ProcedureContext,
793 ) -> Result<(Box<dyn State>, Status)>;
794
795 fn as_any(&self) -> &dyn Any;
797}
798
799#[derive(Debug, Serialize, Deserialize)]
801pub struct RegionMigrationDataOwned {
802 persistent_ctx: PersistentContext,
803 state: Box<dyn State>,
804}
805
806#[derive(Debug, Serialize)]
808pub struct RegionMigrationData<'a> {
809 persistent_ctx: &'a PersistentContext,
810 state: &'a dyn State,
811}
812
813pub(crate) struct RegionMigrationProcedure {
814 state: Box<dyn State>,
815 context: Context,
816 _guards: Vec<RegionMigrationProcedureGuard>,
817}
818
819impl RegionMigrationProcedure {
820 const TYPE_NAME: &'static str = "metasrv-procedure::RegionMigration";
821
822 pub fn new(
823 persistent_context: PersistentContext,
824 context_factory: impl ContextFactory,
825 guards: Vec<RegionMigrationProcedureGuard>,
826 ) -> Self {
827 let state = Box::new(RegionMigrationStart {});
828 Self::new_inner(state, persistent_context, context_factory, guards)
829 }
830
831 fn new_inner(
832 state: Box<dyn State>,
833 persistent_context: PersistentContext,
834 context_factory: impl ContextFactory,
835 guards: Vec<RegionMigrationProcedureGuard>,
836 ) -> Self {
837 Self {
838 state,
839 context: context_factory.new_context(persistent_context),
840 _guards: guards,
841 }
842 }
843
844 fn from_json(
845 json: &str,
846 context_factory: impl ContextFactory,
847 tracker: RegionMigrationProcedureTracker,
848 ) -> ProcedureResult<Self> {
849 let RegionMigrationDataOwned {
850 persistent_ctx,
851 state,
852 } = serde_json::from_str(json).context(FromJsonSnafu)?;
853 let guards = persistent_ctx
854 .region_ids
855 .iter()
856 .flat_map(|region_id| {
857 tracker.insert_running_procedure(&RegionMigrationProcedureTask {
858 region_id: *region_id,
859 from_peer: persistent_ctx.from_peer.clone(),
860 to_peer: persistent_ctx.to_peer.clone(),
861 timeout: persistent_ctx.timeout,
862 trigger_reason: persistent_ctx.trigger_reason,
863 })
864 })
865 .collect::<Vec<_>>();
866
867 let context = context_factory.new_context(persistent_ctx);
868
869 Ok(Self {
870 state,
871 context,
872 _guards: guards,
873 })
874 }
875
876 async fn rollback_inner(&mut self, procedure_ctx: &ProcedureContext) -> Result<()> {
877 let _timer = METRIC_META_REGION_MIGRATION_EXECUTE
878 .with_label_values(&["rollback"])
879 .start_timer();
880 let ctx = &self.context;
881 let to_peer_id = ctx.persistent_ctx.to_peer.id;
882 let table_regions = ctx.persistent_ctx.table_regions();
883 let mut switched_regions = Vec::new();
885 for (table_id, regions) in table_regions {
886 let table_lock = TableLock::Write(table_id).into();
887 let _guard = procedure_ctx.provider.acquire_lock(&table_lock).await;
888 let table_route = match ctx.get_table_route_value(table_id).await {
889 Ok(table_route) => table_route,
890 Err(error::Error::TableRouteNotFound { .. }) => continue,
892 Err(err) => return Err(err),
893 };
894 let region_routes = table_route.region_routes().unwrap();
895 switched_regions.extend(
896 region_routes
897 .iter()
898 .filter(|route| regions.contains(&route.region.id))
899 .filter(|route| {
900 route.leader_peer.as_ref().map(|peer| peer.id) == Some(to_peer_id)
901 })
902 .map(|route| route.region.id),
903 );
904 let downgraded = region_routes
905 .iter()
906 .filter(|route| regions.contains(&route.region.id))
907 .any(|route| route.is_leader_downgrading());
908 if downgraded {
909 info!(
910 "Rollbacking downgraded region leader table route, table: {table_id}, regions: {regions:?}"
911 );
912 let table_metadata_manager = &ctx.table_metadata_manager;
913 table_metadata_manager
914 .update_leader_region_status(table_id, &table_route, |route| {
915 if regions.contains(&route.region.id) {
916 Some(None)
917 } else {
918 None
919 }
920 })
921 .await
922 .context(error::TableMetadataManagerSnafu)
923 .map_err(BoxedError::new)
924 .with_context(|_| error::RetryLaterWithSourceSnafu {
925 reason: format!("Failed to update the table route during the rollback downgraded leader region: {regions:?}"),
926 })?;
927 }
928 }
929 self.context
930 .deregister_failure_detectors_for_candidate_regions()
931 .await;
932 let unswitched_regions = ctx
934 .persistent_ctx
935 .region_ids
936 .iter()
937 .filter(|region_id| !switched_regions.contains(region_id))
938 .copied()
939 .collect::<Vec<_>>();
940 self.context
941 .register_failure_detectors(&unswitched_regions)
942 .await;
943 if !switched_regions.is_empty() {
946 self.context
947 .reset_failure_detectors(&switched_regions)
948 .await;
949 }
950
951 Ok(())
952 }
953}
954
955#[async_trait::async_trait]
956impl Procedure for RegionMigrationProcedure {
957 fn type_name(&self) -> &str {
958 Self::TYPE_NAME
959 }
960
961 async fn rollback(&mut self, ctx: &ProcedureContext) -> ProcedureResult<()> {
962 self.rollback_inner(ctx)
963 .await
964 .map_err(ProcedureError::external)
965 }
966
967 fn rollback_supported(&self) -> bool {
968 true
969 }
970
971 #[tracing::instrument(skip_all, fields(
972 state = %self.state.name(),
973 region_count = self.context.persistent_ctx.region_ids.len(),
974 from_peer = self.context.persistent_ctx.from_peer.id,
975 to_peer = self.context.persistent_ctx.to_peer.id,
976 ))]
977 async fn execute(&mut self, ctx: &ProcedureContext) -> ProcedureResult<Status> {
978 let state = &mut self.state;
979
980 let name = state.name();
981 let _timer = METRIC_META_REGION_MIGRATION_EXECUTE
982 .with_label_values(&[name])
983 .start_timer();
984 match state.next(&mut self.context, ctx).await {
985 Ok((next, status)) => {
986 *state = next;
987 Ok(status)
988 }
989 Err(e) => {
990 if e.is_retryable() {
991 METRIC_META_REGION_MIGRATION_ERROR
992 .with_label_values(&[name, "retryable"])
993 .inc();
994 Err(ProcedureError::retry_later(e))
995 } else {
996 self.context.volatile_ctx.opening_region_guards.clear();
998 self.context
999 .deregister_failure_detectors_for_candidate_regions()
1000 .await;
1001 error!(
1002 e;
1003 "Region migration procedure failed, regions: {:?}, from_peer: {}, to_peer: {}, {}",
1004 self.context.persistent_ctx.region_ids,
1005 self.context.persistent_ctx.from_peer,
1006 self.context.persistent_ctx.to_peer,
1007 self.context.volatile_ctx.metrics,
1008 );
1009 METRIC_META_REGION_MIGRATION_ERROR
1010 .with_label_values(&[name, "external"])
1011 .inc();
1012 Err(ProcedureError::external(e))
1013 }
1014 }
1015 }
1016 }
1017
1018 fn dump(&self) -> ProcedureResult<String> {
1019 let data = RegionMigrationData {
1020 state: self.state.as_ref(),
1021 persistent_ctx: &self.context.persistent_ctx,
1022 };
1023 serde_json::to_string(&data).context(ToJsonSnafu)
1024 }
1025
1026 fn lock_key(&self) -> LockKey {
1027 LockKey::new(self.context.persistent_ctx.lock_key())
1028 }
1029
1030 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
1031 if !ctx.event_type_filter.allows(REGION_MIGRATION_EVENT_TYPE) {
1032 return None;
1033 }
1034
1035 Some(Box::new(RegionMigrationEvent::from_persistent_ctx(
1036 &self.context.persistent_ctx,
1037 self.context
1038 .trigger_reason(ctx.event_context)
1039 .to_trigger_reason(),
1040 )))
1041 }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046 use std::assert_matches;
1047 use std::sync::Arc;
1048
1049 use common_meta::distributed_time_constants::default_distributed_time_constants;
1050 use common_meta::instruction::Instruction;
1051 use common_meta::key::test_utils::{new_test_table_info, new_test_table_info_with_name};
1052 use common_meta::rpc::router::{LeaderState, Region, RegionRoute};
1053
1054 use super::*;
1055 use crate::handler::HeartbeatMailbox;
1056 use crate::procedure::region_migration::open_candidate_region::OpenCandidateRegion;
1057 use crate::procedure::region_migration::test_util::*;
1058 use crate::procedure::test_util::{
1059 new_downgrade_region_reply, new_flush_region_reply_for_region, new_open_region_reply,
1060 new_upgrade_region_reply,
1061 };
1062 use crate::region::supervisor::{Event, RegionFailureDetectorControl};
1063 use crate::service::mailbox::Channel;
1064
1065 fn new_persistent_context() -> PersistentContext {
1066 test_util::new_persistent_context(1, 2, RegionId::new(1024, 1))
1067 }
1068
1069 #[tokio::test]
1070 async fn test_rollback_registers_detectors_for_switched_regions() {
1071 let env = TestingEnv::new();
1072 let switched = RegionId::new(1024, 1);
1076 let unswitched = RegionId::new(1025, 1);
1077 let dropped = RegionId::new(1026, 1);
1078 for (region_id, leader) in [(switched, 2), (unswitched, 1)] {
1079 let table_id = region_id.table_id();
1080 let table_info = new_test_table_info_with_name(table_id, &format!("table_{table_id}"));
1081 let region_routes = vec![RegionRoute {
1082 region: Region::new_test(region_id),
1083 leader_peer: Some(Peer::empty(leader)),
1084 leader_state: (region_id == unswitched).then_some(LeaderState::Downgrading),
1085 ..Default::default()
1086 }];
1087 env.create_physical_table_metadata(table_info, region_routes)
1088 .await;
1089 }
1090 let mut persistent_ctx = new_persistent_context();
1091 persistent_ctx.region_ids = vec![switched, unswitched, dropped];
1092 let (tx, mut rx) = tokio::sync::mpsc::channel(8);
1093 let mut context_factory = env.context_factory();
1094 context_factory.region_failure_detector_controller =
1095 Arc::new(RegionFailureDetectorControl::new(tx));
1096 let mut procedure = RegionMigrationProcedure::new(persistent_ctx, context_factory, vec![]);
1097
1098 procedure
1099 .rollback_inner(&new_procedure_context())
1100 .await
1101 .unwrap();
1102
1103 let target = [switched, unswitched, dropped].map(|region_id| (2, region_id));
1106 let source = [unswitched, dropped].map(|region_id| (1, region_id));
1107 assert_matches!(rx.try_recv(), Ok(Event::DeregisterFailureDetectors(r)) if r == target);
1108 assert_matches!(rx.try_recv(), Ok(Event::RegisterFailureDetectors(r)) if r == source);
1109 assert_matches!(rx.try_recv(), Ok(Event::ResetFailureDetectors(r)) if r == [(2, switched)]);
1110 assert!(rx.try_recv().is_err());
1111 let table_route = env
1112 .table_metadata_manager()
1113 .table_route_manager()
1114 .table_route_storage()
1115 .get(unswitched.table_id())
1116 .await
1117 .unwrap()
1118 .unwrap();
1119 assert_eq!(table_route.region_routes().unwrap()[0].leader_state, None);
1120 }
1121
1122 #[test]
1123 fn test_lock_key() {
1124 let persistent_context = new_persistent_context();
1125 let expected_keys = persistent_context.lock_key();
1126
1127 let env = TestingEnv::new();
1128 let context = env.context_factory();
1129
1130 let procedure = RegionMigrationProcedure::new(persistent_context, context, vec![]);
1131
1132 let key = procedure.lock_key();
1133 let keys = key.keys_to_lock().cloned().collect::<Vec<_>>();
1134
1135 for key in expected_keys {
1136 assert!(keys.contains(&key));
1137 }
1138 }
1139
1140 #[test]
1141 fn test_data_serialization() {
1142 let persistent_context = new_persistent_context();
1143
1144 let env = TestingEnv::new();
1145 let context = env.context_factory();
1146
1147 let procedure = RegionMigrationProcedure::new(persistent_context, context, vec![]);
1148
1149 let serialized = procedure.dump().unwrap();
1150 let expected = r#"{"persistent_ctx":{"catalog_and_schema":[["greptime","public"]],"from_peer":{"id":1,"addr":""},"to_peer":{"id":2,"addr":""},"region_ids":[4398046511105],"timeout":"10s","trigger_reason":"Unknown"},"state":{"region_migration_state":"RegionMigrationStart"}}"#;
1151 assert_eq!(expected, serialized);
1152 }
1153
1154 #[test]
1155 fn test_event_hook_uses_current_persistent_context() {
1156 let env = TestingEnv::new();
1157 let procedure =
1158 RegionMigrationProcedure::new(new_persistent_context(), env.context_factory(), vec![]);
1159 let state = common_procedure::ProcedureState::Running;
1160 let triggers = [
1161 common_procedure::EventTrigger::Recovered,
1162 common_procedure::EventTrigger::Retrying {
1163 phase: common_procedure::RetryPhase::Execute,
1164 attempt: 1,
1165 },
1166 common_procedure::EventTrigger::RollingBack,
1167 common_procedure::EventTrigger::Succeeded,
1168 common_procedure::EventTrigger::Failed,
1169 common_procedure::EventTrigger::Poisoned,
1170 ];
1171
1172 for trigger in triggers {
1173 let event = procedure
1174 .event(&EventContext {
1175 procedure_id: common_procedure::ProcedureId::random(),
1176 lifecycle_state: &state,
1177 trigger,
1178 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
1179 event_context: None,
1180 })
1181 .unwrap();
1182 assert_eq!(event.event_type(), "region_migration");
1183 assert_eq!(event.extra_rows().unwrap().len(), 1);
1184 }
1185 }
1186
1187 #[test]
1188 fn test_backward_compatibility() {
1189 let persistent_ctx = PersistentContext {
1190 #[allow(deprecated)]
1191 catalog: Some("greptime".into()),
1192 #[allow(deprecated)]
1193 schema: Some("public".into()),
1194 catalog_and_schema: vec![],
1195 from_peer: Peer::empty(1),
1196 to_peer: Peer::empty(2),
1197 region_ids: vec![RegionId::new(1024, 1)],
1198 timeout: Duration::from_secs(10),
1199 trigger_reason: RegionMigrationTriggerReason::Unknown,
1200 };
1201 let serialized = r#"{"catalog":"greptime","schema":"public","from_peer":{"id":1,"addr":""},"to_peer":{"id":2,"addr":""},"region_id":4398046511105}"#;
1203 let deserialized: PersistentContext = serde_json::from_str(serialized).unwrap();
1204
1205 assert_eq!(persistent_ctx, deserialized);
1206 }
1207
1208 #[test]
1209 fn test_legacy_trigger_reason_survives_recovery_and_repersistence() {
1210 let serialized = r#"{"persistent_ctx":{"catalog":"greptime","schema":"public","from_peer":{"id":1,"addr":""},"to_peer":{"id":2,"addr":""},"region_id":4398046511105,"trigger_reason":"Failover"},"state":{"region_migration_state":"RegionMigrationStart"}}"#;
1211 let env = TestingEnv::new();
1212 let procedure = RegionMigrationProcedure::from_json(
1213 serialized,
1214 env.context_factory(),
1215 RegionMigrationProcedureTracker::default(),
1216 )
1217 .unwrap();
1218 assert_eq!(
1219 procedure.context.persistent_ctx.trigger_reason,
1220 RegionMigrationTriggerReason::Failover
1221 );
1222 let repersisted = procedure.dump().unwrap();
1223 assert_eq!(
1224 serde_json::from_str::<serde_json::Value>(&repersisted).unwrap()["persistent_ctx"]["trigger_reason"],
1225 "Failover"
1226 );
1227
1228 let recovered = RegionMigrationProcedure::from_json(
1229 &repersisted,
1230 env.context_factory(),
1231 RegionMigrationProcedureTracker::default(),
1232 )
1233 .unwrap();
1234 assert_eq!(
1235 recovered.context.trigger_reason(None),
1236 RegionMigrationTriggerReason::Failover
1237 );
1238 let event_context = PersistentEventContext::new(TriggerReason::AutoRebalance);
1239 assert_eq!(
1240 recovered.context.trigger_reason(Some(&event_context)),
1241 RegionMigrationTriggerReason::AutoRebalance
1242 );
1243
1244 let state = common_procedure::ProcedureState::Running;
1245 let event = recovered
1246 .event(&EventContext {
1247 procedure_id: common_procedure::ProcedureId::random(),
1248 lifecycle_state: &state,
1249 trigger: common_procedure::EventTrigger::Recovered,
1250 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
1251 event_context: None,
1252 })
1253 .unwrap();
1254 assert_eq!(
1255 event.extra_rows().unwrap()[0].values[3].value_data,
1256 Some(api::v1::value::ValueData::StringValue(
1257 "Failover".to_string()
1258 ))
1259 );
1260 }
1261
1262 #[test]
1263 fn test_migration_reason_uses_event_context() {
1264 let env = TestingEnv::new();
1265 let context = env.context_factory().new_context(new_persistent_context());
1266 let event_context = PersistentEventContext::new(TriggerReason::RegionFailover);
1267
1268 assert_eq!(
1269 context.trigger_reason(Some(&event_context)),
1270 RegionMigrationTriggerReason::Failover
1271 );
1272
1273 let procedure =
1274 RegionMigrationProcedure::new(new_persistent_context(), env.context_factory(), vec![]);
1275 let state = common_procedure::ProcedureState::Running;
1276 let event = procedure
1277 .event(&EventContext {
1278 procedure_id: common_procedure::ProcedureId::random(),
1279 lifecycle_state: &state,
1280 trigger: common_procedure::EventTrigger::Submitted,
1281 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
1282 event_context: Some(&event_context),
1283 })
1284 .unwrap();
1285 assert_eq!(
1286 event.extra_rows().unwrap()[0].values[3].value_data,
1287 Some(api::v1::value::ValueData::StringValue(
1288 "Failover".to_string()
1289 ))
1290 );
1291 }
1292
1293 #[derive(Debug, Serialize, Deserialize, Default)]
1294 pub struct MockState;
1295
1296 #[async_trait::async_trait]
1297 #[typetag::serde]
1298 impl State for MockState {
1299 async fn next(
1300 &mut self,
1301 _ctx: &mut Context,
1302 _procedure_ctx: &ProcedureContext,
1303 ) -> Result<(Box<dyn State>, Status)> {
1304 Ok((Box::new(MockState), Status::done()))
1305 }
1306
1307 fn as_any(&self) -> &dyn Any {
1308 self
1309 }
1310 }
1311
1312 #[tokio::test]
1313 async fn test_execution_after_deserialized() {
1314 let env = TestingEnv::new();
1315
1316 fn new_mock_procedure(env: &TestingEnv) -> RegionMigrationProcedure {
1317 let persistent_context = new_persistent_context();
1318 let context_factory = env.context_factory();
1319 let state = Box::<MockState>::default();
1320 RegionMigrationProcedure::new_inner(state, persistent_context, context_factory, vec![])
1321 }
1322
1323 let ctx = TestingEnv::procedure_context();
1324 let mut procedure = new_mock_procedure(&env);
1325 let mut status = None;
1326 for _ in 0..3 {
1327 status = Some(procedure.execute(&ctx).await.unwrap());
1328 }
1329 assert!(status.unwrap().is_done());
1330
1331 let ctx = TestingEnv::procedure_context();
1332 let mut procedure = new_mock_procedure(&env);
1333
1334 status = Some(procedure.execute(&ctx).await.unwrap());
1335
1336 let serialized = procedure.dump().unwrap();
1337
1338 let context_factory = env.context_factory();
1339 let tracker = env.tracker();
1340 let mut procedure =
1341 RegionMigrationProcedure::from_json(&serialized, context_factory, tracker.clone())
1342 .unwrap();
1343 for region_id in &procedure.context.persistent_ctx.region_ids {
1344 assert!(tracker.contains(*region_id));
1345 }
1346
1347 for _ in 1..3 {
1348 status = Some(procedure.execute(&ctx).await.unwrap());
1349 }
1350 assert!(status.unwrap().is_done());
1351 }
1352
1353 #[tokio::test]
1354 async fn test_broadcast_invalidate_table_cache() {
1355 let mut env = TestingEnv::new();
1356 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1357 let ctx = env.context_factory().new_context(persistent_context);
1358 let mailbox_ctx = env.mailbox_context();
1359
1360 ctx.invalidate_table_cache().await.unwrap();
1362
1363 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
1364
1365 mailbox_ctx
1366 .insert_heartbeat_response_receiver(Channel::Frontend(1), tx)
1367 .await;
1368
1369 ctx.invalidate_table_cache().await.unwrap();
1370
1371 let resp = rx.recv().await.unwrap().unwrap();
1372 let msg = resp.mailbox_message.unwrap();
1373
1374 let instruction = HeartbeatMailbox::json_instruction(&msg).unwrap();
1375 assert_eq!(
1376 instruction,
1377 Instruction::InvalidateCaches(vec![CacheIdent::TableId(1024)])
1378 );
1379 }
1380
1381 fn procedure_flow_steps(from_peer_id: u64, to_peer_id: u64) -> Vec<Step> {
1382 vec![
1383 Step::next(
1385 "Should be the open candidate region",
1386 None,
1387 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1388 ),
1389 Step::next(
1391 "Should be the flush leader region",
1392 Some(mock_datanode_reply(
1393 to_peer_id,
1394 Arc::new(|id| Ok(new_open_region_reply(id, true, None))),
1395 )),
1396 Assertion::simple(assert_flush_leader_region, assert_no_persist),
1397 ),
1398 Step::next(
1400 "Should be the flush leader region",
1401 Some(mock_datanode_reply(
1402 from_peer_id,
1403 Arc::new(move |id| {
1404 Ok(new_flush_region_reply_for_region(
1405 id,
1406 RegionId::new(1024, 1),
1407 true,
1408 None,
1409 ))
1410 }),
1411 )),
1412 Assertion::simple(assert_update_metadata_downgrade, assert_no_persist),
1413 ),
1414 Step::next(
1416 "Should be the downgrade leader region",
1417 None,
1418 Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1419 ),
1420 Step::next(
1422 "Should be the upgrade candidate region",
1423 Some(mock_datanode_reply(
1424 from_peer_id,
1425 Arc::new(|id| Ok(new_downgrade_region_reply(id, None, true, None))),
1426 )),
1427 Assertion::simple(assert_upgrade_candidate_region, assert_no_persist),
1428 ),
1429 Step::next(
1431 "Should be the update metadata for upgrading",
1432 Some(mock_datanode_reply(
1433 to_peer_id,
1434 Arc::new(|id| Ok(new_upgrade_region_reply(id, true, true, None))),
1435 )),
1436 Assertion::simple(assert_update_metadata_upgrade, assert_no_persist),
1437 ),
1438 Step::next(
1440 "Should be the close downgraded region",
1441 None,
1442 Assertion::simple(assert_close_downgraded_region, assert_no_persist),
1443 ),
1444 Step::next(
1446 "Should be the region migration end",
1447 None,
1448 Assertion::simple(assert_region_migration_end, assert_done),
1449 ),
1450 Step::next(
1452 "Should be the region migration end again",
1453 None,
1454 Assertion::simple(assert_region_migration_end, assert_done),
1455 ),
1456 ]
1457 }
1458
1459 #[tokio::test]
1460 async fn test_procedure_flow() {
1461 common_telemetry::init_default_ut_logging();
1462
1463 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1464 let state = Box::new(RegionMigrationStart);
1465
1466 let from_peer_id = persistent_context.from_peer.id;
1468 let to_peer_id = persistent_context.to_peer.id;
1469 let from_peer = persistent_context.from_peer.clone();
1470 let to_peer = persistent_context.to_peer.clone();
1471 let region_id = persistent_context.region_ids[0];
1472 let table_info = new_test_table_info(1024);
1473 let region_routes = vec![RegionRoute {
1474 region: Region::new_test(region_id),
1475 leader_peer: Some(from_peer),
1476 follower_peers: vec![to_peer],
1477 ..Default::default()
1478 }];
1479
1480 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1481 suite.init_table_metadata(table_info, region_routes).await;
1482
1483 let steps = procedure_flow_steps(from_peer_id, to_peer_id);
1484 let timer = Instant::now();
1485
1486 let runner = ProcedureMigrationSuiteRunner::new(suite)
1488 .steps(steps)
1489 .run_once()
1490 .await;
1491
1492 let region_lease = default_distributed_time_constants().region_lease.as_secs();
1493
1494 assert!(timer.elapsed().as_secs() < region_lease / 2);
1496
1497 runner.suite.verify_table_metadata().await;
1498 }
1499
1500 #[tokio::test]
1501 async fn test_procedure_flow_open_candidate_region_retryable_error() {
1502 common_telemetry::init_default_ut_logging();
1503
1504 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1505 let state = Box::new(RegionMigrationStart);
1506
1507 let to_peer_id = persistent_context.to_peer.id;
1509 let from_peer = persistent_context.from_peer.clone();
1510 let region_id = persistent_context.region_ids[0];
1511 let table_info = new_test_table_info(1024);
1512 let region_routes = vec![RegionRoute {
1513 region: Region::new_test(region_id),
1514 leader_peer: Some(from_peer),
1515 follower_peers: vec![],
1516 ..Default::default()
1517 }];
1518
1519 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1520 suite.init_table_metadata(table_info, region_routes).await;
1521
1522 let steps = vec![
1523 Step::next(
1525 "Should be the open candidate region",
1526 None,
1527 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1528 ),
1529 Step::next(
1531 "Should be throwing a non-retry error",
1532 Some(mock_datanode_reply(
1533 to_peer_id,
1534 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1535 )),
1536 Assertion::error(|error| assert!(error.is_retryable())),
1537 ),
1538 Step::next(
1540 "Should be throwing a non-retry error again",
1541 Some(mock_datanode_reply(
1542 to_peer_id,
1543 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1544 )),
1545 Assertion::error(|error| assert!(error.is_retryable())),
1546 ),
1547 ];
1548
1549 let setup_to_latest_persisted_state = Step::setup(
1550 "Sets state to UpdateMetadata::Downgrade",
1551 merge_before_test_fn(vec![
1552 setup_state(Arc::new(|| Box::new(OpenCandidateRegion))),
1553 Arc::new(reset_volatile_ctx),
1554 ]),
1555 );
1556
1557 let steps = [
1558 steps.clone(),
1559 vec![setup_to_latest_persisted_state.clone()],
1561 steps.clone()[1..].to_vec(),
1562 vec![setup_to_latest_persisted_state],
1563 steps.clone()[1..].to_vec(),
1564 ]
1565 .concat();
1566
1567 let runner = ProcedureMigrationSuiteRunner::new(suite)
1569 .steps(steps.clone())
1570 .run_once()
1571 .await;
1572
1573 let table_routes_version = runner
1574 .env()
1575 .table_metadata_manager()
1576 .table_route_manager()
1577 .table_route_storage()
1578 .get(region_id.table_id())
1579 .await
1580 .unwrap()
1581 .unwrap()
1582 .version();
1583 assert_eq!(table_routes_version.unwrap(), 0);
1585 }
1586
1587 #[tokio::test]
1588 async fn test_procedure_flow_upgrade_candidate_with_retry_and_failed() {
1589 common_telemetry::init_default_ut_logging();
1590
1591 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1592 let state = Box::new(RegionMigrationStart);
1593
1594 let from_peer_id = persistent_context.from_peer.id;
1596 let to_peer_id = persistent_context.to_peer.id;
1597 let from_peer = persistent_context.from_peer.clone();
1598 let region_id = persistent_context.region_ids[0];
1599 let table_info = new_test_table_info(1024);
1600 let region_routes = vec![RegionRoute {
1601 region: Region::new_test(region_id),
1602 leader_peer: Some(from_peer),
1603 follower_peers: vec![],
1604 ..Default::default()
1605 }];
1606
1607 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1608 suite.init_table_metadata(table_info, region_routes).await;
1609
1610 let steps = vec![
1611 Step::next(
1613 "Should be the open candidate region",
1614 None,
1615 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1616 ),
1617 Step::next(
1619 "Should be the flush leader region",
1620 Some(mock_datanode_reply(
1621 to_peer_id,
1622 Arc::new(|id| Ok(new_open_region_reply(id, true, None))),
1623 )),
1624 Assertion::simple(assert_flush_leader_region, assert_no_persist),
1625 ),
1626 Step::next(
1628 "Should be the flush leader region",
1629 Some(mock_datanode_reply(
1630 from_peer_id,
1631 Arc::new(move |id| {
1632 Ok(new_flush_region_reply_for_region(
1633 id,
1634 RegionId::new(1024, 1),
1635 true,
1636 None,
1637 ))
1638 }),
1639 )),
1640 Assertion::simple(assert_update_metadata_downgrade, assert_no_persist),
1641 ),
1642 Step::next(
1644 "Should be the downgrade leader region",
1645 None,
1646 Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1647 ),
1648 Step::next(
1650 "Should be the upgrade candidate region",
1651 Some(mock_datanode_reply(
1652 from_peer_id,
1653 Arc::new(|id| Ok(new_downgrade_region_reply(id, None, true, None))),
1654 )),
1655 Assertion::simple(assert_upgrade_candidate_region, assert_no_persist),
1656 ),
1657 Step::next(
1659 "Should be the rollback metadata",
1660 Some(mock_datanode_reply(
1661 to_peer_id,
1662 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1663 )),
1664 Assertion::simple(assert_update_metadata_rollback, assert_no_persist),
1665 ),
1666 Step::next(
1668 "Should be the region migration abort",
1669 None,
1670 Assertion::simple(assert_region_migration_abort, assert_no_persist),
1671 ),
1672 Step::next(
1674 "Should throw an error",
1675 None,
1676 Assertion::error(|error| {
1677 assert!(!error.is_retryable());
1678 assert_matches!(error, error::Error::MigrationAbort { .. });
1679 }),
1680 ),
1681 ];
1682
1683 let setup_to_latest_persisted_state = Step::setup(
1684 "Sets state to OpenCandidateRegion",
1685 merge_before_test_fn(vec![
1686 setup_state(Arc::new(|| Box::new(OpenCandidateRegion))),
1687 Arc::new(reset_volatile_ctx),
1688 ]),
1689 );
1690
1691 let steps = [
1692 steps.clone(),
1693 vec![setup_to_latest_persisted_state.clone()],
1694 steps.clone()[1..].to_vec(),
1695 vec![setup_to_latest_persisted_state],
1696 steps.clone()[1..].to_vec(),
1697 ]
1698 .concat();
1699
1700 ProcedureMigrationSuiteRunner::new(suite)
1702 .steps(steps.clone())
1703 .run_once()
1704 .await;
1705 }
1706
1707 #[tokio::test]
1708 async fn test_procedure_flow_upgrade_candidate_with_retry() {
1709 common_telemetry::init_default_ut_logging();
1710
1711 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1712 let state = Box::new(RegionMigrationStart);
1713
1714 let to_peer_id = persistent_context.to_peer.id;
1716 let from_peer_id = persistent_context.from_peer.id;
1717 let from_peer = persistent_context.from_peer.clone();
1718 let region_id = persistent_context.region_ids[0];
1719 let table_info = new_test_table_info(1024);
1720 let region_routes = vec![RegionRoute {
1721 region: Region::new_test(region_id),
1722 leader_peer: Some(from_peer),
1723 follower_peers: vec![],
1724 ..Default::default()
1725 }];
1726
1727 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1728 suite.init_table_metadata(table_info, region_routes).await;
1729
1730 let steps = vec![
1731 Step::next(
1733 "Should be the open candidate region",
1734 None,
1735 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1736 ),
1737 Step::next(
1739 "Should be throwing a retryable error",
1740 Some(mock_datanode_reply(
1741 to_peer_id,
1742 Arc::new(|id| {
1743 Ok(new_open_region_reply(
1744 id,
1745 false,
1746 Some("mock retryable open region error".to_string()),
1747 ))
1748 }),
1749 )),
1750 Assertion::error(|error| assert!(error.is_retryable(), "err: {error:?}")),
1751 ),
1752 Step::next(
1754 "Should be the update metadata for downgrading",
1755 Some(mock_datanode_reply(
1756 to_peer_id,
1757 Arc::new(|id| Ok(new_open_region_reply(id, true, None))),
1758 )),
1759 Assertion::simple(assert_flush_leader_region, assert_no_persist),
1760 ),
1761 Step::next(
1763 "Should be the flush leader region",
1764 Some(mock_datanode_reply(
1765 from_peer_id,
1766 Arc::new(move |id| {
1767 Ok(new_flush_region_reply_for_region(id, region_id, true, None))
1768 }),
1769 )),
1770 Assertion::simple(assert_update_metadata_downgrade, assert_no_persist),
1771 ),
1772 Step::next(
1774 "Should be the downgrade leader region",
1775 None,
1776 Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1777 ),
1778 Step::next(
1780 "Should be the upgrade candidate region",
1781 Some(mock_datanode_reply(
1782 from_peer_id,
1783 merge_mailbox_messages(vec![
1784 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1785 Arc::new(|id| Ok(new_downgrade_region_reply(id, None, true, None))),
1786 ]),
1787 )),
1788 Assertion::simple(assert_upgrade_candidate_region, assert_no_persist),
1789 ),
1790 Step::next(
1792 "Should be the update metadata for upgrading",
1793 Some(mock_datanode_reply(
1794 to_peer_id,
1795 merge_mailbox_messages(vec![
1796 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1797 Arc::new(|id| Ok(new_upgrade_region_reply(id, true, true, None))),
1798 ]),
1799 )),
1800 Assertion::simple(assert_update_metadata_upgrade, assert_no_persist),
1801 ),
1802 Step::next(
1804 "Should be the close downgraded region",
1805 None,
1806 Assertion::simple(assert_close_downgraded_region, assert_no_persist),
1807 ),
1808 Step::next(
1810 "Should be the region migration end",
1811 None,
1812 Assertion::simple(assert_region_migration_end, assert_done),
1813 ),
1814 Step::next(
1816 "Should be the region migration end again",
1817 None,
1818 Assertion::simple(assert_region_migration_end, assert_done),
1819 ),
1820 Step::setup(
1822 "Sets state to RegionMigrationStart",
1823 merge_before_test_fn(vec![
1824 setup_state(Arc::new(|| Box::new(RegionMigrationStart))),
1825 Arc::new(reset_volatile_ctx),
1826 ]),
1827 ),
1828 Step::next(
1832 "Should be the region migration end(has been migrated)",
1833 None,
1834 Assertion::simple(assert_region_migration_end, assert_done),
1835 ),
1836 ];
1837
1838 let steps = [steps.clone()].concat();
1839 let timer = Instant::now();
1840
1841 let runner = ProcedureMigrationSuiteRunner::new(suite)
1843 .steps(steps.clone())
1844 .run_once()
1845 .await;
1846
1847 let region_lease = default_distributed_time_constants().region_lease.as_secs();
1848 assert!(timer.elapsed().as_secs() < region_lease);
1850 runner.suite.verify_table_metadata().await;
1851 }
1852}