From 6024b835a21435a8a1af04298d061c720e6d3839 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:54:55 +0530 Subject: [PATCH 01/29] [KIP-848] Added support for testing with new 'consumer' group protocol. --- tests/integration/cluster_fixture.py | 7 +++++++ tests/integration/conftest.py | 16 +++++++++++++--- .../consumer/test_cooperative_rebalance_1.py | 15 +++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/integration/cluster_fixture.py b/tests/integration/cluster_fixture.py index e23a0547e..321c85c2e 100644 --- a/tests/integration/cluster_fixture.py +++ b/tests/integration/cluster_fixture.py @@ -24,6 +24,7 @@ SerializingProducer from confluent_kafka.admin import AdminClient, NewTopic from confluent_kafka.schema_registry.schema_registry_client import SchemaRegistryClient +import os class KafkaClusterFixture(object): @@ -102,6 +103,9 @@ def cimpl_consumer(self, conf=None): 'auto.offset.reset': 'earliest' }) + if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: + consumer_conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] + if conf is not None: consumer_conf.update(conf) @@ -129,6 +133,9 @@ def consumer(self, conf=None, key_deserializer=None, value_deserializer=None): 'auto.offset.reset': 'earliest' }) + if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: + consumer_conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] + if conf is not None: consumer_conf.update(conf) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3945a2954..3bd718094 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -26,12 +26,20 @@ work_dir = os.path.dirname(os.path.realpath(__file__)) +def use_kraft(): + if 'TEST_TRIVUP_CLUSTER_TYPE' in os.environ and os.environ['TEST_TRIVUP_CLUSTER_TYPE'] == 'kraft': + return True + return 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ and os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] == 'consumer' + + def create_trivup_cluster(conf={}): trivup_fixture_conf = {'with_sr': True, 'debug': True, - 'cp_version': '7.4.0', - 'version': '3.4.0', + 'cp_version': '7.6.0', + 'kraft': use_kraft(), + 'version': 'trunk', 'broker_conf': ['transaction.state.log.replication.factor=1', + 'group.coordinator.rebalance.protocols=classic,consumer', 'transaction.state.log.min.isr=1']} trivup_fixture_conf.update(conf) return TrivupFixture(trivup_fixture_conf) @@ -39,12 +47,14 @@ def create_trivup_cluster(conf={}): def create_sasl_cluster(conf={}): trivup_fixture_conf = {'with_sr': False, - 'version': '3.4.0', + 'version': 'trunk', 'sasl_mechanism': "PLAIN", + 'kraft': use_kraft(), 'sasl_users': 'sasl_user=sasl_user', 'debug': True, 'cp_version': 'latest', 'broker_conf': ['transaction.state.log.replication.factor=1', + 'group.coordinator.rebalance.protocols=classic,consumer', 'transaction.state.log.min.isr=1']} trivup_fixture_conf.update(conf) return TrivupFixture(trivup_fixture_conf) diff --git a/tests/integration/consumer/test_cooperative_rebalance_1.py b/tests/integration/consumer/test_cooperative_rebalance_1.py index d9a94a85f..f7401f250 100644 --- a/tests/integration/consumer/test_cooperative_rebalance_1.py +++ b/tests/integration/consumer/test_cooperative_rebalance_1.py @@ -44,7 +44,13 @@ def __init__(self): def on_assign(self, consumer, partitions): self.assign_count += 1 - assert 1 == len(partitions) + if self.assign_count == 3: + # Assigning both partitions again after assignment lost + # due to max poll interval timeout exceeded + assert 2 == len(partitions) + else: + # Incremental assign cases + assert 1 == len(partitions) def on_revoke(self, consumer, partitions): self.revoke_count += 1 @@ -97,10 +103,11 @@ def on_lost(self, consumer, partitions): assert e.value.args[0].code() == KafkaError._MAX_POLL_EXCEEDED # And poll again to trigger rebalance callbacks - msg4 = consumer.poll(1) - assert msg4 is None + # It will trigger on_lost and then on_assign during rejoin + msg4 = consumer.poll(10) + assert msg4 is not None # Reading messages again after rejoin - assert 2 == reb.assign_count + assert 3 == reb.assign_count assert 1 == reb.lost_count assert 0 == reb.revoke_count From cda669d5012bb8ee1aec3f59786572d9c4e6847a Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:03:24 +0530 Subject: [PATCH 02/29] Build fixes --- tests/integration/conftest.py | 6 +++--- tests/integration/consumer/test_cooperative_rebalance_1.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3bd718094..86084b0c4 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -26,7 +26,7 @@ work_dir = os.path.dirname(os.path.realpath(__file__)) -def use_kraft(): +def _use_kraft(): if 'TEST_TRIVUP_CLUSTER_TYPE' in os.environ and os.environ['TEST_TRIVUP_CLUSTER_TYPE'] == 'kraft': return True return 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ and os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] == 'consumer' @@ -36,7 +36,7 @@ def create_trivup_cluster(conf={}): trivup_fixture_conf = {'with_sr': True, 'debug': True, 'cp_version': '7.6.0', - 'kraft': use_kraft(), + 'kraft': _use_kraft(), 'version': 'trunk', 'broker_conf': ['transaction.state.log.replication.factor=1', 'group.coordinator.rebalance.protocols=classic,consumer', @@ -49,7 +49,7 @@ def create_sasl_cluster(conf={}): trivup_fixture_conf = {'with_sr': False, 'version': 'trunk', 'sasl_mechanism': "PLAIN", - 'kraft': use_kraft(), + 'kraft': _use_kraft(), 'sasl_users': 'sasl_user=sasl_user', 'debug': True, 'cp_version': 'latest', diff --git a/tests/integration/consumer/test_cooperative_rebalance_1.py b/tests/integration/consumer/test_cooperative_rebalance_1.py index f7401f250..7d7fa8b2f 100644 --- a/tests/integration/consumer/test_cooperative_rebalance_1.py +++ b/tests/integration/consumer/test_cooperative_rebalance_1.py @@ -105,7 +105,7 @@ def on_lost(self, consumer, partitions): # And poll again to trigger rebalance callbacks # It will trigger on_lost and then on_assign during rejoin msg4 = consumer.poll(10) - assert msg4 is not None # Reading messages again after rejoin + assert msg4 is not None # Reading messages again after rejoin assert 3 == reb.assign_count assert 1 == reb.lost_count From d5711f59ab77685ca846097dcc88fb7708d478f1 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:15:00 +0530 Subject: [PATCH 03/29] updated trivup --- tests/requirements.txt | 2 +- tests/trivup/trivup-0.12.5.tar.gz | Bin 0 -> 32749 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/trivup/trivup-0.12.5.tar.gz diff --git a/tests/requirements.txt b/tests/requirements.txt index b5e20efb2..87f1e4df0 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -5,7 +5,7 @@ pytest==4.6.9;python_version<="3.0" pytest>=6.0.0;python_version>="3.0" pytest-timeout requests-mock -trivup>=0.8.3 +trivup/trivup-0.12.5.tar fastavro<1.8.0;python_version=="3.7" fastavro>=1.8.4;python_version>"3.7" fastavro diff --git a/tests/trivup/trivup-0.12.5.tar.gz b/tests/trivup/trivup-0.12.5.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..6e6b560cc185cb75421eb14d841d916a3439ceab GIT binary patch literal 32749 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 literal 0 HcmV?d00001 From 9166b2b2712fb3b55368692be2086c7f075335bb Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:19:15 +0530 Subject: [PATCH 04/29] Updated trivup install path --- tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 87f1e4df0..407aaf0d2 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -5,7 +5,7 @@ pytest==4.6.9;python_version<="3.0" pytest>=6.0.0;python_version>="3.0" pytest-timeout requests-mock -trivup/trivup-0.12.5.tar +tests/trivup/trivup-0.12.5.tar.gz fastavro<1.8.0;python_version=="3.7" fastavro>=1.8.4;python_version>"3.7" fastavro From ad33f6098306d2322187ed59eacac41d5493847e Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 26 Apr 2024 19:31:39 +0530 Subject: [PATCH 05/29] Fixed failing test --- tests/integration/conftest.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 86084b0c4..f3629a853 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -25,12 +25,21 @@ work_dir = os.path.dirname(os.path.realpath(__file__)) +GROUP_PROTOCOL_ENV = 'TEST_CONSUMER_GROUP_PROTOCOL' +TRIVUP_CLUSTER_TYPE_ENV = 'TEST_TRIVUP_CLUSTER_TYPE' + +def _use_group_protocol_consumer(): + return GROUP_PROTOCOL_ENV in os.environ and os.environ[GROUP_PROTOCOL_ENV] == 'consumer' def _use_kraft(): - if 'TEST_TRIVUP_CLUSTER_TYPE' in os.environ and os.environ['TEST_TRIVUP_CLUSTER_TYPE'] == 'kraft': - return True - return 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ and os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] == 'consumer' + return _use_group_protocol_consumer() or TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[TRIVUP_CLUSTER_TYPE_ENV] == 'kraft' +def _broker_conf(): + broker_conf = ['transaction.state.log.replication.factor=1', + 'transaction.state.log.min.isr=1'] + if _use_group_protocol_consumer(): + broker_conf.append('group.coordinator.rebalance.protocols=classic,consumer') + return broker_conf def create_trivup_cluster(conf={}): trivup_fixture_conf = {'with_sr': True, @@ -38,9 +47,7 @@ def create_trivup_cluster(conf={}): 'cp_version': '7.6.0', 'kraft': _use_kraft(), 'version': 'trunk', - 'broker_conf': ['transaction.state.log.replication.factor=1', - 'group.coordinator.rebalance.protocols=classic,consumer', - 'transaction.state.log.min.isr=1']} + 'broker_conf': _broker_conf()} trivup_fixture_conf.update(conf) return TrivupFixture(trivup_fixture_conf) @@ -53,9 +60,7 @@ def create_sasl_cluster(conf={}): 'sasl_users': 'sasl_user=sasl_user', 'debug': True, 'cp_version': 'latest', - 'broker_conf': ['transaction.state.log.replication.factor=1', - 'group.coordinator.rebalance.protocols=classic,consumer', - 'transaction.state.log.min.isr=1']} + 'broker_conf': _broker_conf()} trivup_fixture_conf.update(conf) return TrivupFixture(trivup_fixture_conf) From 31b7150aa9534ff9da81a2cb1310ff1c191d579e Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 26 Apr 2024 19:52:14 +0530 Subject: [PATCH 06/29] Style fix --- tests/integration/conftest.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f3629a853..cb1501475 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -28,11 +28,18 @@ GROUP_PROTOCOL_ENV = 'TEST_CONSUMER_GROUP_PROTOCOL' TRIVUP_CLUSTER_TYPE_ENV = 'TEST_TRIVUP_CLUSTER_TYPE' + def _use_group_protocol_consumer(): return GROUP_PROTOCOL_ENV in os.environ and os.environ[GROUP_PROTOCOL_ENV] == 'consumer' + +def _trivup_cluster_type_kraft(): + return TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[TRIVUP_CLUSTER_TYPE_ENV] == 'kratf' + + def _use_kraft(): - return _use_group_protocol_consumer() or TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[TRIVUP_CLUSTER_TYPE_ENV] == 'kraft' + return _use_group_protocol_consumer() or _trivup_cluster_type_kraft() + def _broker_conf(): broker_conf = ['transaction.state.log.replication.factor=1', @@ -41,6 +48,7 @@ def _broker_conf(): broker_conf.append('group.coordinator.rebalance.protocols=classic,consumer') return broker_conf + def create_trivup_cluster(conf={}): trivup_fixture_conf = {'with_sr': True, 'debug': True, From 4c6b0587dbec4fe4024d10390f36bcb83378c01b Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 26 Apr 2024 20:27:02 +0530 Subject: [PATCH 07/29] Added more tests to be run with the new protocol --- tests/integration/conftest.py | 2 +- tests/integration/integration_test.py | 20 ++++++++++++------- .../integration/producer/test_transactions.py | 11 ++++++++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index cb1501475..f3e57e906 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -50,7 +50,7 @@ def _broker_conf(): def create_trivup_cluster(conf={}): - trivup_fixture_conf = {'with_sr': True, + trivup_fixture_conf = {'with_sr': False, 'debug': True, 'cp_version': '7.6.0', 'kraft': _use_kraft(), diff --git a/tests/integration/integration_test.py b/tests/integration/integration_test.py index e4ae6deca..b885309d6 100755 --- a/tests/integration/integration_test.py +++ b/tests/integration/integration_test.py @@ -91,6 +91,12 @@ ('foobin', struct.pack('hhl', 10, 20, 30))] +def _get_consumer(conf): + if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: + conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] + return confluent_kafka.Consumer(conf) + + def error_cb(err): print('Error: %s' % err) @@ -373,7 +379,7 @@ def verify_consumer(): 'enable.partition.eof': True} # Create consumer - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) def print_wmark(consumer, topic_parts): # Verify #294: get_watermark_offsets() should not fail on the first call @@ -483,7 +489,7 @@ def print_wmark(consumer, topic_parts): c.close() # Start a new client and get the committed offsets - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) offsets = c.committed(list(map(lambda p: confluent_kafka.TopicPartition(topic, p), range(0, 3)))) for tp in offsets: print(tp) @@ -500,7 +506,7 @@ def verify_consumer_performance(): 'error_cb': error_cb, 'auto.offset.reset': 'earliest'} - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) def my_on_assign(consumer, partitions): print('on_assign:', len(partitions), 'partitions:') @@ -608,7 +614,7 @@ def verify_batch_consumer(): 'auto.offset.reset': 'earliest'} # Create consumer - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) # Subscribe to a list of topics c.subscribe([topic]) @@ -665,7 +671,7 @@ def verify_batch_consumer(): c.close() # Start a new client and get the committed offsets - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) offsets = c.committed(list(map(lambda p: confluent_kafka.TopicPartition(topic, p), range(0, 3)))) for tp in offsets: print(tp) @@ -682,7 +688,7 @@ def verify_batch_consumer_performance(): 'error_cb': error_cb, 'auto.offset.reset': 'earliest'} - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) def my_on_assign(consumer, partitions): print('on_assign:', len(partitions), 'partitions:') @@ -989,7 +995,7 @@ def stats_cb(stats_json_str): 'statistics.interval.ms': 200, 'auto.offset.reset': 'earliest'} - c = confluent_kafka.Consumer(conf) + c = _get_consumer(conf) c.subscribe([topic]) max_msgcnt = 1000000 diff --git a/tests/integration/producer/test_transactions.py b/tests/integration/producer/test_transactions.py index b43f81437..c29879d53 100644 --- a/tests/integration/producer/test_transactions.py +++ b/tests/integration/producer/test_transactions.py @@ -17,11 +17,18 @@ # import inspect import sys +import os from uuid import uuid1 from confluent_kafka import Consumer, KafkaError +def _get_consumer(conf): + if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: + conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] + return Consumer(conf) + + def called_by(): if sys.version_info < (3, 5): return inspect.stack()[1][3] @@ -114,7 +121,7 @@ def test_send_offsets_committed_transaction(kafka_cluster): 'error_cb': error_cb } consumer_conf.update(kafka_cluster.client_conf()) - consumer = Consumer(consumer_conf) + consumer = _get_consumer(consumer_conf) kafka_cluster.seed_topic(input_topic) consumer.subscribe([input_topic]) @@ -204,7 +211,7 @@ def consume_committed(conf, topic): 'error_cb': prefixed_error_cb(called_by()), } consumer_conf.update(conf) - consumer = Consumer(consumer_conf) + consumer = _get_consumer(consumer_conf) consumer.subscribe([topic]) msg_cnt = read_all_msgs(consumer) From 15e03a6e7390db82308d34a97e90dedc193bb28d Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 26 Apr 2024 20:49:20 +0530 Subject: [PATCH 08/29] Fixed failing tests --- tests/integration/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f3e57e906..cb1501475 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -50,7 +50,7 @@ def _broker_conf(): def create_trivup_cluster(conf={}): - trivup_fixture_conf = {'with_sr': False, + trivup_fixture_conf = {'with_sr': True, 'debug': True, 'cp_version': '7.6.0', 'kraft': _use_kraft(), From a42534de54ad1e066ee44efbfad469f065d5adf9 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Sat, 27 Apr 2024 00:47:35 +0530 Subject: [PATCH 09/29] Added Test common for common functionalities --- tests/common/__init__.py | 44 +++++++++++++++++++ tests/integration/cluster_fixture.py | 6 +-- tests/integration/conftest.py | 25 +++-------- tests/integration/integration_test.py | 21 ++++----- tests/integration/producer/__init__.py | 0 .../integration/producer/test_transactions.py | 13 ++---- tests/test_Consumer.py | 18 ++++---- tests/test_Producer.py | 5 ++- tests/test_log.py | 6 +-- tests/test_misc.py | 13 +++--- 10 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 tests/common/__init__.py create mode 100644 tests/integration/producer/__init__.py diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 000000000..016d1bc46 --- /dev/null +++ b/tests/common/__init__.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2024 Confluent Inc. +# +# Licensed 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. +# + +import os +import confluent_kafka + +_GROUP_PROTOCOL_ENV = 'TEST_CONSUMER_GROUP_PROTOCOL' +_TRIVUP_CLUSTER_TYPE_ENV = 'TEST_TRIVUP_CLUSTER_TYPE' + + +def _trivup_cluster_type_kraft(): + return _TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[_TRIVUP_CLUSTER_TYPE_ENV] == 'kratf' + + +def use_group_protocol_consumer(): + return _GROUP_PROTOCOL_ENV in os.environ and os.environ[_GROUP_PROTOCOL_ENV] == 'consumer' + + +def use_kraft(): + return use_group_protocol_consumer() or _trivup_cluster_type_kraft() + + +def get_consumer(conf=None, **kwargs): + if use_group_protocol_consumer(): + if conf is None: + conf = {} + conf['group.protocol'] = 'consumer' + return confluent_kafka.Consumer(conf, **kwargs) + diff --git a/tests/integration/cluster_fixture.py b/tests/integration/cluster_fixture.py index 321c85c2e..4e1c8f1cd 100644 --- a/tests/integration/cluster_fixture.py +++ b/tests/integration/cluster_fixture.py @@ -25,6 +25,7 @@ from confluent_kafka.admin import AdminClient, NewTopic from confluent_kafka.schema_registry.schema_registry_client import SchemaRegistryClient import os +from ..common import get_consumer class KafkaClusterFixture(object): @@ -103,13 +104,10 @@ def cimpl_consumer(self, conf=None): 'auto.offset.reset': 'earliest' }) - if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: - consumer_conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] - if conf is not None: consumer_conf.update(conf) - return Consumer(consumer_conf) + return get_consumer(consumer_conf) def consumer(self, conf=None, key_deserializer=None, value_deserializer=None): """ diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index cb1501475..a2f82d00c 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,7 +17,7 @@ # import os - +from ..common import * import pytest from tests.integration.cluster_fixture import TrivupFixture @@ -25,35 +25,20 @@ work_dir = os.path.dirname(os.path.realpath(__file__)) -GROUP_PROTOCOL_ENV = 'TEST_CONSUMER_GROUP_PROTOCOL' -TRIVUP_CLUSTER_TYPE_ENV = 'TEST_TRIVUP_CLUSTER_TYPE' - - -def _use_group_protocol_consumer(): - return GROUP_PROTOCOL_ENV in os.environ and os.environ[GROUP_PROTOCOL_ENV] == 'consumer' - - -def _trivup_cluster_type_kraft(): - return TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[TRIVUP_CLUSTER_TYPE_ENV] == 'kratf' - - -def _use_kraft(): - return _use_group_protocol_consumer() or _trivup_cluster_type_kraft() - def _broker_conf(): broker_conf = ['transaction.state.log.replication.factor=1', 'transaction.state.log.min.isr=1'] - if _use_group_protocol_consumer(): + if use_group_protocol_consumer(): broker_conf.append('group.coordinator.rebalance.protocols=classic,consumer') return broker_conf def create_trivup_cluster(conf={}): - trivup_fixture_conf = {'with_sr': True, + trivup_fixture_conf = {'with_sr': False, 'debug': True, 'cp_version': '7.6.0', - 'kraft': _use_kraft(), + 'kraft': use_kraft(), 'version': 'trunk', 'broker_conf': _broker_conf()} trivup_fixture_conf.update(conf) @@ -64,7 +49,7 @@ def create_sasl_cluster(conf={}): trivup_fixture_conf = {'with_sr': False, 'version': 'trunk', 'sasl_mechanism': "PLAIN", - 'kraft': _use_kraft(), + 'kraft': use_kraft(), 'sasl_users': 'sasl_user=sasl_user', 'debug': True, 'cp_version': 'latest', diff --git a/tests/integration/integration_test.py b/tests/integration/integration_test.py index b885309d6..cbb634611 100755 --- a/tests/integration/integration_test.py +++ b/tests/integration/integration_test.py @@ -29,6 +29,7 @@ import gc import struct import re +from ..common import get_consumer try: # Memory tracker @@ -91,12 +92,6 @@ ('foobin', struct.pack('hhl', 10, 20, 30))] -def _get_consumer(conf): - if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: - conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] - return confluent_kafka.Consumer(conf) - - def error_cb(err): print('Error: %s' % err) @@ -379,7 +374,7 @@ def verify_consumer(): 'enable.partition.eof': True} # Create consumer - c = _get_consumer(conf) + c = get_consumer(conf) def print_wmark(consumer, topic_parts): # Verify #294: get_watermark_offsets() should not fail on the first call @@ -489,7 +484,7 @@ def print_wmark(consumer, topic_parts): c.close() # Start a new client and get the committed offsets - c = _get_consumer(conf) + c = get_consumer(conf) offsets = c.committed(list(map(lambda p: confluent_kafka.TopicPartition(topic, p), range(0, 3)))) for tp in offsets: print(tp) @@ -506,7 +501,7 @@ def verify_consumer_performance(): 'error_cb': error_cb, 'auto.offset.reset': 'earliest'} - c = _get_consumer(conf) + c = get_consumer(conf) def my_on_assign(consumer, partitions): print('on_assign:', len(partitions), 'partitions:') @@ -614,7 +609,7 @@ def verify_batch_consumer(): 'auto.offset.reset': 'earliest'} # Create consumer - c = _get_consumer(conf) + c = get_consumer(conf) # Subscribe to a list of topics c.subscribe([topic]) @@ -671,7 +666,7 @@ def verify_batch_consumer(): c.close() # Start a new client and get the committed offsets - c = _get_consumer(conf) + c = get_consumer(conf) offsets = c.committed(list(map(lambda p: confluent_kafka.TopicPartition(topic, p), range(0, 3)))) for tp in offsets: print(tp) @@ -688,7 +683,7 @@ def verify_batch_consumer_performance(): 'error_cb': error_cb, 'auto.offset.reset': 'earliest'} - c = _get_consumer(conf) + c = get_consumer(conf) def my_on_assign(consumer, partitions): print('on_assign:', len(partitions), 'partitions:') @@ -995,7 +990,7 @@ def stats_cb(stats_json_str): 'statistics.interval.ms': 200, 'auto.offset.reset': 'earliest'} - c = _get_consumer(conf) + c = get_consumer(conf) c.subscribe([topic]) max_msgcnt = 1000000 diff --git a/tests/integration/producer/__init__.py b/tests/integration/producer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/producer/test_transactions.py b/tests/integration/producer/test_transactions.py index c29879d53..3987065b8 100644 --- a/tests/integration/producer/test_transactions.py +++ b/tests/integration/producer/test_transactions.py @@ -19,14 +19,9 @@ import sys import os from uuid import uuid1 +from ...common import get_consumer -from confluent_kafka import Consumer, KafkaError - - -def _get_consumer(conf): - if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: - conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] - return Consumer(conf) +from confluent_kafka import KafkaError def called_by(): @@ -121,7 +116,7 @@ def test_send_offsets_committed_transaction(kafka_cluster): 'error_cb': error_cb } consumer_conf.update(kafka_cluster.client_conf()) - consumer = _get_consumer(consumer_conf) + consumer = get_consumer(consumer_conf) kafka_cluster.seed_topic(input_topic) consumer.subscribe([input_topic]) @@ -211,7 +206,7 @@ def consume_committed(conf, topic): 'error_cb': prefixed_error_cb(called_by()), } consumer_conf.update(conf) - consumer = _get_consumer(consumer_conf) + consumer = get_consumer(consumer_conf) consumer.subscribe([topic]) msg_cnt = read_all_msgs(consumer) diff --git a/tests/test_Consumer.py b/tests/test_Consumer.py index 48a8d3f8e..3d15594b2 100644 --- a/tests/test_Consumer.py +++ b/tests/test_Consumer.py @@ -4,7 +4,7 @@ KafkaException, TIMESTAMP_NOT_AVAILABLE, OFFSET_INVALID, libversion) import pytest - +from .common import get_consumer def test_basic_api(): """ Basic API tests, these wont really do anything since there is no @@ -17,7 +17,7 @@ def test_basic_api(): def dummy_commit_cb(err, partitions): pass - kc = Consumer({'group.id': 'test', 'socket.timeout.ms': '100', + kc = get_consumer({'group.id': 'test', 'socket.timeout.ms': '100', 'session.timeout.ms': 1000, # Avoid close() blocking too long 'on_commit': dummy_commit_cb}) @@ -119,7 +119,7 @@ def dummy_assign_revoke(consumer, partitions): def test_store_offsets(): """ Basic store_offsets() tests """ - c = Consumer({'group.id': 'test', + c = get_consumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -161,7 +161,7 @@ def commit_cb(cs, err, ps): cs = CommitState('test', 2) - c = Consumer({'group.id': 'x', + c = get_consumer({'group.id': 'x', 'enable.auto.commit': False, 'socket.timeout.ms': 50, 'session.timeout.ms': 100, 'on_commit': lambda err, ps: commit_cb(cs, err, ps)}) @@ -196,7 +196,7 @@ def poll(self, somearg): @pytest.mark.skipif(libversion()[1] < 0x000b0000, reason="requires librdkafka >=0.11.0") def test_offsets_for_times(): - c = Consumer({'group.id': 'test', + c = get_consumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -216,7 +216,7 @@ def test_offsets_for_times(): def test_multiple_close_does_not_throw_exception(): """ Calling Consumer.close() multiple times should not throw Runtime Exception """ - c = Consumer({'group.id': 'test', + c = get_consumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -232,7 +232,7 @@ def test_multiple_close_does_not_throw_exception(): def test_any_method_after_close_throws_exception(): """ Calling any consumer method after close should throw a RuntimeError """ - c = Consumer({'group.id': 'test', + c = get_consumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -296,7 +296,7 @@ def test_any_method_after_close_throws_exception(): def test_calling_store_offsets_after_close_throws_erro(): """ calling store_offset after close should throw RuntimeError """ - c = Consumer({'group.id': 'test', + c = get_consumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -319,5 +319,5 @@ def test_consumer_without_groupid(): """ Consumer should raise exception if group.id is not set """ with pytest.raises(ValueError) as ex: - Consumer({'bootstrap.servers': "mybroker:9092"}) + get_consumer({'bootstrap.servers': "mybroker:9092"}) assert ex.match('group.id must be set') diff --git a/tests/test_Producer.py b/tests/test_Producer.py index 7b81d2125..2869aa632 100644 --- a/tests/test_Producer.py +++ b/tests/test_Producer.py @@ -2,9 +2,10 @@ # -*- coding: utf-8 -*- import pytest -from confluent_kafka import Producer, Consumer, KafkaError, KafkaException, \ +from confluent_kafka import Producer, KafkaError, KafkaException, \ TopicPartition, libversion from struct import pack +from .common import get_consumer def error_cb(err): @@ -211,7 +212,7 @@ def test_transaction_api(): assert ex.value.args[0].fatal() is False assert ex.value.args[0].txn_requires_abort() is False - consumer = Consumer({"group.id": "testgroup"}) + consumer = get_consumer({"group.id": "testgroup"}) group_metadata = consumer.consumer_group_metadata() consumer.close() diff --git a/tests/test_log.py b/tests/test_log.py index 6cd510819..079d06a8c 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -4,6 +4,7 @@ import confluent_kafka import confluent_kafka.avro import logging +from .common import get_consumer class CountingFilter(logging.Filter): @@ -24,8 +25,7 @@ def test_logging_consumer(): logger.setLevel(logging.DEBUG) f = CountingFilter('consumer') logger.addFilter(f) - - kc = confluent_kafka.Consumer({'group.id': 'test', + kc = get_consumer({'group.id': 'test', 'debug': 'all'}, logger=logger) while f.cnt == 0: @@ -149,7 +149,7 @@ def test_consumer_logger_logging_in_given_format(): handler.setFormatter(logging.Formatter('%(name)s Logger | %(message)s')) logger.addHandler(handler) - c = confluent_kafka.Consumer( + c = get_consumer( {"bootstrap.servers": "test", "group.id": "test", "logger": logger, "debug": "msg"}) c.poll(0) diff --git a/tests/test_misc.py b/tests/test_misc.py index aca7b5a4f..91f0be3d2 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -8,6 +8,7 @@ import os import time import sys +from .common import get_consumer def test_version(): @@ -40,7 +41,7 @@ def error_cb(error_msg): 'error_cb': error_cb } - kc = confluent_kafka.Consumer(**conf) + kc = get_consumer(conf) kc.subscribe(["test"]) while not seen_error_cb: kc.poll(timeout=0.1) @@ -64,7 +65,7 @@ def stats_cb(stats_json_str): 'stats_cb': stats_cb } - kc = confluent_kafka.Consumer(**conf) + kc = get_consumer(conf) kc.subscribe(["test"]) while not seen_stats_cb: @@ -137,7 +138,7 @@ def oauth_cb(oauth_config): 'oauth_cb': oauth_cb } - kc = confluent_kafka.Consumer(**conf) + kc = get_consumer(conf) while not seen_oauth_cb: kc.poll(timeout=0.1) @@ -162,7 +163,7 @@ def oauth_cb(oauth_config): 'oauth_cb': oauth_cb } - kc = confluent_kafka.Consumer(**conf) + kc = get_consumer(conf) while not seen_oauth_cb: kc.poll(timeout=0.1) @@ -189,7 +190,7 @@ def oauth_cb(oauth_config): 'oauth_cb': oauth_cb } - kc = confluent_kafka.Consumer(**conf) + kc = get_consumer(conf) while oauth_cb_count < 2: kc.poll(timeout=0.1) @@ -267,7 +268,7 @@ def on_delivery(err, msg): def test_set_sasl_credentials_api(): clients = [ AdminClient({}), - confluent_kafka.Consumer({"group.id": "dummy"}), + get_consumer({"group.id": "dummy"}), confluent_kafka.Producer({})] for c in clients: From 5ea17eb0182d5a242546e8a7587ac3134f776a10 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Sat, 27 Apr 2024 00:50:11 +0530 Subject: [PATCH 10/29] Enabling SR again --- tests/integration/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index a2f82d00c..f5cdd3844 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -35,7 +35,7 @@ def _broker_conf(): def create_trivup_cluster(conf={}): - trivup_fixture_conf = {'with_sr': False, + trivup_fixture_conf = {'with_sr': True, 'debug': True, 'cp_version': '7.6.0', 'kraft': use_kraft(), From 2e66f2cc607e52712049e42793d92232c6852ef7 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Sat, 27 Apr 2024 01:03:19 +0530 Subject: [PATCH 11/29] Style fixes --- tests/common/__init__.py | 1 - tests/integration/cluster_fixture.py | 2 +- tests/integration/conftest.py | 2 +- .../integration/producer/test_transactions.py | 1 - tests/test_Consumer.py | 51 ++++++++++--------- tests/test_log.py | 4 +- 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index 016d1bc46..ef46e4fdf 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -41,4 +41,3 @@ def get_consumer(conf=None, **kwargs): conf = {} conf['group.protocol'] = 'consumer' return confluent_kafka.Consumer(conf, **kwargs) - diff --git a/tests/integration/cluster_fixture.py b/tests/integration/cluster_fixture.py index 4e1c8f1cd..b248f5dc5 100644 --- a/tests/integration/cluster_fixture.py +++ b/tests/integration/cluster_fixture.py @@ -20,7 +20,7 @@ from trivup.clusters.KafkaCluster import KafkaCluster -from confluent_kafka import Consumer, Producer, DeserializingConsumer, \ +from confluent_kafka import Producer, DeserializingConsumer, \ SerializingProducer from confluent_kafka.admin import AdminClient, NewTopic from confluent_kafka.schema_registry.schema_registry_client import SchemaRegistryClient diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f5cdd3844..406ac7823 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,7 +17,7 @@ # import os -from ..common import * +from ..common import use_group_protocol_consumer, use_kraft import pytest from tests.integration.cluster_fixture import TrivupFixture diff --git a/tests/integration/producer/test_transactions.py b/tests/integration/producer/test_transactions.py index 3987065b8..130f31f87 100644 --- a/tests/integration/producer/test_transactions.py +++ b/tests/integration/producer/test_transactions.py @@ -17,7 +17,6 @@ # import inspect import sys -import os from uuid import uuid1 from ...common import get_consumer diff --git a/tests/test_Consumer.py b/tests/test_Consumer.py index 3d15594b2..c6c174ee7 100644 --- a/tests/test_Consumer.py +++ b/tests/test_Consumer.py @@ -6,6 +6,7 @@ import pytest from .common import get_consumer + def test_basic_api(): """ Basic API tests, these wont really do anything since there is no broker configured. """ @@ -18,8 +19,8 @@ def dummy_commit_cb(err, partitions): pass kc = get_consumer({'group.id': 'test', 'socket.timeout.ms': '100', - 'session.timeout.ms': 1000, # Avoid close() blocking too long - 'on_commit': dummy_commit_cb}) + 'session.timeout.ms': 1000, # Avoid close() blocking too long + 'on_commit': dummy_commit_cb}) kc.subscribe(["test"]) kc.unsubscribe() @@ -120,10 +121,10 @@ def test_store_offsets(): """ Basic store_offsets() tests """ c = get_consumer({'group.id': 'test', - 'enable.auto.commit': True, - 'enable.auto.offset.store': False, - 'socket.timeout.ms': 50, - 'session.timeout.ms': 100}) + 'enable.auto.commit': True, + 'enable.auto.offset.store': False, + 'socket.timeout.ms': 50, + 'session.timeout.ms': 100}) c.subscribe(["test"]) @@ -162,9 +163,9 @@ def commit_cb(cs, err, ps): cs = CommitState('test', 2) c = get_consumer({'group.id': 'x', - 'enable.auto.commit': False, 'socket.timeout.ms': 50, - 'session.timeout.ms': 100, - 'on_commit': lambda err, ps: commit_cb(cs, err, ps)}) + 'enable.auto.commit': False, 'socket.timeout.ms': 50, + 'session.timeout.ms': 100, + 'on_commit': lambda err, ps: commit_cb(cs, err, ps)}) c.assign([TopicPartition(cs.topic, cs.partition)]) @@ -197,10 +198,10 @@ def poll(self, somearg): reason="requires librdkafka >=0.11.0") def test_offsets_for_times(): c = get_consumer({'group.id': 'test', - 'enable.auto.commit': True, - 'enable.auto.offset.store': False, - 'socket.timeout.ms': 50, - 'session.timeout.ms': 100}) + 'enable.auto.commit': True, + 'enable.auto.offset.store': False, + 'socket.timeout.ms': 50, + 'session.timeout.ms': 100}) # Query broker for timestamps for partition try: test_topic_partition = TopicPartition("test", 0, 100) @@ -217,10 +218,10 @@ def test_multiple_close_does_not_throw_exception(): """ Calling Consumer.close() multiple times should not throw Runtime Exception """ c = get_consumer({'group.id': 'test', - 'enable.auto.commit': True, - 'enable.auto.offset.store': False, - 'socket.timeout.ms': 50, - 'session.timeout.ms': 100}) + 'enable.auto.commit': True, + 'enable.auto.offset.store': False, + 'socket.timeout.ms': 50, + 'session.timeout.ms': 100}) c.subscribe(["test"]) @@ -233,10 +234,10 @@ def test_any_method_after_close_throws_exception(): """ Calling any consumer method after close should throw a RuntimeError """ c = get_consumer({'group.id': 'test', - 'enable.auto.commit': True, - 'enable.auto.offset.store': False, - 'socket.timeout.ms': 50, - 'session.timeout.ms': 100}) + 'enable.auto.commit': True, + 'enable.auto.offset.store': False, + 'socket.timeout.ms': 50, + 'session.timeout.ms': 100}) c.subscribe(["test"]) c.unsubscribe() @@ -297,10 +298,10 @@ def test_calling_store_offsets_after_close_throws_erro(): """ calling store_offset after close should throw RuntimeError """ c = get_consumer({'group.id': 'test', - 'enable.auto.commit': True, - 'enable.auto.offset.store': False, - 'socket.timeout.ms': 50, - 'session.timeout.ms': 100}) + 'enable.auto.commit': True, + 'enable.auto.offset.store': False, + 'socket.timeout.ms': 50, + 'session.timeout.ms': 100}) c.subscribe(["test"]) c.unsubscribe() diff --git a/tests/test_log.py b/tests/test_log.py index 079d06a8c..ac9091f49 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -26,8 +26,8 @@ def test_logging_consumer(): f = CountingFilter('consumer') logger.addFilter(f) kc = get_consumer({'group.id': 'test', - 'debug': 'all'}, - logger=logger) + 'debug': 'all'}, + logger=logger) while f.cnt == 0: kc.poll(timeout=0.5) From df10e0d50e31a05774b9baf7c6e27b2427fdfd92 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Sat, 27 Apr 2024 01:40:42 +0530 Subject: [PATCH 12/29] Some refactoring --- tests/common/__init__.py | 19 ++++++++++++++++--- tests/integration/cluster_fixture.py | 11 +++-------- tests/integration/integration_test.py | 6 +++--- tests/soak/soakclient.py | 5 +++-- tests/test_Consumer.py | 2 +- tests/test_log.py | 10 +++++----- 6 files changed, 31 insertions(+), 22 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index ef46e4fdf..fc7d8b566 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -17,7 +17,8 @@ # import os -import confluent_kafka +from confluent_kafka import Consumer, DeserializingConsumer +from confluent_kafka.avro import AvroConsumer _GROUP_PROTOCOL_ENV = 'TEST_CONSUMER_GROUP_PROTOCOL' _TRIVUP_CLUSTER_TYPE_ENV = 'TEST_TRIVUP_CLUSTER_TYPE' @@ -35,9 +36,21 @@ def use_kraft(): return use_group_protocol_consumer() or _trivup_cluster_type_kraft() -def get_consumer(conf=None, **kwargs): +def _get_consumer_generic(consumer_clazz, conf=None, **kwargs): if use_group_protocol_consumer(): if conf is None: conf = {} conf['group.protocol'] = 'consumer' - return confluent_kafka.Consumer(conf, **kwargs) + return consumer_clazz(conf, **kwargs) + + +def get_consumer(conf=None, **kwargs): + return _get_consumer_generic(Consumer, conf, **kwargs) + + +def get_avro_consumer(conf=None, **kwargs): + return _get_consumer_generic(AvroConsumer, conf, **kwargs) + + +def get_deserializing_consumer(conf=None, **kwargs): + return _get_consumer_generic(DeserializingConsumer, conf, **kwargs) diff --git a/tests/integration/cluster_fixture.py b/tests/integration/cluster_fixture.py index b248f5dc5..7fbfeccd8 100644 --- a/tests/integration/cluster_fixture.py +++ b/tests/integration/cluster_fixture.py @@ -20,12 +20,10 @@ from trivup.clusters.KafkaCluster import KafkaCluster -from confluent_kafka import Producer, DeserializingConsumer, \ - SerializingProducer +from confluent_kafka import Producer, SerializingProducer from confluent_kafka.admin import AdminClient, NewTopic from confluent_kafka.schema_registry.schema_registry_client import SchemaRegistryClient -import os -from ..common import get_consumer +from ..common import get_consumer, get_deserializing_consumer class KafkaClusterFixture(object): @@ -131,9 +129,6 @@ def consumer(self, conf=None, key_deserializer=None, value_deserializer=None): 'auto.offset.reset': 'earliest' }) - if 'TEST_CONSUMER_GROUP_PROTOCOL' in os.environ: - consumer_conf['group.protocol'] = os.environ['TEST_CONSUMER_GROUP_PROTOCOL'] - if conf is not None: consumer_conf.update(conf) @@ -143,7 +138,7 @@ def consumer(self, conf=None, key_deserializer=None, value_deserializer=None): if value_deserializer is not None: consumer_conf['value.deserializer'] = value_deserializer - return DeserializingConsumer(consumer_conf) + return get_deserializing_consumer(consumer_conf) def admin(self, conf=None): if conf: diff --git a/tests/integration/integration_test.py b/tests/integration/integration_test.py index cbb634611..013fd4d93 100755 --- a/tests/integration/integration_test.py +++ b/tests/integration/integration_test.py @@ -29,7 +29,7 @@ import gc import struct import re -from ..common import get_consumer +from ..common import get_consumer, get_avro_consumer try: # Memory tracker @@ -878,7 +878,7 @@ def run_avro_loop(producer_conf, consumer_conf): p.produce(**combo) p.flush() - c = avro.AvroConsumer(consumer_conf) + c = get_avro_consumer(consumer_conf) c.subscribe([(t['topic']) for t in combinations]) msgcount = 0 @@ -1117,7 +1117,7 @@ def verify_avro_explicit_read_schema(): p.produce(topic=avro_topic, **combo) p.flush() - c = avro.AvroConsumer(consumer_conf, reader_key_schema=reader_schema, reader_value_schema=reader_schema) + c = get_avro_consumer(consumer_conf, reader_key_schema=reader_schema, reader_value_schema=reader_schema) c.subscribe([avro_topic]) msgcount = 0 diff --git a/tests/soak/soakclient.py b/tests/soak/soakclient.py index e7e914cee..f6c9db57f 100755 --- a/tests/soak/soakclient.py +++ b/tests/soak/soakclient.py @@ -26,10 +26,11 @@ # from confluent_kafka import KafkaError, KafkaException, version -from confluent_kafka import Producer, Consumer +from confluent_kafka import Producer from confluent_kafka.admin import AdminClient, NewTopic from collections import defaultdict from builtins import int +from common import get_consumer import argparse import threading import time @@ -444,7 +445,7 @@ def filter_config(conf, filter_out, strip_prefix): cconf['error_cb'] = self.consumer_error_cb cconf['on_commit'] = self.consumer_commit_cb self.logger.info("consumer: using group.id {}".format(cconf['group.id'])) - self.consumer = Consumer(cconf) + self.consumer = get_consumer(cconf) # Create and start producer thread self.producer_thread = threading.Thread(target=self.producer_thread_main) diff --git a/tests/test_Consumer.py b/tests/test_Consumer.py index c6c174ee7..a869bce59 100644 --- a/tests/test_Consumer.py +++ b/tests/test_Consumer.py @@ -12,7 +12,7 @@ def test_basic_api(): broker configured. """ with pytest.raises(TypeError) as ex: - kc = Consumer() + kc = get_consumer() assert ex.match('expected configuration dict') def dummy_commit_cb(err, partitions): diff --git a/tests/test_log.py b/tests/test_log.py index ac9091f49..17a87ebae 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -4,7 +4,7 @@ import confluent_kafka import confluent_kafka.avro import logging -from .common import get_consumer +from .common import get_consumer, get_avro_consumer class CountingFilter(logging.Filter): @@ -44,10 +44,10 @@ def test_logging_avro_consumer(): f = CountingFilter('avroconsumer') logger.addFilter(f) - kc = confluent_kafka.avro.AvroConsumer({'schema.registry.url': 'http://example.com', - 'group.id': 'test', - 'debug': 'all'}, - logger=logger) + kc = get_avro_consumer({'schema.registry.url': 'http://example.com', + 'group.id': 'test', + 'debug': 'all'}, + logger=logger) while f.cnt == 0: kc.poll(timeout=0.5) From c39a8651bf53afa349b9ad7f93c4c254f2eb1fc7 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Sat, 27 Apr 2024 01:54:58 +0530 Subject: [PATCH 13/29] Added consumer protocol integration tests in semaphore --- .semaphore/semaphore.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index aaae6aebc..53031ecaa 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -138,12 +138,20 @@ blocks: commands: - '[[ -z $DOCKERHUB_APIKEY ]] || docker login --username $DOCKERHUB_USER --password $DOCKERHUB_APIKEY' jobs: - - name: Build + - name: Build and Tests with 'classic' group protocol + commands: + - sem-version python 3.8 + # use a virtualenv + - python3 -m venv _venv && source _venv/bin/activate + - chmod u+r+x tools/source-package-verification.sh + - tools/source-package-verification.sh + - name: Build and Tests with 'consumer' group protocol commands: - sem-version python 3.8 # use a virtualenv - python3 -m venv _venv && source _venv/bin/activate - chmod u+r+x tools/source-package-verification.sh + - export TEST_CONSUMER_GROUP_PROTOCOL=consumer - tools/source-package-verification.sh - name: "Source package verification with Python 3 (Linux arm64)" dependencies: [] From e1f0e798271b81cf01d457e765cd427fe7ae4599 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Mon, 29 Apr 2024 03:04:21 +0530 Subject: [PATCH 14/29] Ignoring failing admin tests --- tools/source-package-verification.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/source-package-verification.sh b/tools/source-package-verification.sh index eb7506061..51368300c 100755 --- a/tools/source-package-verification.sh +++ b/tools/source-package-verification.sh @@ -20,7 +20,11 @@ python setup.py build && python setup.py install if [[ $OS_NAME == linux && $ARCH == x64 ]]; then flake8 --exclude ./_venv,*_pb2.py make docs - python -m pytest --timeout 1200 --ignore=dest + if [[ $TEST_TEST_CONSUMER_GROUP_PROTOCOL == consumer ]]; then + python -m pytest --timeout 1200 --ignore=dest --ignore=tests/integration/admin + else + python -m pytest --timeout 1200 --ignore=dest + fi else python -m pytest --timeout 1200 --ignore=dest --ignore=tests/integration fi From 7f1c7957a48509a799165ab224512dc70acc24b9 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Mon, 29 Apr 2024 03:23:22 +0530 Subject: [PATCH 15/29] Fix typo --- tools/source-package-verification.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/source-package-verification.sh b/tools/source-package-verification.sh index 51368300c..8ad9642e5 100755 --- a/tools/source-package-verification.sh +++ b/tools/source-package-verification.sh @@ -20,7 +20,7 @@ python setup.py build && python setup.py install if [[ $OS_NAME == linux && $ARCH == x64 ]]; then flake8 --exclude ./_venv,*_pb2.py make docs - if [[ $TEST_TEST_CONSUMER_GROUP_PROTOCOL == consumer ]]; then + if [[ $TEST_CONSUMER_GROUP_PROTOCOL == consumer ]]; then python -m pytest --timeout 1200 --ignore=dest --ignore=tests/integration/admin else python -m pytest --timeout 1200 --ignore=dest From 3380c5aaaeda8ae16e6b746ec74caa80ff8a353d Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Tue, 30 Apr 2024 16:37:52 +0530 Subject: [PATCH 16/29] Fixed failing test case --- tests/common/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index fc7d8b566..0dd071203 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -38,9 +38,8 @@ def use_kraft(): def _get_consumer_generic(consumer_clazz, conf=None, **kwargs): if use_group_protocol_consumer(): - if conf is None: - conf = {} - conf['group.protocol'] = 'consumer' + if conf is not None and 'group.id' in conf: + conf['group.protocol'] = 'consumer' return consumer_clazz(conf, **kwargs) From ee6120327a99fe0e26b4692377b7738bb633a26c Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 3 May 2024 13:44:54 +0530 Subject: [PATCH 17/29] Added new fixure for single broker and using this fixure for test_serializer tests --- tests/common/__init__.py | 2 +- tests/integration/conftest.py | 7 +++++ .../serialization/test_serializers.py | 30 +++++++++---------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index 0dd071203..04893496e 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -25,7 +25,7 @@ def _trivup_cluster_type_kraft(): - return _TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[_TRIVUP_CLUSTER_TYPE_ENV] == 'kratf' + return _TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[_TRIVUP_CLUSTER_TYPE_ENV] == 'kraft' def use_group_protocol_consumer(): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 406ac7823..9ed047017 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -120,6 +120,13 @@ def kafka_cluster(): yield fixture +@pytest.fixture(scope="package") +def kafka_single_broker_cluster(): + for fixture in kafka_cluster_fixture( + trivup_cluster_conf={'broker_cnt': 1}): + yield fixture + + @pytest.fixture(scope="package") def sasl_cluster(request): for fixture in sasl_cluster_fixture(request.param): diff --git a/tests/integration/serialization/test_serializers.py b/tests/integration/serialization/test_serializers.py index 7c4f796c2..e1d73cb49 100644 --- a/tests/integration/serialization/test_serializers.py +++ b/tests/integration/serialization/test_serializers.py @@ -31,12 +31,12 @@ (IntegerSerializer(), IntegerDeserializer(), 4124), (DoubleSerializer(), DoubleDeserializer(), None), (IntegerSerializer(), IntegerDeserializer(), None)]) -def test_numeric_serialization(kafka_cluster, serializer, deserializer, data): +def test_numeric_serialization(kafka_single_broker_cluster, serializer, deserializer, data): """ Tests basic serialization/deserialization of numeric types. Args: - kafka_cluster (KafkaClusterFixture): cluster fixture + kafka_single_broker_cluster (KafkaClusterFixture): cluster fixture serializer (Serializer): serializer to test @@ -45,13 +45,13 @@ def test_numeric_serialization(kafka_cluster, serializer, deserializer, data): data(object): input data """ - topic = kafka_cluster.create_topic("serialization-numeric") + topic = kafka_single_broker_cluster.create_topic("serialization-numeric") - producer = kafka_cluster.producer(value_serializer=serializer) + producer = kafka_single_broker_cluster.producer(value_serializer=serializer) producer.produce(topic, value=data) producer.flush() - consumer = kafka_cluster.consumer(value_deserializer=deserializer) + consumer = kafka_single_broker_cluster.consumer(value_deserializer=deserializer) consumer.subscribe([topic]) msg = consumer.poll() @@ -65,26 +65,26 @@ def test_numeric_serialization(kafka_cluster, serializer, deserializer, data): [(u'Jämtland', 'utf_8'), (u'Härjedalen', 'utf_16'), (None, 'utf_32')]) -def test_string_serialization(kafka_cluster, data, codec): +def test_string_serialization(kafka_single_broker_cluster, data, codec): """ Tests basic unicode serialization/deserialization functionality Args: - kafka_cluster (KafkaClusterFixture): cluster fixture + kafka_single_broker_cluster (KafkaClusterFixture): cluster fixture data (unicode): input data codec (str): encoding type """ - topic = kafka_cluster.create_topic("serialization-string") + topic = kafka_single_broker_cluster.create_topic("serialization-string") - producer = kafka_cluster.producer(value_serializer=StringSerializer(codec)) + producer = kafka_single_broker_cluster.producer(value_serializer=StringSerializer(codec)) producer.produce(topic, value=data) producer.flush() - consumer = kafka_cluster.consumer(value_deserializer=StringDeserializer(codec)) + consumer = kafka_single_broker_cluster.consumer(value_deserializer=StringDeserializer(codec)) consumer.subscribe([topic]) @@ -102,13 +102,13 @@ def test_string_serialization(kafka_cluster, data, codec): (StringSerializer('utf_16'), DoubleSerializer(), StringDeserializer('utf_16'), DoubleDeserializer(), u'Härjedalen', 1.2168215450814477)]) -def test_mixed_serialization(kafka_cluster, key_serializer, value_serializer, +def test_mixed_serialization(kafka_single_broker_cluster, key_serializer, value_serializer, key_deserializer, value_deserializer, key, value): """ Tests basic mixed serializer/deserializer functionality. Args: - kafka_cluster (KafkaClusterFixture): cluster fixture + kafka_single_broker_cluster (KafkaClusterFixture): cluster fixture key_serializer (Serializer): serializer to test @@ -119,14 +119,14 @@ def test_mixed_serialization(kafka_cluster, key_serializer, value_serializer, value (object): value data """ - topic = kafka_cluster.create_topic("serialization-numeric") + topic = kafka_single_broker_cluster.create_topic("serialization-numeric") - producer = kafka_cluster.producer(key_serializer=key_serializer, + producer = kafka_single_broker_cluster.producer(key_serializer=key_serializer, value_serializer=value_serializer) producer.produce(topic, key=key, value=value) producer.flush() - consumer = kafka_cluster.consumer(key_deserializer=key_deserializer, + consumer = kafka_single_broker_cluster.consumer(key_deserializer=key_deserializer, value_deserializer=value_deserializer) consumer.subscribe([topic]) From 92b4b6ba6597cfc84500ec2db874ae52cff19e37 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 3 May 2024 14:59:21 +0530 Subject: [PATCH 18/29] Build fixes --- tests/integration/serialization/test_serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/serialization/test_serializers.py b/tests/integration/serialization/test_serializers.py index e1d73cb49..a7196f336 100644 --- a/tests/integration/serialization/test_serializers.py +++ b/tests/integration/serialization/test_serializers.py @@ -122,12 +122,12 @@ def test_mixed_serialization(kafka_single_broker_cluster, key_serializer, value_ topic = kafka_single_broker_cluster.create_topic("serialization-numeric") producer = kafka_single_broker_cluster.producer(key_serializer=key_serializer, - value_serializer=value_serializer) + value_serializer=value_serializer) producer.produce(topic, key=key, value=value) producer.flush() consumer = kafka_single_broker_cluster.consumer(key_deserializer=key_deserializer, - value_deserializer=value_deserializer) + value_deserializer=value_deserializer) consumer.subscribe([topic]) msg = consumer.poll() From 23c893d8cb47ba73704dd36295a31de05953360a Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 3 May 2024 15:58:02 +0530 Subject: [PATCH 19/29] Fixed transiet test failures for proto --- .../schema_registry/test_proto_serializers.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/integration/schema_registry/test_proto_serializers.py b/tests/integration/schema_registry/test_proto_serializers.py index 621beac43..26f213ee2 100644 --- a/tests/integration/schema_registry/test_proto_serializers.py +++ b/tests/integration/schema_registry/test_proto_serializers.py @@ -47,19 +47,19 @@ one_id='oneof_str', is_active=False)}) ]) -def test_protobuf_message_serialization(kafka_cluster, pb2, data): +def test_protobuf_message_serialization(kafka_single_broker_cluster, pb2, data): """ Validates that we get the same message back that we put in. """ - topic = kafka_cluster.create_topic("serialization-proto") - sr = kafka_cluster.schema_registry() + topic = kafka_single_broker_cluster.create_topic("serialization-proto") + sr = kafka_single_broker_cluster.schema_registry() value_serializer = ProtobufSerializer(pb2, sr, {'use.deprecated.format': False}) value_deserializer = ProtobufDeserializer(pb2, {'use.deprecated.format': False}) - producer = kafka_cluster.producer(value_serializer=value_serializer) - consumer = kafka_cluster.consumer(value_deserializer=value_deserializer) + producer = kafka_single_broker_cluster.producer(value_serializer=value_serializer) + consumer = kafka_single_broker_cluster.consumer(value_deserializer=value_deserializer) consumer.assign([TopicPartition(topic, 0)]) expect = pb2(**data) @@ -78,16 +78,16 @@ def test_protobuf_message_serialization(kafka_cluster, pb2, data): (DependencyMessage, ['NestedTestProto.proto', 'PublicTestProto.proto']), (ClickCas, ['metadata_proto.proto', 'common_proto.proto']) ]) -def test_protobuf_reference_registration(kafka_cluster, pb2, expected_refs): +def test_protobuf_reference_registration(kafka_single_broker_cluster, pb2, expected_refs): """ Registers multiple messages with dependencies then queries the Schema Registry to ensure the references match up. """ - sr = kafka_cluster.schema_registry() - topic = kafka_cluster.create_topic("serialization-proto-refs") + sr = kafka_single_broker_cluster.schema_registry() + topic = kafka_single_broker_cluster.create_topic("serialization-proto-refs") serializer = ProtobufSerializer(pb2, sr, {'use.deprecated.format': False}) - producer = kafka_cluster.producer(key_serializer=serializer) + producer = kafka_single_broker_cluster.producer(key_serializer=serializer) producer.produce(topic, key=pb2(), partition=0) producer.flush() @@ -97,7 +97,7 @@ def test_protobuf_reference_registration(kafka_cluster, pb2, expected_refs): assert expected_refs.sort() == [ref.name for ref in registered_refs].sort() -def test_protobuf_serializer_type_mismatch(kafka_cluster): +def test_protobuf_serializer_type_mismatch(kafka_single_broker_cluster): """ Ensures an Exception is raised when deserializing an unexpected type. @@ -105,11 +105,11 @@ def test_protobuf_serializer_type_mismatch(kafka_cluster): pb2_1 = TestProto_pb2.TestMessage pb2_2 = NestedTestProto_pb2.NestedMessage - sr = kafka_cluster.schema_registry() - topic = kafka_cluster.create_topic("serialization-proto-refs") + sr = kafka_single_broker_cluster.schema_registry() + topic = kafka_single_broker_cluster.create_topic("serialization-proto-refs") serializer = ProtobufSerializer(pb2_1, sr, {'use.deprecated.format': False}) - producer = kafka_cluster.producer(key_serializer=serializer) + producer = kafka_single_broker_cluster.producer(key_serializer=serializer) with pytest.raises(KafkaException, match=r"message must be of type Date: Fri, 3 May 2024 17:05:24 +0530 Subject: [PATCH 20/29] Fixed another test --- .../consumer/test_consumer_topicpartition_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/consumer/test_consumer_topicpartition_metadata.py b/tests/integration/consumer/test_consumer_topicpartition_metadata.py index 4c01c1df7..486231c26 100644 --- a/tests/integration/consumer/test_consumer_topicpartition_metadata.py +++ b/tests/integration/consumer/test_consumer_topicpartition_metadata.py @@ -29,11 +29,11 @@ def commit_and_check(consumer, topic, metadata): assert offsets[0].metadata == metadata -def test_consumer_topicpartition_metadata(kafka_cluster): - topic = kafka_cluster.create_topic("test_topicpartition") +def test_consumer_topicpartition_metadata(kafka_single_broker_cluster): + topic = kafka_single_broker_cluster.create_topic("test_topicpartition") consumer_conf = {'group.id': 'pytest'} - c = kafka_cluster.consumer(consumer_conf) + c = kafka_single_broker_cluster.consumer(consumer_conf) # Commit without any metadata. metadata = None From 0324fac71e45fa42448ce5d4257387374e02ee8f Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Mon, 6 May 2024 13:26:05 +0530 Subject: [PATCH 21/29] Added Test*Consumer classes instead of functions --- tests/common/__init__.py | 26 +++++++++++-------- tests/integration/cluster_fixture.py | 6 ++--- tests/integration/integration_test.py | 20 +++++++------- .../integration/producer/test_transactions.py | 6 ++--- tests/soak/soakclient.py | 4 +-- tests/test_Consumer.py | 20 +++++++------- tests/test_Producer.py | 4 +-- tests/test_log.py | 14 +++++----- tests/test_misc.py | 14 +++++----- 9 files changed, 59 insertions(+), 55 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index 04893496e..a0224a405 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -36,20 +36,24 @@ def use_kraft(): return use_group_protocol_consumer() or _trivup_cluster_type_kraft() -def _get_consumer_generic(consumer_clazz, conf=None, **kwargs): - if use_group_protocol_consumer(): - if conf is not None and 'group.id' in conf: - conf['group.protocol'] = 'consumer' - return consumer_clazz(conf, **kwargs) +def _update_conf_group_protocol(conf=None): + if conf is not None and 'group.id' in conf and use_group_protocol_consumer(): + conf['group.protocol'] = 'consumer' -def get_consumer(conf=None, **kwargs): - return _get_consumer_generic(Consumer, conf, **kwargs) +class TestConsumer(Consumer): + def __init__(self, conf, **kwargs): + _update_conf_group_protocol(conf) + super(TestConsumer, self).__init__(conf, **kwargs) -def get_avro_consumer(conf=None, **kwargs): - return _get_consumer_generic(AvroConsumer, conf, **kwargs) +class TestDeserializingConsumer(DeserializingConsumer): + def __init__(self, conf, **kwargs): + _update_conf_group_protocol(conf) + super(TestDeserializingConsumer, self).__init__(conf, **kwargs) -def get_deserializing_consumer(conf=None, **kwargs): - return _get_consumer_generic(DeserializingConsumer, conf, **kwargs) +class TestAvroConsumer(AvroConsumer): + def __init__(self, conf, **kwargs): + _update_conf_group_protocol(conf) + super(TestAvroConsumer, self).__init__(conf, **kwargs) diff --git a/tests/integration/cluster_fixture.py b/tests/integration/cluster_fixture.py index 7fbfeccd8..5a51d8781 100644 --- a/tests/integration/cluster_fixture.py +++ b/tests/integration/cluster_fixture.py @@ -23,7 +23,7 @@ from confluent_kafka import Producer, SerializingProducer from confluent_kafka.admin import AdminClient, NewTopic from confluent_kafka.schema_registry.schema_registry_client import SchemaRegistryClient -from ..common import get_consumer, get_deserializing_consumer +from ..common import TestDeserializingConsumer, TestConsumer class KafkaClusterFixture(object): @@ -105,7 +105,7 @@ def cimpl_consumer(self, conf=None): if conf is not None: consumer_conf.update(conf) - return get_consumer(consumer_conf) + return TestConsumer(consumer_conf) def consumer(self, conf=None, key_deserializer=None, value_deserializer=None): """ @@ -138,7 +138,7 @@ def consumer(self, conf=None, key_deserializer=None, value_deserializer=None): if value_deserializer is not None: consumer_conf['value.deserializer'] = value_deserializer - return get_deserializing_consumer(consumer_conf) + return TestDeserializingConsumer(consumer_conf) def admin(self, conf=None): if conf: diff --git a/tests/integration/integration_test.py b/tests/integration/integration_test.py index 013fd4d93..eebe84c5d 100755 --- a/tests/integration/integration_test.py +++ b/tests/integration/integration_test.py @@ -29,7 +29,7 @@ import gc import struct import re -from ..common import get_consumer, get_avro_consumer +from ..common import TestConsumer, TestAvroConsumer try: # Memory tracker @@ -374,7 +374,7 @@ def verify_consumer(): 'enable.partition.eof': True} # Create consumer - c = get_consumer(conf) + c = TestConsumer(conf) def print_wmark(consumer, topic_parts): # Verify #294: get_watermark_offsets() should not fail on the first call @@ -484,7 +484,7 @@ def print_wmark(consumer, topic_parts): c.close() # Start a new client and get the committed offsets - c = get_consumer(conf) + c = TestConsumer(conf) offsets = c.committed(list(map(lambda p: confluent_kafka.TopicPartition(topic, p), range(0, 3)))) for tp in offsets: print(tp) @@ -501,7 +501,7 @@ def verify_consumer_performance(): 'error_cb': error_cb, 'auto.offset.reset': 'earliest'} - c = get_consumer(conf) + c = TestConsumer(conf) def my_on_assign(consumer, partitions): print('on_assign:', len(partitions), 'partitions:') @@ -609,7 +609,7 @@ def verify_batch_consumer(): 'auto.offset.reset': 'earliest'} # Create consumer - c = get_consumer(conf) + c = TestConsumer(conf) # Subscribe to a list of topics c.subscribe([topic]) @@ -666,7 +666,7 @@ def verify_batch_consumer(): c.close() # Start a new client and get the committed offsets - c = get_consumer(conf) + c = TestConsumer(conf) offsets = c.committed(list(map(lambda p: confluent_kafka.TopicPartition(topic, p), range(0, 3)))) for tp in offsets: print(tp) @@ -683,7 +683,7 @@ def verify_batch_consumer_performance(): 'error_cb': error_cb, 'auto.offset.reset': 'earliest'} - c = get_consumer(conf) + c = TestConsumer(conf) def my_on_assign(consumer, partitions): print('on_assign:', len(partitions), 'partitions:') @@ -878,7 +878,7 @@ def run_avro_loop(producer_conf, consumer_conf): p.produce(**combo) p.flush() - c = get_avro_consumer(consumer_conf) + c = TestAvroConsumer(consumer_conf) c.subscribe([(t['topic']) for t in combinations]) msgcount = 0 @@ -990,7 +990,7 @@ def stats_cb(stats_json_str): 'statistics.interval.ms': 200, 'auto.offset.reset': 'earliest'} - c = get_consumer(conf) + c = TestConsumer(conf) c.subscribe([topic]) max_msgcnt = 1000000 @@ -1117,7 +1117,7 @@ def verify_avro_explicit_read_schema(): p.produce(topic=avro_topic, **combo) p.flush() - c = get_avro_consumer(consumer_conf, reader_key_schema=reader_schema, reader_value_schema=reader_schema) + c = TestAvroConsumer(consumer_conf, reader_key_schema=reader_schema, reader_value_schema=reader_schema) c.subscribe([avro_topic]) msgcount = 0 diff --git a/tests/integration/producer/test_transactions.py b/tests/integration/producer/test_transactions.py index 130f31f87..667bb706d 100644 --- a/tests/integration/producer/test_transactions.py +++ b/tests/integration/producer/test_transactions.py @@ -18,7 +18,7 @@ import inspect import sys from uuid import uuid1 -from ...common import get_consumer +from ...common import TestConsumer from confluent_kafka import KafkaError @@ -115,7 +115,7 @@ def test_send_offsets_committed_transaction(kafka_cluster): 'error_cb': error_cb } consumer_conf.update(kafka_cluster.client_conf()) - consumer = get_consumer(consumer_conf) + consumer = TestConsumer(consumer_conf) kafka_cluster.seed_topic(input_topic) consumer.subscribe([input_topic]) @@ -205,7 +205,7 @@ def consume_committed(conf, topic): 'error_cb': prefixed_error_cb(called_by()), } consumer_conf.update(conf) - consumer = get_consumer(consumer_conf) + consumer = TestConsumer(consumer_conf) consumer.subscribe([topic]) msg_cnt = read_all_msgs(consumer) diff --git a/tests/soak/soakclient.py b/tests/soak/soakclient.py index f6c9db57f..978257448 100755 --- a/tests/soak/soakclient.py +++ b/tests/soak/soakclient.py @@ -30,7 +30,7 @@ from confluent_kafka.admin import AdminClient, NewTopic from collections import defaultdict from builtins import int -from common import get_consumer +from common import TestConsumer import argparse import threading import time @@ -445,7 +445,7 @@ def filter_config(conf, filter_out, strip_prefix): cconf['error_cb'] = self.consumer_error_cb cconf['on_commit'] = self.consumer_commit_cb self.logger.info("consumer: using group.id {}".format(cconf['group.id'])) - self.consumer = get_consumer(cconf) + self.consumer = TestConsumer(cconf) # Create and start producer thread self.producer_thread = threading.Thread(target=self.producer_thread_main) diff --git a/tests/test_Consumer.py b/tests/test_Consumer.py index a869bce59..154fe5bce 100644 --- a/tests/test_Consumer.py +++ b/tests/test_Consumer.py @@ -4,7 +4,7 @@ KafkaException, TIMESTAMP_NOT_AVAILABLE, OFFSET_INVALID, libversion) import pytest -from .common import get_consumer +from .common import TestConsumer def test_basic_api(): @@ -12,13 +12,13 @@ def test_basic_api(): broker configured. """ with pytest.raises(TypeError) as ex: - kc = get_consumer() + kc = TestConsumer() assert ex.match('expected configuration dict') def dummy_commit_cb(err, partitions): pass - kc = get_consumer({'group.id': 'test', 'socket.timeout.ms': '100', + kc = TestConsumer({'group.id': 'test', 'socket.timeout.ms': '100', 'session.timeout.ms': 1000, # Avoid close() blocking too long 'on_commit': dummy_commit_cb}) @@ -120,7 +120,7 @@ def dummy_assign_revoke(consumer, partitions): def test_store_offsets(): """ Basic store_offsets() tests """ - c = get_consumer({'group.id': 'test', + c = TestConsumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -162,7 +162,7 @@ def commit_cb(cs, err, ps): cs = CommitState('test', 2) - c = get_consumer({'group.id': 'x', + c = TestConsumer({'group.id': 'x', 'enable.auto.commit': False, 'socket.timeout.ms': 50, 'session.timeout.ms': 100, 'on_commit': lambda err, ps: commit_cb(cs, err, ps)}) @@ -197,7 +197,7 @@ def poll(self, somearg): @pytest.mark.skipif(libversion()[1] < 0x000b0000, reason="requires librdkafka >=0.11.0") def test_offsets_for_times(): - c = get_consumer({'group.id': 'test', + c = TestConsumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -217,7 +217,7 @@ def test_offsets_for_times(): def test_multiple_close_does_not_throw_exception(): """ Calling Consumer.close() multiple times should not throw Runtime Exception """ - c = get_consumer({'group.id': 'test', + c = TestConsumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -233,7 +233,7 @@ def test_multiple_close_does_not_throw_exception(): def test_any_method_after_close_throws_exception(): """ Calling any consumer method after close should throw a RuntimeError """ - c = get_consumer({'group.id': 'test', + c = TestConsumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -297,7 +297,7 @@ def test_any_method_after_close_throws_exception(): def test_calling_store_offsets_after_close_throws_erro(): """ calling store_offset after close should throw RuntimeError """ - c = get_consumer({'group.id': 'test', + c = TestConsumer({'group.id': 'test', 'enable.auto.commit': True, 'enable.auto.offset.store': False, 'socket.timeout.ms': 50, @@ -320,5 +320,5 @@ def test_consumer_without_groupid(): """ Consumer should raise exception if group.id is not set """ with pytest.raises(ValueError) as ex: - get_consumer({'bootstrap.servers': "mybroker:9092"}) + TestConsumer({'bootstrap.servers': "mybroker:9092"}) assert ex.match('group.id must be set') diff --git a/tests/test_Producer.py b/tests/test_Producer.py index 2869aa632..61dc36bb2 100644 --- a/tests/test_Producer.py +++ b/tests/test_Producer.py @@ -5,7 +5,7 @@ from confluent_kafka import Producer, KafkaError, KafkaException, \ TopicPartition, libversion from struct import pack -from .common import get_consumer +from .common import TestConsumer def error_cb(err): @@ -212,7 +212,7 @@ def test_transaction_api(): assert ex.value.args[0].fatal() is False assert ex.value.args[0].txn_requires_abort() is False - consumer = get_consumer({"group.id": "testgroup"}) + consumer = TestConsumer({"group.id": "testgroup"}) group_metadata = consumer.consumer_group_metadata() consumer.close() diff --git a/tests/test_log.py b/tests/test_log.py index 17a87ebae..813840421 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -4,7 +4,7 @@ import confluent_kafka import confluent_kafka.avro import logging -from .common import get_consumer, get_avro_consumer +from .common import TestConsumer, TestAvroConsumer class CountingFilter(logging.Filter): @@ -25,7 +25,7 @@ def test_logging_consumer(): logger.setLevel(logging.DEBUG) f = CountingFilter('consumer') logger.addFilter(f) - kc = get_consumer({'group.id': 'test', + kc = TestConsumer({'group.id': 'test', 'debug': 'all'}, logger=logger) while f.cnt == 0: @@ -44,10 +44,10 @@ def test_logging_avro_consumer(): f = CountingFilter('avroconsumer') logger.addFilter(f) - kc = get_avro_consumer({'schema.registry.url': 'http://example.com', - 'group.id': 'test', - 'debug': 'all'}, - logger=logger) + kc = TestAvroConsumer({'schema.registry.url': 'http://example.com', + 'group.id': 'test', + 'debug': 'all'}, + logger=logger) while f.cnt == 0: kc.poll(timeout=0.5) @@ -149,7 +149,7 @@ def test_consumer_logger_logging_in_given_format(): handler.setFormatter(logging.Formatter('%(name)s Logger | %(message)s')) logger.addHandler(handler) - c = get_consumer( + c = TestConsumer( {"bootstrap.servers": "test", "group.id": "test", "logger": logger, "debug": "msg"}) c.poll(0) diff --git a/tests/test_misc.py b/tests/test_misc.py index 91f0be3d2..bab35da28 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -8,7 +8,7 @@ import os import time import sys -from .common import get_consumer +from .common import TestConsumer def test_version(): @@ -41,7 +41,7 @@ def error_cb(error_msg): 'error_cb': error_cb } - kc = get_consumer(conf) + kc = TestConsumer(conf) kc.subscribe(["test"]) while not seen_error_cb: kc.poll(timeout=0.1) @@ -65,7 +65,7 @@ def stats_cb(stats_json_str): 'stats_cb': stats_cb } - kc = get_consumer(conf) + kc = TestConsumer(conf) kc.subscribe(["test"]) while not seen_stats_cb: @@ -138,7 +138,7 @@ def oauth_cb(oauth_config): 'oauth_cb': oauth_cb } - kc = get_consumer(conf) + kc = TestConsumer(conf) while not seen_oauth_cb: kc.poll(timeout=0.1) @@ -163,7 +163,7 @@ def oauth_cb(oauth_config): 'oauth_cb': oauth_cb } - kc = get_consumer(conf) + kc = TestConsumer(conf) while not seen_oauth_cb: kc.poll(timeout=0.1) @@ -190,7 +190,7 @@ def oauth_cb(oauth_config): 'oauth_cb': oauth_cb } - kc = get_consumer(conf) + kc = TestConsumer(conf) while oauth_cb_count < 2: kc.poll(timeout=0.1) @@ -268,7 +268,7 @@ def on_delivery(err, msg): def test_set_sasl_credentials_api(): clients = [ AdminClient({}), - get_consumer({"group.id": "dummy"}), + TestConsumer({"group.id": "dummy"}), confluent_kafka.Producer({})] for c in clients: From 264be1446c951251b7475cfd9cef09f7813cbb23 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Mon, 6 May 2024 13:39:31 +0530 Subject: [PATCH 22/29] Build issue --- tests/common/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index a0224a405..1b964117e 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -42,18 +42,18 @@ def _update_conf_group_protocol(conf=None): class TestConsumer(Consumer): - def __init__(self, conf, **kwargs): + def __init__(self, conf=None, **kwargs): _update_conf_group_protocol(conf) super(TestConsumer, self).__init__(conf, **kwargs) class TestDeserializingConsumer(DeserializingConsumer): - def __init__(self, conf, **kwargs): + def __init__(self, conf=None, **kwargs): _update_conf_group_protocol(conf) super(TestDeserializingConsumer, self).__init__(conf, **kwargs) class TestAvroConsumer(AvroConsumer): - def __init__(self, conf, **kwargs): + def __init__(self, conf=None, **kwargs): _update_conf_group_protocol(conf) super(TestAvroConsumer, self).__init__(conf, **kwargs) From bc4896fe6905c67fdb2f09cb132cea04a100cfe4 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Mon, 6 May 2024 15:55:11 +0530 Subject: [PATCH 23/29] Added common TestUtils --- tests/common/__init__.py | 22 ++++++++++++---------- tests/integration/conftest.py | 8 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/common/__init__.py b/tests/common/__init__.py index 1b964117e..a0d54934f 100644 --- a/tests/common/__init__.py +++ b/tests/common/__init__.py @@ -24,21 +24,23 @@ _TRIVUP_CLUSTER_TYPE_ENV = 'TEST_TRIVUP_CLUSTER_TYPE' -def _trivup_cluster_type_kraft(): - return _TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[_TRIVUP_CLUSTER_TYPE_ENV] == 'kraft' - +def _update_conf_group_protocol(conf=None): + if conf is not None and 'group.id' in conf and TestUtils.use_group_protocol_consumer(): + conf['group.protocol'] = 'consumer' -def use_group_protocol_consumer(): - return _GROUP_PROTOCOL_ENV in os.environ and os.environ[_GROUP_PROTOCOL_ENV] == 'consumer' +def _trivup_cluster_type_kraft(): + return _TRIVUP_CLUSTER_TYPE_ENV in os.environ and os.environ[_TRIVUP_CLUSTER_TYPE_ENV] == 'kraft' -def use_kraft(): - return use_group_protocol_consumer() or _trivup_cluster_type_kraft() +class TestUtils: + @staticmethod + def use_kraft(): + return TestUtils.use_group_protocol_consumer() or _trivup_cluster_type_kraft() -def _update_conf_group_protocol(conf=None): - if conf is not None and 'group.id' in conf and use_group_protocol_consumer(): - conf['group.protocol'] = 'consumer' + @staticmethod + def use_group_protocol_consumer(): + return _GROUP_PROTOCOL_ENV in os.environ and os.environ[_GROUP_PROTOCOL_ENV] == 'consumer' class TestConsumer(Consumer): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 9ed047017..0adad7926 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,7 +17,7 @@ # import os -from ..common import use_group_protocol_consumer, use_kraft +from ..common import TestUtils import pytest from tests.integration.cluster_fixture import TrivupFixture @@ -29,7 +29,7 @@ def _broker_conf(): broker_conf = ['transaction.state.log.replication.factor=1', 'transaction.state.log.min.isr=1'] - if use_group_protocol_consumer(): + if TestUtils.use_group_protocol_consumer(): broker_conf.append('group.coordinator.rebalance.protocols=classic,consumer') return broker_conf @@ -38,7 +38,7 @@ def create_trivup_cluster(conf={}): trivup_fixture_conf = {'with_sr': True, 'debug': True, 'cp_version': '7.6.0', - 'kraft': use_kraft(), + 'kraft': TestUtils.use_kraft(), 'version': 'trunk', 'broker_conf': _broker_conf()} trivup_fixture_conf.update(conf) @@ -49,7 +49,7 @@ def create_sasl_cluster(conf={}): trivup_fixture_conf = {'with_sr': False, 'version': 'trunk', 'sasl_mechanism': "PLAIN", - 'kraft': use_kraft(), + 'kraft': TestUtils.use_kraft(), 'sasl_users': 'sasl_user=sasl_user', 'debug': True, 'cp_version': 'latest', From 2c8423469f25d0756dd30495b3a4ea9190540546 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Tue, 7 May 2024 13:50:24 +0530 Subject: [PATCH 24/29] Using specific commit for trivup --- tests/integration/conftest.py | 4 ++-- tests/requirements.txt | 2 +- tests/trivup/trivup-0.12.6.tar.gz | Bin 0 -> 32862 bytes 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 tests/trivup/trivup-0.12.6.tar.gz diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 0adad7926..e680b60b7 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -39,7 +39,7 @@ def create_trivup_cluster(conf={}): 'debug': True, 'cp_version': '7.6.0', 'kraft': TestUtils.use_kraft(), - 'version': 'trunk', + 'version': 'trunk@f6c9feea76d01a46319b0ca602d70aa855057b07', 'broker_conf': _broker_conf()} trivup_fixture_conf.update(conf) return TrivupFixture(trivup_fixture_conf) @@ -47,7 +47,7 @@ def create_trivup_cluster(conf={}): def create_sasl_cluster(conf={}): trivup_fixture_conf = {'with_sr': False, - 'version': 'trunk', + 'version': 'trunk@f6c9feea76d01a46319b0ca602d70aa855057b07', 'sasl_mechanism': "PLAIN", 'kraft': TestUtils.use_kraft(), 'sasl_users': 'sasl_user=sasl_user', diff --git a/tests/requirements.txt b/tests/requirements.txt index 407aaf0d2..3b113a623 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -5,7 +5,7 @@ pytest==4.6.9;python_version<="3.0" pytest>=6.0.0;python_version>="3.0" pytest-timeout requests-mock -tests/trivup/trivup-0.12.5.tar.gz +tests/trivup/trivup-0.12.6.tar.gz fastavro<1.8.0;python_version=="3.7" fastavro>=1.8.4;python_version>"3.7" fastavro diff --git a/tests/trivup/trivup-0.12.6.tar.gz b/tests/trivup/trivup-0.12.6.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5417120a69a5cb6cf73cb0d5ecdc8f6ace6fd4d0 GIT binary patch literal 32862 zcmV)bK&ihUiwFp%?>J@x|8#O`c6D$qFfK7NE;cT7VR8WMy?cM#xUn$Yf2&V{D#@Xg zO0?u_qE`D`#dZ>J9NVv@>~`z;(V;}j=9(f^lJaF!pU?iz3;+_mNwVWjw=K18ERn!q zFc=I5gTaixrmuZ}V%)yB4Ab)e@SA*E{8RL|)!NvMzvJ_EYrVbx2X_01Z}6E1q2WXG zfB5hGtiNK@(4Jb|_D*}N)!Ny3y`yh!@2u}^Zdd+Le*WV>p>JQ$XU&%0Ue~wRe!Ka< zv$chP+dEq=^=~xi+nd{M_-|)>a~tOW<~lrLTiRgJCh~t>%Kz=H_1BwQ>szn%Rw?=aOXdIJ!S4Q1fB!d_|J$3JMe=`rbA1Qo z|N3ToW2d#XwGQ)ttG%=N2iE$Q^Z&n8{_lFTJKw&z3|VblXX~x@b`$=2-C!S$3wIu{ zebaI;ZO5tf9EVZCfcaKn`PY`IS1PBLX$QbVqj_k1E;C${%>#?sE(^T5Kep(}$aW3? zj!is&8Z_9A9bPier@!7jtV})Ap4ejp0W_H5TWn_eQ#%YTlg)hZ+BV@!cxi<2j|D&+ z$GfrJ3pVy#)5b~xRhn91r&4*zk{AWdn@Ci~o(aWZyrBc30VpCoW8_`qD@g^{3+=Jh zfOmFKVax$o2tsYlHB*>CgJZ`4A?oXS7(h!31tbR0HFFMFEkFX=M>HOa1QT5|z43f% zxgk*&U_qTVpt}d}d^R;g%eM_Dh-pj%LlqVDp+|%F2YuE*emnT7ce>Bu`^o9?j|Y4E zd+g26Z18@c?H-@}e0uQi{eZncKHS?s?X%v|9y~o7oF2USG&qI_)m|TJRx9|Tcl0yc z|J%vwe!tI-Puao8lfwf52GCD?M}vd?euEtx?H+#GJ2-mRU;uy}9S_*y!N-FE6dN2j zu+d7k8asZ=KJK6HzK5T^HwT9YgP#eJw+DkGZ0jvF)?+8V)4{>+r^DVUJNa~aa@^l% z(5=eeL4WtKckpq4Plwi_MYjLr{?UN--}ep=b9;%nC;IqiACT<5Ioz*MOVG=`gVX)p z0d^<;whNR4{0I0GH}pWgHb2ZK+8efIA7cn=9(>F=NZ2=cxE7j}5uCu)7_?>E?9Z_uNL0R)f= zUc>h{pZW(xw1cC;{^{wblfl99Q5|~z6OiS!vfG2IdqmyiBkC$p==k(!1d22u;y2h& z@Au*PDH4<@)k6~WfnvJ@rC0^p0|E||KC+|zcZUb>_K$Y=5u0O#^3y?ozYb${(8mG? zyxE_6(B>!VGL8{oRiUp6$2Mpr*}+@Z+xzhV(H4ba?E43TPl#l@?-?hz{@fG-S>Z zYsPFAu$R5rZ0%(wE@a)Rh1kpgD@8@Q{(trQU!MQ`>(>AB{HI+1qxJvf!@K6e(c9y1 zYX57uX#H2uf40`Qx61YZTYNrRp^epe z*c01=$|oGDDO$@84fkRWt+Nh1zXKpHe%p9p`SnEkLwjtw0Tns!A23jzeDB&a@%Nkl zo_w9Cay+wqBg6*!cR-dYugeZx)0$cEj~iA*wdPyf0a^(IedW~p$K3XOo4EcL%{@OdUpIn4#?oH3g@;)FJ+R4RWubL^3CUXel{Tdu^d`NqxK z)IgYPdJJGl0Q8xAQLntL^)3Gz)uCX1aRJYv!TQTeMWAS-8aZ*STYKbK4K{P2kU?J^ zJT`6KSojBC7=8%7a19$e1VF|OHUUM>y7Bz0%Ea@j%eHC;UPWtI`x&>D6X3Re{REv8&tuFgQ^H0{a6@=+@s z`Q8=4zUHbqV))iF!nqIXCK~QKl?r$wwW4tQt*>%-fr(LA*-@YMJYUw^Rtk>z^<+hQN~c8LJ* z`u*O?!CLgj0L2~Z3kqYUF!P zV06}IFpfrOv9aZc+=swo7nVypH9)OYUcT(_?|wSP6TY8%r$+}z?_R!?`x>AGx-`sP zHg`c&53O-%ndqKiT1^kMZ{Q=(^PRiq7da zPj6t~z>f9@KOLWb=mWDgl(K_HXXo@S+@qVF1#6dEST9YXXqT(iN-!X^2l@W zc`h~=8>~4Ml-31+Kqq*+0$j5x*q3eSZM_A*1HV!^f!z`N-)8gDa%TTaB$#@>B{&ut zy$9SmY6kG$;X^pG7|QTax-b~yF%l7`CQR`Nd6-u-&jq|;Q>tS7{6p{Uhu-j{H+a7` zvfVYBZOzb|+2cSDE&;1wJ(z3A`t14NKMZ?&d*r$BS&+Wj9QzhBUXxd%Ne(|y2nlD6 zP59|s5pOgn1`Y3qz<^qWSFAZ=&)=LLe?Sk4<{|VnKo)|rvAOR{+;999j|!&&h=e&h zkC@&Kcf>I*#{!~6Jw-Tj=uKtF`KJSXo04Z^Qq^T0a=q2K(W3AHN@2A&?2b zy@ggm6m%F))F5!=vy_T?4?MZ+;QuB;edb55Y> z-81I-4bH!T7f~qc3XLtH)L8_oIhi|-7;hBeIM5**oF4r6>123%d_3s>pIDh;HA4|! zL#M1uSYJ?%prl~qSPOlyKdf?ifs>3L~f!!j5KL`d$1vd|ML8&JpU;_zn}eoV1=lzO)kFW{=e03@1)Luwp;6^|Hn7^oK3|6(0RqQ zg0XMUxQc4SKDNsudr+lvW?ftiZFl0GS3=`rIGWpz2_@)ne^|G%mI zU#$PHHcI>dfBE`9yLj0s0zxo)gHI6WcY1(yqY^T{zxIzQ*sB3hEW07nt@qtko;VYz99x&T$&OJcDQF z@DOh}L5XMVEr3POiGXaJWUM#wJ`=*LX^Ly=8oH)xb#4KPLH)VQ&Th|cbwsbGQ9&ql zPG+jx7|;u-udx@*tmha|?FqX*Yqp`*p8(ze)6Odu>VM5OKu${hr7c+4FuNL`LeX-Y`|1dgif2-9N;0^M8>1rvDn5cRFen_4%T`lVw6BV5M@P7H}O;q0D z{wPD#5QFZSCOlHKb6G$#MK{4BwR-A3Dvd@L|B)|*p=&72W&m6n98J2oXsQujlMXJ) zr?GpBCLTxFjU(@yB>d_M-a*yI!z!#NE3D=pl&rJdK`1JJUVqH8UD|(2`%h{ADfK_D z|M8Qa-(vnhEdI;Z#^!n%|E1jjm-7GH?f*;vzf%4epY;Eh@_%zX75@ch|9a{F_f0;} z{xQ{fx>h2A@2qpv=L4Zk&E8Sbgoh0lZB_I6qFHGezvZ8xlGeRSoa z_%34qTUYDqD@{?gx4cz2&$2ju-fnBV{VCR|ya07*h-c0z28BT4Zm4qfcD13@C z?R&FY{Xa)Qmg|4H{+H{2x&CL?|9;>+)j`0L^}oHbm5%=gZ+FV||97(fOYeKW;LEjM zErE7+9T=uJi;D4I6-6b0CpBTOj}-x_184&%vKv{vwQxFvKc+RBUvvll+!9OlGjae& zz~puvH~@mTR1+{oK|FSV*E;vj9bQ>?uyFfUO`G0v4M8mHV$J8pOeV{9*oy$q2Wm#6 zE+G0zi0E>T(8i99e(*)mBp`ngl)!Q(fqmgx<}d=tYVsE^u0B$u2(ueo|>0JC@^uN;nU+RCc z{>Km4e}nn|K>PpJM!U5Cm;3)x{+Ijz^7GB*|8R(Rorl9GG5{=#|Gu@encn|zZxpW$3J6F7&`tLd&1!H&)5@&j|bWE zqyglgv8N3o|BO9J5c$e*i2kfY)@5hpi;R{{d77%7|1Pww(*9H0e@gpLssAVRf4VpE zueAScZ({zZ(*9G*|5E;!^1uB2E95`<=)IxCa?AqpE#?1ud!xNm%Ky^-U&{Y)Xa6Vp zzx`^vRoeec`Ja{lrZscCJ3Y93a{0ft1M^?V|LyHI=z{AT?XA-P|6AGr(e^(wf=jac zqt%c`n$^U8?7Fy*j%MV)N1-s|kg@3W$8RH3&{MQR*9p##`=|Yb<0BSWQv*Z21|2jk z+Y|O&zQF*x{@lH4(4hct=E5eMH^x1bu{rk+&}7Ig2MUcZt??BCkx>Pekb~B(9fSd0 z1CUKb^`he5WMYpojIXe?Qy?R5w?5z9OjkfU;sdZ$$*;O}#sy(!=A^;r_>y!Oz3~;FQHM{=(ygw(FJ3;qkk-2Z#IJ=e6lo zXiaDJ3Wxj*fj)o9jF3IAVa%p_m3_yai{eqCxHB>I@04{_*A^yYLOuv2NsFeFRiV^iqU^h=z*~BB1scJobn~_$Ir&i}3M` z1=|>V(Qe10qkg|rKvQr;|}H#4A>F6Z8Dvc2M8#7 z{l_Fur*qdulz6eKaMIoNrZWs6hS63lf7I6`t$#5v>MsTnW6fI;DEF3Q8-#a`^$`!; z5>E!AOfd7p(flG1CFD zVL|1p+D+a-5n4G@1Sv?;j}tISur!>RH#4)GnuOY)V+>>r0Lukjs4&WXWt49AY= zTDg<>9WoTWMI=90pObj|T;)|8_(VX4XPhZ1bS*rO=IG}t@7INZgt3wZ|3?^l|N3eV2xtUnJV)uf8-64XyKjsbb6>l zMWmt^-Kr#q;g2uvYfI;Ai|+XsYj^_7v0QupMfCliKNzlqV%LnH>fyz&?D3%5DNxW2 zVCz2)PJc!u`(3pwl|RZg*D|{;C&SBsn!LN` zzQdY_tQi0aiFmMvj?H-KMwt(Tz;1t?uqN^ssJF9b903)G$E8n!HWE^3zyE zYsep?HDA^+96-QSyMaTIzs3uJ6|F{z*%8gTIA4g$xGAAR^%YH_vTU8>dZ6&fh_yRq z&F6|R$=*zOf) z%m_5F1d686w|EB~Sg?Ww*{TUakRx)r1`Y$n`<2UWtQOo&srgV z8F$Xabp-wSM8coGHwkahZSt)*cTMsOWVK%ZZJohSj9mz7yhzScc}6-EIRU~W5h0Kn z7qA5KY#xkZk`aV{bUZlN-DebEI`SM8?t}n`d>AP!2PGCUxeue^Fx)iyQ;vpjT^PQ} zJv^l&B&bN1A6(iq^uxn!O@OMzB;a7FP3Yv$-dyxS>8_wcgNbaXY#qelGgiYQGIC$F z{uj!NF*WWedk7k>V#wC**qVi?Q=x|G*szJQ;%-GP04;tNFuW0Ym>Sd#RPK~w%m~RQ z1s_z`L0iAMxluKB*|#+jfU!vkLyb=^=5UIWkBshQVxkdX=@>USY&14B;#Ke-wL7&wE4-HrbnA|Dcafc|RQt-lJ!o>TO9*ihBp>4@f?Q=z;g) z;ApSGESp?St=k#pK=LTWh+~r{al}3n0m|ZqDRE}j7=44GUm^nIg$LU-+N_e3a1clz zSb*Z#Q#%y-nKGTyD-|x|e9sFHy^9OW@9yot`ScFx5}N3VEZze(G4vwF0>Q^!7!5fv zyudEN_Z+Jy%H>zn9pf&y_K6XO(1SyIy|6AeB*Kr1O5L*;<~e&YV=tzx_F`Or;bxE~ zOR5p*sd7vK2 zu0?&)bE+?t6mj`Ha_2yM6uArGtVV87t>?Z=`kv!3%z#HBS}21mYUrXlhqNcY z&g$a&60{xMxnq6Lniz8@+`op6!Fy24oLJ3Dtjl?zcmNT7m;QXhfb}a^1!ZvZpu+lz z!21M#;MN1w`RDT4;1W%#E5O6iIcOb()>!}bN}&JuE8YQKJ*WNd!O`2}G}t^mT`R=B zl77lp0bFQZ%X!|#T&E3|S{o!Q(4Okr4)2x#nP3nW81e<}0y)q+?)hE3nzNs6GOw~w z?LP3{*};;!8GJMKIifi&DT^;E^ADm*s;(eL9#2LBOm-wI(ux{U>J~QuKXllR?DFME z@J4EJDrz8AH?I*=(ezjsyofuJqrGJt%BwF~hu7tMWJ)6mvV!d67Qd=#jKewX?TtA= zV^EiG`eQ+uM~lk;X^#3gF|2g)5vFtwP z0AN}Er}ifLe{St;wzju7+UWnkjmH3`{r{WT|3!GmpeI<(hAPP>6TPzHtxv^cr$OIM|>7jV^{k~$D*T_!+)KW{tkB3TO z)MsGNgb=)oA$*IU1&N35e5%J8*x?mC7{(8v*?H)RxP`h%_HMaoA;-4i-B83dglj?BRv`zYQvZ5jgtPLbc2eri!8&*Pz1bg89Hj(K*a~eIDQdeS&{G zhB>v}ih)rjbW9q4=ed?{PjuJQjj^Lo@Zg9Osg$#&{#WXMzjyr)?|-)2uS@-})c-R2 z-`9i!Tde+LfBR~i4SjQ>@}|0?5uEsg)B z_s9Yn=pP^K?J700Zv@gfrSVLU z7aIs;Kt=qu%b_P!l-9caayOTCEC~cfJEf z$F;~qe=ojc8eKyrFmvwk_Tki+gt%5%bp*r{Lt7IjgPGPaF+oG-1fXM@N%si0tPKE3 zUj!Bclj!^RGs8EgQPK+h?9P23q@{e8DGH$Fqg}F}%z3&gN`>8Efn_mJCSco~t_ek= z&k-&~bQ2-;m_Ns3*Gg2rKc69iK@d}8n7E>&-ZptD6H~LMAgTNfz~<=acmqo}zx@ib z9vvFprVDF;Z;y-eGe`);A^b+i%m^{!6S4Sb6BmT*61DHzOm>oPjR#Oaih_%S^9RTce)gGc4Dgp`ZkK3$Q_%ZQ+G_L*&UrXM!NrFtp2G z46!)0PI+3m0|Gq}5?0vr?`WtyQJ2!3ma&Ke#C;k@M!%^!(gieDoHr^i!?bg}mZ?n* z%1f1$Sox)yMgynCI8ZsUvJU0i*kzc$54 z2y2?I)z8m58|U%WOMYwNN5h$0`@Zk_Ie27|rvyz45q{lZ_`mZM9e%xnmohEW#CFXg zsCYF^Rp^vePL2uG0&$BfS`s_haZOn71N*MKuD@b8urW4V(v)CVOX^?~)j?@VV1NIR zs4byOtpCc+)*I|qgSF1HYm;xFdb3Z|g2G9Z(OeOsAw_6%wdPG-`KY6&1|P4&c8be# zTB|hCgapv&77E%@lG&_s4sX&Mr#m=dXhP^hlda7LQZ9Dy`3EbT*QK0>B(`7mpX~& zO5HZCAcTQY28$jm(9VmhjzFA5!sCd+H%PIW~>O$*~bDG== zrwPRt3k*k&H_8er{03}>QlqAbm|qMdkTv)_1rr4)DNd}^&agr0CUJvFTR(1tIzBwy zKW#889P2s2BEdxfip43#f{(VBT3s&C4XvIP8MG86hbbb4@a?W?GwzJ5`<`P3U9R3Y z;)DMb*JbOg{{8|<9c6aVjk}!!lI&~*DYtx<)3PZ1D%Vt9h`#QZdrjvTKWmjGYm6c= z>-B^VNSm#I8%*LALh7YBNN2Z^YLy0?(qD1dV6+M*Ac{|8Y}GU-%EhCKgXvfZ~%3DM(40=8=^0v1YV8Zk9}Hbx^W! zH2@i5n$$oNua!{lAEtOzkUb_I(Lbx;EusO3nw{fQV}|lBnl6!#j`W<-p+r|>e`cQ( z?Zp$nldv##q?IYAdMBrLvP5K&>O{E4sDeT~G|?jKOHq_o=3y8}!$ci6*$(bGmh2_Y zTIWPgb!_7%)5H1^$hCL8`(gi7k+WWGWMh(kOwuoZ$dT-S2D&#mu$YG0N}@9hCl*+p z6wg;Gh0;m|qc8`WoCDnb%l9>-o@^hj+|WKd@bK zI7kIz(!$8j1AYM<^<#tIq_5TW#9mtv%g||k4{)zkQx~S3t&kPsC7@Q~MOM5kMHA{{ zk6TpG@=InD6ug!(zD%yYQ3WhRBD!|lkf$1h@$3X13rLYI)?Mp1wOnx1e@e1P zHX1GWl~h~n^?Fv?$EyVy;Q@iLmK@G{1C8?C1S{0f^EN2d7;3H5RhUv|?QM!a|}hNnDFWxBPTsF_1K^!kVzSooAISJHBPMyJi)!Ir|k;Q1fZrc*`O5@PaMS z4(dD!GO_J!WCy9PDvfarC_(3llM}0AY<k+^p^uu0mabG&8kd$@qB)gN3%BNiR1J-9N}|U}-6Q8j;sKk_yxOUKU}Q*$X_DtD zoF(Xf=C>M&;A57RMg}1)h6}+?aYG}T1;QMO2wzRGBb^U-B^y3 zoQ8R{iTFdTo=78P5M>J03wP(dOdeRyAFnf*%jF%u>dO?$=Vv)Y+L8~ax8N&lkA_c7iCL5%!H6oKk;lw zOVQgLUDUKg*`E&MW1lZa@~ZZtQDA8(l|=^b{LxFxd|+5}7+^RVYZ%}Dt3@hZa?8DR zyAY$DI^2x<9oJ4&jO-E79HgbQPX-qDMcrfMZ;;7bpC6x^vDbdkEg)24*yQX{kgKQU z48M=f^-gZ~pi@^}=dpJ1Q2EBVtEA-|0$a3k6?DZnv?~O+G+2Th%n5Qq6*GZ4QdG<$GfQ2 zG`y~RaP+PN+O^uAH2;a?^cMk8Yl)1~tlOD2MjseFT31id8#-)GVIuM|uRmarGgGvt zDG?Urwe9KDbK~8S+_7T!0lF964pXcOltrmHpjTz zEN!%3e#8tqX*lG^1>>n1>6NvmsB1rb{M+#4_;k>%R$-!JG;q%gVGnA|#9;$IT|TR{ zW|RD^001oT0{2zweTJFXJ+nh$6bZVUt=HR)Xb4^e=byFJ+DAW}@UDqFenHsi8Tnvg zOP@88MYWZNG<}}3nx3{)=Uoj9uA{(64K}aWD~sjBS+jjkYs6o)O4^pBan}xQnNCok z-&_f25?$62z-ild(kZN7Su!{r z9n{bP=*nmouU4z0_Hcba#)|$HNEdR!Fa#NIU=u1LGGcV`5a~Mg$R>GZVJZS^Y|K-R zBQc;G3tE(>N(v~1p)sKcL6d_I#-RH8wx<^WK--tWX1?u??U~`QDc(_zJmlgRL3Fny zA5P>!DHO3P1B@;yS|owTLiic4JhGL_-nQ1H85&4nr_gaw& zrY)rtdXbX4a85<__0ZD^uI|tA!0`0b;eO|=%`f$y*Yub5VD10uFV{E*Ai)4t*ZYUj zVf$OHc2+uo@C3O-9I2e_G;+4UWCI7~FXd24*M(V)hPwOZzFw6Ff1LgDkN+(98aH>a zkve^|HH2OB+k1hdCBXZTq3{uQ+h2G3JrBz5XM=4vi6g>qCh~W7Aykhs$w+Z6QL6kY4?E_ zVRSF13%!y-%FwfXWQ?!$OCIS?r;9M%r8~+5JbY-G`f(4Qy`g768jfw^3Cga3F9PW; zW$35qn)=|En=6c)Va_}#t=xv|8vUjn6!(a|_Y3|~YFNlb5O=)S0(J`2t<&LecF|#{s zYr+wIDL%zIZaiyc-`NlS<0DWPN1$R|S#FVH#iJ~;05meGBg{2i@w%cB}OeJJph@@nE}G1TPY|r|Lj;tBG-|K9UT7 zNn8E1MehId+0_cq>$&#?q&7s#Tj#4b6I}m;#XIX%CokW?UI;IBvYHj@ zut{6peGY4(+Kb*YfUx*Jg#RIX;4LuD9kN_aDzW*y>Gc%gQ>H_KS``@VAfq9rnZYcHnsqY@sbKcPK{>UGRf8Df1qgZ$MQ^ z`cNsLWFI}esWr!ZKH=O2N#xw2>x&j*?2{}6c4@)5<@r9y4$`_Oe^m0tu{?1=<3{M$ zWa%Uk-(jQan1}h^9+Tn~cZl~yhw@$n3}X6zCE&1JQFyS}r3;W>4T^K`ke2#E)CFAg zY4MIdCh9sURY7Qnb0`oYP%ktSD%3qZez*Ap&zV$1&r`}Dj*+2(i-d^JUIENmbdz_y zHh)=tNYc5?RFfBc#g28!DSDAgm$%+i*H%$mI0lI;FI~j6v%0>b_BrkF5}^eMVOB`l z!RZy=D-!mnln6PU^9V$k7Wu-**^G0=1BM_M;@!}9n!A;(-I`(m@m(IB0lQw4hhspi zcSorpFW+N=CQLa6a~uKl!Zo@eAy@cZU#IA(QF?8P#h@myEHw%yX+0mBH;aIw7DXE} z-pMF-i0U^@lChKHyAoQ z(fJ?wtM_(zaI`;o==>M8`^UQ>$XTHN>uW@0eNl1?H{0y20ZxQ7Z{^>A8IND!io(xe&4=#D&kdQS)_T`>6 z$E@hG(9#=p3H5$Hx6Q@^N52BDB37VobnsxEPOzY3vP(Fe9Th|9m!d=X{MKIpYN0iy zcd7*R(NQYpS(CYtEJ%n17s-J;j9d%&@WCvmG{F?$Y`IG;;?Q8e6cO==Mn;rj?DQDq zjck0W4kZM~PN@7dSqEH zPjc*rwt+S&VklLqK}7@MDEP2i9$+reZtAojgVLZp%*UYuDvprAcTq#z{2tYo4$cZ) zy*&~&)MaPM`#fLnzh89E?Urw;8t`R-w`n9p&|ef!=j)aH3k!LMeLjHta7gy);jo_e zHdVP>=}VHMF?^!qB4H&iCjMjY`SYoM?S&YG_{%*i zCPie693B&R)pI4(GRdT~er!eLxw9!6h+MmpS88I1;ey#x&nub~l?tAskrayJDs%pA zVKKa7xlfKQd_(Il4D!S*0{Ho$2%`5g4YY0 z@CC{aM;MXJZCO0Ts{o>qpoM|~rx`3%R^%W7OD%rZzZXnfEC37NZ>zV6wic=?iaJt+EL2iN49+iek#tFm6lDsrrkq;!{L5o%j0*9+ zvOw2-P&|s2FP^BYS7IM`488Jg(-Hx!#2E5*cs?@?ikgeathC3X$iTUAAXw4;o-D;9 z##>#J{vbE%;z8M#PF;b-+l#LFID zPOZ>Dla-pgM3fy_Uf!8rp^)c-?-eA0di8~+R0qq*^G@p)ZTzap)-r2Tx zURj&1Epuz<_2$Ot^{dy$c=OfHXlJtdy4`*~YE3qc$>uZDo=lonYdoH`M_c39+neV4 zc%!v3+BUXd8?Q#MM%z2)WXsqdO-6?G$}qOto0F|qEpuaQW4t+r$FHo7?IlL<{DG8t z`;HnaIZ*30}K-z5LXt7QI;@;@v8qYG;*r~OPt@&OW7uB@R@j)v&-I>Sy{CgKA6+gAHEC`(d7PAbQ|ThG79fRy*0NDbwR zuLxw5U*;a5QDh5FhX9qbu|X*nup#mF9<#6t2YfXS23-woFbP)PaKv!NcvTROHpv%F z6Zv@b%9-g+4M2*1g08RN#5H<#+@TBlk^+hfDJUV-$8_iAP+S@x@rT+IVJ`c_R-hN%?e=qaDm-*kz{O@J{_piwR9;Z!@lA|Z1 z;T0rDmw8q{?CmC!qvIxw#Kp+sSD)yPipM86EN~ZANc%-yf?Ap$opyS^X_j=pndP5T z`i}d}hvriEX8gKkA}C1}sax&vE0=h)jNdFzmQgq0P_|0n+Fc-Jn{ID4=)pQZSf>Y7 z)>QZ53&WrcqI^75BaSnQ1M)nvolatvG;&lslq^0nzM-HF;=J6f-D1;J80c!s1dH)1M%Taa*4&DoP5&^0gT&O<{Z6nLl>rk1HmJ4!w1 zyx>=5&4d$s?gfJp&vYp-??l(O#ZjzQknJKqaLv|Dye+s9Eze=IwUu`{cNEk>wkr3o zJRGTNM7s9+rM9T46q~QyR=OTFw$Yj!R0{ot`Lej(g&u*1&5-F(j zf0qUBN{}tpDt*&)z!yZVeGn{(seo0SGgnznh@VsJq6|#>ik^BSM`hDAlwm?vrEBs| z@k&Yum!-HaLO&_$m!YX?tKg++%R;^6W{+0l_pT6FBwA#(#?wW0saIABd7O`PuAoN; zn$^1`{Jy@vN2_%RG}|F-%U)12 z0W~hs4C2ca4Pb@qdt}-P=msCK1PaKe5{vR%21Duab1Y?H5J5!-zD%f7eAOLyKBqDQ zuj3%5)o-v&5%TQLq=kKsRg%gD(WQciKjEv==C=4iJO zgOE>aRq<_2V)H%mcQvE!%l9cj@^RiS>w7+cu#6US&=!dp;v^nspNkX}CI7&%{w6gALI*|} z$iiSB;lM z6<83oUVqrgM=qx7-XzgRgiOo>^+f~zAc$3=sZ{Ha8+9bJ$(mRE$>T?yqx7UB&r!=8 z_ERpWl_{MT;WZ`KfDD08Wxfy4o;>Dp))Qh=(cy;SMHtjT_68}5j}k)Ne+}LZQaLZM z5;BWHrXUz_6UKntJp9O<)L_?!Gbf%(9cU#4QnYnLm=vBHkssf6RilY_6Bb`hYC< z|5)GNru$!8+fb;z*@ANG>zl3e{@3r#|6{#fx_*?dAEoO@>H1N+etd=N2fe}IP9jN| zeEr4AQUOR8VsND4DkZ$FE?ZVZWem7;x9K>(w~Hj@8K^uAqG^1KfDHp zQllFZqL}>6!`_*e6B+{HOV~7-WWMDk554hKMMD2200AMs8~(-2@B>T!_Bqx;rSfC% z@L+G)2i6Dn>#{Q~gy(qdiGQ1vLcM!esG6t#Q` z39~`QmnChU#-A=uj%}21VQ$iF(_&oJ`3Y*zmc!9J~*q z%c~pwR$%k(1(+9=TTWq#Yp9%{=XWrw3)HcvJYj7&81=Eal92EDISV%YUAO^ z;=-|~MxDan1+YL2-|Y_+dk8{-hrs^>41L@8(7thFp=F1b1?~r;_E6NfP6*Hxmx z%DC!GG6Ua0l95H9f;K~q*v=(whSvrxQX|LW>piw>)7>PVP2Mubmy$(e8`7IOR~vlG zv91lw5NqI#a=-we{_xX)V2v^6S`?33BvPuVemeC7ht}ItBu0GkbV0zVgW3>F|Rb3E_xtqJH`m&vgOJ&j^y1HCY` zGn&Sb>ksz0j${XzW`lPq?n0jua@-&RU_3DT9Unz>_~BE7fJAh}WVYlXU?{14m7d>t z*a{x6;6vBCQK8sV5I8n5aRLL3T!)l<_%#3h+$tf#sK!xNR+(u93*&@k2>c=F3=;mAWw|t6nYR+>(*EJZnJe5GHy}_T)H%F+YD$ zs@HN-h^f$Ja6nwRbjLHypccQ)%ReU82>3U*UnnSm=@Y@Zxo}JoLj;nhx;&ILOhyy} zwdxuoR;}lFLj;(41?Kb&_{6HMw_5drGM8klt7*HK9l{NpgFCsT&K$8f>y(^*hY8_#_w z6+yQ7H&m8DLB8Rfz5U@o6E(T0^S`yRFsV?5&Murx`kIb~HcCTrX_Z*FdyxFN&l zEf;r)W*!I`Ns_8{P?ZC5clguh#m_(e@XN=+!LP^8$6tTmIQZ4v-T#miGPj0j`rDi0 z05v-G6;bAQVdHW%7#{3(#JB$b?&G}YC^Npi2M(yZ%bMZ|rj zu=6A}%=vT{;2|J-{<%oV?q*$~M|V8@{U@Y4sKTzynH@Lf6ln;CZ~G!xN) zC;%@Yp>?8Nggy_)u6I+zkjK$}qGlR*LAQlR<~*IJBtHvmFaBt$~MtE95*S}m$^?L6Gt_3=8UqWiZV4Zu}iK{v=GR^KjC9RZY5=D>b@H7Po3{(yG|MOyT*sZQ5iB&1@m`|KUq# zvEsJsP>7^exX-|D#o4Kq=C#Ej3bCVqhve|E(s+=^#BgL?ptCeFK$q0?6-g1VW1?pDN%`0Q;y0qpzjz0T)=jfre-&rZIXp0_B&dUQuycg_616jFEWblb;Jf z7=Mw|Gsn#_`YsjfIjUcAk(9oi)se~yk5-Rnhal-l!#%Rnd4;|j2F69SQ zaXd>?O>xImR|lT4iIj1)NY|2p>q}^mV7bQ-ApziN#)Z$e2$?^@a+aBRE1^bk1WvJJ z%K}@DWMra9D<^qs$?s{yC-v>5U5IbIHtQ?x=&10jTe^{9@@0Fe4FA>?S&1YYz&9e7 z%#)J_G-8|)|DJ?twx5n7@<1eO3Cy(u$~8Gxp;qWA>_d7hYZXnG%pGk(leQ;rb*xjRHly0|Cq)WmjbTJoKdO_C2k+a2e>s%JuOr-O+al(Xz7~#P!OqO5suIg8xI|M@4U8v==%)@+JP+N6zvZoqTWb;aH7!iMF#%WYhR#V##p*40kvcsdkEK3Oler5%hH zRmy2EI)o=7xW(c9qCeQPNY*G!1H~5bTDr8JW;uka6K@o;ZgGGJDoE#xsHivSw!T5F zvDAI0MX?{AFzAE+uy_ucmP^n<4xPVxe6AygXag@ zs+b2x;0(Q(#fJRkokK@{(^je__S3d0BQ|V2s9s>fPMkBPW{k$G)#VOER7IU*a3@c^ z^<&$%HnzF3ZQC|BwrwXHYh&BCZQJ@i`QNI0pEuu{p6M4;UENbPr_cFx7+#cG1=yrb zTY6R84!Asme>20*w#PU6`UT=BFVeQFEEcD^yYYi+4>CH6a`A;uO!+_pYYhEPiL0O? zUWL<575s8?uAv7%{wY^rG(+;G=yBaYe=b5l@fYx|e5DFe-a(dPu_U#O#R9*yb_do{zX8bZ6%%$s?Mp?%de3c z0S63AFgS9_ou|F&+AvmjPwHs+%w*JHroH%$e+ZaTD_&ZsjwrO9jGgd&;1^s}$y3 zrc9^J{(}ZCI-7J3-%Puz)q&ddos5QhKyoKW$Yf*d;~h;dvd|tN(iY6?6v#+>jL)y zhz`olXUQ2xBy=s-+iCbrD91gb=9J-iGDiMT}4e?$p zsyYROMNADD7M<_1*@iXFqF`ji4aqQiEkhz!NV-luZSXi1OJow9pAu zH3{4EU>h2Zk$3gK~)KaAMr6Z4n-$o z0km_t7ZL#nM7c+kriFZ{1zMCsv#unbkx%sz-^jLH5(j(xNVy<5*K0xw3L$DyU1UPG zBys@n)#~*p`_SslY5{tEc#sL4Z79Ua>OhqRy&z>FD&(gUr}-xMkBZ8bq93t|wMJfed-@qViGuo=lPo1RS2l>esCt#w?W{{5gL z@2@4}=u|mC#P^RjDp=7?EmdbqJ6K)-lmXeAPkd>{8|f**!0jrD-zxGb52g@=j_;=F z5gYgGSm)^2)bUU*fXfP$0R$pC?i|>+LBsFm>w9GT)&;NyY|$@(DDb%Ld-^2wu|?R; z#ZS=Nusw#9;<~EtS|FvD^D^?)cU$)2*n=w{uo)P3YYwmgF7Eo`$lG>hGR|X-W(Y?s zsdFrj4T)h^FMzy|I{O00*Yr5#DT_6mIXDBy_dk91m08RWU=j>7g{pc2^}!0j4rgud9Asl}FW;NxL#{?);@(5~kU%>O~zaa)bu{qEOZ@!cE+m&?Jyi;Yk~Qb20c>Jp|yZ6 z;e`dlJdu+M@vkvZct>}kL|B|vQ=~v`a$FS%J2*Vs^*iO-_MQm=hd3-*CyTcYU94MV z=}M!8!h<7b8kA+Ro(~s2!xS;`$>{z!z!L>M<&o_`LB$9j}Vz6oUFo)h*9TEu3{N6h(NUwk9F=T#9a@3WvGUA+Of$_LIz3^ zoO#kqQN!VkiV+b)|G1wfF{9a=ia#2L)zPl-W(!B5OAUHnOlP zSqKhT9U9FVoeYLSG##xTRx^1YlKu+4@)wl*8z4#f5P*??Q2>pbus3+wzsXW<&6>o| zeJ~vbbXzA}F66AtraZaQDXZV5&D+(#s#d>XS1~hZ!l8jW!Q{E86gs8g9X{6?-`|vN z<8zFiCMnLFP?XiyTi?9#N3n?H$)H*QN#Gsy)s@N@um8pP?hC8?hG2|Z+!Y4Fw1jq0 zkUCqe(HU8?8C85gXV6`#w0&;=}6 zUtyjIkH44G`JFm*ygZm8lKM!-M-N&bO?6*)>Zc8(9odn`9VJn<@$?}zT@Y*_V8Y6T zL#CuR>!y~C8LcZbfeN#i&`pRnWd#oo$r>>tR~3<9oYkUTy;22DWe~5PD@pSm%5Do;9VOu;=DH7=B5Lk$ zlskZB`sw+)mwQqe1baY3S@XLtXE9YNX#q(>NDUm$X+=beJX{kGsl3D`%N%PtaW_KlDIj~HnG#b@PTS%ONp zHGke&%J+mSky)&h8a3{CIabePr4%ZeHZ);r)bQwuO1OSWWgmM2#)A#M2kKtYDV2a> zCoINJ!l1)!4f${LC}Kr7TMu_FQ`+>ZCp>(Se8npFa8BIJ@QqdHe$v^Vc5m@7IXrTq zCo8Q+mk7qpCI%Xt>^tx z%_2DC`D+Y;vDA>{`smU^o0p!s!bUR=^sAOVkcF@pv$65=Iv#1CLu&~DRy@f zq(p401vYcR{T3RyG15@O7WZ>cMu>i!h2k+uaIlIS6zVOJqbYi;c^<4H>i1!L{_=*KeYL*<0KIbLhev8=;ds?D zoSne-$mCI^*B7~2jlb3AAn`R-aOTZh=eZrkTLfXvxLVz#t8?U~?7o7q8;>rG4|uM6 z$Qstn!Q33*5hlROq+s9#&&=?fW2~!VVO!q~&+WfMgYWbBQlM_k*u9#!n3$^-N%Uao zQOJ}Vi44vl@kT8+qxV@y#w2k}KxMPgl9(YPpdv~B64y1Wi zhocEn4fiFl7(B)&XpDsT7V3&Q5(FHRjgVN_oK+sNe8CW=T3saMu)u77TuUY(xm&rp z*1v(d14weLrQ2qVRc*xzRtc5%tfCXP*mP%pf-8N6O{YypZN1Er3$pzm@v*Y)QRe#O z##fSxL`?-&`xm0e=^<{|ngY#}nDVvIlNCQ!G)%g*Bfyg)eG{EqCCLZg0;fGSqcQA& zNOT3+rF@?hC&{Wq0%;$0hwxY^i+_~bynIK~yBKH%>N#HmO-=OGCzVMhCPP9g z*8*7H)Md@s@9OfNZ^tFG3_JYofNk&G1<3&0&oL^GtxbJ77IOU~e zFW)7`9BoSgCJ6t(lt74_C}SC;`w6`G8uN5DfqtevpS=>U)_u1EXXEK!sXJ!{lbE1h zD&AeG%iI_?P4@(5OL61ayT^y+miw^UQcghheXskjik@OSf?t?%+afZRwdi zyj%7Js5sEJEJz)HRHzZ=FH=6&%PAUVXb{q?xe@e$dZg=A@*%~guzsg>e8zwILxCuz zV3>JwM+|(DefXy*r2Q%s<&(7fKEp;YXOukY%;N<_IxPq4X{k=AzwJ5T4QVx91GU9C za#Cuid9b!mVA&J=J*RlakS}NU6)#l}*6m|0c@cW5RzR9<) z*(UuHByDeYPe#yFi4y_n;PTTDqXc}`-}&@Q0lI^JtE-d4ei{r10zOHvh_!{Uh*#BW*5(IKu|M)zLw7@) zu2{fpZ7kr{eWs(n3VVo0=i~f8Y@I3+AYkV0`PdEjdt+^T^BZ*XoBan_XZjh&{4M_r z(4+X7#|#kv1%N#Q)ZT{?>}akCTSxY(Ro6xVKo|V1c~bt{4B4hW0Qf{9nt5(FeEkGV zU#A+sK49GQ2a$$u(cE)hQp`PAjid1hf+2=`EN9Y<O9oSvq)3je{ln>m});C@4xAHeCu#OkqTqlMwJl7|$gF&cMADVOk z6Hh%qWu`?J&_fTioA~7CZOoSgsO)N6qhA5o-~BoOq}&0dW61fIW6+x z-b?A{!3zL|B+-;I`Ky28qFYx4-&jRP+DhSjTb!`?2y*24+=nOWnrCh%3PkQTOe!tz z5jf7Gv>JFx{|s!j+fy4mn3yNSh%h;Wo50bXpyd;Y0+h_nJ6Y0s}p z2GOaM#2;tKJ^w~{A-4X3&>3laml0jg zi*6&u#2FD@b(f*xTL^1N*26_1LacAsSN0i$&0d;f8HEUU6S%LmDO0UM zkiD+A=Xp&agGX#7jx$W0wcIu1SF9y$_6!?2YPcFhG;|30Uwzrh-%i_$PX@=UxA{d& z7s;%(O_S9im=%0pr0^lK_G0xHH-y3Ha|ma#l(bNd9!>?(GKEt0%rg>}KkzZXzhAYV zK22Zp6~6j;z$Ou2_J6WI1ynxtt*yN){|mhSKuy%w0a73OmVgiUX298e`U~@RNzayz z?XJL;Ut5XlyIB0|KLIlCt;U!?Y;#@0Uccp{~G>#_?)No?Z@+hmw$ls}4`P+s@NRR}v__L;XJ-px?8PbORYm z{JfvPU#p~jhSQc8Ih*Je%r?;rjw{pgaUX7-Oag#j;t-$R8eDld@FrjsM7axht<(k{ zt6i^ulLz*=lO6Yp2>5bi=MoSi9v1cZVLd-TON`e2tVCLwp7qZA7)gyZ)9oML6;MFR zCl@c*D(*M>SFp_k&)fO`k+tOKtXl2Y(eHXEvGlPqF&>x(gb$N3n&t%XeYyG20yvB+ z25&yX2G&+nBv8eQ zEtW5EXm@mocf~Q1-hMc|yk5=q@!TB@Y+uI@_U`cmA58YYiqa>={rHWx6_d3vS{&lPaJMGX_vw;aJ~& zr2*-3hd@@g50!Z)L(9B^^GzI~CT1y;9F*;%HDOK^*Kwk5ILXvJ6oh5aPNJ&mdvWka z(Y<5q;x-3$mP@~o(Q+1>EeZb3yv^yS1-*Eu>H5nSJfM$Q!aV9gzb;zdy6(ZQVr6$> ztCv(iE|<;4J($Z0ffz>)Q;);V_atu|$3T~Q0c1)V>swS{K_FI$oki4{8{r9g!Cgin zUT1LqUn-ReJE&#^AzzCux4=vr*!w6hwEIYlLwF=t!ve3@O}iW=-(63$K&!ZGTZv`I zmlM2g%t|~`*iIKRCC%s&vg#$^&kDFvg+c*H@1CU?n#7kLApXTPraiF`=vcG_4<%h2 z%g^Nwc80Y&R99pexD9h9wVu9KR*#zrdI9~b)609=!k4(KGex83mbLb^#u&Rs%W0BO zP*Z0fE%Sgm4YB|eyc{Ld-{C>cM>K?^V$`x`F-8M+8`Q>0H`(3&sob%0gJLqqa(2jN z3#T42j@wQDl;`{YXbHY#Q?~6n!iTXHE z_ucxPlwZ%{O}mB4Uzr%$PNNt_?8t7xD*TBA&ekEUw=i9BryL0K1hxunM(m=LL2E)} zDGgiDOyar6AP1EXEQ;8~_smV2{vIs#2$G-ms>n=VYC}JYwx5ljG4sX0|9Ev$?LVqZ zMZkq@w|k^`qiz#B1S6EGA&wvhnd?_zQJ{|{%Px237FMDr`Hg^U2;56j=}lG(R=hRj z7<%+bdBK^xV|ms3VtM;L#eaDymvlXoj89UEGO3C7%T?wqr!2=dvGkQcr=t~N{;}Lr zgk8VJb&L;9xtFaGy+5@fLJaJ+>d#o;<99;|T?`J*sNCG?{WVQ*J5@7`AVNVc1MY>z z+a$gPoZc$BmZg!+(hulj_E>OU!;jUBwuFM7I-K8ZuYTB>upzsD9n;HrMsf=L&WlRF zC{ex0HFbt>mRMm03xv})#pAw^{=`i)11+WRdk3O207-JG@LAnfYcXm)HmgfOJERmz zjJHBERqIzee@`j0uz!J4YQNVPZvrS~X77JV&Uel1@3k%mT9>zxC)gNLC+M<_I<(N= zo~;f2*&f3^`?_eZ=ITU>Fd8uxsHNnJsM^x7W#Ai-u@F=lu0KrLA2@56(i}w7a1`C_ zJPBw6893Yyghx{hb+uy@u&di)K=+ucnTZ8aADSM7y`vgC;s zPnIN-7hoTCEz*hlSTMTr4(N8hbwMohWtIT+SK=z1TZ> z!sL07Iix5;A7C`AB;9 zsQeR;y6Y4FfS9k>`Us2yiWWs}-{)LCpCfpQFLOcKG*fWvi*Wgu#+Hx0H_Sx;7jWyG zWSaIt4a%<0tXewDT)zTblZd&98`nn2fMJG$?B5N1t5=$(pF;YAOUZ217}1QsC5`o^ z{$QJmOPVII%&DIf)e{18D&6|-v5oW+GZg;4z3JkCY&wP}<|O=x9Ku~Ll(JildtnN! zZ9MaENhBS{WmixTcAmR{@{WwB(|$eDKg8_69jK6l&lX2x%KqTmb0>L#Hf)`<)`F;D zTfeny=)#x78^;LM;7dQU28rQQ3h~b2TskvHOr&`~t3v=ZT3E0wCsPtiTvWwAr|T92 z)lido%sH}HvG_F7o-yrmKn^FPbz4(=z$06qq`m&BxaI!ytoO}JBku3wUwAbXp$!Gi zQ_ua7yAIv{C*JKz9@}oy{*YH0TqYe@KOkGdv@dn)5#TC_i79olc0|{L!(>!lqg5v} zfecYK8KYw7{``r+{IW`5c%L$xMeec?OdQFvH#Hm0(>&OwvWaoSfW%(g)(u{91b60Y z+a{Rs5B$;@-vl>5AYvvhS^7}g&QMxe6lwnGC2;medhrP>2Nj&tuz4IpV7L&+~kEkmI6A0i56;r#=t7r zw+?mHZH%95vL=^fHgwry0%G2#7~)+bDqJ2_pyQdW>@Y|@z9x}}rM%I0aK8CelDZ_q z;_c14IheK2`-gQd7c5^IC~{PKpr*J*39)WJb~Jfec1cC1A>uCxY+lp!83Nx+N+IQy zv&Qy%_1_#6k>nT|e;%f?2)A4z18g#5GjRhE7)E|9v~*=@TCi;E(?tsX=M#IvR7A!8 z=#pr85E*iHzv;DdBxj3LtLL`*%}9xc=8`$!WHGZoGamW3Z!`@XqYq$BXdg5@5vb39 zVsp^U!+e53h;3WoACf8?D&*Bkm5?*IHV~{CGbUp-se~3K;mxa9nOzZ$50I)c^&ZAc zns!UbR8Z#376gjK0-epln== zimH+6=y&M#eGWFx5?+fiTpDVYX+2A}a}m$)q0dcqg3oWM>F2{1pB^5-Hf~(Tb6i&k zz(Z$;Q0piABF5|8v@qG8M;@V`{?NaK65VAl)}7oq*!7<aM=u$uFi(&!O1=4QtPBWbQZK?-Fsno!U-*^_$yrGP_GL@QeOBWcjx}-8P8GS$t&_s*ae* zms1{A2!z3qxY1Vui8648K*jhpMg1>Kd+WsJYpQRfw2wl-s#6_rAF=(NhuptM!e%D(mSxe1o|3?Gt%Q$3s&&b-pxxGB)25DtPc zykd9@NAI18FX7L_S{u>`%=PE##)Yr#y%B~N?j1hyEr6h9;3MD^$aaJ(bg8R z4-)r4)lw&(s!d5EsfT^gClp=_Arw5D9g2=Vx%MjD_#LX!4_eEc1JGX~X}$^`~}VtG+W z7@9Sv8HZCbTm@|*P)*F)^32@jyneCCO+?@_t^Q%d;oiey-L*2=TBI0d7~$41y#9YR zHc(Jy>%>eXW%<&ABDo{ErUl9mY*x^9+?ToKv)m*|(%J)UIHWPcWmNF|b zQina8Zbs2MrJ`mLmE)e`)vM(0U7V{HxwUx;eeb;oRl7u@IPSQ=@M-D}@ zzrMd#>6Mr*h#V)A&(d~5o5L99y!sG-rNCN{voLciq8?KG$S^=D*}WqdcPFoqTN;7U z6=?d}uT1(ww<^gkB^RTOV|zgGjd~@0=HH@3wDV71nyKjb#EBb_-5^s*94*YXWHNAS z5BNOtt(G92GR3R;0o?9&v2$x!)7H`>?w|ki#l1JxfeX=Cuw}7thHer1_onoVkjteE z0f!8Z)ZY`h*0XxkeMdtTSyVB#EF z1e;1fZN7g#W9WXU+Gm)@9@HEV*k~>Bl2V73EwcC0LU)%q>VJyZG)PjVR07zVm**>5ObeuL7(sJSTSVO5o<&11c*0KyE_8*H_LB$ zP^;uv;(k|@;f_L#Q)!Gs6b$~XDQHN3COPef;_pZv1vOl{z`nUq$;0fxA@djbxTL5# zp(vZ1JAVpUQobSqsMtKJ9loGgrm^73!E027YK6rSZS{#a$#0LWcz=TrJ#iIqur0t0 zcgy02)UfZWGc0L0^z#nx#SaZ30a4D^ zLWO6sSzdb9&DBaj5GJA*#Gn-m)&G@pHLmYj_64}o^>*G6OYG@+`-N1hLSzh6sHYa& zv>8P@(a}zoIN|hMBUfsq2u4Lr0!$C2b=>y3W#4|X#De7?PuIcTVW7ke4R;)pn|qym zt#5xBjT|Wx^ru#&Yh}sx7WuX}2OK6svZ5&7qW2?D9Otsm}d@!lB#qiPP_VufLrz}!ylM^_Q zmYAfrdnV)B>3PX3o9(7AWTjT1L2+GQGcJfcFsgfxw)oi149gHtrz4+Dv;(U5XL@pG^y&2J4+OlHyvq zZuL&;j+hKIj_#--kbn;*Vi!;gl8Xtl;3LI)Uz=5$xfIfw60e@YTS4EyTsdrKuVav6 zcm^HuCy7v%Wh@N!z`!6TSH41GI31Kf9erI`#H^2UxUOUm27yn(N9B@{{<$mG{AZ)@c?{+s5$?u; zu}(xm)HJp}`fpWX=m|O-p;E-3ct-&?+Rry`#NO1|DWc}qAwT;cAa>vF&c>%7-FHU} z;G2~Th~o&rvnfz_54a-0!*6`O0ffKa*nan50J{FCyY}bH)PFZh)xPbI0M8F$NdR!) zPfW^hJ2C(vv!>rlGQd3!;n`jLttSQW{|lh(EpMDld4I!%`NUGqV6AS5>bf%uV}eWD z>~q@+J?tLAM_@P2p2G8SUvO*m_{{_(#$FFkS^H@Sl^ibee)-L2C_v=ysFr7XhH&wnI4w-I`lQV zO!g+RT&B34RtwPCBAF>Ja(;FtB5Qkd;7jP>8IUKBz8}Mg|Ts_*k(w^0u-6q3uK&e?{9Op6zqY5vxtZm=3CmnZjI=Fu8m8O|^&L z6J#-Vp4UdMJ;wn#4lNJPgVprEAMRxewgn0EM~4OrZ0XgyH*qu;DkXqe?RwpHldi(l z>XWa8Lrk})rsobgM7JbwGwlX(#U(9;@nLpn(^E*jWigJ9p*G^v45JV1inT7J&GVI! z_YP3+;zHua3s7)y4fB=-@^yQ89f|Ebz_mhEm2AmjBl6J#(|Spvu5XNo5zNfYr9_h`=qElFY+PIn4bYI#1#?_ukWnGiFBDwq zB23SF;BrGDp2HxwAv0lt?HKAiK8NC~gG)$bEs(&Dm(i#efw-1MRA(FTgvPNZGU-HH zDlZ8mt4w|CT{O=LoBSHkU*i~mCi~l$M+M#Cu#pLMpn16ZXV~@DVCM*DIt+9U8CBvp zg*q!U_9~Pqxt{uEbnn^nM49=pMb5MdY47ko)PXLA9%ktaUvZsbr*%_ykx#6|2TH*< z2NN>bV8Ccn%ZSfWq&@8AzdVfFLs0GQ8_MK{e2FJnCMet7NFk- zV7Qd#1RDc=Hh)fQ;X=SJiWtZ8-hDtmahfi`eE-XpeW)t>j5Ru`?%fxKT-NJ+H@mc} z6)zAiqPN-QJE#FBDNKUn-FHStoXr1#5->c5MG{hE1rq{JxkM#Z-WH|86@W8H^!l*$ zbTE2Qu9tvE^g~zy?dREZ@5U`xlBIEL0C~tc41RD@FoRV7IHQQt1P-`_`!Z{3$Cx=yLZkLSL*5_mV(qr&5Y;AE>Agxkl0h&mHGg+XeVOjNfj2(aYt7V z+Q4*)q4G+BSV`m%_a%@u=EjE|!lQ{^KDsJK1vPNlgKOEk&x{^XNO23=$5I!e z3_i*M7u__^#pwqV2> zG2ZBL!(j|8Z)*odVsI23Goc!V;_+z6qa8{&-sb#c13etn zdYRoLSsZ)yVV8>l8d07mzAK_&T!&fJlz^Ezujm|f&JX!B8wL&rhu<@+9vn>3!&(X{ zuY=wIWAbi|69tV{m?iH^3JzLSnfW=%tqhALbb=kV@X}+(^e?y3_0N~oGDF~wa;lr< zJiaUFYbVOC4ui*ov;1ZE)p$H3BI|;F&A5c?!)t!Iv^@7Dq^=vvE@mEKi z{>rrwxf-T69f&i0qj)A^01_Bp#2*{5K!gbe!f3whOwDQ6pd=4DTV2UTLK+0znH;g z>n>>457QyAFrpjYiJ&}`nFV@+_xvBXDoGrIW;u{OL5*rjTqrHmkc9(uSMJO?So5do z@}dPe&74R}1rty0ji40Hqgn$dM@czu*dPFm@QGow4x)iH9_U_BsEHX6FukJ|Z7Ir| z>ig^h=g>L{+vJFpeW#{3nf*<(i`Z;#um;9bQw$-VQPVhT)C-|Y z9JYQYMd-cwK3)wL)6%txuQxQ{v!aT;Iv_bnSS^J6@h^6xLb?IU0W!da2-OcYNZwq*yL;J5YgQe9kD8ipQ|=Rsgy*zAu1?-iqCTKAIgl(LYP55#HlKF=lYHNnS$*J!@LSMa`nTsImlUSRLEPW_lde zHC8I!sek%v_?Xn~_GA&MrkcR9tfasMd8LR1)#$lC4vZxlZazKDO+MWo&6|h=0(qp&)EAzcctgH;79p)UQ7v zuX6iBWs*m%LogIAHgr#1cy#IJii#|l#M){?yURUTnw4v1%}hqZq`LL7?)Qa3u?71> zIDK9%h`nj-=eacKHov~wX~#~ucL6R#wQ02@r6-g(@Aub7Lw5`Z6Ylo%df!?Y0r$HQ zi4!iw9E6Xn#JLdxyOtm-&^k369qV0gDiAlE^<;Dj`?_>)?!QwYB2==QIQ-m)J)+T@ z=3Xl`Qd#=qbn4Crp-qWiDPpFZ{#0DYH85OL)b285g97XZqZ98KQQ^JcPB8A&d2Mr?jNJ!kB|7Lc}l!-P2jt zVq-!0dRr@w%oI<&W99U}sM4ZbXy$+ykm)^u-dQ~=IcYiM-XA!}1jY@6^AhdS2pWq^ zNycZVX049z3pQ?6_3zMs6sW+dVWeu<65fQ}z!$T?*g?efly0n3e!!1!{*g{Vk#Po6 z=Sq2(t@bkiwKpy(RZr8&nS&A1+bhp5>D3YGh+2hj5FQ0hlBjq()=~-9fabqHp}J~E zK|huPt`XPPWHg+j$AsXa!js*@MCPB#s;;K9b->vCuzGK!e#xB#xWpWtu@(o4KuOBB zTxSn`3c~76q0x>aGg{9&M&Q^OKN-gHBt^uNHPqk1g#_bm37NX`42+86ap_G&5pRF3 z`W~}R2(xT(NeK=Zurd98p{$I{1(~E`nk7jst8Z*YMO|N$E6bgb4&7%62<$nHGl(Tf zlp;Fo%QvqCK^X|5J2Ir{~EaZ8aCQr0k{&w29 zHUTFk1XJH(J=O$GH0|1hA_Y#T9m_of{eug-YQqV-{{>UR^1XhjU9TtC6%gYPgmXcm z+X=R!AyfSY1t>KqM4Rx80Q&c9#7@hL@h~TSQ@o00-|f#%9~ct^gpPfBlit%wbcl*l zuKKJ?@F;cIsF**D^O-2mu@Ju=3uZ-lGqs>GPW_@^>)ixBGl+Q8V?BpFws9*e@v{0% zZkZlAj;j3u%W=Fce4}PHi?km*mFm?*sB83Xo**aCN0JZDh#J2*v`=Ky8E}dXysI=s zC^y8$eJfJUW0Ui`fp*$cej#Z|o^$^{Ut;+4F?9}4;`vCG9)8}W-FXlYHD?yCg>MrM ztObm2bKmI~=J;RI(+Qnycx2x0@0PA=sg;MWmoQjPqzi(j`yu(5yB4|hLSaVzYw+H_ z$gNJ=SjwjJLd463iosQ~ZRzdsvA?rj6&){hn^T#fspor9BWw5>-EHBB$UzazLQ*^S z_#R^U%(DV7aS9yLCH|p^>?lM9NbBZ}Xo0}#M{t#&Sh-{_z~;vE35Z$usnvvarC1|F z@ReOP)6Tf%<7$6O{eB$BF27B#xlDH2!n|n1Gyr$}hXTv6JFC0IkhG|$YB}N}ok(?5a1Bg)kZ*eZ!-1_4APpmct z3O?AAI}-Xu}0egVZjY&t|HRNibTHkXwyvX$IKa9GWza~+l&+9*4-J19T5i@cQ7D86zSy?T=BstIH9m~qfFgB_LD z1a*&kgQqrs+1JrcKf8)ysWJYh;E)!x2{La?7=h=cE7lWIZjn1*eDQr1`qwbUcDuq! zeFZ_vcOZKno|KSH#`5kQO3-W+T9sCbHl27mcs%al;QroKEz=gwLa?~Fh(Xz`uWY@L zNPrjNsJY_puc7Dr9r!$kZRJvCf953WLGb>Z1p`py}p7dcM`Y}yZ40i7fPv73b4h`8IyFQFk@(+4p-%6 z!E`4PIW`~~ojjP8obgFd$x*Y$&M5W;BhAqR6$!+sl?UUXrB0A>;HMF*RA}WC)3Sfm zfvJ_OX_W9^^F4>8>vtl?5|)RSP^O{R)XZtTRyj?`vk|*iUUi#@aRjS@5}h?1)0+0u zbLQP52`H+lbN@LvXD&dzmlZ$QPEUPwGuA=4PSAS_ocU~DjkR1L2bm-#nN|go<)bcX zuu27@LG%%)7#}W&KLNSA;IE&uz-fQIN;2U*ciGP-W`NdGL=}^bbyMRn{2A z4@CCHcLFJ8O&+`?=u2&es;D*KzbOFWYp}__#(q&EUTe}!Xs`ZCe{btVuT+mgLg+C}E5Hl(XHGFFjWzuvNW+6!Fxp z-W1-qO=vU_{C(!|K%R7?4oMJosung~GcV+lmR{OwZicB^n|P|wO#8F+{mGgK->2LU zq9!W-sQ1{-Ng$H88dj$9sE>YshplXlXZ)Vdl~h4F@LXF3%g-k zptLUBlvQtjo2yw@6(M_oP`1dZ!>ffysjw58O}f2DUff<{dt7p(EAX#SNu4(={8iau z5Bs+tEDcF9JSGHn6Ds;@3+j*i71Fs&5e2}8P8ySM9PgLh)}|;V8^W(ROlHDa4Kwk< zB}RH5Z+(R)h;3n0LRwQ!Vl1Xs(-zz__jigZyV8jPqXg+XfnLllOeKVjPX=-RC+ zq0nLD=+f|K@_i+)f*19u^S_Onk$L!TI3oe$UeTP}dke#V<~b(}>VD1F<5-B0M8{*x zb6u+Y1BLs8N5qFw)=mnest`;s?5ijTa2lhl777$~pZOtMfxgIV165Iic?SAHJAu2H zsMy8eO-{Mqe`B>m?z2PvH5AaHBQ8$O6Mg0dx$JGmHs_>^6IH7*#FC*gvSh;$nPF|S z6a@Df#@=r2+@CgGq1VZts*!q1!10=-2}av(ig4d_|f2l%0W z0tm^!0dv=T1`dsY8}YuqsG_c-AWT~fe?P@f08x9_R&0`H`-95`;PkeR8h}~zwiSfg zaoJOZ3CREi@UyoE>~s!b0D7hX^Y;MzWPs=cfE^$`0pJ4oJxu5~%g^ZR`%HZ@*fb%! zK?BS`2Dc6T92SrL-Z1j?i;O_Fr#Ac!{-p`qGpt@^2VwS%lou3TJCK~|{LBhyWz8=4 zOJ81KgQ1;Npi|OKn0f5%lPxpU(c{G068C&oIWULLXc&NXdw6qNlJ@eXt|INj-t@Gs z&Ste;pk@1NgoqWkP<&2WFU8FYlbmzlKML)(J{SUYiJ zcpaw~5uarq*?uUzmcruG@R4SH7Te4wM=u{IU(fjj&FdEZAx~hnrJmZt&e5xj9s!w# zr+SI23*M=j*O#zD(V@@vyK$nRXM%dDhA2`F6gNWPt2;UuJN~n6njWKjB&m#AOWm9jjP~%9y!a zJ>e6p?!hmaDAsLUntrKrhL?k5M7x!AKkveqJ2Lj?nl>4lc+|5&BJIHQkv}u3g9mA% zIcVUMOZuiLdd$TH?$a)LOGGmhfFg(Q&OjmayDNf1Gg&vgo7*k9HG7-s?upLp+00r! zz~{R4UBD7%L}r?vt7L;I9^cmm=g{3XgAcFu z-$1PY-Cp-w|LWNSZ2VUyJ_MH1dosVw8CMzB{PZrHJ^&nVWl5y2DvP=BO&@+8fR+!x z56c(70jlq(XFy*<7l07|y7>dp;tjY8v=_K4{c6m6UhrL)|FHbO#&?DQ0FmwSfEI`d H6zKl}VPD7A literal 0 HcmV?d00001 From b89e51a039ce5130bd22f9c73bde8e7ae1b67a85 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Tue, 7 May 2024 14:30:01 +0530 Subject: [PATCH 25/29] Removed trivup 0.12.5 --- tests/trivup/trivup-0.12.5.tar.gz | Bin 32749 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/trivup/trivup-0.12.5.tar.gz diff --git a/tests/trivup/trivup-0.12.5.tar.gz b/tests/trivup/trivup-0.12.5.tar.gz deleted file mode 100644 index 6e6b560cc185cb75421eb14d841d916a3439ceab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32749 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 From 5e13ed7c29f7ebeebb293238ef2d21fff65da3ce Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 17 May 2024 03:58:50 +0530 Subject: [PATCH 26/29] PR comments --- .../admin/test_basic_operations.py | 11 ++++-- .../admin/test_describe_operations.py | 15 +++++++- .../admin/test_incremental_alter_configs.py | 6 ++++ tests/integration/admin/test_list_offsets.py | 5 ++- .../admin/test_user_scram_credentials.py | 35 ++++++++++++------- tests/integration/cluster_fixture.py | 3 +- tests/integration/conftest.py | 10 ++++-- tests/integration/integration_test.py | 6 ++-- .../integration/producer/test_transactions.py | 2 +- tests/test_Consumer.py | 6 ++-- tests/test_Producer.py | 5 +-- tests/test_log.py | 6 ++-- tests/test_misc.py | 10 +++--- tools/source-package-verification.sh | 10 +++--- 14 files changed, 90 insertions(+), 40 deletions(-) diff --git a/tests/integration/admin/test_basic_operations.py b/tests/integration/admin/test_basic_operations.py index 820fd5228..1234fbf6d 100644 --- a/tests/integration/admin/test_basic_operations.py +++ b/tests/integration/admin/test_basic_operations.py @@ -13,10 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import confluent_kafka import struct import time -from confluent_kafka import ConsumerGroupTopicPartitions, TopicPartition, ConsumerGroupState + +from confluent_kafka import ConsumerGroupTopicPartitions, TopicPartition, ConsumerGroupState, KafkaError from confluent_kafka.admin import (NewPartitions, ConfigResource, AclBinding, AclBindingFilter, ResourceType, ResourcePatternType, AclOperation, AclPermissionType) @@ -55,6 +55,7 @@ def verify_admin_acls(admin_client, "User:test-user-2", "*", AclOperation.ALL, AclPermissionType.ALLOW) fs = admin_client.create_acls([acl_binding_1, acl_binding_2, acl_binding_3]) + time.sleep(1) for acl_binding, f in fs.items(): f.result() # trigger exception if there was an error @@ -78,6 +79,7 @@ def verify_admin_acls(admin_client, # expected_acl_bindings = [acl_binding_2, acl_binding_3] fs = admin_client.delete_acls([acl_binding_filter2]) + time.sleep(1) deleted_acl_bindings = sorted(fs[acl_binding_filter2].result()) assert deleted_acl_bindings == expected_acl_bindings, \ "Deleted ACL bindings don't match, actual {} expected {}".format(deleted_acl_bindings, @@ -89,6 +91,7 @@ def verify_admin_acls(admin_client, expected_acl_bindings = [[acl_binding_1], []] delete_acl_binding_filters = [acl_binding_filter3, acl_binding_filter4] fs = admin_client.delete_acls(delete_acl_binding_filters) + time.sleep(1) for acl_binding, expected in zip(delete_acl_binding_filters, expected_acl_bindings): deleted_acl_bindings = sorted(fs[acl_binding].result()) assert deleted_acl_bindings == expected, \ @@ -209,6 +212,7 @@ def test_basic_operations(kafka_cluster): }, validate_only=validate ) + time.sleep(1) admin_client = kafka_cluster.admin() @@ -270,7 +274,7 @@ def consume_messages(group_id, num_messages=None): print('Read all the required messages: exiting') break except ConsumeError as e: - if msg is not None and e.code == confluent_kafka.KafkaError._PARTITION_EOF: + if msg is not None and e.code == KafkaError._PARTITION_EOF: print('Reached end of %s [%d] at offset %d' % ( msg.topic(), msg.partition(), msg.offset())) eof_reached[(msg.topic(), msg.partition())] = True @@ -343,6 +347,7 @@ def verify_config(expconfig, configs): resource.set_config(key, value) fs = admin_client.alter_configs([resource]) + time.sleep(1) fs[resource].result() # will raise exception on failure # diff --git a/tests/integration/admin/test_describe_operations.py b/tests/integration/admin/test_describe_operations.py index ef5d94987..dd69924e2 100644 --- a/tests/integration/admin/test_describe_operations.py +++ b/tests/integration/admin/test_describe_operations.py @@ -13,12 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time import pytest + from confluent_kafka.admin import (AclBinding, AclBindingFilter, ResourceType, ResourcePatternType, AclOperation, AclPermissionType) from confluent_kafka.error import ConsumeError from confluent_kafka import ConsumerGroupState, TopicCollection +from tests.common import TestUtils + topic_prefix = "test-topic" @@ -82,10 +86,12 @@ def perform_admin_operation_sync(operation, *arg, **kwargs): def create_acls(admin_client, acl_bindings): perform_admin_operation_sync(admin_client.create_acls, acl_bindings) + time.sleep(1) def delete_acls(admin_client, acl_binding_filters): perform_admin_operation_sync(admin_client.delete_acls, acl_binding_filters) + time.sleep(1) def verify_provided_describe_for_authorized_operations( @@ -115,6 +121,7 @@ def verify_provided_describe_for_authorized_operations( acl_binding = AclBinding(restype, resname, ResourcePatternType.LITERAL, "User:sasl_user", "*", operation_to_allow, AclPermissionType.ALLOW) create_acls(admin_client, [acl_binding]) + time.sleep(1) # Check with updated authorized operations desc = perform_admin_operation_sync(describe_fn, *arg, **kwargs) @@ -126,6 +133,7 @@ def verify_provided_describe_for_authorized_operations( acl_binding_filter = AclBindingFilter(restype, resname, ResourcePatternType.ANY, None, None, AclOperation.ANY, AclPermissionType.ANY) delete_acls(admin_client, [acl_binding_filter]) + time.sleep(1) return desc @@ -204,12 +212,17 @@ def test_describe_operations(sasl_cluster): }, validate_only=False ) + time.sleep(1) # Verify Authorized Operations in Describe Topics verify_describe_topics(admin_client, our_topic) # Verify Authorized Operations in Describe Groups - verify_describe_groups(sasl_cluster, admin_client, our_topic) + # Skip this test if using group protocol `consumer` + # as there is new RPC for describe_groups() in + # group protocol `consumer` case. + if not TestUtils.use_group_protocol_consumer(): + verify_describe_groups(sasl_cluster, admin_client, our_topic) # Delete Topic perform_admin_operation_sync(admin_client.delete_topics, [our_topic], operation_timeout=0, request_timeout=10) diff --git a/tests/integration/admin/test_incremental_alter_configs.py b/tests/integration/admin/test_incremental_alter_configs.py index ad19a1164..58a96d84e 100644 --- a/tests/integration/admin/test_incremental_alter_configs.py +++ b/tests/integration/admin/test_incremental_alter_configs.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time + from confluent_kafka.admin import ConfigResource, \ ConfigEntry, ResourceType, \ AlterConfigOpType @@ -58,12 +60,14 @@ def test_incremental_alter_configs(kafka_cluster): "config": topic_config, "replication_factor": 1, }) + time.sleep(1) our_topic2 = kafka_cluster.create_topic(topic_prefix2, { "num_partitions": num_partitions, "config": topic_config, "replication_factor": 1, }) + time.sleep(1) admin_client = kafka_cluster.admin() @@ -100,6 +104,7 @@ def test_incremental_alter_configs(kafka_cluster): # Incrementally alter some configuration values # fs = admin_client.incremental_alter_configs([res1, res2]) + time.sleep(1) assert_operation_succeeded(fs, 2) @@ -131,6 +136,7 @@ def test_incremental_alter_configs(kafka_cluster): # Incrementally alter some configuration values # fs = admin_client.incremental_alter_configs([res2]) + time.sleep(1) assert_operation_succeeded(fs, 1) diff --git a/tests/integration/admin/test_list_offsets.py b/tests/integration/admin/test_list_offsets.py index 6a2e0a46a..f6ad018cf 100644 --- a/tests/integration/admin/test_list_offsets.py +++ b/tests/integration/admin/test_list_offsets.py @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from confluent_kafka.admin import ListOffsetsResultInfo, OffsetSpec +import time + from confluent_kafka import TopicPartition, IsolationLevel +from confluent_kafka.admin import ListOffsetsResultInfo, OffsetSpec def test_list_offsets(kafka_cluster): @@ -32,6 +34,7 @@ def test_list_offsets(kafka_cluster): "num_partitions": 1, "replication_factor": 1, }) + time.sleep(1) # Create Producer instance p = kafka_cluster.producer() diff --git a/tests/integration/admin/test_user_scram_credentials.py b/tests/integration/admin/test_user_scram_credentials.py index 21c15bc07..08f704338 100644 --- a/tests/integration/admin/test_user_scram_credentials.py +++ b/tests/integration/admin/test_user_scram_credentials.py @@ -13,13 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import time import concurrent +import pytest + from confluent_kafka.admin import UserScramCredentialsDescription, UserScramCredentialUpsertion, \ UserScramCredentialDeletion, ScramCredentialInfo, \ ScramMechanism from confluent_kafka.error import KafkaException, KafkaError +from tests.common import TestUtils def test_user_scram_credentials(kafka_cluster): """ @@ -47,22 +50,28 @@ def test_user_scram_credentials(kafka_cluster): futmap = admin_client.alter_user_scram_credentials([UserScramCredentialUpsertion(newuser, ScramCredentialInfo(mechanism, iterations), password, salt)]) + time.sleep(1) fut = futmap[newuser] result = fut.result() assert result is None # Try upsertion for newuser,SCRAM_SHA_256 and add newuser,SCRAM_SHA_512 - futmap = admin_client.alter_user_scram_credentials([UserScramCredentialUpsertion( - newuser, - ScramCredentialInfo( - mechanism, iterations), - password, salt), - UserScramCredentialUpsertion( - newuser, - ScramCredentialInfo( - ScramMechanism.SCRAM_SHA_512, 10000), - password) - ]) + request = [UserScramCredentialUpsertion(newuser, + ScramCredentialInfo( + mechanism, iterations), + password, salt), + UserScramCredentialUpsertion(newuser, + ScramCredentialInfo( + ScramMechanism.SCRAM_SHA_512, 10000), + password)] + + if TestUtils.use_kraft(): + futmap = admin_client.alter_user_scram_credentials([request[0]]) + time.sleep(1) + futmap = admin_client.alter_user_scram_credentials([request[1]]) + else: + futmap = admin_client.alter_user_scram_credentials(request) + time.sleep(1) fut = futmap[newuser] result = fut.result() assert result is None @@ -72,6 +81,7 @@ def test_user_scram_credentials(kafka_cluster): newuser, ScramMechanism.SCRAM_SHA_512) ]) + time.sleep(1) fut = futmap[newuser] result = fut.result() assert result is None @@ -101,6 +111,7 @@ def test_user_scram_credentials(kafka_cluster): # Delete newuser futmap = admin_client.alter_user_scram_credentials([UserScramCredentialDeletion(newuser, mechanism)]) + time.sleep(1) assert isinstance(futmap, dict) assert len(futmap) == 1 assert newuser in futmap diff --git a/tests/integration/cluster_fixture.py b/tests/integration/cluster_fixture.py index 5a51d8781..4c5a31038 100644 --- a/tests/integration/cluster_fixture.py +++ b/tests/integration/cluster_fixture.py @@ -23,7 +23,8 @@ from confluent_kafka import Producer, SerializingProducer from confluent_kafka.admin import AdminClient, NewTopic from confluent_kafka.schema_registry.schema_registry_client import SchemaRegistryClient -from ..common import TestDeserializingConsumer, TestConsumer + +from tests.common import TestDeserializingConsumer, TestConsumer class KafkaClusterFixture(object): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index e680b60b7..8a6c712f2 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,9 +17,9 @@ # import os -from ..common import TestUtils import pytest +from tests.common import TestUtils from tests.integration.cluster_fixture import TrivupFixture from tests.integration.cluster_fixture import ByoFixture @@ -34,12 +34,16 @@ def _broker_conf(): return broker_conf +def _broker_version(): + return 'trunk@f6c9feea76d01a46319b0ca602d70aa855057b07' if TestUtils.use_group_protocol_consumer() else '3.7.0' + + def create_trivup_cluster(conf={}): trivup_fixture_conf = {'with_sr': True, 'debug': True, 'cp_version': '7.6.0', 'kraft': TestUtils.use_kraft(), - 'version': 'trunk@f6c9feea76d01a46319b0ca602d70aa855057b07', + 'version': _broker_version(), 'broker_conf': _broker_conf()} trivup_fixture_conf.update(conf) return TrivupFixture(trivup_fixture_conf) @@ -47,7 +51,7 @@ def create_trivup_cluster(conf={}): def create_sasl_cluster(conf={}): trivup_fixture_conf = {'with_sr': False, - 'version': 'trunk@f6c9feea76d01a46319b0ca602d70aa855057b07', + 'version': _broker_version(), 'sasl_mechanism': "PLAIN", 'kraft': TestUtils.use_kraft(), 'sasl_users': 'sasl_user=sasl_user', diff --git a/tests/integration/integration_test.py b/tests/integration/integration_test.py index eebe84c5d..bc0efa1a7 100755 --- a/tests/integration/integration_test.py +++ b/tests/integration/integration_test.py @@ -20,7 +20,6 @@ """ Test script for confluent_kafka module """ -import confluent_kafka import os import time import uuid @@ -29,7 +28,10 @@ import gc import struct import re -from ..common import TestConsumer, TestAvroConsumer + +import confluent_kafka + +from tests.common import TestConsumer, TestAvroConsumer try: # Memory tracker diff --git a/tests/integration/producer/test_transactions.py b/tests/integration/producer/test_transactions.py index 667bb706d..8759af579 100644 --- a/tests/integration/producer/test_transactions.py +++ b/tests/integration/producer/test_transactions.py @@ -18,10 +18,10 @@ import inspect import sys from uuid import uuid1 -from ...common import TestConsumer from confluent_kafka import KafkaError +from tests.common import TestConsumer def called_by(): if sys.version_info < (3, 5): diff --git a/tests/test_Consumer.py b/tests/test_Consumer.py index 154fe5bce..73cc999e3 100644 --- a/tests/test_Consumer.py +++ b/tests/test_Consumer.py @@ -1,10 +1,12 @@ #!/usr/bin/env python +import pytest + from confluent_kafka import (Consumer, TopicPartition, KafkaError, KafkaException, TIMESTAMP_NOT_AVAILABLE, OFFSET_INVALID, libversion) -import pytest -from .common import TestConsumer + +from tests.common import TestConsumer def test_basic_api(): diff --git a/tests/test_Producer.py b/tests/test_Producer.py index 61dc36bb2..0f0d69e1d 100644 --- a/tests/test_Producer.py +++ b/tests/test_Producer.py @@ -1,11 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import pytest +from struct import pack from confluent_kafka import Producer, KafkaError, KafkaException, \ TopicPartition, libversion -from struct import pack -from .common import TestConsumer + +from tests.common import TestConsumer def error_cb(err): diff --git a/tests/test_log.py b/tests/test_log.py index 813840421..47f0fe965 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -1,10 +1,12 @@ #!/usr/bin/env python from io import StringIO +import logging + import confluent_kafka import confluent_kafka.avro -import logging -from .common import TestConsumer, TestAvroConsumer + +from tests.common import TestConsumer, TestAvroConsumer class CountingFilter(logging.Filter): diff --git a/tests/test_misc.py b/tests/test_misc.py index bab35da28..4db5cff5a 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,14 +1,16 @@ #!/usr/bin/env python -import confluent_kafka -from confluent_kafka import Consumer, Producer -from confluent_kafka.admin import AdminClient import json import pytest import os import time import sys -from .common import TestConsumer + +import confluent_kafka +from confluent_kafka import Consumer, Producer +from confluent_kafka.admin import AdminClient + +from tests.common import TestConsumer def test_version(): diff --git a/tools/source-package-verification.sh b/tools/source-package-verification.sh index 8ad9642e5..58ccefddc 100755 --- a/tools/source-package-verification.sh +++ b/tools/source-package-verification.sh @@ -18,13 +18,11 @@ export DYLD_LIBRARY_PATH="$DYLD_LIBRARY_PATH:$PWD/$lib_dir" python setup.py build && python setup.py install if [[ $OS_NAME == linux && $ARCH == x64 ]]; then - flake8 --exclude ./_venv,*_pb2.py - make docs - if [[ $TEST_CONSUMER_GROUP_PROTOCOL == consumer ]]; then - python -m pytest --timeout 1200 --ignore=dest --ignore=tests/integration/admin - else - python -m pytest --timeout 1200 --ignore=dest + if [[ -z $TEST_CONSUMER_GROUP_PROTOCOL ]]; then + flake8 --exclude ./_venv,*_pb2.py + make docs fi + python -m pytest --timeout 1200 --ignore=dest else python -m pytest --timeout 1200 --ignore=dest --ignore=tests/integration fi From 7544434524abb4991050753439dadde7a74ce831 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 17 May 2024 04:12:27 +0530 Subject: [PATCH 27/29] Style check --- tests/integration/admin/test_user_scram_credentials.py | 1 + tests/integration/producer/test_transactions.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/integration/admin/test_user_scram_credentials.py b/tests/integration/admin/test_user_scram_credentials.py index 08f704338..b9c50ff67 100644 --- a/tests/integration/admin/test_user_scram_credentials.py +++ b/tests/integration/admin/test_user_scram_credentials.py @@ -24,6 +24,7 @@ from tests.common import TestUtils + def test_user_scram_credentials(kafka_cluster): """ Tests for the alter and describe SASL/SCRAM credential operations. diff --git a/tests/integration/producer/test_transactions.py b/tests/integration/producer/test_transactions.py index 8759af579..32d1275bb 100644 --- a/tests/integration/producer/test_transactions.py +++ b/tests/integration/producer/test_transactions.py @@ -23,6 +23,7 @@ from tests.common import TestConsumer + def called_by(): if sys.version_info < (3, 5): return inspect.stack()[1][3] From beb5998edbe4054c0ae34278cf5d5b7cfe4e1521 Mon Sep 17 00:00:00 2001 From: Pranav Rathi <4427674+pranavrth@users.noreply.github.com> Date: Fri, 17 May 2024 14:26:51 +0530 Subject: [PATCH 28/29] Skipping one list offsets assert for Zookeeper --- tests/integration/admin/test_list_offsets.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/integration/admin/test_list_offsets.py b/tests/integration/admin/test_list_offsets.py index f6ad018cf..7d5bee91a 100644 --- a/tests/integration/admin/test_list_offsets.py +++ b/tests/integration/admin/test_list_offsets.py @@ -17,6 +17,7 @@ from confluent_kafka import TopicPartition, IsolationLevel from confluent_kafka.admin import ListOffsetsResultInfo, OffsetSpec +from tests.common import TestUtils def test_list_offsets(kafka_cluster): @@ -65,12 +66,13 @@ def test_list_offsets(kafka_cluster): assert isinstance(result, ListOffsetsResultInfo) assert (result.offset == 3) - requests = {topic_partition: OffsetSpec.max_timestamp()} - futmap = admin_client.list_offsets(requests, **kwargs) - for _, fut in futmap.items(): - result = fut.result() - assert isinstance(result, ListOffsetsResultInfo) - assert (result.offset == 1) + if TestUtils.use_kraft(): + requests = {topic_partition: OffsetSpec.max_timestamp()} + futmap = admin_client.list_offsets(requests, **kwargs) + for _, fut in futmap.items(): + result = fut.result() + assert isinstance(result, ListOffsetsResultInfo) + assert (result.offset == 1) requests = {topic_partition: OffsetSpec.for_timestamp(base_timestamp + 150)} futmap = admin_client.list_offsets(requests, **kwargs) From 7823b51fde6fe58fe7d91894a34793ee2f74473b Mon Sep 17 00:00:00 2001 From: mahajanadhitya <115617755+mahajanadhitya@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:25:19 +0530 Subject: [PATCH 29/29] feature/ListGroupsAPI KIP 848 (#2) * rebase commit * cherry-picked --- examples/adminapi.py | 63 ++++++++++++++++--- src/confluent_kafka/__init__.py | 2 +- src/confluent_kafka/_model/__init__.py | 18 ++++++ src/confluent_kafka/admin/__init__.py | 14 ++++- src/confluent_kafka/admin/_group.py | 15 ++++- src/confluent_kafka/src/Admin.c | 56 +++++++++++++++-- src/confluent_kafka/src/AdminTypes.c | 9 ++- .../admin/test_basic_operations.py | 14 ++++- tests/test_Admin.py | 14 ++++- tests/test_log.py | 2 +- 10 files changed, 185 insertions(+), 22 deletions(-) diff --git a/examples/adminapi.py b/examples/adminapi.py index 390aba030..afea5581d 100755 --- a/examples/adminapi.py +++ b/examples/adminapi.py @@ -18,8 +18,8 @@ # Example use of AdminClient operations. from confluent_kafka import (KafkaException, ConsumerGroupTopicPartitions, - TopicPartition, ConsumerGroupState, TopicCollection, - IsolationLevel) + TopicPartition, ConsumerGroupState, + TopicCollection, IsolationLevel) from confluent_kafka.admin import (AdminClient, NewTopic, NewPartitions, ConfigResource, ConfigEntry, ConfigSource, AclBinding, AclBindingFilter, ResourceType, ResourcePatternType, @@ -27,6 +27,8 @@ ScramMechanism, ScramCredentialInfo, UserScramCredentialUpsertion, UserScramCredentialDeletion, OffsetSpec) +from confluent_kafka._model import ConsumerGroupType + import sys import threading import logging @@ -471,18 +473,64 @@ def example_list(a, args): print("id {} client_id: {} client_host: {}".format(m.id, m.client_id, m.client_host)) +def getConsumerGroupState(state_string): + if state_string == "STABLE": + return ConsumerGroupState.STABLE + elif state_string == "DEAD": + return ConsumerGroupState.DEAD + elif state_string == "PREPARING_REBALANCING": + return ConsumerGroupState.PREPARING_REBALANCING + elif state_string == "COMPLETING_REBALANCING": + return ConsumerGroupState.COMPLETING_REBALANCING + elif state_string == "EMPTY": + return ConsumerGroupState.EMPTY + return ConsumerGroupState.UNKNOWN + + +def getConsumerGroupType(type_string): + if type_string == "CONSUMER": + return ConsumerGroupType.CONSUMER + elif type_string == "CLASSIC": + return ConsumerGroupType.CLASSIC + return ConsumerGroupType.UNKNOWN + + def example_list_consumer_groups(a, args): """ List Consumer Groups """ - states = {ConsumerGroupState[state] for state in args} - future = a.list_consumer_groups(request_timeout=10, states=states) + states = set() + group_types = set() + if len(args) > 0: + isType = False + isState = False + for i in range(0, len(args)): + if (args[i] == "-states"): + if (isState): + raise Exception("Invalid Arguments\n Usage: list_consumer_groups [-states ..] " + + "[-types ..]") + isState = True + elif (args[i] == "-types"): + if (isType): + raise Exception("Invalid Arguments\n Usage: list_consumer_groups [-states ..] " + + "[-types ..]") + isType = True + else: + if (isType): + group_types.add(getConsumerGroupType(args[i])) + elif (isState): + states.add(getConsumerGroupState(args[i])) + else: + raise Exception("Invalid Arguments\n Usage: list_consumer_groups [-states ..] " + + "[-types ..]") + + future = a.list_consumer_groups(request_timeout=10, states=states, group_types=group_types) try: list_consumer_groups_result = future.result() print("{} consumer groups".format(len(list_consumer_groups_result.valid))) for valid in list_consumer_groups_result.valid: - print(" id: {} is_simple: {} state: {}".format( - valid.group_id, valid.is_simple_consumer_group, valid.state)) + print(" id: {} is_simple: {} state: {} group_type: {}".format( + valid.group_id, valid.is_simple_consumer_group, valid.state, valid.group_type)) print("{} errors".format(len(list_consumer_groups_result.errors))) for error in list_consumer_groups_result.errors: print(" error: {}".format(error)) @@ -867,7 +915,8 @@ def example_list_offsets(a, args): sys.stderr.write(' delete_acls ' + ' ..\n') sys.stderr.write(' list []\n') - sys.stderr.write(' list_consumer_groups [ ..]\n') + sys.stderr.write(' list_consumer_groups [-states ..] ' + + '[-types ..]\n') sys.stderr.write(' describe_consumer_groups ..\n') sys.stderr.write(' describe_topics ..\n') sys.stderr.write(' describe_cluster \n') diff --git a/src/confluent_kafka/__init__.py b/src/confluent_kafka/__init__.py index bab73a2c6..0a483dd59 100644 --- a/src/confluent_kafka/__init__.py +++ b/src/confluent_kafka/__init__.py @@ -48,7 +48,7 @@ 'Producer', 'DeserializingConsumer', 'SerializingProducer', 'TIMESTAMP_CREATE_TIME', 'TIMESTAMP_LOG_APPEND_TIME', 'TIMESTAMP_NOT_AVAILABLE', 'TopicPartition', 'Node', - 'ConsumerGroupTopicPartitions', 'ConsumerGroupState', 'Uuid', + 'ConsumerGroupTopicPartitions', 'ConsumerGroupState', 'ConsumerGroupType', 'Uuid', 'IsolationLevel'] __version__ = version()[0] diff --git a/src/confluent_kafka/_model/__init__.py b/src/confluent_kafka/_model/__init__.py index 1c2ec89f0..792033622 100644 --- a/src/confluent_kafka/_model/__init__.py +++ b/src/confluent_kafka/_model/__init__.py @@ -95,6 +95,24 @@ def __lt__(self, other): return self.value < other.value +class ConsumerGroupType(Enum): + """ + Enumerates the different types of Consumer Group Type. + + """ + #: Type is not known or not set + UNKNOWN = cimpl.CONSUMER_GROUP_TYPE_UNKNOWN + #: Consumer Type + CONSUMER = cimpl.CONSUMER_GROUP_TYPE_CONSUMER + #: Classic Type + CLASSIC = cimpl.CONSUMER_GROUP_TYPE_CLASSIC + + def __lt__(self, other): + if self.__class__ != other.__class__: + return NotImplemented + return self.value < other.value + + class TopicCollection: """ Represents collection of topics in the form of different identifiers diff --git a/src/confluent_kafka/admin/__init__.py b/src/confluent_kafka/admin/__init__.py index 924361f2e..cb5220168 100644 --- a/src/confluent_kafka/admin/__init__.py +++ b/src/confluent_kafka/admin/__init__.py @@ -54,7 +54,7 @@ from ._listoffsets import (OffsetSpec, # noqa: F401 ListOffsetsResultInfo) -from .._model import TopicCollection as _TopicCollection +from .._model import TopicCollection as _TopicCollection, ConsumerGroupType as _ConsumerGroupType from ..cimpl import (KafkaException, # noqa: F401 KafkaError, @@ -864,6 +864,8 @@ def list_consumer_groups(self, **kwargs): on broker, and response. Default: `socket.timeout.ms*1000.0` :param set(ConsumerGroupState) states: only list consumer groups which are currently in these states. + :param set(ConsumerGroupType) group_types: only list consumer groups which are currently of + these types. :returns: a future. Result method of the future returns :class:`ListConsumerGroupsResult`. @@ -883,6 +885,16 @@ def list_consumer_groups(self, **kwargs): raise TypeError("All elements of states must be of type ConsumerGroupState") kwargs["states_int"] = [state.value for state in states] kwargs.pop("states") + if "group_types" in kwargs: + group_types = kwargs["group_types"] + if group_types is not None: + if not isinstance(group_types, set): + raise TypeError("'group_types' must be a set") + for group_type in group_types: + if not isinstance(group_type, _ConsumerGroupType): + raise TypeError("All elements of group_types must be of type ConsumerGroupType") + kwargs["group_types_int"] = [group_type.value for group_type in group_types] + kwargs.pop("group_types") f, _ = AdminClient._make_futures([], None, AdminClient._make_list_consumer_groups_result) diff --git a/src/confluent_kafka/admin/_group.py b/src/confluent_kafka/admin/_group.py index 82ab98f1d..06a97c91a 100644 --- a/src/confluent_kafka/admin/_group.py +++ b/src/confluent_kafka/admin/_group.py @@ -14,7 +14,7 @@ from .._util import ConversionUtil -from .._model import ConsumerGroupState +from .._model import ConsumerGroupState, ConsumerGroupType from ._acl import AclOperation @@ -31,13 +31,17 @@ class ConsumerGroupListing: Whether a consumer group is simple or not. state : ConsumerGroupState Current state of the consumer group. + group_type : ConsumerGroupType + Current type of the consumer group. """ - def __init__(self, group_id, is_simple_consumer_group, state=None): + def __init__(self, group_id, is_simple_consumer_group, state=None, group_type=None): self.group_id = group_id self.is_simple_consumer_group = is_simple_consumer_group if state is not None: self.state = ConversionUtil.convert_to_enum(state, ConsumerGroupState) + if group_type is not None: + self.group_type = ConversionUtil.convert_to_enum(group_type, ConsumerGroupType) class ListConsumerGroupsResult: @@ -119,6 +123,8 @@ class ConsumerGroupDescription: Partition assignor. state : ConsumerGroupState Current state of the consumer group. + group_type : ConsumerGroupType + Current type of the consumer group. coordinator: Node Consumer group coordinator. authorized_operations: list(AclOperation) @@ -126,7 +132,7 @@ class ConsumerGroupDescription: """ def __init__(self, group_id, is_simple_consumer_group, members, partition_assignor, state, - coordinator, authorized_operations=None): + coordinator, authorized_operations=None, group_type=None): self.group_id = group_id self.is_simple_consumer_group = is_simple_consumer_group self.members = members @@ -139,4 +145,7 @@ def __init__(self, group_id, is_simple_consumer_group, members, partition_assign self.partition_assignor = partition_assignor if state is not None: self.state = ConversionUtil.convert_to_enum(state, ConsumerGroupState) + if group_type is not None: + self.group_type = ConversionUtil.convert_to_enum(group_type, ConsumerGroupType) + self.coordinator = coordinator diff --git a/src/confluent_kafka/src/Admin.c b/src/confluent_kafka/src/Admin.c index 5d59d71d2..18d64a17d 100644 --- a/src/confluent_kafka/src/Admin.c +++ b/src/confluent_kafka/src/Admin.c @@ -82,6 +82,8 @@ struct Admin_options { rd_kafka_IsolationLevel_t isolation_level; rd_kafka_consumer_group_state_t* states; int states_cnt; + rd_kafka_consumer_group_type_t* group_types; + int group_types_cnt; }; /**@brief "unset" value initializers for Admin_options @@ -185,6 +187,13 @@ Admin_options_to_c (Handle *self, rd_kafka_admin_op_t for_api, goto err; } + if (Admin_options_is_set_ptr(options->group_types) && + (err_obj = rd_kafka_AdminOptions_set_match_consumer_group_types( + c_options, options->group_types, options->group_types_cnt))) { + snprintf(errstr, sizeof(errstr), "%s", rd_kafka_error_string(err_obj)); + goto err; + } + return c_options; err: @@ -1698,24 +1707,28 @@ static const char Admin_delete_acls_doc[] = PyDoc_STR( * @brief List consumer groups */ PyObject *Admin_list_consumer_groups (Handle *self, PyObject *args, PyObject *kwargs) { - PyObject *future, *states_int = NULL; + PyObject *future, *states_int, *group_types_int = NULL; struct Admin_options options = Admin_options_INITIALIZER; rd_kafka_AdminOptions_t *c_options = NULL; CallState cs; rd_kafka_queue_t *rkqu; rd_kafka_consumer_group_state_t *c_states = NULL; + rd_kafka_consumer_group_type_t *c_group_types = NULL; int states_cnt = 0; + int group_types_cnt = 0; int i = 0; static char *kws[] = {"future", /* options */ "states_int", + "group_types_int", "request_timeout", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Of", kws, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOf", kws, &future, &states_int, + &group_types_int, &options.request_timeout)) { goto err; } @@ -1746,6 +1759,32 @@ PyObject *Admin_list_consumer_groups (Handle *self, PyObject *args, PyObject *kw } } + if(group_types_int != NULL && group_types_int != Py_None) { + if(!PyList_Check(group_types_int)) { + PyErr_SetString(PyExc_ValueError, + "group_types must of type list"); + goto err; + } + + group_types_cnt = (int)PyList_Size(group_types_int); + + if(group_types_cnt > 0) { + c_group_types = (rd_kafka_consumer_group_type_t *) + malloc(group_types_cnt*sizeof(rd_kafka_consumer_group_type_t)); + for(i = 0 ; i < group_types_cnt ; i++) { + PyObject *group_type = PyList_GET_ITEM(group_types_int, i); + if(!cfl_PyInt_Check(group_type)) { + PyErr_SetString(PyExc_ValueError, + "Element of group_types must be a valid group type"); + goto err; + } + c_group_types[i] = (rd_kafka_consumer_group_type_t) cfl_PyInt_AsInt(group_type); + } + options.group_types = c_group_types; + options.group_types_cnt = group_types_cnt; + } + } + c_options = Admin_options_to_c(self, RD_KAFKA_ADMIN_OP_LISTCONSUMERGROUPS, &options, future); if (!c_options) { @@ -1760,7 +1799,6 @@ PyObject *Admin_list_consumer_groups (Handle *self, PyObject *args, PyObject *kw /* Use librdkafka's background thread queue to automatically dispatch * Admin_background_event_cb() when the admin operation is finished. */ rkqu = rd_kafka_queue_get_background(self->rk); - /* * Call ListConsumerGroupOffsets * @@ -1774,14 +1812,19 @@ PyObject *Admin_list_consumer_groups (Handle *self, PyObject *args, PyObject *kw if(c_states) { free(c_states); } + if(c_group_types) { + free(c_group_types); + } rd_kafka_queue_destroy(rkqu); /* drop reference from get_background */ rd_kafka_AdminOptions_destroy(c_options); - Py_RETURN_NONE; err: if(c_states) { free(c_states); } + if(c_group_types) { + free(c_group_types); + } if (c_options) { rd_kafka_AdminOptions_destroy(c_options); Py_DECREF(future); @@ -1789,7 +1832,7 @@ PyObject *Admin_list_consumer_groups (Handle *self, PyObject *args, PyObject *kw return NULL; } const char Admin_list_consumer_groups_doc[] = PyDoc_STR( - ".. py:function:: list_consumer_groups(future, [states_int], [request_timeout])\n" + ".. py:function:: list_consumer_groups(future, [states_int], [group_types_int], [request_timeout])\n" "\n" " List all the consumer groups.\n" "\n" @@ -3466,7 +3509,6 @@ static PyObject *Admin_c_ListConsumerGroupsResults_to_py( size_t valid_cnt, const rd_kafka_error_t **c_errors_responses, size_t errors_cnt) { - PyObject *result = NULL; PyObject *ListConsumerGroupsResult_type = NULL; PyObject *ConsumerGroupListing_type = NULL; @@ -3509,6 +3551,8 @@ static PyObject *Admin_c_ListConsumerGroupsResults_to_py( cfl_PyDict_SetInt(kwargs, "state", rd_kafka_ConsumerGroupListing_state(c_valid_responses[i])); + cfl_PyDict_SetInt(kwargs, "group_type", rd_kafka_ConsumerGroupListing_type(c_valid_responses[i])); + args = PyTuple_New(0); valid_result = PyObject_Call(ConsumerGroupListing_type, args, kwargs); diff --git a/src/confluent_kafka/src/AdminTypes.c b/src/confluent_kafka/src/AdminTypes.c index 705ce36d9..e56da3b88 100644 --- a/src/confluent_kafka/src/AdminTypes.c +++ b/src/confluent_kafka/src/AdminTypes.c @@ -570,8 +570,14 @@ static void AdminTypes_AddObjectsConsumerGroupStates (PyObject *m) { PyModule_AddIntConstant(m, "CONSUMER_GROUP_STATE_EMPTY", RD_KAFKA_CONSUMER_GROUP_STATE_EMPTY); } +static void AdminTypes_AddObjectsConsumerGroupTypes (PyObject *m) { + /* rd_kafka_consumer_group_type_t */ + PyModule_AddIntConstant(m, "CONSUMER_GROUP_TYPE_UNKNOWN", RD_KAFKA_CONSUMER_GROUP_TYPE_UNKNOWN); + PyModule_AddIntConstant(m, "CONSUMER_GROUP_TYPE_CONSUMER", RD_KAFKA_CONSUMER_GROUP_TYPE_CONSUMER); + PyModule_AddIntConstant(m, "CONSUMER_GROUP_TYPE_CLASSIC", RD_KAFKA_CONSUMER_GROUP_TYPE_CLASSIC); +} + static void AdminTypes_AddObjectsAlterConfigOpType (PyObject *m) { - /* rd_kafka_consumer_group_state_t */ PyModule_AddIntConstant(m, "ALTER_CONFIG_OP_TYPE_SET", RD_KAFKA_ALTER_CONFIG_OP_TYPE_SET); PyModule_AddIntConstant(m, "ALTER_CONFIG_OP_TYPE_DELETE", RD_KAFKA_ALTER_CONFIG_OP_TYPE_DELETE); PyModule_AddIntConstant(m, "ALTER_CONFIG_OP_TYPE_APPEND", RD_KAFKA_ALTER_CONFIG_OP_TYPE_APPEND); @@ -612,6 +618,7 @@ void AdminTypes_AddObjects (PyObject *m) { AdminTypes_AddObjectsAclOperation(m); AdminTypes_AddObjectsAclPermissionType(m); AdminTypes_AddObjectsConsumerGroupStates(m); + AdminTypes_AddObjectsConsumerGroupTypes(m); AdminTypes_AddObjectsAlterConfigOpType(m); AdminTypes_AddObjectsScramMechanismType(m); AdminTypes_AddObjectsIsolationLevel(m); diff --git a/tests/integration/admin/test_basic_operations.py b/tests/integration/admin/test_basic_operations.py index 1234fbf6d..92827aa39 100644 --- a/tests/integration/admin/test_basic_operations.py +++ b/tests/integration/admin/test_basic_operations.py @@ -17,11 +17,12 @@ import time from confluent_kafka import ConsumerGroupTopicPartitions, TopicPartition, ConsumerGroupState, KafkaError +from confluent_kafka._model import ConsumerGroupType from confluent_kafka.admin import (NewPartitions, ConfigResource, AclBinding, AclBindingFilter, ResourceType, ResourcePatternType, AclOperation, AclPermissionType) from confluent_kafka.error import ConsumeError - +from tests.common import TestUtils topic_prefix = "test-topic" @@ -315,6 +316,17 @@ def consume_messages(group_id, num_messages=None): assert isinstance(result.valid, list) assert not result.valid + # List Consumer Groups with Group Type Option Test + if TestUtils.use_group_protocol_consumer(): + future = admin_client.list_consumer_groups(request_timeout=10, types={ConsumerGroupType.CLASSIC}) + result = future.result() + group_ids = [group.group_id for group in result.valid] + assert group1 not in group_ids, "Consumer group {} was found despite passing Classic Group Type".format(group1) + assert group2 not in group_ids, "Consumer group {} was found despite passing Classic Group Type".format(group2) + for group in group_ids: + assert group.group_type == ConsumerGroupType.CLASSIC + + def verify_config(expconfig, configs): """ Verify that the config key,values in expconfig are found diff --git a/tests/test_Admin.py b/tests/test_Admin.py index b59cefb68..84788dd25 100644 --- a/tests/test_Admin.py +++ b/tests/test_Admin.py @@ -6,7 +6,7 @@ ResourcePatternType, AclOperation, AclPermissionType, AlterConfigOpType, \ ScramCredentialInfo, ScramMechanism, \ UserScramCredentialAlteration, UserScramCredentialDeletion, \ - UserScramCredentialUpsertion, OffsetSpec + UserScramCredentialUpsertion, OffsetSpec, _ConsumerGroupType from confluent_kafka import KafkaException, KafkaError, libversion, \ TopicPartition, ConsumerGroupTopicPartitions, ConsumerGroupState, \ IsolationLevel, TopicCollection @@ -616,6 +616,18 @@ def test_list_consumer_groups_api(): with pytest.raises(TypeError): a.list_consumer_groups(states=["EMPTY"]) + with pytest.raises(TypeError): + a.list_consumer_groups(group_types=["UNKNOWN"]) + + with pytest.raises(TypeError): + a.list_consumer_groups(group_types="UNKNOWN") + + with pytest.raises(TypeError): + a.list_consumer_groups(group_types=[_ConsumerGroupType.UNKNOWN]) + + with pytest.raises(TypeError): + a.list_consumer_groups(group_types=[_ConsumerGroupType.CLASSIC, _ConsumerGroupType.CLASSIC]) + with pytest.raises(TypeError): a.list_consumer_groups(states=[ConsumerGroupState.EMPTY, ConsumerGroupState.STABLE]) diff --git a/tests/test_log.py b/tests/test_log.py index 47f0fe965..dece976a7 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -7,7 +7,7 @@ import confluent_kafka.avro from tests.common import TestConsumer, TestAvroConsumer - +import confluent_kafka.admin class CountingFilter(logging.Filter): def __init__(self, name):