kazoo-ruby 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,10 @@ module Kazoo
15
15
  replicas.length
16
16
  end
17
17
 
18
+ def preferred_leader
19
+ @replicas.first
20
+ end
21
+
18
22
  def leader
19
23
  @mutex.synchronize do
20
24
  refresh_state if @leader.nil?
@@ -80,13 +84,14 @@ module Kazoo
80
84
  protected
81
85
 
82
86
  def refresh_state
83
- state_json = cluster.zk.get(path: "/brokers/topics/#{topic.name}/partitions/#{id}/state")
84
- raise Kazoo::Error, "Failed to get partition state. Error code: #{state_json.fetch(:rc)}" unless state_json.fetch(:rc) == Zookeeper::Constants::ZOK
87
+ state_result = cluster.zk.get(path: "/brokers/topics/#{topic.name}/partitions/#{id}/state")
88
+ raise Kazoo::Error, "Failed to get partition state. Error code: #{state_result.fetch(:rc)}" unless state_result.fetch(:rc) == Zookeeper::Constants::ZOK
85
89
 
86
- set_state(JSON.parse(state_json.fetch(:data)))
90
+ set_state_from_json(state_result.fetch(:data))
87
91
  end
88
92
 
89
- def set_state(json)
93
+ def set_state_from_json(json_payload)
94
+ json = JSON.parse(json_payload)
90
95
  raise Kazoo::VersionNotSupported unless json.fetch('version') == 1
91
96
 
92
97
  @leader = cluster.brokers.fetch(json.fetch('leader'))
@@ -0,0 +1,74 @@
1
+ module Kazoo
2
+ # Helper class to assign replicas to brokers for new partitions.
3
+ # It tries to e=venly divide the number of leaders and replicas over the brokers,
4
+ # in order to get a comparable load on all the brokers in the cluster.
5
+ class ReplicaAssigner
6
+ attr_reader :cluster
7
+ attr_reader :broker_leaders, :broker_replicas
8
+
9
+ def initialize(cluster)
10
+ @cluster = cluster
11
+ retrieve_initial_counts
12
+ end
13
+
14
+ def brokers
15
+ @cluster.brokers
16
+ end
17
+
18
+ def retrieve_initial_counts
19
+ @broker_leaders, @broker_replicas = {}, {}
20
+
21
+ @cluster.brokers.each do |_, broker|
22
+ @broker_leaders[broker], @broker_replicas[broker] = 0, 0
23
+ end
24
+
25
+ cluster.partitions.each do |partition|
26
+ @broker_leaders[partition.preferred_leader] += 1
27
+ partition.replicas.each do |broker|
28
+ @broker_replicas[broker] += 1
29
+ end
30
+ end
31
+ end
32
+
33
+ def cluster_leader_count
34
+ broker_leaders.values.inject(0, &:+)
35
+ end
36
+
37
+ def cluster_replica_count
38
+ broker_replicas.values.inject(0, &:+)
39
+ end
40
+
41
+ def assign(replication_factor)
42
+ raise Kazoo::ValidationError, "replication_factor should be higher than 0 " if replication_factor <= 0
43
+ raise Kazoo::ValidationError, "replication_factor should not be higher than the number of brokers " if replication_factor > brokers.length
44
+
45
+ # Order all brokers by the current number of leaders (ascending).
46
+ # The first one will be the leader replica
47
+ leader = @broker_leaders
48
+ .to_a
49
+ .sort_by { |pair| [pair[1], pair[0].id] }
50
+ .map(&:first)
51
+ .first
52
+
53
+ # Update the current broker replica counts.
54
+ # The assigned leader replica counts as a leader, but as a replica as well.
55
+ @broker_leaders[leader] += 1
56
+ @broker_replicas[leader] += 1
57
+
58
+ # To assign the other replcias, we remove the broker that was selected as leader from
59
+ # the list of brokers, and sort the rest by the number of replicas they are currently hosting.
60
+ # Then, we take the number of remaining replcias to complete the replication factor.
61
+ other_replicas = @broker_replicas
62
+ .to_a
63
+ .reject { |(key, _)| key == leader }
64
+ .sort_by { |pair| [pair[1], pair[0].id] }
65
+ .map(&:first)
66
+ .take(replication_factor - 1)
67
+
68
+ # Update the current broker replica counts.
69
+ other_replicas.each { |broker| @broker_replicas[broker] += 1 }
70
+
71
+ [leader].concat(other_replicas)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,215 @@
1
+ module Kazoo
2
+
3
+ # A Kazoo::Subscription describes interest in a set of topics of a Kafka cluster.
4
+ #
5
+ # Use Kazoo::Subscription.build to instantiate a subscription. It will return one of
6
+ # the two known subclasses of Kazoo::Subscription: a Kazoo::StaticSubscription for
7
+ # a static list of topics, or a Kazoo::PatternSUbscription for a dynamic list based
8
+ # on a regular expression that serves as a white list or black list.
9
+ class Subscription
10
+ attr_reader :timestamp, :version
11
+
12
+ # Instantiates a whitelist subscription that matches every topic.
13
+ def self.everything
14
+ build(/.*/)
15
+ end
16
+
17
+ # Instantiates a Kazoo::Subscription based on the subscription argument.
18
+ #
19
+ # - If the subscription argument is the name of a topic, a Kazoo::Topic, or an array of those,
20
+ # it will create a static subscription for the provided topic.
21
+ # - If the subscription argument is a regular expression, it will create a pattern subscription.
22
+ # The `pattern` argument will determine whether it is a white_list (default), or black_list.
23
+ # - If the subscription argument is a Kazoo::Subscription, it will return the argument itself.
24
+ def self.build(subscription, pattern: :white_list, timestamp: Time.now)
25
+ case subscription
26
+ when Kazoo::Subscription
27
+ subscription
28
+ when String, Symbol, Kazoo::Topic, Array
29
+ topic_names = Array(subscription).map { |t| topic_name(t) }
30
+ Kazoo::StaticSubscription.new(topic_names, timestamp: timestamp)
31
+ when Regexp
32
+ Kazoo::PatternSubscription.new(subscription, pattern: pattern, timestamp: timestamp)
33
+ else
34
+ raise ArgumentError, "Don't know how to create a subscription from #{subscription.inspect}"
35
+ end
36
+ end
37
+
38
+ # Instantiates a Kazoo::Subscription based on a JSON payload as it is stored in Zookeeper.
39
+ #
40
+ # This method will raise Kazoo::InvalidSubscription if the JSON payload cannot be parsed.
41
+ # Only version 1 payloads are supported.
42
+ def self.from_json(json_payload)
43
+ json = JSON.parse(json_payload)
44
+ version, timestamp = json.fetch('version'), json.fetch('timestamp')
45
+ raise Kazoo::InvalidSubscription, "Only version 1 subscriptions are supported, found version #{version}!" unless version == 1
46
+
47
+ time = Time.at(BigDecimal.new(timestamp) / BigDecimal.new(1000))
48
+
49
+ pattern, subscription = json.fetch('pattern'), json.fetch('subscription')
50
+ raise Kazoo::InvalidSubscription, "Only subscriptions with a single stream are supported" unless subscription.values.all? { |streams| streams == 1 }
51
+
52
+ case pattern
53
+ when 'static'
54
+ topic_names = subscription.keys
55
+ Kazoo::StaticSubscription.new(topic_names, version: version, timestamp: time)
56
+
57
+ when 'white_list', 'black_list'
58
+ raise Kazoo::InvalidSubscription, "Only pattern subscriptions with a single expression are supported" unless subscription.keys.length == 1
59
+ regexp = Regexp.new(subscription.keys.first.tr(',', '|'))
60
+ Kazoo::PatternSubscription.new(regexp, pattern: pattern.to_sym, version: version, timestamp: time)
61
+
62
+ else
63
+ raise Kazoo::InvalidSubscription, "Unrecognized subscription pattern #{pattern.inspect}"
64
+ end
65
+
66
+ rescue JSON::ParserError, KeyError => e
67
+ raise Kazoo::InvalidSubscription.new(e.message)
68
+ end
69
+
70
+
71
+ # Returns a topic name based on various inputs.
72
+ # Helper method used by Kazoo::Subscription.build
73
+ def self.topic_name(topic)
74
+ case topic
75
+ when String, Symbol; topic.to_s
76
+ when Kazoo::Topic; topic.name
77
+ else raise ArgumentError, "Cannot get topic name from #{topic.inspect}"
78
+ end
79
+ end
80
+
81
+ # Returns an array of all Kazoo::Topic instances in the given Kafka cluster
82
+ # that are matched by this subscription.
83
+ def topics(cluster)
84
+ cluster.topics.values.select { |topic| has_topic?(topic) }
85
+ end
86
+
87
+ # Returns an array of all Kazoo::Partition instances in the given Kafka cluster
88
+ # that are matched by this subscription.
89
+ def partitions(cluster)
90
+ topics(cluster).flat_map { |topic| topic.partitions }
91
+ end
92
+
93
+ # has_topic? should return true if a given Kazoo::Topic is part of this subscription.
94
+ def has_topic?(topic)
95
+ raise NotImplementedError
96
+ end
97
+
98
+ # Returns the JSON representation of this subscription that can be stored in Zookeeper.
99
+ def to_json(options = {})
100
+ JSON.dump(as_json(options))
101
+ end
102
+
103
+ def eql?(other)
104
+ other.kind_of?(Kazoo::Subscription) && other.pattern == pattern && other.subscription == subscription
105
+ end
106
+
107
+ alias_method :==, :eql?
108
+
109
+ def hash
110
+ [pattern, subscription].hash
111
+ end
112
+
113
+ def inspect
114
+ "#<#{self.class.name} pattern=#{pattern} subscription=#{subscription.inspect}>"
115
+ end
116
+
117
+ protected
118
+
119
+ # Subclasses should call super(**kwargs) in their initializer.
120
+ def initialize(timestamp: Time.now, version: 1)
121
+ @timestamp, @version = timestamp, version
122
+ end
123
+
124
+ # Subclasses should return a hash that can be converted to JSON to represent
125
+ # the subscription in Zookeeper.
126
+ def subscription
127
+ raise NotImplementedError
128
+ end
129
+
130
+ # Should return the name of the pattern, i.e. static, white_list, or black_list
131
+ def pattern
132
+ raise NotImplementedError
133
+ end
134
+
135
+ private
136
+
137
+ def as_json(options = {})
138
+ {
139
+ version: version,
140
+ pattern: pattern,
141
+ timestamp: msec_timestamp,
142
+ subscription: subscription,
143
+ }
144
+ end
145
+
146
+ def msec_timestamp
147
+ (timestamp.to_i * 1000) + (timestamp.nsec / 1_000_000)
148
+ end
149
+ end
150
+
151
+ # StaticSubscription describes a subscription based on a static list of topic names.
152
+ class StaticSubscription < Kazoo::Subscription
153
+ attr_reader :topic_names
154
+
155
+ # Instantiates a static subscription instance. The topic_names argument must be
156
+ # an array of strings.
157
+ def initialize(topic_names, **kwargs)
158
+ super(**kwargs)
159
+ @topic_names = topic_names
160
+ end
161
+
162
+ def has_topic?(topic)
163
+ topic_names.include?(topic.name)
164
+ end
165
+
166
+ protected
167
+
168
+ def pattern
169
+ :static
170
+ end
171
+
172
+ def subscription
173
+ topic_names.inject({}) { |hash, topic_name| hash[topic_name] = 1; hash }
174
+ end
175
+ end
176
+
177
+ # PatternSubscription describes a subscription based on a regular expression that
178
+ # serves as either a whitelist or blacklist filter for all topics available in
179
+ # a Kafka cluster.
180
+ class PatternSubscription < Kazoo::Subscription
181
+ PATTERN_TYPES = [:black_list, :white_list].freeze
182
+
183
+ attr_reader :pattern, :regexp
184
+
185
+ def initialize(regexp, pattern: :white_list, **kwargs)
186
+ super(**kwargs)
187
+ raise ArgumentError, "#{pattern.inspect} is not a valid pattern type" unless PATTERN_TYPES.include?(pattern)
188
+ @regexp, @pattern = regexp, pattern
189
+ end
190
+
191
+ # Returns true if this subscription uses a whitelist pattern
192
+ def white_list?
193
+ pattern == :white_list
194
+ end
195
+
196
+ # Returns true if this subscription uses a blacklist pattern
197
+ def black_list?
198
+ pattern == :black_list
199
+ end
200
+
201
+ # Returns true if the whitelist or blacklist does not filter out the provided topic.
202
+ def has_topic?(topic)
203
+ case pattern
204
+ when :white_list; topic.name =~ regexp
205
+ when :black_list; topic.name !~ regexp
206
+ end
207
+ end
208
+
209
+ protected
210
+
211
+ def subscription
212
+ { regexp.inspect[1..-2] => 1 }
213
+ end
214
+ end
215
+ end
data/lib/kazoo/topic.rb CHANGED
@@ -1,40 +1,35 @@
1
1
  module Kazoo
2
2
  class Topic
3
- VALID_TOPIC_NAMES = %r{\A[a-zA-Z0-9\\._\\-]+\z}
3
+ ALL_PRELOAD_METHODS = [:partitions, :config].freeze
4
+ DEFAULT_PRELOAD_METHODS = [:partitions].freeze
5
+ VALID_TOPIC_NAMES = %r{\A[a-zA-Z0-9\\._\\-]+\z}
4
6
  BLACKLISTED_TOPIC_NAMES = %r{\A\.\.?\z}
5
7
 
6
8
  attr_reader :cluster, :name
7
- attr_writer :partitions
8
9
 
9
- def initialize(cluster, name)
10
+ def initialize(cluster, name, config: nil, partitions: nil)
10
11
  @cluster, @name = cluster, name
11
- end
12
-
13
- def self.from_json(cluster, name, json)
14
- raise Kazoo::VersionNotSupported unless json.fetch('version') == 1
15
-
16
- topic = new(cluster, name)
17
- topic.partitions = json.fetch('partitions').map do |(id, replicas)|
18
- topic.partition(id.to_i, replicas: replicas.map { |b| cluster.brokers[b] })
19
- end.sort_by(&:id)
20
12
 
21
- return topic
13
+ self.partitions = partitions
14
+ self.config = config
22
15
  end
23
16
 
24
17
  def partitions
25
- @partitions ||= begin
26
- result = cluster.zk.get(path: "/brokers/topics/#{name}")
27
- raise Kazoo::Error, "Failed to get list of partitions for #{name}. Result code: #{result.fetch(:rc)}" if result.fetch(:rc) != Zookeeper::Constants::ZOK
18
+ @partitions ||= load_partitions_from_zookeeper
19
+ end
28
20
 
29
- partition_json = JSON.parse(result.fetch(:data))
30
- partition_json.fetch('partitions').map do |(id, replicas)|
31
- partition(id.to_i, replicas: replicas.map { |b| cluster.brokers[b] })
32
- end
33
- end
21
+ def partitions=(ps)
22
+ @partitions = ps
23
+ end
24
+
25
+ def partition(index, **kwargs)
26
+ Kazoo::Partition.new(self, index, **kwargs)
34
27
  end
35
28
 
36
- def partition(*args)
37
- Kazoo::Partition.new(self, *args)
29
+ def append_partition(**kwargs)
30
+ new_partition = partition(partitions.length, **kwargs)
31
+ partitions << new_partition
32
+ new_partition
38
33
  end
39
34
 
40
35
  def replication_factor
@@ -42,7 +37,7 @@ module Kazoo
42
37
  end
43
38
 
44
39
  def under_replicated?
45
- partitions.any?(:under_replicated?)
40
+ partitions.any?(&:under_replicated?)
46
41
  end
47
42
 
48
43
  def inspect
@@ -80,29 +75,34 @@ module Kazoo
80
75
  false
81
76
  end
82
77
 
83
- def create
84
- raise Kazoo::Error, "The topic #{name} already exists!" if exists?
85
- validate
78
+ def add_partitions(partitions: nil, replication_factor: nil)
79
+ raise ArgumentError, "partitions must be a positive integer" if Integer(partitions) <= 0
80
+ raise ArgumentError, "replication_factor must be a positive integer" if Integer(replication_factor) <= 0
81
+
82
+ raise Kazoo::TopicNotFound, "The topic #{name} does not exists!" unless exists?
86
83
 
87
- result = cluster.zk.create(
88
- path: "/config/topics/#{name}",
89
- data: JSON.generate(version: 1, config: {})
90
- )
84
+ replica_assigner = Kazoo::ReplicaAssigner.new(cluster)
91
85
 
92
- if result.fetch(:rc) != Zookeeper::Constants::ZOK
93
- raise Kazoo::Error, "Failed to create topic config node for #{name}. Error code: #{result.fetch(:rc)}"
86
+ partitions.times do
87
+ replicas = replica_assigner.assign(replication_factor)
88
+ append_partition(replicas: replicas)
94
89
  end
95
90
 
96
- result = cluster.zk.create(
97
- path: "/brokers/topics/#{name}",
98
- data: JSON.generate(version: 1, partitions: partitions_as_json)
99
- )
91
+ validate
92
+ write_partitions_to_zookeeper
93
+ wait_for_partitions
94
+ cluster.reset_metadata
95
+ end
100
96
 
101
- if result.fetch(:rc) != Zookeeper::Constants::ZOK
102
- raise Kazoo::Error, "Failed to create topic #{name}. Error code: #{result.fetch(:rc)}"
103
- end
97
+ def save
98
+ raise Kazoo::Error, "The topic #{name} already exists!" if exists?
99
+ validate
100
+
101
+ write_config_to_zookeeper
102
+ write_partitions_to_zookeeper
104
103
 
105
104
  wait_for_partitions
105
+ cluster.reset_metadata
106
106
  end
107
107
 
108
108
  def destroy
@@ -136,9 +136,63 @@ module Kazoo
136
136
  else
137
137
  raise Kazoo::Error, "Failed to delete topic #{name}. Error code: #{result.fetch(:rc)}"
138
138
  end
139
+
140
+ cluster.reset_metadata
139
141
  end
140
142
 
141
143
  def config
144
+ @config ||= load_config_from_zookeeper
145
+ end
146
+
147
+ def config=(hash)
148
+ return if hash.nil?
149
+ @config = hash.inject({}) { |h, (k, v)| h[k.to_s] = v.to_s; h }
150
+ end
151
+
152
+ def set_config(key, value)
153
+ config[key.to_s] = value.to_s
154
+ write_config_to_zookeeper
155
+ end
156
+
157
+ def delete_config(key)
158
+ config.delete(key.to_s)
159
+ write_config_to_zookeeper
160
+ end
161
+
162
+ def reset_default_config
163
+ @config = {}
164
+ write_config_to_zookeeper
165
+ end
166
+
167
+ def set_partitions_from_json(json_payload)
168
+ partition_json = JSON.parse(json_payload)
169
+ raise Kazoo::VersionNotSupported if partition_json.fetch('version') != 1
170
+
171
+ @partitions = partition_json.fetch('partitions').map do |(id, replicas)|
172
+ partition(id.to_i, replicas: replicas.map { |b| cluster.brokers[b] })
173
+ end
174
+
175
+ @partitions.sort_by!(&:id)
176
+
177
+ self
178
+ end
179
+
180
+ def set_config_from_json(json_payload)
181
+ config_json = JSON.parse(json_payload)
182
+ raise Kazoo::VersionNotSupported if config_json.fetch('version') != 1
183
+
184
+ @config = config_json.fetch('config')
185
+
186
+ self
187
+ end
188
+
189
+ def load_partitions_from_zookeeper
190
+ result = cluster.zk.get(path: "/brokers/topics/#{name}")
191
+ raise Kazoo::Error, "Failed to get list of partitions for #{name}. Result code: #{result.fetch(:rc)}" if result.fetch(:rc) != Zookeeper::Constants::ZOK
192
+ set_partitions_from_json(result.fetch(:data)).partitions
193
+ end
194
+
195
+ def load_config_from_zookeeper
142
196
  result = cluster.zk.get(path: "/config/topics/#{name}")
143
197
  case result.fetch(:rc)
144
198
  when Zookeeper::Constants::ZOK
@@ -149,33 +203,30 @@ module Kazoo
149
203
  raise Kazoo::Error, "Failed to retrieve topic config"
150
204
  end
151
205
 
152
- config = JSON.parse(result.fetch(:data))
153
- raise Kazoo::VersionNotSupported if config.fetch('version') != 1
154
-
155
- config.fetch('config')
206
+ set_config_from_json(result.fetch(:data)).config
156
207
  end
157
208
 
158
- def set_config(key, value)
159
- new_config = config
160
- new_config[key.to_s] = value.to_s
161
- write_config(new_config)
162
- end
209
+ def write_partitions_to_zookeeper
210
+ path = "/brokers/topics/#{name}"
211
+ data = JSON.generate(version: 1, partitions: partitions_as_json)
163
212
 
164
- def delete_config(key)
165
- new_config = config
166
- new_config.delete(key.to_s)
167
- write_config(new_config)
168
- end
213
+ result = cluster.zk.set(path: path, data: data)
214
+ case result.fetch(:rc)
215
+ when Zookeeper::Constants::ZOK
216
+ # continue
217
+ when Zookeeper::Constants::ZNONODE
218
+ result = cluster.zk.create(path: path, data: data)
219
+ raise Kazoo::Error, "Failed to write partitions to zookeeper. Result code: #{result.fetch(:rc)}" unless result.fetch(:rc) == Zookeeper::Constants::ZOK
220
+ else
221
+ raise Kazoo::Error, "Failed to write partitions to zookeeper. Result code: #{result.fetch(:rc)}"
222
+ end
169
223
 
170
- def reset_default_config
171
- write_config({})
224
+ cluster.reset_metadata
172
225
  end
173
226
 
174
- def write_config(config_hash)
175
- raise Kazoo::TopicNotFound, "Topic #{name.inspect} does not exist" unless exists?
176
-
177
- config = config_hash.inject({}) { |h, (k,v)| h[k.to_s] = v.to_s; h }
178
- config_json = JSON.generate(version: 1, config: config)
227
+ def write_config_to_zookeeper
228
+ config_hash = config.inject({}) { |h, (k,v)| h[k.to_s] = v.to_s; h }
229
+ config_json = JSON.generate(version: 1, config: config_hash)
179
230
 
180
231
  # Set topic config
181
232
  result = cluster.zk.set(path: "/config/topics/#{name}", data: config_json)
@@ -184,20 +235,30 @@ module Kazoo
184
235
  # continue
185
236
  when Zookeeper::Constants::ZNONODE
186
237
  result = cluster.zk.create(path: "/config/topics/#{name}", data: config_json)
187
- raise Kazoo::Error, "Failed to set topic config" unless result.fetch(:rc) == Zookeeper::Constants::ZOK
238
+ raise Kazoo::Error, "Failed to write topic config to zookeeper. Result code: #{result.fetch(:rc)}" unless result.fetch(:rc) == Zookeeper::Constants::ZOK
188
239
  else
189
- raise Kazoo::Error, "Failed to set topic config"
240
+ raise Kazoo::Error, "Failed to write topic config to zookeeper. Result code: #{result.fetch(:rc)}"
190
241
  end
191
242
 
192
243
  # Set config change notification
193
244
  result = cluster.zk.create(path: "/config/changes/config_change_", data: name.inspect, sequence: true)
194
245
  raise Kazoo::Error, "Failed to set topic config change notification" unless result.fetch(:rc) == Zookeeper::Constants::ZOK
246
+
247
+ cluster.reset_metadata
195
248
  end
196
249
 
197
- def self.create(cluster, name, partitions: nil, replication_factor: nil)
198
- topic = new(cluster, name)
199
- topic.send(:sequentially_assign_partitions, partitions, replication_factor)
200
- topic.create
250
+ def self.create(cluster, name, partitions: nil, replication_factor: nil, config: {})
251
+ topic = new(cluster, name, config: config, partitions: [])
252
+ raise Kazoo::Error, "Topic #{name} already exists" if topic.exists?
253
+
254
+ replica_assigner = Kazoo::ReplicaAssigner.new(cluster)
255
+
256
+ partitions.times do
257
+ replicas = replica_assigner.assign(replication_factor)
258
+ topic.append_partition(replicas: replicas)
259
+ end
260
+
261
+ topic.save
201
262
  topic
202
263
  end
203
264
 
@@ -214,21 +275,6 @@ module Kazoo
214
275
  threads.each(&:join)
215
276
  end
216
277
 
217
- def sequentially_assign_partitions(partition_count, replication_factor, brokers: nil)
218
- brokers = cluster.brokers.values if brokers.nil?
219
- raise ArgumentError, "replication_factor should be smaller or equal to the number of brokers" if replication_factor > brokers.length
220
-
221
- # Sequentially assign replicas to brokers. There might be a better way.
222
- @partitions = 0.upto(partition_count - 1).map do |partition_index|
223
- replicas = 0.upto(replication_factor - 1).map do |replica_index|
224
- broker_index = (partition_index + replica_index) % brokers.length
225
- brokers[broker_index]
226
- end
227
-
228
- self.partition(partition_index, replicas: replicas)
229
- end
230
- end
231
-
232
278
  def partitions_as_json
233
279
  partitions.inject({}) do |hash, partition|
234
280
  hash[partition.id] = partition.replicas.map(&:id)