mirror of https://github.com/apache/kafka.git
KAFKA-18285: Add describeStreamsGroup to Admin API (#19116)
Adds `describeStreamsGroup` to Admin API. This exposes the result of the `DESCRIBE_STREAMS_GROUP` RPC in the Admin API. Reviewers: Bill Bejeck <bill@confluent.io>
This commit is contained in:
parent
53b2935c51
commit
618ea2c1ca
|
@ -1959,6 +1959,29 @@ public interface Admin extends AutoCloseable {
|
||||||
*/
|
*/
|
||||||
DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds, DeleteShareGroupsOptions options);
|
DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds, DeleteShareGroupsOptions options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describe streams groups in the cluster.
|
||||||
|
*
|
||||||
|
* @param groupIds The IDs of the groups to describe.
|
||||||
|
* @param options The options to use when describing the groups.
|
||||||
|
* @return The DescribeStreamsGroupsResult.
|
||||||
|
*/
|
||||||
|
DescribeStreamsGroupsResult describeStreamsGroups(Collection<String> groupIds,
|
||||||
|
DescribeStreamsGroupsOptions options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describe streams groups in the cluster, with the default options.
|
||||||
|
* <p>
|
||||||
|
* This is a convenience method for {@link #describeStreamsGroups(Collection, DescribeStreamsGroupsOptions)}
|
||||||
|
* with default options. See the overload for more details.
|
||||||
|
*
|
||||||
|
* @param groupIds The IDs of the groups to describe.
|
||||||
|
* @return The DescribeStreamsGroupsResult.
|
||||||
|
*/
|
||||||
|
default DescribeStreamsGroupsResult describeStreamsGroups(Collection<String> groupIds) {
|
||||||
|
return describeStreamsGroups(groupIds, new DescribeStreamsGroupsOptions());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describe some classic groups in the cluster.
|
* Describe some classic groups in the cluster.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for {@link Admin#describeStreamsGroups(Collection, DescribeStreamsGroupsOptions)}.
|
||||||
|
* <p>
|
||||||
|
* The API of this class is evolving, see {@link Admin} for details.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class DescribeStreamsGroupsOptions extends AbstractOptions<DescribeStreamsGroupsOptions> {
|
||||||
|
private boolean includeAuthorizedOperations;
|
||||||
|
|
||||||
|
public DescribeStreamsGroupsOptions includeAuthorizedOperations(boolean includeAuthorizedOperations) {
|
||||||
|
this.includeAuthorizedOperations = includeAuthorizedOperations;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean includeAuthorizedOperations() {
|
||||||
|
return includeAuthorizedOperations;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.KafkaFuture;
|
||||||
|
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of the {@link KafkaAdminClient#describeStreamsGroups(Collection, DescribeStreamsGroupsOptions)}} call.
|
||||||
|
* <p>
|
||||||
|
* The API of this class is evolving, see {@link Admin} for details.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class DescribeStreamsGroupsResult {
|
||||||
|
|
||||||
|
private final Map<String, KafkaFuture<StreamsGroupDescription>> futures;
|
||||||
|
|
||||||
|
public DescribeStreamsGroupsResult(final Map<String, KafkaFuture<StreamsGroupDescription>> futures) {
|
||||||
|
this.futures = Map.copyOf(futures);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a map from group id to futures which yield streams group descriptions.
|
||||||
|
*/
|
||||||
|
public Map<String, KafkaFuture<StreamsGroupDescription>> describedGroups() {
|
||||||
|
return new HashMap<>(futures);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a future which yields all StreamsGroupDescription objects, if all the describes succeed.
|
||||||
|
*/
|
||||||
|
public KafkaFuture<Map<String, StreamsGroupDescription>> all() {
|
||||||
|
return KafkaFuture.allOf(futures.values().toArray(new KafkaFuture<?>[0])).thenApply(
|
||||||
|
nil -> {
|
||||||
|
Map<String, StreamsGroupDescription> descriptions = new HashMap<>(futures.size());
|
||||||
|
futures.forEach((key, future) -> {
|
||||||
|
try {
|
||||||
|
descriptions.put(key, future.get());
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
// This should be unreachable, since the KafkaFuture#allOf already ensured
|
||||||
|
// that all of the futures completed successfully.
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return descriptions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -333,6 +333,11 @@ public class ForwardingAdmin implements Admin {
|
||||||
return delegate.deleteShareGroups(groupIds, options);
|
return delegate.deleteShareGroups(groupIds, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DescribeStreamsGroupsResult describeStreamsGroups(Collection<String> groupIds, DescribeStreamsGroupsOptions options) {
|
||||||
|
return delegate.describeStreamsGroups(groupIds, options);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListGroupsResult listGroups(ListGroupsOptions options) {
|
public ListGroupsResult listGroups(ListGroupsOptions options) {
|
||||||
return delegate.listGroups(options);
|
return delegate.listGroups(options);
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.apache.kafka.clients.admin.internals.DescribeClassicGroupsHandler;
|
||||||
import org.apache.kafka.clients.admin.internals.DescribeConsumerGroupsHandler;
|
import org.apache.kafka.clients.admin.internals.DescribeConsumerGroupsHandler;
|
||||||
import org.apache.kafka.clients.admin.internals.DescribeProducersHandler;
|
import org.apache.kafka.clients.admin.internals.DescribeProducersHandler;
|
||||||
import org.apache.kafka.clients.admin.internals.DescribeShareGroupsHandler;
|
import org.apache.kafka.clients.admin.internals.DescribeShareGroupsHandler;
|
||||||
|
import org.apache.kafka.clients.admin.internals.DescribeStreamsGroupsHandler;
|
||||||
import org.apache.kafka.clients.admin.internals.DescribeTransactionsHandler;
|
import org.apache.kafka.clients.admin.internals.DescribeTransactionsHandler;
|
||||||
import org.apache.kafka.clients.admin.internals.FenceProducersHandler;
|
import org.apache.kafka.clients.admin.internals.FenceProducersHandler;
|
||||||
import org.apache.kafka.clients.admin.internals.ListConsumerGroupOffsetsHandler;
|
import org.apache.kafka.clients.admin.internals.ListConsumerGroupOffsetsHandler;
|
||||||
|
@ -3840,6 +3841,17 @@ public class KafkaAdminClient extends AdminClient {
|
||||||
return new ListShareGroupOffsetsResult(future.all());
|
return new ListShareGroupOffsetsResult(future.all());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DescribeStreamsGroupsResult describeStreamsGroups(final Collection<String> groupIds,
|
||||||
|
final DescribeStreamsGroupsOptions options) {
|
||||||
|
SimpleAdminApiFuture<CoordinatorKey, StreamsGroupDescription> future =
|
||||||
|
DescribeStreamsGroupsHandler.newFuture(groupIds);
|
||||||
|
DescribeStreamsGroupsHandler handler = new DescribeStreamsGroupsHandler(options.includeAuthorizedOperations(), logContext);
|
||||||
|
invokeDriver(handler, future, options.timeoutMs);
|
||||||
|
return new DescribeStreamsGroupsResult(future.all().entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(entry -> entry.getKey().idValue, Map.Entry::getValue)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DescribeClassicGroupsResult describeClassicGroups(final Collection<String> groupIds,
|
public DescribeClassicGroupsResult describeClassicGroups(final Collection<String> groupIds,
|
||||||
final DescribeClassicGroupsOptions options) {
|
final DescribeClassicGroupsOptions options) {
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.GroupState;
|
||||||
|
import org.apache.kafka.common.Node;
|
||||||
|
import org.apache.kafka.common.acl.AclOperation;
|
||||||
|
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A detailed description of a single streams group in the cluster.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class StreamsGroupDescription {
|
||||||
|
|
||||||
|
private final String groupId;
|
||||||
|
private final int groupEpoch;
|
||||||
|
private final int targetAssignmentEpoch;
|
||||||
|
private final int topologyEpoch;
|
||||||
|
private final Collection<StreamsGroupSubtopologyDescription> subtopologies;
|
||||||
|
private final Collection<StreamsGroupMemberDescription> members;
|
||||||
|
private final GroupState groupState;
|
||||||
|
private final Node coordinator;
|
||||||
|
private final Set<AclOperation> authorizedOperations;
|
||||||
|
|
||||||
|
public StreamsGroupDescription(
|
||||||
|
final String groupId,
|
||||||
|
final int groupEpoch,
|
||||||
|
final int targetAssignmentEpoch,
|
||||||
|
final int topologyEpoch,
|
||||||
|
final Collection<StreamsGroupSubtopologyDescription> subtopologies,
|
||||||
|
final Collection<StreamsGroupMemberDescription> members,
|
||||||
|
final GroupState groupState,
|
||||||
|
final Node coordinator,
|
||||||
|
final Set<AclOperation> authorizedOperations
|
||||||
|
) {
|
||||||
|
this.groupId = Objects.requireNonNull(groupId, "groupId must be non-null");
|
||||||
|
this.groupEpoch = groupEpoch;
|
||||||
|
this.targetAssignmentEpoch = targetAssignmentEpoch;
|
||||||
|
this.topologyEpoch = topologyEpoch;
|
||||||
|
this.subtopologies = Objects.requireNonNull(subtopologies, "subtopologies must be non-null");
|
||||||
|
this.members = Objects.requireNonNull(members, "members must be non-null");
|
||||||
|
this.groupState = Objects.requireNonNull(groupState, "groupState must be non-null");
|
||||||
|
this.coordinator = Objects.requireNonNull(coordinator, "coordinator must be non-null");
|
||||||
|
this.authorizedOperations = authorizedOperations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the streams group.
|
||||||
|
*/
|
||||||
|
public String groupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The epoch of the consumer group.
|
||||||
|
*/
|
||||||
|
public int groupEpoch() {
|
||||||
|
return groupEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The epoch of the target assignment.
|
||||||
|
*/
|
||||||
|
public int targetAssignmentEpoch() {
|
||||||
|
return targetAssignmentEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The epoch of the currently used topology.
|
||||||
|
*/
|
||||||
|
public int topologyEpoch() {
|
||||||
|
return topologyEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the members of the streams group.
|
||||||
|
*/
|
||||||
|
public Collection<StreamsGroupMemberDescription> members() {
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the subtopologies in the streams group.
|
||||||
|
*/
|
||||||
|
public Collection<StreamsGroupSubtopologyDescription> subtopologies() {
|
||||||
|
return subtopologies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the streams group, or UNKNOWN if the state is too new for us to parse.
|
||||||
|
*/
|
||||||
|
public GroupState groupState() {
|
||||||
|
return groupState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The group coordinator, or null if the coordinator is not known.
|
||||||
|
*/
|
||||||
|
public Node coordinator() {
|
||||||
|
return coordinator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* authorizedOperations for this group, or null if that information is not known.
|
||||||
|
*/
|
||||||
|
public Set<AclOperation> authorizedOperations() {
|
||||||
|
return authorizedOperations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final StreamsGroupDescription that = (StreamsGroupDescription) o;
|
||||||
|
return groupEpoch == that.groupEpoch
|
||||||
|
&& targetAssignmentEpoch == that.targetAssignmentEpoch
|
||||||
|
&& topologyEpoch == that.topologyEpoch
|
||||||
|
&& Objects.equals(groupId, that.groupId)
|
||||||
|
&& Objects.equals(subtopologies, that.subtopologies)
|
||||||
|
&& Objects.equals(members, that.members)
|
||||||
|
&& groupState == that.groupState
|
||||||
|
&& Objects.equals(coordinator, that.coordinator)
|
||||||
|
&& Objects.equals(authorizedOperations, that.authorizedOperations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
groupId,
|
||||||
|
groupEpoch,
|
||||||
|
targetAssignmentEpoch,
|
||||||
|
topologyEpoch,
|
||||||
|
subtopologies,
|
||||||
|
members,
|
||||||
|
groupState,
|
||||||
|
coordinator,
|
||||||
|
authorizedOperations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" +
|
||||||
|
"groupId=" + groupId +
|
||||||
|
", groupEpoch=" + groupEpoch +
|
||||||
|
", targetAssignmentEpoch=" + targetAssignmentEpoch +
|
||||||
|
", topologyEpoch=" + topologyEpoch +
|
||||||
|
", subtopologies=" + subtopologies.stream().map(StreamsGroupSubtopologyDescription::toString).collect(Collectors.joining(",")) +
|
||||||
|
", members=" + members.stream().map(StreamsGroupMemberDescription::toString).collect(Collectors.joining(",")) +
|
||||||
|
", groupState=" + groupState +
|
||||||
|
", coordinator=" + coordinator +
|
||||||
|
", authorizedOperations=" + authorizedOperations.stream().map(AclOperation::toString).collect(Collectors.joining(",")) +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of the assignments of a specific group member.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class StreamsGroupMemberAssignment {
|
||||||
|
|
||||||
|
private final List<TaskIds> activeTasks;
|
||||||
|
private final List<TaskIds> standbyTasks;
|
||||||
|
private final List<TaskIds> warmupTasks;
|
||||||
|
|
||||||
|
public StreamsGroupMemberAssignment(
|
||||||
|
final List<TaskIds> activeTasks,
|
||||||
|
final List<TaskIds> standbyTasks,
|
||||||
|
final List<TaskIds> warmupTasks
|
||||||
|
) {
|
||||||
|
this.activeTasks = activeTasks;
|
||||||
|
this.standbyTasks = standbyTasks;
|
||||||
|
this.warmupTasks = warmupTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active tasks for this client.
|
||||||
|
*/
|
||||||
|
public List<TaskIds> activeTasks() {
|
||||||
|
return List.copyOf(activeTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standby tasks for this client.
|
||||||
|
*/
|
||||||
|
public List<TaskIds> standbyTasks() {
|
||||||
|
return List.copyOf(standbyTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warmup tasks for this client.
|
||||||
|
*/
|
||||||
|
public List<TaskIds> warmupTasks() {
|
||||||
|
return List.copyOf(warmupTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final StreamsGroupMemberAssignment that = (StreamsGroupMemberAssignment) o;
|
||||||
|
return Objects.equals(activeTasks, that.activeTasks)
|
||||||
|
&& Objects.equals(standbyTasks, that.standbyTasks)
|
||||||
|
&& Objects.equals(warmupTasks, that.warmupTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
activeTasks,
|
||||||
|
standbyTasks,
|
||||||
|
warmupTasks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" +
|
||||||
|
"activeTasks=" + activeTasks.stream().map(TaskIds::toString).collect(Collectors.joining(",")) +
|
||||||
|
", standbyTasks=" + standbyTasks.stream().map(TaskIds::toString).collect(Collectors.joining(",")) +
|
||||||
|
", warmupTasks=" + warmupTasks.stream().map(TaskIds::toString).collect(Collectors.joining(",")) +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tasks for one subtopology of a member.
|
||||||
|
*/
|
||||||
|
public static class TaskIds {
|
||||||
|
private final String subtopologyId;
|
||||||
|
private final List<Integer> partitions;
|
||||||
|
|
||||||
|
public TaskIds(final String subtopologyId, final List<Integer> partitions) {
|
||||||
|
this.subtopologyId = Objects.requireNonNull(subtopologyId, "subtopologyId must be non-null");
|
||||||
|
this.partitions = Objects.requireNonNull(partitions, "partitions must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subtopology identifier.
|
||||||
|
*/
|
||||||
|
public String subtopologyId() {
|
||||||
|
return subtopologyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The partitions of the subtopology processed by this member.
|
||||||
|
*/
|
||||||
|
public List<Integer> partitions() {
|
||||||
|
return List.copyOf(partitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TaskIds taskIds = (TaskIds) o;
|
||||||
|
return Objects.equals(subtopologyId, taskIds.subtopologyId)
|
||||||
|
&& Objects.equals(partitions, taskIds.partitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
subtopologyId,
|
||||||
|
partitions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return partitions.stream().map(x -> subtopologyId + "_" + x).collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,371 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A detailed description of a single streams groups member in the cluster.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class StreamsGroupMemberDescription {
|
||||||
|
|
||||||
|
private final String memberId;
|
||||||
|
private final int memberEpoch;
|
||||||
|
private final Optional<String> instanceId;
|
||||||
|
private final Optional<String> rackId;
|
||||||
|
private final String clientId;
|
||||||
|
private final String clientHost;
|
||||||
|
private final int topologyEpoch;
|
||||||
|
private final String processId;
|
||||||
|
private final Optional<Endpoint> userEndpoint;
|
||||||
|
private final Map<String, String> clientTags;
|
||||||
|
private final List<TaskOffset> taskOffsets;
|
||||||
|
private final List<TaskOffset> taskEndOffsets;
|
||||||
|
private final StreamsGroupMemberAssignment assignment;
|
||||||
|
private final StreamsGroupMemberAssignment targetAssignment;
|
||||||
|
private final boolean isClassic;
|
||||||
|
|
||||||
|
@SuppressWarnings("ParameterNumber")
|
||||||
|
public StreamsGroupMemberDescription(
|
||||||
|
final String memberId,
|
||||||
|
final int memberEpoch,
|
||||||
|
final Optional<String> instanceId,
|
||||||
|
final Optional<String> rackId,
|
||||||
|
final String clientId,
|
||||||
|
final String clientHost,
|
||||||
|
final int topologyEpoch,
|
||||||
|
final String processId,
|
||||||
|
final Optional<Endpoint> userEndpoint,
|
||||||
|
final Map<String, String> clientTags,
|
||||||
|
final List<TaskOffset> taskOffsets,
|
||||||
|
final List<TaskOffset> taskEndOffsets,
|
||||||
|
final StreamsGroupMemberAssignment assignment,
|
||||||
|
final StreamsGroupMemberAssignment targetAssignment,
|
||||||
|
final boolean isClassic
|
||||||
|
) {
|
||||||
|
this.memberId = Objects.requireNonNull(memberId);
|
||||||
|
this.memberEpoch = memberEpoch;
|
||||||
|
this.instanceId = Objects.requireNonNull(instanceId);
|
||||||
|
this.rackId = Objects.requireNonNull(rackId);
|
||||||
|
this.clientId = Objects.requireNonNull(clientId);
|
||||||
|
this.clientHost = Objects.requireNonNull(clientHost);
|
||||||
|
this.topologyEpoch = topologyEpoch;
|
||||||
|
this.processId = Objects.requireNonNull(processId);
|
||||||
|
this.userEndpoint = Objects.requireNonNull(userEndpoint);
|
||||||
|
this.clientTags = Objects.requireNonNull(clientTags);
|
||||||
|
this.taskOffsets = Objects.requireNonNull(taskOffsets);
|
||||||
|
this.taskEndOffsets = Objects.requireNonNull(taskEndOffsets);
|
||||||
|
this.assignment = Objects.requireNonNull(assignment);
|
||||||
|
this.targetAssignment = Objects.requireNonNull(targetAssignment);
|
||||||
|
this.isClassic = isClassic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the group member.
|
||||||
|
*/
|
||||||
|
public String memberId() {
|
||||||
|
return memberId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The epoch of the group member.
|
||||||
|
*/
|
||||||
|
public int memberEpoch() {
|
||||||
|
return memberEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the instance, used for static membership, if available.
|
||||||
|
*/
|
||||||
|
public Optional<String> instanceId() {
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rack ID of the group member.
|
||||||
|
*/
|
||||||
|
public Optional<String> rackId() {
|
||||||
|
return rackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client ID of the group member.
|
||||||
|
*/
|
||||||
|
public String clientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host of the group member.
|
||||||
|
*/
|
||||||
|
public String clientHost() {
|
||||||
|
return clientHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The epoch of the topology present on the client.
|
||||||
|
*/
|
||||||
|
public int topologyEpoch() {
|
||||||
|
return topologyEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identity of the streams instance that may have multiple clients.
|
||||||
|
*/
|
||||||
|
public String processId() {
|
||||||
|
return processId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-defined endpoint for Interactive Queries.
|
||||||
|
*/
|
||||||
|
public Optional<Endpoint> userEndpoint() {
|
||||||
|
return userEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for rack-aware assignment algorithm.
|
||||||
|
*/
|
||||||
|
public Map<String, String> clientTags() {
|
||||||
|
return Map.copyOf(clientTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cumulative offsets for tasks.
|
||||||
|
*/
|
||||||
|
public List<TaskOffset> taskOffsets() {
|
||||||
|
return List.copyOf(taskOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cumulative task changelog end offsets for tasks.
|
||||||
|
*/
|
||||||
|
public List<TaskOffset> taskEndOffsets() {
|
||||||
|
return List.copyOf(taskEndOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current assignment.
|
||||||
|
*/
|
||||||
|
public StreamsGroupMemberAssignment assignment() {
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target assignment.
|
||||||
|
*/
|
||||||
|
public StreamsGroupMemberAssignment targetAssignment() {
|
||||||
|
return targetAssignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flag indicating whether a member is classic.
|
||||||
|
*/
|
||||||
|
public boolean isClassic() {
|
||||||
|
return isClassic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CyclomaticComplexity")
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final StreamsGroupMemberDescription that = (StreamsGroupMemberDescription) o;
|
||||||
|
return memberEpoch == that.memberEpoch
|
||||||
|
&& topologyEpoch == that.topologyEpoch
|
||||||
|
&& isClassic == that.isClassic
|
||||||
|
&& Objects.equals(memberId, that.memberId)
|
||||||
|
&& Objects.equals(instanceId, that.instanceId)
|
||||||
|
&& Objects.equals(rackId, that.rackId)
|
||||||
|
&& Objects.equals(clientId, that.clientId)
|
||||||
|
&& Objects.equals(clientHost, that.clientHost)
|
||||||
|
&& Objects.equals(processId, that.processId)
|
||||||
|
&& Objects.equals(userEndpoint, that.userEndpoint)
|
||||||
|
&& Objects.equals(clientTags, that.clientTags)
|
||||||
|
&& Objects.equals(taskOffsets, that.taskOffsets)
|
||||||
|
&& Objects.equals(taskEndOffsets, that.taskEndOffsets)
|
||||||
|
&& Objects.equals(assignment, that.assignment)
|
||||||
|
&& Objects.equals(targetAssignment, that.targetAssignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
memberId,
|
||||||
|
memberEpoch,
|
||||||
|
instanceId,
|
||||||
|
rackId,
|
||||||
|
clientId,
|
||||||
|
clientHost,
|
||||||
|
topologyEpoch,
|
||||||
|
processId,
|
||||||
|
userEndpoint,
|
||||||
|
clientTags,
|
||||||
|
taskOffsets,
|
||||||
|
taskEndOffsets,
|
||||||
|
assignment,
|
||||||
|
targetAssignment,
|
||||||
|
isClassic
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" +
|
||||||
|
"memberId=" + memberId +
|
||||||
|
", memberEpoch=" + memberEpoch +
|
||||||
|
", instanceId=" + instanceId.orElse("null") +
|
||||||
|
", rackId=" + rackId.orElse("null") +
|
||||||
|
", clientId=" + clientId +
|
||||||
|
", clientHost=" + clientHost +
|
||||||
|
", topologyEpoch=" + topologyEpoch +
|
||||||
|
", processId=" + processId +
|
||||||
|
", userEndpoint=" + userEndpoint.map(Endpoint::toString).orElse("null") +
|
||||||
|
", clientTags=" + clientTags +
|
||||||
|
", taskOffsets=" + taskOffsets.stream().map(TaskOffset::toString).collect(Collectors.joining(",")) +
|
||||||
|
", taskEndOffsets=" + taskEndOffsets.stream().map(TaskOffset::toString).collect(Collectors.joining(",")) +
|
||||||
|
", assignment=" + assignment +
|
||||||
|
", targetAssignment=" + targetAssignment +
|
||||||
|
", isClassic=" + isClassic +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user-defined endpoint for the member.
|
||||||
|
*/
|
||||||
|
public static class Endpoint {
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
public Endpoint(final String host, final int port) {
|
||||||
|
this.host = Objects.requireNonNull(host);
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String host() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int port() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Endpoint endpoint = (Endpoint) o;
|
||||||
|
return port == endpoint.port && Objects.equals(host, endpoint.host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" +
|
||||||
|
"host=" + host +
|
||||||
|
", port=" + port +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cumulative offset for one task.
|
||||||
|
*/
|
||||||
|
public static class TaskOffset {
|
||||||
|
|
||||||
|
private final String subtopologyId;
|
||||||
|
private final int partition;
|
||||||
|
private final long offset;
|
||||||
|
|
||||||
|
public TaskOffset(final String subtopologyId, final int partition, final long offset) {
|
||||||
|
this.subtopologyId = Objects.requireNonNull(subtopologyId);
|
||||||
|
this.partition = partition;
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subtopology identifier.
|
||||||
|
*/
|
||||||
|
public String subtopologyId() {
|
||||||
|
return subtopologyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The partition of the task.
|
||||||
|
*/
|
||||||
|
public int partition() {
|
||||||
|
return partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cumulative offset (sum of offsets in all input partitions).
|
||||||
|
*/
|
||||||
|
public long offset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TaskOffset that = (TaskOffset) o;
|
||||||
|
return partition == that.partition
|
||||||
|
&& offset == that.offset
|
||||||
|
&& Objects.equals(subtopologyId, that.subtopologyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
subtopologyId,
|
||||||
|
partition,
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return subtopologyId +
|
||||||
|
"_" + partition +
|
||||||
|
"=" + offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A detailed description of a subtopology in a streams group.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class StreamsGroupSubtopologyDescription {
|
||||||
|
|
||||||
|
private final String subtopologyId;
|
||||||
|
private final List<String> sourceTopics;
|
||||||
|
private final List<String> repartitionSinkTopics;
|
||||||
|
private final Map<String, TopicInfo> stateChangelogTopics;
|
||||||
|
private final Map<String, TopicInfo> repartitionSourceTopics;
|
||||||
|
|
||||||
|
public StreamsGroupSubtopologyDescription(
|
||||||
|
final String subtopologyId,
|
||||||
|
final List<String> sourceTopics,
|
||||||
|
final List<String> repartitionSinkTopics,
|
||||||
|
final Map<String, TopicInfo> stateChangelogTopics,
|
||||||
|
final Map<String, TopicInfo> repartitionSourceTopics
|
||||||
|
) {
|
||||||
|
this.subtopologyId = Objects.requireNonNull(subtopologyId, "subtopologyId must be non-null");
|
||||||
|
this.sourceTopics = Objects.requireNonNull(sourceTopics, "sourceTopics must be non-null");
|
||||||
|
this.repartitionSinkTopics = Objects.requireNonNull(repartitionSinkTopics, "repartitionSinkTopics must be non-null");
|
||||||
|
this.stateChangelogTopics = Objects.requireNonNull(stateChangelogTopics, "stateChangelogTopics must be non-null");
|
||||||
|
this.repartitionSourceTopics = Objects.requireNonNull(repartitionSourceTopics, "repartitionSourceTopics must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to uniquely identify the subtopology.
|
||||||
|
*/
|
||||||
|
public String subtopologyId() {
|
||||||
|
return subtopologyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The topics the topology reads from.
|
||||||
|
*/
|
||||||
|
public List<String> sourceTopics() {
|
||||||
|
return List.copyOf(sourceTopics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repartition topics the topology writes to.
|
||||||
|
*/
|
||||||
|
public List<String> repartitionSinkTopics() {
|
||||||
|
return List.copyOf(repartitionSinkTopics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of state changelog topics associated with this subtopology.
|
||||||
|
*/
|
||||||
|
public Map<String, TopicInfo> stateChangelogTopics() {
|
||||||
|
return Map.copyOf(stateChangelogTopics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of source topics that are internally created repartition topics.
|
||||||
|
*/
|
||||||
|
public Map<String, TopicInfo> repartitionSourceTopics() {
|
||||||
|
return Map.copyOf(repartitionSourceTopics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final StreamsGroupSubtopologyDescription that = (StreamsGroupSubtopologyDescription) o;
|
||||||
|
return Objects.equals(subtopologyId, that.subtopologyId)
|
||||||
|
&& Objects.equals(sourceTopics, that.sourceTopics)
|
||||||
|
&& Objects.equals(repartitionSinkTopics, that.repartitionSinkTopics)
|
||||||
|
&& Objects.equals(stateChangelogTopics, that.stateChangelogTopics)
|
||||||
|
&& Objects.equals(repartitionSourceTopics, that.repartitionSourceTopics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
subtopologyId,
|
||||||
|
sourceTopics,
|
||||||
|
repartitionSinkTopics,
|
||||||
|
stateChangelogTopics,
|
||||||
|
repartitionSourceTopics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" +
|
||||||
|
"subtopologyId='" + subtopologyId + '\'' +
|
||||||
|
", sourceTopics=" + sourceTopics +
|
||||||
|
", repartitionSinkTopics=" + repartitionSinkTopics +
|
||||||
|
", stateChangelogTopics=" + stateChangelogTopics +
|
||||||
|
", repartitionSourceTopics=" + repartitionSourceTopics +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a topic. These configs reflect what is required by the topology, but may differ from the current state on the
|
||||||
|
* broker.
|
||||||
|
*/
|
||||||
|
public static class TopicInfo {
|
||||||
|
|
||||||
|
private final int partitions;
|
||||||
|
private final int replicationFactor;
|
||||||
|
private final Map<String, String> topicConfigs;
|
||||||
|
|
||||||
|
public TopicInfo(final int partitions, final int replicationFactor, final Map<String, String> topicConfigs) {
|
||||||
|
this.partitions = partitions;
|
||||||
|
this.replicationFactor = replicationFactor;
|
||||||
|
this.topicConfigs = Objects.requireNonNull(topicConfigs, "topicConfigs must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of partitions in the topic. Can be 0 if no specific number of partitions is enforced.
|
||||||
|
*/
|
||||||
|
public int partitions() {
|
||||||
|
return partitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The replication factor of the topic. Can be 0 if the default replication factor is used.
|
||||||
|
*/
|
||||||
|
public int replicationFactor() {
|
||||||
|
return replicationFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic-level configurations as key-value pairs. Default configuration can be omitted.
|
||||||
|
*/
|
||||||
|
public Map<String, String> topicConfigs() {
|
||||||
|
return Map.copyOf(topicConfigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TopicInfo topicInfo = (TopicInfo) o;
|
||||||
|
return partitions == topicInfo.partitions
|
||||||
|
&& replicationFactor == topicInfo.replicationFactor
|
||||||
|
&& Objects.equals(topicConfigs, topicInfo.topicConfigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
partitions,
|
||||||
|
replicationFactor,
|
||||||
|
topicConfigs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TopicInfo(" +
|
||||||
|
"partitions=" + partitions +
|
||||||
|
", replicationFactor=" + replicationFactor +
|
||||||
|
", topicConfigs=" + topicConfigs.entrySet().stream().map(x -> x.getKey() + "=" + x.getValue())
|
||||||
|
.collect(Collectors.joining(",")) +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,284 @@
|
||||||
|
/*
|
||||||
|
* 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.clients.admin.internals;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.admin.StreamsGroupDescription;
|
||||||
|
import org.apache.kafka.clients.admin.StreamsGroupMemberAssignment;
|
||||||
|
import org.apache.kafka.clients.admin.StreamsGroupMemberDescription;
|
||||||
|
import org.apache.kafka.clients.admin.StreamsGroupSubtopologyDescription;
|
||||||
|
import org.apache.kafka.common.GroupState;
|
||||||
|
import org.apache.kafka.common.Node;
|
||||||
|
import org.apache.kafka.common.acl.AclOperation;
|
||||||
|
import org.apache.kafka.common.message.StreamsGroupDescribeRequestData;
|
||||||
|
import org.apache.kafka.common.message.StreamsGroupDescribeResponseData;
|
||||||
|
import org.apache.kafka.common.protocol.Errors;
|
||||||
|
import org.apache.kafka.common.requests.AbstractResponse;
|
||||||
|
import org.apache.kafka.common.requests.FindCoordinatorRequest.CoordinatorType;
|
||||||
|
import org.apache.kafka.common.requests.StreamsGroupDescribeRequest;
|
||||||
|
import org.apache.kafka.common.requests.StreamsGroupDescribeResponse;
|
||||||
|
import org.apache.kafka.common.utils.LogContext;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.apache.kafka.clients.admin.internals.AdminUtils.validAclOperations;
|
||||||
|
|
||||||
|
public class DescribeStreamsGroupsHandler extends AdminApiHandler.Batched<CoordinatorKey, StreamsGroupDescription> {
|
||||||
|
|
||||||
|
private final boolean includeAuthorizedOperations;
|
||||||
|
private final Logger log;
|
||||||
|
private final AdminApiLookupStrategy<CoordinatorKey> lookupStrategy;
|
||||||
|
|
||||||
|
public DescribeStreamsGroupsHandler(
|
||||||
|
boolean includeAuthorizedOperations,
|
||||||
|
LogContext logContext) {
|
||||||
|
this.includeAuthorizedOperations = includeAuthorizedOperations;
|
||||||
|
this.log = logContext.logger(DescribeStreamsGroupsHandler.class);
|
||||||
|
this.lookupStrategy = new CoordinatorStrategy(CoordinatorType.GROUP, logContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<CoordinatorKey> buildKeySet(Collection<String> groupIds) {
|
||||||
|
return groupIds.stream()
|
||||||
|
.map(CoordinatorKey::byGroupId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdminApiFuture.SimpleAdminApiFuture<CoordinatorKey, StreamsGroupDescription> newFuture(Collection<String> groupIds) {
|
||||||
|
return AdminApiFuture.forKeys(buildKeySet(groupIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apiName() {
|
||||||
|
return "describeStreamsGroups";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminApiLookupStrategy<CoordinatorKey> lookupStrategy() {
|
||||||
|
return lookupStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamsGroupDescribeRequest.Builder buildBatchedRequest(int coordinatorId, Set<CoordinatorKey> keys) {
|
||||||
|
List<String> groupIds = keys.stream().map(key -> {
|
||||||
|
if (key.type != CoordinatorType.GROUP) {
|
||||||
|
throw new IllegalArgumentException("Invalid group coordinator key " + key +
|
||||||
|
" when building `DescribeStreamsGroups` request");
|
||||||
|
}
|
||||||
|
return key.idValue;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
StreamsGroupDescribeRequestData data = new StreamsGroupDescribeRequestData()
|
||||||
|
.setGroupIds(groupIds)
|
||||||
|
.setIncludeAuthorizedOperations(includeAuthorizedOperations);
|
||||||
|
return new StreamsGroupDescribeRequest.Builder(data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiResult<CoordinatorKey, StreamsGroupDescription> handleResponse(
|
||||||
|
Node coordinator,
|
||||||
|
Set<CoordinatorKey> groupIds,
|
||||||
|
AbstractResponse abstractResponse) {
|
||||||
|
final StreamsGroupDescribeResponse response = (StreamsGroupDescribeResponse) abstractResponse;
|
||||||
|
final Map<CoordinatorKey, StreamsGroupDescription> completed = new HashMap<>();
|
||||||
|
final Map<CoordinatorKey, Throwable> failed = new HashMap<>();
|
||||||
|
final Set<CoordinatorKey> groupsToUnmap = new HashSet<>();
|
||||||
|
|
||||||
|
for (StreamsGroupDescribeResponseData.DescribedGroup describedGroup : response.data().groups()) {
|
||||||
|
CoordinatorKey groupIdKey = CoordinatorKey.byGroupId(describedGroup.groupId());
|
||||||
|
Errors error = Errors.forCode(describedGroup.errorCode());
|
||||||
|
if (error != Errors.NONE) {
|
||||||
|
handleError(groupIdKey, describedGroup, coordinator, error, describedGroup.errorMessage(), completed, failed, groupsToUnmap);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (describedGroup.topology() == null) {
|
||||||
|
log.error("`DescribeStreamsGroups` response for group id {} is missing the topology information", groupIdKey.idValue);
|
||||||
|
failed.put(groupIdKey, new IllegalStateException("Topology information is missing"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<AclOperation> authorizedOperations = validAclOperations(describedGroup.authorizedOperations());
|
||||||
|
|
||||||
|
final StreamsGroupDescription streamsGroupDescription = new StreamsGroupDescription(
|
||||||
|
describedGroup.groupId(),
|
||||||
|
describedGroup.groupEpoch(),
|
||||||
|
describedGroup.assignmentEpoch(),
|
||||||
|
describedGroup.topology().epoch(),
|
||||||
|
convertSubtopologies(describedGroup.topology().subtopologies()),
|
||||||
|
convertMembers(describedGroup.members()),
|
||||||
|
GroupState.parse(describedGroup.groupState()),
|
||||||
|
coordinator,
|
||||||
|
authorizedOperations
|
||||||
|
);
|
||||||
|
completed.put(groupIdKey, streamsGroupDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ApiResult<>(completed, failed, new ArrayList<>(groupsToUnmap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<StreamsGroupMemberDescription> convertMembers(final List<StreamsGroupDescribeResponseData.Member> members) {
|
||||||
|
final List<StreamsGroupMemberDescription> memberDescriptions = new ArrayList<>(members.size());
|
||||||
|
members.forEach(groupMember ->
|
||||||
|
memberDescriptions.add(new StreamsGroupMemberDescription(
|
||||||
|
groupMember.memberId(),
|
||||||
|
groupMember.memberEpoch(),
|
||||||
|
Optional.ofNullable(groupMember.instanceId()),
|
||||||
|
Optional.ofNullable(groupMember.rackId()),
|
||||||
|
groupMember.clientId(),
|
||||||
|
groupMember.clientHost(),
|
||||||
|
groupMember.topologyEpoch(),
|
||||||
|
groupMember.processId(),
|
||||||
|
Optional.ofNullable(groupMember.userEndpoint()).map(this::convertEndpoint),
|
||||||
|
convertClientTags(groupMember.clientTags()),
|
||||||
|
convertTaskOffsets(groupMember.taskOffsets()),
|
||||||
|
convertTaskOffsets(groupMember.taskEndOffsets()),
|
||||||
|
convertAssignment(groupMember.assignment()),
|
||||||
|
convertAssignment(groupMember.targetAssignment()),
|
||||||
|
groupMember.isClassic()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
return memberDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<StreamsGroupSubtopologyDescription> convertSubtopologies(final List<StreamsGroupDescribeResponseData.Subtopology> subtopologies) {
|
||||||
|
final List<StreamsGroupSubtopologyDescription> subtopologyDescriptions = new ArrayList<>(subtopologies.size());
|
||||||
|
subtopologies.forEach(subtopology ->
|
||||||
|
subtopologyDescriptions.add(new StreamsGroupSubtopologyDescription(
|
||||||
|
subtopology.subtopologyId(),
|
||||||
|
subtopology.sourceTopics(),
|
||||||
|
subtopology.repartitionSinkTopics(),
|
||||||
|
convertTopicInfos(subtopology.stateChangelogTopics()),
|
||||||
|
convertTopicInfos(subtopology.repartitionSourceTopics())
|
||||||
|
))
|
||||||
|
);
|
||||||
|
return subtopologyDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, StreamsGroupSubtopologyDescription.TopicInfo> convertTopicInfos(final List<StreamsGroupDescribeResponseData.TopicInfo> topicInfos) {
|
||||||
|
return topicInfos.stream().collect(Collectors.toMap(
|
||||||
|
StreamsGroupDescribeResponseData.TopicInfo::name,
|
||||||
|
topicInfo -> new StreamsGroupSubtopologyDescription.TopicInfo(
|
||||||
|
topicInfo.partitions(),
|
||||||
|
topicInfo.replicationFactor(),
|
||||||
|
topicInfo.topicConfigs().stream().collect(Collectors.toMap(
|
||||||
|
StreamsGroupDescribeResponseData.KeyValue::key,
|
||||||
|
StreamsGroupDescribeResponseData.KeyValue::value
|
||||||
|
))
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreamsGroupMemberAssignment.TaskIds convertTaskIds(final StreamsGroupDescribeResponseData.TaskIds taskIds) {
|
||||||
|
return new StreamsGroupMemberAssignment.TaskIds(
|
||||||
|
taskIds.subtopologyId(),
|
||||||
|
taskIds.partitions()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreamsGroupMemberAssignment convertAssignment(final StreamsGroupDescribeResponseData.Assignment assignment) {
|
||||||
|
return new StreamsGroupMemberAssignment(
|
||||||
|
assignment.activeTasks().stream().map(this::convertTaskIds).collect(Collectors.toList()),
|
||||||
|
assignment.standbyTasks().stream().map(this::convertTaskIds).collect(Collectors.toList()),
|
||||||
|
assignment.warmupTasks().stream().map(this::convertTaskIds).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<StreamsGroupMemberDescription.TaskOffset> convertTaskOffsets(final List<StreamsGroupDescribeResponseData.TaskOffset> taskOffsets) {
|
||||||
|
return taskOffsets.stream().map(taskOffset ->
|
||||||
|
new StreamsGroupMemberDescription.TaskOffset(
|
||||||
|
taskOffset.subtopologyId(),
|
||||||
|
taskOffset.partition(),
|
||||||
|
taskOffset.offset()
|
||||||
|
)
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> convertClientTags(final List<StreamsGroupDescribeResponseData.KeyValue> keyValues) {
|
||||||
|
return keyValues.stream().collect(Collectors.toMap(
|
||||||
|
StreamsGroupDescribeResponseData.KeyValue::key,
|
||||||
|
StreamsGroupDescribeResponseData.KeyValue::value
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreamsGroupMemberDescription.Endpoint convertEndpoint(final StreamsGroupDescribeResponseData.Endpoint endpoint) {
|
||||||
|
return new StreamsGroupMemberDescription.Endpoint(endpoint.host(), endpoint.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleError(
|
||||||
|
CoordinatorKey groupId,
|
||||||
|
StreamsGroupDescribeResponseData.DescribedGroup describedGroup,
|
||||||
|
Node coordinator,
|
||||||
|
Errors error,
|
||||||
|
String errorMsg,
|
||||||
|
Map<CoordinatorKey, StreamsGroupDescription> completed,
|
||||||
|
Map<CoordinatorKey, Throwable> failed,
|
||||||
|
Set<CoordinatorKey> groupsToUnmap) {
|
||||||
|
switch (error) {
|
||||||
|
case GROUP_AUTHORIZATION_FAILED:
|
||||||
|
log.debug("`DescribeStreamsGroups` request for group id {} failed due to error {}", groupId.idValue, error);
|
||||||
|
failed.put(groupId, error.exception(errorMsg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COORDINATOR_LOAD_IN_PROGRESS:
|
||||||
|
// If the coordinator is in the middle of loading, then we just need to retry
|
||||||
|
log.debug("`DescribeStreamsGroups` request for group id {} failed because the coordinator " +
|
||||||
|
"is still in the process of loading state. Will retry", groupId.idValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COORDINATOR_NOT_AVAILABLE:
|
||||||
|
case NOT_COORDINATOR:
|
||||||
|
// If the coordinator is unavailable or there was a coordinator change, then we unmap
|
||||||
|
// the key so that we retry the `FindCoordinator` request
|
||||||
|
log.debug("`DescribeStreamsGroups` request for group id {} returned error {}. " +
|
||||||
|
"Will attempt to find the coordinator again and retry", groupId.idValue, error);
|
||||||
|
groupsToUnmap.add(groupId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GROUP_ID_NOT_FOUND:
|
||||||
|
// In order to maintain compatibility with describeConsumerGroups, an unknown group ID is
|
||||||
|
// reported as a DEAD streams group, and the admin client operation did not fail
|
||||||
|
log.debug("`DescribeStreamsGroups` request for group id {} failed because the group does not exist. {}",
|
||||||
|
groupId.idValue, errorMsg != null ? errorMsg : "");
|
||||||
|
final StreamsGroupDescription streamsGroupDescription =
|
||||||
|
new StreamsGroupDescription(
|
||||||
|
groupId.idValue,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
Collections.emptySet(),
|
||||||
|
Collections.emptySet(),
|
||||||
|
GroupState.DEAD,
|
||||||
|
coordinator,
|
||||||
|
validAclOperations(describedGroup.authorizedOperations()));
|
||||||
|
completed.put(groupId, streamsGroupDescription);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.error("`DescribeStreamsGroups` request for group id {} failed due to unexpected error {}", groupId.idValue, error);
|
||||||
|
failed.put(groupId, error.exception(errorMsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -160,6 +160,7 @@ import org.apache.kafka.common.message.OffsetFetchRequestData.OffsetFetchRequest
|
||||||
import org.apache.kafka.common.message.RemoveRaftVoterRequestData;
|
import org.apache.kafka.common.message.RemoveRaftVoterRequestData;
|
||||||
import org.apache.kafka.common.message.RemoveRaftVoterResponseData;
|
import org.apache.kafka.common.message.RemoveRaftVoterResponseData;
|
||||||
import org.apache.kafka.common.message.ShareGroupDescribeResponseData;
|
import org.apache.kafka.common.message.ShareGroupDescribeResponseData;
|
||||||
|
import org.apache.kafka.common.message.StreamsGroupDescribeResponseData;
|
||||||
import org.apache.kafka.common.message.UnregisterBrokerResponseData;
|
import org.apache.kafka.common.message.UnregisterBrokerResponseData;
|
||||||
import org.apache.kafka.common.message.WriteTxnMarkersResponseData;
|
import org.apache.kafka.common.message.WriteTxnMarkersResponseData;
|
||||||
import org.apache.kafka.common.protocol.ApiKeys;
|
import org.apache.kafka.common.protocol.ApiKeys;
|
||||||
|
@ -237,6 +238,7 @@ import org.apache.kafka.common.requests.RemoveRaftVoterRequest;
|
||||||
import org.apache.kafka.common.requests.RemoveRaftVoterResponse;
|
import org.apache.kafka.common.requests.RemoveRaftVoterResponse;
|
||||||
import org.apache.kafka.common.requests.RequestTestUtils;
|
import org.apache.kafka.common.requests.RequestTestUtils;
|
||||||
import org.apache.kafka.common.requests.ShareGroupDescribeResponse;
|
import org.apache.kafka.common.requests.ShareGroupDescribeResponse;
|
||||||
|
import org.apache.kafka.common.requests.StreamsGroupDescribeResponse;
|
||||||
import org.apache.kafka.common.requests.UnregisterBrokerResponse;
|
import org.apache.kafka.common.requests.UnregisterBrokerResponse;
|
||||||
import org.apache.kafka.common.requests.UpdateFeaturesRequest;
|
import org.apache.kafka.common.requests.UpdateFeaturesRequest;
|
||||||
import org.apache.kafka.common.requests.UpdateFeaturesResponse;
|
import org.apache.kafka.common.requests.UpdateFeaturesResponse;
|
||||||
|
@ -5762,6 +5764,233 @@ public class KafkaAdminClientTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDescribeStreamsGroups() throws Exception {
|
||||||
|
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||||
|
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||||
|
|
||||||
|
// Retriable FindCoordinatorResponse errors should be retried
|
||||||
|
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.COORDINATOR_NOT_AVAILABLE, Node.noNode()));
|
||||||
|
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.COORDINATOR_LOAD_IN_PROGRESS, Node.noNode()));
|
||||||
|
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||||
|
|
||||||
|
StreamsGroupDescribeResponseData data = new StreamsGroupDescribeResponseData();
|
||||||
|
|
||||||
|
// Retriable errors should be retried
|
||||||
|
data.groups().add(new StreamsGroupDescribeResponseData.DescribedGroup()
|
||||||
|
.setGroupId(GROUP_ID)
|
||||||
|
.setErrorCode(Errors.COORDINATOR_LOAD_IN_PROGRESS.code()));
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(data));
|
||||||
|
|
||||||
|
// We need to return two responses here, one with NOT_COORDINATOR error when calling describe streams group
|
||||||
|
// api using coordinator that has moved. This will retry whole operation. So we need to again respond with a
|
||||||
|
// FindCoordinatorResponse.
|
||||||
|
//
|
||||||
|
// And the same reason for COORDINATOR_NOT_AVAILABLE error response
|
||||||
|
data = new StreamsGroupDescribeResponseData();
|
||||||
|
data.groups().add(new StreamsGroupDescribeResponseData.DescribedGroup()
|
||||||
|
.setGroupId(GROUP_ID)
|
||||||
|
.setErrorCode(Errors.NOT_COORDINATOR.code()));
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(data));
|
||||||
|
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||||
|
|
||||||
|
data = new StreamsGroupDescribeResponseData();
|
||||||
|
data.groups().add(new StreamsGroupDescribeResponseData.DescribedGroup()
|
||||||
|
.setGroupId(GROUP_ID)
|
||||||
|
.setErrorCode(Errors.COORDINATOR_NOT_AVAILABLE.code()));
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(data));
|
||||||
|
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||||
|
|
||||||
|
data = makeFullStreamsGroupDescribeResponse();
|
||||||
|
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(data));
|
||||||
|
|
||||||
|
final DescribeStreamsGroupsResult result = env.adminClient().describeStreamsGroups(singletonList(GROUP_ID));
|
||||||
|
final StreamsGroupDescription groupDescription = result.describedGroups().get(GROUP_ID).get();
|
||||||
|
|
||||||
|
final String subtopologyId = "my_subtopology";
|
||||||
|
StreamsGroupMemberAssignment.TaskIds expectedActiveTasks1 =
|
||||||
|
new StreamsGroupMemberAssignment.TaskIds(subtopologyId, asList(0, 1, 2));
|
||||||
|
StreamsGroupMemberAssignment.TaskIds expectedStandbyTasks1 =
|
||||||
|
new StreamsGroupMemberAssignment.TaskIds(subtopologyId, asList(3, 4, 5));
|
||||||
|
StreamsGroupMemberAssignment.TaskIds expectedWarmupTasks1 =
|
||||||
|
new StreamsGroupMemberAssignment.TaskIds(subtopologyId, asList(6, 7, 8));
|
||||||
|
StreamsGroupMemberAssignment.TaskIds expectedActiveTasks2 =
|
||||||
|
new StreamsGroupMemberAssignment.TaskIds(subtopologyId, asList(3, 4, 5));
|
||||||
|
StreamsGroupMemberAssignment.TaskIds expectedStandbyTasks2 =
|
||||||
|
new StreamsGroupMemberAssignment.TaskIds(subtopologyId, asList(6, 7, 8));
|
||||||
|
StreamsGroupMemberAssignment.TaskIds expectedWarmupTasks2 =
|
||||||
|
new StreamsGroupMemberAssignment.TaskIds(subtopologyId, asList(0, 1, 2));
|
||||||
|
StreamsGroupMemberAssignment expectedMemberAssignment = new StreamsGroupMemberAssignment(
|
||||||
|
singletonList(expectedActiveTasks1),
|
||||||
|
singletonList(expectedStandbyTasks1),
|
||||||
|
singletonList(expectedWarmupTasks1)
|
||||||
|
);
|
||||||
|
StreamsGroupMemberAssignment expectedTargetAssignment = new StreamsGroupMemberAssignment(
|
||||||
|
singletonList(expectedActiveTasks2),
|
||||||
|
singletonList(expectedStandbyTasks2),
|
||||||
|
singletonList(expectedWarmupTasks2)
|
||||||
|
);
|
||||||
|
final String instanceId = "instance-id";
|
||||||
|
final String rackId = "rack-id";
|
||||||
|
StreamsGroupMemberDescription expectedMemberOne = new StreamsGroupMemberDescription(
|
||||||
|
"0",
|
||||||
|
1,
|
||||||
|
Optional.of(instanceId),
|
||||||
|
Optional.of(rackId),
|
||||||
|
"clientId0",
|
||||||
|
"clientHost",
|
||||||
|
0,
|
||||||
|
"processId",
|
||||||
|
Optional.of(new StreamsGroupMemberDescription.Endpoint("localhost", 8080)),
|
||||||
|
Collections.singletonMap("key", "value"),
|
||||||
|
Collections.singletonList(new StreamsGroupMemberDescription.TaskOffset(subtopologyId, 0, 0)),
|
||||||
|
Collections.singletonList(new StreamsGroupMemberDescription.TaskOffset(subtopologyId, 0, 1)),
|
||||||
|
expectedMemberAssignment,
|
||||||
|
expectedTargetAssignment,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
StreamsGroupMemberDescription expectedMemberTwo = new StreamsGroupMemberDescription(
|
||||||
|
"1",
|
||||||
|
2,
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
"clientId1",
|
||||||
|
"clientHost",
|
||||||
|
1,
|
||||||
|
"processId2",
|
||||||
|
Optional.empty(),
|
||||||
|
Collections.emptyMap(),
|
||||||
|
Collections.emptyList(),
|
||||||
|
Collections.emptyList(),
|
||||||
|
new StreamsGroupMemberAssignment(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()),
|
||||||
|
new StreamsGroupMemberAssignment(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
StreamsGroupSubtopologyDescription expectedSubtopologyDescription = new StreamsGroupSubtopologyDescription(
|
||||||
|
subtopologyId,
|
||||||
|
Collections.singletonList("my_source_topic"),
|
||||||
|
Collections.singletonList("my_repartition_sink_topic"),
|
||||||
|
Collections.singletonMap(
|
||||||
|
"my_changelog_topic",
|
||||||
|
new StreamsGroupSubtopologyDescription.TopicInfo(
|
||||||
|
0,
|
||||||
|
(short) 3,
|
||||||
|
Collections.singletonMap("key1", "value1")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Collections.singletonMap(
|
||||||
|
"my_repartition_topic",
|
||||||
|
new StreamsGroupSubtopologyDescription.TopicInfo(
|
||||||
|
99,
|
||||||
|
(short) 0,
|
||||||
|
Collections.emptyMap()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(1, result.describedGroups().size());
|
||||||
|
assertEquals(GROUP_ID, groupDescription.groupId());
|
||||||
|
assertEquals(2, groupDescription.members().size());
|
||||||
|
Iterator<StreamsGroupMemberDescription> members = groupDescription.members().iterator();
|
||||||
|
assertEquals(expectedMemberOne, members.next());
|
||||||
|
assertEquals(expectedMemberTwo, members.next());
|
||||||
|
assertEquals(1, groupDescription.subtopologies().size());
|
||||||
|
assertEquals(expectedSubtopologyDescription, groupDescription.subtopologies().iterator().next());
|
||||||
|
assertEquals(2, groupDescription.groupEpoch());
|
||||||
|
assertEquals(1, groupDescription.targetAssignmentEpoch());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDescribeStreamsGroupsWithAuthorizedOperationsOmitted() throws Exception {
|
||||||
|
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||||
|
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||||
|
|
||||||
|
env.kafkaClient().prepareResponse(
|
||||||
|
prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||||
|
|
||||||
|
StreamsGroupDescribeResponseData data = makeFullStreamsGroupDescribeResponse();
|
||||||
|
|
||||||
|
data.groups().iterator().next()
|
||||||
|
.setAuthorizedOperations(MetadataResponse.AUTHORIZED_OPERATIONS_OMITTED);
|
||||||
|
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(data));
|
||||||
|
|
||||||
|
final DescribeStreamsGroupsResult result = env.adminClient().describeStreamsGroups(singletonList(GROUP_ID));
|
||||||
|
final StreamsGroupDescription groupDescription = result.describedGroups().get(GROUP_ID).get();
|
||||||
|
|
||||||
|
assertNull(groupDescription.authorizedOperations());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDescribeMultipleStreamsGroups() {
|
||||||
|
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||||
|
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||||
|
|
||||||
|
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||||
|
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds activeTasks = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(0, 1, 2));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds standbyTasks = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(3, 4, 5));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds warmupTasks = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(6, 7, 8));
|
||||||
|
final StreamsGroupDescribeResponseData.Assignment memberAssignment = new StreamsGroupDescribeResponseData.Assignment()
|
||||||
|
.setActiveTasks(singletonList(activeTasks))
|
||||||
|
.setStandbyTasks(singletonList(standbyTasks))
|
||||||
|
.setWarmupTasks(singletonList(warmupTasks));
|
||||||
|
StreamsGroupDescribeResponseData group0Data = new StreamsGroupDescribeResponseData();
|
||||||
|
group0Data.groups().add(new StreamsGroupDescribeResponseData.DescribedGroup()
|
||||||
|
.setGroupId(GROUP_ID)
|
||||||
|
.setGroupState(GroupState.STABLE.toString())
|
||||||
|
.setMembers(asList(
|
||||||
|
new StreamsGroupDescribeResponseData.Member()
|
||||||
|
.setMemberId("0")
|
||||||
|
.setClientId("clientId0")
|
||||||
|
.setClientHost("clientHost")
|
||||||
|
.setAssignment(memberAssignment),
|
||||||
|
new StreamsGroupDescribeResponseData.Member()
|
||||||
|
.setMemberId("1")
|
||||||
|
.setClientId("clientId1")
|
||||||
|
.setClientHost("clientHost")
|
||||||
|
.setAssignment(memberAssignment))));
|
||||||
|
|
||||||
|
StreamsGroupDescribeResponseData group1Data = new StreamsGroupDescribeResponseData();
|
||||||
|
group1Data.groups().add(new StreamsGroupDescribeResponseData.DescribedGroup()
|
||||||
|
.setGroupId("group-1")
|
||||||
|
.setGroupState(GroupState.STABLE.toString())
|
||||||
|
.setMembers(asList(
|
||||||
|
new StreamsGroupDescribeResponseData.Member()
|
||||||
|
.setMemberId("0")
|
||||||
|
.setClientId("clientId0")
|
||||||
|
.setClientHost("clientHost")
|
||||||
|
.setAssignment(memberAssignment),
|
||||||
|
new StreamsGroupDescribeResponseData.Member()
|
||||||
|
.setMemberId("1")
|
||||||
|
.setClientId("clientId1")
|
||||||
|
.setClientHost("clientHost")
|
||||||
|
.setAssignment(memberAssignment))));
|
||||||
|
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(group0Data));
|
||||||
|
env.kafkaClient().prepareResponse(new StreamsGroupDescribeResponse(group1Data));
|
||||||
|
|
||||||
|
Collection<String> groups = new HashSet<>();
|
||||||
|
groups.add(GROUP_ID);
|
||||||
|
groups.add("group-1");
|
||||||
|
final DescribeStreamsGroupsResult result = env.adminClient().describeStreamsGroups(groups);
|
||||||
|
assertEquals(2, result.describedGroups().size());
|
||||||
|
assertEquals(groups, result.describedGroups().keySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDescribeShareGroups() throws Exception {
|
public void testDescribeShareGroups() throws Exception {
|
||||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||||
|
@ -10281,4 +10510,116 @@ public class KafkaAdminClientTest {
|
||||||
assertNull(result.partitionResult(barPartition0).get());
|
assertNull(result.partitionResult(barPartition0).get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static StreamsGroupDescribeResponseData makeFullStreamsGroupDescribeResponse() {
|
||||||
|
StreamsGroupDescribeResponseData data;
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds activeTasks1 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(0, 1, 2));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds standbyTasks1 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(3, 4, 5));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds warmupTasks1 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(6, 7, 8));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds activeTasks2 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(3, 4, 5));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds standbyTasks2 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(6, 7, 8));
|
||||||
|
StreamsGroupDescribeResponseData.TaskIds warmupTasks2 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartitions(asList(0, 1, 2));
|
||||||
|
StreamsGroupDescribeResponseData.Assignment memberAssignment = new StreamsGroupDescribeResponseData.Assignment()
|
||||||
|
.setActiveTasks(singletonList(activeTasks1))
|
||||||
|
.setStandbyTasks(singletonList(standbyTasks1))
|
||||||
|
.setWarmupTasks(singletonList(warmupTasks1));
|
||||||
|
StreamsGroupDescribeResponseData.Assignment targetAssignment = new StreamsGroupDescribeResponseData.Assignment()
|
||||||
|
.setActiveTasks(singletonList(activeTasks2))
|
||||||
|
.setStandbyTasks(singletonList(standbyTasks2))
|
||||||
|
.setWarmupTasks(singletonList(warmupTasks2));
|
||||||
|
StreamsGroupDescribeResponseData.Member memberOne = new StreamsGroupDescribeResponseData.Member()
|
||||||
|
.setMemberId("0")
|
||||||
|
.setMemberEpoch(1)
|
||||||
|
.setInstanceId("instance-id")
|
||||||
|
.setRackId("rack-id")
|
||||||
|
.setClientId("clientId0")
|
||||||
|
.setClientHost("clientHost")
|
||||||
|
.setTopologyEpoch(0)
|
||||||
|
.setProcessId("processId")
|
||||||
|
.setUserEndpoint(new StreamsGroupDescribeResponseData.Endpoint()
|
||||||
|
.setHost("localhost")
|
||||||
|
.setPort(8080)
|
||||||
|
)
|
||||||
|
.setClientTags(Collections.singletonList(new StreamsGroupDescribeResponseData.KeyValue()
|
||||||
|
.setKey("key")
|
||||||
|
.setValue("value")
|
||||||
|
))
|
||||||
|
.setTaskOffsets(Collections.singletonList(new StreamsGroupDescribeResponseData.TaskOffset()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartition(0)
|
||||||
|
.setOffset(0)
|
||||||
|
))
|
||||||
|
.setTaskEndOffsets(Collections.singletonList(new StreamsGroupDescribeResponseData.TaskOffset()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setPartition(0)
|
||||||
|
.setOffset(1)
|
||||||
|
))
|
||||||
|
.setAssignment(memberAssignment)
|
||||||
|
.setTargetAssignment(targetAssignment)
|
||||||
|
.setIsClassic(true);
|
||||||
|
|
||||||
|
StreamsGroupDescribeResponseData.Member memberTwo = new StreamsGroupDescribeResponseData.Member()
|
||||||
|
.setMemberId("1")
|
||||||
|
.setMemberEpoch(2)
|
||||||
|
.setInstanceId(null)
|
||||||
|
.setRackId(null)
|
||||||
|
.setClientId("clientId1")
|
||||||
|
.setClientHost("clientHost")
|
||||||
|
.setTopologyEpoch(1)
|
||||||
|
.setProcessId("processId2")
|
||||||
|
.setUserEndpoint(null)
|
||||||
|
.setClientTags(Collections.emptyList())
|
||||||
|
.setTaskOffsets(Collections.emptyList())
|
||||||
|
.setTaskEndOffsets(Collections.emptyList())
|
||||||
|
.setAssignment(new StreamsGroupDescribeResponseData.Assignment())
|
||||||
|
.setTargetAssignment(new StreamsGroupDescribeResponseData.Assignment())
|
||||||
|
.setIsClassic(false);
|
||||||
|
|
||||||
|
StreamsGroupDescribeResponseData.Subtopology subtopologyDescription = new StreamsGroupDescribeResponseData.Subtopology()
|
||||||
|
.setSubtopologyId("my_subtopology")
|
||||||
|
.setSourceTopics(Collections.singletonList("my_source_topic"))
|
||||||
|
.setRepartitionSinkTopics(Collections.singletonList("my_repartition_sink_topic"))
|
||||||
|
.setStateChangelogTopics(Collections.singletonList(
|
||||||
|
new StreamsGroupDescribeResponseData.TopicInfo()
|
||||||
|
.setName("my_changelog_topic")
|
||||||
|
.setPartitions(0)
|
||||||
|
.setReplicationFactor((short) 3)
|
||||||
|
.setTopicConfigs(Collections.singletonList(new StreamsGroupDescribeResponseData.KeyValue()
|
||||||
|
.setKey("key1")
|
||||||
|
.setValue("value1")
|
||||||
|
))
|
||||||
|
))
|
||||||
|
.setRepartitionSourceTopics(Collections.singletonList(
|
||||||
|
new StreamsGroupDescribeResponseData.TopicInfo()
|
||||||
|
.setName("my_repartition_topic")
|
||||||
|
.setPartitions(99)
|
||||||
|
.setReplicationFactor((short) 0)
|
||||||
|
.setTopicConfigs(Collections.emptyList())
|
||||||
|
));
|
||||||
|
|
||||||
|
data = new StreamsGroupDescribeResponseData();
|
||||||
|
data.groups().add(new StreamsGroupDescribeResponseData.DescribedGroup()
|
||||||
|
.setGroupId(GROUP_ID)
|
||||||
|
.setGroupState(GroupState.STABLE.toString())
|
||||||
|
.setMembers(asList(memberOne, memberTwo))
|
||||||
|
.setTopology(new StreamsGroupDescribeResponseData.Topology()
|
||||||
|
.setEpoch(1)
|
||||||
|
.setSubtopologies(Collections.singletonList(subtopologyDescription))
|
||||||
|
)
|
||||||
|
.setGroupEpoch(2)
|
||||||
|
.setAssignmentEpoch(1));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1429,6 +1429,11 @@ public class MockAdminClient extends AdminClient {
|
||||||
throw new UnsupportedOperationException("Not implemented yet");
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized DescribeStreamsGroupsResult describeStreamsGroups(Collection<String> groupIds, DescribeStreamsGroupsOptions options) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized DescribeClassicGroupsResult describeClassicGroups(Collection<String> groupIds, DescribeClassicGroupsOptions options) {
|
public synchronized DescribeClassicGroupsResult describeClassicGroups(Collection<String> groupIds, DescribeClassicGroupsOptions options) {
|
||||||
throw new UnsupportedOperationException("Not implemented yet");
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
|
|
@ -91,6 +91,8 @@ import org.apache.kafka.clients.admin.DescribeReplicaLogDirsOptions;
|
||||||
import org.apache.kafka.clients.admin.DescribeReplicaLogDirsResult;
|
import org.apache.kafka.clients.admin.DescribeReplicaLogDirsResult;
|
||||||
import org.apache.kafka.clients.admin.DescribeShareGroupsOptions;
|
import org.apache.kafka.clients.admin.DescribeShareGroupsOptions;
|
||||||
import org.apache.kafka.clients.admin.DescribeShareGroupsResult;
|
import org.apache.kafka.clients.admin.DescribeShareGroupsResult;
|
||||||
|
import org.apache.kafka.clients.admin.DescribeStreamsGroupsOptions;
|
||||||
|
import org.apache.kafka.clients.admin.DescribeStreamsGroupsResult;
|
||||||
import org.apache.kafka.clients.admin.DescribeTopicsOptions;
|
import org.apache.kafka.clients.admin.DescribeTopicsOptions;
|
||||||
import org.apache.kafka.clients.admin.DescribeTopicsResult;
|
import org.apache.kafka.clients.admin.DescribeTopicsResult;
|
||||||
import org.apache.kafka.clients.admin.DescribeTransactionsOptions;
|
import org.apache.kafka.clients.admin.DescribeTransactionsOptions;
|
||||||
|
@ -448,6 +450,11 @@ public class TestingMetricsInterceptingAdminClient extends AdminClient {
|
||||||
public DescribeShareGroupsResult describeShareGroups(final Collection<String> groupIds, final DescribeShareGroupsOptions options) {
|
public DescribeShareGroupsResult describeShareGroups(final Collection<String> groupIds, final DescribeShareGroupsOptions options) {
|
||||||
return adminDelegate.describeShareGroups(groupIds, options);
|
return adminDelegate.describeShareGroups(groupIds, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DescribeStreamsGroupsResult describeStreamsGroups(final Collection<String> groupIds, final DescribeStreamsGroupsOptions options) {
|
||||||
|
return adminDelegate.describeStreamsGroups(groupIds, options);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AlterShareGroupOffsetsResult alterShareGroupOffsets(final String groupId, final Map<TopicPartition, Long> offsets, final AlterShareGroupOffsetsOptions options) {
|
public AlterShareGroupOffsetsResult alterShareGroupOffsets(final String groupId, final Map<TopicPartition, Long> offsets, final AlterShareGroupOffsetsOptions options) {
|
||||||
|
|
Loading…
Reference in New Issue