1111
1212import py
1313
14- from kafka import errors , KafkaAdminClient , KafkaClient , KafkaConsumer , KafkaProducer
15- from kafka .errors import InvalidReplicationFactorError , KafkaTimeoutError
16- from kafka .protocol .new .admin import CreateTopicsRequest
17- from kafka .protocol .new .metadata import MetadataRequest
14+ from kafka import errors , KafkaAdminClient
15+ from kafka .errors import InvalidReplicationFactorError
1816from test .testutil import env_kafka_version , random_string
1917from test .service import ExternalService , SpawnedService
2018
2119log = logging .getLogger (__name__ )
2220
2321
22+ def create_topics (broker , topic_names , num_partitions = None , replication_factor = None ):
23+ """Create topics on the given broker fixture.
24+
25+ Uses KafkaAdminClient for Kafka 0.10.1+, falls back to CLI.
26+ """
27+ if num_partitions is None :
28+ num_partitions = broker .partitions
29+ if replication_factor is None :
30+ replication_factor = broker .replicas
31+ if env_kafka_version () >= (0 , 10 , 1 , 0 ):
32+ _create_topics_via_admin (broker , topic_names , num_partitions , replication_factor )
33+ else :
34+ for topic_name in topic_names :
35+ # TODO: verify kafka-topics.sh support for early 0.8 brokers
36+ broker ._create_topic_via_cli (topic_name , num_partitions , replication_factor )
37+
38+
39+ def _create_topics_via_admin (broker , topic_names , num_partitions , replication_factor ):
40+ from kafka .admin import NewTopic
41+ params = broker ._enrich_client_params ({}, client_id = 'topic_creator' )
42+ admin = KafkaAdminClient (** params )
43+ try :
44+ topics = [NewTopic (name , num_partitions , replication_factor ) for name in topic_names ]
45+ admin .create_topics (topics )
46+ except InvalidReplicationFactorError :
47+ time .sleep (0.5 )
48+ topics = [NewTopic (name , num_partitions , replication_factor ) for name in topic_names ]
49+ admin .create_topics (topics )
50+ finally :
51+ admin .close ()
52+
53+
54+ def client_params (broker , client_id = 'client' , ** overrides ):
55+ """Build connection params for a client from a broker fixture."""
56+ return broker ._enrich_client_params (overrides , client_id = '%s_%s' % (client_id , random_string (4 )))
57+
58+
2459def get_open_port ():
2560 sock = socket .socket ()
2661 sock .bind (("127.0.0.1" , 0 ))
@@ -313,10 +348,8 @@ def __init__(self, host, port, broker_id, zookeeper=None, zk_chroot=None,
313348
314349 if self .external :
315350 self .child = ExternalService (self .host , self .port )
316- self ._client = next (self .get_clients (1 , client_id = '_internal_client' ))
317351 self .running = True
318352 else :
319- self ._client = None
320353 self .running = False
321354
322355 self .sasl_config = ''
@@ -500,8 +533,6 @@ def start(self):
500533 else :
501534 raise RuntimeError ('Failed to start KafkaInstance before max_timeout' )
502535
503- self ._client = next (self .get_clients (1 , client_id = '_internal_client' ))
504-
505536 self .out ("Done!" )
506537 self .running = True
507538
@@ -603,82 +634,6 @@ def _format_log_dirs(self):
603634 raise RuntimeError ("Failed to format log dirs!" )
604635 return True
605636
606- def _send_request (self , request , timeout = None ):
607- def _failure (error ):
608- raise error
609- retries = 10
610- while True :
611- node_id = self ._client .least_loaded_node ()
612- for connect_retry in range (40 ):
613- self ._client .maybe_connect (node_id )
614- if self ._client .connected (node_id ):
615- break
616- self ._client .poll (timeout_ms = 100 )
617- else :
618- raise RuntimeError ('Could not connect to broker with node id %s' % (node_id ,))
619-
620- try :
621- future = self ._client .send (node_id , request )
622- future .error_on_callbacks = True
623- future .add_errback (_failure )
624- self ._client .poll (future = future , timeout_ms = timeout )
625- if not future .is_done :
626- raise KafkaTimeoutError ()
627- return future .value
628- except Exception as exc :
629- time .sleep (1 )
630- retries -= 1
631- if retries == 0 :
632- raise exc
633- else :
634- pass # retry
635-
636- def _create_topic (self , topic_name , num_partitions = None , replication_factor = None , timeout_ms = 10000 ):
637- if num_partitions is None :
638- num_partitions = self .partitions
639- if replication_factor is None :
640- replication_factor = self .replicas
641-
642- # Try different methods to create a topic, from the fastest to the slowest
643- if self .auto_create_topic and num_partitions == self .partitions and replication_factor == self .replicas :
644- self ._create_topic_via_metadata (topic_name , timeout_ms )
645- elif env_kafka_version () >= (0 , 10 , 1 , 0 ):
646- try :
647- self ._create_topic_via_admin_api (topic_name , num_partitions , replication_factor , timeout_ms )
648- except InvalidReplicationFactorError :
649- # wait and try again
650- # in CI the brokers sometimes take a while to find themselves
651- time .sleep (0.5 )
652- self ._create_topic_via_admin_api (topic_name , num_partitions , replication_factor , timeout_ms )
653- else :
654- self ._create_topic_via_cli (topic_name , num_partitions , replication_factor )
655-
656- def _create_topic_via_metadata (self , topic_name , timeout_ms = 10000 ):
657- timeout_at = time .time () + timeout_ms / 1000
658- while time .time () < timeout_at :
659- response = self ._send_request (MetadataRequest [0 ]([topic_name ]), timeout_ms )
660- if response .topics [0 ][0 ] == 0 :
661- return
662- log .warning ("Unable to create topic via MetadataRequest: err %d" , response .topics [0 ][0 ])
663- time .sleep (1 )
664- else :
665- raise RuntimeError ('Unable to create topic via MetadataRequest' )
666-
667- def _create_topic_via_admin_api (self , topic_name , num_partitions , replication_factor , timeout_ms = 10000 ):
668- version = self ._client .api_version (CreateTopicsRequest , max_version = 7 )
669- topic = CreateTopicsRequest .CreatableTopic (
670- name = topic_name ,
671- num_partitions = num_partitions ,
672- replication_factor = replication_factor ,
673- assignments = [],
674- configs = []
675- )
676- request = CreateTopicsRequest [version ](topics = [topic ], timeout_ms = timeout_ms )
677- response = self ._send_request (request , timeout = timeout_ms )
678- for topic_result in response .topics :
679- if topic_result .error_code != 0 :
680- raise errors .for_code (topic_result .error_code )
681-
682637 def _create_topic_via_cli (self , topic_name , num_partitions , replication_factor ):
683638 args = self .run_script ('kafka-topics.sh' ,
684639 '--create' ,
@@ -726,10 +681,6 @@ def get_topic_names(self):
726681 raise RuntimeError ("Failed to list topics!" )
727682 return stdout .decode ().splitlines (False )
728683
729- def create_topics (self , topic_names , num_partitions = None , replication_factor = None ):
730- for topic_name in topic_names :
731- self ._create_topic (topic_name , num_partitions , replication_factor )
732-
733684 def _enrich_client_params (self , params , ** defaults ):
734685 params = params .copy ()
735686 for key , value in defaults .items ():
@@ -746,34 +697,6 @@ def _enrich_client_params(self, params, **defaults):
746697 params .setdefault ('ssl_check_hostname' , False )
747698 return params
748699
749- @staticmethod
750- def _create_many_clients (cnt , cls , * args , ** params ):
751- client_id = params ['client_id' ]
752- for _ in range (cnt ):
753- params ['client_id' ] = '%s_%s' % (client_id , random_string (4 ))
754- yield cls (* args , ** params )
755-
756- def get_clients (self , cnt = 1 , ** params ):
757- params = self ._enrich_client_params (params , client_id = 'client' )
758- for client in self ._create_many_clients (cnt , KafkaClient , ** params ):
759- yield client
760-
761- def get_admin_clients (self , cnt , ** params ):
762- params = self ._enrich_client_params (params , client_id = 'admin_client' )
763- for client in self ._create_many_clients (cnt , KafkaAdminClient , ** params ):
764- yield client
765-
766- def get_consumers (self , cnt , topics , ** params ):
767- params = self ._enrich_client_params (
768- params , client_id = 'consumer' , heartbeat_interval_ms = 500 , auto_offset_reset = 'earliest'
769- )
770- for client in self ._create_many_clients (cnt , KafkaConsumer , * topics , ** params ):
771- yield client
772-
773- def get_producers (self , cnt , ** params ):
774- params = self ._enrich_client_params (params , client_id = 'producer' )
775- for client in self ._create_many_clients (cnt , KafkaProducer , ** params ):
776- yield client
777700
778701
779702def get_api_versions ():
0 commit comments