@platformatic/kafka 2.2.3 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -374,7 +374,7 @@ export class Admin extends Base {
374
374
  }
375
375
  #handleNotControllerError(error, value, callback) {
376
376
  if (error && error?.findBy?.('apiCode', 41)) {
377
- this.metadata({ topics: [] }, (metadataError, metadata) => {
377
+ this[kMetadata]({ topics: [], forceUpdate: true }, (metadataError, metadata) => {
378
378
  /* c8 ignore next 4 - Hard to test */
379
379
  if (metadataError) {
380
380
  callback(metadataError);
@@ -407,7 +407,7 @@ export class Admin extends Base {
407
407
  });
408
408
  }, (error, metadata) => {
409
409
  if (error) {
410
- deduplicateCallback(error);
410
+ deduplicateCallback(new MultipleErrors('Listing topics failed.', [error]));
411
411
  return;
412
412
  }
413
413
  const topics = new Set();
@@ -460,7 +460,7 @@ export class Admin extends Base {
460
460
  });
461
461
  }, (error, response) => {
462
462
  if (error) {
463
- deduplicateCallback(error);
463
+ deduplicateCallback(new MultipleErrors('Creating topics failed.', [error]));
464
464
  return;
465
465
  }
466
466
  const created = [];
@@ -497,7 +497,13 @@ export class Admin extends Base {
497
497
  });
498
498
  });
499
499
  });
500
- }, deduplicateCallback, 0);
500
+ }, (error, response) => {
501
+ if (error) {
502
+ callback(new MultipleErrors('Deleting topics failed.', [error]));
503
+ return;
504
+ }
505
+ deduplicateCallback(null, response);
506
+ }, 0);
501
507
  }, error => callback(error));
502
508
  }
503
509
  #createPartitions(options, callback) {
@@ -529,13 +535,14 @@ export class Admin extends Base {
529
535
  });
530
536
  }
531
537
  #listGroups(options, callback) {
532
- // Find all the brokers in the cluster
533
- this[kMetadata]({ topics: [] }, (error, metadata) => {
538
+ this[kMetadata]({ topics: [], forceUpdate: true }, (error, metadata) => {
534
539
  if (error) {
535
540
  callback(error);
536
541
  return;
537
542
  }
538
- runConcurrentCallbacks('Listing groups failed.', metadata.brokers, ([, broker], concurrentCallback) => {
543
+ runConcurrentCallbacks('Listing groups failed.',
544
+ // metadata must be defined as error was undefined
545
+ metadata.brokers, ([, broker], concurrentCallback) => {
539
546
  this[kGetConnection](broker, (error, connection) => {
540
547
  if (error) {
541
548
  concurrentCallback(error);
@@ -573,45 +580,42 @@ export class Admin extends Base {
573
580
  });
574
581
  }
575
582
  #describeGroups(options, callback) {
576
- this[kMetadata]({ topics: [] }, (error, metadata) => {
577
- if (error) {
578
- callback(error);
579
- return;
580
- }
583
+ this[kPerformWithRetry]('describeGroups', retryCallback => {
581
584
  this.#findCoordinator({ keyType: FindCoordinatorKeyTypes.GROUP, keys: options.groups }, (error, coordinators) => {
582
585
  if (error) {
583
- callback(error);
586
+ // FindCoordinator already retried some times so no retry again
587
+ callback(new MultipleErrors('Describing groups failed.', error instanceof MultipleErrors ? error.errors : [error]));
584
588
  return;
585
589
  }
586
- // Group the groups by coordinator
587
590
  const coordinatorsMap = new Map();
588
- for (const { key: group, nodeId: node } of coordinators) {
589
- let coordinator = coordinatorsMap.get(node);
590
- if (!coordinator) {
591
- coordinator = [];
592
- coordinatorsMap.set(node, coordinator);
591
+ // coordinators must be defined as error was undefined
592
+ for (const coordinator of coordinators) {
593
+ let coordinatorAggregate = coordinatorsMap.get(coordinator.nodeId);
594
+ if (!coordinatorAggregate) {
595
+ coordinatorAggregate = { host: coordinator.host, port: coordinator.port, groups: [] };
596
+ coordinatorsMap.set(coordinator.nodeId, coordinatorAggregate);
593
597
  }
594
- coordinator.push(group);
598
+ coordinatorAggregate.groups.push(coordinator.key);
595
599
  }
596
- runConcurrentCallbacks('Describing groups failed.', coordinatorsMap, ([node, groups], concurrentCallback) => {
597
- this[kGetConnection](metadata.brokers.get(node), (error, connection) => {
598
- if (error) {
599
- concurrentCallback(error);
600
- return;
601
- }
602
- this[kPerformWithRetry]('describeGroups', retryCallback => {
600
+ runConcurrentCallbacks('Describing groups failed.', coordinatorsMap.values(), (coordinator, concurrentCallback) => {
601
+ this[kPerformWithRetry]('describeGroupsOnCoordinator', retryCallback => {
602
+ this[kGetConnection](coordinator, (error, connection) => {
603
+ if (error) {
604
+ retryCallback(error);
605
+ return;
606
+ }
603
607
  this[kGetApi]('DescribeGroups', (error, api) => {
604
608
  if (error) {
605
609
  retryCallback(error);
606
610
  return;
607
611
  }
608
- api(connection, groups, options.includeAuthorizedOperations ?? false, retryCallback);
612
+ api(connection, coordinator.groups, options.includeAuthorizedOperations ?? false, retryCallback);
609
613
  });
610
- }, concurrentCallback, 0);
611
- });
614
+ });
615
+ }, concurrentCallback, 0);
612
616
  }, (error, results) => {
613
617
  if (error) {
614
- callback(error);
618
+ retryCallback(error);
615
619
  return;
616
620
  }
617
621
  const groups = new Map();
@@ -655,59 +659,58 @@ export class Admin extends Base {
655
659
  groups.set(group.id, group);
656
660
  }
657
661
  }
658
- callback(null, groups);
662
+ retryCallback(null, groups);
659
663
  });
660
664
  });
661
- });
665
+ }, callback, 0);
662
666
  }
663
667
  #deleteGroups(options, callback) {
664
- this[kMetadata]({ topics: [] }, (error, metadata) => {
665
- if (error) {
666
- callback(error);
667
- return;
668
- }
668
+ this[kPerformWithRetry]('deleteGroups', retryCallback => {
669
669
  this.#findCoordinator({ keyType: FindCoordinatorKeyTypes.GROUP, keys: options.groups }, (error, coordinators) => {
670
670
  if (error) {
671
- callback(error);
671
+ // FindCoordinator already retried some times so no retry again
672
+ callback(new MultipleErrors('Deleting groups failed.', error instanceof MultipleErrors ? error.errors : [error]));
672
673
  return;
673
674
  }
674
- // Group the groups by coordinator
675
675
  const coordinatorsMap = new Map();
676
- for (const { key: group, nodeId: node } of coordinators) {
677
- let coordinator = coordinatorsMap.get(node);
678
- if (!coordinator) {
679
- coordinator = [];
680
- coordinatorsMap.set(node, coordinator);
676
+ // coordinators must be defined as error was undefined
677
+ for (const coordinator of coordinators) {
678
+ let coordinatorAggregate = coordinatorsMap.get(coordinator.nodeId);
679
+ if (!coordinatorAggregate) {
680
+ coordinatorAggregate = { host: coordinator.host, port: coordinator.port, groups: [] };
681
+ coordinatorsMap.set(coordinator.nodeId, coordinatorAggregate);
681
682
  }
682
- coordinator.push(group);
683
+ coordinatorAggregate.groups.push(coordinator.key);
683
684
  }
684
- runConcurrentCallbacks('Deleting groups failed.', coordinatorsMap, ([node, groups], concurrentCallback) => {
685
- this[kGetConnection](metadata.brokers.get(node), (error, connection) => {
686
- if (error) {
687
- concurrentCallback(error);
688
- return;
689
- }
690
- this[kPerformWithRetry]('deleteGroups', retryCallback => {
685
+ runConcurrentCallbacks('Deleting groups failed.', coordinatorsMap.values(), (coordinator, concurrentCallback) => {
686
+ this[kPerformWithRetry]('deleteGroupsOnCoordinator', retryCallback => {
687
+ this[kGetConnection](coordinator, (error, connection) => {
688
+ if (error) {
689
+ retryCallback(error);
690
+ return;
691
+ }
691
692
  this[kGetApi]('DeleteGroups', (error, api) => {
692
693
  if (error) {
693
694
  retryCallback(error);
694
695
  return;
695
696
  }
696
- api(connection, groups, retryCallback);
697
+ api(connection, coordinator.groups, retryCallback);
697
698
  });
698
- }, concurrentCallback, 0);
699
- });
700
- }, error => callback(error));
699
+ });
700
+ }, concurrentCallback, 0);
701
+ }, retryCallback);
701
702
  });
702
- });
703
+ }, callback, 0);
703
704
  }
704
705
  #removeMembersFromConsumerGroup(options, callback) {
705
706
  if (!options.members || options.members.length === 0) {
707
+ // Remove all members if none are specified
706
708
  this.#describeGroups({ groups: [options.groupId] }, (error, groupsMap) => {
707
709
  if (error) {
708
710
  callback(new MultipleErrors('Removing members from consumer group failed.', [error]));
709
711
  return;
710
712
  }
713
+ // groupsMap must be defined as error was undefined
711
714
  const group = groupsMap.get(options.groupId);
712
715
  /* c8 ignore next 4 - Hard to test */
713
716
  if (!group) {
@@ -724,61 +727,46 @@ export class Admin extends Base {
724
727
  });
725
728
  return;
726
729
  }
727
- this[kMetadata]({ topics: [] }, (error, metadata) => {
728
- if (error) {
729
- callback(error, undefined);
730
- return;
731
- }
730
+ this[kPerformWithRetry]('removeMembersFromConsumerGroup', retryCallback => {
732
731
  this.#findCoordinator({ keyType: FindCoordinatorKeyTypes.GROUP, keys: [options.groupId] }, (error, coordinators) => {
733
732
  if (error) {
734
- callback(new MultipleErrors('Removing members from consumer group failed.', [error]));
733
+ // FindCoordinator already retried some times so no retry again
734
+ callback(new MultipleErrors('Removing members from consumer group failed.', error instanceof MultipleErrors ? error.errors : [error]));
735
735
  return;
736
736
  }
737
737
  const coordinator = coordinators.find(c => c.key === options.groupId);
738
738
  /* c8 ignore next 8 - Hard to test */
739
739
  if (!coordinator) {
740
- callback(new MultipleErrors('Removing members from consumer group failed.', [
741
- new Error(`No coordinator found for group ${options.groupId}`)
742
- ]));
740
+ retryCallback(new Error('Group coordinator not found'));
743
741
  return;
744
742
  }
745
- const broker = metadata.brokers.get(coordinator.nodeId);
746
- /* c8 ignore next 8 - Hard to test */
747
- if (!broker) {
748
- callback(new MultipleErrors('Removing members from consumer group failed.', [
749
- new Error(`Broker ${coordinator.nodeId} not found`)
750
- ]));
751
- return;
752
- }
753
- this[kGetConnection](broker, (error, connection) => {
743
+ this[kGetConnection](coordinator, (error, connection) => {
754
744
  if (error) {
755
- callback(new MultipleErrors('Removing members from consumer group failed.', [error]));
745
+ retryCallback(error);
756
746
  return;
757
747
  }
758
- this[kPerformWithRetry]('removeMembersFromConsumerGroup', retryCallback => {
759
- this[kGetApi]('LeaveGroup', (error, api) => {
760
- if (error) {
761
- retryCallback(error);
762
- return;
763
- }
764
- /* c8 ignore next 5 - Hard to test */
765
- const members = options.members.map(member => ({
766
- memberId: typeof member === 'string' ? member : member.memberId,
767
- groupInstanceId: null,
768
- reason: typeof member === 'string' ? 'Not specified' : member.reason
769
- }));
770
- api(connection, options.groupId, members, retryCallback);
771
- });
772
- }, error => {
748
+ this[kGetApi]('LeaveGroup', (error, api) => {
773
749
  if (error) {
774
- callback(new MultipleErrors('Removing members from consumer group failed.', [error]));
750
+ retryCallback(error);
775
751
  return;
776
752
  }
777
- callback(null);
778
- }, 0);
753
+ /* c8 ignore next 5 - Hard to test */
754
+ const members = options.members.map(member => ({
755
+ memberId: typeof member === 'string' ? member : member.memberId,
756
+ groupInstanceId: null,
757
+ reason: typeof member === 'string' ? 'Not specified' : member.reason
758
+ }));
759
+ api(connection, options.groupId, members, retryCallback);
760
+ });
779
761
  });
780
762
  });
781
- });
763
+ }, error => {
764
+ if (error) {
765
+ callback(new MultipleErrors('Removing members from consumer group failed.', [error]));
766
+ return;
767
+ }
768
+ callback(null);
769
+ }, 0);
782
770
  }
783
771
  #findCoordinator(options, callback) {
784
772
  this[kPerformWithRetry]('findCoordinator', retryCallback => {
@@ -797,7 +785,7 @@ export class Admin extends Base {
797
785
  });
798
786
  }, (error, response) => {
799
787
  if (error) {
800
- callback(error);
788
+ callback(new MultipleErrors('Finding coordinator failed.', [error]));
801
789
  return;
802
790
  }
803
791
  callback(null, response.coordinators.map(coordinator => ({
@@ -855,19 +843,21 @@ export class Admin extends Base {
855
843
  }, 0);
856
844
  }
857
845
  #describeLogDirs(options, callback) {
858
- this[kMetadata]({ topics: [] }, (error, metadata) => {
846
+ this[kMetadata]({ topics: [], forceUpdate: true }, (error, metadata) => {
859
847
  /* c8 ignore next 4 - Hard to test */
860
848
  if (error) {
861
849
  callback(error);
862
850
  return;
863
851
  }
864
- runConcurrentCallbacks('Describing log dirs failed.', metadata.brokers, ([id, broker], concurrentCallback) => {
865
- this[kGetConnection](broker, (error, connection) => {
866
- if (error) {
867
- concurrentCallback(error);
868
- return;
869
- }
870
- this[kPerformWithRetry]('describeLogDirs', retryCallback => {
852
+ runConcurrentCallbacks('Describing log dirs failed.',
853
+ // metadata must be defined as error was undefined
854
+ metadata.brokers, ([id, broker], concurrentCallback) => {
855
+ this[kPerformWithRetry]('describeLogDirs', retryCallback => {
856
+ this[kGetConnection](broker, (error, connection) => {
857
+ if (error) {
858
+ retryCallback(error);
859
+ return;
860
+ }
871
861
  this[kGetApi]('DescribeLogDirs', (error, api) => {
872
862
  if (error) {
873
863
  retryCallback(error);
@@ -875,82 +865,84 @@ export class Admin extends Base {
875
865
  }
876
866
  api(connection, options.topics, retryCallback);
877
867
  });
878
- }, (error, response) => {
879
- if (error) {
880
- concurrentCallback(error);
881
- return;
882
- }
883
- concurrentCallback(null, {
884
- broker: id,
885
- throttleTimeMs: response.throttleTimeMs,
886
- results: response.results.map(result => ({
887
- logDir: result.logDir,
888
- topics: result.topics,
889
- totalBytes: result.totalBytes,
890
- usableBytes: result.usableBytes
891
- }))
892
- });
893
- }, 0);
894
- });
868
+ });
869
+ }, (error, response) => {
870
+ if (error) {
871
+ concurrentCallback(error);
872
+ return;
873
+ }
874
+ concurrentCallback(null, {
875
+ broker: id,
876
+ throttleTimeMs: response.throttleTimeMs,
877
+ results: response.results.map(result => ({
878
+ logDir: result.logDir,
879
+ topics: result.topics,
880
+ totalBytes: result.totalBytes,
881
+ usableBytes: result.usableBytes
882
+ }))
883
+ });
884
+ }, 0);
895
885
  }, callback);
896
886
  });
897
887
  }
898
888
  #listConsumerGroupOffsets(options, callback) {
899
- this[kMetadata]({ topics: [] }, (error, metadata) => {
900
- if (error) {
901
- callback(error);
902
- return;
903
- }
889
+ this[kPerformWithRetry]('listConsumerGroupOffsets', retryCallback => {
904
890
  /* c8 ignore next - Hard to test */
905
891
  const groupIds = options.groups.map(group => (typeof group === 'string' ? group : group.groupId));
906
892
  this.#findCoordinator({ keyType: FindCoordinatorKeyTypes.GROUP, keys: groupIds }, (error, coordinators) => {
907
893
  if (error) {
908
- callback(new MultipleErrors('Listing consumer group offsets failed.', [error]));
894
+ // FindCoordinator already retried some times so no retry again
895
+ callback(new MultipleErrors('Listing consumer group offsets failed.', error instanceof MultipleErrors ? error.errors : [error]));
909
896
  return;
910
897
  }
911
898
  const coordinatorsMap = new Map();
912
- for (const { key: groupId, nodeId: node } of coordinators) {
899
+ for (const coordinator of coordinators) {
913
900
  const groupRequest = {
914
- groupId,
901
+ groupId: coordinator.key,
915
902
  memberId: null,
916
903
  memberEpoch: -1
917
904
  };
918
905
  for (const group of options.groups) {
919
- if (typeof group !== 'string' && group.groupId === groupId && !!group.topics && group.topics.length > 0) {
906
+ if (typeof group !== 'string' &&
907
+ group.groupId === coordinator.key &&
908
+ !!group.topics &&
909
+ group.topics.length > 0) {
920
910
  groupRequest.topics = group.topics;
921
911
  break;
922
912
  }
923
913
  }
924
- let coordinator = coordinatorsMap.get(node);
925
- if (!coordinator) {
926
- coordinator = [];
927
- coordinatorsMap.set(node, coordinator);
914
+ let offsetFetchRequestGroups = coordinatorsMap.get(coordinator.nodeId);
915
+ if (!offsetFetchRequestGroups) {
916
+ offsetFetchRequestGroups = { coordinator, groupRequests: [] };
917
+ coordinatorsMap.set(coordinator.nodeId, offsetFetchRequestGroups);
928
918
  }
929
- coordinator.push(groupRequest);
919
+ offsetFetchRequestGroups.groupRequests.push(groupRequest);
930
920
  }
931
- runConcurrentCallbacks('Listing consumer group offsets failed.', coordinatorsMap, ([node, groups], concurrentCallback) => {
932
- this[kGetConnection](metadata.brokers.get(node), (error, connection) => {
933
- if (error) {
934
- concurrentCallback(error);
935
- return;
936
- }
937
- this[kPerformWithRetry]('offsetFetch', retryCallback => {
921
+ runConcurrentCallbacks('Listing consumer group offsets failed.', coordinatorsMap.values(), ({ coordinator, groupRequests }, concurrentCallback) => {
922
+ this[kPerformWithRetry]('offsetFetch', retryCallback => {
923
+ this[kGetConnection](coordinator, (error, connection) => {
924
+ if (error) {
925
+ retryCallback(error);
926
+ return;
927
+ }
938
928
  this[kGetApi]('OffsetFetch', (error, api) => {
939
929
  if (error) {
940
930
  retryCallback(error);
941
931
  return;
942
932
  }
943
933
  /* c8 ignore next - Hard to test */
944
- api(connection, groups, options.requireStable ?? false, retryCallback);
934
+ api(connection, groupRequests, options.requireStable ?? false, retryCallback);
945
935
  });
946
- }, concurrentCallback, 0);
947
- });
936
+ });
937
+ }, concurrentCallback, 0);
948
938
  }, (error, responses) => {
949
939
  if (error) {
950
- callback(error);
940
+ retryCallback(error);
951
941
  return;
952
942
  }
953
- callback(null, responses.flatMap(r => r.groups.map(group => ({
943
+ retryCallback(null,
944
+ // responses must be defined as error was undefined
945
+ responses.flatMap(r => r.groups.map(group => ({
954
946
  groupId: group.groupId,
955
947
  topics: group.topics.map(topic => ({
956
948
  name: topic.name,
@@ -964,125 +956,101 @@ export class Admin extends Base {
964
956
  }))));
965
957
  });
966
958
  });
967
- });
959
+ }, callback, 0);
968
960
  }
969
961
  #alterConsumerGroupOffsets(options, callback) {
970
- this[kMetadata]({ topics: [] }, (error, metadata) => {
971
- if (error) {
972
- callback(error, undefined);
973
- return;
974
- }
962
+ this[kPerformWithRetry]('alterConsumerGroupOffsets', retryCallback => {
975
963
  this.#findCoordinator({ keyType: FindCoordinatorKeyTypes.GROUP, keys: [options.groupId] }, (error, coordinators) => {
976
964
  if (error) {
977
- callback(new MultipleErrors('Altering consumer group offsets failed.', [error]));
965
+ // FindCoordinator already retried some times so no retry again
966
+ callback(new MultipleErrors('Altering consumer group offsets failed.', error instanceof MultipleErrors ? error.errors : [error]));
978
967
  return;
979
968
  }
980
969
  const coordinator = coordinators.find(c => c.key === options.groupId);
981
970
  /* c8 ignore next 9 - Hard to test */
982
971
  if (!coordinator) {
983
- callback(new MultipleErrors('Altering consumer group offsets failed.', [
984
- new Error(`No coordinator found for group ${options.groupId}`)
985
- ]));
986
- return;
987
- }
988
- const broker = metadata.brokers.get(coordinator.nodeId);
989
- /* c8 ignore next 9 - Hard to test */
990
- if (!broker) {
991
- callback(new MultipleErrors('Altering consumer group offsets failed.', [
992
- new Error(`Broker ${coordinator.nodeId} not found`)
993
- ]));
972
+ retryCallback(new Error('Group coordinator not found'));
994
973
  return;
995
974
  }
996
- this[kGetConnection](broker, (error, connection) => {
975
+ this[kGetConnection](coordinator, (error, connection) => {
997
976
  if (error) {
998
- callback(new MultipleErrors('Altering consumer group offsets failed.', [error]));
977
+ retryCallback(error);
999
978
  return;
1000
979
  }
1001
- this[kPerformWithRetry]('alterConsumerGroupOffsets', retryCallback => {
1002
- this[kGetApi]('OffsetCommit', (error, api) => {
1003
- if (error) {
1004
- retryCallback(error);
1005
- return;
1006
- }
1007
- const topics = [];
1008
- for (const topic of options.topics) {
1009
- const partitions = topic.partitionOffsets.map(p => ({
1010
- partitionIndex: p.partition,
1011
- committedOffset: p.offset,
1012
- committedLeaderEpoch: -1,
1013
- committedMetadata: null
1014
- }));
1015
- topics.push({ name: topic.name, partitions });
1016
- }
1017
- api(connection, options.groupId, -1, '', null, topics, retryCallback);
1018
- });
1019
- }, error => {
980
+ this[kGetApi]('OffsetCommit', (error, api) => {
1020
981
  if (error) {
1021
- callback(new MultipleErrors('Altering consumer group offsets failed.', [error]));
982
+ retryCallback(error);
1022
983
  return;
1023
984
  }
1024
- callback(null);
1025
- }, 0);
985
+ const topics = [];
986
+ for (const topic of options.topics) {
987
+ const partitions = topic.partitionOffsets.map(p => ({
988
+ partitionIndex: p.partition,
989
+ committedOffset: p.offset,
990
+ committedLeaderEpoch: -1,
991
+ committedMetadata: null
992
+ }));
993
+ topics.push({ name: topic.name, partitions });
994
+ }
995
+ api(connection, options.groupId, -1, '', null, topics, retryCallback);
996
+ });
1026
997
  });
1027
998
  });
1028
- });
1029
- }
1030
- #deleteConsumerGroupOffsets(options, callback) {
1031
- this[kMetadata]({ topics: [] }, (error, metadata) => {
999
+ }, error => {
1032
1000
  if (error) {
1033
- callback(error);
1001
+ callback(new MultipleErrors('Altering consumer group offsets failed.', [error]));
1034
1002
  return;
1035
1003
  }
1004
+ callback(null);
1005
+ }, 0);
1006
+ }
1007
+ #deleteConsumerGroupOffsets(options, callback) {
1008
+ this[kPerformWithRetry]('deleteConsumerGroupOffsets', retryCallback => {
1036
1009
  this.#findCoordinator({ keyType: FindCoordinatorKeyTypes.GROUP, keys: [options.groupId] }, (error, coordinators) => {
1037
1010
  if (error) {
1038
- callback(new MultipleErrors('Deleting consumer group offsets failed.', [error]));
1011
+ // FindCoordinator already retried some times so no retry again
1012
+ callback(new MultipleErrors('Deleting consumer group offsets failed.', error instanceof MultipleErrors ? error.errors : [error]));
1039
1013
  return;
1040
1014
  }
1041
1015
  const coordinator = coordinators.find(c => c.key === options.groupId);
1042
1016
  /* c8 ignore next 9 - Hard to test */
1043
1017
  if (!coordinator) {
1044
- callback(new MultipleErrors('Deleting consumer group offsets failed.', [
1045
- new Error(`No coordinator found for group ${options.groupId}`)
1046
- ]));
1047
- return;
1048
- }
1049
- const broker = metadata.brokers.get(coordinator.nodeId);
1050
- /* c8 ignore next 9 - Hard to test */
1051
- if (!broker) {
1052
- callback(new MultipleErrors('Deleting consumer group offsets failed.', [
1053
- new Error(`Broker ${coordinator.nodeId} not found`)
1054
- ]));
1018
+ retryCallback(new Error('Group coordinator not found'));
1055
1019
  return;
1056
1020
  }
1057
- this[kGetConnection](broker, (error, connection) => {
1021
+ this[kGetConnection](coordinator, (error, connection) => {
1058
1022
  if (error) {
1059
- callback(new MultipleErrors('Deleting consumer group offsets failed.', [error]));
1023
+ retryCallback(error);
1060
1024
  return;
1061
1025
  }
1062
- this[kPerformWithRetry]('deleteOffset', retryCallback => {
1063
- this[kGetApi]('OffsetDelete', (error, api) => {
1026
+ this[kGetApi]('OffsetDelete', (error, api) => {
1027
+ if (error) {
1028
+ retryCallback(error);
1029
+ return;
1030
+ }
1031
+ api(connection, options.groupId, options.topics.map(t => ({
1032
+ name: t.name,
1033
+ partitions: t.partitionIndexes.map(p => ({ partitionIndex: p }))
1034
+ })), (error, response) => {
1064
1035
  if (error) {
1065
1036
  retryCallback(error);
1066
1037
  return;
1067
1038
  }
1068
- api(connection, options.groupId, options.topics.map(t => ({
1069
- name: t.name,
1070
- partitions: t.partitionIndexes.map(p => ({ partitionIndex: p }))
1071
- })), retryCallback);
1039
+ retryCallback(null, response.topics.map(topic => ({
1040
+ name: topic.name,
1041
+ partitionIndexes: topic.partitions.map(p => p.partitionIndex)
1042
+ })));
1072
1043
  });
1073
- }, (error, response) => {
1074
- if (error) {
1075
- callback(new MultipleErrors('Deleting consumer group offsets failed.', [error]));
1076
- return;
1077
- }
1078
- callback(null, response.topics.map(topic => ({
1079
- name: topic.name,
1080
- partitionIndexes: topic.partitions.map(p => p.partitionIndex)
1081
- })));
1082
- }, 0);
1044
+ });
1083
1045
  });
1084
1046
  });
1085
- });
1047
+ }, (error, results) => {
1048
+ if (error) {
1049
+ callback(new MultipleErrors('Deleting consumer group offsets failed.', [error]));
1050
+ return;
1051
+ }
1052
+ callback(null, results);
1053
+ }, 0);
1086
1054
  }
1087
1055
  #getConfigRequestsDistributedToBrokers(resources) {
1088
1056
  const brokerConfigResourceMap = new Map();
@@ -1108,7 +1076,7 @@ export class Admin extends Base {
1108
1076
  this[kGetBootstrapConnection](callback);
1109
1077
  return;
1110
1078
  }
1111
- this[kMetadata]({ topics: [] }, (error, metadata) => {
1079
+ this[kMetadata]({ topics: [], forceUpdate: true }, (error, metadata) => {
1112
1080
  /* c8 ignore next 4 - Hard to test */
1113
1081
  if (error) {
1114
1082
  callback(error);
@@ -1125,13 +1093,7 @@ export class Admin extends Base {
1125
1093
  }
1126
1094
  #describeConfigs(options, callback) {
1127
1095
  runConcurrentCallbacks('Describing configs failed.', this.#getConfigRequestsDistributedToBrokers(options.resources), ([brokerId, resources], concurrentCallback) => {
1128
- this.#describeConfigsOnBroker({ ...options, resources }, brokerId, (error, response) => {
1129
- if (error) {
1130
- concurrentCallback(error);
1131
- return;
1132
- }
1133
- concurrentCallback(null, response);
1134
- });
1096
+ this.#describeConfigsOnBroker({ ...options, resources }, brokerId, concurrentCallback);
1135
1097
  }, (error, results) => {
1136
1098
  callback(error, results?.flat());
1137
1099
  });
@@ -1307,154 +1269,166 @@ export class Admin extends Base {
1307
1269
  }, 0);
1308
1270
  }
1309
1271
  #deleteRecords(options, callback) {
1310
- this[kMetadata]({ topics: options.topics.map(topic => topic.name) }, (error, metadata) => {
1311
- if (error) {
1312
- callback(error);
1313
- return;
1314
- }
1315
- const requestsByLeader = new Map();
1316
- for (const topic of options.topics) {
1317
- for (const partition of topic.partitions) {
1318
- const { leader } = metadata.topics.get(topic.name).partitions[partition.partition];
1319
- let leaderRequests = requestsByLeader.get(leader);
1320
- if (!leaderRequests) {
1321
- leaderRequests = new Map();
1322
- requestsByLeader.set(leader, leaderRequests);
1323
- }
1324
- let topicRequest = leaderRequests.get(topic.name);
1325
- if (!topicRequest) {
1326
- topicRequest = { name: topic.name, partitions: [] };
1327
- leaderRequests.set(topic.name, topicRequest);
1328
- }
1329
- topicRequest.partitions.push({
1330
- partitionIndex: partition.partition,
1331
- offset: partition.offset
1332
- });
1272
+ this[kPerformWithRetry]('deleteRecords', retryCallback => {
1273
+ this[kMetadata]({ topics: options.topics.map(topic => topic.name), forceUpdate: true }, (error, metadata) => {
1274
+ if (error) {
1275
+ // Metadata request already retried some times so no retry again
1276
+ callback(error);
1277
+ return;
1333
1278
  }
1334
- }
1335
- const requests = new Map();
1336
- for (const [leader, topics] of requestsByLeader) {
1337
- requests.set(leader, Array.from(topics.values()));
1338
- }
1339
- runConcurrentCallbacks('Deleting records failed.', requests, ([leader, requests], concurrentCallback) => {
1340
- this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
1341
- if (error) {
1342
- concurrentCallback(error);
1343
- return;
1279
+ const requests = new Map();
1280
+ for (const topic of options.topics) {
1281
+ for (const partition of topic.partitions) {
1282
+ // metadata!.topics.get(topic.name) must be defined as the metadata request was successful
1283
+ const topicData = metadata.topics.get(topic.name);
1284
+ const targetPartitionData = topicData.partitions[partition.partition];
1285
+ /* c8 ignore next 4 - Hard to test */
1286
+ if (!targetPartitionData) {
1287
+ retryCallback(new UserError('Unknown partition.'));
1288
+ return;
1289
+ }
1290
+ const broker = metadata.brokers.get(targetPartitionData.leader);
1291
+ let leaderRequests = requests.get(broker);
1292
+ if (!leaderRequests) {
1293
+ leaderRequests = [];
1294
+ requests.set(broker, leaderRequests);
1295
+ }
1296
+ let topicRequest = leaderRequests.find(tr => tr.name === topic.name);
1297
+ if (!topicRequest) {
1298
+ topicRequest = { name: topic.name, partitions: [] };
1299
+ leaderRequests.push(topicRequest);
1300
+ }
1301
+ topicRequest.partitions.push({
1302
+ partitionIndex: partition.partition,
1303
+ offset: partition.offset
1304
+ });
1344
1305
  }
1345
- this[kPerformWithRetry]('deleteRecords', retryCallback => {
1346
- this[kGetApi]('DeleteRecords', (error, api) => {
1306
+ }
1307
+ runConcurrentCallbacks('Deleting records failed.', requests, ([leader, requests], concurrentCallback) => {
1308
+ this[kPerformWithRetry]('deleteRecordsOnLeader', retryCallback => {
1309
+ this[kGetConnection](leader, (error, connection) => {
1347
1310
  if (error) {
1348
1311
  retryCallback(error);
1349
1312
  return;
1350
1313
  }
1351
- api(connection, Array.from(requests.values()), this[kOptions].timeout, retryCallback);
1314
+ this[kGetApi]('DeleteRecords', (error, api) => {
1315
+ if (error) {
1316
+ retryCallback(error);
1317
+ return;
1318
+ }
1319
+ api(connection, requests, this[kOptions].timeout, retryCallback);
1320
+ });
1352
1321
  });
1353
1322
  }, concurrentCallback, 0);
1323
+ }, (error, responses) => {
1324
+ if (error) {
1325
+ // Do not retry failed deletion requests
1326
+ callback(error);
1327
+ return;
1328
+ }
1329
+ const deletedRecordsByTopic = new Map();
1330
+ for (const response of responses) {
1331
+ for (const topic of response.topics) {
1332
+ let topicDeletedRecords = deletedRecordsByTopic.get(topic.name);
1333
+ if (!topicDeletedRecords) {
1334
+ topicDeletedRecords = { name: topic.name, partitions: [] };
1335
+ deletedRecordsByTopic.set(topic.name, topicDeletedRecords);
1336
+ }
1337
+ for (const partition of topic.partitions) {
1338
+ topicDeletedRecords.partitions.push({
1339
+ partition: partition.partitionIndex,
1340
+ lowWatermark: partition.lowWatermark
1341
+ });
1342
+ }
1343
+ }
1344
+ }
1345
+ retryCallback(null, Array.from(deletedRecordsByTopic.values()));
1354
1346
  });
1355
- }, (error, responses) => {
1347
+ });
1348
+ }, callback, 0);
1349
+ }
1350
+ #listOffsets(options, callback) {
1351
+ this[kPerformWithRetry]('listOffsets', retryCallback => {
1352
+ this[kMetadata]({ topics: options.topics.map(topic => topic.name), forceUpdate: true }, (error, metadata) => {
1356
1353
  if (error) {
1354
+ // Metadata request already retried some times so no retry again
1357
1355
  callback(error);
1358
1356
  return;
1359
1357
  }
1360
- const deletedRecordsByTopic = new Map();
1361
- for (const response of responses) {
1362
- for (const topic of response.topics) {
1363
- let topicDeletedRecords = deletedRecordsByTopic.get(topic.name);
1364
- if (!topicDeletedRecords) {
1365
- topicDeletedRecords = { name: topic.name, partitions: [] };
1366
- deletedRecordsByTopic.set(topic.name, topicDeletedRecords);
1358
+ // metadata must be defined at this point
1359
+ const { topics, brokers } = metadata;
1360
+ const requests = new Map();
1361
+ for (const topic of options.topics) {
1362
+ for (const partition of topic.partitions) {
1363
+ // topics.get(topic.name) must be defined as the metadata request was successful
1364
+ const topicData = topics.get(topic.name);
1365
+ const targetPartitionData = topicData.partitions[partition.partitionIndex];
1366
+ /* c8 ignore next 4 - Hard to test */
1367
+ if (!targetPartitionData) {
1368
+ retryCallback(new UserError('Unknown partition.'));
1369
+ return;
1367
1370
  }
1368
- for (const partition of topic.partitions) {
1369
- topicDeletedRecords.partitions.push({
1370
- partition: partition.partitionIndex,
1371
- lowWatermark: partition.lowWatermark
1372
- });
1371
+ const broker = brokers.get(targetPartitionData.leader);
1372
+ let leaderRequests = requests.get(broker);
1373
+ if (!leaderRequests) {
1374
+ leaderRequests = [];
1375
+ requests.set(broker, leaderRequests);
1373
1376
  }
1377
+ let topicRequest = leaderRequests.find(t => t.name === topic.name);
1378
+ if (!topicRequest) {
1379
+ topicRequest = { name: topic.name, partitions: [] };
1380
+ leaderRequests.push(topicRequest);
1381
+ }
1382
+ topicRequest.partitions.push({
1383
+ partitionIndex: partition.partitionIndex,
1384
+ currentLeaderEpoch: targetPartitionData.leaderEpoch,
1385
+ /* c8 ignore next - Hard to test */
1386
+ timestamp: partition.timestamp ?? -1n
1387
+ });
1374
1388
  }
1375
1389
  }
1376
- callback(null, Array.from(deletedRecordsByTopic.values()));
1377
- });
1378
- });
1379
- }
1380
- #listOffsets(options, callback) {
1381
- this[kMetadata]({ topics: options.topics.map(topic => topic.name) }, (error, metadata) => {
1382
- if (error) {
1383
- callback(error);
1384
- return;
1385
- }
1386
- // metadata must be defined at this point
1387
- const { topics, brokers } = metadata;
1388
- const requests = new Map();
1389
- for (const topic of options.topics) {
1390
- for (const partition of topic.partitions) {
1391
- // topics.get(topic.name) must be defined as the metadata request was successful
1392
- const topicData = topics.get(topic.name);
1393
- const targetPartitionData = topicData.partitions[partition.partitionIndex];
1394
- /* c8 ignore next 4 - Hard to test */
1395
- if (!targetPartitionData) {
1396
- callback(new UserError(`Unknown partition ${partition.partitionIndex} for topic ${topic.name}.`));
1397
- return;
1398
- }
1399
- let leaderRequests = requests.get(targetPartitionData.leader);
1400
- if (!leaderRequests) {
1401
- leaderRequests = [];
1402
- requests.set(targetPartitionData.leader, leaderRequests);
1403
- }
1404
- let topicRequest = leaderRequests.find(t => t.name === topic.name);
1405
- if (!topicRequest) {
1406
- topicRequest = { name: topic.name, partitions: [] };
1407
- leaderRequests.push(topicRequest);
1408
- }
1409
- topicRequest.partitions.push({
1410
- partitionIndex: partition.partitionIndex,
1411
- currentLeaderEpoch: targetPartitionData.leaderEpoch,
1412
- /* c8 ignore next - Hard to test */
1413
- timestamp: partition.timestamp ?? -1n
1414
- });
1415
- }
1416
- }
1417
- runConcurrentCallbacks('Listing offsets failed.', requests, ([leader, requests], concurrentCallback) => {
1418
- this[kGetConnection](brokers.get(leader), (error, connection) => {
1419
- if (error) {
1420
- concurrentCallback(error);
1421
- return;
1422
- }
1390
+ runConcurrentCallbacks('Listing offsets failed.', requests, ([leader, requests], concurrentCallback) => {
1423
1391
  this[kPerformWithRetry]('listOffsets', retryCallback => {
1424
- this[kGetApi]('ListOffsets', (error, api) => {
1392
+ this[kGetConnection](leader, (error, connection) => {
1425
1393
  if (error) {
1426
1394
  retryCallback(error);
1427
1395
  return;
1428
1396
  }
1429
- api(connection, -1, options.isolationLevel ?? FetchIsolationLevels.READ_UNCOMMITTED, Array.from(requests.values()), retryCallback);
1397
+ this[kGetApi]('ListOffsets', (error, api) => {
1398
+ if (error) {
1399
+ retryCallback(error);
1400
+ return;
1401
+ }
1402
+ api(connection, -1, options.isolationLevel ?? FetchIsolationLevels.READ_UNCOMMITTED, requests, retryCallback);
1403
+ });
1430
1404
  });
1431
1405
  }, concurrentCallback, 0);
1432
- });
1433
- }, (error, responses) => {
1434
- if (error) {
1435
- callback(error);
1436
- return;
1437
- }
1438
- const ret = [];
1439
- for (const response of responses) {
1440
- for (const topic of response.topics) {
1441
- let topicOffsets = ret.find(t => t.name === topic.name);
1442
- if (!topicOffsets) {
1443
- topicOffsets = { name: topic.name, partitions: [] };
1444
- ret.push(topicOffsets);
1445
- }
1446
- for (const partition of topic.partitions) {
1447
- topicOffsets.partitions.push({
1448
- offset: partition.offset,
1449
- timestamp: partition.timestamp,
1450
- partitionIndex: partition.partitionIndex,
1451
- leaderEpoch: partition.leaderEpoch
1452
- });
1406
+ }, (error, responses) => {
1407
+ if (error) {
1408
+ retryCallback(error);
1409
+ return;
1410
+ }
1411
+ const ret = [];
1412
+ for (const response of responses) {
1413
+ for (const topic of response.topics) {
1414
+ let topicOffsets = ret.find(t => t.name === topic.name);
1415
+ if (!topicOffsets) {
1416
+ topicOffsets = { name: topic.name, partitions: [] };
1417
+ ret.push(topicOffsets);
1418
+ }
1419
+ for (const partition of topic.partitions) {
1420
+ topicOffsets.partitions.push({
1421
+ offset: partition.offset,
1422
+ timestamp: partition.timestamp,
1423
+ partitionIndex: partition.partitionIndex,
1424
+ leaderEpoch: partition.leaderEpoch
1425
+ });
1426
+ }
1453
1427
  }
1454
1428
  }
1455
- }
1456
- callback(null, ret);
1429
+ retryCallback(null, ret);
1430
+ });
1457
1431
  });
1458
- });
1432
+ }, callback, 0);
1459
1433
  }
1460
1434
  }