kafkat 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NzIyMjdlYzlmM2JhZDI1YzAzZWM1ZTU2ZmU4NWVhNjI3NjY1NTkwMQ==
4
+ NGFmNTRiNDc3NzkxZmNmYzU1Y2YzZDA5ZmE3ODY5MWQyNGFmNzZmMA==
5
5
  data.tar.gz: !binary |-
6
- YjNkZDUzZGZlYmIzNGRiYTNkNTBhNjAwYWE2NmI5NzEwMWRmOTVkMw==
6
+ NDA5MGExMTQwNWQ0NTU3MGY4YTdmODkxYTY2OTYxNzg1MzdlYWExMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NGQzMjRmOThmYmE3MDQ5ZDUyYTg4Zjg0ZTI3Zjc0MDRlOGY5NmYwMGNlZmI0
10
- ZDlmZWE4OWM3MTI5MGE5NmU0MjU2NGQyYjk5NjNkZDFlZmRlMTllOWRkMDYx
11
- NzhhNDcyYjIyZDU2M2E3NzZhYjE1ZGMyODI3ZjM4ZjJhODM5Mjk=
9
+ NmFiZTUxZjc2NzhlNmJmNThlMGZiNjE0ZjEzYjA5NThjMjEyYzEyMGFiZDg1
10
+ NWZkZTYxYjgwMDIxNjMwMGRlNzM0MzZmYTVkNzNiYmUyMmI4NWQxNDEzNzY2
11
+ ZGZiODVjZGI2ZTEwZmU3YzQ4YjBmMmM0ZDZmODI3MmY1NmRmNjc=
12
12
  data.tar.gz: !binary |-
13
- MGIwYzM3NWMzMTZhNWJlMzdjY2JiNTRkYzNlMzBiZGQ4YmM2MWQ1MjI4ZWNm
14
- Y2ExNGMxNTVjMTQ2Y2QzMWQzODlhZWE2YzljNTkyYTkyMWRiNzBiODkzZDdi
15
- NjQ0OGE1MDlmNDYwY2M5NDZjNzJiYWUxOGE0NDdmMjY0ZjhhMjE=
13
+ Zjc1NmU5OWY0NDBjZTVjODlmNDY4MThiMWUyY2RmNWQ4OWRmOWZhMzEyMmVl
14
+ MzUyOTBjOGEzY2QxMWQ5NWZjMGJmMjRiNWM3ZmRiYzdmOWU5YTQwZDJhMGVh
15
+ NmIzNjg5ZGEyOTEwYzVlZWM5MTQ1MDI4ZWU4OTE3MDk2YjJjMjk=
@@ -1,6 +1,8 @@
1
1
  # CHANGE LOG
2
2
 
3
-
3
+ ## 0.3.0
4
+ - [feature] add `verify-replicas` command
5
+ - [bug-fix] use a pool of Zookeeper connections and handle Zookeeper errors correctly
4
6
 
5
7
  ## 0.2.0
6
- - add `cluster-restart` command
8
+ - [feature] add `cluster-restart` command
@@ -15,6 +15,7 @@ module Kafkat
15
15
  class Base
16
16
  include Formatting
17
17
  include CommandIO
18
+ include Kafkat::Logging
18
19
 
19
20
  attr_reader :config
20
21
 
@@ -46,6 +46,9 @@ module Kafkat
46
46
 
47
47
  assignments =
48
48
  generate_assignments(source_broker, topics, destination_brokers)
49
+
50
+ print "Num of topics got from zookeeper: #{topics.length}\n"
51
+ print "Num of partitions in the assignment: #{assignments.size}\n"
49
52
  prompt_and_execute_assignments(assignments)
50
53
  end
51
54
 
@@ -57,6 +60,7 @@ module Kafkat
57
60
 
58
61
  t.partitions.each do |p|
59
62
  if p.replicas.include? source_broker
63
+ replicas_size = p.replicas.length
60
64
  replicas = p.replicas - [source_broker]
61
65
  source_broker_is_leader = p.replicas.first == source_broker
62
66
  potential_broker_ids = destination_brokers - replicas
@@ -75,6 +79,11 @@ module Kafkat
75
79
  end
76
80
  partitions_by_broker[assigned_broker_id] += 1
77
81
 
82
+ if replicas.length != replicas_size
83
+ STDERR.print "ERROR: Number of replicas changes after reassignment topic: #{t.name}, partition: #{p.id} \n"
84
+ exit 1
85
+ end
86
+
78
87
  assignments << Assignment.new(t.name, p.id, replicas)
79
88
  end
80
89
  end
@@ -7,8 +7,9 @@ module Kafkat
7
7
  'Print all topics.'
8
8
 
9
9
  def run
10
- ts = zookeeper.get_topics
11
- ts.each { |name, t| print_topic(t) }
10
+ topic_names = zookeeper.get_topic_names
11
+
12
+ topic_names.each { |name| print_topic_name(name) }
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,92 @@
1
+ module Kafkat
2
+ module Command
3
+ class VerifyReplicas < Base
4
+ register_as 'verify-replicas'
5
+
6
+ usage 'verify-replicas [--topics] [--broker <id>] [--print-details] [--print-summary]',
7
+ 'Check if all partitions in a topic have same number of replicas.'
8
+
9
+ def run
10
+ opts = Trollop.options do
11
+ opt :topics, "topic names", type: :string
12
+ opt :broker, "broker ID", type: :string
13
+ opt :print_details, "show replica size of mismatched partitions", :default => false
14
+ opt :print_summary, "show summary of mismatched partitions", :default => false
15
+ end
16
+
17
+ topic_names = opts[:topics]
18
+ print_details = opts[:print_details]
19
+ print_summary = opts[:print_summary]
20
+
21
+ if topic_names
22
+ topics_list = topic_names.split(',')
23
+ topics = zookeeper.get_topics(topics_list)
24
+ end
25
+ topics ||= zookeeper.get_topics
26
+ broker = opts[:broker] && opts[:broker].to_i
27
+
28
+ partition_replica_size, partition_replica_size_stat = verify_replicas(broker, topics)
29
+
30
+ print_summary = !print_details || print_summary
31
+ print_mismatched_partitions(partition_replica_size, partition_replica_size_stat, print_details, print_summary)
32
+ end
33
+
34
+ def verify_replicas(broker, topics)
35
+ partition_replica_size = {}
36
+ partition_replica_size_stat = {}
37
+
38
+ topics.each do |_, t|
39
+ partition_replica_size[t.name] = {}
40
+ partition_replica_size_stat[t.name] = {}
41
+
42
+ t.partitions.each do |p|
43
+ replica_size = p.replicas.length
44
+
45
+ next if broker && !p.replicas.include?(broker)
46
+
47
+ partition_replica_size_stat[t.name][replica_size] ||= 0
48
+ partition_replica_size_stat[t.name][replica_size] += 1
49
+
50
+ partition_replica_size[t.name][p.id] = replica_size
51
+ end
52
+
53
+ end
54
+
55
+ return partition_replica_size, partition_replica_size_stat
56
+ end
57
+
58
+ def print_mismatched_partitions(partition_replica_size, partition_replica_size_stat, print_details, print_summary)
59
+ topic_column_width = partition_replica_size.keys.max_by(&:length).length
60
+ if print_details
61
+ printf "%-#{topic_column_width}s %-10s %-15s %-20s\n", "topic", "partition", "replica_size", "replication_factor"
62
+
63
+ partition_replica_size.each do |topic_name, partition|
64
+ replication_factor = partition_replica_size_stat[topic_name].key(partition_replica_size_stat[topic_name].values.max)
65
+
66
+ partition.each do |id, replica_size|
67
+ if replica_size != replication_factor
68
+ printf "%-#{topic_column_width}s %-10d %-15d %-20d\n", topic_name, id, replica_size, replication_factor
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ if print_summary
75
+ printf "%-#{topic_column_width}s %-15s %-10s %-15s %-20s\n", "topic", "replica_size", "count", "percentage", "replication_factor"
76
+ partition_replica_size_stat.each do |topic_name, partition|
77
+ if partition.values.size > 1
78
+ replication_factor = partition_replica_size_stat[topic_name].key(partition_replica_size_stat[topic_name].values.max)
79
+ num_partitions = 0.0
80
+ partition.each { |key, value| num_partitions += value }
81
+
82
+ partition.each do |replica_size, cnt|
83
+ printf "%-#{topic_column_width}s %-15d %-10d %-15d %-20d\n", topic_name, replica_size, cnt, (cnt * 100 /num_partitions)
84
+ .to_i, replication_factor
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,4 +1,5 @@
1
1
  require 'thread'
2
+ require 'zk'
2
3
 
3
4
  module Kafkat
4
5
  module Interface
@@ -44,47 +45,69 @@ module Kafkat
44
45
  raise NotFoundError
45
46
  end
46
47
 
48
+ def get_topic_names()
49
+ return zk.children(topics_path)
50
+ end
51
+
47
52
  def get_topics(names=nil)
53
+ error_msgs = {}
48
54
  topics = {}
49
- names ||= zk.children(topics_path)
55
+
56
+ if names == nil
57
+ pool.with_connection do |cnx|
58
+ names = cnx.children(topics_path)
59
+ end
60
+ end
50
61
 
51
62
  threads = names.map do |name|
52
63
  Thread.new do
53
64
  begin
54
65
  topics[name] = get_topic(name)
55
66
  rescue => e
67
+ error_msgs[name] = e
56
68
  end
57
69
  end
58
70
  end
59
71
  threads.map(&:join)
60
72
 
73
+ unless error_msgs.empty?
74
+ STDERR.print "ERROR: zk cmds failed on get_topics: \n#{error_msgs.values.join("\n")}\n"
75
+ exit 1
76
+ end
61
77
  topics
62
78
  end
63
79
 
64
80
  def get_topic(name)
81
+ partition_queue = Queue.new
65
82
  path1 = topic_path(name)
66
- topic_string = zk.get(path1).first
67
- topic_json = JSON.parse(topic_string)
83
+ path2 = topic_partitions_path(name)
68
84
 
69
85
  partitions = []
70
- path2 = topic_partitions_path(name)
86
+ topic_string = pool.with_connection { |cnx| cnx.get(path1).first }
87
+ partition_ids = pool.with_connection { |cnx| cnx.children(path2) }
71
88
 
72
- threads = zk.children(path2).map do |id|
89
+ topic_json = JSON.parse(topic_string)
90
+
91
+ threads = partition_ids.map do |id|
73
92
  id = id.to_i
93
+
74
94
  Thread.new do
75
95
  path3 = topic_partition_state_path(name, id)
76
- partition_string = zk.get(path3).first
96
+ partition_string = pool.with_connection { |cnx| cnx.get(path3).first }
77
97
  partition_json = JSON.parse(partition_string)
78
-
79
98
  replicas = topic_json['partitions'][id.to_s]
80
99
  leader = partition_json['leader']
81
100
  isr = partition_json['isr']
82
101
 
83
- partitions << Partition.new(name, id, replicas, leader, isr)
102
+ partition_queue << Partition.new(name, id, replicas, leader, isr)
84
103
  end
85
104
  end
86
105
  threads.map(&:join)
87
106
 
107
+ until partition_queue.empty? do
108
+ partitions << partition_queue.pop
109
+ end
110
+
88
111
  partitions.sort_by!(&:id)
89
112
  Topic.new(name, partitions)
90
113
  rescue ZK::Exceptions::NoNode
@@ -115,6 +138,10 @@ module Kafkat
115
138
 
116
139
  private
117
140
 
141
+ def pool
142
+ @pool ||= ZK.new_pool(zk_path, :min_clients => 10, :max_clients => 300, :timeout => 1)
143
+ end
144
+
118
145
  def zk
119
146
  @zk ||= ZK.new(zk_path)
120
147
  end
@@ -1,2 +1,4 @@
1
1
  require 'kafkat/utility/formatting'
2
2
  require 'kafkat/utility/command_io'
3
+ require 'kafkat/utility/logging'
4
+
@@ -18,4 +18,4 @@ module Kafkat
18
18
  end
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -23,6 +23,11 @@ module Kafkat
23
23
  print "\n"
24
24
  end
25
25
 
26
+ def print_topic_name(topic_name)
27
+ print justify(topic_name)
28
+ print "\n"
29
+ end
30
+
26
31
  def print_topic_header
27
32
  print justify('Topic')
28
33
  print "\n"
@@ -0,0 +1,7 @@
1
+ module Kafkat
2
+ module Logging
3
+ def print_err(message)
4
+ STDERR.print message
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module Kafkat
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -34,6 +34,20 @@ module Kafkat
34
34
  Partition.new(name, 3, [0, 1, 2], 0, [0]),
35
35
  Partition.new(name, 4, [0, 1, 2], 1, [1])]}
36
36
  end
37
+
38
+ factory :topic_rep_factor_three_with_four_replicas_in_partition1 do
39
+ name "topic_name1"
40
+ partitions [Partition.new("topic_name1", 0, [0, 1, 2], 0, [0]),
41
+ Partition.new("topic_name1", 1, [0, 1, 2, 6], 1, [1]),
42
+ Partition.new("topic_name1", 2, [0, 1, 2], 2, [2])]
43
+ end
44
+
45
+ factory :topic2_rep_factor_three do
46
+ name "topic_name2"
47
+ partitions [Partition.new("topic_name2", 0, [3, 4, 5], 0, [0]),
48
+ Partition.new("topic_name2", 1, [3, 4, 5], 0, [0]),
49
+ Partition.new("topic_name2", 2, [3, 4, 5], 1, [1])]
50
+ end
37
51
  end
38
52
  end
39
- end
53
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ module Kafkat
4
+ RSpec.describe Command::VerifyReplicas do
5
+ let(:verify_replicas) { Command::VerifyReplicas.new({}) }
6
+
7
+ context 'two topics with replication factor 3' do
8
+ let(:topic_rep_factor_three_with_four_replicas_in_partition1) {
9
+ FactoryGirl.build(:topic_rep_factor_three_with_four_replicas_in_partition1) }
10
+ let(:topic2_rep_factor_three) { FactoryGirl.build(:topic2_rep_factor_three) }
11
+
12
+ it 'should return empty mismatched partitions for all brokers' do
13
+ partition_replica_size, partition_replica_size_stat = verify_replicas.verify_replicas(nil,
14
+ {"topic_name2" => topic2_rep_factor_three})
15
+
16
+ expect(partition_replica_size).to eq({"topic_name2" => {0 => 3, 1 => 3, 2 => 3}})
17
+ expect(partition_replica_size_stat).to eq({"topic_name2" => {3 => 3}})
18
+ end
19
+
20
+ it 'should return topic 1 partition 1 for all brokers' do
21
+ partition_replica_size, partition_replica_size_stat = verify_replicas.verify_replicas(nil,
22
+ {"topic_name1" => topic_rep_factor_three_with_four_replicas_in_partition1,
23
+ "topic_name2" => topic2_rep_factor_three})
24
+
25
+ expect(partition_replica_size).to eq({"topic_name1" => {0 => 3, 1 => 4, 2 => 3}, "topic_name2" => {0 => 3, 1 => 3, 2 => 3}})
26
+ expect(partition_replica_size_stat).to eq({"topic_name1" => {3 => 2, 4 => 1}, "topic_name2" => {3 => 3}})
27
+ end
28
+
29
+ it 'should return topic 1 partition 1 for broker 6' do
30
+ partition_replica_size, partition_replica_size_stat = verify_replicas.verify_replicas(6,
31
+ {"topic_name1" => topic_rep_factor_three_with_four_replicas_in_partition1,
32
+ "topic_name2" => topic2_rep_factor_three})
33
+
34
+ expect(partition_replica_size).to eq({"topic_name1" => {1 => 4}, "topic_name2" => {}})
35
+ expect(partition_replica_size_stat).to eq({"topic_name1" => {4 => 1}, "topic_name2" => {}})
36
+ end
37
+
38
+ it 'should return empty mismatched partition for broker 3' do
39
+ partition_replica_size, partition_replica_size_stat = verify_replicas.verify_replicas(3,
40
+ {"topic_name1" => topic_rep_factor_three_with_four_replicas_in_partition1,
41
+ "topic_name2" => topic2_rep_factor_three})
42
+
43
+ expect(partition_replica_size).to eq({"topic_name1" => {}, "topic_name2" => {0 => 3, 1 => 3, 2 => 3}})
44
+ expect(partition_replica_size_stat).to eq({"topic_name1" => {}, "topic_name2" => {3 => 3}})
45
+ end
46
+ end
47
+
48
+
49
+ end
50
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kafkat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nelson Gauthier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-19 00:00:00.000000000 Z
11
+ date: 2016-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zk
@@ -242,6 +242,7 @@ files:
242
242
  - lib/kafkat/command/set-replication-factor.rb
243
243
  - lib/kafkat/command/shutdown.rb
244
244
  - lib/kafkat/command/topics.rb
245
+ - lib/kafkat/command/verify-replicas.rb
245
246
  - lib/kafkat/config.rb
246
247
  - lib/kafkat/interface.rb
247
248
  - lib/kafkat/interface/admin.rb
@@ -251,10 +252,12 @@ files:
251
252
  - lib/kafkat/utility.rb
252
253
  - lib/kafkat/utility/command_io.rb
253
254
  - lib/kafkat/utility/formatting.rb
255
+ - lib/kafkat/utility/logging.rb
254
256
  - lib/kafkat/version.rb
255
257
  - spec/factories/topic.rb
256
258
  - spec/lib/kafkat/command/cluster_restart_spec.rb
257
259
  - spec/lib/kafkat/command/drain_spec.rb
260
+ - spec/lib/kafkat/command/verify-replicas_spec.rb
258
261
  - spec/spec_helper.rb
259
262
  homepage: https://github.com/airbnb/kafkat
260
263
  licenses:
@@ -276,7 +279,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
276
279
  version: '0'
277
280
  requirements: []
278
281
  rubyforge_project:
279
- rubygems_version: 2.4.1
282
+ rubygems_version: 2.6.6
280
283
  signing_key:
281
284
  specification_version: 4
282
285
  summary: Simplified command-line administration for Kafka brokers
@@ -284,4 +287,5 @@ test_files:
284
287
  - spec/factories/topic.rb
285
288
  - spec/lib/kafkat/command/cluster_restart_spec.rb
286
289
  - spec/lib/kafkat/command/drain_spec.rb
290
+ - spec/lib/kafkat/command/verify-replicas_spec.rb
287
291
  - spec/spec_helper.rb