Coverage Report

Created: 2026-10-17 09:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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