common/cpp/obj-client/obj_storage_client.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #pragma once |
19 | | |
20 | | #include <gen_cpp/Status_types.h> |
21 | | |
22 | | #include <cstdint> |
23 | | #include <filesystem> |
24 | | #include <functional> |
25 | | #include <memory> |
26 | | #include <optional> |
27 | | #include <string> |
28 | | #include <string_view> |
29 | | #include <utility> |
30 | | #include <vector> |
31 | | |
32 | | namespace doris { |
33 | | // Names are in lexico order. |
34 | | enum class ObjStorageProvider : uint8_t { |
35 | | UNKNOWN = 0, |
36 | | AWS = 1, |
37 | | AZURE = 2, |
38 | | BOS = 3, |
39 | | COS = 4, |
40 | | OSS = 5, |
41 | | OBS = 6, |
42 | | GCP = 7, |
43 | | TOS = 8, |
44 | | }; |
45 | | |
46 | | /// eg: |
47 | | /// s3://bucket1/path/to/file.txt |
48 | | /// path: s3://bucket1/path/to/file.txt |
49 | | /// bucket: bucket1 |
50 | | /// key: path/to/file.txt |
51 | | struct ObjStoragePath { |
52 | | std::filesystem::path path = ""; |
53 | | std::string bucket {}; // blob container in azure |
54 | | std::string key {}; // blob name in azure |
55 | | std::string prefix {}; // for list and recursive delete |
56 | | |
57 | | // Empty means recursive listing; "/" lists direct children. |
58 | | std::string delimiter {}; // NOLINT(readability-redundant-member-init) |
59 | | }; |
60 | | |
61 | | struct ObjStorageEndpointInfo { |
62 | | std::string endpoint {}; |
63 | | std::string ak {}; |
64 | | std::string sk {}; |
65 | | std::string tls_debug_context {}; |
66 | | }; |
67 | | |
68 | | struct ObjectMeta { |
69 | | std::string key {}; |
70 | | int64_t size {0}; |
71 | | int64_t mtime_s {0}; |
72 | | std::optional<std::string> etag {}; |
73 | | }; |
74 | | |
75 | | struct ObjStorageCompletedPart { |
76 | | int part_num = 0; |
77 | | std::string etag {}; |
78 | | }; |
79 | | |
80 | | struct ObjStorageStatus { |
81 | | enum Code : int { |
82 | | OK = TStatusCode::OK, |
83 | | INTERNAL_ERROR = TStatusCode::INTERNAL_ERROR, |
84 | | LIMIT_REACH = TStatusCode::LIMIT_REACH, |
85 | | NOT_FOUND = TStatusCode::NOT_FOUND, |
86 | | END_OF_FILE = TStatusCode::END_OF_FILE, |
87 | | IO_ERROR = TStatusCode::IO_ERROR, |
88 | | NETWORK_ERROR = TStatusCode::NETWORK_ERROR, |
89 | | // Keep the legacy BE error code for access-denied object storage responses. |
90 | | PERMISSION_DENIED = -256, |
91 | | }; |
92 | | |
93 | 17 | ObjStorageStatus(int r = OK, std::string msg = "") : code(r), msg(std::move(msg)) {} |
94 | | // clang-format off |
95 | | int code {OK}; // To unify the error handle logic with BE, we'd better use the same error code as BE |
96 | | // clang-format on |
97 | | std::string msg; |
98 | | }; |
99 | | |
100 | | // We only store error code along with err_msg instead of Status to unify BE and recycler's error handle logic |
101 | | struct ObjStorageResponse { |
102 | | ObjStorageStatus status {ObjStorageStatus::OK, ""}; |
103 | | int http_code {200}; |
104 | | std::string request_id {}; |
105 | 0 | static ObjStorageResponse OK() { |
106 | | // clang-format off |
107 | 0 | return { |
108 | 0 | .status = ObjStorageStatus{ObjStorageStatus::OK, ""}, |
109 | 0 | .http_code = 200, |
110 | 0 | .request_id = "" |
111 | 0 | }; |
112 | | // clang-format on |
113 | 0 | } |
114 | | |
115 | 0 | static ObjStorageResponse rate_limit(int status_code, int http_code, std::string message) { |
116 | 0 | return { |
117 | 0 | .status = ObjStorageStatus {status_code, std::move(message)}, |
118 | 0 | .http_code = http_code, |
119 | 0 | }; |
120 | 0 | } |
121 | | |
122 | 339k | bool ok() const { return status.code == ObjStorageStatus::OK; } |
123 | | }; |
124 | | |
125 | | // Convert a provider HTTP response code into the object-storage status domain. A non-positive |
126 | | // response code means that no HTTP response was received. |
127 | | ObjStorageStatus obj_storage_status_from_http_code(int http_code, std::string message); |
128 | | |
129 | | struct ObjStorageUploadResult { |
130 | | ObjStorageResponse resp = ObjStorageResponse::OK(); |
131 | | std::optional<std::string> upload_id = std::nullopt; |
132 | | std::optional<std::string> etag = std::nullopt; |
133 | | }; |
134 | | |
135 | | struct ObjStorageHeadResult { |
136 | | ObjStorageResponse resp = ObjStorageResponse::OK(); |
137 | | long long file_size {0}; |
138 | | std::optional<std::string> content_type {}; |
139 | | std::optional<std::string> etag {}; |
140 | | }; |
141 | | |
142 | | struct ObjStorageListResult { |
143 | | ObjStorageResponse resp = ObjStorageResponse::OK(); |
144 | | std::optional<ObjectMeta> object = std::nullopt; |
145 | | }; |
146 | | |
147 | | struct ObjStorageListPageResult { |
148 | | ObjStorageResponse resp = ObjStorageResponse::OK(); |
149 | | std::vector<ObjectMeta> objects {}; |
150 | | std::string continuation_token {}; |
151 | | bool has_more = false; |
152 | | }; |
153 | | |
154 | | struct ObjStorageCapabilities { |
155 | | size_t max_delete_batch = 1; |
156 | | size_t max_list_page = 1000; |
157 | | }; |
158 | | |
159 | | using ObjStorageDeleteTask = std::function<ObjStorageResponse()>; |
160 | | |
161 | | // Implementations must enqueue tasks immediately and remain reusable after wait() completes. |
162 | | // Recycler implements this in cloud/src/recycler/s3_accessor.cpp; BE uses the synchronous fallback. |
163 | | class ObjStorageDeleteExecutor { |
164 | | public: |
165 | | virtual ~ObjStorageDeleteExecutor() = default; |
166 | | virtual ObjStorageResponse submit(ObjStorageDeleteTask task) = 0; |
167 | | virtual ObjStorageResponse wait() = 0; |
168 | | }; |
169 | | |
170 | | struct ObjStorageRecursiveDeleteOptions { |
171 | | int64_t expiration_time = 0; |
172 | | size_t max_tasks_per_batch = 1; |
173 | | std::shared_ptr<ObjStorageDeleteExecutor> executor {}; |
174 | | }; |
175 | | |
176 | | class ObjStorageListIterator; |
177 | | class RateLimitedObjStorageClient; |
178 | | |
179 | | // Provider implementations are in common/cpp/obj-client/s3_obj_storage_client.cpp and |
180 | | // common/cpp/obj-client/azure_obj_storage_client.cpp. |
181 | | // Clients are shared-owned so a lazy list iterator can keep the complete decorator chain alive. |
182 | | class ObjStorageClient : public std::enable_shared_from_this<ObjStorageClient> { |
183 | | public: |
184 | | ObjStorageClient() = default; |
185 | 0 | virtual ~ObjStorageClient() = default; |
186 | | |
187 | | ObjStorageClient(const ObjStorageClient&) = delete; |
188 | | ObjStorageClient& operator=(const ObjStorageClient&) = delete; |
189 | | // Create a multipart upload request. The returned token may be provider-issued or local and |
190 | | // identifies this writer's parts. |
191 | | // The input parameters should include the bucket and key for the object storage. |
192 | | virtual ObjStorageUploadResult create_multipart_upload(const ObjStoragePath& opts) = 0; |
193 | | // To directly upload a piece of data to object storage and generate a user-visible file. |
194 | | // You need to clearly specify the bucket and key |
195 | | virtual ObjStorageResponse put_object(const ObjStoragePath& opts, std::string_view stream) = 0; |
196 | | // Upload one part of a large object without making the object visible to users. upload_id is |
197 | | // the provider-issued or local writer token returned by create_multipart_upload. |
198 | | // Reusing the same bucket, key, upload_id, and part_num replaces that writer's staged part. |
199 | | virtual ObjStorageUploadResult upload_part(const ObjStoragePath& opts, |
200 | | const std::string& upload_id, |
201 | | std::string_view stream, int part_num) = 0; |
202 | | // Combine the parts belonging to upload_id into the key passed in opts. After this succeeds, |
203 | | // the complete object is visible in object storage. |
204 | | virtual ObjStorageResponse complete_multipart_upload( |
205 | | const ObjStoragePath& opts, const std::string& upload_id, |
206 | | const std::vector<ObjStorageCompletedPart>& completed_parts) = 0; |
207 | | // According to the passed bucket and key, it will access whether the corresponding file exists in the object storage. |
208 | | // If it exists, it will return the corresponding file size |
209 | | virtual ObjStorageHeadResult head_object(const ObjStoragePath& opts) = 0; |
210 | | // According to the bucket and key, it finds the corresponding file in the object storage |
211 | | // and starting from the offset, it reads bytes_read bytes into the buffer, with size_return recording the actual number of bytes read |
212 | | virtual ObjStorageResponse get_object(const ObjStoragePath& opts, void* buffer, size_t offset, |
213 | | size_t bytes_read, size_t* size_return) = 0; |
214 | | // Return a lazy iterator that fetches one provider page at a time. |
215 | | virtual std::unique_ptr<ObjStorageListIterator> list_objects(const ObjStoragePath& opts); |
216 | | // Collect all objects by consuming the lazy iterator. This preserves the eager BE API. |
217 | | // **Notice**: The files returned by this function contain the full key in object storage. |
218 | | virtual ObjStorageResponse list_objects(const ObjStoragePath& opts, |
219 | | std::vector<ObjectMeta>* objects); |
220 | | |
221 | | // According to the bucket and prefix specified by the user, it performs batch deletion based on the object names in the object array. |
222 | | virtual ObjStorageResponse delete_objects(const ObjStoragePath& opts, |
223 | | std::vector<std::string> objs) = 0; |
224 | | // Delete the file named key in the object storage bucket. |
225 | | virtual ObjStorageResponse delete_object(const ObjStoragePath& opts) = 0; |
226 | | virtual ObjStorageCapabilities capabilities() const = 0; |
227 | | // Return a presigned URL for users to access the object |
228 | | virtual std::string generate_presigned_url(const ObjStoragePath& opts, |
229 | | int64_t expiration_secs) = 0; |
230 | | |
231 | | // Get the objects' expiration time on the bucket |
232 | | virtual ObjStorageResponse get_lifecycle(const std::string& bucket, |
233 | | int64_t* expiration_days) = 0; |
234 | | |
235 | | // Check if the objects' versioning is on or off |
236 | | // returns 0 when versioning is on, otherwise versioning is off or check failed |
237 | | virtual ObjStorageResponse check_versioning(const std::string& bucket) = 0; |
238 | | |
239 | | virtual ObjStorageResponse abort_multipart_upload(const ObjStoragePath& path, |
240 | | const std::string& upload_id) = 0; |
241 | | |
242 | | // Fetch at most one page. One call corresponds to exactly one provider request. |
243 | | virtual ObjStorageListPageResult list_objects_page(const ObjStoragePath& opts, |
244 | | std::string_view continuation_token) = 0; |
245 | | |
246 | | private: |
247 | | friend class ObjStorageListIterator; |
248 | | friend class RateLimitedObjStorageClient; |
249 | | }; |
250 | | |
251 | | // A client-side iterator returned by ObjStorageClient. It requests one fixed-size page at a time. |
252 | | class ObjStorageListIterator { |
253 | | public: |
254 | | ObjStorageListIterator(std::shared_ptr<ObjStorageClient> client, ObjStoragePath opts) |
255 | 17 | : client_(std::move(client)), opts_(std::move(opts)) {} |
256 | | |
257 | 0 | bool is_valid() const { return is_valid_; } |
258 | | ObjStorageResponse has_next(); |
259 | | ObjStorageListResult next(); |
260 | | |
261 | | private: |
262 | | std::shared_ptr<ObjStorageClient> client_; |
263 | | ObjStoragePath opts_; |
264 | | std::vector<ObjectMeta> objects_; |
265 | | size_t next_index_ = 0; |
266 | | std::string continuation_token_; |
267 | | bool has_more_ = true; |
268 | | bool is_valid_ = true; |
269 | | }; |
270 | | |
271 | | // Provider-independent recursive deletion shared by concrete clients. Passing the complete client |
272 | | // decorator chain keeps it alive for asynchronous delete tasks and applies policy per list page and |
273 | | // delete batch. |
274 | | ObjStorageResponse delete_objects_recursively(std::shared_ptr<ObjStorageClient> client, |
275 | | const ObjStoragePath& path, |
276 | | const ObjStorageRecursiveDeleteOptions& options = {}); |
277 | | } // namespace doris |
278 | | |
279 | | // Keep the BE namespace spelling source-compatible while the implementation is |
280 | | // shared with Recycler in `doris`. |
281 | | namespace doris::io { |
282 | | using ::doris::ObjStorageCapabilities; |
283 | | using ::doris::ObjStorageClient; |
284 | | using ::doris::ObjStorageCompletedPart; |
285 | | using ::doris::ObjStorageDeleteExecutor; |
286 | | using ::doris::ObjStorageDeleteTask; |
287 | | using ::doris::ObjStorageEndpointInfo; |
288 | | using ::doris::ObjStorageHeadResult; |
289 | | using ::doris::ObjStorageListIterator; |
290 | | using ::doris::ObjStorageListPageResult; |
291 | | using ::doris::ObjStorageListResult; |
292 | | using ::doris::ObjStoragePath; |
293 | | using ::doris::ObjStorageProvider; |
294 | | using ::doris::ObjStorageRecursiveDeleteOptions; |
295 | | using ::doris::ObjStorageResponse; |
296 | | using ::doris::ObjStorageStatus; |
297 | | using ::doris::ObjStorageUploadResult; |
298 | | using ::doris::ObjectMeta; |
299 | | using ::doris::delete_objects_recursively; |
300 | | } // namespace doris::io |