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.
- checksums.yaml +4 -4
- data/lib/kazoo.rb +7 -0
- data/lib/kazoo/broker.rb +31 -16
- data/lib/kazoo/cli/consumers.rb +36 -1
- data/lib/kazoo/cli/topics.rb +14 -1
- data/lib/kazoo/cluster.rb +61 -29
- data/lib/kazoo/consumergroup.rb +148 -34
- data/lib/kazoo/partition.rb +9 -4
- data/lib/kazoo/replica_assigner.rb +74 -0
- data/lib/kazoo/subscription.rb +215 -0
- data/lib/kazoo/topic.rb +128 -82
- data/lib/kazoo/version.rb +1 -1
- data/test/functional/functional_consumergroup_test.rb +105 -53
- data/test/functional/functional_subscription_test.rb +62 -0
- data/test/functional/functional_topic_management_test.rb +25 -2
- data/test/replica_assigner_test.rb +80 -0
- data/test/subscription_test.rb +148 -0
- data/test/topic_test.rb +6 -19
- metadata +10 -2
data/lib/kazoo/partition.rb
CHANGED
@@ -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
|
-
|
84
|
-
raise Kazoo::Error, "Failed to get partition state. Error code: #{
|
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
|
-
|
90
|
+
set_state_from_json(state_result.fetch(:data))
|
87
91
|
end
|
88
92
|
|
89
|
-
def
|
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
|
-
|
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
|
-
|
13
|
+
self.partitions = partitions
|
14
|
+
self.config = config
|
22
15
|
end
|
23
16
|
|
24
17
|
def partitions
|
25
|
-
@partitions ||=
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
37
|
-
|
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?(
|
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
|
84
|
-
raise
|
85
|
-
|
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
|
-
|
88
|
-
path: "/config/topics/#{name}",
|
89
|
-
data: JSON.generate(version: 1, config: {})
|
90
|
-
)
|
84
|
+
replica_assigner = Kazoo::ReplicaAssigner.new(cluster)
|
91
85
|
|
92
|
-
|
93
|
-
|
86
|
+
partitions.times do
|
87
|
+
replicas = replica_assigner.assign(replication_factor)
|
88
|
+
append_partition(replicas: replicas)
|
94
89
|
end
|
95
90
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
91
|
+
validate
|
92
|
+
write_partitions_to_zookeeper
|
93
|
+
wait_for_partitions
|
94
|
+
cluster.reset_metadata
|
95
|
+
end
|
100
96
|
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
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
|
159
|
-
|
160
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
171
|
-
write_config({})
|
224
|
+
cluster.reset_metadata
|
172
225
|
end
|
173
226
|
|
174
|
-
def
|
175
|
-
|
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
|
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
|
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
|
-
|
200
|
-
|
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)
|