-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature delete consumergroups #2040
Changes from all commits
f31b57c
a91e8dd
64253aa
4b76d41
804faf4
16cd36a
792cf2b
fa8af0c
11d8020
12f0a00
52bde48
2f678ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,9 @@ | |
from kafka.metrics import MetricConfig, Metrics | ||
from kafka.protocol.admin import ( | ||
CreateTopicsRequest, DeleteTopicsRequest, DescribeConfigsRequest, AlterConfigsRequest, CreatePartitionsRequest, | ||
ListGroupsRequest, DescribeGroupsRequest, DescribeAclsRequest, CreateAclsRequest, DeleteAclsRequest) | ||
ListGroupsRequest, DescribeGroupsRequest, DescribeAclsRequest, CreateAclsRequest, DeleteAclsRequest, | ||
DeleteGroupsRequest | ||
) | ||
from kafka.protocol.commit import GroupCoordinatorRequest, OffsetFetchRequest | ||
from kafka.protocol.metadata import MetadataRequest | ||
from kafka.protocol.types import Array | ||
|
@@ -337,12 +339,34 @@ def _find_coordinator_id(self, group_id): | |
name as a string. | ||
:return: The node_id of the broker that is the coordinator. | ||
""" | ||
# Note: Java may change how this is implemented in KAFKA-6791. | ||
future = self._find_coordinator_id_send_request(group_id) | ||
self._wait_for_futures([future]) | ||
response = future.value | ||
return self._find_coordinator_id_process_response(response) | ||
|
||
def _find_many_coordinator_ids(self, group_ids): | ||
"""Find the broker node_id of the coordinator for each of the given groups. | ||
|
||
Sends a FindCoordinatorRequest message to the cluster for each group_id. | ||
Will block until the FindCoordinatorResponse is received for all groups. | ||
Any errors are immediately raised. | ||
|
||
:param group_ids: A list of consumer group IDs. This is typically the group | ||
name as a string. | ||
:return: A list of tuples (group_id, node_id) where node_id is the id | ||
of the broker that is the coordinator for the corresponding group. | ||
""" | ||
futures = { | ||
group_id: self._find_coordinator_id_send_request(group_id) | ||
for group_id in group_ids | ||
} | ||
self._wait_for_futures(list(futures.values())) | ||
groups_coordinators = [ | ||
(group_id, self._find_coordinator_id_process_response(f.value)) | ||
for group_id, f in futures.items() | ||
] | ||
return groups_coordinators | ||
|
||
def _send_request_to_node(self, node_id, request): | ||
"""Send a Kafka protocol message to a specific broker. | ||
|
||
|
@@ -1261,8 +1285,69 @@ def list_consumer_group_offsets(self, group_id, group_coordinator_id=None, | |
response = future.value | ||
return self._list_consumer_group_offsets_process_response(response) | ||
|
||
# delete groups protocol not yet implemented | ||
# Note: send the request to the group's coordinator. | ||
def delete_consumer_groups(self, group_ids, group_coordinator_id=None): | ||
"""Delete Consumer Group Offsets for given consumer groups. | ||
|
||
Note: | ||
This does not verify that the group ids actually exist and | ||
group_coordinator_id is the correct coordinator for all these groups. | ||
|
||
The result needs checking for potential errors. | ||
|
||
:param group_ids: The consumer group ids of the groups which are to be deleted. | ||
:param group_coordinator_id: The node_id of the broker which is the coordinator for | ||
all the groups. Use only if all groups are coordinated by the same broker. | ||
If set to None, will query the cluster to find the coordinator for every single group. | ||
Explicitly specifying this can be useful to prevent | ||
that extra network round trips if you already know the group | ||
coordinator. Default: None. | ||
:return: A list of tuples (group_id, KafkaError) | ||
""" | ||
if group_coordinator_id is not None: | ||
futures = [self._delete_consumer_groups_send_request(group_ids, group_coordinator_id)] | ||
else: | ||
groups_coordinators = defaultdict(list) | ||
for group_id, group_coordinator_id in self._find_many_coordinator_ids(group_ids): | ||
groups_coordinators[group_coordinator_id].append(group_id) | ||
futures = [ | ||
self._delete_consumer_groups_send_request(group_ids, group_coordinator_id) | ||
for group_coordinator_id, group_ids in groups_coordinators.items() | ||
] | ||
|
||
self._wait_for_futures(futures) | ||
|
||
results = [] | ||
for f in futures: | ||
results.extend(self._convert_delete_groups_response(f.value)) | ||
return results | ||
|
||
def _convert_delete_groups_response(self, response): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is outside the scope of this PR, but this client code is a little inconsistent currently between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I also thought about this one a while... In the end my reasoning was something like this: the |
||
if response.API_VERSION <= 1: | ||
results = [] | ||
for group_id, error_code in response.results: | ||
results.append((group_id, Errors.for_code(error_code))) | ||
return results | ||
else: | ||
raise NotImplementedError( | ||
"Support for DeleteGroupsResponse_v{} has not yet been added to KafkaAdminClient." | ||
.format(response.API_VERSION)) | ||
|
||
def _delete_consumer_groups_send_request(self, group_ids, group_coordinator_id): | ||
"""Send a DeleteGroups request to a broker. | ||
|
||
:param group_ids: The consumer group ids of the groups which are to be deleted. | ||
:param group_coordinator_id: The node_id of the broker which is the coordinator for | ||
all the groups. | ||
:return: A message future | ||
""" | ||
version = self._matching_api_version(DeleteGroupsRequest) | ||
if version <= 1: | ||
request = DeleteGroupsRequest[version](group_ids) | ||
else: | ||
raise NotImplementedError( | ||
"Support for DeleteGroupsRequest_v{} has not yet been added to KafkaAdminClient." | ||
.format(version)) | ||
return self._send_request_to_node(group_coordinator_id, request) | ||
|
||
def _wait_for_futures(self, futures): | ||
while not all(future.succeeded() for future in futures): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Java have this method? When I wrote a bunch of this code two years ago I don't recall seeing anything that handled multiple group_ids at once, but I haven't been spelunking in their code for a while...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know, this one is mostly copy pasted from the method above. I went through https://kafka.apache.org/protocol but it doesn't look like there is an API that can provide the coordinator id for more than one group. Not even the DescribeGroups API returns the coordinators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this more, I certainly think it'd be convenient to have this method... we could also use it within the
describe_consumer_groups()
method to emit all the group coordinator requests in parallel: #2124There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍