Skip to main content

meta_srv/procedure/region_migration/
open_candidate_region.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::ops::Div;
17
18use api::v1::meta::MailboxMessage;
19use common_meta::RegionIdent;
20use common_meta::distributed_time_constants::default_distributed_time_constants;
21use common_meta::instruction::{
22    Instruction, InstructionReply, OpenRegion, OpenRegionReason, SimpleReply,
23};
24use common_meta::key::datanode_table::RegionInfo;
25use common_procedure::{Context as ProcedureContext, Status};
26use common_telemetry::info;
27use common_telemetry::tracing_context::TracingContext;
28use serde::{Deserialize, Serialize};
29use snafu::{OptionExt, ResultExt};
30use store_api::region_engine::RegionRole;
31use store_api::region_request::RegionRequirements;
32use tokio::time::Instant;
33
34use crate::error::{self, Result};
35use crate::handler::HeartbeatMailbox;
36use crate::procedure::region_migration::flush_leader_region::PreFlushRegion;
37use crate::procedure::region_migration::{Context, RegionMigrationTriggerReason, State};
38use crate::procedure::utils::instruction_error_result;
39use crate::service::mailbox::Channel;
40
41#[derive(Debug, Serialize, Deserialize)]
42pub struct OpenCandidateRegion;
43
44#[async_trait::async_trait]
45#[typetag::serde]
46impl State for OpenCandidateRegion {
47    async fn next(
48        &mut self,
49        ctx: &mut Context,
50        procedure_ctx: &ProcedureContext,
51    ) -> Result<(Box<dyn State>, Status)> {
52        let trigger_reason = ctx.trigger_reason(procedure_ctx.event_context.as_ref());
53        let instruction = self
54            .build_open_region_instruction(ctx, trigger_reason)
55            .await?;
56        let now = Instant::now();
57        self.open_candidate_region(ctx, instruction).await?;
58        ctx.update_open_candidate_region_elapsed(now);
59
60        Ok((Box::new(PreFlushRegion), Status::executing(false)))
61    }
62
63    fn as_any(&self) -> &dyn Any {
64        self
65    }
66}
67
68impl OpenCandidateRegion {
69    /// Builds open region instructions
70    ///
71    /// Abort(non-retry):
72    /// - Datanode Table is not found.
73    async fn build_open_region_instruction(
74        &self,
75        ctx: &mut Context,
76        trigger_reason: RegionMigrationTriggerReason,
77    ) -> Result<Instruction> {
78        let region_ids = ctx.persistent_ctx.region_ids.clone();
79        let from_peer_id = ctx.persistent_ctx.from_peer.id;
80        let to_peer_id = ctx.persistent_ctx.to_peer.id;
81        let reason = match trigger_reason {
82            RegionMigrationTriggerReason::Failover => OpenRegionReason::RegionFailover,
83            _ => OpenRegionReason::RegionMigration,
84        };
85        let datanode_table_values = ctx.get_from_peer_datanode_table_values().await?;
86        let mut open_regions = Vec::with_capacity(region_ids.len());
87
88        for region_id in region_ids {
89            let table_id = region_id.table_id();
90            let region_number = region_id.region_number();
91            let datanode_table_value = datanode_table_values.get(&table_id).context(
92                error::DatanodeTableNotFoundSnafu {
93                    table_id,
94                    datanode_id: from_peer_id,
95                },
96            )?;
97            let RegionInfo {
98                region_storage_path,
99                region_options,
100                region_wal_options,
101                engine,
102            } = datanode_table_value.region_info.clone();
103
104            open_regions.push(OpenRegion::new(
105                RegionIdent {
106                    datanode_id: to_peer_id,
107                    table_id,
108                    region_number,
109                    engine,
110                },
111                &region_storage_path,
112                region_options,
113                region_wal_options,
114                true,
115                Some(reason),
116                RegionRequirements::object_storage(),
117            ));
118        }
119
120        Ok(Instruction::OpenRegions(open_regions))
121    }
122
123    /// Opens the candidate region.
124    ///
125    /// Abort(non-retry):
126    /// - The Datanode is unreachable(e.g., Candidate pusher is not found).
127    /// - Unexpected instruction reply.
128    /// - Another procedure is opening the candidate region.
129    ///
130    /// Retry:
131    /// - Exceeded deadline of open instruction.
132    /// - Datanode failed to open the candidate region.
133    async fn open_candidate_region(
134        &self,
135        ctx: &mut Context,
136        open_instruction: Instruction,
137    ) -> Result<()> {
138        let pc = &ctx.persistent_ctx;
139        let vc = &mut ctx.volatile_ctx;
140        let region_ids = &pc.region_ids;
141        let candidate = &pc.to_peer;
142
143        // This method might be invoked multiple times.
144        // Only registers the guard if `opening_region_guard` is absent.
145        if vc.opening_region_guards.is_empty() {
146            for region_id in region_ids {
147                // Registers the opening region.
148                let guard = ctx
149                    .opening_region_keeper
150                    .register_with_role(candidate.id, *region_id, RegionRole::Follower)
151                    .context(error::RegionOperatingRaceSnafu {
152                        peer_id: candidate.id,
153                        region_id: *region_id,
154                    })?;
155                vc.opening_region_guards.push(guard);
156            }
157        }
158
159        let tracing_ctx = TracingContext::from_current_span();
160        let msg = MailboxMessage::json_message(
161            &format!("Open candidate regions: {:?}", region_ids),
162            &format!("Metasrv@{}", ctx.server_addr()),
163            &format!("Datanode-{}@{}", candidate.id, candidate.addr),
164            common_time::util::current_time_millis(),
165            &open_instruction,
166            Some(tracing_ctx.to_w3c()),
167        )
168        .with_context(|_| error::SerializeToJsonSnafu {
169            input: open_instruction.to_string(),
170        })?;
171
172        let operation_timeout =
173            ctx.next_operation_timeout()
174                .context(error::ExceededDeadlineSnafu {
175                    operation: "Open candidate region",
176                })?;
177        let operation_timeout = operation_timeout
178            .div(2)
179            .max(default_distributed_time_constants().region_lease);
180        let ch = Channel::Datanode(candidate.id);
181        let now = Instant::now();
182        let receiver = ctx.mailbox.send(&ch, msg, operation_timeout).await?;
183
184        match receiver.await {
185            Ok(msg) => {
186                let reply = HeartbeatMailbox::json_reply(&msg)?;
187                info!(
188                    "Received open region reply: {:?}, region: {:?}, elapsed: {:?}",
189                    reply,
190                    region_ids,
191                    now.elapsed()
192                );
193                let InstructionReply::OpenRegions(SimpleReply { result, error }) = reply else {
194                    return error::UnexpectedInstructionReplySnafu {
195                        mailbox_message: msg.to_string(),
196                        reason: "expect open region reply",
197                    }
198                    .fail();
199                };
200
201                if result {
202                    Ok(())
203                } else if let Some(error) = error {
204                    instruction_error_result(
205                        &error,
206                        format!(
207                            "Region {region_ids:?} is not opened by datanode {:?}, error: {error:?}, elapsed: {:?}",
208                            candidate,
209                            now.elapsed()
210                        ),
211                    )
212                } else {
213                    error::UnexpectedSnafu {
214                        violated: format!(
215                            "Region {region_ids:?} is not opened by datanode {:?}, but error is absent, elapsed: {:?}",
216                            candidate,
217                            now.elapsed()
218                        ),
219                    }
220                    .fail()
221                }
222            }
223            Err(error::Error::MailboxTimeout { .. }) => {
224                let reason = format!(
225                    "Mailbox received timeout for open candidate region {region_ids:?} on datanode {:?}, elapsed: {:?}",
226                    candidate,
227                    now.elapsed()
228                );
229                error::RetryLaterSnafu { reason }.fail()
230            }
231            Err(e) => Err(e),
232        }
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use std::assert_matches;
239    use std::collections::HashMap;
240
241    use common_catalog::consts::MITO2_ENGINE;
242    use common_error::ext::RetryHint;
243    use common_error::status_code::StatusCode;
244    use common_meta::DatanodeId;
245    use common_meta::instruction::InstructionError;
246    use common_meta::key::table_route::TableRouteValue;
247    use common_meta::key::test_utils::new_test_table_info;
248    use common_meta::peer::Peer;
249    use common_meta::rpc::router::{Region, RegionRoute};
250    use store_api::storage::RegionId;
251
252    use super::*;
253    use crate::error::Error;
254    use crate::procedure::region_migration::test_util::{self, TestingEnv, new_procedure_context};
255    use crate::procedure::region_migration::{ContextFactory, PersistentContext};
256    use crate::procedure::test_util::{
257        new_close_region_reply, new_open_region_reply, new_open_region_reply_with_error,
258        send_mock_reply,
259    };
260
261    fn new_persistent_context() -> PersistentContext {
262        test_util::new_persistent_context(1, 2, RegionId::new(1024, 1))
263    }
264
265    fn new_mock_open_instruction(datanode_id: DatanodeId, region_id: RegionId) -> Instruction {
266        Instruction::OpenRegions(vec![OpenRegion::new(
267            RegionIdent {
268                datanode_id,
269                table_id: region_id.table_id(),
270                region_number: region_id.region_number(),
271                engine: MITO2_ENGINE.to_string(),
272            },
273            "/bar/foo/region/",
274            Default::default(),
275            Default::default(),
276            true,
277            Some(OpenRegionReason::RegionMigration),
278            RegionRequirements::object_storage(),
279        )])
280    }
281
282    #[tokio::test]
283    async fn test_datanode_table_is_not_found_error() {
284        let state = OpenCandidateRegion;
285        let persistent_context = new_persistent_context();
286        let env = TestingEnv::new();
287        let mut ctx = env.context_factory().new_context(persistent_context);
288
289        let err = state
290            .build_open_region_instruction(&mut ctx, RegionMigrationTriggerReason::Unknown)
291            .await
292            .unwrap_err();
293
294        assert_matches!(err, Error::DatanodeTableNotFound { .. });
295        assert!(!err.is_retryable());
296    }
297
298    #[tokio::test]
299    async fn test_build_open_region_instruction_reason() {
300        let state = OpenCandidateRegion;
301        let persistent_context = new_persistent_context();
302        let from_peer_id = persistent_context.from_peer.id;
303        let region_id = persistent_context.region_ids[0];
304        let env = TestingEnv::new();
305
306        let table_info = new_test_table_info(1024);
307        let region_routes = vec![RegionRoute {
308            region: Region::new_test(region_id),
309            leader_peer: Some(Peer::empty(from_peer_id)),
310            ..Default::default()
311        }];
312        env.table_metadata_manager()
313            .create_table_metadata(
314                table_info,
315                TableRouteValue::physical(region_routes),
316                HashMap::default(),
317            )
318            .await
319            .unwrap();
320
321        let mut ctx = env
322            .context_factory()
323            .new_context(persistent_context.clone());
324        let instruction = state
325            .build_open_region_instruction(&mut ctx, RegionMigrationTriggerReason::Unknown)
326            .await
327            .unwrap();
328        let open_regions = instruction.into_open_regions().unwrap();
329        assert_eq!(
330            Some(OpenRegionReason::RegionMigration),
331            open_regions[0].reason
332        );
333        assert_eq!(
334            RegionRequirements::object_storage(),
335            open_regions[0].requirements
336        );
337
338        let mut ctx = env.context_factory().new_context(persistent_context);
339        let instruction = state
340            .build_open_region_instruction(&mut ctx, RegionMigrationTriggerReason::Failover)
341            .await
342            .unwrap();
343        let open_regions = instruction.into_open_regions().unwrap();
344        assert_eq!(
345            Some(OpenRegionReason::RegionFailover),
346            open_regions[0].reason
347        );
348        assert_eq!(
349            RegionRequirements::object_storage(),
350            open_regions[0].requirements
351        );
352    }
353
354    #[tokio::test]
355    async fn test_datanode_is_unreachable() {
356        let state = OpenCandidateRegion;
357        // from_peer: 1
358        // to_peer: 2
359        let persistent_context = new_persistent_context();
360        let region_id = persistent_context.region_ids[0];
361        let to_peer_id = persistent_context.to_peer.id;
362        let env = TestingEnv::new();
363        let mut ctx = env.context_factory().new_context(persistent_context);
364
365        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
366        let err = state
367            .open_candidate_region(&mut ctx, open_instruction)
368            .await
369            .unwrap_err();
370
371        assert_matches!(err, Error::PusherNotFound { .. });
372        assert!(!err.is_retryable());
373    }
374
375    #[tokio::test]
376    async fn test_candidate_region_opening_error() {
377        let state = OpenCandidateRegion;
378        // from_peer: 1
379        // to_peer: 2
380        let persistent_context = new_persistent_context();
381        let region_id = persistent_context.region_ids[0];
382        let to_peer_id = persistent_context.to_peer.id;
383
384        let env = TestingEnv::new();
385        let mut ctx = env.context_factory().new_context(persistent_context);
386        let opening_region_keeper = env.opening_region_keeper();
387        let _guard = opening_region_keeper
388            .register_with_role(to_peer_id, region_id, RegionRole::Follower)
389            .unwrap();
390
391        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
392        let err = state
393            .open_candidate_region(&mut ctx, open_instruction)
394            .await
395            .unwrap_err();
396
397        assert_matches!(err, Error::RegionOperatingRace { .. });
398        assert!(!err.is_retryable());
399    }
400
401    #[tokio::test]
402    async fn test_unexpected_instruction_reply() {
403        let state = OpenCandidateRegion;
404        // from_peer: 1
405        // to_peer: 2
406        let persistent_context = new_persistent_context();
407        let region_id = persistent_context.region_ids[0];
408        let to_peer_id = persistent_context.to_peer.id;
409
410        let mut env = TestingEnv::new();
411        let mut ctx = env.context_factory().new_context(persistent_context);
412        let mailbox_ctx = env.mailbox_context();
413        let mailbox = mailbox_ctx.mailbox().clone();
414
415        let (tx, rx) = tokio::sync::mpsc::channel(1);
416
417        mailbox_ctx
418            .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
419            .await;
420
421        // Sends an incorrect reply.
422        send_mock_reply(mailbox, rx, |id| Ok(new_close_region_reply(id)));
423
424        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
425        let err = state
426            .open_candidate_region(&mut ctx, open_instruction)
427            .await
428            .unwrap_err();
429
430        assert_matches!(err, Error::UnexpectedInstructionReply { .. });
431        assert!(!err.is_retryable());
432    }
433
434    #[tokio::test]
435    async fn test_instruction_exceeded_deadline() {
436        let state = OpenCandidateRegion;
437        // from_peer: 1
438        // to_peer: 2
439        let persistent_context = new_persistent_context();
440        let region_id = persistent_context.region_ids[0];
441        let to_peer_id = persistent_context.to_peer.id;
442
443        let mut env = TestingEnv::new();
444        let mut ctx = env.context_factory().new_context(persistent_context);
445        let mailbox_ctx = env.mailbox_context();
446        let mailbox = mailbox_ctx.mailbox().clone();
447
448        let (tx, rx) = tokio::sync::mpsc::channel(1);
449
450        mailbox_ctx
451            .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
452            .await;
453
454        // Sends an timeout error.
455        send_mock_reply(mailbox, rx, |id| {
456            Err(error::MailboxTimeoutSnafu { id }.build())
457        });
458
459        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
460        let err = state
461            .open_candidate_region(&mut ctx, open_instruction)
462            .await
463            .unwrap_err();
464
465        assert_matches!(err, Error::RetryLater { .. });
466        assert!(err.is_retryable());
467    }
468
469    #[tokio::test]
470    async fn test_open_candidate_region_failed() {
471        let state = OpenCandidateRegion;
472        // from_peer: 1
473        // to_peer: 2
474        let persistent_context = new_persistent_context();
475        let region_id = persistent_context.region_ids[0];
476        let to_peer_id = persistent_context.to_peer.id;
477        let mut env = TestingEnv::new();
478
479        let mut ctx = env.context_factory().new_context(persistent_context);
480        let mailbox_ctx = env.mailbox_context();
481        let mailbox = mailbox_ctx.mailbox().clone();
482
483        let (tx, rx) = tokio::sync::mpsc::channel(1);
484
485        mailbox_ctx
486            .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
487            .await;
488
489        send_mock_reply(mailbox, rx, |id| {
490            Ok(new_open_region_reply_with_error(
491                id,
492                false,
493                Some(InstructionError {
494                    code: StatusCode::StorageUnavailable,
495                    message: "test mocked".to_string(),
496                    retry_hint: RetryHint::Retryable,
497                }),
498            ))
499        });
500
501        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
502        let err = state
503            .open_candidate_region(&mut ctx, open_instruction)
504            .await
505            .unwrap_err();
506
507        assert_matches!(err, Error::RetryLater { .. });
508        assert!(err.is_retryable());
509        assert!(format!("{err:?}").contains("test mocked"));
510    }
511
512    #[tokio::test]
513    async fn test_open_candidate_region_non_retryable_instruction_error() {
514        let state = OpenCandidateRegion;
515        let persistent_context = new_persistent_context();
516        let region_id = persistent_context.region_ids[0];
517        let to_peer_id = persistent_context.to_peer.id;
518        let mut env = TestingEnv::new();
519
520        let mut ctx = env.context_factory().new_context(persistent_context);
521        let mailbox_ctx = env.mailbox_context();
522        let mailbox = mailbox_ctx.mailbox().clone();
523
524        let (tx, rx) = tokio::sync::mpsc::channel(1);
525
526        mailbox_ctx
527            .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
528            .await;
529
530        send_mock_reply(mailbox, rx, |id| {
531            Ok(new_open_region_reply_with_error(
532                id,
533                false,
534                Some(InstructionError {
535                    code: StatusCode::Internal,
536                    message: "non retryable mocked".to_string(),
537                    retry_hint: RetryHint::NonRetryable,
538                }),
539            ))
540        });
541
542        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
543        let err = state
544            .open_candidate_region(&mut ctx, open_instruction)
545            .await
546            .unwrap_err();
547
548        assert_matches!(err, Error::Unexpected { .. });
549        assert!(!err.is_retryable());
550        assert!(format!("{err:?}").contains("non retryable mocked"));
551    }
552
553    #[tokio::test]
554    async fn test_open_candidate_region_false_without_error_is_unexpected() {
555        let state = OpenCandidateRegion;
556        let persistent_context = new_persistent_context();
557        let region_id = persistent_context.region_ids[0];
558        let to_peer_id = persistent_context.to_peer.id;
559        let mut env = TestingEnv::new();
560
561        let mut ctx = env.context_factory().new_context(persistent_context);
562        let mailbox_ctx = env.mailbox_context();
563        let mailbox = mailbox_ctx.mailbox().clone();
564
565        let (tx, rx) = tokio::sync::mpsc::channel(1);
566
567        mailbox_ctx
568            .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
569            .await;
570
571        send_mock_reply(mailbox, rx, |id| {
572            Ok(new_open_region_reply_with_error(id, false, None))
573        });
574
575        let open_instruction = new_mock_open_instruction(to_peer_id, region_id);
576        let err = state
577            .open_candidate_region(&mut ctx, open_instruction)
578            .await
579            .unwrap_err();
580
581        assert_matches!(err, Error::Unexpected { .. });
582        assert!(!err.is_retryable());
583    }
584
585    #[tokio::test]
586    async fn test_next_flush_leader_region_state() {
587        let mut state = Box::new(OpenCandidateRegion);
588        // from_peer: 1
589        // to_peer: 2
590        let persistent_context = new_persistent_context();
591        let from_peer_id = persistent_context.from_peer.id;
592        let region_id = persistent_context.region_ids[0];
593        let to_peer_id = persistent_context.to_peer.id;
594        let mut env = TestingEnv::new();
595
596        // Prepares table
597        let table_info = new_test_table_info(1024);
598        let region_routes = vec![RegionRoute {
599            region: Region::new_test(region_id),
600            leader_peer: Some(Peer::empty(from_peer_id)),
601            ..Default::default()
602        }];
603
604        env.table_metadata_manager()
605            .create_table_metadata(
606                table_info,
607                TableRouteValue::physical(region_routes),
608                HashMap::default(),
609            )
610            .await
611            .unwrap();
612
613        let mut ctx = env.context_factory().new_context(persistent_context);
614        let mailbox_ctx = env.mailbox_context();
615        let mailbox = mailbox_ctx.mailbox().clone();
616
617        let (tx, rx) = tokio::sync::mpsc::channel(1);
618
619        mailbox_ctx
620            .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
621            .await;
622
623        send_mock_reply(mailbox, rx, |id| Ok(new_open_region_reply(id, true, None)));
624        let procedure_ctx = new_procedure_context();
625        let (next, _) = state.next(&mut ctx, &procedure_ctx).await.unwrap();
626        let vc = ctx.volatile_ctx;
627        assert_eq!(vc.opening_region_guards[0].info(), (to_peer_id, region_id));
628
629        let flush_leader_region = next.as_any().downcast_ref::<PreFlushRegion>().unwrap();
630        assert_matches!(flush_leader_region, PreFlushRegion);
631    }
632}