Skip to main content

meta_srv/procedure/
region_migration.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub(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
75/// The default timeout for region migration.
76pub 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/// It's shared in each step and available even after recovering.
98///
99/// It will only be updated/stored after the Red node has succeeded.
100///
101/// **Notes: Stores with too large data in the context might incur replication overhead.**
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103pub struct PersistentContext {
104    /// The table catalog.
105    #[deprecated(note = "use `catalog_and_schema` instead")]
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub(crate) catalog: Option<String>,
108    /// The table schema.
109    #[deprecated(note = "use `catalog_and_schema` instead")]
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub(crate) schema: Option<String>,
112    /// The catalog and schema of the regions.
113    #[serde(default, skip_serializing_if = "Vec::is_empty")]
114    pub(crate) catalog_and_schema: Vec<(String, String)>,
115    /// The [Peer] of migration source.
116    pub(crate) from_peer: Peer,
117    /// The [Peer] of migration destination.
118    pub(crate) to_peer: Peer,
119    /// The [RegionId] of migration region.
120    #[serde(deserialize_with = "single_or_multiple_from", alias = "region_id")]
121    pub(crate) region_ids: Vec<RegionId>,
122    /// The timeout for downgrading leader region and upgrading candidate region operations.
123    #[serde(with = "humantime_serde", default = "default_timeout")]
124    pub(crate) timeout: Duration,
125    /// The trigger reason persisted for compatibility with versions without procedure context.
126    #[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        // Sort the region ids to ensure the same order of region ids.
192        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    /// Returns the table ids of the regions.
201    ///
202    /// The return value is a set of table ids.
203    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    /// Returns the table regions map.
213    ///
214    /// The key is the table id, the value is the region ids of the table.
215    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/// Metrics of region migration.
228#[derive(Debug, Clone, Default)]
229pub struct Metrics {
230    /// Elapsed time of downgrading region and upgrading region.
231    operations_elapsed: Duration,
232    /// Elapsed time of flushing leader region.
233    flush_leader_region_elapsed: Duration,
234    /// Elapsed time of downgrading leader region.
235    downgrade_leader_region_elapsed: Duration,
236    /// Elapsed time of open candidate region.
237    open_candidate_region_elapsed: Duration,
238    /// Elapsed time of upgrade candidate region.
239    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    /// Updates the elapsed time of downgrading region and upgrading region.
262    pub fn update_operations_elapsed(&mut self, elapsed: Duration) {
263        self.operations_elapsed += elapsed;
264    }
265
266    /// Updates the elapsed time of flushing leader region.
267    pub fn update_flush_leader_region_elapsed(&mut self, elapsed: Duration) {
268        self.flush_leader_region_elapsed += elapsed;
269    }
270
271    /// Updates the elapsed time of downgrading leader region.
272    pub fn update_downgrade_leader_region_elapsed(&mut self, elapsed: Duration) {
273        self.downgrade_leader_region_elapsed += elapsed;
274    }
275
276    /// Updates the elapsed time of open candidate region.
277    pub fn update_open_candidate_region_elapsed(&mut self, elapsed: Duration) {
278        self.open_candidate_region_elapsed += elapsed;
279    }
280
281    /// Updates the elapsed time of upgrade candidate region.
282    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/// It's shared in each step and available in executing (including retrying).
324///
325/// It will be dropped if the procedure runner crashes.
326///
327/// The additional remote fetches are only required in the worst cases.
328#[derive(Debug, Clone, Default)]
329pub struct VolatileContext {
330    /// `opening_region_guard` will be set after the
331    /// [OpenCandidateRegion](crate::procedure::region_migration::open_candidate_region::OpenCandidateRegion) step.
332    ///
333    /// `opening_region_guards` should be consumed after
334    /// the corresponding [RegionRoute](common_meta::rpc::router::RegionRoute) of the opening region
335    /// was written into [TableRouteValue](common_meta::key::table_route::TableRouteValue).
336    opening_region_guards: Vec<OperatingRegionGuard>,
337    /// The deadline of leader region lease.
338    leader_region_lease_deadline: Option<Instant>,
339    /// The datanode table values.
340    from_peer_datanode_table_values: Option<HashMap<TableId, DatanodeTableValue>>,
341    /// The last_entry_ids of leader regions.
342    leader_region_last_entry_ids: HashMap<RegionId, u64>,
343    /// The last_entry_ids of leader metadata regions (Only used for metric engine).
344    leader_region_metadata_last_entry_ids: HashMap<RegionId, u64>,
345    /// Metrics of region migration.
346    metrics: Metrics,
347}
348
349impl VolatileContext {
350    /// Sets the `leader_region_lease_deadline` if it does not exist.
351    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    /// Resets the `leader_region_lease_deadline`.
358    pub fn reset_leader_region_lease_deadline(&mut self) {
359        self.leader_region_lease_deadline = None;
360    }
361
362    /// Sets the `leader_region_last_entry_id`.
363    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    /// Sets the `leader_region_metadata_last_entry_id`.
369    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
375/// Used to generate new [Context].
376pub trait ContextFactory {
377    fn new_context(self, persistent_ctx: PersistentContext) -> Context;
378}
379
380/// Default implementation.
381#[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    /// Returns an [`DefaultContextFactory`].
395    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
433/// The context of procedure execution.
434pub 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    /// Returns the next operation's timeout.
456    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    /// Updates operations elapsed.
463    pub fn update_operations_elapsed(&mut self, instant: Instant) {
464        self.volatile_ctx
465            .metrics
466            .update_operations_elapsed(instant.elapsed());
467    }
468
469    /// Updates the elapsed time of flushing leader region.
470    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    /// Updates the elapsed time of downgrading leader region.
477    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    /// Updates the elapsed time of open candidate region.
484    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    /// Updates the elapsed time of upgrade candidate region.
491    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    /// Returns address of meta server.
498    pub fn server_addr(&self) -> &str {
499        &self.server_addr
500    }
501
502    /// Returns the table ids of the regions.
503    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    /// Returns the `table_routes` of [VolatileContext] if any.
514    /// Otherwise, returns the value retrieved from remote.
515    ///
516    /// Retry:
517    /// - Failed to retrieve the metadata of table.
518    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    /// Returns the `table_route` of [VolatileContext] if any.
544    /// Otherwise, returns the value retrieved from remote.
545    ///
546    /// Retry:
547    /// - Failed to retrieve the metadata of table.
548    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    /// Returns the `from_peer_datanode_table_values` of [VolatileContext] if any.
568    /// Otherwise, returns the value retrieved from remote.
569    ///
570    /// Retry:
571    /// - Failed to retrieve the metadata of datanode table.
572    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    /// Returns the `from_peer_datanode_table_value` of [VolatileContext] if any.
605    /// Otherwise, returns the value retrieved from remote.
606    ///
607    /// Retry:
608    /// - Failed to retrieve the metadata of datanode table.
609    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    /// Notifies the RegionSupervisor to register failure detectors of the given regions on the
634    /// source peer.
635    ///
636    /// The original failure detector was removed once the procedure was triggered.
637    /// Now, we need to register the failure detector for the failed region again.
638    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    /// Notifies the RegionSupervisor to reset failure detectors of the given regions on the target
654    /// peer.
655    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    /// Notifies the RegionSupervisor to deregister failure detectors.
671    ///
672    /// The original failure detectors won't be removed once the procedure was triggered.
673    /// We need to deregister the failure detectors for the original region if the procedure is finished.
674    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    /// Notifies the RegionSupervisor to deregister failure detectors for the candidate regions on the destination peer.
688    ///
689    /// The candidate regions may be created on the destination peer,
690    /// so we need to deregister the failure detectors for the candidate regions if the procedure is aborted.
691    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    /// Fetches replay checkpoints and merges them with topic pruned entry ids.
705    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    /// Broadcasts the invalidate table cache message.
761    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        // ignore the result
768        let ctx = common_meta::cache_invalidator::Context::default();
769        let _ = self.cache_invalidator.invalidate(&ctx, &cache_idents).await;
770        Ok(())
771    }
772
773    /// Returns the [PersistentContext] of the procedure.
774    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        // short name
785        type_name.split("::").last().unwrap_or(type_name)
786    }
787
788    /// Yields the next [State] and [Status].
789    async fn next(
790        &mut self,
791        ctx: &mut Context,
792        procedure_ctx: &ProcedureContext,
793    ) -> Result<(Box<dyn State>, Status)>;
794
795    /// Returns as [Any](std::any::Any).
796    fn as_any(&self) -> &dyn Any;
797}
798
799/// Persistent data of [RegionMigrationProcedure].
800#[derive(Debug, Serialize, Deserialize)]
801pub struct RegionMigrationDataOwned {
802    persistent_ctx: PersistentContext,
803    state: Box<dyn State>,
804}
805
806/// Persistent data of [RegionMigrationProcedure].
807#[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        // Regions whose route was switched to the target before the procedure aborted.
884        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                // The table has been dropped meanwhile.
891                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        // The switched regions are served by the target, so they are monitored there instead.
933        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        // The target's next heartbeat would register these detectors again, but nothing would if
944        // the target stopped before sending one.
945        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                    // Consumes the opening region guard before deregistering the failure detectors.
997                    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        // The route switch aborted after the table of `switched` was switched to the target and
1073        // before the table of `unswitched`, whose leader is still downgrading; the table of
1074        // `dropped` has been dropped.
1075        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        // The target's detectors are replaced by the source's, except for `switched`, which stays
1104        // monitored on the target.
1105        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        // NOTES: Changes it will break backward compatibility.
1202        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        // No receivers.
1361        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            // MigrationStart
1384            Step::next(
1385                "Should be the open candidate region",
1386                None,
1387                Assertion::simple(assert_open_candidate_region, assert_need_persist),
1388            ),
1389            // OpenCandidateRegion
1390            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            // Flush Leader Region
1399            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            // UpdateMetadata::Downgrade
1415            Step::next(
1416                "Should be the downgrade leader region",
1417                None,
1418                Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1419            ),
1420            // Downgrade Candidate
1421            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            // Upgrade Candidate
1430            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            // UpdateMetadata::Upgrade
1439            Step::next(
1440                "Should be the close downgraded region",
1441                None,
1442                Assertion::simple(assert_close_downgraded_region, assert_no_persist),
1443            ),
1444            // CloseDowngradedRegion
1445            Step::next(
1446                "Should be the region migration end",
1447                None,
1448                Assertion::simple(assert_region_migration_end, assert_done),
1449            ),
1450            // RegionMigrationEnd
1451            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        // The table metadata.
1467        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        // Run the table tests.
1487        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        // Ensure it didn't run into the slow path.
1495        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        // The table metadata.
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            // Migration Start
1524            Step::next(
1525                "Should be the open candidate region",
1526                None,
1527                Assertion::simple(assert_open_candidate_region, assert_need_persist),
1528            ),
1529            // OpenCandidateRegion
1530            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            // OpenCandidateRegion
1539            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            // Mocks the volatile ctx lost(i.g., Meta leader restarts).
1560            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        // Run the table tests.
1568        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        // Should be unchanged.
1584        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        // The table metadata.
1595        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            // MigrationStart
1612            Step::next(
1613                "Should be the open candidate region",
1614                None,
1615                Assertion::simple(assert_open_candidate_region, assert_need_persist),
1616            ),
1617            // OpenCandidateRegion
1618            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            // Flush Leader Region
1627            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            // UpdateMetadata::Downgrade
1643            Step::next(
1644                "Should be the downgrade leader region",
1645                None,
1646                Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1647            ),
1648            // Downgrade Candidate
1649            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            // Upgrade Candidate
1658            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            // UpdateMetadata::Rollback
1667            Step::next(
1668                "Should be the region migration abort",
1669                None,
1670                Assertion::simple(assert_region_migration_abort, assert_no_persist),
1671            ),
1672            // RegionMigrationAbort
1673            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        // Run the table tests.
1701        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        // The table metadata.
1715        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            // Migration Start
1732            Step::next(
1733                "Should be the open candidate region",
1734                None,
1735                Assertion::simple(assert_open_candidate_region, assert_need_persist),
1736            ),
1737            // OpenCandidateRegion
1738            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            // OpenCandidateRegion
1753            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            // Flush Leader Region
1762            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            // UpdateMetadata::Downgrade
1773            Step::next(
1774                "Should be the downgrade leader region",
1775                None,
1776                Assertion::simple(assert_downgrade_leader_region, assert_no_persist),
1777            ),
1778            // Downgrade Leader
1779            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            // Upgrade Candidate
1791            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            // UpdateMetadata::Upgrade
1803            Step::next(
1804                "Should be the close downgraded region",
1805                None,
1806                Assertion::simple(assert_close_downgraded_region, assert_no_persist),
1807            ),
1808            // CloseDowngradedRegion
1809            Step::next(
1810                "Should be the region migration end",
1811                None,
1812                Assertion::simple(assert_region_migration_end, assert_done),
1813            ),
1814            // RegionMigrationEnd
1815            Step::next(
1816                "Should be the region migration end again",
1817                None,
1818                Assertion::simple(assert_region_migration_end, assert_done),
1819            ),
1820            // RegionMigrationStart
1821            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            // RegionMigrationEnd
1829            // Note: We can't run this test multiple times;
1830            // the `peer_id`'s `DatanodeTable` will be removed after first-time migration success.
1831            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        // Run the table tests.
1842        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        // Ensure it didn't run into the slow path.
1849        assert!(timer.elapsed().as_secs() < region_lease);
1850        runner.suite.verify_table_metadata().await;
1851    }
1852}