Skip to content

Commit 1886cac

Browse files
authored
Support ListOffsets v3 in consumer (#2501)
1 parent 1594e38 commit 1886cac

File tree

6 files changed

+107
-74
lines changed

6 files changed

+107
-74
lines changed

kafka/conn.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from kafka.oauth.abstract import AbstractTokenProvider
2727
from kafka.protocol.admin import SaslHandShakeRequest, DescribeAclsRequest, DescribeClientQuotasRequest
2828
from kafka.protocol.commit import OffsetFetchRequest
29-
from kafka.protocol.offset import OffsetRequest
29+
from kafka.protocol.list_offsets import ListOffsetsRequest
3030
from kafka.protocol.produce import ProduceRequest
3131
from kafka.protocol.metadata import MetadataRequest
3232
from kafka.protocol.fetch import FetchRequest
@@ -1202,7 +1202,7 @@ def _infer_broker_version_from_api_versions(self, api_versions):
12021202
((2, 5), DescribeAclsRequest[2]),
12031203
((2, 4), ProduceRequest[8]),
12041204
((2, 3), FetchRequest[11]),
1205-
((2, 2), OffsetRequest[5]),
1205+
((2, 2), ListOffsetsRequest[5]),
12061206
((2, 1), FetchRequest[10]),
12071207
((2, 0), FetchRequest[8]),
12081208
((1, 1), FetchRequest[7]),

kafka/consumer/fetcher.py

+27-16
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from kafka.future import Future
1414
from kafka.metrics.stats import Avg, Count, Max, Rate
1515
from kafka.protocol.fetch import FetchRequest
16-
from kafka.protocol.offset import (
17-
OffsetRequest, OffsetResetStrategy, UNKNOWN_OFFSET
16+
from kafka.protocol.list_offsets import (
17+
ListOffsetsRequest, OffsetResetStrategy, UNKNOWN_OFFSET
1818
)
1919
from kafka.record import MemoryRecords
2020
from kafka.serializer import Deserializer
@@ -272,7 +272,7 @@ def _retrieve_offsets(self, timestamps, timeout_ms=float("inf")):
272272
if not timestamps:
273273
return {}
274274

275-
future = self._send_offset_requests(timestamps)
275+
future = self._send_list_offsets_requests(timestamps)
276276
self._client.poll(future=future, timeout_ms=remaining_ms)
277277

278278
if future.succeeded():
@@ -519,7 +519,7 @@ def _deserialize(self, f, topic, bytes_):
519519
return f.deserialize(topic, bytes_)
520520
return f(bytes_)
521521

522-
def _send_offset_requests(self, timestamps):
522+
def _send_list_offsets_requests(self, timestamps):
523523
"""Fetch offsets for each partition in timestamps dict. This may send
524524
request to multiple nodes, based on who is Leader for partition.
525525
@@ -564,13 +564,13 @@ def on_fail(err):
564564
list_offsets_future.failure(err)
565565

566566
for node_id, timestamps in six.iteritems(timestamps_by_node):
567-
_f = self._send_offset_request(node_id, timestamps)
567+
_f = self._send_list_offsets_request(node_id, timestamps)
568568
_f.add_callback(on_success)
569569
_f.add_errback(on_fail)
570570
return list_offsets_future
571571

572-
def _send_offset_request(self, node_id, timestamps):
573-
version = self._client.api_version(OffsetRequest, max_version=1)
572+
def _send_list_offsets_request(self, node_id, timestamps):
573+
version = self._client.api_version(ListOffsetsRequest, max_version=3)
574574
by_topic = collections.defaultdict(list)
575575
for tp, timestamp in six.iteritems(timestamps):
576576
if version >= 1:
@@ -579,28 +579,39 @@ def _send_offset_request(self, node_id, timestamps):
579579
data = (tp.partition, timestamp, 1)
580580
by_topic[tp.topic].append(data)
581581

582-
request = OffsetRequest[version](-1, list(six.iteritems(by_topic)))
582+
if version <= 1:
583+
request = ListOffsetsRequest[version](
584+
-1,
585+
list(six.iteritems(by_topic)))
586+
else:
587+
request = ListOffsetsRequest[version](
588+
-1,
589+
self._isolation_level,
590+
list(six.iteritems(by_topic)))
591+
583592

584593
# Client returns a future that only fails on network issues
585594
# so create a separate future and attach a callback to update it
586595
# based on response error codes
587596
future = Future()
588597

589598
_f = self._client.send(node_id, request)
590-
_f.add_callback(self._handle_offset_response, future)
599+
_f.add_callback(self._handle_list_offsets_response, future)
591600
_f.add_errback(lambda e: future.failure(e))
592601
return future
593602

594-
def _handle_offset_response(self, future, response):
595-
"""Callback for the response of the list offset call above.
603+
def _handle_list_offsets_response(self, future, response):
604+
"""Callback for the response of the ListOffsets api call
596605
597606
Arguments:
598607
future (Future): the future to update based on response
599-
response (OffsetResponse): response from the server
608+
response (ListOffsetsResponse): response from the server
600609
601610
Raises:
602611
AssertionError: if response does not match partition
603612
"""
613+
if response.API_VERSION >= 2 and response.throttle_time_ms > 0:
614+
log.warning("ListOffsetsRequest throttled by broker (%d ms)", response.throttle_time_ms)
604615
timestamp_offset_map = {}
605616
for topic, part_data in response.topics:
606617
for partition_info in part_data:
@@ -610,18 +621,18 @@ def _handle_offset_response(self, future, response):
610621
if error_type is Errors.NoError:
611622
if response.API_VERSION == 0:
612623
offsets = partition_info[2]
613-
assert len(offsets) <= 1, 'Expected OffsetResponse with one offset'
624+
assert len(offsets) <= 1, 'Expected ListOffsetsResponse with one offset'
614625
if not offsets:
615626
offset = UNKNOWN_OFFSET
616627
else:
617628
offset = offsets[0]
618-
log.debug("Handling v0 ListOffsetResponse response for %s. "
629+
log.debug("Handling v0 ListOffsetsResponse response for %s. "
619630
"Fetched offset %s", partition, offset)
620631
if offset != UNKNOWN_OFFSET:
621632
timestamp_offset_map[partition] = (offset, None)
622633
else:
623634
timestamp, offset = partition_info[2:]
624-
log.debug("Handling ListOffsetResponse response for %s. "
635+
log.debug("Handling ListOffsetsResponse response for %s. "
625636
"Fetched offset %s, timestamp %s",
626637
partition, offset, timestamp)
627638
if offset != UNKNOWN_OFFSET:
@@ -638,7 +649,7 @@ def _handle_offset_response(self, future, response):
638649
future.failure(error_type(partition))
639650
return
640651
elif error_type is Errors.UnknownTopicOrPartitionError:
641-
log.warning("Received unknown topic or partition error in ListOffset "
652+
log.warning("Received unknown topic or partition error in ListOffsets "
642653
"request for partition %s. The topic/partition " +
643654
"may not exist or the user may not have Describe access "
644655
"to it.", partition)

kafka/consumer/group.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from kafka.coordinator.assignors.range import RangePartitionAssignor
1717
from kafka.coordinator.assignors.roundrobin import RoundRobinPartitionAssignor
1818
from kafka.metrics import MetricConfig, Metrics
19-
from kafka.protocol.offset import OffsetResetStrategy
19+
from kafka.protocol.list_offsets import OffsetResetStrategy
2020
from kafka.structs import TopicPartition
2121
from kafka.version import __version__
2222

kafka/consumer/subscription_state.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from kafka.vendor import six
88

99
from kafka.errors import IllegalStateError
10-
from kafka.protocol.offset import OffsetResetStrategy
10+
from kafka.protocol.list_offsets import OffsetResetStrategy
1111
from kafka.structs import OffsetAndMetadata
1212

1313
log = logging.getLogger(__name__)

kafka/protocol/offset.py renamed to kafka/protocol/list_offsets.py

+28-28
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class OffsetResetStrategy(object):
1212
NONE = 0
1313

1414

15-
class OffsetResponse_v0(Response):
15+
class ListOffsetsResponse_v0(Response):
1616
API_KEY = 2
1717
API_VERSION = 0
1818
SCHEMA = Schema(
@@ -24,7 +24,7 @@ class OffsetResponse_v0(Response):
2424
('offsets', Array(Int64))))))
2525
)
2626

27-
class OffsetResponse_v1(Response):
27+
class ListOffsetsResponse_v1(Response):
2828
API_KEY = 2
2929
API_VERSION = 1
3030
SCHEMA = Schema(
@@ -38,7 +38,7 @@ class OffsetResponse_v1(Response):
3838
)
3939

4040

41-
class OffsetResponse_v2(Response):
41+
class ListOffsetsResponse_v2(Response):
4242
API_KEY = 2
4343
API_VERSION = 2
4444
SCHEMA = Schema(
@@ -53,16 +53,16 @@ class OffsetResponse_v2(Response):
5353
)
5454

5555

56-
class OffsetResponse_v3(Response):
56+
class ListOffsetsResponse_v3(Response):
5757
"""
5858
on quota violation, brokers send out responses before throttling
5959
"""
6060
API_KEY = 2
6161
API_VERSION = 3
62-
SCHEMA = OffsetResponse_v2.SCHEMA
62+
SCHEMA = ListOffsetsResponse_v2.SCHEMA
6363

6464

65-
class OffsetResponse_v4(Response):
65+
class ListOffsetsResponse_v4(Response):
6666
"""
6767
Add leader_epoch to response
6868
"""
@@ -81,19 +81,19 @@ class OffsetResponse_v4(Response):
8181
)
8282

8383

84-
class OffsetResponse_v5(Response):
84+
class ListOffsetsResponse_v5(Response):
8585
"""
8686
adds a new error code, OFFSET_NOT_AVAILABLE
8787
"""
8888
API_KEY = 2
8989
API_VERSION = 5
90-
SCHEMA = OffsetResponse_v4.SCHEMA
90+
SCHEMA = ListOffsetsResponse_v4.SCHEMA
9191

9292

93-
class OffsetRequest_v0(Request):
93+
class ListOffsetsRequest_v0(Request):
9494
API_KEY = 2
9595
API_VERSION = 0
96-
RESPONSE_TYPE = OffsetResponse_v0
96+
RESPONSE_TYPE = ListOffsetsResponse_v0
9797
SCHEMA = Schema(
9898
('replica_id', Int32),
9999
('topics', Array(
@@ -107,10 +107,10 @@ class OffsetRequest_v0(Request):
107107
'replica_id': -1
108108
}
109109

110-
class OffsetRequest_v1(Request):
110+
class ListOffsetsRequest_v1(Request):
111111
API_KEY = 2
112112
API_VERSION = 1
113-
RESPONSE_TYPE = OffsetResponse_v1
113+
RESPONSE_TYPE = ListOffsetsResponse_v1
114114
SCHEMA = Schema(
115115
('replica_id', Int32),
116116
('topics', Array(
@@ -124,10 +124,10 @@ class OffsetRequest_v1(Request):
124124
}
125125

126126

127-
class OffsetRequest_v2(Request):
127+
class ListOffsetsRequest_v2(Request):
128128
API_KEY = 2
129129
API_VERSION = 2
130-
RESPONSE_TYPE = OffsetResponse_v2
130+
RESPONSE_TYPE = ListOffsetsResponse_v2
131131
SCHEMA = Schema(
132132
('replica_id', Int32),
133133
('isolation_level', Int8), # <- added isolation_level
@@ -142,23 +142,23 @@ class OffsetRequest_v2(Request):
142142
}
143143

144144

145-
class OffsetRequest_v3(Request):
145+
class ListOffsetsRequest_v3(Request):
146146
API_KEY = 2
147147
API_VERSION = 3
148-
RESPONSE_TYPE = OffsetResponse_v3
149-
SCHEMA = OffsetRequest_v2.SCHEMA
148+
RESPONSE_TYPE = ListOffsetsResponse_v3
149+
SCHEMA = ListOffsetsRequest_v2.SCHEMA
150150
DEFAULTS = {
151151
'replica_id': -1
152152
}
153153

154154

155-
class OffsetRequest_v4(Request):
155+
class ListOffsetsRequest_v4(Request):
156156
"""
157157
Add current_leader_epoch to request
158158
"""
159159
API_KEY = 2
160160
API_VERSION = 4
161-
RESPONSE_TYPE = OffsetResponse_v4
161+
RESPONSE_TYPE = ListOffsetsResponse_v4
162162
SCHEMA = Schema(
163163
('replica_id', Int32),
164164
('isolation_level', Int8), # <- added isolation_level
@@ -174,21 +174,21 @@ class OffsetRequest_v4(Request):
174174
}
175175

176176

177-
class OffsetRequest_v5(Request):
177+
class ListOffsetsRequest_v5(Request):
178178
API_KEY = 2
179179
API_VERSION = 5
180-
RESPONSE_TYPE = OffsetResponse_v5
181-
SCHEMA = OffsetRequest_v4.SCHEMA
180+
RESPONSE_TYPE = ListOffsetsResponse_v5
181+
SCHEMA = ListOffsetsRequest_v4.SCHEMA
182182
DEFAULTS = {
183183
'replica_id': -1
184184
}
185185

186186

187-
OffsetRequest = [
188-
OffsetRequest_v0, OffsetRequest_v1, OffsetRequest_v2,
189-
OffsetRequest_v3, OffsetRequest_v4, OffsetRequest_v5,
187+
ListOffsetsRequest = [
188+
ListOffsetsRequest_v0, ListOffsetsRequest_v1, ListOffsetsRequest_v2,
189+
ListOffsetsRequest_v3, ListOffsetsRequest_v4, ListOffsetsRequest_v5,
190190
]
191-
OffsetResponse = [
192-
OffsetResponse_v0, OffsetResponse_v1, OffsetResponse_v2,
193-
OffsetResponse_v3, OffsetResponse_v4, OffsetResponse_v5,
191+
ListOffsetsResponse = [
192+
ListOffsetsResponse_v0, ListOffsetsResponse_v1, ListOffsetsResponse_v2,
193+
ListOffsetsResponse_v3, ListOffsetsResponse_v4, ListOffsetsResponse_v5,
194194
]

0 commit comments

Comments
 (0)