mirror of https://github.com/apache/kafka.git
KAFKA-17593; [5/N] Include resolved regular expressions into target assignment computation (#17750)
This patch does a few things: * Refactors the `TargetAssignmentBuilder` to use inheritance to differentiate Consumer and Share groups. * Introduces `UnionSet` to lazily aggregate the subscriptions for a given member. * Wires the resolved regular expressions in the `GroupMetadataManager`. At the moment, they are only used when the target assignment is computed. Reviewers: Sean Quah <squah@confluent.io>, Jeff Kim <jeff.kim@confluent.io>, Lianet Magrans <lmagrans@confluent.io>
This commit is contained in:
parent
05bca43c61
commit
a802865aad
|
@ -2606,8 +2606,8 @@ public class GroupMetadataManager {
|
||||||
updatedMember
|
updatedMember
|
||||||
).orElse(defaultConsumerGroupAssignor.name());
|
).orElse(defaultConsumerGroupAssignor.name());
|
||||||
try {
|
try {
|
||||||
TargetAssignmentBuilder<ConsumerGroupMember> assignmentResultBuilder =
|
TargetAssignmentBuilder.ConsumerTargetAssignmentBuilder assignmentResultBuilder =
|
||||||
new TargetAssignmentBuilder<ConsumerGroupMember>(group.groupId(), groupEpoch, consumerGroupAssignors.get(preferredServerAssignor))
|
new TargetAssignmentBuilder.ConsumerTargetAssignmentBuilder(group.groupId(), groupEpoch, consumerGroupAssignors.get(preferredServerAssignor))
|
||||||
.withMembers(group.members())
|
.withMembers(group.members())
|
||||||
.withStaticMembers(group.staticMembers())
|
.withStaticMembers(group.staticMembers())
|
||||||
.withSubscriptionMetadata(subscriptionMetadata)
|
.withSubscriptionMetadata(subscriptionMetadata)
|
||||||
|
@ -2615,6 +2615,7 @@ public class GroupMetadataManager {
|
||||||
.withTargetAssignment(group.targetAssignment())
|
.withTargetAssignment(group.targetAssignment())
|
||||||
.withInvertedTargetAssignment(group.invertedTargetAssignment())
|
.withInvertedTargetAssignment(group.invertedTargetAssignment())
|
||||||
.withTopicsImage(metadataImage.topics())
|
.withTopicsImage(metadataImage.topics())
|
||||||
|
.withResolvedRegularExpressions(group.resolvedRegularExpressions())
|
||||||
.addOrUpdateMember(updatedMember.memberId(), updatedMember);
|
.addOrUpdateMember(updatedMember.memberId(), updatedMember);
|
||||||
|
|
||||||
// If the instance id was associated to a different member, it means that the
|
// If the instance id was associated to a different member, it means that the
|
||||||
|
@ -2673,16 +2674,14 @@ public class GroupMetadataManager {
|
||||||
List<CoordinatorRecord> records
|
List<CoordinatorRecord> records
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
TargetAssignmentBuilder<ShareGroupMember> assignmentResultBuilder =
|
TargetAssignmentBuilder.ShareTargetAssignmentBuilder assignmentResultBuilder =
|
||||||
new TargetAssignmentBuilder<ShareGroupMember>(group.groupId(), groupEpoch, shareGroupAssignor)
|
new TargetAssignmentBuilder.ShareTargetAssignmentBuilder(group.groupId(), groupEpoch, shareGroupAssignor)
|
||||||
.withMembers(group.members())
|
.withMembers(group.members())
|
||||||
.withSubscriptionMetadata(subscriptionMetadata)
|
.withSubscriptionMetadata(subscriptionMetadata)
|
||||||
.withSubscriptionType(subscriptionType)
|
.withSubscriptionType(subscriptionType)
|
||||||
.withTargetAssignment(group.targetAssignment())
|
.withTargetAssignment(group.targetAssignment())
|
||||||
.withInvertedTargetAssignment(group.invertedTargetAssignment())
|
.withInvertedTargetAssignment(group.invertedTargetAssignment())
|
||||||
.withTopicsImage(metadataImage.topics())
|
.withTopicsImage(metadataImage.topics())
|
||||||
.withTargetAssignmentRecordBuilder(GroupCoordinatorRecordHelpers::newShareGroupTargetAssignmentRecord)
|
|
||||||
.withTargetAssignmentEpochRecordBuilder(GroupCoordinatorRecordHelpers::newShareGroupTargetAssignmentEpochRecord)
|
|
||||||
.addOrUpdateMember(updatedMember.memberId(), updatedMember);
|
.addOrUpdateMember(updatedMember.memberId(), updatedMember);
|
||||||
|
|
||||||
long startTimeMs = time.milliseconds();
|
long startTimeMs = time.milliseconds();
|
||||||
|
|
|
@ -24,6 +24,9 @@ import org.apache.kafka.coordinator.group.api.assignor.MemberAssignment;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignor;
|
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignor;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignorException;
|
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignorException;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.SubscriptionType;
|
import org.apache.kafka.coordinator.group.api.assignor.SubscriptionType;
|
||||||
|
import org.apache.kafka.coordinator.group.modern.consumer.ConsumerGroupMember;
|
||||||
|
import org.apache.kafka.coordinator.group.modern.consumer.ResolvedRegularExpression;
|
||||||
|
import org.apache.kafka.coordinator.group.modern.share.ShareGroupMember;
|
||||||
import org.apache.kafka.image.TopicsImage;
|
import org.apache.kafka.image.TopicsImage;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -47,7 +50,7 @@ import java.util.Set;
|
||||||
* is deleted as part of the member deletion process. In other words, this class
|
* is deleted as part of the member deletion process. In other words, this class
|
||||||
* does not yield a tombstone for removed members.
|
* does not yield a tombstone for removed members.
|
||||||
*/
|
*/
|
||||||
public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
public abstract class TargetAssignmentBuilder<T extends ModernGroupMember, U extends TargetAssignmentBuilder<T, U>> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The assignment result returned by {{@link TargetAssignmentBuilder#build()}}.
|
* The assignment result returned by {{@link TargetAssignmentBuilder#build()}}.
|
||||||
|
@ -89,6 +92,144 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ConsumerTargetAssignmentBuilder extends TargetAssignmentBuilder<ConsumerGroupMember, ConsumerTargetAssignmentBuilder> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resolved regular expressions.
|
||||||
|
*/
|
||||||
|
private Map<String, ResolvedRegularExpression> resolvedRegularExpressions = Collections.emptyMap();
|
||||||
|
|
||||||
|
public ConsumerTargetAssignmentBuilder(
|
||||||
|
String groupId,
|
||||||
|
int groupEpoch,
|
||||||
|
PartitionAssignor assignor
|
||||||
|
) {
|
||||||
|
super(groupId, groupEpoch, assignor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all the existing resolved regular expressions.
|
||||||
|
*
|
||||||
|
* @param resolvedRegularExpressions The resolved regular expressions.
|
||||||
|
* @return This object.
|
||||||
|
*/
|
||||||
|
public ConsumerTargetAssignmentBuilder withResolvedRegularExpressions(
|
||||||
|
Map<String, ResolvedRegularExpression> resolvedRegularExpressions
|
||||||
|
) {
|
||||||
|
this.resolvedRegularExpressions = resolvedRegularExpressions;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ConsumerTargetAssignmentBuilder self() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CoordinatorRecord newTargetAssignmentRecord(
|
||||||
|
String groupId,
|
||||||
|
String memberId,
|
||||||
|
Map<Uuid, Set<Integer>> partitions
|
||||||
|
) {
|
||||||
|
return GroupCoordinatorRecordHelpers.newConsumerGroupTargetAssignmentRecord(
|
||||||
|
groupId,
|
||||||
|
memberId,
|
||||||
|
partitions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CoordinatorRecord newTargetAssignmentEpochRecord(String groupId, int assignmentEpoch) {
|
||||||
|
return GroupCoordinatorRecordHelpers.newConsumerGroupTargetAssignmentEpochRecord(
|
||||||
|
groupId,
|
||||||
|
assignmentEpoch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MemberSubscriptionAndAssignmentImpl newMemberSubscriptionAndAssignment(
|
||||||
|
ConsumerGroupMember member,
|
||||||
|
Assignment memberAssignment,
|
||||||
|
TopicIds.TopicResolver topicResolver
|
||||||
|
) {
|
||||||
|
Set<String> subscriptions = member.subscribedTopicNames();
|
||||||
|
|
||||||
|
// Check whether the member is also subscribed to a regular expression. If it is,
|
||||||
|
// create the union of the two subscriptions.
|
||||||
|
String subscribedTopicRegex = member.subscribedTopicRegex();
|
||||||
|
if (subscribedTopicRegex != null && !subscribedTopicRegex.isEmpty()) {
|
||||||
|
ResolvedRegularExpression resolvedRegularExpression = resolvedRegularExpressions.get(subscribedTopicRegex);
|
||||||
|
if (resolvedRegularExpression != null) {
|
||||||
|
if (subscriptions.isEmpty()) {
|
||||||
|
subscriptions = resolvedRegularExpression.topics;
|
||||||
|
} else if (!resolvedRegularExpression.topics.isEmpty()) {
|
||||||
|
// We only use a UnionSet when the member uses both type of subscriptions. The
|
||||||
|
// protocol allows it. However, the Apache Kafka Consumer does not support it.
|
||||||
|
// Other clients such as librdkafka may support it.
|
||||||
|
subscriptions = new UnionSet<>(subscriptions, resolvedRegularExpression.topics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MemberSubscriptionAndAssignmentImpl(
|
||||||
|
Optional.ofNullable(member.rackId()),
|
||||||
|
Optional.ofNullable(member.instanceId()),
|
||||||
|
new TopicIds(subscriptions, topicResolver),
|
||||||
|
memberAssignment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ShareTargetAssignmentBuilder extends TargetAssignmentBuilder<ShareGroupMember, ShareTargetAssignmentBuilder> {
|
||||||
|
public ShareTargetAssignmentBuilder(
|
||||||
|
String groupId,
|
||||||
|
int groupEpoch,
|
||||||
|
PartitionAssignor assignor
|
||||||
|
) {
|
||||||
|
super(groupId, groupEpoch, assignor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ShareTargetAssignmentBuilder self() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CoordinatorRecord newTargetAssignmentRecord(
|
||||||
|
String groupId,
|
||||||
|
String memberId,
|
||||||
|
Map<Uuid, Set<Integer>> partitions
|
||||||
|
) {
|
||||||
|
return GroupCoordinatorRecordHelpers.newShareGroupTargetAssignmentRecord(
|
||||||
|
groupId,
|
||||||
|
memberId,
|
||||||
|
partitions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CoordinatorRecord newTargetAssignmentEpochRecord(String groupId, int assignmentEpoch) {
|
||||||
|
return GroupCoordinatorRecordHelpers.newShareGroupTargetAssignmentEpochRecord(
|
||||||
|
groupId,
|
||||||
|
assignmentEpoch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MemberSubscriptionAndAssignmentImpl newMemberSubscriptionAndAssignment(
|
||||||
|
ShareGroupMember member,
|
||||||
|
Assignment memberAssignment,
|
||||||
|
TopicIds.TopicResolver topicResolver
|
||||||
|
) {
|
||||||
|
return new MemberSubscriptionAndAssignmentImpl(
|
||||||
|
Optional.ofNullable(member.rackId()),
|
||||||
|
Optional.ofNullable(member.instanceId()),
|
||||||
|
new TopicIds(member.subscribedTopicNames(), topicResolver),
|
||||||
|
memberAssignment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The group id.
|
* The group id.
|
||||||
*/
|
*/
|
||||||
|
@ -146,27 +287,6 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
*/
|
*/
|
||||||
private Map<String, String> staticMembers = new HashMap<>();
|
private Map<String, String> staticMembers = new HashMap<>();
|
||||||
|
|
||||||
public interface TargetAssignmentRecordBuilder {
|
|
||||||
CoordinatorRecord build(
|
|
||||||
final String groupId,
|
|
||||||
final String memberId,
|
|
||||||
final Map<Uuid, Set<Integer>> partitions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface TargetAssignmentEpochRecordBuilder {
|
|
||||||
CoordinatorRecord build(
|
|
||||||
final String groupId,
|
|
||||||
final int assignmentEpoch
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TargetAssignmentRecordBuilder targetAssignmentRecordBuilder =
|
|
||||||
GroupCoordinatorRecordHelpers::newConsumerGroupTargetAssignmentRecord;
|
|
||||||
|
|
||||||
private TargetAssignmentEpochRecordBuilder targetAssignmentEpochRecordBuilder =
|
|
||||||
GroupCoordinatorRecordHelpers::newConsumerGroupTargetAssignmentEpochRecord;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the object.
|
* Constructs the object.
|
||||||
*
|
*
|
||||||
|
@ -190,11 +310,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param members The existing members in the consumer group.
|
* @param members The existing members in the consumer group.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withMembers(
|
public U withMembers(
|
||||||
Map<String, T> members
|
Map<String, T> members
|
||||||
) {
|
) {
|
||||||
this.members = members;
|
this.members = members;
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -203,11 +323,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param staticMembers The existing static members in the consumer group.
|
* @param staticMembers The existing static members in the consumer group.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withStaticMembers(
|
public U withStaticMembers(
|
||||||
Map<String, String> staticMembers
|
Map<String, String> staticMembers
|
||||||
) {
|
) {
|
||||||
this.staticMembers = staticMembers;
|
this.staticMembers = staticMembers;
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,11 +336,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param subscriptionMetadata The subscription metadata.
|
* @param subscriptionMetadata The subscription metadata.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withSubscriptionMetadata(
|
public U withSubscriptionMetadata(
|
||||||
Map<String, TopicMetadata> subscriptionMetadata
|
Map<String, TopicMetadata> subscriptionMetadata
|
||||||
) {
|
) {
|
||||||
this.subscriptionMetadata = subscriptionMetadata;
|
this.subscriptionMetadata = subscriptionMetadata;
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -229,11 +349,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param subscriptionType Subscription type of the group.
|
* @param subscriptionType Subscription type of the group.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withSubscriptionType(
|
public U withSubscriptionType(
|
||||||
SubscriptionType subscriptionType
|
SubscriptionType subscriptionType
|
||||||
) {
|
) {
|
||||||
this.subscriptionType = subscriptionType;
|
this.subscriptionType = subscriptionType;
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -242,11 +362,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param targetAssignment The existing target assignment.
|
* @param targetAssignment The existing target assignment.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withTargetAssignment(
|
public U withTargetAssignment(
|
||||||
Map<String, Assignment> targetAssignment
|
Map<String, Assignment> targetAssignment
|
||||||
) {
|
) {
|
||||||
this.targetAssignment = targetAssignment;
|
this.targetAssignment = targetAssignment;
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,11 +375,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param invertedTargetAssignment The reverse lookup map of the current target assignment.
|
* @param invertedTargetAssignment The reverse lookup map of the current target assignment.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withInvertedTargetAssignment(
|
public U withInvertedTargetAssignment(
|
||||||
Map<Uuid, Map<Integer, String>> invertedTargetAssignment
|
Map<Uuid, Map<Integer, String>> invertedTargetAssignment
|
||||||
) {
|
) {
|
||||||
this.invertedTargetAssignment = invertedTargetAssignment;
|
this.invertedTargetAssignment = invertedTargetAssignment;
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -268,25 +388,11 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param topicsImage The topics image.
|
* @param topicsImage The topics image.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> withTopicsImage(
|
public U withTopicsImage(
|
||||||
TopicsImage topicsImage
|
TopicsImage topicsImage
|
||||||
) {
|
) {
|
||||||
this.topicsImage = topicsImage;
|
this.topicsImage = topicsImage;
|
||||||
return this;
|
return self();
|
||||||
}
|
|
||||||
|
|
||||||
public TargetAssignmentBuilder<T> withTargetAssignmentRecordBuilder(
|
|
||||||
TargetAssignmentRecordBuilder targetAssignmentRecordBuilder
|
|
||||||
) {
|
|
||||||
this.targetAssignmentRecordBuilder = targetAssignmentRecordBuilder;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TargetAssignmentBuilder<T> withTargetAssignmentEpochRecordBuilder(
|
|
||||||
TargetAssignmentEpochRecordBuilder targetAssignmentEpochRecordBuilder
|
|
||||||
) {
|
|
||||||
this.targetAssignmentEpochRecordBuilder = targetAssignmentEpochRecordBuilder;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,12 +403,12 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param member The member to add or update.
|
* @param member The member to add or update.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> addOrUpdateMember(
|
public U addOrUpdateMember(
|
||||||
String memberId,
|
String memberId,
|
||||||
T member
|
T member
|
||||||
) {
|
) {
|
||||||
this.updatedMembers.put(memberId, member);
|
this.updatedMembers.put(memberId, member);
|
||||||
return this;
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -312,7 +418,7 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
* @param memberId The member id.
|
* @param memberId The member id.
|
||||||
* @return This object.
|
* @return This object.
|
||||||
*/
|
*/
|
||||||
public TargetAssignmentBuilder<T> removeMember(
|
public U removeMember(
|
||||||
String memberId
|
String memberId
|
||||||
) {
|
) {
|
||||||
return addOrUpdateMember(memberId, null);
|
return addOrUpdateMember(memberId, null);
|
||||||
|
@ -331,7 +437,7 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
|
|
||||||
// Prepare the member spec for all members.
|
// Prepare the member spec for all members.
|
||||||
members.forEach((memberId, member) ->
|
members.forEach((memberId, member) ->
|
||||||
memberSpecs.put(memberId, createMemberSubscriptionAndAssignment(
|
memberSpecs.put(memberId, newMemberSubscriptionAndAssignment(
|
||||||
member,
|
member,
|
||||||
targetAssignment.getOrDefault(memberId, Assignment.EMPTY),
|
targetAssignment.getOrDefault(memberId, Assignment.EMPTY),
|
||||||
topicResolver
|
topicResolver
|
||||||
|
@ -353,7 +459,7 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memberSpecs.put(memberId, createMemberSubscriptionAndAssignment(
|
memberSpecs.put(memberId, newMemberSubscriptionAndAssignment(
|
||||||
updatedMemberOrNull,
|
updatedMemberOrNull,
|
||||||
assignment,
|
assignment,
|
||||||
topicResolver
|
topicResolver
|
||||||
|
@ -391,7 +497,7 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
if (!newMemberAssignment.equals(oldMemberAssignment)) {
|
if (!newMemberAssignment.equals(oldMemberAssignment)) {
|
||||||
// If the member had no assignment or had a different assignment, we
|
// If the member had no assignment or had a different assignment, we
|
||||||
// create a record for the new assignment.
|
// create a record for the new assignment.
|
||||||
records.add(targetAssignmentRecordBuilder.build(
|
records.add(newTargetAssignmentRecord(
|
||||||
groupId,
|
groupId,
|
||||||
memberId,
|
memberId,
|
||||||
newMemberAssignment.partitions()
|
newMemberAssignment.partitions()
|
||||||
|
@ -400,11 +506,30 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bump the target assignment epoch.
|
// Bump the target assignment epoch.
|
||||||
records.add(targetAssignmentEpochRecordBuilder.build(groupId, groupEpoch));
|
records.add(newTargetAssignmentEpochRecord(groupId, groupEpoch));
|
||||||
|
|
||||||
return new TargetAssignmentResult(records, newGroupAssignment.members());
|
return new TargetAssignmentResult(records, newGroupAssignment.members());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract U self();
|
||||||
|
|
||||||
|
protected abstract CoordinatorRecord newTargetAssignmentRecord(
|
||||||
|
String groupId,
|
||||||
|
String memberId,
|
||||||
|
Map<Uuid, Set<Integer>> partitions
|
||||||
|
);
|
||||||
|
|
||||||
|
protected abstract CoordinatorRecord newTargetAssignmentEpochRecord(
|
||||||
|
String groupId,
|
||||||
|
int assignmentEpoch
|
||||||
|
);
|
||||||
|
|
||||||
|
protected abstract MemberSubscriptionAndAssignmentImpl newMemberSubscriptionAndAssignment(
|
||||||
|
T member,
|
||||||
|
Assignment memberAssignment,
|
||||||
|
TopicIds.TopicResolver topicResolver
|
||||||
|
);
|
||||||
|
|
||||||
private Assignment newMemberAssignment(
|
private Assignment newMemberAssignment(
|
||||||
GroupAssignment newGroupAssignment,
|
GroupAssignment newGroupAssignment,
|
||||||
String memberId
|
String memberId
|
||||||
|
@ -416,18 +541,4 @@ public class TargetAssignmentBuilder<T extends ModernGroupMember> {
|
||||||
return Assignment.EMPTY;
|
return Assignment.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private for testing
|
|
||||||
static <T extends ModernGroupMember> MemberSubscriptionAndAssignmentImpl createMemberSubscriptionAndAssignment(
|
|
||||||
T member,
|
|
||||||
Assignment memberAssignment,
|
|
||||||
TopicIds.TopicResolver topicResolver
|
|
||||||
) {
|
|
||||||
return new MemberSubscriptionAndAssignmentImpl(
|
|
||||||
Optional.ofNullable(member.rackId()),
|
|
||||||
Optional.ofNullable(member.instanceId()),
|
|
||||||
new TopicIds(member.subscribedTopicNames(), topicResolver),
|
|
||||||
memberAssignment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.kafka.coordinator.group.modern;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set which presents the union of two underlying sets without
|
||||||
|
* materializing it. This class expects the underlying sets to
|
||||||
|
* be immutable.
|
||||||
|
*
|
||||||
|
* @param <T> The set type.
|
||||||
|
*/
|
||||||
|
public class UnionSet<T> implements Set<T> {
|
||||||
|
private final Set<T> largeSet;
|
||||||
|
private final Set<T> smallSet;
|
||||||
|
private int size = -1;
|
||||||
|
|
||||||
|
public UnionSet(Set<T> s1, Set<T> s2) {
|
||||||
|
Objects.requireNonNull(s1);
|
||||||
|
Objects.requireNonNull(s2);
|
||||||
|
|
||||||
|
if (s1.size() > s2.size()) {
|
||||||
|
largeSet = s1;
|
||||||
|
smallSet = s2;
|
||||||
|
} else {
|
||||||
|
largeSet = s2;
|
||||||
|
smallSet = s1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
if (size == -1) {
|
||||||
|
size = largeSet.size();
|
||||||
|
for (T item : smallSet) {
|
||||||
|
if (!largeSet.contains(item)) {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return largeSet.isEmpty() && smallSet.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return largeSet.contains(o) || smallSet.contains(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new Iterator<T>() {
|
||||||
|
private final Iterator<T> largeSetIterator = largeSet.iterator();
|
||||||
|
private final Iterator<T> smallSetIterator = smallSet.iterator();
|
||||||
|
private T next = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (next != null) return true;
|
||||||
|
if (largeSetIterator.hasNext()) {
|
||||||
|
next = largeSetIterator.next();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
while (smallSetIterator.hasNext()) {
|
||||||
|
next = smallSetIterator.next();
|
||||||
|
if (!largeSet.contains(next)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (!hasNext()) throw new NoSuchElementException();
|
||||||
|
T result = next;
|
||||||
|
next = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
Object[] array = new Object[size()];
|
||||||
|
int index = 0;
|
||||||
|
for (T item : largeSet) {
|
||||||
|
array[index] = item;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
for (T item : smallSet) {
|
||||||
|
if (!largeSet.contains(item)) {
|
||||||
|
array[index] = item;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <U> U[] toArray(U[] array) {
|
||||||
|
int size = size();
|
||||||
|
if (array.length < size) {
|
||||||
|
// Create a new array of the same type with the correct size
|
||||||
|
array = (U[]) Array.newInstance(array.getClass().getComponentType(), size);
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (T item : largeSet) {
|
||||||
|
array[index] = (U) item;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
for (T item : smallSet) {
|
||||||
|
if (!largeSet.contains(item)) {
|
||||||
|
array[index] = (U) item;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (array.length > size) {
|
||||||
|
array[size] = null;
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(Collection<?> c) {
|
||||||
|
for (Object o : c) {
|
||||||
|
if (!contains(o)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Set)) return false;
|
||||||
|
|
||||||
|
Set<?> set = (Set<?>) o;
|
||||||
|
if (set.size() != size()) return false;
|
||||||
|
return containsAll(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int h = 0;
|
||||||
|
for (T item : largeSet) {
|
||||||
|
h += item.hashCode();
|
||||||
|
}
|
||||||
|
for (T item : smallSet) {
|
||||||
|
if (!largeSet.contains(item)) {
|
||||||
|
h += item.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "UnionSet(" +
|
||||||
|
"largeSet=" + largeSet +
|
||||||
|
", smallSet=" + smallSet +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
}
|
|
@ -416,6 +416,13 @@ public class ConsumerGroup extends ModernGroup<ConsumerGroupMember> {
|
||||||
return Collections.unmodifiableMap(staticMembers);
|
return Collections.unmodifiableMap(staticMembers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return An immutable Map containing all the resolved regular expressions.
|
||||||
|
*/
|
||||||
|
public Map<String, ResolvedRegularExpression> resolvedRegularExpressions() {
|
||||||
|
return Collections.unmodifiableMap(resolvedRegularExpressions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current epoch of a partition or -1 if the partition
|
* Returns the current epoch of a partition or -1 if the partition
|
||||||
* does not have one.
|
* does not have one.
|
||||||
|
|
|
@ -70,6 +70,7 @@ import org.apache.kafka.coordinator.common.runtime.MockCoordinatorTimer.ExpiredT
|
||||||
import org.apache.kafka.coordinator.common.runtime.MockCoordinatorTimer.ScheduledTimeout;
|
import org.apache.kafka.coordinator.common.runtime.MockCoordinatorTimer.ScheduledTimeout;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.ConsumerGroupPartitionAssignor;
|
import org.apache.kafka.coordinator.group.api.assignor.ConsumerGroupPartitionAssignor;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.GroupAssignment;
|
import org.apache.kafka.coordinator.group.api.assignor.GroupAssignment;
|
||||||
|
import org.apache.kafka.coordinator.group.api.assignor.GroupSpec;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignorException;
|
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignorException;
|
||||||
import org.apache.kafka.coordinator.group.classic.ClassicGroup;
|
import org.apache.kafka.coordinator.group.classic.ClassicGroup;
|
||||||
import org.apache.kafka.coordinator.group.classic.ClassicGroupMember;
|
import org.apache.kafka.coordinator.group.classic.ClassicGroupMember;
|
||||||
|
@ -14979,6 +14980,83 @@ public class GroupMetadataManagerTest {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConsumerGroupMemberPicksUpExistingResolvedRegularExpression() {
|
||||||
|
String groupId = "fooup";
|
||||||
|
String memberId1 = Uuid.randomUuid().toString();
|
||||||
|
String memberId2 = Uuid.randomUuid().toString();
|
||||||
|
|
||||||
|
Uuid fooTopicId = Uuid.randomUuid();
|
||||||
|
String fooTopicName = "foo";
|
||||||
|
|
||||||
|
ConsumerGroupPartitionAssignor assignor = mock(ConsumerGroupPartitionAssignor.class);
|
||||||
|
when(assignor.name()).thenReturn("range");
|
||||||
|
when(assignor.assign(any(), any())).thenAnswer(answer -> {
|
||||||
|
GroupSpec spec = answer.getArgument(0);
|
||||||
|
|
||||||
|
List.of(memberId1, memberId2).forEach(memberId ->
|
||||||
|
assertEquals(
|
||||||
|
Collections.singleton(fooTopicId),
|
||||||
|
spec.memberSubscription(memberId).subscribedTopicIds(),
|
||||||
|
String.format("Member %s has unexpected subscribed topic ids", memberId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new GroupAssignment(Map.of(
|
||||||
|
memberId1, new MemberAssignmentImpl(mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 0)
|
||||||
|
)),
|
||||||
|
memberId2, new MemberAssignmentImpl(mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 1)
|
||||||
|
))
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext.Builder()
|
||||||
|
.withConsumerGroupAssignors(Collections.singletonList(assignor))
|
||||||
|
.withMetadataImage(new MetadataImageBuilder()
|
||||||
|
.addTopic(fooTopicId, fooTopicName, 2)
|
||||||
|
.build())
|
||||||
|
.withConsumerGroup(new ConsumerGroupBuilder(groupId, 10)
|
||||||
|
.withMember(new ConsumerGroupMember.Builder(memberId1)
|
||||||
|
.setState(MemberState.STABLE)
|
||||||
|
.setMemberEpoch(10)
|
||||||
|
.setPreviousMemberEpoch(10)
|
||||||
|
.setClientId(DEFAULT_CLIENT_ID)
|
||||||
|
.setClientHost(DEFAULT_CLIENT_ADDRESS.toString())
|
||||||
|
.setSubscribedTopicRegex("foo*")
|
||||||
|
.setServerAssignorName("range")
|
||||||
|
.setAssignedPartitions(mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 0, 1)))
|
||||||
|
.build())
|
||||||
|
.withResolvedRegularExpression("foo*", new ResolvedRegularExpression(
|
||||||
|
Collections.singleton(fooTopicName),
|
||||||
|
100L,
|
||||||
|
12345L))
|
||||||
|
.withAssignment(memberId1, mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 0, 1)))
|
||||||
|
.withAssignmentEpoch(10))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CoordinatorResult<ConsumerGroupHeartbeatResponseData, CoordinatorRecord> result = context.consumerGroupHeartbeat(
|
||||||
|
new ConsumerGroupHeartbeatRequestData()
|
||||||
|
.setGroupId(groupId)
|
||||||
|
.setMemberId(memberId2)
|
||||||
|
.setMemberEpoch(0)
|
||||||
|
.setRebalanceTimeoutMs(10000)
|
||||||
|
.setSubscribedTopicRegex("foo*")
|
||||||
|
.setTopicPartitions(Collections.emptyList()));
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
new ConsumerGroupHeartbeatResponseData()
|
||||||
|
.setMemberId(memberId2)
|
||||||
|
.setMemberEpoch(11)
|
||||||
|
.setHeartbeatIntervalMs(5000)
|
||||||
|
.setAssignment(new ConsumerGroupHeartbeatResponseData.Assignment()),
|
||||||
|
result.response()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static void checkJoinGroupResponse(
|
private static void checkJoinGroupResponse(
|
||||||
JoinGroupResponseData expectedResponse,
|
JoinGroupResponseData expectedResponse,
|
||||||
JoinGroupResponseData actualResponse,
|
JoinGroupResponseData actualResponse,
|
||||||
|
|
|
@ -21,10 +21,10 @@ import org.apache.kafka.coordinator.group.AssignmentTestUtil;
|
||||||
import org.apache.kafka.coordinator.group.MetadataImageBuilder;
|
import org.apache.kafka.coordinator.group.MetadataImageBuilder;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.GroupAssignment;
|
import org.apache.kafka.coordinator.group.api.assignor.GroupAssignment;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.MemberAssignment;
|
import org.apache.kafka.coordinator.group.api.assignor.MemberAssignment;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.MemberSubscription;
|
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignor;
|
import org.apache.kafka.coordinator.group.api.assignor.PartitionAssignor;
|
||||||
import org.apache.kafka.coordinator.group.api.assignor.SubscriptionType;
|
import org.apache.kafka.coordinator.group.api.assignor.SubscriptionType;
|
||||||
import org.apache.kafka.coordinator.group.modern.consumer.ConsumerGroupMember;
|
import org.apache.kafka.coordinator.group.modern.consumer.ConsumerGroupMember;
|
||||||
|
import org.apache.kafka.coordinator.group.modern.consumer.ResolvedRegularExpression;
|
||||||
import org.apache.kafka.image.TopicsImage;
|
import org.apache.kafka.image.TopicsImage;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -43,7 +43,6 @@ import static org.apache.kafka.coordinator.group.AssignmentTestUtil.mkTopicAssig
|
||||||
import static org.apache.kafka.coordinator.group.GroupCoordinatorRecordHelpers.newConsumerGroupTargetAssignmentEpochRecord;
|
import static org.apache.kafka.coordinator.group.GroupCoordinatorRecordHelpers.newConsumerGroupTargetAssignmentEpochRecord;
|
||||||
import static org.apache.kafka.coordinator.group.GroupCoordinatorRecordHelpers.newConsumerGroupTargetAssignmentRecord;
|
import static org.apache.kafka.coordinator.group.GroupCoordinatorRecordHelpers.newConsumerGroupTargetAssignmentRecord;
|
||||||
import static org.apache.kafka.coordinator.group.api.assignor.SubscriptionType.HOMOGENEOUS;
|
import static org.apache.kafka.coordinator.group.api.assignor.SubscriptionType.HOMOGENEOUS;
|
||||||
import static org.apache.kafka.coordinator.group.modern.TargetAssignmentBuilder.createMemberSubscriptionAndAssignment;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
@ -63,6 +62,7 @@ public class TargetAssignmentBuilderTest {
|
||||||
private final Map<String, Assignment> targetAssignment = new HashMap<>();
|
private final Map<String, Assignment> targetAssignment = new HashMap<>();
|
||||||
private final Map<String, MemberAssignment> memberAssignments = new HashMap<>();
|
private final Map<String, MemberAssignment> memberAssignments = new HashMap<>();
|
||||||
private final Map<String, String> staticMembers = new HashMap<>();
|
private final Map<String, String> staticMembers = new HashMap<>();
|
||||||
|
private final Map<String, ResolvedRegularExpression> resolvedRegularExpressions = new HashMap<>();
|
||||||
private MetadataImageBuilder topicsImageBuilder = new MetadataImageBuilder();
|
private MetadataImageBuilder topicsImageBuilder = new MetadataImageBuilder();
|
||||||
|
|
||||||
public TargetAssignmentBuilderTestContext(
|
public TargetAssignmentBuilderTestContext(
|
||||||
|
@ -78,17 +78,37 @@ public class TargetAssignmentBuilderTest {
|
||||||
List<String> subscriptions,
|
List<String> subscriptions,
|
||||||
Map<Uuid, Set<Integer>> targetPartitions
|
Map<Uuid, Set<Integer>> targetPartitions
|
||||||
) {
|
) {
|
||||||
addGroupMember(memberId, null, subscriptions, targetPartitions);
|
addGroupMember(memberId, null, subscriptions, "", targetPartitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addGroupMember(
|
public void addGroupMember(
|
||||||
|
String memberId,
|
||||||
|
List<String> subscriptions,
|
||||||
|
String subscribedRegex,
|
||||||
|
Map<Uuid, Set<Integer>> targetPartitions
|
||||||
|
) {
|
||||||
|
addGroupMember(memberId, null, subscriptions, subscribedRegex, targetPartitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGroupMember(
|
||||||
String memberId,
|
String memberId,
|
||||||
String instanceId,
|
String instanceId,
|
||||||
List<String> subscriptions,
|
List<String> subscriptions,
|
||||||
Map<Uuid, Set<Integer>> targetPartitions
|
Map<Uuid, Set<Integer>> targetPartitions
|
||||||
|
) {
|
||||||
|
addGroupMember(memberId, instanceId, subscriptions, "", targetPartitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGroupMember(
|
||||||
|
String memberId,
|
||||||
|
String instanceId,
|
||||||
|
List<String> subscriptions,
|
||||||
|
String subscribedRegex,
|
||||||
|
Map<Uuid, Set<Integer>> targetPartitions
|
||||||
) {
|
) {
|
||||||
ConsumerGroupMember.Builder memberBuilder = new ConsumerGroupMember.Builder(memberId)
|
ConsumerGroupMember.Builder memberBuilder = new ConsumerGroupMember.Builder(memberId)
|
||||||
.setSubscribedTopicNames(subscriptions);
|
.setSubscribedTopicNames(subscriptions)
|
||||||
|
.setSubscribedTopicRegex(subscribedRegex);
|
||||||
|
|
||||||
if (instanceId != null) {
|
if (instanceId != null) {
|
||||||
memberBuilder.setInstanceId(instanceId);
|
memberBuilder.setInstanceId(instanceId);
|
||||||
|
@ -158,6 +178,45 @@ public class TargetAssignmentBuilderTest {
|
||||||
memberAssignments.put(memberId, new MemberAssignmentImpl(assignment));
|
memberAssignments.put(memberId, new MemberAssignmentImpl(assignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addResolvedRegularExpression(
|
||||||
|
String regex,
|
||||||
|
ResolvedRegularExpression resolvedRegularExpression
|
||||||
|
) {
|
||||||
|
resolvedRegularExpressions.put(regex, resolvedRegularExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemberSubscriptionAndAssignmentImpl newMemberSubscriptionAndAssignment(
|
||||||
|
ConsumerGroupMember member,
|
||||||
|
Assignment memberAssignment,
|
||||||
|
TopicIds.TopicResolver topicResolver
|
||||||
|
) {
|
||||||
|
Set<String> subscriptions = member.subscribedTopicNames();
|
||||||
|
|
||||||
|
// Check whether the member is also subscribed to a regular expression. If it is,
|
||||||
|
// create the union of the two subscriptions.
|
||||||
|
String subscribedTopicRegex = member.subscribedTopicRegex();
|
||||||
|
if (subscribedTopicRegex != null && !subscribedTopicRegex.isEmpty()) {
|
||||||
|
ResolvedRegularExpression resolvedRegularExpression = resolvedRegularExpressions.get(subscribedTopicRegex);
|
||||||
|
if (resolvedRegularExpression != null) {
|
||||||
|
if (subscriptions.isEmpty()) {
|
||||||
|
subscriptions = resolvedRegularExpression.topics;
|
||||||
|
} else if (!resolvedRegularExpression.topics.isEmpty()) {
|
||||||
|
// We only use a UnionSet when the member uses both type of subscriptions. The
|
||||||
|
// protocol allows it. However, the Apache Kafka Consumer does not support it.
|
||||||
|
// Other clients such as librdkafka may support it.
|
||||||
|
subscriptions = new UnionSet<>(subscriptions, resolvedRegularExpression.topics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MemberSubscriptionAndAssignmentImpl(
|
||||||
|
Optional.ofNullable(member.rackId()),
|
||||||
|
Optional.ofNullable(member.instanceId()),
|
||||||
|
new TopicIds(subscriptions, topicResolver),
|
||||||
|
memberAssignment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public TargetAssignmentBuilder.TargetAssignmentResult build() {
|
public TargetAssignmentBuilder.TargetAssignmentResult build() {
|
||||||
TopicsImage topicsImage = topicsImageBuilder.build().topics();
|
TopicsImage topicsImage = topicsImageBuilder.build().topics();
|
||||||
TopicIds.TopicResolver topicResolver = new TopicIds.CachedTopicResolver(topicsImage);
|
TopicIds.TopicResolver topicResolver = new TopicIds.CachedTopicResolver(topicsImage);
|
||||||
|
@ -166,7 +225,7 @@ public class TargetAssignmentBuilderTest {
|
||||||
|
|
||||||
// All the existing members are prepared.
|
// All the existing members are prepared.
|
||||||
members.forEach((memberId, member) ->
|
members.forEach((memberId, member) ->
|
||||||
memberSubscriptions.put(memberId, createMemberSubscriptionAndAssignment(
|
memberSubscriptions.put(memberId, newMemberSubscriptionAndAssignment(
|
||||||
member,
|
member,
|
||||||
targetAssignment.getOrDefault(memberId, Assignment.EMPTY),
|
targetAssignment.getOrDefault(memberId, Assignment.EMPTY),
|
||||||
topicResolver
|
topicResolver
|
||||||
|
@ -189,7 +248,7 @@ public class TargetAssignmentBuilderTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memberSubscriptions.put(memberId, createMemberSubscriptionAndAssignment(
|
memberSubscriptions.put(memberId, newMemberSubscriptionAndAssignment(
|
||||||
updatedMemberOrNull,
|
updatedMemberOrNull,
|
||||||
assignment,
|
assignment,
|
||||||
topicResolver
|
topicResolver
|
||||||
|
@ -223,15 +282,16 @@ public class TargetAssignmentBuilderTest {
|
||||||
.thenReturn(new GroupAssignment(memberAssignments));
|
.thenReturn(new GroupAssignment(memberAssignments));
|
||||||
|
|
||||||
// Create and populate the assignment builder.
|
// Create and populate the assignment builder.
|
||||||
TargetAssignmentBuilder<ConsumerGroupMember> builder =
|
TargetAssignmentBuilder.ConsumerTargetAssignmentBuilder builder =
|
||||||
new TargetAssignmentBuilder<ConsumerGroupMember>(groupId, groupEpoch, assignor)
|
new TargetAssignmentBuilder.ConsumerTargetAssignmentBuilder(groupId, groupEpoch, assignor)
|
||||||
.withMembers(members)
|
.withMembers(members)
|
||||||
.withStaticMembers(staticMembers)
|
.withStaticMembers(staticMembers)
|
||||||
.withSubscriptionMetadata(subscriptionMetadata)
|
.withSubscriptionMetadata(subscriptionMetadata)
|
||||||
.withSubscriptionType(subscriptionType)
|
.withSubscriptionType(subscriptionType)
|
||||||
.withTargetAssignment(targetAssignment)
|
.withTargetAssignment(targetAssignment)
|
||||||
.withInvertedTargetAssignment(invertedTargetAssignment)
|
.withInvertedTargetAssignment(invertedTargetAssignment)
|
||||||
.withTopicsImage(topicsImage);
|
.withTopicsImage(topicsImage)
|
||||||
|
.withResolvedRegularExpressions(resolvedRegularExpressions);
|
||||||
|
|
||||||
// Add the updated members or delete the deleted members.
|
// Add the updated members or delete the deleted members.
|
||||||
updatedMembers.forEach((memberId, updatedMemberOrNull) -> {
|
updatedMembers.forEach((memberId, updatedMemberOrNull) -> {
|
||||||
|
@ -254,42 +314,6 @@ public class TargetAssignmentBuilderTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateMemberSubscriptionSpecImpl() {
|
|
||||||
Uuid fooTopicId = Uuid.randomUuid();
|
|
||||||
Uuid barTopicId = Uuid.randomUuid();
|
|
||||||
TopicsImage topicsImage = new MetadataImageBuilder()
|
|
||||||
.addTopic(fooTopicId, "foo", 5)
|
|
||||||
.addTopic(barTopicId, "bar", 5)
|
|
||||||
.build()
|
|
||||||
.topics();
|
|
||||||
TopicIds.TopicResolver topicResolver = new TopicIds.DefaultTopicResolver(topicsImage);
|
|
||||||
|
|
||||||
ConsumerGroupMember member = new ConsumerGroupMember.Builder("member-id")
|
|
||||||
.setSubscribedTopicNames(Arrays.asList("foo", "bar", "zar"))
|
|
||||||
.setRackId("rackId")
|
|
||||||
.setInstanceId("instanceId")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Assignment assignment = new Assignment(mkAssignment(
|
|
||||||
mkTopicAssignment(fooTopicId, 1, 2, 3),
|
|
||||||
mkTopicAssignment(barTopicId, 1, 2, 3)
|
|
||||||
));
|
|
||||||
|
|
||||||
MemberSubscription subscriptionSpec = createMemberSubscriptionAndAssignment(
|
|
||||||
member,
|
|
||||||
assignment,
|
|
||||||
topicResolver
|
|
||||||
);
|
|
||||||
|
|
||||||
assertEquals(new MemberSubscriptionAndAssignmentImpl(
|
|
||||||
Optional.of("rackId"),
|
|
||||||
Optional.of("instanceId"),
|
|
||||||
new TopicIds(Set.of("bar", "foo", "zar"), topicsImage),
|
|
||||||
assignment
|
|
||||||
), subscriptionSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmpty() {
|
public void testEmpty() {
|
||||||
TargetAssignmentBuilderTestContext context = new TargetAssignmentBuilderTestContext(
|
TargetAssignmentBuilderTestContext context = new TargetAssignmentBuilderTestContext(
|
||||||
|
@ -810,4 +834,80 @@ public class TargetAssignmentBuilderTest {
|
||||||
|
|
||||||
assertEquals(expectedAssignment, result.targetAssignment());
|
assertEquals(expectedAssignment, result.targetAssignment());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegularExpressions() {
|
||||||
|
TargetAssignmentBuilderTestContext context = new TargetAssignmentBuilderTestContext(
|
||||||
|
"my-group",
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
Uuid fooTopicId = context.addTopicMetadata("foo", 6);
|
||||||
|
Uuid barTopicId = context.addTopicMetadata("bar", 6);
|
||||||
|
|
||||||
|
context.addGroupMember("member-1", Arrays.asList("bar", "zar"), "foo*", mkAssignment());
|
||||||
|
|
||||||
|
context.addGroupMember("member-2", Arrays.asList("foo", "bar", "zar"), mkAssignment());
|
||||||
|
|
||||||
|
context.addGroupMember("member-3", Collections.emptyList(), "foo*", mkAssignment());
|
||||||
|
|
||||||
|
context.addResolvedRegularExpression("foo*", new ResolvedRegularExpression(
|
||||||
|
Collections.singleton("foo"),
|
||||||
|
10L,
|
||||||
|
12345L
|
||||||
|
));
|
||||||
|
|
||||||
|
context.prepareMemberAssignment("member-1", mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 1, 2),
|
||||||
|
mkTopicAssignment(barTopicId, 1, 2, 3)
|
||||||
|
));
|
||||||
|
|
||||||
|
context.prepareMemberAssignment("member-2", mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 3, 4),
|
||||||
|
mkTopicAssignment(barTopicId, 4, 5, 6)
|
||||||
|
));
|
||||||
|
|
||||||
|
context.prepareMemberAssignment("member-3", mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 5, 6)
|
||||||
|
));
|
||||||
|
|
||||||
|
TargetAssignmentBuilder.TargetAssignmentResult result = context.build();
|
||||||
|
|
||||||
|
assertEquals(4, result.records().size());
|
||||||
|
|
||||||
|
assertUnorderedListEquals(Arrays.asList(
|
||||||
|
newConsumerGroupTargetAssignmentRecord("my-group", "member-1", mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 1, 2),
|
||||||
|
mkTopicAssignment(barTopicId, 1, 2, 3)
|
||||||
|
)),
|
||||||
|
newConsumerGroupTargetAssignmentRecord("my-group", "member-2", mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 3, 4),
|
||||||
|
mkTopicAssignment(barTopicId, 4, 5, 6)
|
||||||
|
)),
|
||||||
|
newConsumerGroupTargetAssignmentRecord("my-group", "member-3", mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 5, 6)
|
||||||
|
))
|
||||||
|
), result.records().subList(0, 3));
|
||||||
|
|
||||||
|
assertEquals(newConsumerGroupTargetAssignmentEpochRecord(
|
||||||
|
"my-group",
|
||||||
|
20
|
||||||
|
), result.records().get(3));
|
||||||
|
|
||||||
|
Map<String, MemberAssignment> expectedAssignment = new HashMap<>();
|
||||||
|
expectedAssignment.put("member-1", new MemberAssignmentImpl(mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 1, 2),
|
||||||
|
mkTopicAssignment(barTopicId, 1, 2, 3)
|
||||||
|
)));
|
||||||
|
expectedAssignment.put("member-2", new MemberAssignmentImpl(mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 3, 4),
|
||||||
|
mkTopicAssignment(barTopicId, 4, 5, 6)
|
||||||
|
)));
|
||||||
|
|
||||||
|
expectedAssignment.put("member-3", new MemberAssignmentImpl(mkAssignment(
|
||||||
|
mkTopicAssignment(fooTopicId, 5, 6)
|
||||||
|
)));
|
||||||
|
|
||||||
|
assertEquals(expectedAssignment, result.targetAssignment());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.kafka.coordinator.group.modern;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class UnionSetTest {
|
||||||
|
@Test
|
||||||
|
public void testSetsCannotBeNull() {
|
||||||
|
assertThrows(NullPointerException.class, () -> new UnionSet<String>(Collections.emptySet(), null));
|
||||||
|
assertThrows(NullPointerException.class, () -> new UnionSet<String>(null, Collections.emptySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnion() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Integer> result = new ArrayList<>();
|
||||||
|
result.addAll(union);
|
||||||
|
result.sort(Integer::compareTo);
|
||||||
|
|
||||||
|
assertEquals(List.of(1, 2, 3, 4, 5), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSize() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(5, union.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsEmpty() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFalse(union.isEmpty());
|
||||||
|
|
||||||
|
union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Collections.emptySet()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFalse(union.isEmpty());
|
||||||
|
|
||||||
|
union = new UnionSet<>(
|
||||||
|
Collections.emptySet(),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFalse(union.isEmpty());
|
||||||
|
|
||||||
|
union = new UnionSet<>(
|
||||||
|
Collections.emptySet(),
|
||||||
|
Collections.emptySet()
|
||||||
|
);
|
||||||
|
assertTrue(union.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContains() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
IntStream.range(1, 6).forEach(item -> assertTrue(union.contains(item)));
|
||||||
|
|
||||||
|
assertFalse(union.contains(0));
|
||||||
|
assertFalse(union.contains(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToArray() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
Object[] expected = {1, 2, 3, 4, 5};
|
||||||
|
Object[] actual = union.toArray();
|
||||||
|
Arrays.sort(actual);
|
||||||
|
assertArrayEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToArrayWithArrayParameter() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
Integer[] input = new Integer[5];
|
||||||
|
Integer[] expected = {1, 2, 3, 4, 5};
|
||||||
|
union.toArray(input);
|
||||||
|
Arrays.sort(input);
|
||||||
|
assertArrayEquals(expected, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEquals() {
|
||||||
|
UnionSet<Integer> union = new UnionSet<>(
|
||||||
|
Set.of(1, 2, 3),
|
||||||
|
Set.of(2, 3, 4, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(Set.of(1, 2, 3, 4, 5), union);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ public class ConsumerGroupBuilder {
|
||||||
private final Map<String, ConsumerGroupMember> members = new HashMap<>();
|
private final Map<String, ConsumerGroupMember> members = new HashMap<>();
|
||||||
private final Map<String, Assignment> assignments = new HashMap<>();
|
private final Map<String, Assignment> assignments = new HashMap<>();
|
||||||
private Map<String, TopicMetadata> subscriptionMetadata;
|
private Map<String, TopicMetadata> subscriptionMetadata;
|
||||||
|
private final Map<String, ResolvedRegularExpression> resolvedRegularExpressions = new HashMap<>();
|
||||||
|
|
||||||
public ConsumerGroupBuilder(String groupId, int groupEpoch) {
|
public ConsumerGroupBuilder(String groupId, int groupEpoch) {
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
|
@ -49,6 +50,14 @@ public class ConsumerGroupBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConsumerGroupBuilder withResolvedRegularExpression(
|
||||||
|
String regex,
|
||||||
|
ResolvedRegularExpression resolvedRegularExpression
|
||||||
|
) {
|
||||||
|
this.resolvedRegularExpressions.put(regex, resolvedRegularExpression);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ConsumerGroupBuilder withSubscriptionMetadata(Map<String, TopicMetadata> subscriptionMetadata) {
|
public ConsumerGroupBuilder withSubscriptionMetadata(Map<String, TopicMetadata> subscriptionMetadata) {
|
||||||
this.subscriptionMetadata = subscriptionMetadata;
|
this.subscriptionMetadata = subscriptionMetadata;
|
||||||
return this;
|
return this;
|
||||||
|
@ -72,6 +81,11 @@ public class ConsumerGroupBuilder {
|
||||||
records.add(GroupCoordinatorRecordHelpers.newConsumerGroupMemberSubscriptionRecord(groupId, member))
|
records.add(GroupCoordinatorRecordHelpers.newConsumerGroupMemberSubscriptionRecord(groupId, member))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add resolved regular expressions.
|
||||||
|
resolvedRegularExpressions.forEach((regex, resolvedRegularExpression) ->
|
||||||
|
records.add(GroupCoordinatorRecordHelpers.newConsumerGroupRegularExpressionRecord(groupId, regex, resolvedRegularExpression))
|
||||||
|
);
|
||||||
|
|
||||||
// Add subscription metadata.
|
// Add subscription metadata.
|
||||||
if (subscriptionMetadata == null) {
|
if (subscriptionMetadata == null) {
|
||||||
subscriptionMetadata = new HashMap<>();
|
subscriptionMetadata = new HashMap<>();
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class TargetAssignmentBuilderBenchmark {
|
||||||
|
|
||||||
private PartitionAssignor partitionAssignor;
|
private PartitionAssignor partitionAssignor;
|
||||||
|
|
||||||
private TargetAssignmentBuilder<ConsumerGroupMember> targetAssignmentBuilder;
|
private TargetAssignmentBuilder.ConsumerTargetAssignmentBuilder targetAssignmentBuilder;
|
||||||
|
|
||||||
/** The number of homogeneous subgroups to create for the heterogeneous subscription case. */
|
/** The number of homogeneous subgroups to create for the heterogeneous subscription case. */
|
||||||
private static final int MAX_BUCKET_COUNT = 5;
|
private static final int MAX_BUCKET_COUNT = 5;
|
||||||
|
@ -116,7 +116,7 @@ public class TargetAssignmentBuilderBenchmark {
|
||||||
.setSubscribedTopicNames(allTopicNames)
|
.setSubscribedTopicNames(allTopicNames)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
targetAssignmentBuilder = new TargetAssignmentBuilder<ConsumerGroupMember>(GROUP_ID, GROUP_EPOCH, partitionAssignor)
|
targetAssignmentBuilder = new TargetAssignmentBuilder.ConsumerTargetAssignmentBuilder(GROUP_ID, GROUP_EPOCH, partitionAssignor)
|
||||||
.withMembers(members)
|
.withMembers(members)
|
||||||
.withSubscriptionMetadata(subscriptionMetadata)
|
.withSubscriptionMetadata(subscriptionMetadata)
|
||||||
.withSubscriptionType(subscriptionType)
|
.withSubscriptionType(subscriptionType)
|
||||||
|
|
Loading…
Reference in New Issue