blob: d573c17e2085011059fb1ad936fb9b84233d4432 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "obj_storage_client.h"
#include <cpp/sync_point.h>
#include <glog/logging.h>
#include <algorithm>
#include <chrono>
namespace doris {
ObjStorageStatus obj_storage_status_from_http_code(int http_code, std::string message) {
switch (http_code) {
case 401:
case 403:
return {ObjStorageStatus::PERMISSION_DENIED, std::move(message)};
case 404:
return {ObjStorageStatus::NOT_FOUND, std::move(message)};
case 429:
return {ObjStorageStatus::LIMIT_REACH, std::move(message)};
default:
return {http_code <= 0 ? ObjStorageStatus::NETWORK_ERROR : ObjStorageStatus::INTERNAL_ERROR,
std::move(message)};
}
}
std::unique_ptr<ObjStorageListIterator> ObjStorageClient::list_objects(const ObjStoragePath& opts) {
return std::make_unique<ObjStorageListIterator>(shared_from_this(), opts);
}
ObjStorageResponse ObjStorageClient::list_objects(const ObjStoragePath& opts,
std::vector<ObjectMeta>* objects) {
objects->clear();
auto iter = list_objects(opts);
for (;;) {
auto result = iter->next();
if (!result.object.has_value()) {
if (!result.resp.ok()) {
objects->clear();
}
return result.resp;
}
objects->emplace_back(std::move(*result.object));
}
}
ObjStorageResponse ObjStorageListIterator::has_next() {
if (!is_valid_) {
return {
.status = {ObjStorageStatus::INTERNAL_ERROR, "Iterator is invalid"},
.http_code = 0,
};
}
while (next_index_ == objects_.size()) {
if (!has_more_) {
return {
.status = {ObjStorageStatus::END_OF_FILE, "No more results"},
.http_code = 200,
};
}
auto page = client_->list_objects_page(opts_, continuation_token_);
if (!page.resp.ok()) {
is_valid_ = false;
return page.resp;
}
objects_ = std::move(page.objects);
next_index_ = 0;
continuation_token_ = std::move(page.continuation_token);
has_more_ = page.has_more;
}
return ObjStorageResponse::OK();
}
ObjStorageListResult ObjStorageListIterator::next() {
auto response = has_next();
if (response.status.code == ObjStorageStatus::END_OF_FILE) {
return {.resp = ObjStorageResponse::OK(), .object = {}};
}
if (!response.ok()) {
return {.resp = std::move(response), .object = {}};
}
return {
.resp = ObjStorageResponse::OK(),
.object = std::move(objects_[next_index_++]),
};
}
ObjStorageResponse delete_objects_recursively(
std::shared_ptr<ObjStorageClient> client, const ObjStoragePath& path,
const ObjStorageRecursiveDeleteOptions& delete_options) {
const auto start_time = std::chrono::steady_clock::now();
auto list_path = path;
if (list_path.prefix.empty()) {
list_path.prefix = list_path.key;
}
auto delete_batch_size = std::max<size_t>(1, client->capabilities().max_delete_batch);
TEST_SYNC_POINT_CALLBACK("ObjStorageClient::delete_objects_recursively_", &delete_batch_size);
delete_batch_size = std::max<size_t>(1, delete_batch_size);
const auto max_tasks_per_batch = std::max<size_t>(1, delete_options.max_tasks_per_batch);
std::vector<std::string> keys;
keys.reserve(delete_batch_size);
size_t pending_tasks = 0;
size_t total_batches = 0;
size_t num_deleted = 0;
size_t error_count = 0;
auto first_error = ObjStorageResponse::OK();
auto elapsed_milliseconds = [&]() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start_time)
.count();
};
auto finish = [&](ObjStorageResponse response) {
LOG(INFO) << "delete objects under " << list_path.bucket << "/" << list_path.prefix
<< " finished, ret=" << response.status.code
<< ", total_batches=" << total_batches << ", num_deleted=" << num_deleted
<< ", error_count=" << error_count << ", cost=" << elapsed_milliseconds()
<< " ms";
return response;
};
auto record_error = [&](ObjStorageResponse response) {
if (response.ok()) {
return;
}
++error_count;
if (first_error.ok()) {
first_error = std::move(response);
}
};
auto wait_for_tasks = [&]() {
if (pending_tasks == 0) {
return ObjStorageResponse::OK();
}
const auto tasks_in_batch = pending_tasks;
pending_tasks = 0;
auto response = delete_options.executor ? delete_options.executor->wait()
: ObjStorageResponse::OK();
++total_batches;
LOG(INFO) << "delete objects under " << list_path.bucket << "/" << list_path.prefix
<< " batch " << total_batches << " completed"
<< ", tasks_in_batch=" << tasks_in_batch << ", total_deleted=" << num_deleted
<< ", elapsed=" << elapsed_milliseconds() << " ms";
return response;
};
auto submit_delete_task = [&]() -> bool {
ObjStorageDeleteTask task = [client, bucket = path.bucket,
batch = std::move(keys)]() mutable {
return client->delete_objects(ObjStoragePath {.bucket = std::move(bucket)},
std::move(batch));
};
keys.clear();
keys.reserve(delete_batch_size);
ObjStorageResponse response;
if (delete_options.executor) {
response = delete_options.executor->submit(std::move(task));
} else {
response = task();
}
++pending_tasks;
if (!response.ok()) {
record_error(std::move(response));
record_error(wait_for_tasks());
return false;
}
if (pending_tasks == max_tasks_per_batch) {
record_error(wait_for_tasks());
}
// Match the pre-refactor Recycler behavior: do not scan the next task batch after the
// current batch reports a submit, delete, or wait failure.
return first_error.ok();
};
auto iter = client->list_objects(list_path);
for (;;) {
auto result = iter->next();
if (!result.object.has_value()) {
if (result.resp.ok()) {
break;
}
if (!keys.empty()) {
submit_delete_task();
}
record_error(wait_for_tasks());
record_error(std::move(result.resp));
return finish(std::move(first_error));
}
auto& object = *result.object;
if (delete_options.expiration_time > 0 && object.mtime_s > delete_options.expiration_time) {
continue;
}
++num_deleted;
keys.emplace_back(std::move(object.key));
if (keys.size() == delete_batch_size && !submit_delete_task()) {
return finish(std::move(first_error));
}
}
if (!keys.empty() && !submit_delete_task()) {
return finish(std::move(first_error));
}
record_error(wait_for_tasks());
return finish(std::move(first_error));
}
} // namespace doris