-
Notifications
You must be signed in to change notification settings - Fork 757
kafka: add a reserved Redpanda-specific Kafka API-key range #30731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
nguyen-andrew
merged 11 commits into
redpanda-data:dev
from
nguyen-andrew:kafka-redpanda-api-range
Jun 23, 2026
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
952991d
kafka: extract type_list into its own header
nguyen-andrew 7086ef9
kafka: rename the handler request type-lists
nguyen-andrew bd13616
kafka: add DescribeRedpandaRoles wire schema and round-trip test
nguyen-andrew eeddcdf
kafka: add the DescribeRedpandaRoles handler
nguyen-andrew 45c3975
kafka: add api_key_indexed_array dual-region container
nguyen-andrew f7b263b
kafka: add reserved api-key range type-lists and table alias
nguyen-andrew 1b9aa0c
kafka: make flex-version mapping reserved-range aware
nguyen-andrew 2bd206b
kafka: store handler probes in a reserved-range-aware table
nguyen-andrew 42439de
kafka: route reserved api keys through the dispatch LUT
nguyen-andrew 1cdcc7f
kafka: build throughput api-key bitmap as a dual-region table
nguyen-andrew 2c4b827
kafka: add end-to-end DescribeRedpandaRoles dispatch test
nguyen-andrew File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| /* | ||
| * Copyright 2026 Redpanda Data, Inc. | ||
| * | ||
| * Use of this software is governed by the Business Source License | ||
| * included in the file licenses/BSL.md | ||
| * | ||
| * As of the Change Date specified in that file, in accordance with | ||
| * the Business Source License, use of this software will be governed | ||
| * by the Apache License, Version 2.0 | ||
| */ | ||
| #pragma once | ||
|
|
||
| #include "kafka/protocol/types.h" | ||
|
|
||
| #include <fmt/format.h> | ||
|
|
||
| #include <array> | ||
| #include <cstddef> | ||
| #include <stdexcept> | ||
|
|
||
| namespace kafka { | ||
|
|
||
| // Cold helper kept out of line so fmt::format doesn't bloat at() and block it | ||
| // from inlining at its hot call sites. | ||
| [[noreturn]] inline void throw_api_key_out_of_range(api_key key) { | ||
| throw std::out_of_range( | ||
| fmt::format("api_key_indexed_array::at: {} out of range", key())); | ||
| } | ||
|
|
||
| /// \brief A dense, array-backed map from kafka::api_key to T spanning two | ||
| /// disjoint key regions: the standard Kafka range [0, StandardSize) and the | ||
| /// reserved Redpanda range [ReservedBase, ReservedBase + ReservedSize). | ||
| /// | ||
| /// Indexing routes to whichever region a key falls in, so callers treat it as | ||
| /// a single array keyed by api_key without paying for the large gap between the | ||
| /// two regions. Every member is constexpr, so the same type backs the | ||
| /// compile-time dispatch/flex tables and the run-time probe/throughput tables. | ||
| template< | ||
| typename T, | ||
| std::size_t StandardSize, | ||
| std::size_t ReservedSize, | ||
| api_key::type ReservedBase = redpanda_api_key_base()> | ||
| class api_key_indexed_array { | ||
| // Regions must not overlap, or standard keys in [ReservedBase, | ||
| // StandardSize) would silently alias the reserved region. | ||
| static_assert( | ||
| static_cast<std::size_t>(ReservedBase) >= StandardSize, | ||
| "reserved region overlaps the standard range"); | ||
|
|
||
| public: | ||
| constexpr api_key_indexed_array() = default; | ||
|
|
||
| /// Fill both regions with \p init (e.g. the flex map's invalid sentinel). | ||
| explicit constexpr api_key_indexed_array(const T& init) { | ||
| _standard.fill(init); | ||
| _reserved.fill(init); | ||
| } | ||
|
|
||
| /// Unchecked access, like std::array::operator[]. Out-of-range keys are | ||
| /// undefined at run time and a compile error under constant evaluation. | ||
| /// Standard keys (the common case) are routed first. | ||
| constexpr T& operator[](api_key key) noexcept { | ||
| if (!is_reserved(key)) [[likely]] { | ||
| return _standard[static_cast<std::size_t>(key())]; | ||
| } | ||
| return _reserved[reserved_index(key)]; | ||
| } | ||
| constexpr const T& operator[](api_key key) const noexcept { | ||
| if (!is_reserved(key)) [[likely]] { | ||
| return _standard[static_cast<std::size_t>(key())]; | ||
| } | ||
| return _reserved[reserved_index(key)]; | ||
| } | ||
|
|
||
| /// Checked access with std::array::at semantics: throws std::out_of_range | ||
| /// for any key that is not a valid index in either region. Standard keys | ||
| /// (the common case) are checked and returned first. | ||
| constexpr T& at(api_key key) { | ||
| if (in_standard_range(key)) [[likely]] { | ||
| return _standard[static_cast<std::size_t>(key())]; | ||
| } | ||
| if (is_reserved(key)) { | ||
| return _reserved.at(reserved_index(key)); | ||
| } | ||
| throw_api_key_out_of_range(key); | ||
| } | ||
| constexpr const T& at(api_key key) const { | ||
| if (in_standard_range(key)) [[likely]] { | ||
| return _standard[static_cast<std::size_t>(key())]; | ||
| } | ||
| if (is_reserved(key)) { | ||
| return _reserved.at(reserved_index(key)); | ||
| } | ||
| throw_api_key_out_of_range(key); | ||
| } | ||
|
|
||
| /// True iff \p key is a valid index in either region. | ||
| constexpr bool contains(api_key key) const noexcept { | ||
| if (in_standard_range(key)) [[likely]] { | ||
| return true; | ||
| } | ||
| return in_reserved_range(key); | ||
| } | ||
|
|
||
| /// Pointer to the element for \p key, or nullptr if out of range. | ||
| constexpr const T* find(api_key key) const noexcept { | ||
| if (in_standard_range(key)) [[likely]] { | ||
| return &_standard[static_cast<std::size_t>(key())]; | ||
| } | ||
| if (in_reserved_range(key)) { | ||
| return &_reserved[reserved_index(key)]; | ||
| } | ||
| return nullptr; | ||
| } | ||
| constexpr T* find(api_key key) noexcept { | ||
| if (in_standard_range(key)) [[likely]] { | ||
| return &_standard[static_cast<std::size_t>(key())]; | ||
| } | ||
| if (in_reserved_range(key)) { | ||
| return &_reserved[reserved_index(key)]; | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| /// Pointer to the first element (standard region then reserved) for which | ||
| /// pred(api_key, element) is true, or nullptr if none match. | ||
| template<typename Pred> | ||
| // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) | ||
| constexpr const T* find_if(Pred&& pred) const noexcept { | ||
| for (std::size_t i = 0; i < StandardSize; ++i) { | ||
| if (pred(api_key(static_cast<api_key::type>(i)), _standard[i])) { | ||
| return &_standard[i]; | ||
| } | ||
| } | ||
| for (std::size_t i = 0; i < ReservedSize; ++i) { | ||
| if ( | ||
| pred( | ||
| api_key(static_cast<api_key::type>(ReservedBase + i)), | ||
| _reserved[i])) { | ||
| return &_reserved[i]; | ||
| } | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| /// Invoke f(api_key, T&) for every slot, standard region then reserved | ||
| /// region, passing the real api_key (not a 0..N index). | ||
| template<typename F> | ||
| // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) | ||
| constexpr void for_each(F&& f) { | ||
| for (std::size_t i = 0; i < StandardSize; ++i) { | ||
| f(api_key(static_cast<api_key::type>(i)), _standard[i]); | ||
| } | ||
| for (std::size_t i = 0; i < ReservedSize; ++i) { | ||
| f(api_key(static_cast<api_key::type>(ReservedBase + i)), | ||
| _reserved[i]); | ||
| } | ||
| } | ||
| template<typename F> | ||
| // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) | ||
| constexpr void for_each(F&& f) const { | ||
| for (std::size_t i = 0; i < StandardSize; ++i) { | ||
| f(api_key(static_cast<api_key::type>(i)), _standard[i]); | ||
| } | ||
| for (std::size_t i = 0; i < ReservedSize; ++i) { | ||
| f(api_key(static_cast<api_key::type>(ReservedBase + i)), | ||
| _reserved[i]); | ||
| } | ||
| } | ||
|
|
||
| constexpr bool operator==(const api_key_indexed_array&) const = default; | ||
|
|
||
| static constexpr std::size_t standard_size() { return StandardSize; } | ||
| static constexpr std::size_t reserved_size() { return ReservedSize; } | ||
| static constexpr api_key reserved_base() { return api_key(ReservedBase); } | ||
|
|
||
| private: | ||
| static constexpr bool in_standard_range(api_key key) noexcept { | ||
| return key() >= 0 && static_cast<std::size_t>(key()) < StandardSize; | ||
| } | ||
| static constexpr bool in_reserved_range(api_key key) noexcept { | ||
| return is_reserved(key) && reserved_index(key) < ReservedSize; | ||
| } | ||
| static constexpr bool is_reserved(api_key key) noexcept { | ||
| return key() >= ReservedBase; | ||
| } | ||
| static constexpr std::size_t reserved_index(api_key key) noexcept { | ||
| return static_cast<std::size_t>(key() - ReservedBase); | ||
| } | ||
|
|
||
| std::array<T, StandardSize> _standard{}; | ||
| std::array<T, ReservedSize> _reserved{}; | ||
| }; | ||
|
|
||
| } // namespace kafka | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * Copyright 2026 Redpanda Data, Inc. | ||
| * | ||
| * Use of this software is governed by the Business Source License | ||
| * included in the file licenses/BSL.md | ||
| * | ||
| * As of the Change Date specified in that file, in accordance with | ||
| * the Business Source License, use of this software will be governed | ||
| * by the Apache License, Version 2.0 | ||
| */ | ||
| #pragma once | ||
|
|
||
| #include "kafka/protocol/api_key_indexed_array.h" | ||
|
|
||
| #include <cstddef> | ||
|
|
||
| namespace kafka { | ||
|
|
||
| // Sizes of the two api_key_indexed_array regions, kept here as literals so this | ||
| // lean header -- and its many transitive includers via | ||
| // handler_probe.h -> connection_context.h -> request_context.h -- need not pull | ||
| // in the request schemata that messages.h includes. messages.h static_asserts | ||
| // these against the live request type lists, so they cannot silently drift: add | ||
| // an API past these bounds and messages.h fails to compile, pointing back here. | ||
| inline constexpr std::size_t standard_api_key_table_size = 67; | ||
| inline constexpr std::size_t reserved_api_key_table_size = 1; | ||
|
|
||
| /// Kafka-key-indexed dense table sized to the standard and reserved ranges. | ||
| template<typename T> | ||
| using api_key_table = api_key_indexed_array< | ||
| T, | ||
| standard_api_key_table_size, | ||
| reserved_api_key_table_size>; | ||
|
|
||
| } // namespace kafka |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /* | ||
| * Copyright 2026 Redpanda Data, Inc. | ||
| * | ||
| * Use of this software is governed by the Business Source License | ||
| * included in the file licenses/BSL.md | ||
| * | ||
| * As of the Change Date specified in that file, in accordance with | ||
| * the Business Source License, use of this software will be governed | ||
| * by the Apache License, Version 2.0 | ||
| */ | ||
| #pragma once | ||
| #include "bytes/iobuf.h" | ||
| #include "kafka/protocol/errors.h" | ||
| #include "kafka/protocol/schemata/describe_redpanda_roles_request.h" | ||
| #include "kafka/protocol/schemata/describe_redpanda_roles_response.h" | ||
|
|
||
| #include <seastar/core/future.hh> | ||
|
|
||
| namespace kafka { | ||
|
|
||
| struct describe_redpanda_roles_request final { | ||
| using api_type = describe_redpanda_roles_api; | ||
|
|
||
| describe_redpanda_roles_request_data data; | ||
|
|
||
| void encode(protocol::encoder& writer, api_version version) { | ||
| data.encode(writer, version); | ||
| } | ||
|
|
||
| void decode(protocol::decoder& reader, api_version version) { | ||
| data.decode(reader, version); | ||
| } | ||
|
|
||
| fmt::iterator format_to(fmt::iterator it) const { | ||
| return fmt::format_to(it, "{}", data); | ||
| } | ||
| }; | ||
|
|
||
| struct describe_redpanda_roles_response final { | ||
| using api_type = describe_redpanda_roles_api; | ||
|
|
||
| describe_redpanda_roles_response_data data; | ||
|
|
||
| void encode(protocol::encoder& writer, api_version version) { | ||
| data.encode(writer, version); | ||
| } | ||
|
|
||
| void decode(iobuf buf, api_version version) { | ||
| data.decode(std::move(buf), version); | ||
| } | ||
|
|
||
| fmt::iterator format_to(fmt::iterator it) const { | ||
| return fmt::format_to(it, "{}", data); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace kafka |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fyi, you should be able to use the c++23 deducing self feature to avoid duplicating code for each of the const/non-const versions of these functions.