kazoo-ruby 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)