meta_srv/procedure/region_migration/update_metadata/
upgrade_candidate_region.rs1use common_error::ext::BoxedError;
16use common_meta::key::datanode_table::RegionInfo;
17use common_meta::lock_key::TableLock;
18use common_meta::rpc::router::{RegionRoute, region_distribution};
19use common_procedure::ContextProviderRef;
20use common_telemetry::{error, info, warn};
21use snafu::{OptionExt, ResultExt, ensure};
22use store_api::storage::RegionId;
23
24use crate::error::{self, Result};
25use crate::procedure::region_migration::Context;
26use crate::procedure::region_migration::update_metadata::UpdateMetadata;
27
28impl UpdateMetadata {
29 fn build_upgrade_candidate_region_metadata(
31 &self,
32 ctx: &mut Context,
33 region_ids: &[RegionId],
34 mut region_routes: Vec<RegionRoute>,
35 ) -> Result<Vec<RegionRoute>> {
36 let old_leader_peer = &ctx.persistent_ctx.from_peer;
37 let new_leader_peer = &ctx.persistent_ctx.to_peer;
38 for region_id in region_ids {
39 let region_route = region_routes
41 .iter_mut()
42 .find(|route| route.region.id == *region_id)
43 .context(error::RegionRouteNotFoundSnafu {
44 region_id: *region_id,
45 })?;
46
47 region_route.set_leader_state(None);
49
50 ensure!(
52 region_route
53 .leader_peer
54 .take_if(|old_leader| old_leader.id == old_leader_peer.id)
55 .is_some(),
56 error::UnexpectedSnafu {
57 violated: format!(
58 "Unexpected region leader: {:?} during the candidate-to-leader upgrade; expected: {:?}",
59 region_route.leader_peer, old_leader_peer
60 ),
61 }
62 );
63
64 region_route.leader_peer = Some(new_leader_peer.clone());
66
67 let removed = region_route
69 .follower_peers
70 .extract_if(.., |peer| peer.id == new_leader_peer.id)
71 .collect::<Vec<_>>();
72
73 if removed.len() > 1 {
75 warn!(
76 "Removed duplicate followers: {removed:?} during candidate-to-leader upgrade for region: {region_id}"
77 );
78 }
79 }
80
81 info!(
82 "Building metadata for upgrading candidate region to new leader: {:?} for regions: {:?}",
83 new_leader_peer, region_ids,
84 );
85
86 Ok(region_routes)
87 }
88
89 fn check_metadata_updated(
98 &self,
99 ctx: &mut Context,
100 region_ids: &[RegionId],
101 region_routes: &[RegionRoute],
102 ) -> Result<bool> {
103 for region_id in region_ids {
105 let region_route = region_routes
107 .iter()
108 .find(|route| route.region.id == *region_id)
109 .context(error::RegionRouteNotFoundSnafu {
110 region_id: *region_id,
111 })?;
112
113 let leader_peer = region_route.leader_peer.as_ref().with_context(||error::UnexpectedSnafu {
115 violated: format!(
116 "The leader peer of region {region_id} is not found during the metadata upgrade check"
117 ),
118 })?;
119
120 if leader_peer.id != ctx.persistent_ctx.to_peer.id {
122 return Ok(false);
123 } else {
124 ensure!(
126 !region_route.is_leader_downgrading(),
127 error::UnexpectedSnafu {
128 violated: format!(
129 "Unexpected intermediate state is found during the metadata upgrade check for region {region_id}"
130 ),
131 }
132 );
133 }
134 }
135
136 Ok(true)
138 }
139
140 pub async fn upgrade_candidate_region(
150 &self,
151 ctx: &mut Context,
152 ctx_provider: &ContextProviderRef,
153 ) -> Result<()> {
154 let table_metadata_manager = ctx.table_metadata_manager.clone();
155 let table_regions = ctx.persistent_ctx.table_regions();
156 let from_peer_id = ctx.persistent_ctx.from_peer.id;
157 let to_peer_id = ctx.persistent_ctx.to_peer.id;
158
159 for (table_id, region_ids) in table_regions {
160 let table_lock = TableLock::Write(table_id).into();
161 let _guard = ctx_provider.acquire_lock(&table_lock).await;
162
163 let table_route_value = ctx.get_table_route_value(table_id).await?;
164 let region_routes = table_route_value.region_routes().with_context(|_| {
165 error::UnexpectedLogicalRouteTableSnafu {
166 err_msg: format!("TableRoute({table_id:?}) is a non-physical TableRouteValue."),
167 }
168 })?;
169 if self.check_metadata_updated(ctx, ®ion_ids, region_routes)? {
170 continue;
171 }
172 let datanode_table_value = ctx.get_from_peer_datanode_table_value(table_id).await?;
173 let RegionInfo {
174 region_storage_path,
175 region_options,
176 region_wal_options,
177 engine,
178 } = datanode_table_value.region_info.clone();
179 let new_region_routes = self.build_upgrade_candidate_region_metadata(
180 ctx,
181 ®ion_ids,
182 region_routes.clone(),
183 )?;
184 let region_distribution = region_distribution(region_routes);
185 info!(
186 "Trying to update region routes to {:?} for table: {}",
187 region_distribution, table_id,
188 );
189
190 if let Err(err) = table_metadata_manager
191 .update_table_route(
192 table_id,
193 RegionInfo {
194 engine: engine.clone(),
195 region_storage_path: region_storage_path.clone(),
196 region_options: region_options.clone(),
197 region_wal_options: region_wal_options.clone(),
198 },
199 &table_route_value,
200 new_region_routes,
201 ®ion_options,
202 ®ion_wal_options,
203 )
204 .await
205 .context(error::TableMetadataManagerSnafu)
206 {
207 error!(err; "Failed to update the table route during the upgrading candidate region: {region_ids:?}, from_peer_id: {from_peer_id}");
208 return Err(BoxedError::new(err)).context(error::RetryLaterWithSourceSnafu {
209 reason: format!("Failed to update the table route during the upgrading candidate region: {table_id}"),
210 });
211 };
212 info!(
213 "Upgrading candidate region table route success, table_id: {table_id}, regions: {region_ids:?}, to_peer_id: {to_peer_id}"
214 );
215 }
216
217 ctx.deregister_failure_detectors().await;
218 ctx.reset_failure_detectors(&ctx.persistent_ctx.region_ids)
219 .await;
220 ctx.volatile_ctx.opening_region_guards.clear();
222
223 Ok(())
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use std::assert_matches;
230
231 use common_meta::key::test_utils::new_test_table_info;
232 use common_meta::peer::Peer;
233 use common_meta::region_keeper::MemoryRegionKeeper;
234 use common_meta::rpc::router::{LeaderState, Region, RegionRoute};
235 use common_time::util::current_time_millis;
236 use store_api::region_engine::RegionRole;
237 use store_api::storage::RegionId;
238
239 use crate::error::Error;
240 use crate::procedure::region_migration::close_downgraded_region::CloseDowngradedRegion;
241 use crate::procedure::region_migration::test_util::{self, TestingEnv, new_procedure_context};
242 use crate::procedure::region_migration::update_metadata::UpdateMetadata;
243 use crate::procedure::region_migration::{ContextFactory, PersistentContext, State};
244
245 fn new_persistent_context() -> PersistentContext {
246 test_util::new_persistent_context(1, 2, RegionId::new(1024, 1))
247 }
248
249 #[tokio::test]
250 async fn test_table_route_is_not_found_error() {
251 let env = TestingEnv::new();
252 let persistent_context = new_persistent_context();
253 let ctx = env.context_factory().new_context(persistent_context);
254
255 let err = ctx.get_table_route_value(1024).await.unwrap_err();
256
257 assert_matches!(err, Error::TableRouteNotFound { .. });
258 assert!(!err.is_retryable());
259 }
260
261 #[tokio::test]
262 async fn test_region_route_is_not_found() {
263 let state = UpdateMetadata::Upgrade;
264 let env = TestingEnv::new();
265 let persistent_context = new_persistent_context();
266 let mut ctx = env.context_factory().new_context(persistent_context);
267
268 let table_info = new_test_table_info(1024);
269 let region_routes = vec![RegionRoute {
270 region: Region::new_test(RegionId::new(1024, 2)),
271 leader_peer: Some(Peer::empty(4)),
272 ..Default::default()
273 }];
274 env.create_physical_table_metadata(table_info, region_routes)
275 .await;
276
277 let table_route_value = ctx.get_table_route_value(1024).await.unwrap();
278 let region_routes = table_route_value
279 .into_inner()
280 .into_physical_table_route()
281 .region_routes;
282 let err = state
283 .build_upgrade_candidate_region_metadata(
284 &mut ctx,
285 &[RegionId::new(1024, 1)],
286 region_routes,
287 )
288 .unwrap_err();
289
290 assert_matches!(err, Error::RegionRouteNotFound { .. });
291 assert!(!err.is_retryable());
292 }
293
294 #[tokio::test]
295 async fn test_region_route_expected_leader() {
296 let state = UpdateMetadata::Upgrade;
297 let env = TestingEnv::new();
298 let persistent_context = new_persistent_context();
299 let mut ctx = env.context_factory().new_context(persistent_context);
300
301 let table_info = new_test_table_info(1024);
302 let region_routes = vec![RegionRoute {
303 region: Region::new_test(RegionId::new(1024, 1)),
304 leader_peer: Some(Peer::empty(3)),
305 ..Default::default()
306 }];
307
308 env.create_physical_table_metadata(table_info, region_routes)
309 .await;
310
311 let table_route_value = ctx.get_table_route_value(1024).await.unwrap();
312 let region_routes = table_route_value
313 .into_inner()
314 .into_physical_table_route()
315 .region_routes;
316 let err = state
317 .build_upgrade_candidate_region_metadata(
318 &mut ctx,
319 &[RegionId::new(1024, 1)],
320 region_routes,
321 )
322 .unwrap_err();
323
324 assert_matches!(err, Error::Unexpected { .. });
325 assert!(!err.is_retryable());
326 assert!(err.to_string().contains("Unexpected region leader"));
327 }
328
329 #[tokio::test]
330 async fn test_build_upgrade_candidate_region_metadata() {
331 let state = UpdateMetadata::Upgrade;
332 let env = TestingEnv::new();
333 let persistent_context = new_persistent_context();
334 let mut ctx = env.context_factory().new_context(persistent_context);
335
336 let table_info = new_test_table_info(1024);
337 let region_routes = vec![RegionRoute {
338 region: Region::new_test(RegionId::new(1024, 1)),
339 leader_peer: Some(Peer::empty(1)),
340 follower_peers: vec![Peer::empty(2), Peer::empty(3)],
341 leader_state: Some(LeaderState::Downgrading),
342 leader_down_since: Some(current_time_millis()),
343 write_route_policy: None,
344 }];
345
346 env.create_physical_table_metadata(table_info, region_routes)
347 .await;
348
349 let table_route_value = ctx.get_table_route_value(1024).await.unwrap();
350 let region_routes = table_route_value
351 .into_inner()
352 .into_physical_table_route()
353 .region_routes;
354 let new_region_routes = state
355 .build_upgrade_candidate_region_metadata(
356 &mut ctx,
357 &[RegionId::new(1024, 1)],
358 region_routes,
359 )
360 .unwrap();
361
362 assert!(!new_region_routes[0].is_leader_downgrading());
363 assert!(new_region_routes[0].leader_down_since.is_none());
364 assert_eq!(new_region_routes[0].follower_peers, vec![Peer::empty(3)]);
365 assert_eq!(new_region_routes[0].leader_peer.as_ref().unwrap().id, 2);
366 }
367
368 #[tokio::test]
369 async fn test_check_metadata() {
370 let state = UpdateMetadata::Upgrade;
371 let env = TestingEnv::new();
372 let persistent_context = new_persistent_context();
373 let leader_peer = persistent_context.from_peer.clone();
374
375 let mut ctx = env.context_factory().new_context(persistent_context);
376 let table_info = new_test_table_info(1024);
377 let region_routes = vec![RegionRoute {
378 region: Region::new_test(RegionId::new(1024, 1)),
379 leader_peer: Some(leader_peer),
380 follower_peers: vec![Peer::empty(2), Peer::empty(3)],
381 leader_state: None,
382 leader_down_since: None,
383 write_route_policy: None,
384 }];
385
386 env.create_physical_table_metadata(table_info, region_routes)
387 .await;
388 let table_routes = ctx.get_table_route_value(1024).await.unwrap();
389 let region_routes = table_routes.region_routes().unwrap();
390 let updated = state
391 .check_metadata_updated(&mut ctx, &[RegionId::new(1024, 1)], region_routes)
392 .unwrap();
393 assert!(!updated);
394 }
395
396 #[tokio::test]
397 async fn test_check_metadata_updated() {
398 let state = UpdateMetadata::Upgrade;
399 let env = TestingEnv::new();
400 let persistent_context = new_persistent_context();
401 let candidate_peer = persistent_context.to_peer.clone();
402
403 let mut ctx = env.context_factory().new_context(persistent_context);
404 let table_info = new_test_table_info(1024);
405 let region_routes = vec![RegionRoute {
406 region: Region::new_test(RegionId::new(1024, 1)),
407 leader_peer: Some(candidate_peer),
408 follower_peers: vec![Peer::empty(2), Peer::empty(3)],
409 leader_state: None,
410 leader_down_since: None,
411 write_route_policy: None,
412 }];
413
414 env.create_physical_table_metadata(table_info, region_routes)
415 .await;
416
417 let table_routes = ctx.get_table_route_value(1024).await.unwrap();
418 let region_routes = table_routes.region_routes().unwrap();
419 let updated = state
420 .check_metadata_updated(&mut ctx, &[RegionId::new(1024, 1)], region_routes)
421 .unwrap();
422 assert!(updated);
423 }
424
425 #[tokio::test]
426 async fn test_check_metadata_intermediate_state() {
427 let state = UpdateMetadata::Upgrade;
428 let env = TestingEnv::new();
429 let persistent_context = new_persistent_context();
430 let candidate_peer = persistent_context.to_peer.clone();
431
432 let mut ctx = env.context_factory().new_context(persistent_context);
433 let table_info = new_test_table_info(1024);
434 let region_routes = vec![RegionRoute {
435 region: Region::new_test(RegionId::new(1024, 1)),
436 leader_peer: Some(candidate_peer),
437 follower_peers: vec![Peer::empty(2), Peer::empty(3)],
438 leader_state: Some(LeaderState::Downgrading),
439 leader_down_since: None,
440 write_route_policy: None,
441 }];
442
443 env.create_physical_table_metadata(table_info, region_routes)
444 .await;
445
446 let table_routes = ctx.get_table_route_value(1024).await.unwrap();
447 let region_routes = table_routes.region_routes().unwrap();
448 let err = state
449 .check_metadata_updated(&mut ctx, &[RegionId::new(1024, 1)], region_routes)
450 .unwrap_err();
451 assert_matches!(err, Error::Unexpected { .. });
452 assert!(err.to_string().contains("intermediate state"));
453 }
454
455 #[tokio::test]
456 async fn test_next_close_downgraded_region_state() {
457 let mut state = Box::new(UpdateMetadata::Upgrade);
458 let env = TestingEnv::new();
459 let persistent_context = new_persistent_context();
460 let mut ctx = env.context_factory().new_context(persistent_context);
461 let opening_keeper = MemoryRegionKeeper::default();
462
463 let table_id = 1024;
464 let table_info = new_test_table_info(table_id);
465 let region_routes = vec![RegionRoute {
466 region: Region::new_test(RegionId::new(table_id, 1)),
467 leader_peer: Some(Peer::empty(1)),
468 leader_state: Some(LeaderState::Downgrading),
469 ..Default::default()
470 }];
471
472 let guard = opening_keeper
473 .register_with_role(2, RegionId::new(table_id, 1), RegionRole::Follower)
474 .unwrap();
475 ctx.volatile_ctx.opening_region_guards.push(guard);
476
477 env.create_physical_table_metadata(table_info, region_routes)
478 .await;
479
480 let table_metadata_manager = env.table_metadata_manager();
481
482 let procedure_ctx = new_procedure_context();
483 let (next, _) = state.next(&mut ctx, &procedure_ctx).await.unwrap();
484
485 let _ = next
486 .as_any()
487 .downcast_ref::<CloseDowngradedRegion>()
488 .unwrap();
489
490 let table_route = table_metadata_manager
491 .table_route_manager()
492 .table_route_storage()
493 .get(table_id)
494 .await
495 .unwrap()
496 .unwrap();
497 let region_routes = table_route.region_routes().unwrap();
498
499 assert!(ctx.volatile_ctx.opening_region_guards.is_empty());
500 assert_eq!(region_routes.len(), 1);
501 assert!(!region_routes[0].is_leader_downgrading());
502 assert!(region_routes[0].follower_peers.is_empty());
503 assert_eq!(region_routes[0].leader_peer.as_ref().unwrap().id, 2);
504 }
505}