@@ -48,6 +48,7 @@ def test_shadow_link_full_sync(self):
4848 expected_acl = self ._seed_source_acl ()
4949 source_offsets = self ._seed_source_consumer_group (topic )
5050 subject = self ._seed_source_schema ()
51+ expected_role = self ._seed_source_role ()
5152
5253 assert not self .topic_exists_in_target (topic .name ), (
5354 f"{ topic .name } unexpectedly present on the shadow cluster before linking"
@@ -88,6 +89,13 @@ def test_shadow_link_full_sync(self):
8889 retry_on_exc = True ,
8990 err_msg = "schema registry subject was not synced to the shadow cluster" ,
9091 )
92+ wait_until (
93+ lambda : self ._role_synced (rpk , expected_role ),
94+ timeout_sec = 90 ,
95+ backoff_sec = 2 ,
96+ retry_on_exc = True ,
97+ err_msg = "role was not synced to the shadow cluster" ,
98+ )
9199
92100 def _full_link_config (self ) -> dict [str , Any ]:
93101 """
@@ -132,6 +140,16 @@ def _full_link_config(self) -> dict[str, Any]:
132140 "schema_registry_sync_options" : {
133141 "shadow_schema_registry_topic" : {},
134142 },
143+ "role_sync_options" : {
144+ "interval" : "1s" ,
145+ "role_name_filters" : [
146+ {
147+ "pattern_type" : "PREFIX" ,
148+ "filter_type" : "INCLUDE" ,
149+ "name" : "synced-" ,
150+ }
151+ ],
152+ },
135153 }
136154
137155 def _seed_source_topic (self ) -> TopicSpec :
@@ -191,6 +209,16 @@ def _seed_source_schema(self) -> str:
191209 self .source_cluster_rpk .create_schema_from_str (subject , schema )
192210 return subject
193211
212+ def _seed_source_role (self ) -> dict [str , str ]:
213+ """Create a role with a member on the source cluster, and return the
214+ role name and member principal expected to appear on the shadow
215+ cluster. The name matches the link's PREFIX 'synced-' role filter."""
216+ role = "synced-test-role"
217+ member = "role-member"
218+ self .source_cluster_rpk .create_role (role )
219+ self .source_cluster_rpk .assign_role (role , [member ])
220+ return {"role" : role , "member" : member }
221+
194222 def _assert_link_listed (self , rpk : RpkTool ) -> None :
195223 links = rpk .shadow_list ()
196224 self .logger .debug (f"rpk shadow list: { links } " )
@@ -224,6 +252,17 @@ def _schema_synced(self, subject: str) -> bool:
224252 names = [s ["subject" ] if isinstance (s , dict ) else s for s in subjects ]
225253 return subject in names
226254
255+ def _role_synced (self , rpk : RpkTool , expected : dict [str , str ]) -> bool :
256+ roles = rpk .list_roles ().get ("roles" , [])
257+ self .logger .debug (f"target roles: { roles } " )
258+ if expected ["role" ] not in roles :
259+ return False
260+ members = [
261+ m ["name" ] for m in rpk .describe_role (expected ["role" ]).get ("members" , [])
262+ ]
263+ self .logger .debug (f"target role { expected ['role' ]} members: { members } " )
264+ return expected ["member" ] in members
265+
227266 def _wait_link_active (self , rpk : RpkTool ) -> None :
228267 def link_active () -> bool :
229268 status = rpk .shadow_status (self .LINK_NAME )
0 commit comments