Skip to content

Commit b6b79fe

Browse files
authored
Merge pull request #30966 from Lazin/ct/potato-flag
cloud_topics: gate tiered_cloud on full upgrade and add tiered_v1/tiered_v2 storage modes
2 parents 8ef6dae + 20abbaa commit b6b79fe

59 files changed

Lines changed: 1720 additions & 228 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/v/cloud_topics/app.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ ss::future<> app::construct(
194194
metadata_cache,
195195
&controller->get_shard_table(),
196196
&controller->get_partition_manager(),
197-
connection_cache);
197+
connection_cache,
198+
&controller->get_feature_table());
198199

199200
co_await construct_service(
200201
topic_manifest_upload_mgr, std::ref(*remote), bucket);

src/v/cloud_topics/level_zero/notifier/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ redpanda_cc_library(
2020
"//src/v/cluster:partition_leaders_table",
2121
"//src/v/cluster:shard_table",
2222
"//src/v/cluster:state_machine_registry",
23+
"//src/v/features",
2324
"//src/v/model",
2425
"//src/v/rpc",
2526
"//src/v/ssx:future_util",

src/v/cloud_topics/level_zero/notifier/level_zero_notifier.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "cluster/partition_leaders_table.h"
2222
#include "cluster/partition_manager.h"
2323
#include "cluster/shard_table.h"
24+
#include "features/feature_table.h"
2425
#include "model/timeout_clock.h"
2526
#include "rpc/connection_cache.h"
2627
#include "ssx/future-util.h"
@@ -51,15 +52,23 @@ level_zero_notifier::level_zero_notifier(
5152
ss::sharded<cluster::shard_table>* shard_table,
5253
ss::sharded<cluster::partition_manager>* partition_manager,
5354
ss::sharded<rpc::connection_cache>* connections,
55+
ss::sharded<features::feature_table>* features,
5456
std::chrono::milliseconds retry_backoff)
5557
: _self(self)
5658
, _leaders(leaders)
5759
, _metadata(metadata)
5860
, _shard_table(shard_table)
5961
, _partition_manager(partition_manager)
6062
, _connections(connections)
63+
, _features(features)
6164
, _retry_backoff(retry_backoff) {}
6265

66+
bool level_zero_notifier::notifications_enabled() const {
67+
return _features != nullptr
68+
&& _features->local().is_active(
69+
features::feature::tiered_cloud_topics);
70+
}
71+
6372
std::optional<model::ntp>
6473
level_zero_notifier::resolve_ntp(const model::topic_id_partition& tidp) const {
6574
auto tns = _metadata->local().get_name_by_id(tidp.topic_id);
@@ -78,6 +87,15 @@ ss::future<> level_zero_notifier::stop() {
7887
ss::future<std::expected<void, ctp_stm_api_errc>>
7988
level_zero_notifier::set_min_allowed_local_threshold(
8089
model::topic_id_partition tidp, kafka::offset new_floor) {
90+
if (!notifications_enabled()) {
91+
vlog(
92+
cd_log.debug,
93+
"{} set_min_allowed_local_threshold: skipping notification, the "
94+
"tiered_cloud_topics feature is not active yet, new floor {}",
95+
tidp,
96+
new_floor);
97+
co_return std::expected<void, ctp_stm_api_errc>{};
98+
}
8199
auto ntp = resolve_ntp(tidp);
82100
if (!ntp.has_value()) {
83101
vlog(
@@ -104,6 +122,15 @@ level_zero_notifier::set_min_allowed_local_threshold(
104122
ss::future<std::expected<void, ctp_stm_api_errc>>
105123
level_zero_notifier::set_min_allowed_local_threshold_locally(
106124
model::topic_id_partition tidp, kafka::offset new_floor) {
125+
if (!notifications_enabled()) {
126+
vlog(
127+
cd_log.debug,
128+
"{} set_min_allowed_local_threshold_locally: skipping notification, "
129+
"the tiered_cloud_topics feature is not active yet, new floor {}",
130+
tidp,
131+
new_floor);
132+
co_return std::expected<void, ctp_stm_api_errc>{};
133+
}
107134
auto ntp = resolve_ntp(tidp);
108135
if (!ntp.has_value()) {
109136
vlog(

src/v/cloud_topics/level_zero/notifier/level_zero_notifier.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "cloud_topics/level_zero/stm/ctp_stm_api.h"
1414
#include "cluster/fwd.h"
15+
#include "features/fwd.h"
1516
#include "model/fundamental.h"
1617
#include "rpc/fwd.h"
1718
#include "ssx/semaphore.h"
@@ -35,6 +36,18 @@ namespace cloud_topics {
3536
/// replicates the floor through ctp_stm_api, retrying transient failures up to
3637
/// max_attempts. The replication result is returned to the caller, which
3738
/// decides whether a failure is fatal -- the call is not fire-and-forget.
39+
///
40+
/// While the tiered_cloud_topics feature is not active (the cluster is not
41+
/// fully upgraded to v26.2) every notification attempt is a no-op that
42+
/// reports success. The gate is not hypothetical: storage.mode=cloud topics
43+
/// may exist during the upgrade (only tiered_cloud creation is blocked) and
44+
/// L1 compaction of a cloud topic hands the notifier a new floor like any
45+
/// other, so without the gate the set_min_allowed_local_threshold stm
46+
/// command would be replicated to pre-v26.2 replicas that cannot apply it.
47+
/// Skipping the floor update is safe in the meantime: cloud-mode reads are
48+
/// routed through the last reconciled offset rather than the floor, and no
49+
/// tiered_cloud topic (the only reader of local data below the floor) can
50+
/// exist until the feature is active.
3851
class level_zero_notifier
3952
: public ss::peering_sharded_service<level_zero_notifier> {
4053
public:
@@ -54,6 +67,7 @@ class level_zero_notifier
5467
ss::sharded<cluster::shard_table>* shard_table,
5568
ss::sharded<cluster::partition_manager>* partition_manager,
5669
ss::sharded<rpc::connection_cache>* connections,
70+
ss::sharded<features::feature_table>* features,
5771
std::chrono::milliseconds retry_backoff = default_retry_backoff);
5872

5973
ss::future<> stop();
@@ -85,6 +99,10 @@ class level_zero_notifier
8599
replicate(model::ntp ntp, ctp_stm_api& api, kafka::offset new_floor);
86100

87101
private:
102+
// True once the tiered_cloud_topics feature is active (the cluster is
103+
// fully upgraded to v26.2). A null feature table reads as inactive.
104+
bool notifications_enabled() const;
105+
88106
// Resolve a topic_id_partition to an ntp via the metadata cache. Returns
89107
// nullopt when the topic id is unknown (e.g. deleted topic or stale
90108
// metadata on this node).
@@ -122,6 +140,7 @@ class level_zero_notifier
122140
ss::sharded<cluster::shard_table>* _shard_table;
123141
ss::sharded<cluster::partition_manager>* _partition_manager;
124142
ss::sharded<rpc::connection_cache>* _connections;
143+
ss::sharded<features::feature_table>* _features;
125144
std::chrono::milliseconds _retry_backoff;
126145
ssx::semaphore _inflight{
127146
max_concurrent_replications, "level_zero_notifier::inflight"};

src/v/cloud_topics/level_zero/notifier/tests/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ redpanda_cc_gtest(
1313
"//src/v/cloud_topics/level_zero/stm:ctp_stm_api",
1414
"//src/v/cloud_topics/level_zero/stm:ctp_stm_factory",
1515
"//src/v/cluster:state_machine_registry",
16+
"//src/v/features",
1617
"//src/v/model",
1718
"//src/v/raft/tests:raft_fixture",
1819
"//src/v/rpc",

src/v/cloud_topics/level_zero/notifier/tests/level_zero_notifier_test.cc

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "cloud_topics/level_zero/stm/ctp_stm.h"
1414
#include "cloud_topics/level_zero/stm/ctp_stm_api.h"
1515
#include "cloud_topics/logger.h"
16+
#include "features/feature_table.h"
1617
#include "model/fundamental.h"
1718
#include "raft/tests/raft_fixture.h"
1819
#include "rpc/errc.h"
@@ -54,7 +55,14 @@ TEST_F_CORO(level_zero_notifier_fixture, replicate_to_leader_succeeds) {
5455
co_await wait_for_leader(raft::default_timeout());
5556

5657
ct::level_zero_notifier notifier(
57-
self_node, nullptr, nullptr, nullptr, nullptr, nullptr, tiny_backoff);
58+
self_node,
59+
nullptr,
60+
nullptr,
61+
nullptr,
62+
nullptr,
63+
nullptr,
64+
nullptr,
65+
tiny_backoff);
5866
auto leader_api = api(node(*get_leader()));
5967
auto res = co_await notifier.replicate(
6068
test_ntp, leader_api, kafka::offset(42));
@@ -80,7 +88,14 @@ TEST_F_CORO(level_zero_notifier_fixture, gives_up_on_follower) {
8088
ASSERT_TRUE_CORO(follower != nullptr);
8189

8290
ct::level_zero_notifier notifier(
83-
self_node, nullptr, nullptr, nullptr, nullptr, nullptr, tiny_backoff);
91+
self_node,
92+
nullptr,
93+
nullptr,
94+
nullptr,
95+
nullptr,
96+
nullptr,
97+
nullptr,
98+
tiny_backoff);
8499
auto follower_api = api(*follower);
85100
auto res = co_await notifier.replicate(
86101
test_ntp, follower_api, kafka::offset(7));
@@ -89,6 +104,38 @@ TEST_F_CORO(level_zero_notifier_fixture, gives_up_on_follower) {
89104
ASSERT_FALSE_CORO(res.has_value());
90105
}
91106

107+
// While the tiered_cloud_topics feature is not active, notification attempts
108+
// are no-ops that report success without touching any of the notifier's
109+
// dependencies (all null here, so reaching past the gate would crash).
110+
TEST_F_CORO(
111+
level_zero_notifier_fixture, notification_is_noop_until_feature_active) {
112+
ss::sharded<features::feature_table> features;
113+
co_await features.start();
114+
115+
ct::level_zero_notifier notifier(
116+
self_node,
117+
nullptr,
118+
nullptr,
119+
nullptr,
120+
nullptr,
121+
nullptr,
122+
&features,
123+
tiny_backoff);
124+
const model::topic_id_partition tidp(
125+
model::topic_id(), model::partition_id(0));
126+
127+
auto res = co_await notifier.set_min_allowed_local_threshold(
128+
tidp, kafka::offset(42));
129+
ASSERT_TRUE_CORO(res.has_value());
130+
131+
auto local_res = co_await notifier.set_min_allowed_local_threshold_locally(
132+
tidp, kafka::offset(42));
133+
ASSERT_TRUE_CORO(local_res.has_value());
134+
135+
co_await notifier.stop();
136+
co_await features.stop();
137+
}
138+
92139
TEST(level_zero_notifier_routing, map_transport_error) {
93140
using ct::notifier_detail::map_transport_error;
94141
EXPECT_EQ(

src/v/cluster_link/manager.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ manager::manager(
9393
std::unique_ptr<kafka_rpc_client_service> kafka_rpc_client_service,
9494
std::unique_ptr<members_table_provider> members_table_provider,
9595
std::unique_ptr<sr_preflight_checker> sr_preflight,
96+
ss::sharded<features::feature_table>* feature_table,
9697
ss::lowres_clock::duration task_reconciler_interval,
9798
config::binding<int16_t> default_topic_replication,
9899
ss::scheduling_group scheduling_group)
@@ -103,6 +104,7 @@ manager::manager(
103104
, _topic_creator(std::move(topic_creator))
104105
, _security_service(std::move(security_service))
105106
, _registry(std::move(registry))
107+
, _feature_table(feature_table)
106108
, _link_factory(std::move(link_factory))
107109
, _cluster_factory(std::move(cluster_factory))
108110
, _group_router(std::move(group_router))
@@ -1000,6 +1002,7 @@ ss::future<> manager::on_controller_leadership(::model::term_id term) {
10001002
_topic_creator.get(),
10011003
_topic_metadata_cache.get(),
10021004
_registry.get(),
1005+
_feature_table,
10031006
topic_reconciler_interval,
10041007
_default_topic_replication,
10051008
_scheduling_group);

src/v/cluster_link/manager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "cluster_link/topic_reconciler.h"
2323
#include "cluster_link/types.h"
2424
#include "container/chunked_vector.h"
25+
#include "features/fwd.h"
2526
#include "kafka/data/rpc/deps.h"
2627
#include "kafka/data/rpc/fwd.h"
2728
#include "model/fundamental.h"
@@ -58,6 +59,7 @@ class manager {
5859
std::unique_ptr<kafka_rpc_client_service> kafka_rpc_client_service,
5960
std::unique_ptr<members_table_provider> members_table_provider,
6061
std::unique_ptr<sr_preflight_checker> sr_preflight,
62+
ss::sharded<features::feature_table>* feature_table,
6163
ss::lowres_clock::duration task_reconciler_interval,
6264
config::binding<int16_t> default_topic_replication,
6365
ss::scheduling_group scheduling_group);
@@ -237,6 +239,7 @@ class manager {
237239
std::unique_ptr<kafka::data::rpc::topic_creator> _topic_creator;
238240
std::unique_ptr<security_service> _security_service;
239241
std::unique_ptr<link_registry> _registry;
242+
ss::sharded<features::feature_table>* _feature_table;
240243
std::unique_ptr<link_factory> _link_factory;
241244
std::unique_ptr<cluster_factory> _cluster_factory;
242245
std::unique_ptr<topic_reconciler> _topic_reconciler;

src/v/cluster_link/model/types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ inline auto default_synced_topic_properties = std::to_array<std::string_view>({
7070
kafka::topic_property_min_compaction_lag_ms,
7171
kafka::topic_property_max_compaction_lag_ms,
7272
kafka::topic_property_redpanda_storage_mode,
73+
kafka::topic_property_redpanda_storage_mode_impl,
7374
});
7475

7576
/// List of topic properties that are not permitted to be synced

src/v/cluster_link/service.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,7 @@ ss::future<> service::maybe_start_manager() {
13061306
members_table_provider::make_default(&_controller->get_members_table()),
13071307
sr_preflight_checker::make_default(
13081308
*_schema_registry_dest, source_sr_prober::make_default()),
1309+
&_controller->get_feature_table(),
13091310
30s, // Temporary until we have a proper configuration for this
13101311
config::shard_local_cfg().default_topic_replication.bind(),
13111312
_scheduling_group);

0 commit comments

Comments
 (0)