Coverage Report

Created: 2026-08-18 13:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
common/cpp/obj-client/azure_obj_storage_client.cpp
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
#include "azure_obj_storage_client.h"
19
20
#include <butil/guid.h>
21
22
#include <cctype>
23
#include <cstdint>
24
#include <string_view>
25
#include <vector>
26
27
#include "cpp/obj_retry_strategy.h"
28
29
using namespace Azure::Storage::Blobs;
30
31
namespace {
32
1
std::string wrap_object_storage_path_msg(const doris::ObjStoragePath& opts) {
33
1
    return fmt::format("bucket {}, key {}, prefix {}, path {}", opts.bucket, opts.key, opts.prefix,
34
1
                       opts.path.native());
35
1
}
36
37
1
doris::ObjStoragePath with_object_key(const doris::ObjStoragePath& opts, std::string_view key) {
38
1
    auto path = opts;
39
1
    path.key = key;
40
1
    return path;
41
1
}
42
43
6
std::string to_lower_ascii(std::string_view input) {
44
6
    std::string lowered(input);
45
6
    std::transform(lowered.begin(), lowered.end(), lowered.begin(),
46
231
                   [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
47
6
    return lowered;
48
6
}
49
50
3
std::string encode_azure_block_id(std::string_view upload_id, int part_num) {
51
    // Azure has no multipart upload namespace. Include the writer UUID in every block ID so
52
    // concurrent writers for the same key cannot stage interchangeable blocks.
53
3
    std::vector<unsigned char> raw_id(upload_id.begin(), upload_id.end());
54
3
    auto part = static_cast<uint32_t>(part_num);
55
15
    for (size_t i = 0; i < sizeof(part); ++i) {
56
12
        raw_id.push_back(static_cast<unsigned char>(part >> (i * 8)));
57
12
    }
58
3
    Aws::Utils::ByteBuffer bytes(raw_id.data(), raw_id.size());
59
3
    return Aws::Utils::HashingUtils::Base64Encode(bytes);
60
3
}
61
62
constexpr char SAS_TOKEN_URL_TEMPLATE[] = "{}/{}/{}{}";
63
constexpr char BlobNotFound[] = "BlobNotFound";
64
} // namespace
65
66
namespace doris {
67
68
3
std::string azure_multipart_block_id(std::string_view upload_id, int part_num) {
69
3
    return encode_azure_block_id(upload_id, part_num);
70
3
}
71
72
std::string build_azure_batch_delete_failure_message(const ObjStoragePath& opts,
73
1
                                                     std::string_view key) {
74
1
    return fmt::format("Azure batch delete failed, path msg {}",
75
1
                       wrap_object_storage_path_msg(with_object_key(opts, key)));
76
1
}
77
78
// As Azure's doc said, the batch size is 256
79
// You can find out the num in https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch?tabs=microsoft-entra-id
80
// > Each batch request supports a maximum of 256 subrequests.
81
constexpr size_t BlobBatchMaxOperations = 256;
82
83
6
bool is_azure_tls_ca_error_message(std::string_view message) {
84
6
    std::string lower = to_lower_ascii(message);
85
6
    return lower.find("ssl ca cert") != std::string::npos ||
86
6
           lower.find("peer failed verification") != std::string::npos ||
87
6
           lower.find("unable to get local issuer certificate") != std::string::npos ||
88
6
           lower.find("problem with the ssl ca cert") != std::string::npos;
89
6
}
90
91
std::string build_azure_tls_debug_suffix(std::string_view error_message,
92
3
                                         std::string_view tls_debug_context) {
93
3
    if (tls_debug_context.empty() || !is_azure_tls_ca_error_message(error_message)) {
94
2
        return "";
95
2
    }
96
1
    return fmt::format(", {}", tls_debug_context);
97
3
}
98
99
static ObjStorageResponse make_azure_std_exception_response(const std::exception& e,
100
                                                            const ObjStoragePath& opts,
101
0
                                                            std::string_view tls_debug_context) {
102
0
    auto msg = fmt::format("Azure request failed because {}, path msg {}{}", e.what(),
103
0
                           wrap_object_storage_path_msg(opts),
104
0
                           build_azure_tls_debug_suffix(e.what(), tls_debug_context));
105
0
    LOG(WARNING) << msg;
106
0
    return {.status = ObjStorageStatus {ObjStorageStatus::INTERNAL_ERROR, std::move(msg)},
107
0
            .http_code = 0,
108
0
            .request_id = ""};
109
0
}
110
111
template <typename Func>
112
ObjStorageResponse do_azure_client_call(Func f, const ObjStoragePath& opts,
113
0
                                        std::string_view tls_debug_context) {
114
0
    try {
115
0
        f();
116
0
    } catch (Azure::Core::RequestFailedException& e) {
117
0
        doris::record_object_request_failed(static_cast<int>(e.StatusCode));
118
0
        auto msg = fmt::format(
119
0
                "Azure request failed because {}, error msg {}, http code {}, path msg {}{}",
120
0
                e.what(), e.Message, static_cast<int>(e.StatusCode),
121
0
                wrap_object_storage_path_msg(opts),
122
0
                build_azure_tls_debug_suffix(fmt::format("{} {}", e.what(), e.Message),
123
0
                                             tls_debug_context));
124
0
        LOG(WARNING) << msg;
125
0
        return {.status = obj_storage_status_from_http_code(static_cast<int>(e.StatusCode),
126
0
                                                            std::move(msg)),
127
0
                .http_code = static_cast<int>(e.StatusCode),
128
0
                .request_id = std::move(e.RequestId)};
129
0
    } catch (const std::exception& e) {
130
0
        return make_azure_std_exception_response(e, opts, tls_debug_context);
131
0
    }
132
0
    return ObjStorageResponse::OK();
133
0
}
Unexecuted instantiation: _ZN5doris20do_azure_client_callIZNS_17AzureBatchDeleter7executeEvEUlvE_EENS_18ObjStorageResponseET_RKNS_14ObjStoragePathESt17basic_string_viewIcSt11char_traitsIcEE
Unexecuted instantiation: azure_obj_storage_client.cpp:_ZN5doris20do_azure_client_callIZNS_21AzureObjStorageClient10put_objectERKNS_14ObjStoragePathESt17basic_string_viewIcSt11char_traitsIcEEE3$_0EENS_18ObjStorageResponseET_S4_S8_
Unexecuted instantiation: azure_obj_storage_client.cpp:_ZN5doris20do_azure_client_callIZNS_21AzureObjStorageClient25complete_multipart_uploadERKNS_14ObjStoragePathERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorINS_23ObjStorageCompletedPartESaISE_EEE3$_1EENS_18ObjStorageResponseET_S4_St17basic_string_viewIcS8_E
Unexecuted instantiation: azure_obj_storage_client.cpp:_ZN5doris20do_azure_client_callIZNS_21AzureObjStorageClient10get_objectERKNS_14ObjStoragePathEPvmmPmE3$_0EENS_18ObjStorageResponseET_S4_St17basic_string_viewIcSt11char_traitsIcEE
134
135
struct AzureBatchDeleter {
136
    AzureBatchDeleter(BlobContainerClient* client, const ObjStoragePath& opts,
137
                      std::string_view representative_key, std::string_view tls_debug_context)
138
0
            : _client(client),
139
0
              _batch(client->CreateBatch()),
140
0
              _opts(with_object_key(opts, representative_key)),
141
0
              _tls_debug_context(tls_debug_context) {}
142
    // Submit one blob to be deleted in `AzureBatchDeleter::execute`
143
0
    void delete_blob(const std::string& blob_name) {
144
0
        deferred_resps.emplace_back(DeferredDeleteResponse {
145
0
                .key = blob_name, .response = _batch.DeleteBlob(blob_name)});
146
0
    }
147
0
    ObjStorageResponse execute() {
148
0
        if (deferred_resps.empty()) {
149
0
            return ObjStorageResponse::OK();
150
0
        }
151
0
        auto resp = do_azure_client_call(
152
0
                [&]() {
153
0
                    client_bvar::ScopedLatency scoped_latency(
154
0
                            client_bvar::s3_delete_objects_latency);
155
0
                    _client->SubmitBatch(_batch);
156
0
                },
157
0
                _opts, _tls_debug_context);
158
0
        if (resp.status.code != ObjStorageStatus::OK) {
159
0
            return resp;
160
0
        }
161
162
0
        for (auto&& deferred : deferred_resps) {
163
0
            try {
164
0
                auto r = deferred.response.GetResponse();
165
0
                if (!r.Value.Deleted) {
166
0
                    auto msg = build_azure_batch_delete_failure_message(_opts, deferred.key);
167
0
                    LOG(WARNING) << msg;
168
0
                    return {.status = ObjStorageStatus {ObjStorageStatus::INTERNAL_ERROR,
169
0
                                                        std::move(msg)},
170
0
                            .http_code = 0,
171
0
                            .request_id = ""};
172
0
                }
173
0
            } catch (Azure::Core::RequestFailedException& e) {
174
0
                if (Azure::Core::Http::HttpStatusCode::NotFound == e.StatusCode &&
175
0
                    0 == strcmp(e.ErrorCode.c_str(), BlobNotFound)) {
176
0
                    continue;
177
0
                }
178
0
                doris::record_object_request_failed(static_cast<int>(e.StatusCode));
179
0
                auto msg = fmt::format(
180
0
                        "Azure request failed because {}, error msg {}, http code {}, path msg "
181
0
                        "{}{}",
182
0
                        e.what(), e.Message, static_cast<int>(e.StatusCode),
183
0
                        wrap_object_storage_path_msg(with_object_key(_opts, deferred.key)),
184
0
                        build_azure_tls_debug_suffix(fmt::format("{} {}", e.what(), e.Message),
185
0
                                                     _tls_debug_context));
186
0
                LOG(WARNING) << msg;
187
0
                return {.status = obj_storage_status_from_http_code(static_cast<int>(e.StatusCode),
188
0
                                                                    std::move(msg)),
189
0
                        .http_code = static_cast<int>(e.StatusCode),
190
0
                        .request_id = std::move(e.RequestId)};
191
0
            }
192
0
        }
193
194
0
        return ObjStorageResponse::OK();
195
0
    }
196
197
private:
198
    struct DeferredDeleteResponse {
199
        std::string key;
200
        Azure::Storage::DeferredResponse<Models::DeleteBlobResult> response;
201
    };
202
203
    BlobContainerClient* _client;
204
    BlobContainerBatch _batch;
205
    ObjStoragePath _opts;
206
    std::string_view _tls_debug_context;
207
    std::vector<DeferredDeleteResponse> deferred_resps;
208
};
209
210
2
ObjStorageUploadResult AzureObjStorageClient::create_multipart_upload(const ObjStoragePath&) {
211
    // Azure has no provider-side multipart session. This local UUID namespaces the writer's
212
    // staged block IDs and is carried through the same interface as an S3 upload ID.
213
2
    auto upload_id = butil::GenerateGUID();
214
2
    if (upload_id.empty()) {
215
0
        return {.resp = {.status = {ObjStorageStatus::INTERNAL_ERROR,
216
0
                                    "failed to generate Azure multipart upload ID"}}};
217
0
    }
218
2
    return ObjStorageUploadResult {
219
2
            .resp = ObjStorageResponse::OK(),
220
2
            .upload_id = std::move(upload_id),
221
2
    };
222
2
}
223
224
ObjStorageResponse AzureObjStorageClient::put_object(const ObjStoragePath& opts,
225
0
                                                     std::string_view stream) {
226
0
    auto client = _client->GetBlockBlobClient(opts.key);
227
0
    return do_azure_client_call(
228
0
            [&]() {
229
0
                client_bvar::ScopedLatency scoped_latency(client_bvar::s3_put_latency);
230
0
                client.UploadFrom(reinterpret_cast<const uint8_t*>(stream.data()), stream.size());
231
0
            },
232
0
            opts, _config.tls_debug_context);
233
0
}
234
235
ObjStorageUploadResult AzureObjStorageClient::upload_part(const ObjStoragePath& opts,
236
                                                          const std::string& upload_id,
237
0
                                                          std::string_view stream, int part_num) {
238
0
    DCHECK(!upload_id.empty());
239
0
    auto client = _client->GetBlockBlobClient(opts.key);
240
0
    std::string block_id = azure_multipart_block_id(upload_id, part_num);
241
0
    try {
242
0
        Azure::Core::IO::MemoryBodyStream memory_body(
243
0
                reinterpret_cast<const uint8_t*>(stream.data()), stream.size());
244
        // The blockId must be base64 encoded
245
0
        client_bvar::ScopedLatency scoped_latency(client_bvar::s3_multi_part_upload_latency);
246
0
        client.StageBlock(block_id, memory_body);
247
0
    } catch (Azure::Core::RequestFailedException& e) {
248
0
        record_object_request_failed(static_cast<int>(e.StatusCode));
249
0
        auto tls_debug_suffix = build_azure_tls_debug_suffix(
250
0
                fmt::format("{} {}", e.what(), e.Message), _config.tls_debug_context);
251
0
        auto msg = fmt::format(
252
0
                "Azure request failed because {}, error msg {}, http code {}, path msg {}{}",
253
0
                e.what(), e.Message, static_cast<int>(e.StatusCode),
254
0
                wrap_object_storage_path_msg(opts), tls_debug_suffix);
255
0
        LOG(WARNING) << msg;
256
        // clang-format off
257
0
        return {
258
0
            .resp = {
259
0
                .status = obj_storage_status_from_http_code(static_cast<int>(e.StatusCode),
260
0
                                                            std::move(msg)),
261
0
                .http_code = static_cast<int>(e.StatusCode),
262
0
                .request_id = std::move(e.RequestId),
263
0
            },
264
0
        };
265
        // clang-format on
266
0
    } catch (const std::exception& e) {
267
0
        return {.resp = make_azure_std_exception_response(e, opts, _config.tls_debug_context)};
268
0
    }
269
0
    return ObjStorageUploadResult {.resp = ObjStorageResponse::OK(), .etag = std::move(block_id)};
270
0
}
271
272
ObjStorageResponse AzureObjStorageClient::complete_multipart_upload(
273
        const ObjStoragePath& opts, const std::string& upload_id,
274
0
        const std::vector<ObjStorageCompletedPart>& completed_parts) {
275
0
    DCHECK(!upload_id.empty());
276
0
    auto client = _client->GetBlockBlobClient(opts.key);
277
0
    std::vector<std::string> string_block_ids;
278
0
    std::ranges::transform(completed_parts, std::back_inserter(string_block_ids),
279
0
                           [&upload_id](const ObjStorageCompletedPart& i) {
280
0
                               return azure_multipart_block_id(upload_id, i.part_num);
281
0
                           });
282
0
    return do_azure_client_call(
283
0
            [&]() {
284
0
                client_bvar::ScopedLatency scoped_latency(
285
0
                        client_bvar::s3_multi_part_upload_latency);
286
0
                client.CommitBlockList(string_block_ids);
287
0
            },
288
0
            opts, _config.tls_debug_context);
289
0
}
290
291
0
ObjStorageHeadResult AzureObjStorageClient::head_object(const ObjStoragePath& opts) {
292
0
    try {
293
0
        Models::BlobProperties properties = [&]() {
294
0
            client_bvar::ScopedLatency scoped_latency(client_bvar::s3_head_latency);
295
0
            return _client->GetBlockBlobClient(opts.key).GetProperties().Value;
296
0
        }();
297
0
        return {.resp = ObjStorageResponse::OK(), .file_size = properties.BlobSize};
298
0
    } catch (Azure::Core::RequestFailedException& e) {
299
0
        if (e.StatusCode == Azure::Core::Http::HttpStatusCode::NotFound) {
300
0
            return ObjStorageHeadResult {
301
0
                    .resp = {.status = obj_storage_status_from_http_code(
302
0
                                     static_cast<int>(e.StatusCode), ""),
303
0
                             .http_code = static_cast<int>(e.StatusCode),
304
0
                             .request_id = std::move(e.RequestId)},
305
0
            };
306
0
        }
307
0
        record_object_request_failed(static_cast<int>(e.StatusCode));
308
0
        auto tls_debug_suffix = build_azure_tls_debug_suffix(
309
0
                fmt::format("{} {}", e.what(), e.Message), _config.tls_debug_context);
310
0
        auto msg = fmt::format(
311
0
                "Azure request failed because {}, error msg {}, http code {}, path msg {}{}",
312
0
                e.what(), e.Message, static_cast<int>(e.StatusCode),
313
0
                wrap_object_storage_path_msg(opts), tls_debug_suffix);
314
0
        LOG(WARNING) << msg << ", request_id=" << e.RequestId;
315
0
        return ObjStorageHeadResult {
316
0
                .resp = {.status = obj_storage_status_from_http_code(static_cast<int>(e.StatusCode),
317
0
                                                                     std::move(msg)),
318
0
                         .http_code = static_cast<int>(e.StatusCode),
319
0
                         .request_id = std::move(e.RequestId)},
320
0
        };
321
0
    } catch (const std::exception& e) {
322
0
        return {.resp = make_azure_std_exception_response(e, opts, _config.tls_debug_context)};
323
0
    }
324
0
}
325
326
ObjStorageResponse AzureObjStorageClient::get_object(const ObjStoragePath& opts, void* buffer,
327
                                                     size_t offset, size_t bytes_read,
328
0
                                                     size_t* size_return) {
329
0
    auto client = _client->GetBlockBlobClient(opts.key);
330
0
    auto response = do_azure_client_call(
331
0
            [&]() {
332
0
                DownloadBlobToOptions download_opts;
333
0
                Azure::Core::Http::HttpRange range {.Offset = static_cast<int64_t>(offset),
334
0
                                                    .Length = bytes_read};
335
0
                download_opts.Range = range;
336
0
                client_bvar::ScopedLatency scoped_latency(client_bvar::s3_get_latency);
337
0
                auto resp = client.DownloadTo(reinterpret_cast<uint8_t*>(buffer), bytes_read,
338
0
                                              download_opts);
339
0
                *size_return = resp.Value.ContentRange.Length.Value();
340
0
            },
341
0
            opts, _config.tls_debug_context);
342
0
    if (!response.ok() || *size_return == bytes_read) {
343
0
        return response;
344
0
    }
345
0
    return {
346
0
            .status = {ObjStorageStatus::INTERNAL_ERROR,
347
0
                       fmt::format("incomplete read from {}, expect {}, got {}",
348
0
                                   wrap_object_storage_path_msg(opts), bytes_read, *size_return)},
349
0
            .http_code = response.http_code,
350
0
            .request_id = std::move(response.request_id),
351
0
    };
352
0
}
353
354
ObjStorageListPageResult AzureObjStorageClient::list_objects_page(
355
0
        const ObjStoragePath& opts, std::string_view continuation_token) {
356
0
    const auto& prefix = opts.prefix.empty() ? opts.key : opts.prefix;
357
0
    ListBlobsOptions request;
358
0
    request.Prefix = prefix;
359
0
    request.PageSizeHint = static_cast<int32_t>(capabilities().max_list_page);
360
0
    if (!continuation_token.empty()) {
361
0
        request.ContinuationToken = std::string(continuation_token);
362
0
    }
363
0
    TEST_SYNC_POINT_CALLBACK("AzureObjStorageClient::list_objects", &request);
364
365
0
    try {
366
0
        auto response = [&]() {
367
0
            client_bvar::ScopedLatency scoped_latency(client_bvar::s3_list_latency);
368
0
            return _client->ListBlobs(request);
369
0
        }();
370
0
        const bool has_more = response.NextPageToken.HasValue();
371
0
        auto next_token = has_more ? response.NextPageToken.Value() : std::string {};
372
0
        if (has_more && next_token.empty()) {
373
0
            return {
374
0
                    .resp = {.status = {ObjStorageStatus::INTERNAL_ERROR,
375
0
                                        "Azure list response has an empty continuation token"},
376
0
                             .http_code = 0},
377
0
            };
378
0
        }
379
0
        ObjStorageListPageResult page {.resp = ObjStorageResponse::OK(),
380
0
                                       .continuation_token = std::move(next_token),
381
0
                                       .has_more = has_more};
382
0
        page.objects.reserve(response.Blobs.size());
383
0
        for (auto&& item : response.Blobs) {
384
0
            DCHECK(item.Name.starts_with(*request.Prefix)) << item.Name << ' ' << *request.Prefix;
385
0
            page.objects.emplace_back(ObjectMeta {
386
0
                    .key = std::move(item.Name),
387
0
                    .size = item.BlobSize,
388
                    // `Azure::DateTime` adds the offset of `SystemClockEpoch` to the given Unix timestamp,
389
                    // so here we need to subtract this offset to obtain the Unix timestamp of the mtime.
390
                    // https://github.com/Azure/azure-sdk-for-cpp/blob/azure-core_1.12.0/sdk/core/azure-core/inc/azure/core/datetime.hpp#L129
391
0
                    .mtime_s = duration_cast<std::chrono::seconds>(item.Details.LastModified -
392
0
                                                                   SystemClockEpoch)
393
0
                                       .count()});
394
0
        }
395
0
        return page;
396
0
    } catch (Azure::Core::RequestFailedException& e) {
397
0
        record_object_request_failed(static_cast<int>(e.StatusCode));
398
0
        auto tls_debug_suffix = build_azure_tls_debug_suffix(
399
0
                fmt::format("{} {}", e.what(), e.Message), _config.tls_debug_context);
400
0
        LOG(WARNING) << fmt::format("Azure request failed because {}, url: {}, prefix: {}{}",
401
0
                                    e.what(), _client->GetUrl(), request.Prefix.Value(),
402
0
                                    tls_debug_suffix);
403
0
        return {
404
0
                .resp = {.status = obj_storage_status_from_http_code(static_cast<int>(e.StatusCode),
405
0
                                                                     e.Message + tls_debug_suffix),
406
0
                         .http_code = static_cast<int>(e.StatusCode),
407
0
                         .request_id = std::move(e.RequestId)},
408
0
        };
409
0
    } catch (std::exception& e) {
410
0
        LOG(WARNING) << fmt::format("Azure request failed because {}, url: {}, prefix: {}",
411
0
                                    e.what(), _client->GetUrl(), request.Prefix.Value());
412
0
        return {
413
0
                .resp = {.status = {ObjStorageStatus::INTERNAL_ERROR, e.what()},
414
0
                         .http_code = 0,
415
0
                         .request_id = ""},
416
0
        };
417
0
    }
418
0
}
419
420
// As Azure's doc said, the batch size is 256
421
// You can find out the num in https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch?tabs=microsoft-entra-id
422
// > Each batch request supports a maximum of 256 subrequests.
423
ObjStorageResponse AzureObjStorageClient::delete_objects(const ObjStoragePath& opts,
424
0
                                                         std::vector<std::string> objs) {
425
    // TODO(ByteYue) : use range to adate this code when compiler is ready
426
    // auto chunkedView = objs | std::views::chunk(BlobBatchMaxOperations);
427
0
    auto begin = std::begin(objs);
428
0
    auto end = std::end(objs);
429
430
0
    while (begin != end) {
431
0
        auto chunk_end = begin;
432
0
        size_t batch_size = BlobBatchMaxOperations;
433
0
        TEST_SYNC_POINT_CALLBACK("AzureObjClient::delete_objects", &batch_size);
434
0
        TEST_SYNC_POINT_CALLBACK("AzureObjStorageClient::delete_objects", &batch_size);
435
0
        batch_size = std::max<size_t>(1, batch_size);
436
0
        std::advance(chunk_end,
437
0
                     std::min(batch_size, static_cast<size_t>(std::distance(begin, end))));
438
439
0
        auto deleter = AzureBatchDeleter(_client.get(), opts, *begin, _config.tls_debug_context);
440
0
        std::ranges::for_each(std::ranges::subrange(begin, chunk_end),
441
0
                              [&](const std::string& obj) { deleter.delete_blob(obj); });
442
0
        begin = chunk_end;
443
0
        if (auto resp = deleter.execute(); resp.status.code != ObjStorageStatus::OK) {
444
0
            return resp;
445
0
        }
446
0
    }
447
0
    return ObjStorageResponse::OK();
448
0
}
449
450
0
ObjStorageResponse AzureObjStorageClient::delete_object(const ObjStoragePath& opts) {
451
0
    try {
452
0
        auto resp = [&]() {
453
0
            client_bvar::ScopedLatency scoped_latency(client_bvar::s3_delete_object_latency);
454
0
            return _client->DeleteBlob(opts.key);
455
0
        }();
456
0
        if (!resp.Value.Deleted) {
457
0
            return {
458
0
                    .status = ObjStorageStatus {ObjStorageStatus::IO_ERROR,
459
0
                                                "Delete azure blob failed"},
460
0
                    .http_code = 0,
461
0
                    .request_id = "",
462
0
            };
463
0
        }
464
0
        return ObjStorageResponse::OK();
465
0
    } catch (Azure::Core::RequestFailedException& e) {
466
0
        if (e.StatusCode == Azure::Core::Http::HttpStatusCode::NotFound &&
467
0
            e.ErrorCode == BlobNotFound) {
468
0
            return ObjStorageResponse::OK();
469
0
        }
470
0
        record_object_request_failed(static_cast<int>(e.StatusCode));
471
0
        auto tls_debug_suffix = build_azure_tls_debug_suffix(
472
0
                fmt::format("{} {}", e.what(), e.Message), _config.tls_debug_context);
473
0
        auto msg = fmt::format(
474
0
                "Azure request failed because {}, error msg {}, http code {}, path msg {}{}",
475
0
                e.what(), e.Message, static_cast<int>(e.StatusCode),
476
0
                wrap_object_storage_path_msg(opts), tls_debug_suffix);
477
0
        LOG(WARNING) << msg;
478
0
        return {
479
0
                .status = obj_storage_status_from_http_code(static_cast<int>(e.StatusCode),
480
0
                                                            std::move(msg)),
481
0
                .http_code = static_cast<int>(e.StatusCode),
482
0
                .request_id = std::move(e.RequestId),
483
0
        };
484
0
    } catch (std::exception& e) {
485
0
        auto msg = fmt::format("Azure request failed because {}, path msg {}{}", e.what(),
486
0
                               wrap_object_storage_path_msg(opts),
487
0
                               build_azure_tls_debug_suffix(e.what(), _config.tls_debug_context));
488
0
        LOG(WARNING) << msg;
489
0
        return {
490
0
                .status = ObjStorageStatus {ObjStorageStatus::INTERNAL_ERROR, std::move(msg)},
491
0
                .http_code = 0,
492
0
                .request_id = "",
493
0
        };
494
0
    }
495
0
}
496
497
std::string AzureObjStorageClient::generate_presigned_url(const ObjStoragePath& opts,
498
0
                                                          int64_t expiration_secs) {
499
0
    Azure::Storage::Sas::BlobSasBuilder sas_builder;
500
0
    sas_builder.ExpiresOn =
501
0
            std::chrono::system_clock::now() + std::chrono::seconds(expiration_secs);
502
0
    sas_builder.BlobContainerName = opts.bucket;
503
0
    sas_builder.BlobName = opts.key;
504
0
    sas_builder.Resource = Azure::Storage::Sas::BlobSasResource::Blob;
505
0
    sas_builder.Protocol = Azure::Storage::Sas::SasProtocol::HttpsOnly;
506
0
    sas_builder.SetPermissions(Azure::Storage::Sas::BlobSasPermissions::Read);
507
508
0
    auto credential = _credential;
509
0
    if (credential == nullptr) {
510
0
        credential = std::make_shared<Azure::Storage::StorageSharedKeyCredential>(_config.ak,
511
0
                                                                                  _config.sk);
512
0
    }
513
0
    std::string sasToken = sas_builder.GenerateSasToken(*credential);
514
515
0
    auto sasURL =
516
0
            fmt::format(SAS_TOKEN_URL_TEMPLATE, _config.endpoint, opts.bucket, opts.key, sasToken);
517
0
    if (sasURL.find("://") == std::string::npos) {
518
0
        sasURL = "https://" + sasURL;
519
0
    }
520
0
    return sasURL;
521
0
}
522
523
ObjStorageResponse AzureObjStorageClient::get_lifecycle(const std::string& /*bucket*/,
524
0
                                                        int64_t* expiration_days) {
525
    // TODO(plat1ko)
526
0
    *expiration_days = INT64_MAX;
527
0
    return ObjStorageResponse::OK();
528
0
}
529
530
0
ObjStorageResponse AzureObjStorageClient::check_versioning(const std::string& /*bucket*/) {
531
    // TODO(plat1ko)
532
0
    return ObjStorageResponse::OK();
533
0
}
534
535
ObjStorageResponse AzureObjStorageClient::abort_multipart_upload(const ObjStoragePath& opts,
536
0
                                                                 const std::string& upload_id) {
537
    // delete uncommitted blobs
538
    // https://learn.microsoft.com/en-us/rest/api/storageservices/delete-blob?tabs=microsoft-entra-id#remarks
539
0
    return delete_object(opts);
540
0
}
541
} // namespace doris