Skip to content

Commit 62c1d54

Browse files
cluster_link: carry schema-read diagnostics through the source reader
read_subject_version now returns source_schema_read (the schema plus the unsupported fields the source carried but Redpanda cannot store) instead of a bare stored_schema, so the reconciler can later apply the unsupported-feature policy at a single per-node import point. This commit only threads the diagnostics through the interface: the stub, the test fake, and the reconciler are updated to the new type, but the reconciler does not yet act on the unsupported set, so behavior is unchanged. A test asserts a read carrying diagnostics still imports.
1 parent 8af4a02 commit 62c1d54

10 files changed

Lines changed: 87 additions & 21 deletions

src/v/cluster_link/schema_registry_sync/http_source_reader.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ http_source_reader::list_subject_versions(
266266
co_return std::move(res.value());
267267
}
268268

269-
ss::future<source_result<ppsr::stored_schema>>
269+
ss::future<source_result<ppsr::source_schema_read>>
270270
http_source_reader::read_subject_version(
271271
ppsr::context_subject sub,
272272
ppsr::schema_version version,
@@ -285,9 +285,11 @@ http_source_reader::read_subject_version(
285285
if (!res.has_value()) {
286286
co_return std::unexpected(to_source_error(std::move(res.error())));
287287
}
288-
// Unmodeled response fields (parsed_schema::unknown_fields) are ignored;
289-
// honoring the unsupported-feature policy is future work.
290-
co_return std::move(res.value().schema);
288+
// Carry the read through unchanged: the schema plus any unsupported fields
289+
// the source served but Redpanda cannot store. The reconciler applies the
290+
// configured unsupported-feature policy to
291+
// `source_schema_read::unsupported`.
292+
co_return std::move(res.value());
291293
}
292294

293295
ss::future<source_result<std::optional<ppsr::mode>>>

src/v/cluster_link/schema_registry_sync/http_source_reader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class http_source_reader final : public source_reader {
7777
list_subject_versions(
7878
ppsr::context_subject, ppsr::include_deleted, ss::abort_source&) override;
7979

80-
ss::future<source_result<ppsr::stored_schema>> read_subject_version(
80+
ss::future<source_result<ppsr::source_schema_read>> read_subject_version(
8181
ppsr::context_subject, ppsr::schema_version, ss::abort_source&) override;
8282

8383
ss::future<source_result<std::optional<ppsr::mode>>>

src/v/cluster_link/schema_registry_sync/reconciler.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,9 @@ reconciler::discover(const ppsr::subject_version& n, ss::abort_source& as) {
289289
co_return source_result<void>{};
290290
}
291291

292-
adjust_units(_mem, units, body_size(fetched.value()));
292+
adjust_units(_mem, units, body_size(fetched.value().schema));
293293

294-
auto refs = resolve_refs(fetched.value());
294+
auto refs = resolve_refs(fetched.value().schema);
295295
chunked_hash_set<ppsr::subject_version> missing;
296296
for (auto& ref : refs) {
297297
if (!_in_scope(ref.sub)) {
@@ -369,18 +369,18 @@ reconciler::do_import(const ppsr::subject_version& n, ss::abort_source& as) {
369369
co_return source_result<void>{};
370370
}
371371

372-
adjust_units(_mem, units, body_size(fetched.value()));
372+
adjust_units(_mem, units, body_size(fetched.value().schema));
373373

374374
co_await import_body(n, std::move(fetched.value()));
375375
co_return source_result<void>{};
376376
}
377377

378378
ss::future<bool> reconciler::import_body(
379-
const ppsr::subject_version& n, ppsr::stored_schema schema) {
379+
const ppsr::subject_version& n, ppsr::source_schema_read read) {
380380
data(n).state = node_state::importing;
381381
// The graph key `n` stays in the source namespace; only the schema written
382382
// to the destination is remapped.
383-
auto remapped = remap_for_import(*_mapper, std::move(schema));
383+
auto remapped = remap_for_import(*_mapper, std::move(read.schema));
384384
if (!remapped.has_value()) {
385385
vlog(
386386
cllog.warn,

src/v/cluster_link/schema_registry_sync/reconciler.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,13 @@ class reconciler {
160160
ss::future<source_result<void>>
161161
do_import(const ppsr::subject_version& n, ss::abort_source& as);
162162

163-
/// Imports a fetched body, classifying conflicts as per-item failures.
164-
/// Returns true on a successful import.
163+
/// Imports a fetched read, classifying conflicts as per-item failures.
164+
/// Returns true on a successful import. Carries the whole
165+
/// source_schema_read (not just the schema) so the unsupported-feature
166+
/// policy can act on `read.unsupported` at the single per-node import
167+
/// point.
165168
ss::future<bool>
166-
import_body(const ppsr::subject_version& n, ppsr::stored_schema schema);
169+
import_body(const ppsr::subject_version& n, ppsr::source_schema_read read);
167170

168171
/// Marks a node replicated and releases dependents whose in-degree hits 0.
169172
void wake(const ppsr::subject_version& n);

src/v/cluster_link/schema_registry_sync/source_reader.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ class source_reader {
7575

7676
/// Reads a specific subject version's schema. The reconcile engine's
7777
/// schema-body fetch path: called for every node it discovers and imports.
78-
virtual ss::future<source_result<ppsr::stored_schema>> read_subject_version(
78+
/// Returns the schema projected into Redpanda's supported model plus any
79+
/// unsupported fields the source carried but Redpanda cannot store, for the
80+
/// caller to apply its unsupported-feature policy.
81+
virtual ss::future<source_result<ppsr::source_schema_read>>
82+
read_subject_version(
7983
ppsr::context_subject, ppsr::schema_version, ss::abort_source&) = 0;
8084

8185
/// Reads the source's own (non-inherited) mode override for a subject or

src/v/cluster_link/schema_registry_sync/tests/http_source_reader_test.cc

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,20 @@ TEST(http_source_reader, read_subject_version_returns_schema) {
170170
.get();
171171
reader.stop().get();
172172

173-
ASSERT_TRUE(res.has_value());
174-
EXPECT_EQ(res->id, pps::schema_id{100001});
175-
EXPECT_EQ(res->version, pps::schema_version{3});
173+
EXPECT_THAT(
174+
res,
175+
Optional(AllOf(
176+
Field(
177+
"schema",
178+
&pps::source_schema_read::schema,
179+
AllOf(
180+
Field("id", &pps::stored_schema::id, pps::schema_id{100001}),
181+
Field(
182+
"version",
183+
&pps::stored_schema::version,
184+
pps::schema_version{3}))),
185+
Field(
186+
"unsupported", &pps::source_schema_read::unsupported, IsEmpty()))));
176187
}
177188

178189
// Discovery and body fetch must request deleted entries so the reconcile can

src/v/cluster_link/schema_registry_sync/tests/reconciler_test.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ TEST(reconciler, replicates_chain_in_reference_order) {
5757
EXPECT_LT(ia, ib);
5858
}
5959

60+
// A read carrying unsupported-feature diagnostics still imports normally: the
61+
// source_reader interface carries `unsupported` through, but the reconciler
62+
// does not yet act on it (policy enforcement is a later commit).
63+
TEST(reconciler, imports_schema_with_unsupported_diagnostics) {
64+
reconcile_harness h;
65+
auto a = ppsr::context_subject::unqualified("a");
66+
h.source.add(a, 1);
67+
chunked_vector<ppsr::unsupported_feature> unsupported;
68+
unsupported.push_back(
69+
ppsr::unsupported_feature{.json_pointer = "/ruleSet"});
70+
h.source.set_unsupported(a, 1, std::move(unsupported));
71+
72+
srs::work_set work;
73+
work.upserts.push_back(key(a, 1));
74+
75+
auto stats = h.run(std::move(work)).get();
76+
ASSERT_TRUE(stats.has_value());
77+
EXPECT_EQ(stats->versions_changed, 1);
78+
EXPECT_EQ(stats->errors, 0);
79+
EXPECT_GE(index_of(h.destination.get_all(), "a"), 0);
80+
}
81+
6082
// Tolerance test: a schema that lists the same reference twice is handled and
6183
// the referrer still imports referent-first. Holds with or without the dedup.
6284
TEST(reconciler, duplicate_reference_still_imports) {

src/v/cluster_link/schema_registry_sync/tests/sr_sync_test_fixtures.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,21 @@ struct fake_source_state {
141141
std::move(err));
142142
}
143143

144+
// Unsupported features to attach to a specific (subject, version)'s read,
145+
// letting a test drive the reconciler's unsupported-feature policy.
146+
chunked_hash_map<
147+
ppsr::subject_version,
148+
chunked_vector<ppsr::unsupported_feature>>
149+
unsupported_features;
150+
151+
void set_unsupported(
152+
const ppsr::context_subject& sub,
153+
int32_t version,
154+
chunked_vector<ppsr::unsupported_feature> features) {
155+
unsupported_features[ppsr::subject_version{
156+
sub, ppsr::schema_version{version}}] = std::move(features);
157+
}
158+
144159
void add(
145160
const ppsr::context_subject& sub,
146161
int32_t version,
@@ -244,7 +259,8 @@ class fake_source_reader final : public srs::source_reader {
244259
co_return versions;
245260
}
246261

247-
ss::future<srs::source_result<ppsr::stored_schema>> read_subject_version(
262+
ss::future<srs::source_result<ppsr::source_schema_read>>
263+
read_subject_version(
248264
ppsr::context_subject sub,
249265
ppsr::schema_version version,
250266
ss::abort_source&) override {
@@ -257,7 +273,15 @@ class fake_source_reader final : public srs::source_reader {
257273
}
258274
for (const auto& s : _state->schemas) {
259275
if (s.schema.sub() == sub && s.version == version) {
260-
co_return s.share();
276+
chunked_vector<ppsr::unsupported_feature> unsupported;
277+
if (
278+
auto it = _state->unsupported_features.find(
279+
ppsr::subject_version{sub, version});
280+
it != _state->unsupported_features.end()) {
281+
unsupported = it->second.copy();
282+
}
283+
co_return ppsr::source_schema_read{
284+
.schema = s.share(), .unsupported = std::move(unsupported)};
261285
}
262286
}
263287
co_return std::unexpected(

src/v/cluster_link/schema_registry_sync/unavailable_source_reader.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ unavailable_source_reader::list_subject_versions(
3939
co_return std::unexpected(unavailable());
4040
}
4141

42-
ss::future<source_result<ppsr::stored_schema>>
42+
ss::future<source_result<ppsr::source_schema_read>>
4343
unavailable_source_reader::read_subject_version(
4444
ppsr::context_subject, ppsr::schema_version, ss::abort_source&) {
4545
co_return std::unexpected(unavailable());

src/v/cluster_link/schema_registry_sync/unavailable_source_reader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class unavailable_source_reader final : public source_reader {
4343
list_subject_versions(
4444
ppsr::context_subject, ppsr::include_deleted, ss::abort_source&) override;
4545

46-
ss::future<source_result<ppsr::stored_schema>> read_subject_version(
46+
ss::future<source_result<ppsr::source_schema_read>> read_subject_version(
4747
ppsr::context_subject, ppsr::schema_version, ss::abort_source&) override;
4848

4949
ss::future<source_result<std::optional<ppsr::mode>>>

0 commit comments

Comments
 (0)