Skip to content

Commit 150f550

Browse files
cluster_link: e2e-test SR-sync against a Confluent source
Split the SR-sync e2e test into a non-collected base 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. secondary_num_brokers lets the Confluent leaf shrink the Kafka source to 2 brokers, fitting the inherited 6-node budget (3 dest + 2 kafka + 1 SR).
1 parent fae8bcd commit 150f550

2 files changed

Lines changed: 102 additions & 12 deletions

File tree

tests/rptest/tests/cluster_linking_schema_registry_sync_test.py

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727
from rptest.clients.rpk import RpkTool
2828
from rptest.services.admin import Admin
2929
from rptest.services.cluster import TestContext, cluster
30-
from rptest.services.multi_cluster_services import SecondaryClusterArgs
30+
from rptest.services.confluent_schema_registry import ConfluentSchemaRegistryService
31+
from rptest.services.multi_cluster_services import (
32+
SecondaryClusterArgs,
33+
SecondaryClusterSpec,
34+
ServiceType,
35+
)
3136
from rptest.services.redpanda import SchemaRegistryConfig
3237
from rptest.tests.cluster_linking_test_base import ShadowLinkTestBase
3338
from rptest.tests.schema_registry_test import SchemaRegistryRedpandaClient
@@ -38,9 +43,14 @@
3843
Pair = tuple[str, int]
3944

4045

41-
class SchemaRegistrySyncE2ETest(ShadowLinkTestBase):
46+
class SchemaRegistrySyncE2EBase(ShadowLinkTestBase):
4247
"""Source cluster SR -> destination cluster SR over the shadow-link HTTP
43-
source reader, at scale and with concurrent / post-sync additions."""
48+
source reader, at scale and with concurrent / post-sync additions.
49+
50+
Shared implementation for the per-source-vendor leaf classes below. Ducktape
51+
only collects leaf Test classes (no subclasses), so this base is not run
52+
directly; the concrete `SchemaRegistrySyncE2ETest` (Redpanda source) and
53+
`ConfluentSchemaRegistrySyncE2ETest` (Confluent source) leaves are."""
4454

4555
# A layered diamond DAG (top -> mid -> leaf), like the reconciler
4656
# concurrent_stress unit test but larger: adjacent referrers share referents,
@@ -67,6 +77,19 @@ def __init__(self, test_context: TestContext, *args: Any, **kwargs: Any):
6777
**kwargs,
6878
)
6979

80+
# --- source Schema Registry hooks --------------------------------------
81+
# Overridden by cross-vendor subclasses (e.g. a Confluent Platform source)
82+
# to point the seeding client and the shadow-link source URL at a
83+
# non-Redpanda registry. The destination side stays Redpanda throughout.
84+
85+
def _make_source_client(self) -> SchemaRegistryRedpandaClient:
86+
return SchemaRegistryRedpandaClient(self.source_cluster_service)
87+
88+
def _source_sr_url(self) -> str:
89+
# Derive from the seeding client so the link source_url and the client
90+
# can never point at different registries.
91+
return self._make_source_client().base_uri()
92+
7093
# --- schema builders ---------------------------------------------------
7194

7295
def _record(self, name: str, fields: list[dict]) -> dict:
@@ -199,7 +222,7 @@ def _create_sr_link(
199222
# subsequent full syncs land quickly. An optional exact-subject filter
200223
# scopes replication. Returns the source URL used.
201224
if source_url is None:
202-
source_url = self.source_cluster_service.schema_reg(limit=1)
225+
source_url = self._source_sr_url()
203226
req = self.create_default_link_request(
204227
link_name=LINK_NAME,
205228
mirror_all_topics=False,
@@ -440,7 +463,7 @@ def _verify_rpk_status(self, expected_subjects: int, expected_versions: int):
440463

441464
@cluster(num_nodes=6)
442465
def test_schema_registry_api_sync_e2e(self):
443-
src = SchemaRegistryRedpandaClient(self.source_cluster_service)
466+
src = self._make_source_client()
444467
dest = SchemaRegistryRedpandaClient(self.target_cluster_service)
445468

446469
# The destination `_schemas` topic is NOT warmed up here on purpose:
@@ -517,7 +540,7 @@ def test_schema_registry_api_sync_e2e(self):
517540

518541
@cluster(num_nodes=6)
519542
def test_schema_registry_api_sync_soft_delete(self):
520-
src = SchemaRegistryRedpandaClient(self.source_cluster_service)
543+
src = self._make_source_client()
521544
dest = SchemaRegistryRedpandaClient(self.target_cluster_service)
522545

523546
# seeded-value: mixed active/deleted before the first sync.
@@ -559,7 +582,7 @@ def _v2(name: str) -> dict:
559582

560583
@cluster(num_nodes=6)
561584
def test_schema_registry_api_sync_out_of_scope_reference(self):
562-
src = SchemaRegistryRedpandaClient(self.source_cluster_service)
585+
src = self._make_source_client()
563586
dest = SchemaRegistryRedpandaClient(self.target_cluster_service)
564587

565588
# leaf-0 is a referent; mid-0 references it; ok-value is independent.
@@ -601,7 +624,7 @@ def errored() -> bool:
601624

602625
@cluster(num_nodes=6)
603626
def test_schema_registry_api_sync_recovers_after_source_unavailable(self):
604-
src = SchemaRegistryRedpandaClient(self.source_cluster_service)
627+
src = self._make_source_client()
605628
dest = SchemaRegistryRedpandaClient(self.target_cluster_service)
606629

607630
self._register(
@@ -630,7 +653,7 @@ def parked() -> bool:
630653

631654
# Repoint the link at the real source. The config change forces a fresh
632655
# full sync, which now reaches the source and replicates.
633-
good_url = self.source_cluster_service.schema_reg(limit=1)
656+
good_url = self._source_sr_url()
634657
link = self.get_link(LINK_NAME)
635658
link.configurations.schema_registry_sync_options.shadow_schema_registry_api.source_url = good_url
636659
self.update_link(
@@ -659,7 +682,7 @@ def recovered() -> bool:
659682

660683
@cluster(num_nodes=6)
661684
def test_schema_registry_api_sync_memory_backpressure(self):
662-
src = SchemaRegistryRedpandaClient(self.source_cluster_service)
685+
src = self._make_source_client()
663686
dest = SchemaRegistryRedpandaClient(self.target_cluster_service)
664687

665688
# Shrink the reconcile memory budget to its 1 MiB floor and raise
@@ -689,7 +712,7 @@ def test_schema_registry_api_sync_memory_backpressure(self):
689712

690713
@cluster(num_nodes=6)
691714
def test_schema_registry_api_sync_survives_leadership_change(self):
692-
src = SchemaRegistryRedpandaClient(self.source_cluster_service)
715+
src = self._make_source_client()
693716
dest = SchemaRegistryRedpandaClient(self.target_cluster_service)
694717
admin = Admin(self.target_cluster_service)
695718

@@ -836,3 +859,65 @@ def returning_leader_fresh() -> bool:
836859
backoff_sec=1,
837860
err_msg="reused instance did not reset state on regaining leadership",
838861
)
862+
863+
864+
class SchemaRegistrySyncE2ETest(SchemaRegistrySyncE2EBase):
865+
"""Redpanda source cluster variant: the base's default source hooks point at
866+
the secondary Redpanda cluster's Schema Registry."""
867+
868+
869+
class ConfluentSchemaRegistrySyncE2ETest(SchemaRegistrySyncE2EBase):
870+
"""Runs the full SchemaRegistrySyncE2ETest validation surface with a
871+
Confluent Platform Schema Registry (backed by an Apache Kafka source
872+
cluster) as the shadow-link source instead of a secondary Redpanda cluster.
873+
The destination stays Redpanda. See CORE-16776 / CORE-16357.
874+
875+
Node budget: 3 (destination Redpanda) + 2 (Apache Kafka source) + 1
876+
(Confluent SR) = 6, matching the inherited @cluster(num_nodes=6).
877+
"""
878+
879+
# Two-broker Kafka source so the topology uses exactly the inherited
880+
# num_nodes budget (see class docstring).
881+
secondary_num_brokers = 2
882+
883+
def __init__(self, test_context: TestContext, *args: Any, **kwargs: Any):
884+
# Skip SchemaRegistrySyncE2ETest.__init__, which wires a Redpanda
885+
# secondary SR; the source here is Apache Kafka + Confluent SR, so go
886+
# straight to the shadow-link base with only the destination SR set.
887+
ShadowLinkTestBase.__init__(
888+
self,
889+
test_context,
890+
schema_registry_config=SchemaRegistryConfig(),
891+
*args,
892+
**kwargs,
893+
)
894+
self._confluent_sr: ConfluentSchemaRegistryService | None = None
895+
896+
def get_source_cluster_spec(self) -> SecondaryClusterSpec:
897+
# Source cluster is Apache Kafka (KRaft, secondary_num_brokers nodes);
898+
# the Confluent SR is attached to it in setUp. Mirrors the Kafka-source
899+
# topology used by cluster_linking_e2e_test.
900+
return SecondaryClusterSpec(
901+
ServiceType.KAFKA, kafka_version="3.8.0", kafka_quorum="COMBINED_KRAFT"
902+
)
903+
904+
def setUp(self):
905+
# Starts the Apache Kafka source + Redpanda destination clusters.
906+
super().setUp()
907+
# Attach a Confluent Schema Registry to the Kafka source and start it.
908+
self._confluent_sr = ConfluentSchemaRegistryService(
909+
self.test_context, bootstrap_provider=self.source_cluster_service
910+
)
911+
self._confluent_sr.start()
912+
913+
def _confluent(self) -> ConfluentSchemaRegistryService:
914+
assert self._confluent_sr is not None, "Confluent SR not started"
915+
return self._confluent_sr
916+
917+
def _make_source_client(self) -> SchemaRegistryRedpandaClient:
918+
sr = self._confluent()
919+
# The base client hardcodes port 8081 and only needs
920+
# .nodes[*].account.hostname + .logger, so the ducktape service
921+
# duck-types cleanly; the Confluent SR must use the default port.
922+
assert sr.port == 8081, "Confluent SR must listen on 8081 for the SR client"
923+
return SchemaRegistryRedpandaClient(sr) # type: ignore[arg-type]

tests/rptest/tests/cluster_linking_test_base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ class ShadowLinkTestBase(PreallocNodesTest):
585585
the target cluster. Secondary service is used as the source cluster.
586586
"""
587587

588+
# Broker count for the secondary (source) cluster. Subclasses can shrink
589+
# this to stay within their @cluster(num_nodes=...) budget when the source
590+
# topology needs extra nodes (e.g. a separate Schema Registry service).
591+
secondary_num_brokers: int = 3
592+
588593
def __init__(
589594
self,
590595
test_context: TestContext,
@@ -741,7 +746,7 @@ def setUp(self):
741746
self.logger,
742747
self.redpanda,
743748
secondary_spec=self.source_cluster_spec,
744-
num_brokers=3,
749+
num_brokers=self.secondary_num_brokers,
745750
secondary_args=self.secondary_cluster_args,
746751
)
747752
self.services.setUp()

0 commit comments

Comments
 (0)