datanode/heartbeat/handler/
remap_manifest.rs1use common_meta::instruction::{
16 InstructionError, InstructionReply, RemapManifest, RemapManifestReply,
17};
18use common_telemetry::{error, info, warn};
19use store_api::region_engine::RemapManifestsRequest;
20
21use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
22
23pub struct RemapManifestHandler;
24
25#[async_trait::async_trait]
26impl InstructionHandler for RemapManifestHandler {
27 type Instruction = RemapManifest;
28 async fn handle(
29 &self,
30 ctx: &HandlerContext,
31 request: Self::Instruction,
32 ) -> Option<InstructionReply> {
33 let RemapManifest {
34 region_id,
35 input_regions,
36 region_mapping,
37 new_partition_exprs,
38 } = request;
39 info!(
40 "Datanode received remap manifest request, region_id: {}, input_regions: {}, target_regions: {}",
41 region_id,
42 input_regions.len(),
43 new_partition_exprs.len()
44 );
45 let Some(leader) = ctx.region_server.is_region_leader(region_id) else {
46 warn!("Region: {} is not found", region_id);
47 return Some(InstructionReply::RemapManifest(RemapManifestReply {
48 exists: false,
49 manifest_paths: Default::default(),
50 error: None,
51 }));
52 };
53
54 if !leader {
55 warn!("Region: {} is not leader", region_id);
56 return Some(InstructionReply::RemapManifest(RemapManifestReply {
57 exists: true,
58 manifest_paths: Default::default(),
59 error: Some(InstructionError::legacy_internal_retryable(
60 "Region is not leader",
61 )),
62 }));
63 }
64
65 let reply = match ctx
66 .region_server
67 .remap_manifests(RemapManifestsRequest {
68 region_id,
69 input_regions,
70 region_mapping,
71 new_partition_exprs,
72 })
73 .await
74 {
75 Ok(result) => InstructionReply::RemapManifest(RemapManifestReply {
76 exists: true,
77 manifest_paths: result.manifest_paths,
78 error: None,
79 }),
80 Err(e) => {
81 error!(
82 e;
83 "Remap manifests failed on datanode, region_id: {}",
84 region_id
85 );
86 InstructionReply::RemapManifest(RemapManifestReply {
87 exists: true,
88 manifest_paths: Default::default(),
89 error: Some(InstructionError::from_error(&e)),
90 })
91 }
92 };
93
94 Some(reply)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::collections::HashMap;
101 use std::sync::Arc;
102
103 use common_meta::instruction::RemapManifest;
104 use common_meta::kv_backend::memory::MemoryKvBackend;
105 use datatypes::value::Value;
106 use mito2::config::MitoConfig;
107 use mito2::engine::MITO_ENGINE_NAME;
108 use mito2::test_util::{CreateRequestBuilder, TestEnv};
109 use partition::expr::{PartitionExpr, col};
110 use store_api::path_utils::table_dir;
111 use store_api::region_engine::RegionRole;
112 use store_api::region_request::{
113 EnterStagingRequest, RegionRequest, StagingPartitionDirective,
114 };
115 use store_api::storage::RegionId;
116
117 use crate::heartbeat::handler::remap_manifest::RemapManifestHandler;
118 use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
119 use crate::region_server::RegionServer;
120 use crate::tests::{MockRegionEngine, mock_region_server};
121
122 #[tokio::test]
123 async fn test_region_not_exist() {
124 let mut mock_region_server = mock_region_server();
125 let (mock_engine, _) = MockRegionEngine::new(MITO_ENGINE_NAME);
126 mock_region_server.register_engine(mock_engine);
127 let kv_backend = Arc::new(MemoryKvBackend::new());
128 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
129 let region_id = RegionId::new(1024, 1);
130 let reply = RemapManifestHandler
131 .handle(
132 &handler_context,
133 RemapManifest {
134 region_id,
135 input_regions: vec![],
136 region_mapping: HashMap::new(),
137 new_partition_exprs: HashMap::new(),
138 },
139 )
140 .await
141 .unwrap();
142 let reply = &reply.expect_remap_manifest_reply();
143 assert!(!reply.exists);
144 assert!(reply.error.is_none());
145 assert!(reply.manifest_paths.is_empty());
146 }
147
148 #[tokio::test]
149 async fn test_region_not_leader() {
150 let mock_region_server = mock_region_server();
151 let region_id = RegionId::new(1024, 1);
152 let (mock_engine, _) =
153 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
154 region_engine.mock_role = Some(Some(RegionRole::Follower));
155 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| Ok(0)));
156 });
157 mock_region_server.register_test_region(region_id, mock_engine);
158 let kv_backend = Arc::new(MemoryKvBackend::new());
159 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
160 let reply = RemapManifestHandler
161 .handle(
162 &handler_context,
163 RemapManifest {
164 region_id,
165 input_regions: vec![],
166 region_mapping: HashMap::new(),
167 new_partition_exprs: HashMap::new(),
168 },
169 )
170 .await
171 .unwrap();
172 let reply = reply.expect_remap_manifest_reply();
173 assert!(reply.exists);
174 assert!(reply.error.is_some());
175 }
176
177 fn range_expr(col_name: &str, start: i64, end: i64) -> PartitionExpr {
178 col(col_name)
179 .gt_eq(Value::Int64(start))
180 .and(col(col_name).lt(Value::Int64(end)))
181 }
182
183 async fn prepare_region(region_server: &RegionServer) {
184 let region_specs = [
185 (RegionId::new(1024, 1), range_expr("x", 0, 50)),
186 (RegionId::new(1024, 2), range_expr("x", 50, 100)),
187 ];
188
189 for (region_id, partition_expr) in region_specs {
190 let builder = CreateRequestBuilder::new();
191 let mut create_req = builder.build();
192 create_req.table_dir = table_dir("test", 1024);
193 region_server
194 .handle_request(region_id, RegionRequest::Create(create_req))
195 .await
196 .unwrap();
197 region_server
198 .handle_request(
199 region_id,
200 RegionRequest::EnterStaging(EnterStagingRequest {
201 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(
202 partition_expr.as_json_str().unwrap(),
203 ),
204 }),
205 )
206 .await
207 .unwrap();
208 }
209 }
210
211 #[tokio::test]
212 async fn test_remap_manifest() {
213 common_telemetry::init_default_ut_logging();
214 let mut region_server = mock_region_server();
215 let region_id = RegionId::new(1024, 1);
216 let mut engine_env = TestEnv::new().await;
217 let engine = engine_env.create_engine(MitoConfig::default()).await;
218 region_server.register_engine(Arc::new(engine.clone()));
219 prepare_region(®ion_server).await;
220
221 let kv_backend = Arc::new(MemoryKvBackend::new());
222 let handler_context = HandlerContext::new_for_test(region_server, kv_backend);
223 let region_id2 = RegionId::new(1024, 2);
224 let reply = RemapManifestHandler
225 .handle(
226 &handler_context,
227 RemapManifest {
228 region_id,
229 input_regions: vec![region_id, region_id2],
230 region_mapping: HashMap::from([
231 (region_id, vec![region_id]),
232 (region_id2, vec![region_id]),
233 ]),
234 new_partition_exprs: HashMap::from([(
235 region_id,
236 range_expr("x", 0, 100).as_json_str().unwrap(),
237 )]),
238 },
239 )
240 .await
241 .unwrap();
242 let reply = reply.expect_remap_manifest_reply();
243 assert!(reply.exists);
244 assert!(reply.error.is_none(), "{}", reply.error.unwrap());
245 assert_eq!(reply.manifest_paths.len(), 1);
246
247 let reply = RemapManifestHandler
249 .handle(
250 &handler_context,
251 RemapManifest {
252 region_id,
253 input_regions: vec![region_id],
254 region_mapping: HashMap::from([
255 (region_id, vec![region_id]),
256 (region_id2, vec![region_id]),
257 ]),
258 new_partition_exprs: HashMap::from([(
259 region_id,
260 range_expr("x", 0, 100).as_json_str().unwrap(),
261 )]),
262 },
263 )
264 .await
265 .unwrap();
266 let reply = reply.expect_remap_manifest_reply();
267 assert!(reply.exists);
268 assert!(reply.error.is_some());
269 assert!(reply.manifest_paths.is_empty());
270 }
271}