datanode/heartbeat/handler/
enter_staging.rs1use common_meta::instruction::{
16 EnterStagingRegion, EnterStagingRegionReply, EnterStagingRegionsReply, InstructionError,
17 InstructionReply, StagingPartitionDirective as InstructionStagingPartitionDirective,
18};
19use common_telemetry::{error, warn};
20use futures::future::join_all;
21use store_api::region_request::{
22 EnterStagingRequest, RegionRequest,
23 StagingPartitionDirective as RequestStagingPartitionDirective,
24};
25
26use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
27
28#[derive(Debug, Clone, Copy, Default)]
29pub struct EnterStagingRegionsHandler;
30
31#[async_trait::async_trait]
32impl InstructionHandler for EnterStagingRegionsHandler {
33 type Instruction = Vec<EnterStagingRegion>;
34
35 async fn handle(
36 &self,
37 ctx: &HandlerContext,
38 enter_staging: Self::Instruction,
39 ) -> Option<InstructionReply> {
40 let futures = enter_staging.into_iter().map(|enter_staging_region| {
41 Self::handle_enter_staging_region(ctx, enter_staging_region)
42 });
43 let results = join_all(futures).await;
44 Some(InstructionReply::EnterStagingRegions(
45 EnterStagingRegionsReply::new(results),
46 ))
47 }
48}
49
50impl EnterStagingRegionsHandler {
51 async fn handle_enter_staging_region(
52 ctx: &HandlerContext,
53 EnterStagingRegion {
54 region_id,
55 partition_directive,
56 }: EnterStagingRegion,
57 ) -> EnterStagingRegionReply {
58 let Some(writable) = ctx.region_server.is_region_leader(region_id) else {
59 warn!("Region: {} is not found", region_id);
60 return EnterStagingRegionReply {
61 region_id,
62 ready: false,
63 exists: false,
64 error: None,
65 };
66 };
67 if !writable {
68 warn!("Region: {} is not writable", region_id);
69 return EnterStagingRegionReply {
70 region_id,
71 ready: false,
72 exists: true,
73 error: Some(InstructionError::legacy_internal_retryable(
74 "Region is not writable",
75 )),
76 };
77 }
78
79 common_telemetry::info!("Datanode received enter staging region: {}", region_id);
80
81 let partition_directive = match partition_directive {
82 InstructionStagingPartitionDirective::UpdatePartitionExpr(expr) => {
83 RequestStagingPartitionDirective::UpdatePartitionExpr(expr)
84 }
85 InstructionStagingPartitionDirective::RejectAllWrites => {
86 RequestStagingPartitionDirective::RejectAllWrites
87 }
88 };
89
90 match ctx
91 .region_server
92 .handle_request(
93 region_id,
94 RegionRequest::EnterStaging(EnterStagingRequest {
95 partition_directive,
96 }),
97 )
98 .await
99 {
100 Ok(_) => EnterStagingRegionReply {
101 region_id,
102 ready: true,
103 exists: true,
104 error: None,
105 },
106 Err(err) => {
107 error!(err; "Failed to enter staging region, region_id: {}", region_id);
108 EnterStagingRegionReply {
109 region_id,
110 ready: false,
111 exists: true,
112 error: Some(InstructionError::from_error(&err)),
113 }
114 }
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::sync::Arc;
122
123 use common_meta::instruction::{
124 EnterStagingRegion, StagingPartitionDirective as InstructionStagingPartitionDirective,
125 };
126 use common_meta::kv_backend::memory::MemoryKvBackend;
127 use mito2::config::MitoConfig;
128 use mito2::engine::MITO_ENGINE_NAME;
129 use mito2::test_util::{CreateRequestBuilder, TestEnv};
130 use store_api::path_utils::table_dir;
131 use store_api::region_engine::RegionRole;
132 use store_api::region_request::RegionRequest;
133 use store_api::storage::RegionId;
134
135 use crate::heartbeat::handler::enter_staging::EnterStagingRegionsHandler;
136 use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
137 use crate::region_server::RegionServer;
138 use crate::tests::{MockRegionEngine, mock_region_server};
139
140 const PARTITION_EXPR: &str = "partition_expr";
141
142 #[tokio::test]
143 async fn test_region_not_exist() {
144 let mut mock_region_server = mock_region_server();
145 let (mock_engine, _) = MockRegionEngine::new(MITO_ENGINE_NAME);
146 mock_region_server.register_engine(mock_engine);
147 let kv_backend = Arc::new(MemoryKvBackend::new());
148 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
149 let region_id = RegionId::new(1024, 1);
150 let replies = EnterStagingRegionsHandler
151 .handle(
152 &handler_context,
153 vec![EnterStagingRegion {
154 region_id,
155 partition_directive: InstructionStagingPartitionDirective::UpdatePartitionExpr(
156 "".to_string(),
157 ),
158 }],
159 )
160 .await
161 .unwrap();
162 let replies = replies.expect_enter_staging_regions_reply();
163 let reply = &replies[0];
164 assert!(!reply.exists);
165 assert!(reply.error.is_none());
166 assert!(!reply.ready);
167 }
168
169 #[tokio::test]
170 async fn test_region_not_writable() {
171 let mock_region_server = mock_region_server();
172 let region_id = RegionId::new(1024, 1);
173 let (mock_engine, _) =
174 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
175 region_engine.mock_role = Some(Some(RegionRole::Follower));
176 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| Ok(0)));
177 });
178 mock_region_server.register_test_region(region_id, mock_engine);
179 let kv_backend = Arc::new(MemoryKvBackend::new());
180 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
181 let replies = EnterStagingRegionsHandler
182 .handle(
183 &handler_context,
184 vec![EnterStagingRegion {
185 region_id,
186 partition_directive: InstructionStagingPartitionDirective::UpdatePartitionExpr(
187 "".to_string(),
188 ),
189 }],
190 )
191 .await
192 .unwrap();
193 let replies = replies.expect_enter_staging_regions_reply();
194 let reply = &replies[0];
195 assert!(reply.exists);
196 assert!(reply.error.is_some());
197 assert!(!reply.ready);
198 }
199
200 async fn prepare_region(region_server: &RegionServer) {
201 let builder = CreateRequestBuilder::new();
202 let mut create_req = builder.build();
203 create_req.table_dir = table_dir("test", 1024);
204 let region_id = RegionId::new(1024, 1);
205 region_server
206 .handle_request(region_id, RegionRequest::Create(create_req))
207 .await
208 .unwrap();
209 }
210
211 #[tokio::test]
212 async fn test_enter_staging() {
213 let mut region_server = mock_region_server();
214 let region_id = RegionId::new(1024, 1);
215 let mut engine_env = TestEnv::new().await;
216 let engine = engine_env.create_engine(MitoConfig::default()).await;
217 region_server.register_engine(Arc::new(engine.clone()));
218 prepare_region(®ion_server).await;
219
220 let kv_backend = Arc::new(MemoryKvBackend::new());
221 let handler_context = HandlerContext::new_for_test(region_server, kv_backend);
222 let replies = EnterStagingRegionsHandler
223 .handle(
224 &handler_context,
225 vec![EnterStagingRegion {
226 region_id,
227 partition_directive: InstructionStagingPartitionDirective::UpdatePartitionExpr(
228 PARTITION_EXPR.to_string(),
229 ),
230 }],
231 )
232 .await
233 .unwrap();
234 let replies = replies.expect_enter_staging_regions_reply();
235 let reply = &replies[0];
236 assert!(reply.exists);
237 assert!(reply.error.is_none());
238 assert!(reply.ready);
239
240 let replies = EnterStagingRegionsHandler
242 .handle(
243 &handler_context,
244 vec![EnterStagingRegion {
245 region_id,
246 partition_directive: InstructionStagingPartitionDirective::UpdatePartitionExpr(
247 PARTITION_EXPR.to_string(),
248 ),
249 }],
250 )
251 .await
252 .unwrap();
253 let replies = replies.expect_enter_staging_regions_reply();
254 let reply = &replies[0];
255 assert!(reply.exists);
256 assert!(reply.error.is_none());
257 assert!(reply.ready);
258
259 let replies = EnterStagingRegionsHandler
261 .handle(
262 &handler_context,
263 vec![EnterStagingRegion {
264 region_id,
265 partition_directive: InstructionStagingPartitionDirective::UpdatePartitionExpr(
266 "".to_string(),
267 ),
268 }],
269 )
270 .await
271 .unwrap();
272 let replies = replies.expect_enter_staging_regions_reply();
273 let reply = &replies[0];
274 assert!(reply.exists);
275 assert!(reply.error.is_some());
276 assert!(!reply.ready);
277 }
278
279 #[tokio::test]
280 async fn test_enter_staging_reject_all_writes() {
281 let mut region_server = mock_region_server();
282 let region_id = RegionId::new(1024, 1);
283 let mut engine_env = TestEnv::new().await;
284 let engine = engine_env.create_engine(MitoConfig::default()).await;
285 region_server.register_engine(Arc::new(engine.clone()));
286 prepare_region(®ion_server).await;
287
288 let kv_backend = Arc::new(MemoryKvBackend::new());
289 let handler_context = HandlerContext::new_for_test(region_server, kv_backend);
290 let replies = EnterStagingRegionsHandler
291 .handle(
292 &handler_context,
293 vec![EnterStagingRegion {
294 region_id,
295 partition_directive: InstructionStagingPartitionDirective::RejectAllWrites,
296 }],
297 )
298 .await
299 .unwrap();
300 let replies = replies.expect_enter_staging_regions_reply();
301 let reply = &replies[0];
302 assert!(reply.exists);
303 assert!(reply.ready);
304 assert!(reply.error.is_none());
305 }
306}