mirror of https://github.com/apache/kafka.git
KAFKA-16718-2/n: KafkaAdminClient and GroupCoordinator implementation for DeleteShareGroupOffsets RPC (#18976)
This PR contains the implementation of KafkaAdminClient and GroupCoordinator for DeleteShareGroupOffsets RPC. - Added `deleteShareGroupOffsets` to `KafkaAdminClient` - Added implementation for `handleDeleteShareGroupOffsetsRequest` in `KafkaApis.scala` - Added `deleteShareGroupOffsets` to `GroupCoordinator` as well. internally this makes use of `persister.deleteState` to persist the changes in share coordinator Reviewers: Andrew Schofield <aschofield@confluent.io>, Sushant Mahajan <smahajan@confluent.io>
This commit is contained in:
parent
43e22ef5d6
commit
5148174196
|
@ -1947,13 +1947,28 @@ public interface Admin extends AutoCloseable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete share groups from the cluster with the default options.
|
||||
* Delete offsets for a set of partitions in a share group.
|
||||
*
|
||||
* @param groupIds Collection of share group ids which are to be deleted.
|
||||
* @return The DeleteShareGroupsResult.
|
||||
* @param groupId The group for which to delete offsets.
|
||||
* @param partitions The topic-partitions.
|
||||
* @param options The options to use when deleting offsets in a share group.
|
||||
* @return The DeleteShareGroupOffsetsResult.
|
||||
*/
|
||||
default DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds) {
|
||||
return deleteShareGroups(groupIds, new DeleteShareGroupsOptions());
|
||||
DeleteShareGroupOffsetsResult deleteShareGroupOffsets(String groupId, Set<TopicPartition> partitions, DeleteShareGroupOffsetsOptions options);
|
||||
|
||||
/**
|
||||
* Delete offsets for a set of partitions in a share group with the default options.
|
||||
*
|
||||
* <p>
|
||||
* This is a convenience method for {@link #deleteShareGroupOffsets(String, Set, DeleteShareGroupOffsetsOptions)} with default options.
|
||||
* See the overload for more details.
|
||||
*
|
||||
* @param groupId The group for which to delete offsets.
|
||||
* @param partitions The topic-partitions.
|
||||
* @return The DeleteShareGroupOffsetsResult.
|
||||
*/
|
||||
default DeleteShareGroupOffsetsResult deleteShareGroupOffsets(String groupId, Set<TopicPartition> partitions) {
|
||||
return deleteShareGroupOffsets(groupId, partitions, new DeleteShareGroupOffsetsOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1965,6 +1980,16 @@ public interface Admin extends AutoCloseable {
|
|||
*/
|
||||
DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds, DeleteShareGroupsOptions options);
|
||||
|
||||
/**
|
||||
* Delete share groups from the cluster with the default options.
|
||||
*
|
||||
* @param groupIds Collection of share group ids which are to be deleted.
|
||||
* @return The DeleteShareGroupsResult.
|
||||
*/
|
||||
default DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds) {
|
||||
return deleteShareGroups(groupIds, new DeleteShareGroupsOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe streams groups in the cluster.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.Set;
|
||||
|
||||
/**
|
||||
* Options for the {@link Admin#deleteShareGroupOffsets(String, Set, DeleteShareGroupOffsetsOptions)} call.
|
||||
* <p>
|
||||
* The API of this class is evolving, see {@link Admin} for details.
|
||||
*/
|
||||
@InterfaceStability.Evolving
|
||||
public class DeleteShareGroupOffsetsOptions extends AbstractOptions<DeleteShareGroupOffsetsOptions> {
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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.TopicPartition;
|
||||
import org.apache.kafka.common.annotation.InterfaceStability;
|
||||
import org.apache.kafka.common.errors.ApiException;
|
||||
import org.apache.kafka.common.internals.KafkaFutureImpl;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The result of the {@link Admin#deleteShareGroupOffsets(String, Set, DeleteShareGroupOffsetsOptions)} call.
|
||||
* <p>
|
||||
* The API of this class is evolving, see {@link Admin} for details.
|
||||
*/
|
||||
@InterfaceStability.Evolving
|
||||
public class DeleteShareGroupOffsetsResult {
|
||||
|
||||
private final KafkaFuture<Map<TopicPartition, ApiException>> future;
|
||||
private final Set<TopicPartition> partitions;
|
||||
|
||||
DeleteShareGroupOffsetsResult(KafkaFuture<Map<TopicPartition, ApiException>> future, Set<TopicPartition> partitions) {
|
||||
this.future = future;
|
||||
this.partitions = partitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a future which succeeds only if all the deletions succeed.
|
||||
* If not, the first partition error shall be returned.
|
||||
*/
|
||||
public KafkaFuture<Void> all() {
|
||||
final KafkaFutureImpl<Void> result = new KafkaFutureImpl<>();
|
||||
|
||||
this.future.whenComplete((topicPartitions, throwable) -> {
|
||||
if (throwable != null) {
|
||||
result.completeExceptionally(throwable);
|
||||
} else {
|
||||
for (TopicPartition partition : partitions) {
|
||||
if (maybeCompleteExceptionally(topicPartitions, partition, result)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
result.complete(null);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a future which can be used to check the result for a given partition.
|
||||
*/
|
||||
public KafkaFuture<Void> partitionResult(final TopicPartition partition) {
|
||||
if (!partitions.contains(partition)) {
|
||||
throw new IllegalArgumentException("Partition " + partition + " was not included in the original request");
|
||||
}
|
||||
final KafkaFutureImpl<Void> result = new KafkaFutureImpl<>();
|
||||
|
||||
this.future.whenComplete((topicPartitions, throwable) -> {
|
||||
if (throwable != null) {
|
||||
result.completeExceptionally(throwable);
|
||||
} else if (!maybeCompleteExceptionally(topicPartitions, partition, result)) {
|
||||
result.complete(null);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean maybeCompleteExceptionally(Map<TopicPartition, ApiException> partitionLevelErrors,
|
||||
TopicPartition partition,
|
||||
KafkaFutureImpl<Void> result) {
|
||||
Throwable exception;
|
||||
if (!partitionLevelErrors.containsKey(partition)) {
|
||||
exception = new IllegalArgumentException("Offset deletion result for partition \"" + partition + "\" was not included in the response");
|
||||
} else {
|
||||
exception = partitionLevelErrors.get(partition);
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
result.completeExceptionally(exception);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -333,6 +333,11 @@ public class ForwardingAdmin implements Admin {
|
|||
return delegate.listShareGroupOffsets(groupSpecs, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteShareGroupOffsetsResult deleteShareGroupOffsets(String groupId, Set<TopicPartition> partitions, DeleteShareGroupOffsetsOptions options) {
|
||||
return delegate.deleteShareGroupOffsets(groupId, partitions, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds, DeleteShareGroupsOptions options) {
|
||||
return delegate.deleteShareGroups(groupIds, options);
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.apache.kafka.clients.admin.internals.CoordinatorKey;
|
|||
import org.apache.kafka.clients.admin.internals.DeleteConsumerGroupOffsetsHandler;
|
||||
import org.apache.kafka.clients.admin.internals.DeleteConsumerGroupsHandler;
|
||||
import org.apache.kafka.clients.admin.internals.DeleteRecordsHandler;
|
||||
import org.apache.kafka.clients.admin.internals.DeleteShareGroupOffsetsHandler;
|
||||
import org.apache.kafka.clients.admin.internals.DeleteShareGroupsHandler;
|
||||
import org.apache.kafka.clients.admin.internals.DescribeClassicGroupsHandler;
|
||||
import org.apache.kafka.clients.admin.internals.DescribeConsumerGroupsHandler;
|
||||
|
@ -3841,6 +3842,14 @@ public class KafkaAdminClient extends AdminClient {
|
|||
return new ListShareGroupOffsetsResult(future.all());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteShareGroupOffsetsResult deleteShareGroupOffsets(String groupId, Set<TopicPartition> partitions, DeleteShareGroupOffsetsOptions options) {
|
||||
SimpleAdminApiFuture<CoordinatorKey, Map<TopicPartition, ApiException>> future = DeleteShareGroupOffsetsHandler.newFuture(groupId);
|
||||
DeleteShareGroupOffsetsHandler handler = new DeleteShareGroupOffsetsHandler(groupId, partitions, logContext);
|
||||
invokeDriver(handler, future, options.timeoutMs);
|
||||
return new DeleteShareGroupOffsetsResult(future.get(CoordinatorKey.byGroupId(groupId)), partitions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DescribeStreamsGroupsResult describeStreamsGroups(final Collection<String> groupIds,
|
||||
final DescribeStreamsGroupsOptions options) {
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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.DeleteShareGroupOffsetsOptions;
|
||||
import org.apache.kafka.clients.admin.KafkaAdminClient;
|
||||
import org.apache.kafka.common.Node;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
import org.apache.kafka.common.errors.ApiException;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.protocol.Errors;
|
||||
import org.apache.kafka.common.requests.AbstractResponse;
|
||||
import org.apache.kafka.common.requests.DeleteShareGroupOffsetsRequest;
|
||||
import org.apache.kafka.common.requests.DeleteShareGroupOffsetsResponse;
|
||||
import org.apache.kafka.common.requests.FindCoordinatorRequest;
|
||||
import org.apache.kafka.common.utils.LogContext;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class is the handler for {@link KafkaAdminClient#deleteShareGroupOffsets(String, Set, DeleteShareGroupOffsetsOptions)} call
|
||||
*/
|
||||
public class DeleteShareGroupOffsetsHandler extends AdminApiHandler.Batched<CoordinatorKey, Map<TopicPartition, ApiException>> {
|
||||
|
||||
private final CoordinatorKey groupId;
|
||||
|
||||
private final Logger log;
|
||||
|
||||
private final Set<TopicPartition> partitions;
|
||||
|
||||
private final CoordinatorStrategy lookupStrategy;
|
||||
|
||||
public DeleteShareGroupOffsetsHandler(String groupId, Set<TopicPartition> partitions, LogContext logContext) {
|
||||
this.groupId = CoordinatorKey.byGroupId(groupId);
|
||||
this.partitions = partitions;
|
||||
this.log = logContext.logger(DeleteShareGroupOffsetsHandler.class);
|
||||
this.lookupStrategy = new CoordinatorStrategy(FindCoordinatorRequest.CoordinatorType.GROUP, logContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiName() {
|
||||
return "deleteShareGroupOffsets";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminApiLookupStrategy<CoordinatorKey> lookupStrategy() {
|
||||
return lookupStrategy;
|
||||
}
|
||||
|
||||
public static AdminApiFuture.SimpleAdminApiFuture<CoordinatorKey, Map<TopicPartition, ApiException>> newFuture(String groupId) {
|
||||
return AdminApiFuture.forKeys(Collections.singleton(CoordinatorKey.byGroupId(groupId)));
|
||||
}
|
||||
|
||||
private void validateKeys(Set<CoordinatorKey> groupIds) {
|
||||
if (!groupIds.equals(Collections.singleton(groupId))) {
|
||||
throw new IllegalArgumentException("Received unexpected group ids " + groupIds +
|
||||
" (expected only " + Collections.singleton(groupId) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DeleteShareGroupOffsetsRequest.Builder buildBatchedRequest(int brokerId, Set<CoordinatorKey> groupIds) {
|
||||
validateKeys(groupIds);
|
||||
|
||||
final List<DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic> topics =
|
||||
new ArrayList<>();
|
||||
partitions.stream().collect(Collectors.groupingBy(TopicPartition::topic)).forEach((topic, topicPartitions) -> topics.add(
|
||||
new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topic)
|
||||
.setPartitions(topicPartitions.stream()
|
||||
.map(TopicPartition::partition)
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
));
|
||||
|
||||
return new DeleteShareGroupOffsetsRequest.Builder(
|
||||
new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId(groupId.idValue)
|
||||
.setTopics(topics),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResult<CoordinatorKey, Map<TopicPartition, ApiException>> handleResponse(
|
||||
Node coordinator,
|
||||
Set<CoordinatorKey> groupIds,
|
||||
AbstractResponse abstractResponse
|
||||
) {
|
||||
validateKeys(groupIds);
|
||||
|
||||
final DeleteShareGroupOffsetsResponse response = (DeleteShareGroupOffsetsResponse) abstractResponse;
|
||||
|
||||
final Errors groupError = Errors.forCode(response.data().errorCode());
|
||||
final String groupErrorMessage = response.data().errorMessage();
|
||||
|
||||
if (groupError != Errors.NONE) {
|
||||
final Set<CoordinatorKey> groupsToUnmap = new HashSet<>();
|
||||
final Map<CoordinatorKey, Throwable> groupsFailed = new HashMap<>();
|
||||
handleGroupError(groupId, groupError, groupErrorMessage, groupsFailed, groupsToUnmap);
|
||||
|
||||
return new ApiResult<>(Collections.emptyMap(), groupsFailed, new ArrayList<>(groupsToUnmap));
|
||||
} else {
|
||||
final Map<TopicPartition, ApiException> partitionResults = new HashMap<>();
|
||||
response.data().responses().forEach(topic ->
|
||||
topic.partitions().forEach(partition -> {
|
||||
if (partition.errorCode() != Errors.NONE.code()) {
|
||||
final Errors partitionError = Errors.forCode(partition.errorCode());
|
||||
final String partitionErrorMessage = partition.errorMessage();
|
||||
log.debug("DeleteShareGroupOffsets request for group id {}, topic {} and partition {} failed and returned error {}." + partitionErrorMessage,
|
||||
groupId.idValue, topic.topicName(), partition.partitionIndex(), partitionError);
|
||||
}
|
||||
partitionResults.put(
|
||||
new TopicPartition(topic.topicName(), partition.partitionIndex()),
|
||||
Errors.forCode(partition.errorCode()).exception(partition.errorMessage())
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
return ApiResult.completed(groupId, partitionResults);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGroupError(
|
||||
CoordinatorKey groupId,
|
||||
Errors error,
|
||||
String errorMessage,
|
||||
Map<CoordinatorKey, Throwable> failed,
|
||||
Set<CoordinatorKey> groupsToUnmap
|
||||
) {
|
||||
switch (error) {
|
||||
case COORDINATOR_LOAD_IN_PROGRESS:
|
||||
case REBALANCE_IN_PROGRESS:
|
||||
// If the coordinator is in the middle of loading, then we just need to retry
|
||||
log.debug("DeleteShareGroupOffsets request for group id {} failed because the coordinator" +
|
||||
" is still in the process of loading state. Will retry. " + errorMessage, 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("DeleteShareGroupOffsets request for group id {} returned error {}. Will rediscover the coordinator and retry. " + errorMessage,
|
||||
groupId.idValue, error);
|
||||
groupsToUnmap.add(groupId);
|
||||
break;
|
||||
case INVALID_GROUP_ID:
|
||||
case GROUP_ID_NOT_FOUND:
|
||||
case NON_EMPTY_GROUP:
|
||||
case INVALID_REQUEST:
|
||||
case UNKNOWN_SERVER_ERROR:
|
||||
case KAFKA_STORAGE_ERROR:
|
||||
case GROUP_AUTHORIZATION_FAILED:
|
||||
log.debug("DeleteShareGroupOffsets request for group id {} failed due to error {}. " + errorMessage, groupId.idValue, error);
|
||||
failed.put(groupId, error.exception(errorMessage));
|
||||
break;
|
||||
default:
|
||||
log.error("DeleteShareGroupOffsets request for group id {} failed due to unexpected error {}. " + errorMessage, groupId.idValue, error);
|
||||
failed.put(groupId, error.exception(errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,10 +23,6 @@ import org.apache.kafka.common.protocol.ApiKeys;
|
|||
import org.apache.kafka.common.protocol.Errors;
|
||||
import org.apache.kafka.common.protocol.Readable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DeleteShareGroupOffsetsRequest extends AbstractRequest {
|
||||
public static class Builder extends AbstractRequest.Builder<DeleteShareGroupOffsetsRequest> {
|
||||
|
||||
|
@ -59,19 +55,20 @@ public class DeleteShareGroupOffsetsRequest extends AbstractRequest {
|
|||
this.data = data;
|
||||
}
|
||||
|
||||
DeleteShareGroupOffsetsResponse getErrorResponse(int throttleTimeMs, Errors error) {
|
||||
return getErrorResponse(throttleTimeMs, error.code(), error.message());
|
||||
}
|
||||
|
||||
public DeleteShareGroupOffsetsResponse getErrorResponse(int throttleTimeMs, short errorCode, String errorMessage) {
|
||||
return new DeleteShareGroupOffsetsResponse(new DeleteShareGroupOffsetsResponseData()
|
||||
.setThrottleTimeMs(throttleTimeMs)
|
||||
.setErrorMessage(errorMessage)
|
||||
.setErrorCode(errorCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteShareGroupOffsetsResponse getErrorResponse(int throttleTimeMs, Throwable e) {
|
||||
List<DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic> results = new ArrayList<>();
|
||||
data.topics().forEach(
|
||||
topicResult -> results.add(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topicResult.topicName())
|
||||
.setPartitions(topicResult.partitions().stream()
|
||||
.map(partitionData -> new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partitionData)
|
||||
.setErrorCode(Errors.forException(e).code()))
|
||||
.collect(Collectors.toList()))));
|
||||
return new DeleteShareGroupOffsetsResponse(new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(results));
|
||||
return getErrorResponse(throttleTimeMs, Errors.forException(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,4 +82,14 @@ public class DeleteShareGroupOffsetsRequest extends AbstractRequest {
|
|||
version
|
||||
);
|
||||
}
|
||||
|
||||
public static DeleteShareGroupOffsetsResponseData getErrorDeleteResponseData(Errors error) {
|
||||
return getErrorDeleteResponseData(error.code(), error.message());
|
||||
}
|
||||
|
||||
public static DeleteShareGroupOffsetsResponseData getErrorDeleteResponseData(short errorCode, String errorMessage) {
|
||||
return new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(errorCode)
|
||||
.setErrorMessage(errorMessage);
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ public class DeleteShareGroupOffsetsResponse extends AbstractResponse {
|
|||
@Override
|
||||
public Map<Errors, Integer> errorCounts() {
|
||||
Map<Errors, Integer> counts = new EnumMap<>(Errors.class);
|
||||
updateErrorCounts(counts, Errors.forCode(data.errorCode()));
|
||||
data.responses().forEach(
|
||||
topicResult -> topicResult.partitions().forEach(
|
||||
partitionResult -> updateErrorCounts(counts, Errors.forCode(partitionResult.errorCode()))
|
||||
|
|
|
@ -94,6 +94,7 @@ public class ShareGroupDescribeRequest extends AbstractRequest {
|
|||
.map(groupId -> new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId(groupId)
|
||||
.setErrorCode(error.code())
|
||||
.setErrorMessage(error.message())
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,8 @@ import org.apache.kafka.common.message.DeleteGroupsResponseData;
|
|||
import org.apache.kafka.common.message.DeleteGroupsResponseData.DeletableGroupResult;
|
||||
import org.apache.kafka.common.message.DeleteGroupsResponseData.DeletableGroupResultCollection;
|
||||
import org.apache.kafka.common.message.DeleteRecordsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteTopicsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteTopicsResponseData.DeletableTopicResult;
|
||||
import org.apache.kafka.common.message.DeleteTopicsResponseData.DeletableTopicResultCollection;
|
||||
|
@ -191,6 +193,8 @@ import org.apache.kafka.common.requests.CreateTopicsResponse;
|
|||
import org.apache.kafka.common.requests.DeleteAclsResponse;
|
||||
import org.apache.kafka.common.requests.DeleteGroupsResponse;
|
||||
import org.apache.kafka.common.requests.DeleteRecordsResponse;
|
||||
import org.apache.kafka.common.requests.DeleteShareGroupOffsetsRequest;
|
||||
import org.apache.kafka.common.requests.DeleteShareGroupOffsetsResponse;
|
||||
import org.apache.kafka.common.requests.DeleteTopicsRequest;
|
||||
import org.apache.kafka.common.requests.DeleteTopicsResponse;
|
||||
import org.apache.kafka.common.requests.DescribeAclsResponse;
|
||||
|
@ -10780,6 +10784,156 @@ public class KafkaAdminClientTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsOptionsWithBatchedApi() throws Exception {
|
||||
final Cluster cluster = mockCluster(3, 0);
|
||||
final Time time = new MockTime();
|
||||
|
||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(time, cluster,
|
||||
AdminClientConfig.RETRIES_CONFIG, "0")) {
|
||||
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||
|
||||
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||
|
||||
final Set<TopicPartition> partitions = Collections.singleton(new TopicPartition("A", 0));
|
||||
final DeleteShareGroupOffsetsOptions options = new DeleteShareGroupOffsetsOptions();
|
||||
|
||||
env.adminClient().deleteShareGroupOffsets(GROUP_ID, partitions, options);
|
||||
|
||||
final MockClient mockClient = env.kafkaClient();
|
||||
waitForRequest(mockClient, ApiKeys.DELETE_SHARE_GROUP_OFFSETS);
|
||||
|
||||
ClientRequest clientRequest = mockClient.requests().peek();
|
||||
assertNotNull(clientRequest);
|
||||
DeleteShareGroupOffsetsRequestData data = ((DeleteShareGroupOffsetsRequest.Builder) clientRequest.requestBuilder()).build().data();
|
||||
assertEquals(GROUP_ID, data.groupId());
|
||||
assertEquals(1, data.topics().size());
|
||||
assertEquals(Collections.singletonList("A"),
|
||||
data.topics().stream().map(DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic::topicName).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsets() throws Exception {
|
||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData data = new DeleteShareGroupOffsetsResponseData().setResponses(
|
||||
List.of(
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic().setTopicName("foo").setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(0), new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(1))),
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic().setTopicName("bar").setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(0)))
|
||||
)
|
||||
);
|
||||
|
||||
TopicPartition fooTopicPartition0 = new TopicPartition("foo", 0);
|
||||
TopicPartition fooTopicPartition1 = new TopicPartition("foo", 1);
|
||||
TopicPartition barPartition0 = new TopicPartition("bar", 0);
|
||||
TopicPartition zooTopicPartition0 = new TopicPartition("zoo", 0);
|
||||
|
||||
env.kafkaClient().prepareResponse(new DeleteShareGroupOffsetsResponse(data));
|
||||
final DeleteShareGroupOffsetsResult result = env.adminClient().deleteShareGroupOffsets(GROUP_ID, Set.of(fooTopicPartition0, fooTopicPartition1, barPartition0));
|
||||
|
||||
assertNull(result.all().get());
|
||||
assertNull(result.partitionResult(fooTopicPartition0).get());
|
||||
assertNull(result.partitionResult(fooTopicPartition1).get());
|
||||
assertNull(result.partitionResult(barPartition0).get());
|
||||
assertThrows(IllegalArgumentException.class, () -> result.partitionResult(zooTopicPartition0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsEmpty() throws Exception {
|
||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||
|
||||
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData data = new DeleteShareGroupOffsetsResponseData().setResponses(
|
||||
Collections.emptyList()
|
||||
);
|
||||
env.kafkaClient().prepareResponse(new DeleteShareGroupOffsetsResponse(data));
|
||||
|
||||
final DeleteShareGroupOffsetsResult result = env.adminClient().deleteShareGroupOffsets(GROUP_ID, Collections.emptySet());
|
||||
assertDoesNotThrow(() -> result.all().get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithErrorInGroup() throws Exception {
|
||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData data = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.GROUP_AUTHORIZATION_FAILED.code())
|
||||
.setErrorMessage(Errors.GROUP_AUTHORIZATION_FAILED.message());
|
||||
|
||||
TopicPartition fooTopicPartition0 = new TopicPartition("foo", 0);
|
||||
TopicPartition fooTopicPartition1 = new TopicPartition("foo", 1);
|
||||
TopicPartition barTopicPartition0 = new TopicPartition("bar", 0);
|
||||
|
||||
env.kafkaClient().prepareResponse(new DeleteShareGroupOffsetsResponse(data));
|
||||
final DeleteShareGroupOffsetsResult result = env.adminClient().deleteShareGroupOffsets(GROUP_ID, Set.of(fooTopicPartition0, fooTopicPartition1, barTopicPartition0));
|
||||
|
||||
TestUtils.assertFutureThrows(Errors.GROUP_AUTHORIZATION_FAILED.exception().getClass(), result.all());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithErrorInOnePartition() throws Exception {
|
||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData data = new DeleteShareGroupOffsetsResponseData().setResponses(
|
||||
List.of(
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic().setTopicName("foo").setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(0), new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(1).setErrorCode(Errors.KAFKA_STORAGE_ERROR.code()).setErrorMessage(Errors.KAFKA_STORAGE_ERROR.message()))),
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic().setTopicName("bar").setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(0)))
|
||||
)
|
||||
);
|
||||
|
||||
TopicPartition fooTopicPartition0 = new TopicPartition("foo", 0);
|
||||
TopicPartition fooTopicPartition1 = new TopicPartition("foo", 1);
|
||||
TopicPartition barTopicPartition0 = new TopicPartition("bar", 0);
|
||||
|
||||
env.kafkaClient().prepareResponse(new DeleteShareGroupOffsetsResponse(data));
|
||||
final DeleteShareGroupOffsetsResult result = env.adminClient().deleteShareGroupOffsets(GROUP_ID, Set.of(fooTopicPartition0, fooTopicPartition1, barTopicPartition0));
|
||||
|
||||
TestUtils.assertFutureThrows(Errors.KAFKA_STORAGE_ERROR.exception().getClass(), result.all());
|
||||
assertNull(result.partitionResult(fooTopicPartition0).get());
|
||||
TestUtils.assertFutureThrows(Errors.KAFKA_STORAGE_ERROR.exception().getClass(), result.partitionResult(fooTopicPartition1));
|
||||
assertNull(result.partitionResult(barTopicPartition0).get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithPartitionNotPresentInResult() throws Exception {
|
||||
try (AdminClientUnitTestEnv env = new AdminClientUnitTestEnv(mockCluster(1, 0))) {
|
||||
env.kafkaClient().setNodeApiVersions(NodeApiVersions.create());
|
||||
env.kafkaClient().prepareResponse(prepareFindCoordinatorResponse(Errors.NONE, env.cluster().controller()));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData data = new DeleteShareGroupOffsetsResponseData().setResponses(
|
||||
List.of(
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic().setTopicName("foo").setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(0), new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(1))),
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic().setTopicName("bar").setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition().setPartitionIndex(0)))
|
||||
)
|
||||
);
|
||||
|
||||
TopicPartition fooTopicPartition0 = new TopicPartition("foo", 0);
|
||||
TopicPartition fooTopicPartition1 = new TopicPartition("foo", 1);
|
||||
TopicPartition barTopicPartition0 = new TopicPartition("bar", 0);
|
||||
TopicPartition barTopicPartition1 = new TopicPartition("bar", 1);
|
||||
|
||||
env.kafkaClient().prepareResponse(new DeleteShareGroupOffsetsResponse(data));
|
||||
final DeleteShareGroupOffsetsResult result = env.adminClient().deleteShareGroupOffsets(GROUP_ID, Set.of(fooTopicPartition0, fooTopicPartition1, barTopicPartition0));
|
||||
|
||||
assertDoesNotThrow(() -> result.all().get());
|
||||
assertThrows(IllegalArgumentException.class, () -> result.partitionResult(barTopicPartition1));
|
||||
assertNull(result.partitionResult(barTopicPartition0).get());
|
||||
}
|
||||
}
|
||||
|
||||
private static StreamsGroupDescribeResponseData makeFullStreamsGroupDescribeResponse() {
|
||||
StreamsGroupDescribeResponseData data;
|
||||
StreamsGroupDescribeResponseData.TaskIds activeTasks1 = new StreamsGroupDescribeResponseData.TaskIds()
|
||||
|
|
|
@ -1429,6 +1429,11 @@ public class MockAdminClient extends AdminClient {
|
|||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DeleteShareGroupOffsetsResult deleteShareGroupOffsets(String groupId, Set<TopicPartition> partitions, DeleteShareGroupOffsetsOptions options) {
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DeleteShareGroupsResult deleteShareGroups(Collection<String> groupIds, DeleteShareGroupsOptions options) {
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
|
|
|
@ -32,6 +32,8 @@ import org.apache.kafka.common.internals.Topic.{GROUP_METADATA_TOPIC_NAME, SHARE
|
|||
import org.apache.kafka.common.internals.{FatalExitError, Topic}
|
||||
import org.apache.kafka.common.message.AddPartitionsToTxnResponseData.{AddPartitionsToTxnResult, AddPartitionsToTxnResultCollection}
|
||||
import org.apache.kafka.common.message.DeleteRecordsResponseData.{DeleteRecordsPartitionResult, DeleteRecordsTopicResult}
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic
|
||||
import org.apache.kafka.common.message.ListClientMetricsResourcesResponseData.ClientMetricsResource
|
||||
import org.apache.kafka.common.message.ListOffsetsRequestData.ListOffsetsPartition
|
||||
import org.apache.kafka.common.message.ListOffsetsResponseData.{ListOffsetsPartitionResponse, ListOffsetsTopicResponse}
|
||||
|
@ -75,6 +77,7 @@ import java.util.concurrent.{CompletableFuture, ConcurrentHashMap}
|
|||
import java.util.stream.Collectors
|
||||
import java.util.{Collections, Optional}
|
||||
import scala.annotation.nowarn
|
||||
import scala.collection.convert.ImplicitConversions.`collection AsScalaIterable`
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.collection.{Map, Seq, Set, mutable}
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
@ -3625,8 +3628,64 @@ class KafkaApis(val requestChannel: RequestChannel,
|
|||
|
||||
def handleDeleteShareGroupOffsetsRequest(request: RequestChannel.Request): Unit = {
|
||||
val deleteShareGroupOffsetsRequest = request.body[DeleteShareGroupOffsetsRequest]
|
||||
requestHelper.sendMaybeThrottle(request, deleteShareGroupOffsetsRequest.getErrorResponse(Errors.UNSUPPORTED_VERSION.exception))
|
||||
CompletableFuture.completedFuture[Unit](())
|
||||
|
||||
val groupId = deleteShareGroupOffsetsRequest.data.groupId
|
||||
|
||||
if (!isShareGroupProtocolEnabled) {
|
||||
requestHelper.sendMaybeThrottle(request, deleteShareGroupOffsetsRequest.getErrorResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, Errors.UNSUPPORTED_VERSION.exception))
|
||||
return
|
||||
} else if (!authHelper.authorize(request.context, DELETE, GROUP, groupId)) {
|
||||
requestHelper.sendMaybeThrottle(request, deleteShareGroupOffsetsRequest.getErrorResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, Errors.GROUP_AUTHORIZATION_FAILED.exception))
|
||||
return
|
||||
}
|
||||
|
||||
val deleteShareGroupOffsetsResponseTopics: util.List[DeleteShareGroupOffsetsResponseTopic] = new util.ArrayList[DeleteShareGroupOffsetsResponseTopic]()
|
||||
|
||||
val authorizedTopics: util.List[DeleteShareGroupOffsetsRequestTopic] =
|
||||
new util.ArrayList[DeleteShareGroupOffsetsRequestTopic]
|
||||
|
||||
deleteShareGroupOffsetsRequest.data.topics.forEach{ topic =>
|
||||
if (!authHelper.authorize(request.context, READ, TOPIC, topic.topicName)) {
|
||||
deleteShareGroupOffsetsResponseTopics.add(
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topic.topicName)
|
||||
.setPartitions(topic.partitions.map(partition => {
|
||||
new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partition)
|
||||
.setErrorCode(Errors.TOPIC_AUTHORIZATION_FAILED.code)
|
||||
.setErrorMessage(Errors.TOPIC_AUTHORIZATION_FAILED.message())
|
||||
}).toList.asJava)
|
||||
)
|
||||
} else {
|
||||
authorizedTopics.add(topic)
|
||||
}
|
||||
}
|
||||
|
||||
if (authorizedTopics.isEmpty) {
|
||||
requestHelper.sendMaybeThrottle(request, new DeleteShareGroupOffsetsResponse(new DeleteShareGroupOffsetsResponseData()))
|
||||
return
|
||||
}
|
||||
|
||||
groupCoordinator.deleteShareGroupOffsets(
|
||||
request.context,
|
||||
new DeleteShareGroupOffsetsRequestData().setGroupId(groupId).setTopics(authorizedTopics)
|
||||
).handle[Unit] {(responseData, exception) => {
|
||||
if (exception != null) {
|
||||
requestHelper.sendMaybeThrottle(request, deleteShareGroupOffsetsRequest.getErrorResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, exception))
|
||||
} else if (responseData.errorCode() != Errors.NONE.code) {
|
||||
requestHelper.sendMaybeThrottle(
|
||||
request,
|
||||
deleteShareGroupOffsetsRequest.getErrorResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, responseData.errorCode(), responseData.errorMessage())
|
||||
)
|
||||
} else {
|
||||
responseData.responses.forEach { topic => {
|
||||
deleteShareGroupOffsetsResponseTopics.add(topic)
|
||||
}}
|
||||
val deleteShareGroupStateResponse = new DeleteShareGroupOffsetsResponse(new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(deleteShareGroupOffsetsResponseTopics))
|
||||
requestHelper.sendMaybeThrottle(request, deleteShareGroupStateResponse)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
// Visible for Testing
|
||||
|
|
|
@ -43,6 +43,8 @@ import org.apache.kafka.common.message.ApiMessageType.ListenerType
|
|||
import org.apache.kafka.common.message.ConsumerGroupDescribeResponseData.{DescribedGroup, TopicPartitions}
|
||||
import org.apache.kafka.common.message.CreateTopicsRequestData.CreatableTopic
|
||||
import org.apache.kafka.common.message.CreateTopicsResponseData.CreatableTopicResult
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsResponseData.{DeleteShareGroupOffsetsResponsePartition, DeleteShareGroupOffsetsResponseTopic}
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsRequestData.{DescribeShareGroupOffsetsRequestGroup, DescribeShareGroupOffsetsRequestTopic}
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsResponseData.{DescribeShareGroupOffsetsResponseGroup, DescribeShareGroupOffsetsResponsePartition, DescribeShareGroupOffsetsResponseTopic}
|
||||
import org.apache.kafka.common.message.IncrementalAlterConfigsRequestData.{AlterConfigsResource => IAlterConfigsResource, AlterConfigsResourceCollection => IAlterConfigsResourceCollection, AlterableConfig => IAlterableConfig, AlterableConfigCollection => IAlterableConfigCollection}
|
||||
|
@ -11809,6 +11811,358 @@ class KafkaApisTest extends Logging {
|
|||
assertEquals(describeShareGroupOffsetsResponse, response.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsReturnsUnsupportedVersion(): Unit = {
|
||||
val deleteShareGroupOffsetsRequest = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("group")
|
||||
.setTopics(util.List.of(new DeleteShareGroupOffsetsRequestTopic().setTopicName("topic-1").setPartitions(util.List.of(1))))
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequest, true).build())
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
kafkaApis = createKafkaApis()
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
response.data.responses.forEach(topic => topic.partitions.forEach(partition => assertEquals(Errors.UNSUPPORTED_VERSION.code, partition.errorCode)))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsRequestsGroupAuthorizationFailed(): Unit = {
|
||||
val deleteShareGroupOffsetsRequest = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("group")
|
||||
.setTopics(util.List.of(new DeleteShareGroupOffsetsRequestTopic().setTopicName("topic-1").setPartitions(util.List.of(1))))
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequest, true).build)
|
||||
|
||||
val authorizer: Authorizer = mock(classOf[Authorizer])
|
||||
when(authorizer.authorize(any[RequestContext], any[util.List[Action]]))
|
||||
.thenReturn(util.List.of(AuthorizationResult.DENIED))
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
kafkaApis = createKafkaApis(
|
||||
overrideProperties = Map(ShareGroupConfig.SHARE_GROUP_ENABLE_CONFIG -> "true"),
|
||||
authorizer = Some(authorizer),
|
||||
)
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
assertEquals(Errors.GROUP_AUTHORIZATION_FAILED.code, response.data.errorCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsRequestsTopicAuthorizationFailed(): Unit = {
|
||||
|
||||
def buildExpectedActionsTopic(topic: String): util.List[Action] = {
|
||||
val pattern = new ResourcePattern(ResourceType.TOPIC, topic, PatternType.LITERAL)
|
||||
val action = new Action(AclOperation.READ, pattern, 1, true, true)
|
||||
Collections.singletonList(action)
|
||||
}
|
||||
|
||||
def buildExpectedActionsGroup(topic: String): util.List[Action] = {
|
||||
val pattern = new ResourcePattern(ResourceType.GROUP, topic, PatternType.LITERAL)
|
||||
val action = new Action(AclOperation.DELETE, pattern, 1, true, true)
|
||||
Collections.singletonList(action)
|
||||
}
|
||||
|
||||
val groupId = "group"
|
||||
|
||||
val topicName1 = "topic-1"
|
||||
val topicId1 = Uuid.randomUuid
|
||||
val topicName2 = "topic-2"
|
||||
val topicId2 = Uuid.randomUuid
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
addTopicToMetadataCache(topicName1, 2, topicId = topicId1)
|
||||
addTopicToMetadataCache(topicName2, 2, topicId = topicId2)
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic1 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName1)
|
||||
.setPartitions(util.List.of(0, 1))
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic2 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName2)
|
||||
.setPartitions(util.List.of(0, 1))
|
||||
|
||||
val deleteShareGroupOffsetsRequestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId(groupId)
|
||||
.setTopics(util.List.of(deleteShareGroupOffsetsRequestTopic1, deleteShareGroupOffsetsRequestTopic2))
|
||||
|
||||
val deleteShareGroupOffsetsGroupCoordinatorRequestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId(groupId)
|
||||
.setTopics(util.List.of(deleteShareGroupOffsetsRequestTopic2))
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequestData, true).build)
|
||||
|
||||
val resultFuture = new CompletableFuture[DeleteShareGroupOffsetsResponseData]
|
||||
when(groupCoordinator.deleteShareGroupOffsets(
|
||||
requestChannelRequest.context,
|
||||
deleteShareGroupOffsetsGroupCoordinatorRequestData
|
||||
)).thenReturn(resultFuture)
|
||||
|
||||
val authorizer: Authorizer = mock(classOf[Authorizer])
|
||||
when(authorizer.authorize(any[RequestContext], ArgumentMatchers.eq(buildExpectedActionsGroup(groupId))))
|
||||
.thenReturn(util.List.of(AuthorizationResult.ALLOWED))
|
||||
when(authorizer.authorize(any[RequestContext], ArgumentMatchers.eq(buildExpectedActionsTopic(topicName1))))
|
||||
.thenReturn(util.List.of(AuthorizationResult.DENIED))
|
||||
when(authorizer.authorize(any[RequestContext], ArgumentMatchers.eq(buildExpectedActionsTopic(topicName2))))
|
||||
.thenReturn(util.List.of(AuthorizationResult.ALLOWED))
|
||||
|
||||
kafkaApis = createKafkaApis(
|
||||
overrideProperties = Map(ShareGroupConfig.SHARE_GROUP_ENABLE_CONFIG -> "true"),
|
||||
authorizer = Some(authorizer)
|
||||
)
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val deleteShareGroupOffsetsResponseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
.setResponses(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topicName2)
|
||||
.setTopicId(topicId2)
|
||||
.setPartitions(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(0)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code()),
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(1)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
))
|
||||
))
|
||||
|
||||
val expectedResponseTopics: util.List[DeleteShareGroupOffsetsResponseTopic] = new util.ArrayList[DeleteShareGroupOffsetsResponseTopic]()
|
||||
|
||||
expectedResponseTopics.add(
|
||||
new DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topicName1)
|
||||
.setPartitions(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(0)
|
||||
.setErrorCode(Errors.TOPIC_AUTHORIZATION_FAILED.code)
|
||||
.setErrorMessage(Errors.TOPIC_AUTHORIZATION_FAILED.message()),
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(1)
|
||||
.setErrorCode(Errors.TOPIC_AUTHORIZATION_FAILED.code)
|
||||
.setErrorMessage(Errors.TOPIC_AUTHORIZATION_FAILED.message())
|
||||
))
|
||||
)
|
||||
|
||||
deleteShareGroupOffsetsResponseData.responses.forEach{ topic => {
|
||||
expectedResponseTopics.add(topic)
|
||||
}}
|
||||
|
||||
val expectedResponseData: DeleteShareGroupOffsetsResponseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
.setErrorMessage(null)
|
||||
.setResponses(expectedResponseTopics)
|
||||
|
||||
resultFuture.complete(deleteShareGroupOffsetsResponseData)
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
assertEquals(expectedResponseData, response.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsRequestSuccess(): Unit = {
|
||||
val topicName1 = "topic-1"
|
||||
val topicId1 = Uuid.randomUuid
|
||||
val topicName2 = "topic-2"
|
||||
val topicId2 = Uuid.randomUuid
|
||||
val topicName3 = "topic-3"
|
||||
val topicId3 = Uuid.randomUuid
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
addTopicToMetadataCache(topicName1, 1, topicId = topicId1)
|
||||
addTopicToMetadataCache(topicName2, 2, topicId = topicId2)
|
||||
addTopicToMetadataCache(topicName3, 3, topicId = topicId3)
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic1 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName1)
|
||||
.setPartitions(util.List.of(0))
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic2 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName2)
|
||||
.setPartitions(util.List.of(0, 1))
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic3 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName3)
|
||||
.setPartitions(util.List.of(0, 1, 2))
|
||||
|
||||
val deleteShareGroupOffsetsRequestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("group")
|
||||
.setTopics(util.List.of(deleteShareGroupOffsetsRequestTopic1, deleteShareGroupOffsetsRequestTopic2, deleteShareGroupOffsetsRequestTopic3))
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequestData, true).build)
|
||||
|
||||
val resultFuture = new CompletableFuture[DeleteShareGroupOffsetsResponseData]
|
||||
when(groupCoordinator.deleteShareGroupOffsets(
|
||||
requestChannelRequest.context,
|
||||
deleteShareGroupOffsetsRequestData
|
||||
)).thenReturn(resultFuture)
|
||||
|
||||
kafkaApis = createKafkaApis(
|
||||
overrideProperties = Map(ShareGroupConfig.SHARE_GROUP_ENABLE_CONFIG -> "true"),
|
||||
)
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val deleteShareGroupOffsetsResponseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
.setResponses(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topicName1)
|
||||
.setTopicId(topicId1)
|
||||
.setPartitions(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(0)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
)),
|
||||
new DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topicName2)
|
||||
.setTopicId(topicId2)
|
||||
.setPartitions(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(0)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code()),
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(1)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
)),
|
||||
new DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topicName3)
|
||||
.setTopicId(topicId3)
|
||||
.setPartitions(util.List.of(
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(0)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code()),
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(1)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code()),
|
||||
new DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(2)
|
||||
.setErrorMessage(null)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
))
|
||||
))
|
||||
|
||||
resultFuture.complete(deleteShareGroupOffsetsResponseData)
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
assertEquals(deleteShareGroupOffsetsResponseData, response.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsRequestGroupCoordinatorThrowsError(): Unit = {
|
||||
val topicName1 = "topic-1"
|
||||
val topicId1 = Uuid.randomUuid
|
||||
val topicName2 = "topic-2"
|
||||
val topicId2 = Uuid.randomUuid
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
addTopicToMetadataCache(topicName1, 1, topicId = topicId1)
|
||||
addTopicToMetadataCache(topicName2, 2, topicId = topicId2)
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic1 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName1)
|
||||
.setPartitions(util.List.of(0))
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic2 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName2)
|
||||
.setPartitions(util.List.of(0, 1))
|
||||
|
||||
val deleteShareGroupOffsetsRequestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("group")
|
||||
.setTopics(util.List.of(deleteShareGroupOffsetsRequestTopic1, deleteShareGroupOffsetsRequestTopic2))
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequestData, true).build)
|
||||
|
||||
when(groupCoordinator.deleteShareGroupOffsets(
|
||||
requestChannelRequest.context,
|
||||
deleteShareGroupOffsetsRequestData
|
||||
)).thenReturn(CompletableFuture.failedFuture(Errors.UNKNOWN_SERVER_ERROR.exception))
|
||||
|
||||
kafkaApis = createKafkaApis(
|
||||
overrideProperties = Map(ShareGroupConfig.SHARE_GROUP_ENABLE_CONFIG -> "true"),
|
||||
)
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val deleteShareGroupOffsetsResponseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorMessage(Errors.UNKNOWN_SERVER_ERROR.message())
|
||||
.setErrorCode(Errors.UNKNOWN_SERVER_ERROR.code())
|
||||
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
assertEquals(deleteShareGroupOffsetsResponseData, response.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsRequestGroupCoordinatorErrorResponse(): Unit = {
|
||||
val topicName1 = "topic-1"
|
||||
val topicId1 = Uuid.randomUuid
|
||||
val topicName2 = "topic-2"
|
||||
val topicId2 = Uuid.randomUuid
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
addTopicToMetadataCache(topicName1, 1, topicId = topicId1)
|
||||
addTopicToMetadataCache(topicName2, 2, topicId = topicId2)
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic1 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName1)
|
||||
.setPartitions(util.List.of(0))
|
||||
|
||||
val deleteShareGroupOffsetsRequestTopic2 = new DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(topicName2)
|
||||
.setPartitions(util.List.of(0, 1))
|
||||
|
||||
val deleteShareGroupOffsetsRequestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("group")
|
||||
.setTopics(util.List.of(deleteShareGroupOffsetsRequestTopic1, deleteShareGroupOffsetsRequestTopic2))
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequestData, true).build)
|
||||
|
||||
val groupCoordinatorResponse: DeleteShareGroupOffsetsResponseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.UNKNOWN_SERVER_ERROR.code())
|
||||
.setErrorMessage(Errors.UNKNOWN_SERVER_ERROR.message())
|
||||
|
||||
when(groupCoordinator.deleteShareGroupOffsets(
|
||||
requestChannelRequest.context,
|
||||
deleteShareGroupOffsetsRequestData
|
||||
)).thenReturn(CompletableFuture.completedFuture(groupCoordinatorResponse))
|
||||
|
||||
kafkaApis = createKafkaApis(
|
||||
overrideProperties = Map(ShareGroupConfig.SHARE_GROUP_ENABLE_CONFIG -> "true"),
|
||||
)
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val deleteShareGroupOffsetsResponseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorMessage(Errors.UNKNOWN_SERVER_ERROR.message())
|
||||
.setErrorCode(Errors.UNKNOWN_SERVER_ERROR.code())
|
||||
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
assertEquals(deleteShareGroupOffsetsResponseData, response.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDeleteShareGroupOffsetsRequestEmptyTopicsSuccess(): Unit = {
|
||||
metadataCache = new KRaftMetadataCache(brokerId, () => KRaftVersion.KRAFT_VERSION_0)
|
||||
|
||||
val deleteShareGroupOffsetsRequest = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("group")
|
||||
|
||||
val requestChannelRequest = buildRequest(new DeleteShareGroupOffsetsRequest.Builder(deleteShareGroupOffsetsRequest, true).build)
|
||||
|
||||
val resultFuture = new CompletableFuture[DeleteShareGroupOffsetsResponseData]
|
||||
kafkaApis = createKafkaApis(
|
||||
overrideProperties = Map(ShareGroupConfig.SHARE_GROUP_ENABLE_CONFIG -> "true"),
|
||||
)
|
||||
kafkaApis.handle(requestChannelRequest, RequestLocal.noCaching)
|
||||
|
||||
val deleteShareGroupOffsetsResponse = new DeleteShareGroupOffsetsResponseData()
|
||||
|
||||
resultFuture.complete(deleteShareGroupOffsetsResponse)
|
||||
val response = verifyNoThrottling[DeleteShareGroupOffsetsResponse](requestChannelRequest)
|
||||
assertEquals(deleteShareGroupOffsetsResponse, response.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testWriteShareGroupStateSuccess(): Unit = {
|
||||
val topicId = Uuid.randomUuid();
|
||||
|
|
|
@ -21,6 +21,8 @@ import org.apache.kafka.common.message.ConsumerGroupDescribeResponseData;
|
|||
import org.apache.kafka.common.message.ConsumerGroupHeartbeatRequestData;
|
||||
import org.apache.kafka.common.message.ConsumerGroupHeartbeatResponseData;
|
||||
import org.apache.kafka.common.message.DeleteGroupsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsResponseData;
|
||||
import org.apache.kafka.common.message.DescribeGroupsResponseData;
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsResponseData;
|
||||
|
@ -311,6 +313,20 @@ public interface GroupCoordinator {
|
|||
DescribeShareGroupOffsetsRequestData.DescribeShareGroupOffsetsRequestGroup request
|
||||
);
|
||||
|
||||
/**
|
||||
* Delete the Share Group Offsets for a given group.
|
||||
*
|
||||
* @param context The request context
|
||||
* @param request The DeleteShareGroupOffsetsRequestGroup request.
|
||||
*
|
||||
* @return A future yielding the results.
|
||||
* The error codes of the response are set to indicate the errors occurred during the execution.
|
||||
*/
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> deleteShareGroupOffsets(
|
||||
RequestContext context,
|
||||
DeleteShareGroupOffsetsRequestData request
|
||||
);
|
||||
|
||||
/**
|
||||
* Commit offsets for a given Group.
|
||||
*
|
||||
|
|
|
@ -26,6 +26,9 @@ import org.apache.kafka.common.message.ConsumerGroupDescribeResponseData;
|
|||
import org.apache.kafka.common.message.ConsumerGroupHeartbeatRequestData;
|
||||
import org.apache.kafka.common.message.ConsumerGroupHeartbeatResponseData;
|
||||
import org.apache.kafka.common.message.DeleteGroupsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupStateRequestData;
|
||||
import org.apache.kafka.common.message.DescribeGroupsResponseData;
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsResponseData;
|
||||
|
@ -59,6 +62,7 @@ import org.apache.kafka.common.protocol.Errors;
|
|||
import org.apache.kafka.common.requests.ApiError;
|
||||
import org.apache.kafka.common.requests.ConsumerGroupDescribeRequest;
|
||||
import org.apache.kafka.common.requests.DeleteGroupsRequest;
|
||||
import org.apache.kafka.common.requests.DeleteShareGroupOffsetsRequest;
|
||||
import org.apache.kafka.common.requests.DescribeGroupsRequest;
|
||||
import org.apache.kafka.common.requests.DescribeShareGroupOffsetsRequest;
|
||||
import org.apache.kafka.common.requests.OffsetCommitRequest;
|
||||
|
@ -932,7 +936,8 @@ public class GroupCoordinatorService implements GroupCoordinator {
|
|||
@Override
|
||||
public CompletableFuture<List<DescribedGroup>> shareGroupDescribe(
|
||||
RequestContext context,
|
||||
List<String> groupIds) {
|
||||
List<String> groupIds
|
||||
) {
|
||||
if (!isActive.get()) {
|
||||
return CompletableFuture.completedFuture(ShareGroupDescribeRequest.getErrorDescribedGroupList(
|
||||
groupIds,
|
||||
|
@ -1243,6 +1248,48 @@ public class GroupCoordinatorService implements GroupCoordinator {
|
|||
});
|
||||
}
|
||||
|
||||
private void populateDeleteShareGroupOffsetsFuture(
|
||||
DeleteShareGroupOffsetsRequestData requestData,
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future,
|
||||
Map<Uuid, String> requestTopicIdToNameMapping,
|
||||
List<DeleteShareGroupStateRequestData.DeleteStateData> deleteShareGroupStateRequestTopicsData,
|
||||
List<DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic> deleteShareGroupOffsetsResponseTopicList
|
||||
|
||||
) {
|
||||
DeleteShareGroupStateRequestData deleteShareGroupStateRequestData = new DeleteShareGroupStateRequestData()
|
||||
.setGroupId(requestData.groupId())
|
||||
.setTopics(deleteShareGroupStateRequestTopicsData);
|
||||
|
||||
persister.deleteState(DeleteShareGroupStateParameters.from(deleteShareGroupStateRequestData))
|
||||
.whenComplete((result, error) -> {
|
||||
if (error != null) {
|
||||
log.error("Failed to delete share group state");
|
||||
future.completeExceptionally(error);
|
||||
return;
|
||||
}
|
||||
if (result == null || result.topicsData() == null) {
|
||||
log.error("Result is null for the delete share group state");
|
||||
future.completeExceptionally(new IllegalStateException("Result is null for the delete share group state"));
|
||||
return;
|
||||
}
|
||||
result.topicsData().forEach(topicData ->
|
||||
deleteShareGroupOffsetsResponseTopicList.add(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicId(topicData.topicId())
|
||||
.setTopicName(requestTopicIdToNameMapping.get(topicData.topicId()))
|
||||
.setPartitions(topicData.partitions().stream().map(
|
||||
partitionData -> new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partitionData.partition())
|
||||
.setErrorMessage(partitionData.errorCode() == Errors.NONE.code() ? null : Errors.forCode(partitionData.errorCode()).message())
|
||||
.setErrorCode(partitionData.errorCode())
|
||||
).toList())
|
||||
));
|
||||
|
||||
future.complete(
|
||||
new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(deleteShareGroupOffsetsResponseTopicList));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link GroupCoordinator#fetchOffsets(RequestContext, OffsetFetchRequestData.OffsetFetchRequestGroup, boolean)}.
|
||||
*/
|
||||
|
@ -1508,6 +1555,110 @@ public class GroupCoordinatorService implements GroupCoordinator {
|
|||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link GroupCoordinator#deleteShareGroupOffsets(RequestContext, DeleteShareGroupOffsetsRequestData)}.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<DeleteShareGroupOffsetsResponseData> deleteShareGroupOffsets(
|
||||
RequestContext context,
|
||||
DeleteShareGroupOffsetsRequestData requestData
|
||||
) {
|
||||
if (!isActive.get()) {
|
||||
return CompletableFuture.completedFuture(
|
||||
DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(Errors.COORDINATOR_NOT_AVAILABLE));
|
||||
}
|
||||
|
||||
if (metadataImage == null) {
|
||||
return CompletableFuture.completedFuture(
|
||||
DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(Errors.COORDINATOR_NOT_AVAILABLE));
|
||||
}
|
||||
|
||||
String groupId = requestData.groupId();
|
||||
|
||||
if (!isGroupIdNotEmpty(groupId)) {
|
||||
return CompletableFuture.completedFuture(
|
||||
DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(Errors.INVALID_GROUP_ID));
|
||||
}
|
||||
|
||||
Map<Uuid, String> requestTopicIdToNameMapping = new HashMap<>();
|
||||
List<DeleteShareGroupStateRequestData.DeleteStateData> deleteShareGroupStateRequestTopicsData = new ArrayList<>(requestData.topics().size());
|
||||
List<DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic> deleteShareGroupOffsetsResponseTopicList = new ArrayList<>(requestData.topics().size());
|
||||
|
||||
requestData.topics().forEach(topic -> {
|
||||
Uuid topicId = metadataImage.topics().topicNameToIdView().get(topic.topicName());
|
||||
if (topicId != null) {
|
||||
requestTopicIdToNameMapping.put(topicId, topic.topicName());
|
||||
deleteShareGroupStateRequestTopicsData.add(new DeleteShareGroupStateRequestData.DeleteStateData()
|
||||
.setTopicId(topicId)
|
||||
.setPartitions(
|
||||
topic.partitions().stream().map(
|
||||
partitionIndex -> new DeleteShareGroupStateRequestData.PartitionData().setPartition(partitionIndex)
|
||||
).toList()
|
||||
));
|
||||
} else {
|
||||
deleteShareGroupOffsetsResponseTopicList.add(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(topic.topicName())
|
||||
.setPartitions(topic.partitions().stream().map(
|
||||
partition -> new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partition)
|
||||
.setErrorCode(Errors.UNKNOWN_TOPIC_OR_PARTITION.code())
|
||||
.setErrorMessage(Errors.UNKNOWN_TOPIC_OR_PARTITION.message())
|
||||
).toList()));
|
||||
}
|
||||
});
|
||||
|
||||
// If the request for the persister is empty, just complete the operation right away.
|
||||
if (deleteShareGroupStateRequestTopicsData.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(
|
||||
new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(deleteShareGroupOffsetsResponseTopicList));
|
||||
}
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future = new CompletableFuture<>();
|
||||
|
||||
TopicPartition topicPartition = topicPartitionFor(groupId);
|
||||
|
||||
// This is done to make sure the provided group is empty. Offsets can be deleted only for an empty share group.
|
||||
CompletableFuture<List<ShareGroupDescribeResponseData.DescribedGroup>> describeGroupFuture =
|
||||
runtime.scheduleReadOperation(
|
||||
"share-group-describe",
|
||||
topicPartition,
|
||||
(coordinator, lastCommittedOffset) -> coordinator.shareGroupDescribe(List.of(groupId), lastCommittedOffset)
|
||||
).exceptionally(exception -> handleOperationException(
|
||||
"share-group-describe",
|
||||
List.of(groupId),
|
||||
exception,
|
||||
(error, __) -> ShareGroupDescribeRequest.getErrorDescribedGroupList(List.of(groupId), error),
|
||||
log
|
||||
));
|
||||
|
||||
describeGroupFuture.whenComplete((groups, throwable) -> {
|
||||
if (throwable != null) {
|
||||
log.error("Failed to describe the share group {}", groupId, throwable);
|
||||
future.complete(DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(Errors.forException(throwable)));
|
||||
} else if (groups == null || groups.isEmpty()) {
|
||||
log.error("Describe share group resulted in empty response for group {}", groupId);
|
||||
future.complete(DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(Errors.GROUP_ID_NOT_FOUND));
|
||||
} else if (groups.get(0).errorCode() != Errors.NONE.code()) {
|
||||
log.error("Failed to describe the share group {}", groupId);
|
||||
future.complete(DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(groups.get(0).errorCode(), groups.get(0).errorMessage()));
|
||||
} else if (groups.get(0).members() != null && !groups.get(0).members().isEmpty()) {
|
||||
log.error("Provided group {} is not empty", groupId);
|
||||
future.complete(DeleteShareGroupOffsetsRequest.getErrorDeleteResponseData(Errors.NON_EMPTY_GROUP));
|
||||
} else {
|
||||
populateDeleteShareGroupOffsetsFuture(
|
||||
requestData,
|
||||
future,
|
||||
requestTopicIdToNameMapping,
|
||||
deleteShareGroupStateRequestTopicsData,
|
||||
deleteShareGroupOffsetsResponseTopicList
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link GroupCoordinator#commitOffsets(RequestContext, OffsetCommitRequestData, BufferSupplier)}.
|
||||
*/
|
||||
|
|
|
@ -42,6 +42,10 @@ import org.apache.kafka.common.message.ConsumerGroupDescribeResponseData;
|
|||
import org.apache.kafka.common.message.ConsumerGroupHeartbeatRequestData;
|
||||
import org.apache.kafka.common.message.ConsumerGroupHeartbeatResponseData;
|
||||
import org.apache.kafka.common.message.DeleteGroupsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupOffsetsResponseData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupStateRequestData;
|
||||
import org.apache.kafka.common.message.DeleteShareGroupStateResponseData;
|
||||
import org.apache.kafka.common.message.DescribeGroupsResponseData;
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsRequestData;
|
||||
import org.apache.kafka.common.message.DescribeShareGroupOffsetsResponseData;
|
||||
|
@ -117,6 +121,7 @@ import org.mockito.ArgumentMatchers;
|
|||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
@ -2789,6 +2794,7 @@ public class GroupCoordinatorServiceTest {
|
|||
List.of(new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id")
|
||||
.setErrorCode(Errors.COORDINATOR_LOAD_IN_PROGRESS.code())
|
||||
.setErrorMessage(Errors.COORDINATOR_LOAD_IN_PROGRESS.message())
|
||||
),
|
||||
future.get()
|
||||
);
|
||||
|
@ -2816,6 +2822,7 @@ public class GroupCoordinatorServiceTest {
|
|||
List.of(new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id")
|
||||
.setErrorCode(Errors.COORDINATOR_NOT_AVAILABLE.code())
|
||||
.setErrorMessage(Errors.COORDINATOR_NOT_AVAILABLE.message())
|
||||
),
|
||||
future.get()
|
||||
);
|
||||
|
@ -3282,6 +3289,531 @@ public class GroupCoordinatorServiceTest {
|
|||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithNoOpPersister() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(
|
||||
List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setTopicId(TOPIC_ID)
|
||||
.setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partition)
|
||||
.setErrorCode(PartitionFactory.DEFAULT_ERROR_CODE)
|
||||
.setErrorMessage(null))))
|
||||
);
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id-1");
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DESCRIBE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithDefaultPersister() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupStateRequestData deleteShareGroupStateRequestData = new DeleteShareGroupStateRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupStateRequestData.DeleteStateData()
|
||||
.setTopicId(TOPIC_ID)
|
||||
.setPartitions(List.of(new DeleteShareGroupStateRequestData.PartitionData()
|
||||
.setPartition(partition)))));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(
|
||||
List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setTopicId(TOPIC_ID)
|
||||
.setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partition)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
.setErrorMessage(null))))
|
||||
);
|
||||
|
||||
DeleteShareGroupStateResponseData deleteShareGroupStateResponseData = new DeleteShareGroupStateResponseData()
|
||||
.setResults(
|
||||
List.of(new DeleteShareGroupStateResponseData.DeleteStateResult()
|
||||
.setTopicId(TOPIC_ID)
|
||||
.setPartitions(List.of(new DeleteShareGroupStateResponseData.PartitionResult()
|
||||
.setPartition(partition)
|
||||
.setErrorCode(Errors.NONE.code())
|
||||
.setErrorMessage(null)))
|
||||
)
|
||||
);
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id-1");
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
DeleteShareGroupStateParameters deleteShareGroupStateParameters = DeleteShareGroupStateParameters.from(deleteShareGroupStateRequestData);
|
||||
DeleteShareGroupStateResult deleteShareGroupStateResult = DeleteShareGroupStateResult.from(deleteShareGroupStateResponseData);
|
||||
when(persister.deleteState(
|
||||
ArgumentMatchers.eq(deleteShareGroupStateParameters)
|
||||
)).thenReturn(CompletableFuture.completedFuture(deleteShareGroupStateResult));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsNonexistentTopicWithDefaultPersister() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName("badtopic")
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setResponses(
|
||||
List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponseTopic()
|
||||
.setTopicName("badtopic")
|
||||
.setPartitions(List.of(new DeleteShareGroupOffsetsResponseData.DeleteShareGroupOffsetsResponsePartition()
|
||||
.setPartitionIndex(partition)
|
||||
.setErrorCode(Errors.UNKNOWN_TOPIC_OR_PARTITION.code())
|
||||
.setErrorMessage(Errors.UNKNOWN_TOPIC_OR_PARTITION.message()))))
|
||||
);
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithDefaultPersisterThrowsError() {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id-1");
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
when(persister.deleteState(ArgumentMatchers.any()))
|
||||
.thenReturn(CompletableFuture.failedFuture(new Exception("Unable to validate delete share group state request")));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
assertFutureThrows(Exception.class, future, "Unable to validate delete share group state request");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithDefaultPersisterNullResult() {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id-1");
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
when(persister.deleteState(ArgumentMatchers.any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
assertFutureThrows(IllegalStateException.class, future, "Result is null for the delete share group state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsWithDefaultPersisterNullTopicData() {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id-1");
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
DeleteShareGroupStateResult deleteShareGroupStateResult =
|
||||
new DeleteShareGroupStateResult.Builder().setTopicsData(null).build();
|
||||
|
||||
when(persister.deleteState(ArgumentMatchers.any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(deleteShareGroupStateResult));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
assertFutureThrows(IllegalStateException.class, future, "Result is null for the delete share group state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsCoordinatorNotActive() throws ExecutionException, InterruptedException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.build();
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.COORDINATOR_NOT_AVAILABLE.code())
|
||||
.setErrorMessage(Errors.COORDINATOR_NOT_AVAILABLE.message());
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsMetadataImageNull() throws ExecutionException, InterruptedException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.build(true);
|
||||
|
||||
// Forcing a null Metadata Image
|
||||
service.onNewMetadataImage(null, null);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.COORDINATOR_NOT_AVAILABLE.code())
|
||||
.setErrorMessage(Errors.COORDINATOR_NOT_AVAILABLE.message());
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsInvalidGroupId() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.INVALID_GROUP_ID.code())
|
||||
.setErrorMessage(Errors.INVALID_GROUP_ID.message());
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsDescribeThrowsError() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.UNKNOWN_SERVER_ERROR.code())
|
||||
.setErrorMessage(Errors.UNKNOWN_SERVER_ERROR.message());
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(FutureUtils.failedFuture(Errors.UNKNOWN_SERVER_ERROR.exception()));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsDescribeReturnsNull() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.GROUP_ID_NOT_FOUND.code())
|
||||
.setErrorMessage(Errors.GROUP_ID_NOT_FOUND.message());
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsDescribeReturnsEmpty() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.GROUP_ID_NOT_FOUND.code())
|
||||
.setErrorMessage(Errors.GROUP_ID_NOT_FOUND.message());
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(Collections.emptyList()));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsDescribeReturnsError() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.GROUP_ID_NOT_FOUND.code())
|
||||
.setErrorMessage(Errors.GROUP_ID_NOT_FOUND.message());
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setErrorCode(Errors.GROUP_ID_NOT_FOUND.code())
|
||||
.setErrorMessage(Errors.GROUP_ID_NOT_FOUND.message());
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShareGroupOffsetsGroupIsNotEmpty() throws InterruptedException, ExecutionException {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
Persister persister = mock(DefaultStatePersister.class);
|
||||
GroupCoordinatorService service = new GroupCoordinatorServiceBuilder()
|
||||
.setConfig(createConfig())
|
||||
.setRuntime(runtime)
|
||||
.setPersister(persister)
|
||||
.build(true);
|
||||
service.startup(() -> 1);
|
||||
|
||||
int partition = 1;
|
||||
DeleteShareGroupOffsetsRequestData requestData = new DeleteShareGroupOffsetsRequestData()
|
||||
.setGroupId("share-group-id")
|
||||
.setTopics(List.of(new DeleteShareGroupOffsetsRequestData.DeleteShareGroupOffsetsRequestTopic()
|
||||
.setTopicName(TOPIC_NAME)
|
||||
.setPartitions(List.of(partition))
|
||||
));
|
||||
|
||||
DeleteShareGroupOffsetsResponseData responseData = new DeleteShareGroupOffsetsResponseData()
|
||||
.setErrorCode(Errors.NON_EMPTY_GROUP.code())
|
||||
.setErrorMessage(Errors.NON_EMPTY_GROUP.message());
|
||||
|
||||
ShareGroupDescribeResponseData.DescribedGroup describedGroup = new ShareGroupDescribeResponseData.DescribedGroup()
|
||||
.setGroupId("share-group-id-1")
|
||||
.setMembers(List.of(new ShareGroupDescribeResponseData.Member()));
|
||||
|
||||
when(runtime.scheduleReadOperation(
|
||||
ArgumentMatchers.eq("share-group-describe"),
|
||||
ArgumentMatchers.eq(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, 0)),
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(CompletableFuture.completedFuture(List.of(describedGroup)));
|
||||
|
||||
CompletableFuture<DeleteShareGroupOffsetsResponseData> future =
|
||||
service.deleteShareGroupOffsets(requestContext(ApiKeys.DELETE_SHARE_GROUP_OFFSETS), requestData);
|
||||
|
||||
assertEquals(responseData, future.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersisterInitializeSuccess() {
|
||||
CoordinatorRuntime<GroupCoordinatorShard, CoordinatorRecord> runtime = mockRuntime();
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.kafka.server.share.persister;
|
|||
import org.apache.kafka.common.message.DeleteShareGroupStateRequestData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
@ -64,4 +65,16 @@ public class DeleteShareGroupStateParameters implements PersisterParameters {
|
|||
return new DeleteShareGroupStateParameters(groupTopicPartitionData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DeleteShareGroupStateParameters that = (DeleteShareGroupStateParameters) o;
|
||||
return Objects.equals(groupTopicPartitionData, that.groupTopicPartitionData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(groupTopicPartitionData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ import org.apache.kafka.clients.admin.DeleteConsumerGroupsOptions;
|
|||
import org.apache.kafka.clients.admin.DeleteConsumerGroupsResult;
|
||||
import org.apache.kafka.clients.admin.DeleteRecordsOptions;
|
||||
import org.apache.kafka.clients.admin.DeleteRecordsResult;
|
||||
import org.apache.kafka.clients.admin.DeleteShareGroupOffsetsOptions;
|
||||
import org.apache.kafka.clients.admin.DeleteShareGroupOffsetsResult;
|
||||
import org.apache.kafka.clients.admin.DeleteShareGroupsOptions;
|
||||
import org.apache.kafka.clients.admin.DeleteShareGroupsResult;
|
||||
import org.apache.kafka.clients.admin.DeleteStreamsGroupOffsetsOptions;
|
||||
|
@ -473,6 +475,11 @@ public class TestingMetricsInterceptingAdminClient extends AdminClient {
|
|||
return adminDelegate.listShareGroupOffsets(groupSpecs, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteShareGroupOffsetsResult deleteShareGroupOffsets(final String groupId, final Set<TopicPartition> partitions, final DeleteShareGroupOffsetsOptions options) {
|
||||
return adminDelegate.deleteShareGroupOffsets(groupId, partitions, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteShareGroupsResult deleteShareGroups(final Collection<String> groupIds, final DeleteShareGroupsOptions options) {
|
||||
return adminDelegate.deleteShareGroups(groupIds, options);
|
||||
|
|
Loading…
Reference in New Issue