1use std::any::Any;
16use std::collections::HashSet;
17use std::time::Duration;
18
19use api::v1::meta::MailboxMessage;
20use common_meta::instruction::{
21 Instruction, InstructionReply, UpgradeRegion, UpgradeRegionReply, UpgradeRegionsReply,
22};
23use common_meta::lock_key::RemoteWalLock;
24use common_meta::wal_provider::extract_topic_from_wal_options;
25use common_procedure::{Context as ProcedureContext, Status};
26use common_telemetry::tracing_context::TracingContext;
27use common_telemetry::{error, info};
28use common_wal::options::WalOptions;
29use serde::{Deserialize, Serialize};
30use snafu::{OptionExt, ResultExt, ensure};
31use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
32use tokio::time::{Instant, sleep};
33
34use crate::error::{self, Result};
35use crate::handler::HeartbeatMailbox;
36use crate::procedure::region_migration::update_metadata::UpdateMetadata;
37use crate::procedure::region_migration::{Context, State};
38use crate::procedure::utils::instruction_error_result;
39use crate::service::mailbox::Channel;
40
41#[derive(Debug, Serialize, Deserialize)]
42pub struct UpgradeCandidateRegion {
43 pub(crate) optimistic_retry: usize,
45 pub(crate) retry_initial_interval: Duration,
47 pub(crate) require_ready: bool,
50}
51
52impl Default for UpgradeCandidateRegion {
53 fn default() -> Self {
54 Self {
55 optimistic_retry: 3,
56 retry_initial_interval: Duration::from_millis(500),
57 require_ready: true,
58 }
59 }
60}
61
62#[async_trait::async_trait]
63#[typetag::serde]
64impl State for UpgradeCandidateRegion {
65 async fn next(
66 &mut self,
67 ctx: &mut Context,
68 procedure_ctx: &ProcedureContext,
69 ) -> Result<(Box<dyn State>, Status)> {
70 let now = Instant::now();
71
72 let topics = self.get_kafka_topics(ctx).await?;
73 if self
74 .upgrade_region_with_retry(ctx, procedure_ctx, topics)
75 .await
76 {
77 ctx.update_upgrade_candidate_region_elapsed(now);
78 Ok((Box::new(UpdateMetadata::Upgrade), Status::executing(false)))
79 } else {
80 ctx.update_upgrade_candidate_region_elapsed(now);
81 Ok((Box::new(UpdateMetadata::Rollback), Status::executing(false)))
82 }
83 }
84
85 fn as_any(&self) -> &dyn Any {
86 self
87 }
88}
89
90impl UpgradeCandidateRegion {
91 async fn get_kafka_topics(&self, ctx: &mut Context) -> Result<HashSet<String>> {
92 let table_regions = ctx.persistent_ctx.table_regions();
93 let datanode_table_values = ctx.get_from_peer_datanode_table_values().await?;
94 let mut topics = HashSet::new();
95 for (table_id, regions) in table_regions {
96 let Some(datanode_table_value) = datanode_table_values.get(&table_id) else {
97 continue;
98 };
99
100 let region_wal_options = &datanode_table_value.region_info.region_wal_options;
101
102 for region_id in regions {
103 let Some(WalOptions::Kafka(kafka_wal_options)) =
104 region_wal_options.get(®ion_id.region_number())
105 else {
106 continue;
107 };
108 if !topics.contains(&kafka_wal_options.topic) {
109 topics.insert(kafka_wal_options.topic.clone());
110 }
111 }
112 }
113
114 Ok(topics)
115 }
116
117 async fn build_upgrade_region_instruction(
119 &self,
120 ctx: &mut Context,
121 replay_timeout: Duration,
122 ) -> Result<Instruction> {
123 let region_ids = ctx.persistent_ctx.region_ids.clone();
124 let datanode_table_values = ctx.get_from_peer_datanode_table_values().await?;
125 let mut region_topic = Vec::with_capacity(region_ids.len());
126 for region_id in region_ids.iter() {
127 let table_id = region_id.table_id();
128 if let Some(datanode_table_value) = datanode_table_values.get(&table_id)
129 && let Some(topic) = extract_topic_from_wal_options(
130 *region_id,
131 &datanode_table_value.region_info.region_wal_options,
132 )
133 {
134 let is_metric_engine =
135 datanode_table_value.region_info.engine == METRIC_ENGINE_NAME;
136 region_topic.push((*region_id, topic, is_metric_engine));
137 }
138 }
139
140 let replay_checkpoints = ctx
141 .get_replay_checkpoints_with_topic_pruned_entry_ids(®ion_topic)
142 .await?;
143 let mut upgrade_regions = Vec::with_capacity(region_ids.len());
145 for region_id in region_ids {
146 let last_entry_id = ctx
147 .volatile_ctx
148 .leader_region_last_entry_ids
149 .get(®ion_id)
150 .copied();
151 let metadata_last_entry_id = ctx
152 .volatile_ctx
153 .leader_region_metadata_last_entry_ids
154 .get(®ion_id)
155 .copied();
156 let checkpoint = replay_checkpoints.get(®ion_id).copied();
157 upgrade_regions.push(UpgradeRegion {
158 region_id,
159 last_entry_id,
160 metadata_last_entry_id,
161 replay_timeout,
162 location_id: Some(ctx.persistent_ctx.from_peer.id),
163 replay_entry_id: checkpoint.map(|c| c.entry_id),
164 metadata_replay_entry_id: checkpoint.and_then(|c| c.metadata_entry_id),
165 });
166 }
167
168 Ok(Instruction::UpgradeRegions(upgrade_regions))
169 }
170
171 fn handle_upgrade_region_reply(
172 &self,
173 ctx: &mut Context,
174 UpgradeRegionReply {
175 region_id,
176 ready,
177 exists,
178 error,
179 }: &UpgradeRegionReply,
180 now: &Instant,
181 ) -> Result<()> {
182 let candidate = &ctx.persistent_ctx.to_peer;
183 if let Some(error) = error {
184 return instruction_error_result(
185 error,
186 format!(
187 "Failed to upgrade the region {} on datanode {:?}, error: {:?}, elapsed: {:?}",
188 region_id,
189 candidate,
190 error,
191 now.elapsed()
192 ),
193 );
194 }
195
196 ensure!(
197 exists,
198 error::UnexpectedSnafu {
199 violated: format!(
200 "Candidate region {} doesn't exist on datanode {:?}",
201 region_id, candidate
202 )
203 }
204 );
205
206 if self.require_ready && !ready {
207 return error::RetryLaterSnafu {
208 reason: format!(
209 "Candidate region {} still replaying the wal on datanode {:?}, elapsed: {:?}",
210 region_id,
211 candidate,
212 now.elapsed()
213 ),
214 }
215 .fail();
216 }
217
218 Ok(())
219 }
220
221 async fn upgrade_region(&self, ctx: &mut Context) -> Result<()> {
236 let operation_timeout =
237 ctx.next_operation_timeout()
238 .context(error::ExceededDeadlineSnafu {
239 operation: "Upgrade region",
240 })?;
241 let upgrade_instruction = self
242 .build_upgrade_region_instruction(ctx, operation_timeout)
243 .await?;
244
245 let pc = &ctx.persistent_ctx;
246 let region_ids = &pc.region_ids;
247 let candidate = &pc.to_peer;
248
249 let tracing_ctx = TracingContext::from_current_span();
250 let msg = MailboxMessage::json_message(
251 &format!("Upgrade candidate regions: {:?}", region_ids),
252 &format!("Metasrv@{}", ctx.server_addr()),
253 &format!("Datanode-{}@{}", candidate.id, candidate.addr),
254 common_time::util::current_time_millis(),
255 &upgrade_instruction,
256 Some(tracing_ctx.to_w3c()),
257 )
258 .with_context(|_| error::SerializeToJsonSnafu {
259 input: upgrade_instruction.to_string(),
260 })?;
261
262 let ch = Channel::Datanode(candidate.id);
263 let receiver = ctx.mailbox.send(&ch, msg, operation_timeout).await?;
264
265 let now = Instant::now();
266 match receiver.await {
267 Ok(msg) => {
268 let reply = HeartbeatMailbox::json_reply(&msg)?;
269 info!(
270 "Received upgrade region reply: {:?}, regions: {:?}, elapsed: {:?}",
271 reply,
272 region_ids,
273 now.elapsed()
274 );
275 let InstructionReply::UpgradeRegions(UpgradeRegionsReply { replies }) = reply
276 else {
277 return error::UnexpectedInstructionReplySnafu {
278 mailbox_message: msg.to_string(),
279 reason: "Unexpected reply of the upgrade region instruction",
280 }
281 .fail();
282 };
283 for reply in replies {
284 self.handle_upgrade_region_reply(ctx, &reply, &now)?;
285 }
286 Ok(())
287 }
288 Err(error::Error::MailboxTimeout { .. }) => {
289 let reason = format!(
290 "Mailbox received timeout for upgrade candidate regions {region_ids:?} on datanode {:?}, elapsed: {:?}",
291 candidate,
292 now.elapsed()
293 );
294 error::RetryLaterSnafu { reason }.fail()
295 }
296 Err(err) => Err(err),
297 }
298 }
299
300 async fn upgrade_region_with_retry(
304 &self,
305 ctx: &mut Context,
306 procedure_ctx: &ProcedureContext,
307 topics: HashSet<String>,
308 ) -> bool {
309 let mut retry = 0;
310 let mut upgraded = false;
311
312 let mut guards = Vec::with_capacity(topics.len());
316 for topic in &topics {
317 guards.push(
318 procedure_ctx
319 .provider
320 .acquire_lock(&(RemoteWalLock::Read(topic.clone()).into()))
321 .await,
322 );
323 }
324
325 loop {
326 let timer = Instant::now();
327 if let Err(err) = self.upgrade_region(ctx).await {
328 retry += 1;
329 ctx.update_operations_elapsed(timer);
330 if matches!(err, error::Error::ExceededDeadline { .. }) {
331 error!("Failed to upgrade region, exceeded deadline");
332 break;
333 } else if err.is_retryable() && retry < self.optimistic_retry {
334 error!("Failed to upgrade region, error: {err:?}, retry later");
335 sleep(self.retry_initial_interval).await;
336 } else {
337 error!("Failed to upgrade region, error: {err:?}");
338 break;
339 }
340 } else {
341 ctx.update_operations_elapsed(timer);
342 upgraded = true;
343 break;
344 }
345 }
346
347 upgraded
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use std::assert_matches;
354 use std::collections::HashMap;
355
356 use common_meta::key::table_route::TableRouteValue;
357 use common_meta::key::test_utils::new_test_table_info;
358 use common_meta::key::topic_name::TopicNameKey;
359 use common_meta::key::topic_region::{ReplayCheckpoint, TopicRegionKey, TopicRegionValue};
360 use common_meta::peer::Peer;
361 use common_meta::rpc::router::{Region, RegionRoute};
362 use common_meta::wal_provider::RegionWalOptions;
363 use common_wal::options::KafkaWalOptions;
364 use store_api::storage::RegionId;
365
366 use super::*;
367 use crate::error::Error;
368 use crate::procedure::region_migration::test_util::{TestingEnv, new_procedure_context};
369 use crate::procedure::region_migration::{
370 ContextFactory, PersistentContext, RegionMigrationTriggerReason,
371 };
372 use crate::procedure::test_util::{
373 new_close_region_reply, new_upgrade_region_reply, send_mock_reply,
374 };
375
376 fn new_persistent_context() -> PersistentContext {
377 PersistentContext::new(
378 vec![("greptime".into(), "public".into())],
379 Peer::empty(1),
380 Peer::empty(2),
381 vec![RegionId::new(1024, 1)],
382 Duration::from_millis(1000),
383 RegionMigrationTriggerReason::Unknown,
384 )
385 }
386
387 fn kafka_wal_options(topic: &str) -> RegionWalOptions {
388 RegionWalOptions::from([(
389 1,
390 WalOptions::Kafka(KafkaWalOptions::new(topic.to_string())),
391 )])
392 }
393
394 async fn prepare_table_metadata(ctx: &Context, wal_options: RegionWalOptions) {
395 prepare_table_metadata_with_engine(ctx, wal_options, "engine").await;
396 }
397
398 async fn prepare_table_metadata_with_engine(
399 ctx: &Context,
400 wal_options: RegionWalOptions,
401 engine: &str,
402 ) {
403 let region_id = ctx.persistent_ctx.region_ids[0];
404 let mut table_info = new_test_table_info(region_id.table_id());
405 table_info.meta.engine = engine.to_string();
406 let region_routes = vec![RegionRoute {
407 region: Region::new_test(region_id),
408 leader_peer: Some(ctx.persistent_ctx.from_peer.clone()),
409 follower_peers: vec![ctx.persistent_ctx.to_peer.clone()],
410 ..Default::default()
411 }];
412 ctx.table_metadata_manager
413 .create_table_metadata(
414 table_info,
415 TableRouteValue::physical(region_routes),
416 wal_options,
417 )
418 .await
419 .unwrap();
420 }
421
422 #[tokio::test]
423 async fn test_build_upgrade_region_instruction_merges_topic_pruned_entry_id() {
424 let state = UpgradeCandidateRegion::default();
425 let persistent_context = new_persistent_context();
426 let env = TestingEnv::new();
427 let mut ctx = env.context_factory().new_context(persistent_context);
428 let region_id = ctx.persistent_ctx.region_ids[0];
429 let topic = "test_topic";
430 prepare_table_metadata(&ctx, kafka_wal_options(topic)).await;
431 ctx.table_metadata_manager
432 .topic_region_manager()
433 .batch_put(&[(
434 TopicRegionKey::new(region_id, topic),
435 Some(TopicRegionValue::new(Some(ReplayCheckpoint::new(10, None)))),
436 )])
437 .await
438 .unwrap();
439 ctx.table_metadata_manager
440 .topic_name_manager()
441 .batch_put(vec![TopicNameKey::new(topic)])
442 .await
443 .unwrap();
444 let prev = ctx
445 .table_metadata_manager
446 .topic_name_manager()
447 .get(topic)
448 .await
449 .unwrap();
450 ctx.table_metadata_manager
451 .topic_name_manager()
452 .update(topic, 20, prev)
453 .await
454 .unwrap();
455
456 let instruction = state
457 .build_upgrade_region_instruction(&mut ctx, Duration::from_secs(1))
458 .await
459 .unwrap();
460 let Instruction::UpgradeRegions(upgrade_regions) = instruction else {
461 unreachable!()
462 };
463
464 assert_eq!(upgrade_regions.len(), 1);
465 assert_eq!(upgrade_regions[0].replay_entry_id, Some(20));
466 assert_eq!(upgrade_regions[0].metadata_replay_entry_id, None);
467 }
468
469 #[tokio::test]
470 async fn test_build_upgrade_region_instruction_merges_metric_metadata_pruned_entry_id() {
471 let state = UpgradeCandidateRegion::default();
472 let persistent_context = new_persistent_context();
473 let env = TestingEnv::new();
474 let mut ctx = env.context_factory().new_context(persistent_context);
475 let region_id = ctx.persistent_ctx.region_ids[0];
476 let topic = "test_topic";
477 prepare_table_metadata_with_engine(&ctx, kafka_wal_options(topic), METRIC_ENGINE_NAME)
478 .await;
479 ctx.table_metadata_manager
480 .topic_region_manager()
481 .batch_put(&[(
482 TopicRegionKey::new(region_id, topic),
483 Some(TopicRegionValue::new(Some(ReplayCheckpoint::new(
484 10,
485 Some(5),
486 )))),
487 )])
488 .await
489 .unwrap();
490 ctx.table_metadata_manager
491 .topic_name_manager()
492 .batch_put(vec![TopicNameKey::new(topic)])
493 .await
494 .unwrap();
495 let prev = ctx
496 .table_metadata_manager
497 .topic_name_manager()
498 .get(topic)
499 .await
500 .unwrap();
501 ctx.table_metadata_manager
502 .topic_name_manager()
503 .update(topic, 20, prev)
504 .await
505 .unwrap();
506
507 let instruction = state
508 .build_upgrade_region_instruction(&mut ctx, Duration::from_secs(1))
509 .await
510 .unwrap();
511 let Instruction::UpgradeRegions(upgrade_regions) = instruction else {
512 unreachable!()
513 };
514
515 assert_eq!(upgrade_regions.len(), 1);
516 assert_eq!(upgrade_regions[0].replay_entry_id, Some(20));
517 assert_eq!(upgrade_regions[0].metadata_replay_entry_id, Some(20));
518 }
519
520 #[tokio::test]
521 async fn test_datanode_is_unreachable() {
522 let state = UpgradeCandidateRegion::default();
523 let persistent_context = new_persistent_context();
524 let env = TestingEnv::new();
525 let mut ctx = env.context_factory().new_context(persistent_context);
526 prepare_table_metadata(&ctx, HashMap::default()).await;
527 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
528
529 assert_matches!(err, Error::PusherNotFound { .. });
530 assert!(!err.is_retryable());
531 }
532
533 #[tokio::test]
534 async fn test_pusher_dropped() {
535 let state = UpgradeCandidateRegion::default();
536 let persistent_context = new_persistent_context();
537 let to_peer_id = persistent_context.to_peer.id;
538
539 let mut env = TestingEnv::new();
540 let mut ctx = env.context_factory().new_context(persistent_context);
541 prepare_table_metadata(&ctx, HashMap::default()).await;
542 let mailbox_ctx = env.mailbox_context();
543
544 let (tx, rx) = tokio::sync::mpsc::channel(1);
545
546 mailbox_ctx
547 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
548 .await;
549
550 drop(rx);
551
552 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
553
554 assert_matches!(err, Error::PushMessage { .. });
555 assert!(!err.is_retryable());
556 }
557
558 #[tokio::test]
559 async fn test_procedure_exceeded_deadline() {
560 let state = UpgradeCandidateRegion::default();
561 let persistent_context = new_persistent_context();
562 let env = TestingEnv::new();
563 let mut ctx = env.context_factory().new_context(persistent_context);
564 prepare_table_metadata(&ctx, HashMap::default()).await;
565 ctx.volatile_ctx.metrics.operations_elapsed =
566 ctx.persistent_ctx.timeout + Duration::from_secs(1);
567
568 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
569
570 assert_matches!(err, Error::ExceededDeadline { .. });
571 assert!(!err.is_retryable());
572 }
573
574 #[tokio::test]
575 async fn test_unexpected_instruction_reply() {
576 let state = UpgradeCandidateRegion::default();
577 let persistent_context = new_persistent_context();
578 let to_peer_id = persistent_context.to_peer.id;
579
580 let mut env = TestingEnv::new();
581 let mut ctx = env.context_factory().new_context(persistent_context);
582 prepare_table_metadata(&ctx, HashMap::default()).await;
583 let mailbox_ctx = env.mailbox_context();
584 let mailbox = mailbox_ctx.mailbox().clone();
585
586 let (tx, rx) = tokio::sync::mpsc::channel(1);
587
588 mailbox_ctx
589 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
590 .await;
591
592 send_mock_reply(mailbox, rx, |id| Ok(new_close_region_reply(id)));
593
594 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
595 assert_matches!(err, Error::UnexpectedInstructionReply { .. });
596 assert!(!err.is_retryable());
597 }
598
599 #[tokio::test]
600 async fn test_upgrade_region_failed() {
601 let state = UpgradeCandidateRegion::default();
602 let persistent_context = new_persistent_context();
603 let to_peer_id = persistent_context.to_peer.id;
604
605 let mut env = TestingEnv::new();
606 let mut ctx = env.context_factory().new_context(persistent_context);
607 prepare_table_metadata(&ctx, HashMap::default()).await;
608 let mailbox_ctx = env.mailbox_context();
609 let mailbox = mailbox_ctx.mailbox().clone();
610
611 let (tx, rx) = tokio::sync::mpsc::channel(1);
612
613 mailbox_ctx
614 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
615 .await;
616
617 send_mock_reply(mailbox, rx, |id| {
619 Ok(new_upgrade_region_reply(
620 id,
621 true,
622 true,
623 Some("test mocked".to_string()),
624 ))
625 });
626
627 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
628
629 assert_matches!(err, Error::RetryLater { .. });
630 assert!(err.is_retryable());
631 assert!(format!("{err:?}").contains("test mocked"));
632 }
633
634 #[tokio::test]
635 async fn test_upgrade_region_not_found() {
636 let state = UpgradeCandidateRegion::default();
637 let persistent_context = new_persistent_context();
638 let to_peer_id = persistent_context.to_peer.id;
639
640 let mut env = TestingEnv::new();
641 let mut ctx = env.context_factory().new_context(persistent_context);
642 prepare_table_metadata(&ctx, HashMap::default()).await;
643 let mailbox_ctx = env.mailbox_context();
644 let mailbox = mailbox_ctx.mailbox().clone();
645
646 let (tx, rx) = tokio::sync::mpsc::channel(1);
647
648 mailbox_ctx
649 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
650 .await;
651
652 send_mock_reply(mailbox, rx, |id| {
653 Ok(new_upgrade_region_reply(id, true, false, None))
654 });
655
656 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
657
658 assert_matches!(err, Error::Unexpected { .. });
659 assert!(!err.is_retryable());
660 assert!(err.to_string().contains("doesn't exist"));
661 }
662
663 #[tokio::test]
664 async fn test_upgrade_region_require_ready() {
665 let mut state = UpgradeCandidateRegion {
666 require_ready: true,
667 ..Default::default()
668 };
669
670 let persistent_context = new_persistent_context();
671 let to_peer_id = persistent_context.to_peer.id;
672
673 let mut env = TestingEnv::new();
674 let mut ctx = env.context_factory().new_context(persistent_context);
675 prepare_table_metadata(&ctx, HashMap::default()).await;
676 let mailbox_ctx = env.mailbox_context();
677 let mailbox = mailbox_ctx.mailbox().clone();
678
679 let (tx, rx) = tokio::sync::mpsc::channel(1);
680
681 mailbox_ctx
682 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
683 .await;
684
685 send_mock_reply(mailbox, rx, |id| {
686 Ok(new_upgrade_region_reply(id, false, true, None))
687 });
688
689 let err = state.upgrade_region(&mut ctx).await.unwrap_err();
690
691 assert_matches!(err, Error::RetryLater { .. });
692 assert!(err.is_retryable());
693 assert!(format!("{err:?}").contains("still replaying the wal"));
694
695 state.require_ready = false;
697
698 let mailbox = mailbox_ctx.mailbox().clone();
699 let (tx, rx) = tokio::sync::mpsc::channel(1);
700
701 mailbox_ctx
702 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
703 .await;
704
705 send_mock_reply(mailbox, rx, |id| {
706 Ok(new_upgrade_region_reply(id, false, true, None))
707 });
708
709 state.upgrade_region(&mut ctx).await.unwrap();
710 }
711
712 #[tokio::test]
713 async fn test_upgrade_region_with_retry_ok() {
714 let mut state = Box::<UpgradeCandidateRegion>::default();
715 state.retry_initial_interval = Duration::from_millis(100);
716 let persistent_context = new_persistent_context();
717 let to_peer_id = persistent_context.to_peer.id;
718
719 let mut env = TestingEnv::new();
720 let mut ctx = env.context_factory().new_context(persistent_context);
721 prepare_table_metadata(&ctx, HashMap::default()).await;
722 let mailbox_ctx = env.mailbox_context();
723 let mailbox = mailbox_ctx.mailbox().clone();
724
725 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
726
727 mailbox_ctx
728 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
729 .await;
730
731 common_runtime::spawn_global(async move {
732 let resp = rx.recv().await.unwrap().unwrap();
733 let reply_id = resp.mailbox_message.unwrap().id;
734 mailbox
735 .on_recv(
736 reply_id,
737 Err(error::MailboxTimeoutSnafu { id: reply_id }.build()),
738 )
739 .await
740 .unwrap();
741
742 let resp = rx.recv().await.unwrap().unwrap();
744 let reply_id = resp.mailbox_message.unwrap().id;
745 mailbox
746 .on_recv(
747 reply_id,
748 Ok(new_upgrade_region_reply(reply_id, false, true, None)),
749 )
750 .await
751 .unwrap();
752
753 let resp = rx.recv().await.unwrap().unwrap();
755 let reply_id = resp.mailbox_message.unwrap().id;
756 mailbox
757 .on_recv(
758 reply_id,
759 Ok(new_upgrade_region_reply(reply_id, true, true, None)),
760 )
761 .await
762 .unwrap();
763 });
764
765 let procedure_ctx = new_procedure_context();
766 let (next, _) = state.next(&mut ctx, &procedure_ctx).await.unwrap();
767
768 let update_metadata = next.as_any().downcast_ref::<UpdateMetadata>().unwrap();
769
770 assert_matches!(update_metadata, UpdateMetadata::Upgrade);
771 }
772
773 #[tokio::test]
774 async fn test_upgrade_region_with_retry_failed() {
775 let mut state = Box::<UpgradeCandidateRegion>::default();
776 state.retry_initial_interval = Duration::from_millis(100);
777 let persistent_context = new_persistent_context();
778 let to_peer_id = persistent_context.to_peer.id;
779
780 let mut env = TestingEnv::new();
781 let mut ctx = env.context_factory().new_context(persistent_context);
782 prepare_table_metadata(&ctx, HashMap::default()).await;
783 let mailbox_ctx = env.mailbox_context();
784 let mailbox = mailbox_ctx.mailbox().clone();
785
786 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
787
788 mailbox_ctx
789 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
790 .await;
791
792 common_runtime::spawn_global(async move {
793 let resp = rx.recv().await.unwrap().unwrap();
794 let reply_id = resp.mailbox_message.unwrap().id;
795 mailbox
796 .on_recv(
797 reply_id,
798 Err(error::MailboxTimeoutSnafu { id: reply_id }.build()),
799 )
800 .await
801 .unwrap();
802
803 let resp = rx.recv().await.unwrap().unwrap();
805 let reply_id = resp.mailbox_message.unwrap().id;
806 mailbox
807 .on_recv(
808 reply_id,
809 Ok(new_upgrade_region_reply(reply_id, false, true, None)),
810 )
811 .await
812 .unwrap();
813
814 let resp = rx.recv().await.unwrap().unwrap();
816 let reply_id = resp.mailbox_message.unwrap().id;
817 mailbox
818 .on_recv(
819 reply_id,
820 Ok(new_upgrade_region_reply(reply_id, false, false, None)),
821 )
822 .await
823 .unwrap();
824 });
825 let procedure_ctx = new_procedure_context();
826 let (next, _) = state.next(&mut ctx, &procedure_ctx).await.unwrap();
827
828 let update_metadata = next.as_any().downcast_ref::<UpdateMetadata>().unwrap();
829 assert_matches!(update_metadata, UpdateMetadata::Rollback);
830 }
831
832 #[tokio::test]
833 async fn test_upgrade_region_procedure_exceeded_deadline() {
834 let mut state = Box::<UpgradeCandidateRegion>::default();
835 state.retry_initial_interval = Duration::from_millis(100);
836 let persistent_context = new_persistent_context();
837 let to_peer_id = persistent_context.to_peer.id;
838
839 let mut env = TestingEnv::new();
840 let mut ctx = env.context_factory().new_context(persistent_context);
841 prepare_table_metadata(&ctx, HashMap::default()).await;
842 let mailbox_ctx = env.mailbox_context();
843 let mailbox = mailbox_ctx.mailbox().clone();
844 ctx.volatile_ctx.metrics.operations_elapsed =
845 ctx.persistent_ctx.timeout + Duration::from_secs(1);
846
847 let (tx, rx) = tokio::sync::mpsc::channel(1);
848 mailbox_ctx
849 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
850 .await;
851
852 send_mock_reply(mailbox, rx, |id| {
853 Ok(new_upgrade_region_reply(id, false, true, None))
854 });
855 let procedure_ctx = new_procedure_context();
856 let (next, _) = state.next(&mut ctx, &procedure_ctx).await.unwrap();
857 let update_metadata = next.as_any().downcast_ref::<UpdateMetadata>().unwrap();
858 assert_matches!(update_metadata, UpdateMetadata::Rollback);
859 }
860}
861
862#[cfg(test)]
873mod wal_prune_deadlock_tests {
874 use std::assert_matches;
875 use std::sync::Arc;
876 use std::sync::atomic::{AtomicUsize, Ordering};
877
878 use common_meta::key::table_route::TableRouteValue;
879 use common_meta::key::test_utils::new_test_table_info;
880 use common_meta::peer::Peer;
881 use common_meta::region_registry::LeaderRegionRegistry;
882 use common_meta::rpc::router::{Region, RegionRoute};
883 use common_meta::wal_provider::{RegionWalOptions, build_kafka_client};
884 use common_procedure::Procedure;
885 use common_wal::config::kafka::common::KafkaConnectionConfig;
886 use common_wal::maybe_skip_kafka_integration_test;
887 use common_wal::options::KafkaWalOptions;
888 use common_wal::test_util::get_kafka_endpoints;
889 use store_api::storage::RegionId;
890 use tokio::time::timeout;
891
892 use super::*;
893 use crate::procedure::region_migration::test_util::{TestingEnv, new_procedure_context};
894 use crate::procedure::region_migration::{
895 ContextFactory, PersistentContext, RegionMigrationTriggerReason,
896 };
897 use crate::procedure::wal_prune::{Context as WalPruneContext, WalPruneProcedure};
898
899 struct Scenario {
900 env: TestingEnv,
901 ctx: Context,
902 topic: String,
903 upgrade_instructions: Arc<AtomicUsize>,
904 }
905
906 async fn setup(name: &str) -> Scenario {
909 let topic = format!("{name}_{}", uuid::Uuid::new_v4());
910 let region_id = RegionId::new(1024, 1);
911 let persistent_ctx = PersistentContext::new(
912 vec![("greptime".into(), "public".into())],
913 Peer::empty(1),
914 Peer::empty(2),
915 vec![region_id],
916 Duration::from_millis(1000),
918 RegionMigrationTriggerReason::Failover,
919 );
920 let to_peer_id = persistent_ctx.to_peer.id;
921
922 let mut env = TestingEnv::new();
923 let ctx = env.context_factory().new_context(persistent_ctx);
924 let region_routes = vec![RegionRoute {
925 region: Region::new_test(region_id),
926 leader_peer: Some(ctx.persistent_ctx.from_peer.clone()),
927 follower_peers: vec![ctx.persistent_ctx.to_peer.clone()],
928 ..Default::default()
929 }];
930 let wal_options = RegionWalOptions::from([(
931 region_id.region_number(),
932 WalOptions::Kafka(KafkaWalOptions::new(topic.clone())),
933 )]);
934 ctx.table_metadata_manager
935 .create_table_metadata(
936 new_test_table_info(region_id.table_id()),
937 TableRouteValue::physical(region_routes),
938 wal_options,
939 )
940 .await
941 .unwrap();
942
943 let (tx, mut rx) = tokio::sync::mpsc::channel(8);
944 env.mailbox_context()
945 .insert_heartbeat_response_receiver(Channel::Datanode(to_peer_id), tx)
946 .await;
947 let upgrade_instructions = Arc::new(AtomicUsize::new(0));
948 let counter = upgrade_instructions.clone();
949 common_runtime::spawn_global(async move {
950 while let Some(Ok(_)) = rx.recv().await {
951 counter.fetch_add(1, Ordering::SeqCst);
952 }
953 });
954
955 Scenario {
956 env,
957 ctx,
958 topic,
959 upgrade_instructions,
960 }
961 }
962
963 async fn wait_for_first_instruction(counter: &AtomicUsize) {
964 timeout(Duration::from_secs(5), async {
965 while counter.load(Ordering::SeqCst) == 0 {
966 sleep(Duration::from_millis(10)).await;
967 }
968 })
969 .await
970 .unwrap();
971 }
972
973 #[tokio::test]
974 async fn test_upgrade_retry_does_not_deadlock_with_wal_prune() {
975 maybe_skip_kafka_integration_test!();
976 let Scenario {
977 env,
978 mut ctx,
979 topic,
980 upgrade_instructions,
981 } = setup("upgrade_wal_prune_deadlock").await;
982 let procedure_ctx = new_procedure_context();
984
985 let migration_procedure_ctx = procedure_ctx.clone();
986 let migration = tokio::spawn(async move {
987 let mut state = Box::<UpgradeCandidateRegion>::default();
988 state.next(&mut ctx, &migration_procedure_ctx).await
989 });
990 wait_for_first_instruction(&upgrade_instructions).await;
992
993 let client = build_kafka_client(&KafkaConnectionConfig {
995 broker_endpoints: get_kafka_endpoints(),
996 ..Default::default()
997 })
998 .await
999 .unwrap();
1000 client
1001 .controller_client()
1002 .unwrap()
1003 .create_topic(&topic, 1, 1, 5000)
1004 .await
1005 .unwrap();
1006 let mut wal_prune = WalPruneProcedure::new(
1007 WalPruneContext {
1008 client: Arc::new(client),
1009 table_metadata_manager: env.table_metadata_manager().clone(),
1010 leader_region_registry: Arc::new(LeaderRegionRegistry::new()),
1011 },
1012 None,
1013 topic,
1014 0,
1015 false,
1016 );
1017 let wal_prune_procedure_ctx = procedure_ctx.clone();
1018 let wal_prune =
1019 tokio::spawn(async move { wal_prune.execute(&wal_prune_procedure_ctx).await });
1020
1021 let (next, _) = timeout(Duration::from_secs(10), migration)
1025 .await
1026 .expect("upgrade step should not deadlock")
1027 .unwrap()
1028 .unwrap();
1029 let update_metadata = next.as_any().downcast_ref::<UpdateMetadata>().unwrap();
1030 assert_matches!(update_metadata, UpdateMetadata::Rollback);
1031 assert_eq!(1, upgrade_instructions.load(Ordering::SeqCst));
1032
1033 timeout(Duration::from_secs(10), wal_prune)
1034 .await
1035 .expect("WAL prune should not deadlock")
1036 .unwrap()
1037 .unwrap();
1038 }
1039
1040 #[tokio::test]
1041 async fn test_upgrade_retry_without_wal_prune_rolls_back() {
1042 maybe_skip_kafka_integration_test!();
1043 let Scenario {
1044 mut ctx,
1045 upgrade_instructions,
1046 ..
1047 } = setup("upgrade_wal_prune_control").await;
1048 let procedure_ctx = new_procedure_context();
1049
1050 let mut state = Box::<UpgradeCandidateRegion>::default();
1051 let (next, _) = timeout(
1052 Duration::from_secs(10),
1053 state.next(&mut ctx, &procedure_ctx),
1054 )
1055 .await
1056 .expect("upgrade step should finish without WAL prune")
1057 .unwrap();
1058 let update_metadata = next.as_any().downcast_ref::<UpdateMetadata>().unwrap();
1059 assert_matches!(update_metadata, UpdateMetadata::Rollback);
1060 assert_eq!(1, upgrade_instructions.load(Ordering::SeqCst));
1061 }
1062}