Skip to main content

file_engine/
engine.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::collections::HashMap;
17use std::sync::{Arc, RwLock};
18
19use api::region::RegionResponse;
20use async_trait::async_trait;
21use common_catalog::consts::FILE_ENGINE;
22use common_error::ext::BoxedError;
23use common_recordbatch::SendableRecordBatchStream;
24use common_telemetry::{error, info};
25use object_store::ObjectStore;
26use snafu::{OptionExt, ensure};
27use store_api::metadata::RegionMetadataRef;
28use store_api::region_engine::{
29    RegionEngine, RegionRole, RegionScannerRef, RegionStatistic, RemapManifestsRequest,
30    RemapManifestsResponse, SetRegionRoleStateResponse, SetRegionRoleStateSuccess,
31    SettableRegionRoleState, SinglePartitionScanner, SyncRegionFromRequest, SyncRegionFromResponse,
32};
33use store_api::region_request::{
34    AffectedRows, RegionCloseRequest, RegionCreateRequest, RegionDropRequest, RegionOpenRequest,
35    RegionRequest, RegionRequirements,
36};
37use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
38use tokio::sync::Mutex;
39
40use crate::config::EngineConfig;
41use crate::error::{
42    RegionNotFoundSnafu, Result as EngineResult, UnexpectedEngineSnafu, UnsupportedSnafu,
43};
44use crate::region::{FileRegion, FileRegionRef};
45
46pub struct FileRegionEngine {
47    inner: EngineInnerRef,
48}
49
50impl FileRegionEngine {
51    pub fn new(_config: EngineConfig, object_store: ObjectStore) -> Self {
52        Self {
53            inner: Arc::new(EngineInner::new(object_store)),
54        }
55    }
56
57    async fn handle_query(
58        &self,
59        region_id: RegionId,
60        request: ScanRequest,
61    ) -> Result<SendableRecordBatchStream, BoxedError> {
62        self.inner
63            .get_region(region_id)
64            .await
65            .context(RegionNotFoundSnafu { region_id })
66            .map_err(BoxedError::new)?
67            .query(request)
68            .map_err(BoxedError::new)
69    }
70}
71
72#[async_trait]
73impl RegionEngine for FileRegionEngine {
74    fn name(&self) -> &str {
75        FILE_ENGINE
76    }
77
78    async fn handle_request(
79        &self,
80        region_id: RegionId,
81        request: RegionRequest,
82    ) -> Result<RegionResponse, BoxedError> {
83        self.inner
84            .handle_request(region_id, request)
85            .await
86            .map_err(BoxedError::new)
87    }
88
89    async fn handle_query(
90        &self,
91        region_id: RegionId,
92        request: ScanRequest,
93    ) -> Result<RegionScannerRef, BoxedError> {
94        let stream = self.handle_query(region_id, request).await?;
95        let metadata = self.get_metadata(region_id).await?;
96        // We don't support enabling append mode for file engine.
97        let scanner = Box::new(SinglePartitionScanner::new(stream, false, metadata, None));
98        Ok(scanner)
99    }
100
101    async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
102        self.inner
103            .get_region(region_id)
104            .await
105            .map(|r| r.metadata())
106            .context(RegionNotFoundSnafu { region_id })
107            .map_err(BoxedError::new)
108    }
109
110    async fn stop(&self) -> Result<(), BoxedError> {
111        self.inner.stop().await.map_err(BoxedError::new)
112    }
113
114    fn region_statistic(&self, _: RegionId) -> Option<RegionStatistic> {
115        None
116    }
117
118    async fn get_committed_sequence(&self, _: RegionId) -> Result<SequenceNumber, BoxedError> {
119        Ok(Default::default())
120    }
121
122    fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
123        self.inner
124            .set_region_role(region_id, role)
125            .map_err(BoxedError::new)
126    }
127
128    async fn set_region_role_state_gracefully(
129        &self,
130        region_id: RegionId,
131        _region_role_state: SettableRegionRoleState,
132    ) -> Result<SetRegionRoleStateResponse, BoxedError> {
133        let exists = self.inner.get_region(region_id).await.is_some();
134
135        if exists {
136            Ok(SetRegionRoleStateResponse::success(
137                SetRegionRoleStateSuccess::file(),
138            ))
139        } else {
140            Ok(SetRegionRoleStateResponse::NotFound)
141        }
142    }
143
144    async fn sync_region(
145        &self,
146        _region_id: RegionId,
147        _request: SyncRegionFromRequest,
148    ) -> Result<SyncRegionFromResponse, BoxedError> {
149        // File engine doesn't need to sync region manifest.
150        Ok(SyncRegionFromResponse::NotSupported)
151    }
152
153    async fn remap_manifests(
154        &self,
155        _request: RemapManifestsRequest,
156    ) -> Result<RemapManifestsResponse, BoxedError> {
157        Err(BoxedError::new(
158            UnsupportedSnafu {
159                operation: "remap_manifests",
160            }
161            .build(),
162        ))
163    }
164
165    fn role(&self, region_id: RegionId) -> Option<RegionRole> {
166        self.inner.state(region_id)
167    }
168
169    fn as_any(&self) -> &dyn Any {
170        self
171    }
172}
173
174struct EngineInner {
175    /// All regions opened by the engine.
176    ///
177    /// Writing to `regions` should also hold the `region_mutex`.
178    regions: RwLock<HashMap<RegionId, FileRegionRef>>,
179
180    /// Region mutex is used to protect the operations such as creating/opening/closing
181    /// a region, to avoid things like opening the same region simultaneously.
182    region_mutex: Mutex<()>,
183
184    object_store: ObjectStore,
185}
186
187type EngineInnerRef = Arc<EngineInner>;
188
189fn ensure_open_requirements(
190    requirements: RegionRequirements,
191    object_store: &ObjectStore,
192) -> EngineResult<()> {
193    if !requirements.object_storage {
194        return Ok(());
195    }
196
197    ensure!(
198        object_store::util::is_object_storage(object_store),
199        UnsupportedSnafu {
200            operation: "open region with object storage requirement on non-object storage"
201        }
202    );
203
204    Ok(())
205}
206
207impl EngineInner {
208    fn new(object_store: ObjectStore) -> Self {
209        Self {
210            regions: RwLock::new(HashMap::new()),
211            region_mutex: Mutex::new(()),
212            object_store,
213        }
214    }
215
216    async fn handle_request(
217        &self,
218        region_id: RegionId,
219        request: RegionRequest,
220    ) -> EngineResult<RegionResponse> {
221        let result = match request {
222            RegionRequest::Create(req) => self.handle_create(region_id, req).await,
223            RegionRequest::Drop(req) => self.handle_drop(region_id, req).await,
224            RegionRequest::Open(req) => self.handle_open(region_id, req).await,
225            RegionRequest::Close(req) => self.handle_close(region_id, req).await,
226            _ => UnsupportedSnafu {
227                operation: request.to_string(),
228            }
229            .fail(),
230        };
231        result.map(RegionResponse::new)
232    }
233
234    async fn stop(&self) -> EngineResult<()> {
235        let _lock = self.region_mutex.lock().await;
236        self.regions.write().unwrap().clear();
237        Ok(())
238    }
239
240    fn set_region_role(&self, _region_id: RegionId, _region_role: RegionRole) -> EngineResult<()> {
241        // TODO(zhongzc): Improve the semantics and implementation of this API.
242        Ok(())
243    }
244
245    fn state(&self, region_id: RegionId) -> Option<RegionRole> {
246        if self.regions.read().unwrap().get(&region_id).is_some() {
247            Some(RegionRole::Leader)
248        } else {
249            None
250        }
251    }
252}
253
254impl EngineInner {
255    async fn handle_create(
256        &self,
257        region_id: RegionId,
258        request: RegionCreateRequest,
259    ) -> EngineResult<AffectedRows> {
260        ensure!(
261            request.engine == FILE_ENGINE,
262            UnexpectedEngineSnafu {
263                engine: request.engine
264            }
265        );
266
267        if self.exists(region_id).await {
268            return Ok(0);
269        }
270
271        info!("Try to create region, region_id: {}", region_id);
272
273        let _lock = self.region_mutex.lock().await;
274        // Check again after acquiring the lock
275        if self.exists(region_id).await {
276            return Ok(0);
277        }
278
279        let res = FileRegion::create(region_id, request, &self.object_store).await;
280        let region = res.inspect_err(|err| {
281            error!(
282                err;
283                "Failed to create region, region_id: {}",
284                region_id
285            );
286        })?;
287        self.regions.write().unwrap().insert(region_id, region);
288
289        info!("A new region is created, region_id: {}", region_id);
290        Ok(0)
291    }
292
293    async fn handle_open(
294        &self,
295        region_id: RegionId,
296        request: RegionOpenRequest,
297    ) -> EngineResult<AffectedRows> {
298        if self.exists(region_id).await {
299            return Ok(0);
300        }
301
302        info!("Try to open region, region_id: {}", region_id);
303
304        let _lock = self.region_mutex.lock().await;
305        // Check again after acquiring the lock
306        if self.exists(region_id).await {
307            return Ok(0);
308        }
309
310        ensure_open_requirements(request.requirements, &self.object_store)?;
311
312        let res = FileRegion::open(region_id, request, &self.object_store).await;
313        let region = res.inspect_err(|err| {
314            error!(
315                err;
316                "Failed to open region, region_id: {}",
317                region_id
318            );
319        })?;
320        self.regions.write().unwrap().insert(region_id, region);
321
322        info!("Region opened, region_id: {}", region_id);
323        Ok(0)
324    }
325
326    async fn handle_close(
327        &self,
328        region_id: RegionId,
329        _request: RegionCloseRequest,
330    ) -> EngineResult<AffectedRows> {
331        let _lock = self.region_mutex.lock().await;
332
333        let mut regions = self.regions.write().unwrap();
334        if regions.remove(&region_id).is_some() {
335            info!("Region closed, region_id: {}", region_id);
336        }
337
338        Ok(0)
339    }
340
341    async fn handle_drop(
342        &self,
343        region_id: RegionId,
344        _request: RegionDropRequest,
345    ) -> EngineResult<AffectedRows> {
346        if !self.exists(region_id).await {
347            return RegionNotFoundSnafu { region_id }.fail();
348        }
349
350        info!("Try to drop region, region_id: {}", region_id);
351
352        let _lock = self.region_mutex.lock().await;
353
354        let region = self.get_region(region_id).await;
355        if let Some(region) = region {
356            let res = FileRegion::drop(&region, &self.object_store).await;
357            res.inspect_err(|err| {
358                error!(
359                    err;
360                    "Failed to drop region, region_id: {}",
361                    region_id
362                );
363            })?;
364        }
365        let _ = self.regions.write().unwrap().remove(&region_id);
366
367        info!("Region dropped, region_id: {}", region_id);
368        Ok(0)
369    }
370
371    async fn get_region(&self, region_id: RegionId) -> Option<FileRegionRef> {
372        self.regions.read().unwrap().get(&region_id).cloned()
373    }
374
375    async fn exists(&self, region_id: RegionId) -> bool {
376        self.regions.read().unwrap().contains_key(&region_id)
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use object_store::services::{Fs, S3};
383
384    use super::*;
385    use crate::error::Error;
386
387    fn build_fs_object_store() -> ObjectStore {
388        ObjectStore::new(Fs::default().root("/tmp"))
389            .unwrap()
390            .finish()
391    }
392
393    fn build_s3_object_store() -> ObjectStore {
394        ObjectStore::new(
395            S3::default()
396                .bucket("test-bucket")
397                .region("us-east-1")
398                .disable_ec2_metadata(),
399        )
400        .unwrap()
401        .finish()
402    }
403
404    #[test]
405    fn test_empty_open_requirements_are_supported() {
406        ensure_open_requirements(RegionRequirements::empty(), &build_fs_object_store()).unwrap();
407    }
408
409    #[test]
410    fn test_object_storage_open_requirement_rejects_fs_object_store() {
411        let err = ensure_open_requirements(
412            RegionRequirements::object_storage(),
413            &build_fs_object_store(),
414        )
415        .unwrap_err();
416
417        assert!(matches!(err, Error::Unsupported { .. }));
418    }
419
420    #[test]
421    fn test_object_storage_open_requirement_accepts_s3_object_store() {
422        ensure_open_requirements(
423            RegionRequirements::object_storage(),
424            &build_s3_object_store(),
425        )
426        .unwrap();
427    }
428}