kafka-python 2.1.3__tar.gz → 2.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kafka_python-2.1.3 → kafka_python-2.1.4}/CHANGES.md +13 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/PKG-INFO +1 -1
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/client_async.py +4 -2
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/conn.py +33 -25
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/fetcher.py +23 -8
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/metadata.py +1 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/oauth.py +15 -2
- kafka_python-2.1.4/kafka/version.py +1 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/PKG-INFO +1 -1
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_client_async.py +3 -2
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_fetcher.py +2 -0
- kafka_python-2.1.3/kafka/version.py +0 -1
- {kafka_python-2.1.3 → kafka_python-2.1.4}/AUTHORS.md +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/LICENSE +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/MANIFEST.in +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/README.rst +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/client.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/cluster.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/codec.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/group.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/consumer/subscription_state.py +2 -2
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/base.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/consumer.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/errors.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/future.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/buffer.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/future.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/kafka.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/record_accumulator.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/producer/sender.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/admin.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/api.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/api_versions.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/broker_api_versions.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/fetch.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/find_coordinator.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/group.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/list_offsets.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/message.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/offset_for_leader_epoch.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/parser.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/sasl_authenticate.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/sasl_handshake.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/protocol/types.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/abc.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/default_records.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/record/util.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/abc.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/gssapi.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/msk.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/plain.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/scram.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/sasl/sspi.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/socks5_wrapper.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/structs.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/util.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/six.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/SOURCES.txt +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/pyproject.toml +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/setup.cfg +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/setup.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_admin.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_admin_integration.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_assignors.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_cluster.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_codec.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_conn.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_consumer.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_consumer_group.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_consumer_integration.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_coordinator.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_metrics.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_object_conversion.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_package.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_partition_movements.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_partitioner.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_producer.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_protocol.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_sasl_integration.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_sender.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_subscription_state.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/test_util.py +0 -0
- {kafka_python-2.1.3 → kafka_python-2.1.4}/test/testutil.py +0 -0
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# 2.1.4 (Mar 28, 2025)
|
|
2
|
+
|
|
3
|
+
Fixes
|
|
4
|
+
* Dont block pending FetchRequests when Metadata update requested (#2576)
|
|
5
|
+
* Fix MetadataRequest for no topics (#2573)
|
|
6
|
+
* Send final error byte x01 on Sasl OAuth failure (#2572)
|
|
7
|
+
* Reset SASL state on disconnect (#2571)
|
|
8
|
+
* Try import new Sequence before old to avoid DeprecationWarning
|
|
9
|
+
|
|
10
|
+
Improvements
|
|
11
|
+
* Update Makefile default to 4.0 broker; add make fixture
|
|
12
|
+
* Improve connection state logging (#2574)
|
|
13
|
+
|
|
1
14
|
# 2.1.3 (Mar 25, 2025)
|
|
2
15
|
|
|
3
16
|
Fixes
|
|
@@ -365,7 +365,7 @@ class KafkaClient(object):
|
|
|
365
365
|
self._connecting.remove(node_id)
|
|
366
366
|
try:
|
|
367
367
|
self._selector.unregister(sock)
|
|
368
|
-
except KeyError:
|
|
368
|
+
except (KeyError, ValueError):
|
|
369
369
|
pass
|
|
370
370
|
|
|
371
371
|
if self._sensors:
|
|
@@ -978,8 +978,10 @@ class KafkaClient(object):
|
|
|
978
978
|
topics = list(self.config['bootstrap_topics_filter'])
|
|
979
979
|
|
|
980
980
|
api_version = self.api_version(MetadataRequest, max_version=7)
|
|
981
|
-
if self.cluster.need_all_topic_metadata
|
|
981
|
+
if self.cluster.need_all_topic_metadata:
|
|
982
982
|
topics = MetadataRequest[api_version].ALL_TOPICS
|
|
983
|
+
elif not topics:
|
|
984
|
+
topics = MetadataRequest[api_version].NO_TOPICS
|
|
983
985
|
if api_version >= 4:
|
|
984
986
|
request = MetadataRequest[api_version](topics, self.config['allow_auto_create_topics'])
|
|
985
987
|
else:
|
|
@@ -271,12 +271,10 @@ class BrokerConnection(object):
|
|
|
271
271
|
assert self.config['security_protocol'] in self.SECURITY_PROTOCOLS, (
|
|
272
272
|
'security_protocol must be in ' + ', '.join(self.SECURITY_PROTOCOLS))
|
|
273
273
|
|
|
274
|
-
self._sasl_mechanism = None
|
|
275
274
|
if self.config['security_protocol'] in ('SSL', 'SASL_SSL'):
|
|
276
275
|
assert ssl_available, "Python wasn't built with SSL support"
|
|
277
276
|
|
|
278
|
-
|
|
279
|
-
self._sasl_mechanism = get_sasl_mechanism(self.config['sasl_mechanism'])(**self.config)
|
|
277
|
+
self._init_sasl_mechanism()
|
|
280
278
|
|
|
281
279
|
# This is not a general lock / this class is not generally thread-safe yet
|
|
282
280
|
# However, to avoid pushing responsibility for maintaining
|
|
@@ -312,11 +310,17 @@ class BrokerConnection(object):
|
|
|
312
310
|
self.config['metric_group_prefix'],
|
|
313
311
|
self.node_id)
|
|
314
312
|
|
|
313
|
+
def _init_sasl_mechanism(self):
|
|
314
|
+
if self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL'):
|
|
315
|
+
self._sasl_mechanism = get_sasl_mechanism(self.config['sasl_mechanism'])(**self.config)
|
|
316
|
+
else:
|
|
317
|
+
self._sasl_mechanism = None
|
|
318
|
+
|
|
315
319
|
def _dns_lookup(self):
|
|
316
320
|
self._gai = dns_lookup(self.host, self.port, self.afi)
|
|
317
321
|
if not self._gai:
|
|
318
|
-
log.error('DNS lookup failed for %s:%i (%s)',
|
|
319
|
-
self.host, self.port, self.afi)
|
|
322
|
+
log.error('%s: DNS lookup failed for %s:%i (%s)',
|
|
323
|
+
self, self.host, self.port, self.afi)
|
|
320
324
|
return False
|
|
321
325
|
return True
|
|
322
326
|
|
|
@@ -362,6 +366,7 @@ class BrokerConnection(object):
|
|
|
362
366
|
def connect(self):
|
|
363
367
|
"""Attempt to connect and return ConnectionState"""
|
|
364
368
|
if self.state is ConnectionStates.DISCONNECTED and not self.blacked_out():
|
|
369
|
+
self.state = ConnectionStates.CONNECTING
|
|
365
370
|
self.last_attempt = time.time()
|
|
366
371
|
next_lookup = self._next_afi_sockaddr()
|
|
367
372
|
if not next_lookup:
|
|
@@ -386,7 +391,6 @@ class BrokerConnection(object):
|
|
|
386
391
|
self._sock.setsockopt(*option)
|
|
387
392
|
|
|
388
393
|
self._sock.setblocking(False)
|
|
389
|
-
self.state = ConnectionStates.CONNECTING
|
|
390
394
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
391
395
|
log.info('%s: connecting to %s:%d [%s %s]', self, self.host,
|
|
392
396
|
self.port, self._sock_addr, AFI_NAMES[self._sock_afi])
|
|
@@ -408,20 +412,20 @@ class BrokerConnection(object):
|
|
|
408
412
|
log.debug('%s: established TCP connection', self)
|
|
409
413
|
|
|
410
414
|
if self.config['security_protocol'] in ('SSL', 'SASL_SSL'):
|
|
411
|
-
log.debug('%s: initiating SSL handshake', self)
|
|
412
415
|
self.state = ConnectionStates.HANDSHAKE
|
|
416
|
+
log.debug('%s: initiating SSL handshake', self)
|
|
413
417
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
414
418
|
# _wrap_ssl can alter the connection state -- disconnects on failure
|
|
415
419
|
self._wrap_ssl()
|
|
416
420
|
else:
|
|
417
|
-
log.debug('%s: checking broker Api Versions', self)
|
|
418
421
|
self.state = ConnectionStates.API_VERSIONS_SEND
|
|
422
|
+
log.debug('%s: checking broker Api Versions', self)
|
|
419
423
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
420
424
|
|
|
421
425
|
# Connection failed
|
|
422
426
|
# WSAEINVAL == 10022, but errno.WSAEINVAL is not available on non-win systems
|
|
423
427
|
elif ret not in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK, 10022):
|
|
424
|
-
log.error('Connect attempt
|
|
428
|
+
log.error('%s: Connect attempt returned error %s.'
|
|
425
429
|
' Disconnecting.', self, ret)
|
|
426
430
|
errstr = errno.errorcode.get(ret, 'UNKNOWN')
|
|
427
431
|
self.close(Errors.KafkaConnectionError('{} {}'.format(ret, errstr)))
|
|
@@ -434,8 +438,8 @@ class BrokerConnection(object):
|
|
|
434
438
|
if self.state is ConnectionStates.HANDSHAKE:
|
|
435
439
|
if self._try_handshake():
|
|
436
440
|
log.debug('%s: completed SSL handshake.', self)
|
|
437
|
-
log.debug('%s: checking broker Api Versions', self)
|
|
438
441
|
self.state = ConnectionStates.API_VERSIONS_SEND
|
|
442
|
+
log.debug('%s: checking broker Api Versions', self)
|
|
439
443
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
440
444
|
|
|
441
445
|
if self.state in (ConnectionStates.API_VERSIONS_SEND, ConnectionStates.API_VERSIONS_RECV):
|
|
@@ -443,13 +447,13 @@ class BrokerConnection(object):
|
|
|
443
447
|
# _try_api_versions_check has side-effects: possibly disconnected on socket errors
|
|
444
448
|
if self.state in (ConnectionStates.API_VERSIONS_SEND, ConnectionStates.API_VERSIONS_RECV):
|
|
445
449
|
if self.config['security_protocol'] in ('SASL_PLAINTEXT', 'SASL_SSL'):
|
|
446
|
-
log.debug('%s: initiating SASL authentication', self)
|
|
447
450
|
self.state = ConnectionStates.AUTHENTICATING
|
|
451
|
+
log.debug('%s: initiating SASL authentication', self)
|
|
448
452
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
449
453
|
else:
|
|
450
454
|
# security_protocol PLAINTEXT
|
|
451
|
-
log.info('%s: Connection complete.', self)
|
|
452
455
|
self.state = ConnectionStates.CONNECTED
|
|
456
|
+
log.info('%s: Connection complete.', self)
|
|
453
457
|
self._reset_reconnect_backoff()
|
|
454
458
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
455
459
|
|
|
@@ -458,8 +462,8 @@ class BrokerConnection(object):
|
|
|
458
462
|
if self._try_authenticate():
|
|
459
463
|
# _try_authenticate has side-effects: possibly disconnected on socket errors
|
|
460
464
|
if self.state is ConnectionStates.AUTHENTICATING:
|
|
461
|
-
log.info('%s: Connection complete.', self)
|
|
462
465
|
self.state = ConnectionStates.CONNECTED
|
|
466
|
+
log.info('%s: Connection complete.', self)
|
|
463
467
|
self._reset_reconnect_backoff()
|
|
464
468
|
self.config['state_change_callback'](self.node_id, self._sock, self)
|
|
465
469
|
|
|
@@ -468,7 +472,7 @@ class BrokerConnection(object):
|
|
|
468
472
|
# Connection timed out
|
|
469
473
|
request_timeout = self.config['request_timeout_ms'] / 1000.0
|
|
470
474
|
if time.time() > request_timeout + self.last_attempt:
|
|
471
|
-
log.error('Connection attempt
|
|
475
|
+
log.error('%s: Connection attempt timed out', self)
|
|
472
476
|
self.close(Errors.KafkaConnectionError('timeout'))
|
|
473
477
|
return self.state
|
|
474
478
|
|
|
@@ -527,7 +531,7 @@ class BrokerConnection(object):
|
|
|
527
531
|
except (SSLWantReadError, SSLWantWriteError):
|
|
528
532
|
pass
|
|
529
533
|
except (SSLZeroReturnError, ConnectionError, TimeoutError, SSLEOFError):
|
|
530
|
-
log.warning('SSL connection closed by server during handshake.')
|
|
534
|
+
log.warning('%s: SSL connection closed by server during handshake.', self)
|
|
531
535
|
self.close(Errors.KafkaConnectionError('SSL connection closed by server during handshake'))
|
|
532
536
|
# Other SSLErrors will be raised to user
|
|
533
537
|
|
|
@@ -607,7 +611,7 @@ class BrokerConnection(object):
|
|
|
607
611
|
for api_key, min_version, max_version, *rest in response.api_versions
|
|
608
612
|
])
|
|
609
613
|
self._api_version = self._infer_broker_version_from_api_versions(self._api_versions)
|
|
610
|
-
log.info('Broker version identified as %s', '.'.join(map(str, self._api_version)))
|
|
614
|
+
log.info('%s: Broker version identified as %s', self, '.'.join(map(str, self._api_version)))
|
|
611
615
|
future.success(self._api_version)
|
|
612
616
|
self.connect()
|
|
613
617
|
|
|
@@ -617,7 +621,7 @@ class BrokerConnection(object):
|
|
|
617
621
|
# after failure connection is closed, so state should already be DISCONNECTED
|
|
618
622
|
|
|
619
623
|
def _handle_check_version_response(self, future, version, _response):
|
|
620
|
-
log.info('Broker version identified as %s', '.'.join(map(str, version)))
|
|
624
|
+
log.info('%s: Broker version identified as %s', self, '.'.join(map(str, version)))
|
|
621
625
|
log.info('Set configuration api_version=%s to skip auto'
|
|
622
626
|
' check_version requests on startup', version)
|
|
623
627
|
self._api_versions = BROKER_API_VERSIONS[version]
|
|
@@ -747,6 +751,7 @@ class BrokerConnection(object):
|
|
|
747
751
|
request = SaslAuthenticateRequest[0](sasl_auth_bytes)
|
|
748
752
|
self._send(request, blocking=True)
|
|
749
753
|
else:
|
|
754
|
+
log.debug('%s: Sending %d raw sasl auth bytes to server', self, len(sasl_auth_bytes))
|
|
750
755
|
try:
|
|
751
756
|
self._send_bytes_blocking(Int32.encode(len(sasl_auth_bytes)) + sasl_auth_bytes)
|
|
752
757
|
except (ConnectionError, TimeoutError) as e:
|
|
@@ -776,7 +781,7 @@ class BrokerConnection(object):
|
|
|
776
781
|
latency_ms = (time.time() - timestamp) * 1000
|
|
777
782
|
if self._sensors:
|
|
778
783
|
self._sensors.request_time.record(latency_ms)
|
|
779
|
-
log.debug('%s Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
|
|
784
|
+
log.debug('%s: Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
|
|
780
785
|
|
|
781
786
|
error_type = Errors.for_code(response.error_code)
|
|
782
787
|
if error_type is not Errors.NoError:
|
|
@@ -787,6 +792,7 @@ class BrokerConnection(object):
|
|
|
787
792
|
return response.auth_bytes
|
|
788
793
|
else:
|
|
789
794
|
# unframed bytes w/ SaslHandhake v0
|
|
795
|
+
log.debug('%s: Received %d raw sasl auth bytes from server', self, nbytes)
|
|
790
796
|
return data[4:]
|
|
791
797
|
|
|
792
798
|
def _sasl_authenticate(self, future):
|
|
@@ -930,6 +936,7 @@ class BrokerConnection(object):
|
|
|
930
936
|
self._update_reconnect_backoff()
|
|
931
937
|
self._api_versions_future = None
|
|
932
938
|
self._sasl_auth_future = None
|
|
939
|
+
self._init_sasl_mechanism()
|
|
933
940
|
self._protocol = KafkaProtocol(
|
|
934
941
|
client_id=self.config['client_id'],
|
|
935
942
|
api_version=self.config['api_version'])
|
|
@@ -949,7 +956,8 @@ class BrokerConnection(object):
|
|
|
949
956
|
|
|
950
957
|
# drop lock before state change callback and processing futures
|
|
951
958
|
self.config['state_change_callback'](self.node_id, sock, self)
|
|
952
|
-
sock
|
|
959
|
+
if sock:
|
|
960
|
+
sock.close()
|
|
953
961
|
for (_correlation_id, (future, _timestamp, _timeout)) in ifrs:
|
|
954
962
|
future.failure(error)
|
|
955
963
|
|
|
@@ -995,7 +1003,7 @@ class BrokerConnection(object):
|
|
|
995
1003
|
|
|
996
1004
|
correlation_id = self._protocol.send_request(request)
|
|
997
1005
|
|
|
998
|
-
log.debug('%s Request %d (timeout_ms %s): %s', self, correlation_id, request_timeout_ms, request)
|
|
1006
|
+
log.debug('%s: Request %d (timeout_ms %s): %s', self, correlation_id, request_timeout_ms, request)
|
|
999
1007
|
if request.expect_response():
|
|
1000
1008
|
assert correlation_id not in self.in_flight_requests, 'Correlation ID already in-flight!'
|
|
1001
1009
|
sent_time = time.time()
|
|
@@ -1029,7 +1037,7 @@ class BrokerConnection(object):
|
|
|
1029
1037
|
return True
|
|
1030
1038
|
|
|
1031
1039
|
except (ConnectionError, TimeoutError) as e:
|
|
1032
|
-
log.exception("Error sending request data
|
|
1040
|
+
log.exception("%s: Error sending request data", self)
|
|
1033
1041
|
error = Errors.KafkaConnectionError("%s: %s" % (self, e))
|
|
1034
1042
|
self.close(error=error)
|
|
1035
1043
|
return False
|
|
@@ -1062,7 +1070,7 @@ class BrokerConnection(object):
|
|
|
1062
1070
|
return len(self._send_buffer) == 0
|
|
1063
1071
|
|
|
1064
1072
|
except (ConnectionError, TimeoutError, Exception) as e:
|
|
1065
|
-
log.exception("Error sending request data
|
|
1073
|
+
log.exception("%s: Error sending request data", self)
|
|
1066
1074
|
error = Errors.KafkaConnectionError("%s: %s" % (self, e))
|
|
1067
1075
|
self.close(error=error)
|
|
1068
1076
|
return False
|
|
@@ -1099,7 +1107,7 @@ class BrokerConnection(object):
|
|
|
1099
1107
|
if not responses and self.requests_timed_out():
|
|
1100
1108
|
timed_out = self.timed_out_ifrs()
|
|
1101
1109
|
timeout_ms = (timed_out[0][2] - timed_out[0][1]) * 1000
|
|
1102
|
-
log.warning('%s timed out after %s ms. Closing connection.',
|
|
1110
|
+
log.warning('%s: timed out after %s ms. Closing connection.',
|
|
1103
1111
|
self, timeout_ms)
|
|
1104
1112
|
self.close(error=Errors.RequestTimedOutError(
|
|
1105
1113
|
'Request timed out after %s ms' %
|
|
@@ -1118,7 +1126,7 @@ class BrokerConnection(object):
|
|
|
1118
1126
|
if self._sensors:
|
|
1119
1127
|
self._sensors.request_time.record(latency_ms)
|
|
1120
1128
|
|
|
1121
|
-
log.debug('%s Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
|
|
1129
|
+
log.debug('%s: Response %d (%s ms): %s', self, correlation_id, latency_ms, response)
|
|
1122
1130
|
self._maybe_throttle(response)
|
|
1123
1131
|
responses[i] = (response, future)
|
|
1124
1132
|
|
|
@@ -1130,7 +1138,7 @@ class BrokerConnection(object):
|
|
|
1130
1138
|
err = None
|
|
1131
1139
|
with self._lock:
|
|
1132
1140
|
if not self._can_send_recv():
|
|
1133
|
-
log.warning('%s cannot recv: socket not connected', self)
|
|
1141
|
+
log.warning('%s: cannot recv: socket not connected', self)
|
|
1134
1142
|
return ()
|
|
1135
1143
|
|
|
1136
1144
|
while len(recvd) < self.config['sock_chunk_buffer_count']:
|
|
@@ -114,6 +114,7 @@ class Fetcher(six.Iterator):
|
|
|
114
114
|
self._sensors = FetchManagerMetrics(metrics, self.config['metric_group_prefix'])
|
|
115
115
|
self._isolation_level = READ_UNCOMMITTED
|
|
116
116
|
self._session_handlers = {}
|
|
117
|
+
self._nodes_with_pending_fetch_requests = set()
|
|
117
118
|
|
|
118
119
|
def send_fetches(self):
|
|
119
120
|
"""Send FetchRequests for all assigned partitions that do not already have
|
|
@@ -124,12 +125,12 @@ class Fetcher(six.Iterator):
|
|
|
124
125
|
"""
|
|
125
126
|
futures = []
|
|
126
127
|
for node_id, (request, fetch_offsets) in six.iteritems(self._create_fetch_requests()):
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
log.debug("Sending FetchRequest to node %s", node_id)
|
|
129
|
+
self._nodes_with_pending_fetch_requests.add(node_id)
|
|
130
|
+
future = self._client.send(node_id, request, wakeup=False)
|
|
131
|
+
future.add_callback(self._handle_fetch_response, node_id, fetch_offsets, time.time())
|
|
132
|
+
future.add_errback(self._handle_fetch_error, node_id)
|
|
133
|
+
futures.append(future)
|
|
133
134
|
self._fetch_futures.extend(futures)
|
|
134
135
|
self._clean_done_fetch_futures()
|
|
135
136
|
return futures
|
|
@@ -593,8 +594,20 @@ class Fetcher(six.Iterator):
|
|
|
593
594
|
" Requesting metadata update", partition)
|
|
594
595
|
self._client.cluster.request_update()
|
|
595
596
|
|
|
596
|
-
elif self._client.
|
|
597
|
-
|
|
597
|
+
elif not self._client.connected(node_id) and self._client.connection_delay(node_id) > 0:
|
|
598
|
+
# If we try to send during the reconnect backoff window, then the request is just
|
|
599
|
+
# going to be failed anyway before being sent, so skip the send for now
|
|
600
|
+
log.log(0, "Skipping fetch for partition %s because node %s is awaiting reconnect backoff",
|
|
601
|
+
partition, node_id)
|
|
602
|
+
|
|
603
|
+
elif self._client.throttle_delay(node_id) > 0:
|
|
604
|
+
# If we try to send while throttled, then the request is just
|
|
605
|
+
# going to be failed anyway before being sent, so skip the send for now
|
|
606
|
+
log.log(0, "Skipping fetch for partition %s because node %s is throttled",
|
|
607
|
+
partition, node_id)
|
|
608
|
+
|
|
609
|
+
elif node_id in self._nodes_with_pending_fetch_requests:
|
|
610
|
+
log.log(0, "Skipping fetch for partition %s because there is a pending fetch request to node %s",
|
|
598
611
|
partition, node_id)
|
|
599
612
|
continue
|
|
600
613
|
|
|
@@ -707,12 +720,14 @@ class Fetcher(six.Iterator):
|
|
|
707
720
|
self._completed_fetches.append(completed_fetch)
|
|
708
721
|
|
|
709
722
|
self._sensors.fetch_latency.record((time.time() - send_time) * 1000)
|
|
723
|
+
self._nodes_with_pending_fetch_requests.remove(node_id)
|
|
710
724
|
|
|
711
725
|
def _handle_fetch_error(self, node_id, exception):
|
|
712
726
|
level = logging.INFO if isinstance(exception, Errors.Cancelled) else logging.ERROR
|
|
713
727
|
log.log(level, 'Fetch to node %s failed: %s', node_id, exception)
|
|
714
728
|
if node_id in self._session_handlers:
|
|
715
729
|
self._session_handlers[node_id].handle_error(exception)
|
|
730
|
+
self._nodes_with_pending_fetch_requests.remove(node_id)
|
|
716
731
|
|
|
717
732
|
def _parse_fetched_data(self, completed_fetch):
|
|
718
733
|
tp = completed_fetch.topic_partition
|
|
@@ -172,6 +172,7 @@ class MetadataRequest_v0(Request):
|
|
|
172
172
|
('topics', Array(String('utf-8')))
|
|
173
173
|
)
|
|
174
174
|
ALL_TOPICS = [] # Empty Array (len 0) for topics returns all topics
|
|
175
|
+
NO_TOPICS = [] # v0 does not support a 'no topics' request, so we'll just ask for ALL
|
|
175
176
|
|
|
176
177
|
|
|
177
178
|
class MetadataRequest_v1(Request):
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
+
import logging
|
|
4
5
|
|
|
5
6
|
from kafka.sasl.abc import SaslMechanism
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
8
12
|
class SaslMechanismOAuth(SaslMechanism):
|
|
9
13
|
|
|
10
14
|
def __init__(self, **config):
|
|
@@ -12,17 +16,26 @@ class SaslMechanismOAuth(SaslMechanism):
|
|
|
12
16
|
assert isinstance(config['sasl_oauth_token_provider'], AbstractTokenProvider), \
|
|
13
17
|
'sasl_oauth_token_provider must implement kafka.sasl.oauth.AbstractTokenProvider'
|
|
14
18
|
self.token_provider = config['sasl_oauth_token_provider']
|
|
19
|
+
self._error = None
|
|
15
20
|
self._is_done = False
|
|
16
21
|
self._is_authenticated = False
|
|
17
22
|
|
|
18
23
|
def auth_bytes(self):
|
|
24
|
+
if self._error:
|
|
25
|
+
# Server should respond to this with SaslAuthenticate failure, which ends the auth process
|
|
26
|
+
return self._error
|
|
19
27
|
token = self.token_provider.token()
|
|
20
28
|
extensions = self._token_extensions()
|
|
21
29
|
return "n,,\x01auth=Bearer {}{}\x01\x01".format(token, extensions).encode('utf-8')
|
|
22
30
|
|
|
23
31
|
def receive(self, auth_bytes):
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
if auth_bytes != b'':
|
|
33
|
+
error = auth_bytes.decode('utf-8')
|
|
34
|
+
log.debug("Sending x01 response to server after receiving SASL OAuth error: %s", error)
|
|
35
|
+
self._error = b'\x01'
|
|
36
|
+
else:
|
|
37
|
+
self._is_done = True
|
|
38
|
+
self._is_authenticated = True
|
|
26
39
|
|
|
27
40
|
def is_done(self):
|
|
28
41
|
return self._is_done
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.1.4'
|
|
@@ -32,7 +32,7 @@ def cli(mocker, conn):
|
|
|
32
32
|
|
|
33
33
|
def test_bootstrap(mocker, conn):
|
|
34
34
|
conn.state = ConnectionStates.CONNECTED
|
|
35
|
-
cli = KafkaClient(api_version=(
|
|
35
|
+
cli = KafkaClient(api_version=(2, 1))
|
|
36
36
|
mocker.patch.object(cli, '_selector')
|
|
37
37
|
future = cli.cluster.request_update()
|
|
38
38
|
cli.poll(future=future)
|
|
@@ -43,7 +43,7 @@ def test_bootstrap(mocker, conn):
|
|
|
43
43
|
kwargs.pop('state_change_callback')
|
|
44
44
|
kwargs.pop('node_id')
|
|
45
45
|
assert kwargs == cli.config
|
|
46
|
-
conn.send.assert_called_once_with(MetadataRequest[
|
|
46
|
+
conn.send.assert_called_once_with(MetadataRequest[7]([], True), blocking=False, request_timeout_ms=None)
|
|
47
47
|
assert cli._bootstrap_fails == 0
|
|
48
48
|
assert cli.cluster.brokers() == set([BrokerMetadata(0, 'foo', 12, None),
|
|
49
49
|
BrokerMetadata(1, 'bar', 34, None)])
|
|
@@ -330,6 +330,7 @@ def test_maybe_refresh_metadata_update(mocker, client):
|
|
|
330
330
|
mocker.patch.object(client, 'least_loaded_node', return_value='foobar')
|
|
331
331
|
mocker.patch.object(client, '_can_send_request', return_value=True)
|
|
332
332
|
send = mocker.patch.object(client, 'send')
|
|
333
|
+
client.cluster.need_all_topic_metadata = True
|
|
333
334
|
|
|
334
335
|
client.poll(timeout_ms=12345678)
|
|
335
336
|
client._poll.assert_called_with(9999.999) # request_timeout_ms
|
|
@@ -423,6 +423,7 @@ def test_fetched_records(fetcher, topic, mocker):
|
|
|
423
423
|
),
|
|
424
424
|
])
|
|
425
425
|
def test__handle_fetch_response(fetcher, fetch_offsets, fetch_response, num_partitions):
|
|
426
|
+
fetcher._nodes_with_pending_fetch_requests.add(0)
|
|
426
427
|
fetcher._handle_fetch_response(0, fetch_offsets, time.time(), fetch_response)
|
|
427
428
|
assert len(fetcher._completed_fetches) == num_partitions
|
|
428
429
|
|
|
@@ -438,6 +439,7 @@ def test__handle_fetch_response(fetcher, fetch_offsets, fetch_response, num_part
|
|
|
438
439
|
)
|
|
439
440
|
])
|
|
440
441
|
def test__handle_fetch_error(fetcher, caplog, exception, log_level):
|
|
442
|
+
fetcher._nodes_with_pending_fetch_requests.add(3)
|
|
441
443
|
fetcher._handle_fetch_error(3, exception)
|
|
442
444
|
assert len(caplog.records) == 1
|
|
443
445
|
assert caplog.records[0].levelname == logging.getLevelName(log_level)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.1.3'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -3,9 +3,9 @@ from __future__ import absolute_import
|
|
|
3
3
|
import abc
|
|
4
4
|
from collections import defaultdict, OrderedDict
|
|
5
5
|
try:
|
|
6
|
-
from collections import Sequence
|
|
7
|
-
except ImportError:
|
|
8
6
|
from collections.abc import Sequence
|
|
7
|
+
except ImportError:
|
|
8
|
+
from collections import Sequence
|
|
9
9
|
try:
|
|
10
10
|
# enum in stdlib as of py3.4
|
|
11
11
|
from enum import IntEnum # pylint: disable=import-error
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/partition_movements.py
RENAMED
|
File without changes
|
|
File without changes
|
{kafka_python-2.1.3 → kafka_python-2.1.4}/kafka/coordinator/assignors/sticky/sticky_assignor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|