[CORE-16776] cluster_link: test SR-sync against a Confluent source#31049
Conversation
0808316 to
150f550
Compare
There was a problem hiding this comment.
Pull request overview
Adds cross-vendor end-to-end ducktape coverage for shadow-link Schema Registry “API mode” sync using a Confluent Platform Schema Registry + Apache Kafka as the source, and fixes soft-delete propagation so it converges even when the source vendor omits deleted from per-version responses.
Changes:
- Add a Confluent Schema Registry ducktape service and docker image dependency to run it in CI.
- Refactor the SR sync E2E test into a shared base with two leaf variants (Redpanda-source and Confluent-source), and make secondary broker count configurable to fit node budgets.
- Fix SR soft-delete replication by threading an authoritative “soft-deleted” partition (
work_set.soft_deleted) into the reconciler import path; update unit tests and the mirroring task accordingly.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/type-checking/type-check-strictness.json | Adds the new Confluent SR service file to the type-check strictness mapping. |
| tests/rptest/tests/cluster_linking_test_base.py | Makes secondary cluster broker count configurable via secondary_num_brokers. |
| tests/rptest/tests/cluster_linking_schema_registry_sync_test.py | Splits SR sync E2E into a base + per-vendor leaf tests; adds Confluent-source topology and hooks. |
| tests/rptest/services/confluent_schema_registry.py | Introduces a ducktape Service wrapper to run a single-node Confluent Schema Registry. |
| tests/docker/ducktape-deps/confluent-schema-registry | Downloads/pins Confluent Platform Community tarball for Schema Registry. |
| tests/docker/Dockerfile | Adds a build stage and copies /opt/confluent into the final ducktape image. |
| src/v/cluster_link/schema_registry_sync/tests/reconciler_test.cc | Updates/extends unit tests to cover caller-authoritative soft-delete behavior. |
| src/v/cluster_link/schema_registry_sync/reconciler.h | Extends work_set with soft_deleted and tracks it in the reconciler. |
| src/v/cluster_link/schema_registry_sync/reconciler.cc | Forces imported deleted-state from _soft_deleted rather than trusting per-version bodies. |
| src/v/cluster_link/schema_registry_sync/mirroring_task.cc | Populates work.soft_deleted from the source listing partition during full sync. |
150f550 to
e6b4d21
Compare
|
force-pushed: fixed copilot comments |
| // reconciler imports these soft-deleted regardless of the | ||
| // per-version source body, which a standard source (e.g. Confluent) | ||
| // does not populate with a `deleted` flag by default. | ||
| work.soft_deleted.insert(node); |
There was a problem hiding this comment.
It is unclear to me what this part of the commit message means
The reconciler took an imported version's soft-delete state from the
per-version source response body. A standard source (e.g. Confluent)
omits thedeletedflag from that body by default, so a soft-deleted
source version imported active and the full-sync diff re-imported it
every cycle, never converging. (Confluent can be coaxed into emitting
the flag with theConfluent-Accept-Unknown-Properties: truerequest
header.)
It sounds like you are saying that the fix in this commit is not needed if something else behaves differently (e.g. Confluent-Accept-Unknown-Properties: true is set). Is that accurate?
There was a problem hiding this comment.
Reworded this bit. The current fix works regardless of whether we get the deleted property, it derives the state from the listing and never reads the body flag. If we do get it, this will work as a fallback (that's @pgellert comment below, I'll try to handle that in a next force-push)
EDIT: will do that in a followup PR
| f"listeners=http://0.0.0.0:{self.port}\n" | ||
| f"kafkastore.bootstrap.servers={self._bootstrap_servers()}\n" | ||
| "kafkastore.topic=_schemas\n" | ||
| "schema.compatibility.level=none\n" |
There was a problem hiding this comment.
is compat level something we need to eventually surface into the test matrix?
There was a problem hiding this comment.
We could, but it is just a lower-layer default of the compatibility level returned in GET /config, so we are already replicating it.
| def _source_sr_url(self) -> str: | ||
| # Derive from the seeding client so the link source_url and the client | ||
| # can never point at different registries. | ||
| return self._make_source_client().base_uri() |
There was a problem hiding this comment.
Replacements below with self._source_sr_url() had been using self.source_cluster_service.schema_reg(limit=1), and now they use base_uri(). Are those equivalent?
There was a problem hiding this comment.
Yes, they are
| # Skip SchemaRegistrySyncE2EBase.__init__, which wires a Redpanda | ||
| # secondary SR; the source here is Apache Kafka + Confluent SR, so go | ||
| # straight to the shadow-link base with only the destination SR set. |
There was a problem hiding this comment.
this is confusing. are we inheriting from SchemaRegistrySyncE2EBase then just to get some code reuse? If so, then maybe we need to break out functionality into a MixIn instead?
There was a problem hiding this comment.
I'm wondering how hard it would be to hook up these tests to be parametrizable on the source cluster like we do it for AK vs Redpanda source clusters:
redpanda/tests/rptest/tests/cluster_linking_e2e_test.py
Lines 1180 to 1181 in 7299281
with just a simple selector for Redpanda SR or Confluent SR as the source. And then we can just parametrize all these tests on the source.
There was a problem hiding this comment.
Moved to mixins, if it's fine with you I'd leave it like this.
| # Two-broker Kafka source so the topology uses exactly the inherited | ||
| # num_nodes budget (see class docstring). | ||
| secondary_num_brokers = 2 |
There was a problem hiding this comment.
i don't understand this. is it configuring something in a super class?
There was a problem hiding this comment.
Yes, it was. Removed and passed this as part of SecondaryClusterArgs. This controls the size of source cluster nodes.
| schema.deleted = _soft_deleted.contains(n) ? ppsr::is_deleted::yes | ||
| : ppsr::is_deleted::no; |
There was a problem hiding this comment.
I'm not opposed to turning this into a follow-up task, but I think if we do get the deleted field on the response, then we should prefer that over the _soft_deleted set, because:
- The deleted field on the response is more up to date (came from a later API call than the list versions earlier)
- The field will be present in the majority of the cases
- It is not derived from a set difference between two API calls that may race with a low probability
- It would force us to represent the optional-presence of the deleted field in the rest client's response object, which makes this behaviour more explicit to any other future call sites as well.
There was a problem hiding this comment.
Lets tackle this in a followup
| # Skip SchemaRegistrySyncE2EBase.__init__, which wires a Redpanda | ||
| # secondary SR; the source here is Apache Kafka + Confluent SR, so go | ||
| # straight to the shadow-link base with only the destination SR set. |
There was a problem hiding this comment.
I'm wondering how hard it would be to hook up these tests to be parametrizable on the source cluster like we do it for AK vs Redpanda source clusters:
redpanda/tests/rptest/tests/cluster_linking_e2e_test.py
Lines 1180 to 1181 in 7299281
with just a simple selector for Redpanda SR or Confluent SR as the source. And then we can just parametrize all these tests on the source.
| f"listeners=http://0.0.0.0:{self.port}\n" | ||
| f"kafkastore.bootstrap.servers={self._bootstrap_servers()}\n" | ||
| "kafkastore.topic=_schemas\n" | ||
| "schema.compatibility.level=none\n" |
There was a problem hiding this comment.
We could, but it is just a lower-layer default of the compatibility level returned in GET /config, so we are already replicating it.
e6b4d21 to
4bf7cc2
Compare
|
force-pushed: fixed comments from @dotnwat
|
4bf7cc2 to
7c7622f
Compare
|
force-pushed: to resolve merge conflicts |
Retry command for Build#86930please wait until all jobs are finished before running the slash command |
7c7622f to
d22b228
Compare
second force-push: to resolve merge conflict |
The reconciler took an imported version's soft-delete state from the per-version source response body. That body field cannot be relied on across SR vendors: a standard source (e.g. Confluent) omits the `deleted` flag by default, so a soft-deleted source version imported active and the full-sync diff re-imported it every cycle, never converging. Rather than depend on that field, derive the deleted-state from the active-vs-deleted listing partition, which every vendor reports reliably. Thread it from the caller through work_set.soft_deleted into import_body, which sets the imported deleted-state from it and no longer reads the body flag at all. Converges in both directions. reconciler_test gains a regression test where the source body reports a version active but the caller declares it soft-deleted.
Download Confluent Platform Community (7.7.1) into the test-node image and add a single-node ConfluentSchemaRegistryService that runs the registry against any bootstrap_provider (an Apache Kafka or Redpanda cluster). Used as a cross-vendor source in the shadow-link SR tests.
Split the SR-sync e2e test into a non-collected mixin holding the shared logic plus per-source leaves: the existing Redpanda-source test (same test-ids) and a new Confluent-source leaf backed by an Apache Kafka cluster. Source registry access goes through _make_source_client / _source_sr_url hooks so the same validation surface runs against either vendor. Move the source broker count onto SecondaryClusterArgs so a leaf sizes its source where it configures it, instead of a class attribute the base reads back. The Confluent leaf holds its Kafka source to a single broker, fitting a 5-node budget (3 dest + 1 kafka + 1 SR); the Confluent SR needs a node of its own, and a one-broker source is enough with the SR's _schemas at RF=1.
d22b228 to
589e7dd
Compare
Adds end-to-end ducktape coverage for the shadow-link Schema Registry
"API mode" sync against a Confluent Platform source registry (backed by
an Apache Kafka cluster), alongside the existing Redpanda-to-Redpanda
coverage, and fixes the one product gap it surfaced. The suite is
restructured into a shared mixin with a thin leaf per source vendor, so
the same tests run against either source through a _make_source_client
hook.
Standing up the Confluent source revealed that soft-delete propagation
did not converge cross-vendor: the reconciler took an imported version's
deleted-state from the per-version source response body, which a
standard source (e.g. Confluent) omits by default, so a soft-deleted
version imported active and the full-sync diff re-imported it every
cycle, forever. The fix derives the deleted-state from the caller's
authoritative active-vs-deleted listing partition instead.
Release Notes