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) {
638 let datanode_id = self.persistent_ctx.from_peer.id;
639 let region_ids = &self.persistent_ctx.region_ids;
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_for_candidate_regions(&self) {
655 let datanode_id = self.persistent_ctx.to_peer.id;
656 let region_ids = &self.persistent_ctx.region_ids;
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 after migration success 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 table_regions = ctx.persistent_ctx.table_regions();
882 for (table_id, regions) in table_regions {
883 let table_lock = TableLock::Write(table_id).into();
884 let _guard = procedure_ctx.provider.acquire_lock(&table_lock).await;
885 let table_route = ctx.get_table_route_value(table_id).await?;
886 let region_routes = table_route.region_routes().unwrap();
887 let downgraded = region_routes
888 .iter()
889 .filter(|route| regions.contains(&route.region.id))
890 .any(|route| route.is_leader_downgrading());
891 if downgraded {
892 info!(
893 "Rollbacking downgraded region leader table route, table: {table_id}, regions: {regions:?}"
894 );
895 let table_metadata_manager = &ctx.table_metadata_manager;
896 table_metadata_manager
897 .update_leader_region_status(table_id, &table_route, |route| {
898 if regions.contains(&route.region.id) {
899 Some(None)
900 } else {
901 None
902 }
903 })
904 .await
905 .context(error::TableMetadataManagerSnafu)
906 .map_err(BoxedError::new)
907 .with_context(|_| error::RetryLaterWithSourceSnafu {
908 reason: format!("Failed to update the table route during the rollback downgraded leader region: {regions:?}"),
909 })?;
910 }
911 }
912 self.context
913 .deregister_failure_detectors_for_candidate_regions()
914 .await;
915 self.context.register_failure_detectors().await;
916
917 Ok(())
918 }
919}
920
921#[async_trait::async_trait]
922impl Procedure for RegionMigrationProcedure {
923 fn type_name(&self) -> &str {
924 Self::TYPE_NAME
925 }
926
927 async fn rollback(&mut self, ctx: &ProcedureContext) -> ProcedureResult<()> {
928 self.rollback_inner(ctx)
929 .await
930 .map_err(ProcedureError::external)
931 }
932
933 fn rollback_supported(&self) -> bool {
934 true
935 }
936
937 #[tracing::instrument(skip_all, fields(
938 state = %self.state.name(),
939 region_count = self.context.persistent_ctx.region_ids.len(),
940 from_peer = self.context.persistent_ctx.from_peer.id,
941 to_peer = self.context.persistent_ctx.to_peer.id,
942 ))]
943 async fn execute(&mut self, ctx: &ProcedureContext) -> ProcedureResult<Status> {
944 let state = &mut self.state;
945
946 let name = state.name();
947 let _timer = METRIC_META_REGION_MIGRATION_EXECUTE
948 .with_label_values(&[name])
949 .start_timer();
950 match state.next(&mut self.context, ctx).await {
951 Ok((next, status)) => {
952 *state = next;
953 Ok(status)
954 }
955 Err(e) => {
956 if e.is_retryable() {
957 METRIC_META_REGION_MIGRATION_ERROR
958 .with_label_values(&[name, "retryable"])
959 .inc();
960 Err(ProcedureError::retry_later(e))
961 } else {
962 self.context.volatile_ctx.opening_region_guards.clear();
964 self.context
965 .deregister_failure_detectors_for_candidate_regions()
966 .await;
967 error!(
968 e;
969 "Region migration procedure failed, regions: {:?}, from_peer: {}, to_peer: {}, {}",
970 self.context.persistent_ctx.region_ids,
971 self.context.persistent_ctx.from_peer,
972 self.context.persistent_ctx.to_peer,
973 self.context.volatile_ctx.metrics,
974 );
975 METRIC_META_REGION_MIGRATION_ERROR
976 .with_label_values(&[name, "external"])
977 .inc();
978 Err(ProcedureError::external(e))
979 }
980 }
981 }
982 }
983
984 fn dump(&self) -> ProcedureResult<String> {
985 let data = RegionMigrationData {
986 state: self.state.as_ref(),
987 persistent_ctx: &self.context.persistent_ctx,
988 };
989 serde_json::to_string(&data).context(ToJsonSnafu)
990 }
991
992 fn lock_key(&self) -> LockKey {
993 LockKey::new(self.context.persistent_ctx.lock_key())
994 }
995
996 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
997 if !ctx.event_type_filter.allows(REGION_MIGRATION_EVENT_TYPE) {
998 return None;
999 }
1000
1001 Some(Box::new(RegionMigrationEvent::from_persistent_ctx(
1002 &self.context.persistent_ctx,
1003 self.context
1004 .trigger_reason(ctx.event_context)
1005 .to_trigger_reason(),
1006 )))
1007 }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use std::assert_matches;
1013 use std::sync::Arc;
1014
1015 use common_meta::distributed_time_constants::default_distributed_time_constants;
1016 use common_meta::instruction::Instruction;
1017 use common_meta::key::test_utils::new_test_table_info;
1018 use common_meta::rpc::router::{Region, RegionRoute};
1019
1020 use super::*;
1021 use crate::handler::HeartbeatMailbox;
1022 use crate::procedure::region_migration::open_candidate_region::OpenCandidateRegion;
1023 use crate::procedure::region_migration::test_util::*;
1024 use crate::procedure::test_util::{
1025 new_downgrade_region_reply, new_flush_region_reply_for_region, new_open_region_reply,
1026 new_upgrade_region_reply,
1027 };
1028 use crate::service::mailbox::Channel;
1029
1030 fn new_persistent_context() -> PersistentContext {
1031 test_util::new_persistent_context(1, 2, RegionId::new(1024, 1))
1032 }
1033
1034 #[test]
1035 fn test_lock_key() {
1036 let persistent_context = new_persistent_context();
1037 let expected_keys = persistent_context.lock_key();
1038
1039 let env = TestingEnv::new();
1040 let context = env.context_factory();
1041
1042 let procedure = RegionMigrationProcedure::new(persistent_context, context, vec![]);
1043
1044 let key = procedure.lock_key();
1045 let keys = key.keys_to_lock().cloned().collect::<Vec<_>>();
1046
1047 for key in expected_keys {
1048 assert!(keys.contains(&key));
1049 }
1050 }
1051
1052 #[test]
1053 fn test_data_serialization() {
1054 let persistent_context = new_persistent_context();
1055
1056 let env = TestingEnv::new();
1057 let context = env.context_factory();
1058
1059 let procedure = RegionMigrationProcedure::new(persistent_context, context, vec![]);
1060
1061 let serialized = procedure.dump().unwrap();
1062 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"}}"#;
1063 assert_eq!(expected, serialized);
1064 }
1065
1066 #[test]
1067 fn test_event_hook_uses_current_persistent_context() {
1068 let env = TestingEnv::new();
1069 let procedure =
1070 RegionMigrationProcedure::new(new_persistent_context(), env.context_factory(), vec![]);
1071 let state = common_procedure::ProcedureState::Running;
1072 let triggers = [
1073 common_procedure::EventTrigger::Recovered,
1074 common_procedure::EventTrigger::Retrying {
1075 phase: common_procedure::RetryPhase::Execute,
1076 attempt: 1,
1077 },
1078 common_procedure::EventTrigger::RollingBack,
1079 common_procedure::EventTrigger::Succeeded,
1080 common_procedure::EventTrigger::Failed,
1081 common_procedure::EventTrigger::Poisoned,
1082 ];
1083
1084 for trigger in triggers {
1085 let event = procedure
1086 .event(&EventContext {
1087 procedure_id: common_procedure::ProcedureId::random(),
1088 lifecycle_state: &state,
1089 trigger,
1090 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
1091 event_context: None,
1092 })
1093 .unwrap();
1094 assert_eq!(event.event_type(), "region_migration");
1095 assert_eq!(event.extra_rows().unwrap().len(), 1);
1096 }
1097 }
1098
1099 #[test]
1100 fn test_backward_compatibility() {
1101 let persistent_ctx = PersistentContext {
1102 #[allow(deprecated)]
1103 catalog: Some("greptime".into()),
1104 #[allow(deprecated)]
1105 schema: Some("public".into()),
1106 catalog_and_schema: vec![],
1107 from_peer: Peer::empty(1),
1108 to_peer: Peer::empty(2),
1109 region_ids: vec![RegionId::new(1024, 1)],
1110 timeout: Duration::from_secs(10),
1111 trigger_reason: RegionMigrationTriggerReason::Unknown,
1112 };
1113 let serialized = r#"{"catalog":"greptime","schema":"public","from_peer":{"id":1,"addr":""},"to_peer":{"id":2,"addr":""},"region_id":4398046511105}"#;
1115 let deserialized: PersistentContext = serde_json::from_str(serialized).unwrap();
1116
1117 assert_eq!(persistent_ctx, deserialized);
1118 }
1119
1120 #[test]
1121 fn test_legacy_trigger_reason_survives_recovery_and_repersistence() {
1122 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"}}"#;
1123 let env = TestingEnv::new();
1124 let procedure = RegionMigrationProcedure::from_json(
1125 serialized,
1126 env.context_factory(),
1127 RegionMigrationProcedureTracker::default(),
1128 )
1129 .unwrap();
1130 assert_eq!(
1131 procedure.context.persistent_ctx.trigger_reason,
1132 RegionMigrationTriggerReason::Failover
1133 );
1134 let repersisted = procedure.dump().unwrap();
1135 assert_eq!(
1136 serde_json::from_str::<serde_json::Value>(&repersisted).unwrap()["persistent_ctx"]["trigger_reason"],
1137 "Failover"
1138 );
1139
1140 let recovered = RegionMigrationProcedure::from_json(
1141 &repersisted,
1142 env.context_factory(),
1143 RegionMigrationProcedureTracker::default(),
1144 )
1145 .unwrap();
1146 assert_eq!(
1147 recovered.context.trigger_reason(None),
1148 RegionMigrationTriggerReason::Failover
1149 );
1150 let event_context = PersistentEventContext::new(TriggerReason::AutoRebalance);
1151 assert_eq!(
1152 recovered.context.trigger_reason(Some(&event_context)),
1153 RegionMigrationTriggerReason::AutoRebalance
1154 );
1155
1156 let state = common_procedure::ProcedureState::Running;
1157 let event = recovered
1158 .event(&EventContext {
1159 procedure_id: common_procedure::ProcedureId::random(),
1160 lifecycle_state: &state,
1161 trigger: common_procedure::EventTrigger::Recovered,
1162 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
1163 event_context: None,
1164 })
1165 .unwrap();
1166 assert_eq!(
1167 event.extra_rows().unwrap()[0].values[3].value_data,
1168 Some(api::v1::value::ValueData::StringValue(
1169 "Failover".to_string()
1170 ))
1171 );
1172 }
1173
1174 #[test]
1175 fn test_migration_reason_uses_event_context() {
1176 let env = TestingEnv::new();
1177 let context = env.context_factory().new_context(new_persistent_context());
1178 let event_context = PersistentEventContext::new(TriggerReason::RegionFailover);
1179
1180 assert_eq!(
1181 context.trigger_reason(Some(&event_context)),
1182 RegionMigrationTriggerReason::Failover
1183 );
1184
1185 let procedure =
1186 RegionMigrationProcedure::new(new_persistent_context(), env.context_factory(), vec![]);
1187 let state = common_procedure::ProcedureState::Running;
1188 let event = procedure
1189 .event(&EventContext {
1190 procedure_id: common_procedure::ProcedureId::random(),
1191 lifecycle_state: &state,
1192 trigger: common_procedure::EventTrigger::Submitted,
1193 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
1194 event_context: Some(&event_context),
1195 })
1196 .unwrap();
1197 assert_eq!(
1198 event.extra_rows().unwrap()[0].values[3].value_data,
1199 Some(api::v1::value::ValueData::StringValue(
1200 "Failover".to_string()
1201 ))
1202 );
1203 }
1204
1205 #[derive(Debug, Serialize, Deserialize, Default)]
1206 pub struct MockState;
1207
1208 #[async_trait::async_trait]
1209 #[typetag::serde]
1210 impl State for MockState {
1211 async fn next(
1212 &mut self,
1213 _ctx: &mut Context,
1214 _procedure_ctx: &ProcedureContext,
1215 ) -> Result<(Box<dyn State>, Status)> {
1216 Ok((Box::new(MockState), Status::done()))
1217 }
1218
1219 fn as_any(&self) -> &dyn Any {
1220 self
1221 }
1222 }
1223
1224 #[tokio::test]
1225 async fn test_execution_after_deserialized() {
1226 let env = TestingEnv::new();
1227
1228 fn new_mock_procedure(env: &TestingEnv) -> RegionMigrationProcedure {
1229 let persistent_context = new_persistent_context();
1230 let context_factory = env.context_factory();
1231 let state = Box::<MockState>::default();
1232 RegionMigrationProcedure::new_inner(state, persistent_context, context_factory, vec![])
1233 }
1234
1235 let ctx = TestingEnv::procedure_context();
1236 let mut procedure = new_mock_procedure(&env);
1237 let mut status = None;
1238 for _ in 0..3 {
1239 status = Some(procedure.execute(&ctx).await.unwrap());
1240 }
1241 assert!(status.unwrap().is_done());
1242
1243 let ctx = TestingEnv::procedure_context();
1244 let mut procedure = new_mock_procedure(&env);
1245
1246 status = Some(procedure.execute(&ctx).await.unwrap());
1247
1248 let serialized = procedure.dump().unwrap();
1249
1250 let context_factory = env.context_factory();
1251 let tracker = env.tracker();
1252 let mut procedure =
1253 RegionMigrationProcedure::from_json(&serialized, context_factory, tracker.clone())
1254 .unwrap();
1255 for region_id in &procedure.context.persistent_ctx.region_ids {
1256 assert!(tracker.contains(*region_id));
1257 }
1258
1259 for _ in 1..3 {
1260 status = Some(procedure.execute(&ctx).await.unwrap());
1261 }
1262 assert!(status.unwrap().is_done());
1263 }
1264
1265 #[tokio::test]
1266 async fn test_broadcast_invalidate_table_cache() {
1267 let mut env = TestingEnv::new();
1268 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1269 let ctx = env.context_factory().new_context(persistent_context);
1270 let mailbox_ctx = env.mailbox_context();
1271
1272 ctx.invalidate_table_cache().await.unwrap();
1274
1275 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
1276
1277 mailbox_ctx
1278 .insert_heartbeat_response_receiver(Channel::Frontend(1), tx)
1279 .await;
1280
1281 ctx.invalidate_table_cache().await.unwrap();
1282
1283 let resp = rx.recv().await.unwrap().unwrap();
1284 let msg = resp.mailbox_message.unwrap();
1285
1286 let instruction = HeartbeatMailbox::json_instruction(&msg).unwrap();
1287 assert_eq!(
1288 instruction,
1289 Instruction::InvalidateCaches(vec![CacheIdent::TableId(1024)])
1290 );
1291 }
1292
1293 fn procedure_flow_steps(from_peer_id: u64, to_peer_id: u64) -> Vec<Step> {
1294 vec![
1295 Step::next(
1297 "Should be the open candidate region",
1298 None,
1299 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1300 ),
1301 Step::next(
1303 "Should be the flush leader region",
1304 Some(mock_datanode_reply(
1305 to_peer_id,
1306 Arc::new(|id| Ok(new_open_region_reply(id, true, None))),
1307 )),
1308 Assertion::simple(assert_flush_leader_region, assert_no_persist),
1309 ),
1310 Step::next(
1312 "Should be the flush leader region",
1313 Some(mock_datanode_reply(
1314 from_peer_id,
1315 Arc::new(move |id| {
1316 Ok(new_flush_region_reply_for_region(
1317 id,
1318 RegionId::new(1024, 1),
1319 true,
1320 None,
1321 ))
1322 }),
1323 )),
1324 Assertion::simple(assert_update_metadata_downgrade, assert_no_persist),
1325 ),
1326 Step::next(
1328 "Should be the downgrade leader region",
1329 None,
1330 Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1331 ),
1332 Step::next(
1334 "Should be the upgrade candidate region",
1335 Some(mock_datanode_reply(
1336 from_peer_id,
1337 Arc::new(|id| Ok(new_downgrade_region_reply(id, None, true, None))),
1338 )),
1339 Assertion::simple(assert_upgrade_candidate_region, assert_no_persist),
1340 ),
1341 Step::next(
1343 "Should be the update metadata for upgrading",
1344 Some(mock_datanode_reply(
1345 to_peer_id,
1346 Arc::new(|id| Ok(new_upgrade_region_reply(id, true, true, None))),
1347 )),
1348 Assertion::simple(assert_update_metadata_upgrade, assert_no_persist),
1349 ),
1350 Step::next(
1352 "Should be the close downgraded region",
1353 None,
1354 Assertion::simple(assert_close_downgraded_region, assert_no_persist),
1355 ),
1356 Step::next(
1358 "Should be the region migration end",
1359 None,
1360 Assertion::simple(assert_region_migration_end, assert_done),
1361 ),
1362 Step::next(
1364 "Should be the region migration end again",
1365 None,
1366 Assertion::simple(assert_region_migration_end, assert_done),
1367 ),
1368 ]
1369 }
1370
1371 #[tokio::test]
1372 async fn test_procedure_flow() {
1373 common_telemetry::init_default_ut_logging();
1374
1375 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1376 let state = Box::new(RegionMigrationStart);
1377
1378 let from_peer_id = persistent_context.from_peer.id;
1380 let to_peer_id = persistent_context.to_peer.id;
1381 let from_peer = persistent_context.from_peer.clone();
1382 let to_peer = persistent_context.to_peer.clone();
1383 let region_id = persistent_context.region_ids[0];
1384 let table_info = new_test_table_info(1024);
1385 let region_routes = vec![RegionRoute {
1386 region: Region::new_test(region_id),
1387 leader_peer: Some(from_peer),
1388 follower_peers: vec![to_peer],
1389 ..Default::default()
1390 }];
1391
1392 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1393 suite.init_table_metadata(table_info, region_routes).await;
1394
1395 let steps = procedure_flow_steps(from_peer_id, to_peer_id);
1396 let timer = Instant::now();
1397
1398 let runner = ProcedureMigrationSuiteRunner::new(suite)
1400 .steps(steps)
1401 .run_once()
1402 .await;
1403
1404 let region_lease = default_distributed_time_constants().region_lease.as_secs();
1405
1406 assert!(timer.elapsed().as_secs() < region_lease / 2);
1408
1409 runner.suite.verify_table_metadata().await;
1410 }
1411
1412 #[tokio::test]
1413 async fn test_procedure_flow_open_candidate_region_retryable_error() {
1414 common_telemetry::init_default_ut_logging();
1415
1416 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1417 let state = Box::new(RegionMigrationStart);
1418
1419 let to_peer_id = persistent_context.to_peer.id;
1421 let from_peer = persistent_context.from_peer.clone();
1422 let region_id = persistent_context.region_ids[0];
1423 let table_info = new_test_table_info(1024);
1424 let region_routes = vec![RegionRoute {
1425 region: Region::new_test(region_id),
1426 leader_peer: Some(from_peer),
1427 follower_peers: vec![],
1428 ..Default::default()
1429 }];
1430
1431 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1432 suite.init_table_metadata(table_info, region_routes).await;
1433
1434 let steps = vec![
1435 Step::next(
1437 "Should be the open candidate region",
1438 None,
1439 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1440 ),
1441 Step::next(
1443 "Should be throwing a non-retry error",
1444 Some(mock_datanode_reply(
1445 to_peer_id,
1446 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1447 )),
1448 Assertion::error(|error| assert!(error.is_retryable())),
1449 ),
1450 Step::next(
1452 "Should be throwing a non-retry error again",
1453 Some(mock_datanode_reply(
1454 to_peer_id,
1455 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1456 )),
1457 Assertion::error(|error| assert!(error.is_retryable())),
1458 ),
1459 ];
1460
1461 let setup_to_latest_persisted_state = Step::setup(
1462 "Sets state to UpdateMetadata::Downgrade",
1463 merge_before_test_fn(vec![
1464 setup_state(Arc::new(|| Box::new(OpenCandidateRegion))),
1465 Arc::new(reset_volatile_ctx),
1466 ]),
1467 );
1468
1469 let steps = [
1470 steps.clone(),
1471 vec![setup_to_latest_persisted_state.clone()],
1473 steps.clone()[1..].to_vec(),
1474 vec![setup_to_latest_persisted_state],
1475 steps.clone()[1..].to_vec(),
1476 ]
1477 .concat();
1478
1479 let runner = ProcedureMigrationSuiteRunner::new(suite)
1481 .steps(steps.clone())
1482 .run_once()
1483 .await;
1484
1485 let table_routes_version = runner
1486 .env()
1487 .table_metadata_manager()
1488 .table_route_manager()
1489 .table_route_storage()
1490 .get(region_id.table_id())
1491 .await
1492 .unwrap()
1493 .unwrap()
1494 .version();
1495 assert_eq!(table_routes_version.unwrap(), 0);
1497 }
1498
1499 #[tokio::test]
1500 async fn test_procedure_flow_upgrade_candidate_with_retry_and_failed() {
1501 common_telemetry::init_default_ut_logging();
1502
1503 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1504 let state = Box::new(RegionMigrationStart);
1505
1506 let from_peer_id = persistent_context.from_peer.id;
1508 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 the flush leader region",
1532 Some(mock_datanode_reply(
1533 to_peer_id,
1534 Arc::new(|id| Ok(new_open_region_reply(id, true, None))),
1535 )),
1536 Assertion::simple(assert_flush_leader_region, assert_no_persist),
1537 ),
1538 Step::next(
1540 "Should be the flush leader region",
1541 Some(mock_datanode_reply(
1542 from_peer_id,
1543 Arc::new(move |id| {
1544 Ok(new_flush_region_reply_for_region(
1545 id,
1546 RegionId::new(1024, 1),
1547 true,
1548 None,
1549 ))
1550 }),
1551 )),
1552 Assertion::simple(assert_update_metadata_downgrade, assert_no_persist),
1553 ),
1554 Step::next(
1556 "Should be the downgrade leader region",
1557 None,
1558 Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1559 ),
1560 Step::next(
1562 "Should be the upgrade candidate region",
1563 Some(mock_datanode_reply(
1564 from_peer_id,
1565 Arc::new(|id| Ok(new_downgrade_region_reply(id, None, true, None))),
1566 )),
1567 Assertion::simple(assert_upgrade_candidate_region, assert_no_persist),
1568 ),
1569 Step::next(
1571 "Should be the rollback metadata",
1572 Some(mock_datanode_reply(
1573 to_peer_id,
1574 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1575 )),
1576 Assertion::simple(assert_update_metadata_rollback, assert_no_persist),
1577 ),
1578 Step::next(
1580 "Should be the region migration abort",
1581 None,
1582 Assertion::simple(assert_region_migration_abort, assert_no_persist),
1583 ),
1584 Step::next(
1586 "Should throw an error",
1587 None,
1588 Assertion::error(|error| {
1589 assert!(!error.is_retryable());
1590 assert_matches!(error, error::Error::MigrationAbort { .. });
1591 }),
1592 ),
1593 ];
1594
1595 let setup_to_latest_persisted_state = Step::setup(
1596 "Sets state to OpenCandidateRegion",
1597 merge_before_test_fn(vec![
1598 setup_state(Arc::new(|| Box::new(OpenCandidateRegion))),
1599 Arc::new(reset_volatile_ctx),
1600 ]),
1601 );
1602
1603 let steps = [
1604 steps.clone(),
1605 vec![setup_to_latest_persisted_state.clone()],
1606 steps.clone()[1..].to_vec(),
1607 vec![setup_to_latest_persisted_state],
1608 steps.clone()[1..].to_vec(),
1609 ]
1610 .concat();
1611
1612 ProcedureMigrationSuiteRunner::new(suite)
1614 .steps(steps.clone())
1615 .run_once()
1616 .await;
1617 }
1618
1619 #[tokio::test]
1620 async fn test_procedure_flow_upgrade_candidate_with_retry() {
1621 common_telemetry::init_default_ut_logging();
1622
1623 let persistent_context = test_util::new_persistent_context(1, 2, RegionId::new(1024, 1));
1624 let state = Box::new(RegionMigrationStart);
1625
1626 let to_peer_id = persistent_context.to_peer.id;
1628 let from_peer_id = persistent_context.from_peer.id;
1629 let from_peer = persistent_context.from_peer.clone();
1630 let region_id = persistent_context.region_ids[0];
1631 let table_info = new_test_table_info(1024);
1632 let region_routes = vec![RegionRoute {
1633 region: Region::new_test(region_id),
1634 leader_peer: Some(from_peer),
1635 follower_peers: vec![],
1636 ..Default::default()
1637 }];
1638
1639 let suite = ProcedureMigrationTestSuite::new(persistent_context, state);
1640 suite.init_table_metadata(table_info, region_routes).await;
1641
1642 let steps = vec![
1643 Step::next(
1645 "Should be the open candidate region",
1646 None,
1647 Assertion::simple(assert_open_candidate_region, assert_need_persist),
1648 ),
1649 Step::next(
1651 "Should be throwing a retryable error",
1652 Some(mock_datanode_reply(
1653 to_peer_id,
1654 Arc::new(|id| {
1655 Ok(new_open_region_reply(
1656 id,
1657 false,
1658 Some("mock retryable open region error".to_string()),
1659 ))
1660 }),
1661 )),
1662 Assertion::error(|error| assert!(error.is_retryable(), "err: {error:?}")),
1663 ),
1664 Step::next(
1666 "Should be the update metadata for downgrading",
1667 Some(mock_datanode_reply(
1668 to_peer_id,
1669 Arc::new(|id| Ok(new_open_region_reply(id, true, None))),
1670 )),
1671 Assertion::simple(assert_flush_leader_region, assert_no_persist),
1672 ),
1673 Step::next(
1675 "Should be the flush leader region",
1676 Some(mock_datanode_reply(
1677 from_peer_id,
1678 Arc::new(move |id| {
1679 Ok(new_flush_region_reply_for_region(id, region_id, true, None))
1680 }),
1681 )),
1682 Assertion::simple(assert_update_metadata_downgrade, assert_no_persist),
1683 ),
1684 Step::next(
1686 "Should be the downgrade leader region",
1687 None,
1688 Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1689 ),
1690 Step::next(
1692 "Should be the upgrade candidate region",
1693 Some(mock_datanode_reply(
1694 from_peer_id,
1695 merge_mailbox_messages(vec![
1696 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1697 Arc::new(|id| Ok(new_downgrade_region_reply(id, None, true, None))),
1698 ]),
1699 )),
1700 Assertion::simple(assert_upgrade_candidate_region, assert_no_persist),
1701 ),
1702 Step::next(
1704 "Should be the update metadata for upgrading",
1705 Some(mock_datanode_reply(
1706 to_peer_id,
1707 merge_mailbox_messages(vec![
1708 Arc::new(|id| error::MailboxTimeoutSnafu { id }.fail()),
1709 Arc::new(|id| Ok(new_upgrade_region_reply(id, true, true, None))),
1710 ]),
1711 )),
1712 Assertion::simple(assert_update_metadata_upgrade, assert_no_persist),
1713 ),
1714 Step::next(
1716 "Should be the close downgraded region",
1717 None,
1718 Assertion::simple(assert_close_downgraded_region, assert_no_persist),
1719 ),
1720 Step::next(
1722 "Should be the region migration end",
1723 None,
1724 Assertion::simple(assert_region_migration_end, assert_done),
1725 ),
1726 Step::next(
1728 "Should be the region migration end again",
1729 None,
1730 Assertion::simple(assert_region_migration_end, assert_done),
1731 ),
1732 Step::setup(
1734 "Sets state to RegionMigrationStart",
1735 merge_before_test_fn(vec![
1736 setup_state(Arc::new(|| Box::new(RegionMigrationStart))),
1737 Arc::new(reset_volatile_ctx),
1738 ]),
1739 ),
1740 Step::next(
1744 "Should be the region migration end(has been migrated)",
1745 None,
1746 Assertion::simple(assert_region_migration_end, assert_done),
1747 ),
1748 ];
1749
1750 let steps = [steps.clone()].concat();
1751 let timer = Instant::now();
1752
1753 let runner = ProcedureMigrationSuiteRunner::new(suite)
1755 .steps(steps.clone())
1756 .run_once()
1757 .await;
1758
1759 let region_lease = default_distributed_time_constants().region_lease.as_secs();
1760 assert!(timer.elapsed().as_secs() < region_lease);
1762 runner.suite.verify_table_metadata().await;
1763 }
1764}