diff options
author | Maksim Denisov <denisov.maksim@huawei.com> | 2024-09-17 10:20:55 +0200 |
---|---|---|
committer | Maksim Denisov <denisov.maksim@huawei.com> | 2024-09-18 09:45:19 +0200 |
commit | 60748d2914568e54f852281141f7c128c3ca1f2c (patch) | |
tree | 144ebe2871a46bc8b250892e3fd0ac948647763e /src | |
parent | 7d2f632b1dd1fe2ca01ef89716efe355e4d32687 (diff) | |
download | justbuild-60748d2914568e54f852281141f7c128c3ca1f2c.tar.gz |
Implement ByteStreamUtils::ReadRequest class
...and remove split serialization/deserialization implementations.
Diffstat (limited to 'src')
6 files changed, 165 insertions, 24 deletions
diff --git a/src/buildtool/execution_api/common/TARGETS b/src/buildtool/execution_api/common/TARGETS index a060d0fa..19363342 100644 --- a/src/buildtool/execution_api/common/TARGETS +++ b/src/buildtool/execution_api/common/TARGETS @@ -34,6 +34,9 @@ { "type": ["@", "rules", "CC", "library"] , "name": ["bytestream_utils"] , "hdrs": ["bytestream_utils.hpp"] + , "srcs": ["bytestream_utils.cpp"] + , "private-deps": + [["@", "fmt", "", "fmt"], ["src/buildtool/common", "bazel_types"]] , "stage": ["src", "buildtool", "execution_api", "common"] } , "api_bundle": diff --git a/src/buildtool/execution_api/common/bytestream_utils.cpp b/src/buildtool/execution_api/common/bytestream_utils.cpp new file mode 100644 index 00000000..cd1f0d19 --- /dev/null +++ b/src/buildtool/execution_api/common/bytestream_utils.cpp @@ -0,0 +1,104 @@ +// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd. +// +// Licensed 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 "src/buildtool/execution_api/common/bytestream_utils.hpp" + +#include <string_view> +#include <utility> +#include <vector> + +#include "fmt/core.h" +#include "src/buildtool/common/bazel_types.hpp" + +namespace { +/// \brief Split a string into parts with '/' delimiter +/// \param request String to be split +/// \return A vector of parts on success or an empty vector on failure. +[[nodiscard]] auto SplitRequest(std::string const& request) noexcept + -> std::vector<std::string_view> { + std::vector<std::string_view> parts; + try { + std::size_t shift = 0; + for (std::size_t length = 0; shift + length < request.size(); + ++length) { + if (request.at(shift + length) == '/') { + parts.emplace_back(request.data() + shift, length); + shift += length + 1; + length = 0; + } + } + + if (shift < request.size()) { + parts.emplace_back(request.data() + shift, request.size() - shift); + } + } catch (...) { + return {}; + } + return parts; +} + +[[nodiscard]] inline auto ToBazelDigest(std::string hash, + std::int64_t size) noexcept + -> bazel_re::Digest { + bazel_re::Digest digest{}; + digest.set_hash(std::move(hash)); + digest.set_size_bytes(size); + return digest; +} +} // namespace + +ByteStreamUtils::ReadRequest::ReadRequest( + std::string instance_name, + bazel_re::Digest const& digest) noexcept + : instance_name_{std::move(instance_name)}, + hash_{digest.hash()}, + size_{digest.size_bytes()} {} + +auto ByteStreamUtils::ReadRequest::ToString() && noexcept -> std::string { + return fmt::format("{}/{}/{}/{}", + std::move(instance_name_), + ByteStreamUtils::kBlobs, + std::move(hash_), + size_); +} + +auto ByteStreamUtils::ReadRequest::FromString( + std::string const& request) noexcept -> std::optional<ReadRequest> { + static constexpr std::size_t kInstanceNameIndex = 0U; + static constexpr std::size_t kBlobsIndex = 1U; + static constexpr std::size_t kHashIndex = 2U; + static constexpr std::size_t kSizeIndex = 3U; + static constexpr std::size_t kReadRequestPartsCount = 4U; + + auto const parts = ::SplitRequest(request); + if (parts.size() != kReadRequestPartsCount or + parts.at(kBlobsIndex).compare(ByteStreamUtils::kBlobs) != 0) { + return std::nullopt; + } + + ReadRequest result; + result.instance_name_ = std::string(parts.at(kInstanceNameIndex)); + result.hash_ = std::string(parts.at(kHashIndex)); + try { + result.size_ = std::stoi(std::string(parts.at(kSizeIndex))); + } catch (...) { + return std::nullopt; + } + return result; +} + +auto ByteStreamUtils::ReadRequest::GetDigest() const noexcept + -> bazel_re::Digest { + return ToBazelDigest(hash_, size_); +} diff --git a/src/buildtool/execution_api/common/bytestream_utils.hpp b/src/buildtool/execution_api/common/bytestream_utils.hpp index c28c9627..9b46073f 100644 --- a/src/buildtool/execution_api/common/bytestream_utils.hpp +++ b/src/buildtool/execution_api/common/bytestream_utils.hpp @@ -16,11 +16,51 @@ #define INCLUDED_SRC_BUILDTOOL_EXECUTION_API_COMMON_BYTESTREAM_UTILS_HPP #include <cstddef> +#include <cstdint> +#include <optional> +#include <string> + +namespace build::bazel::remote::execution::v2 { +class Digest; +} +namespace bazel_re = build::bazel::remote::execution::v2; class ByteStreamUtils final { + static constexpr auto* kBlobs = "blobs"; + public: // Chunk size for uploads (default size used by BuildBarn) static constexpr std::size_t kChunkSize = 64 * 1024; + + /// \brief Create a read request for the bytestream service to be + /// transferred over the net. Handles serialization/deserialization on its + /// own. The pattern is: + /// "{instance_name}/{kBlobs}/{digest.hash()}/{digest.size_bytes()}". + /// "instance_name_example/blobs/62183d7a696acf7e69e218efc82c93135f8c85f895/4424712" + class ReadRequest final { + public: + explicit ReadRequest(std::string instance_name, + bazel_re::Digest const& digest) noexcept; + + [[nodiscard]] auto ToString() && noexcept -> std::string; + + [[nodiscard]] static auto FromString( + std::string const& request) noexcept -> std::optional<ReadRequest>; + + [[nodiscard]] auto GetInstanceName() const noexcept + -> std::string const& { + return instance_name_; + } + + [[nodiscard]] auto GetDigest() const noexcept -> bazel_re::Digest; + + private: + std::string instance_name_; + std::string hash_; + std::int64_t size_ = 0; + + ReadRequest() = default; + }; }; #endif // INCLUDED_SRC_BUILDTOOL_EXECUTION_API_COMMON_BYTESTREAM_UTILS_HPP diff --git a/src/buildtool/execution_api/execution_service/bytestream_server.cpp b/src/buildtool/execution_api/execution_service/bytestream_server.cpp index 985f41a9..789fdf95 100644 --- a/src/buildtool/execution_api/execution_service/bytestream_server.cpp +++ b/src/buildtool/execution_api/execution_service/bytestream_server.cpp @@ -63,18 +63,16 @@ auto BytestreamServiceImpl::Read( ::grpc::ServerWriter<::google::bytestream::ReadResponse>* writer) -> ::grpc::Status { logger_.Emit(LogLevel::Trace, "Read {}", request->resource_name()); - // resource_name is of type - // remote-execution/blobs/62f408d64bca5de775c4b1dbc3288fc03afd6b19eb/0 - auto const digest = ParseResourceName(request->resource_name()); - if (not digest) { + auto const read_request = + ByteStreamUtils::ReadRequest::FromString(request->resource_name()); + if (not read_request) { auto const str = fmt::format("could not parse {}", request->resource_name()); logger_.Emit(LogLevel::Error, "{}", str); return ::grpc::Status{::grpc::StatusCode::INVALID_ARGUMENT, str}; } - auto const read_digest = ArtifactDigestFactory::FromBazel( - storage_config_.hash_function.GetType(), *digest); + storage_config_.hash_function.GetType(), read_request->GetDigest()); if (not read_digest) { logger_.Emit(LogLevel::Debug, "{}", read_digest.error()); return ::grpc::Status{::grpc::StatusCode::INVALID_ARGUMENT, diff --git a/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp b/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp index 1516ea0c..ea248810 100644 --- a/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp +++ b/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp @@ -28,6 +28,7 @@ #include "src/buildtool/common/remote/client_common.hpp" #include "src/buildtool/common/remote/retry.hpp" #include "src/buildtool/common/remote/retry_config.hpp" +#include "src/buildtool/execution_api/common/bytestream_utils.hpp" #include "src/buildtool/execution_api/common/execution_common.hpp" #include "src/buildtool/execution_api/common/message_limits.hpp" #include "src/buildtool/file_system/object_type.hpp" @@ -35,13 +36,6 @@ namespace { -[[nodiscard]] auto ToResourceName(std::string const& instance_name, - bazel_re::Digest const& digest) noexcept - -> std::string { - return fmt::format( - "{}/blobs/{}/{}", instance_name, digest.hash(), digest.size_bytes()); -} - // In order to determine whether blob splitting is supported at the remote, a // trial request to the remote CAS service is issued. This is just a workaround // until the blob split API extension is accepted as part of the official remote @@ -341,13 +335,15 @@ auto BazelCasClient::UpdateSingleBlob(std::string const& instance_name, auto BazelCasClient::IncrementalReadSingleBlob(std::string const& instance_name, bazel_re::Digest const& digest) const noexcept -> ByteStreamClient::IncrementalReader { - return stream_->IncrementalRead(ToResourceName(instance_name, digest)); + return stream_->IncrementalRead( + ByteStreamUtils::ReadRequest{instance_name, digest}); } auto BazelCasClient::ReadSingleBlob( std::string const& instance_name, bazel_re::Digest const& digest) const noexcept -> std::optional<BazelBlob> { - if (auto data = stream_->Read(ToResourceName(instance_name, digest))) { + if (auto data = stream_->Read( + ByteStreamUtils::ReadRequest{instance_name, digest})) { return BazelBlob{digest, std::move(*data), /*is_exec=*/false}; } return std::nullopt; diff --git a/src/buildtool/execution_api/remote/bazel/bytestream_client.hpp b/src/buildtool/execution_api/remote/bazel/bytestream_client.hpp index a444e61b..02538384 100644 --- a/src/buildtool/execution_api/remote/bazel/bytestream_client.hpp +++ b/src/buildtool/execution_api/remote/bazel/bytestream_client.hpp @@ -70,11 +70,11 @@ class ByteStreamClient { IncrementalReader( gsl::not_null<google::bytestream::ByteStream::Stub*> const& stub, - Logger const* logger, - std::string const& resource_name) + ByteStreamUtils::ReadRequest&& read_request, + Logger const* logger) : logger_{logger} { google::bytestream::ReadRequest request{}; - request.set_resource_name(resource_name); + request.set_resource_name(std::move(read_request).ToString()); reader_ = stub->Read(&ctx_, request); } }; @@ -86,14 +86,14 @@ class ByteStreamClient { CreateChannelWithCredentials(server, port, auth)); } - [[nodiscard]] auto IncrementalRead( - std::string const& resource_name) const noexcept -> IncrementalReader { - return IncrementalReader{stub_.get(), &logger_, resource_name}; + [[nodiscard]] auto IncrementalRead(ByteStreamUtils::ReadRequest&& request) + const noexcept -> IncrementalReader { + return IncrementalReader{stub_.get(), std::move(request), &logger_}; } - [[nodiscard]] auto Read(std::string const& resource_name) const noexcept - -> std::optional<std::string> { - auto reader = IncrementalRead(resource_name); + [[nodiscard]] auto Read(ByteStreamUtils::ReadRequest&& request) + const noexcept -> std::optional<std::string> { + auto reader = IncrementalRead(std::move(request)); std::string output{}; auto data = reader.Next(); while (data and not data->empty()) { |