1use 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 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 ®ion_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 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 if vc.opening_region_guards.is_empty() {
146 for region_id in region_ids {
147 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 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 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 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 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 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 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 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 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 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}