1616 */
1717package org .apache .kafka .connect .mirror ;
1818
19+ import org .apache .kafka .clients .consumer .Consumer ;
20+ import org .apache .kafka .clients .consumer .ConsumerRecord ;
21+ import org .apache .kafka .clients .consumer .MockConsumer ;
22+ import org .apache .kafka .clients .consumer .OffsetAndMetadata ;
23+ import org .apache .kafka .clients .consumer .internals .AutoOffsetResetStrategy ;
1924import org .apache .kafka .common .Configurable ;
25+ import org .apache .kafka .common .TopicPartition ;
2026
2127import org .junit .jupiter .api .Test ;
2228
29+ import java .time .Duration ;
2330import java .util .HashSet ;
2431import java .util .List ;
2532import java .util .Map ;
2633import java .util .Set ;
34+ import java .util .regex .Pattern ;
2735
2836import static org .junit .jupiter .api .Assertions .assertEquals ;
2937import static org .junit .jupiter .api .Assertions .assertFalse ;
38+ import static org .junit .jupiter .api .Assertions .assertThrows ;
3039import static org .junit .jupiter .api .Assertions .assertTrue ;
3140
3241public class MirrorClientTest {
3342
43+ private static final String SOURCE = "source" ;
44+
3445 private static class FakeMirrorClient extends MirrorClient {
3546
3647 List <String > topics ;
48+ public MockConsumer <byte [], byte []> consumer ;
3749
3850 FakeMirrorClient (List <String > topics ) {
3951 this (new DefaultReplicationPolicy (), topics );
@@ -52,6 +64,15 @@ private static class FakeMirrorClient extends MirrorClient {
5264 protected Set <String > listTopics () {
5365 return new HashSet <>(topics );
5466 }
67+
68+ @ Override
69+ Consumer <byte [], byte []> consumer () {
70+ if (consumer == null ) {
71+ return super .consumer ();
72+ } else {
73+ return consumer ;
74+ }
75+ }
5576 }
5677
5778 @ Test
@@ -208,9 +229,91 @@ public void testIdentityReplicationTopicSource() {
208229 .topicSource ("backup.heartbeats" ));
209230 }
210231
232+ @ Test
233+ public void testRemoteConsumerOffsetsIllegalArgs () {
234+ FakeMirrorClient client = new FakeMirrorClient ();
235+ assertThrows (IllegalArgumentException .class , () -> client .remoteConsumerOffsets ((Pattern ) null , "" , Duration .ofSeconds (1L )));
236+ assertThrows (IllegalArgumentException .class , () -> client .remoteConsumerOffsets (Pattern .compile ("" ), null , Duration .ofSeconds (1L )));
237+ assertThrows (IllegalArgumentException .class , () -> client .remoteConsumerOffsets (Pattern .compile ("" ), "" , null ));
238+ }
239+
240+ @ Test
241+ public void testRemoteConsumerOffsets () {
242+ String grp0 = "mygroup0" ;
243+ String grp1 = "mygroup1" ;
244+ FakeMirrorClient client = new FakeMirrorClient ();
245+ String checkpointTopic = client .replicationPolicy ().checkpointsTopic (SOURCE );
246+ TopicPartition checkpointTp = new TopicPartition (checkpointTopic , 0 );
247+
248+ TopicPartition t0p0 = new TopicPartition ("topic0" , 0 );
249+ TopicPartition t0p1 = new TopicPartition ("topic0" , 1 );
250+
251+ Checkpoint cp0 = new Checkpoint (grp0 , t0p0 , 1L , 1L , "cp0" );
252+ Checkpoint cp1 = new Checkpoint (grp0 , t0p0 , 2L , 2L , "cp1" );
253+ Checkpoint cp2 = new Checkpoint (grp0 , t0p1 , 3L , 3L , "cp2" );
254+ Checkpoint cp3 = new Checkpoint (grp1 , t0p1 , 4L , 4L , "cp3" );
255+
256+ // Batch translation matches only mygroup0
257+ client .consumer = buildConsumer (checkpointTp , cp0 , cp1 , cp2 , cp3 );
258+ Map <String , Map <TopicPartition , OffsetAndMetadata >> offsets = client .remoteConsumerOffsets (
259+ Pattern .compile (grp0 ), SOURCE , Duration .ofSeconds (10L ));
260+ Map <String , Map <TopicPartition , OffsetAndMetadata >> expectedOffsets = Map .of (
261+ grp0 , Map .of (
262+ t0p0 , cp1 .offsetAndMetadata (),
263+ t0p1 , cp2 .offsetAndMetadata ()
264+ )
265+ );
266+ assertEquals (expectedOffsets , offsets );
267+
268+ // Batch translation matches all groups
269+ client .consumer = buildConsumer (checkpointTp , cp0 , cp1 , cp2 , cp3 );
270+ offsets = client .remoteConsumerOffsets (Pattern .compile (".*" ), SOURCE , Duration .ofSeconds (10L ));
271+ expectedOffsets = Map .of (
272+ grp0 , Map .of (
273+ t0p0 , cp1 .offsetAndMetadata (),
274+ t0p1 , cp2 .offsetAndMetadata ()
275+ ),
276+ grp1 , Map .of (
277+ t0p1 , cp3 .offsetAndMetadata ()
278+ )
279+ );
280+ assertEquals (expectedOffsets , offsets );
281+
282+ // Batch translation matches nothing
283+ client .consumer = buildConsumer (checkpointTp , cp0 , cp1 , cp2 , cp3 );
284+ offsets = client .remoteConsumerOffsets (Pattern .compile ("unknown-group" ), SOURCE , Duration .ofSeconds (10L ));
285+ assertTrue (offsets .isEmpty ());
286+
287+ // Translation for mygroup0
288+ client .consumer = buildConsumer (checkpointTp , cp0 , cp1 , cp2 , cp3 );
289+ Map <TopicPartition , OffsetAndMetadata > offsets2 = client .remoteConsumerOffsets (grp0 , SOURCE , Duration .ofSeconds (10L ));
290+ Map <TopicPartition , OffsetAndMetadata > expectedOffsets2 = Map .of (
291+ t0p0 , cp1 .offsetAndMetadata (),
292+ t0p1 , cp2 .offsetAndMetadata ()
293+ );
294+ assertEquals (expectedOffsets2 , offsets2 );
295+
296+ // Translation for unknown group
297+ client .consumer = buildConsumer (checkpointTp , cp0 , cp1 , cp2 , cp3 );
298+ offsets2 = client .remoteConsumerOffsets ("unknown-group" , SOURCE , Duration .ofSeconds (10L ));
299+ assertTrue (offsets2 .isEmpty ());
300+ }
301+
211302 private ReplicationPolicy identityReplicationPolicy (String source ) {
212303 IdentityReplicationPolicy policy = new IdentityReplicationPolicy ();
213304 policy .configure (Map .of (IdentityReplicationPolicy .SOURCE_CLUSTER_ALIAS_CONFIG , source ));
214305 return policy ;
215306 }
307+
308+ private MockConsumer <byte [], byte []> buildConsumer (TopicPartition checkpointTp , Checkpoint ... checkpoints ) {
309+ MockConsumer <byte [], byte []> consumer = new MockConsumer <>(AutoOffsetResetStrategy .NONE .name ());
310+ consumer .updateBeginningOffsets (Map .of (checkpointTp , 0L ));
311+ consumer .assign (Set .of (checkpointTp ));
312+ for (int i = 0 ; i < checkpoints .length ; i ++) {
313+ Checkpoint checkpoint = checkpoints [i ];
314+ consumer .addRecord (new ConsumerRecord <>(checkpointTp .topic (), 0 , i , checkpoint .recordKey (), checkpoint .recordValue ()));
315+ }
316+ consumer .updateEndOffsets (Map .of (checkpointTp , checkpoints .length - 1L ));
317+ return consumer ;
318+ }
216319}
0 commit comments