kafkat 0.0.9
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 +15 -0
- data/.gitignore +6 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +202 -0
- data/README.md +59 -0
- data/Rakefile +8 -0
- data/bin/kafkat +4 -0
- data/kafkat.gemspec +26 -0
- data/lib/kafkat/cli.rb +71 -0
- data/lib/kafkat/cluster/assignment.rb +4 -0
- data/lib/kafkat/cluster/broker.rb +4 -0
- data/lib/kafkat/cluster/partition.rb +11 -0
- data/lib/kafkat/cluster/topic.rb +4 -0
- data/lib/kafkat/cluster.rb +4 -0
- data/lib/kafkat/command/brokers.rb +16 -0
- data/lib/kafkat/command/clean-indexes.rb +30 -0
- data/lib/kafkat/command/controller.rb +18 -0
- data/lib/kafkat/command/elect-leaders.rb +31 -0
- data/lib/kafkat/command/partitions.rb +50 -0
- data/lib/kafkat/command/reassign.rb +89 -0
- data/lib/kafkat/command/resign-rewrite.rb +76 -0
- data/lib/kafkat/command/shutdown.rb +30 -0
- data/lib/kafkat/command/topics.rb +15 -0
- data/lib/kafkat/command.rb +68 -0
- data/lib/kafkat/config.rb +45 -0
- data/lib/kafkat/interface/admin.rb +90 -0
- data/lib/kafkat/interface/kafka_logs.rb +54 -0
- data/lib/kafkat/interface/zookeeper.rb +147 -0
- data/lib/kafkat/interface.rb +3 -0
- data/lib/kafkat/utility/formatting.rb +63 -0
- data/lib/kafkat/utility.rb +1 -0
- data/lib/kafkat/version.rb +3 -0
- data/lib/kafkat.rb +14 -0
- metadata +148 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
module Kafkat
|
2
|
+
module Command
|
3
|
+
class Reassign < Base
|
4
|
+
register_as 'reassign'
|
5
|
+
|
6
|
+
usage 'reassign [topic] [--brokers <ids>] [--replicas <n>]',
|
7
|
+
'Begin reassignment of partitions.'
|
8
|
+
|
9
|
+
def run
|
10
|
+
topic_name = ARGV.shift unless ARGV[0] && ARGV[0].start_with?('--')
|
11
|
+
|
12
|
+
all_brokers = zookeeper.get_brokers
|
13
|
+
topics = topic_name && zookeeper.get_topics([topic_name])
|
14
|
+
topics ||= zookeeper.get_topics
|
15
|
+
|
16
|
+
opts = Trollop.options do
|
17
|
+
opt :brokers, "replica set (broker IDs)", type: :string
|
18
|
+
opt :replicas, "number of replicas (count)", type: :integer
|
19
|
+
end
|
20
|
+
|
21
|
+
broker_ids = opts[:brokers] && opts[:brokers].split(',').map(&:to_i)
|
22
|
+
replica_count = opts[:replicas]
|
23
|
+
|
24
|
+
broker_ids ||= zookeeper.get_brokers.values.map(&:id)
|
25
|
+
|
26
|
+
all_brokers_id = all_brokers.values.map(&:id)
|
27
|
+
broker_ids.each do |id|
|
28
|
+
if !all_brokers_id.include?(id)
|
29
|
+
print "ERROR: Broker #{id} is not currently active.\n"
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# *** This logic is duplicated from Kakfa 0.8.1.1 ***
|
35
|
+
|
36
|
+
assignments = []
|
37
|
+
broker_count = broker_ids.size
|
38
|
+
|
39
|
+
topics.each do |_, t|
|
40
|
+
# This is how Kafka's AdminUtils determines these values.
|
41
|
+
partition_count = t.partitions.size
|
42
|
+
topic_replica_count = replica_count || t.partitions[0].replicas.size
|
43
|
+
|
44
|
+
if topic_replica_count > broker_count
|
45
|
+
print "ERROR: Replication factor is larger than brokers.\n"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
49
|
+
start_index = Random.rand(broker_count)
|
50
|
+
replica_shift = Random.rand(broker_count)
|
51
|
+
|
52
|
+
t.partitions.each do |p|
|
53
|
+
replica_shift += 1 if p.id > 0 && p.id % broker_count == 0
|
54
|
+
first_replica_index = (p.id + start_index) % broker_count
|
55
|
+
|
56
|
+
replicas = [broker_ids[first_replica_index]]
|
57
|
+
|
58
|
+
(0...topic_replica_count-1).each do |i|
|
59
|
+
shift = 1 + (replica_shift + i) % (broker_count - 1)
|
60
|
+
index = (first_replica_index + shift) % broker_count
|
61
|
+
replicas << broker_ids[index]
|
62
|
+
end
|
63
|
+
|
64
|
+
replicas.reverse!
|
65
|
+
assignments << Assignment.new(t.name, p.id, replicas)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# ****************
|
70
|
+
|
71
|
+
print "This operation executes the following assignments:\n\n"
|
72
|
+
print_assignment_header
|
73
|
+
assignments.each { |a| print_assignment(a) }
|
74
|
+
print "\n"
|
75
|
+
|
76
|
+
return unless agree("Proceed (y/n)?")
|
77
|
+
|
78
|
+
result = nil
|
79
|
+
begin
|
80
|
+
print "\nBeginning.\n"
|
81
|
+
result = admin.reassign!(assignments)
|
82
|
+
print "Started.\n"
|
83
|
+
rescue Admin::ExecutionFailedError
|
84
|
+
print result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Kafkat
|
2
|
+
module Command
|
3
|
+
class ResignForce < Base
|
4
|
+
register_as 'resign-rewrite'
|
5
|
+
|
6
|
+
usage 'resign-rewrite <broker id>',
|
7
|
+
'Forcibly rewrite leaderships to exclude a broker.'
|
8
|
+
|
9
|
+
usage 'resign-rewrite <broker id> --force',
|
10
|
+
'Same as above but proceed if there are no available ISRs.'
|
11
|
+
|
12
|
+
def run
|
13
|
+
broker_id = ARGV[0] && ARGV.shift.to_i
|
14
|
+
if broker_id.nil?
|
15
|
+
puts "You must specify a broker ID."
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
opts = Trollop.options do
|
20
|
+
opt :force, "force"
|
21
|
+
end
|
22
|
+
|
23
|
+
print "This operation rewrites leaderships in ZK to exclude broker '#{broker_id}'.\n"
|
24
|
+
print "WARNING: This is a last resort. Try the 'shutdown' command first!\n\n".red
|
25
|
+
|
26
|
+
return unless agree("Proceed (y/n)?")
|
27
|
+
|
28
|
+
brokers = zookeeper.get_brokers
|
29
|
+
topics = zookeeper.get_topics
|
30
|
+
force = opts[:force]
|
31
|
+
|
32
|
+
ops = {}
|
33
|
+
topics.each do |_, t|
|
34
|
+
t.partitions.each do |p|
|
35
|
+
next if p.leader != broker_id
|
36
|
+
|
37
|
+
alternates = p.isr.reject { |i| i == broker_id }
|
38
|
+
new_leader_id = alternates.sample
|
39
|
+
|
40
|
+
if !new_leader_id && !force
|
41
|
+
print "Partition #{t.name}-#{p.id} has no other ISRs!\n"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
|
45
|
+
new_leader_id ||= -1
|
46
|
+
ops[p] = new_leader_id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
print "\n"
|
51
|
+
print "Summary of the new assignments:\n\n"
|
52
|
+
|
53
|
+
print "Partition\tLeader\n"
|
54
|
+
ops.each do |p, lid|
|
55
|
+
print justify("#{p.topic_name}-#{p.id}")
|
56
|
+
print justify(lid.to_s)
|
57
|
+
print "\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
print "\nStarting.\n"
|
62
|
+
ops.each do |p, lid|
|
63
|
+
retryable(tries: 3, on: Interface::Zookeeper::WriteConflictError) do
|
64
|
+
zookeeper.write_leader(p, lid)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue Interface::Zookeeper::WriteConflictError => e
|
68
|
+
print "Failed to update leaderships in ZK. Try re-running.\n\n"
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
print "Done.\n"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Kafkat
|
2
|
+
module Command
|
3
|
+
class Resign < Base
|
4
|
+
register_as 'shutdown'
|
5
|
+
|
6
|
+
usage 'shutdown <broker id>',
|
7
|
+
'Gracefully remove leaderships from a broker (requires JMX).'
|
8
|
+
|
9
|
+
def run
|
10
|
+
broker_id = ARGV[0] && ARGV.shift.to_i
|
11
|
+
if broker_id.nil?
|
12
|
+
puts "You must specify a broker ID."
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
print "This operation gracefully removes leaderships from broker '#{broker_id}'.\n"
|
17
|
+
return unless agree("Proceed (y/n)?")
|
18
|
+
|
19
|
+
result = nil
|
20
|
+
begin
|
21
|
+
print "\nBeginning shutdown.\n"
|
22
|
+
result = admin.shutdown!(broker_id)
|
23
|
+
print "Started.\n"
|
24
|
+
rescue Admin::ExecutionFailedError
|
25
|
+
print result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Kafkat
|
2
|
+
module Command
|
3
|
+
class NotFoundError < StandardError; end
|
4
|
+
|
5
|
+
def self.all
|
6
|
+
@all ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.get(name)
|
10
|
+
klass = all[name.downcase]
|
11
|
+
raise NotFoundError if !klass
|
12
|
+
klass
|
13
|
+
end
|
14
|
+
|
15
|
+
class Base
|
16
|
+
include Formatting
|
17
|
+
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_reader :command_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.register_as(name)
|
25
|
+
@command_name = name
|
26
|
+
Command.all[name] = self
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.usages
|
30
|
+
@usages ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.usage(format, description)
|
34
|
+
usages << [format, description]
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(config)
|
38
|
+
@config = config
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def admin
|
46
|
+
@admin ||= begin
|
47
|
+
Interface::Admin.new(config)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def zookeeper
|
52
|
+
@zookeeper ||= begin
|
53
|
+
Interface::Zookeeper.new(config)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def kafka_logs
|
58
|
+
@kafka_logs ||= begin
|
59
|
+
Interface::KafkaLogs.new(config)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Require all of the commands.
|
67
|
+
command_glob = File.expand_path("../command/*.rb", __FILE__)
|
68
|
+
Dir[command_glob].each { |f| require f }
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Kafkat
|
2
|
+
class Config
|
3
|
+
CONFIG_PATHS = [
|
4
|
+
'~/.kafkatcfg',
|
5
|
+
'/etc/kafkatcfg'
|
6
|
+
]
|
7
|
+
|
8
|
+
class NotFoundError < StandardError; end
|
9
|
+
class ParseError < StandardError; end
|
10
|
+
|
11
|
+
attr_reader :kafka_path
|
12
|
+
attr_reader :log_path
|
13
|
+
attr_reader :zk_path
|
14
|
+
|
15
|
+
def self.load!
|
16
|
+
string = nil
|
17
|
+
e = nil
|
18
|
+
|
19
|
+
CONFIG_PATHS.each do |rel_path|
|
20
|
+
begin
|
21
|
+
path = File.expand_path(rel_path)
|
22
|
+
string = File.read(path)
|
23
|
+
break
|
24
|
+
rescue => e
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
raise e if e && string.nil?
|
29
|
+
|
30
|
+
json = JSON.parse(string)
|
31
|
+
self.new(json)
|
32
|
+
|
33
|
+
rescue Errno::ENOENT
|
34
|
+
raise NotFoundError
|
35
|
+
rescue JSON::JSONError
|
36
|
+
raise ParseError
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(json)
|
40
|
+
@kafka_path = json['kafka_path']
|
41
|
+
@log_path = json['log_path']
|
42
|
+
@zk_path = json['zk_path']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Kafkat
|
4
|
+
module Interface
|
5
|
+
class Admin
|
6
|
+
class ExecutionFailedError < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :kafka_path
|
9
|
+
attr_reader :zk_path
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@kafka_path = config.kafka_path
|
13
|
+
@zk_path = config.zk_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def elect_leaders!(partitions)
|
17
|
+
file = Tempfile.new('kafkat-partitions.json')
|
18
|
+
|
19
|
+
json_partitions = []
|
20
|
+
partitions.each do |p|
|
21
|
+
json_partitions << {
|
22
|
+
'topic' => p.topic_name,
|
23
|
+
'partition' => p.id
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
json = {'partitions' => json_partitions}
|
28
|
+
file.write(JSON.dump(json))
|
29
|
+
file.close
|
30
|
+
|
31
|
+
run_tool(
|
32
|
+
'kafka-preferred-replica-election',
|
33
|
+
'--path-to-json-file', file.path
|
34
|
+
)
|
35
|
+
ensure
|
36
|
+
file.unlink
|
37
|
+
end
|
38
|
+
|
39
|
+
def reassign!(assignments)
|
40
|
+
file = Tempfile.new('kafkat-partitions.json')
|
41
|
+
|
42
|
+
json_partitions = []
|
43
|
+
assignments.each do |a|
|
44
|
+
json_partitions << {
|
45
|
+
'topic' => a.topic_name,
|
46
|
+
'partition' => a.partition_id,
|
47
|
+
'replicas' => a.replicas
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
json = {
|
52
|
+
'partitions' => json_partitions,
|
53
|
+
'version' => 1
|
54
|
+
}
|
55
|
+
|
56
|
+
file.write(JSON.dump(json))
|
57
|
+
file.close
|
58
|
+
|
59
|
+
run_tool(
|
60
|
+
'kafka-reassign-partitions',
|
61
|
+
'--execute',
|
62
|
+
'--reassignment-json-file', file.path
|
63
|
+
)
|
64
|
+
ensure
|
65
|
+
file.unlink
|
66
|
+
end
|
67
|
+
|
68
|
+
def shutdown!(broker_id, options={})
|
69
|
+
args = ['--broker', broker_id]
|
70
|
+
args += ['--num.retries', options[:retries]] if options[:retries]
|
71
|
+
args += ['--retry.interval.ms', option[:interval]] if options[:interval]
|
72
|
+
|
73
|
+
run_tool(
|
74
|
+
'kafka-run-class',
|
75
|
+
'kafka.admin.ShutdownBroker',
|
76
|
+
*args
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_tool(name, *args)
|
81
|
+
path = File.join(kafka_path, "bin/#{name}.sh")
|
82
|
+
args += ['--zookeeper', "\"#{zk_path}\""]
|
83
|
+
args_string = args.join(' ')
|
84
|
+
result = `#{path} #{args_string}`
|
85
|
+
raise ExecutionFailedError if $?.to_i > 0
|
86
|
+
result
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Kafkat
|
2
|
+
module Interface
|
3
|
+
class KafkaLogs
|
4
|
+
UNTRUNCATED_SIZE = 10 * 1024 * 1024 # 1MB
|
5
|
+
|
6
|
+
class NoLogsError < StandardError; end
|
7
|
+
class KafkaRunningError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :log_path
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@log_path = config.log_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def clean_indexes!
|
16
|
+
check_exists
|
17
|
+
|
18
|
+
to_remove = []
|
19
|
+
lock_for_write do
|
20
|
+
index_glob = File.join(log_path, '**/*.index')
|
21
|
+
Dir[index_glob].each do |index_path|
|
22
|
+
size = File.size(index_path)
|
23
|
+
to_remove << index_path if size == UNTRUNCATED_SIZE
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
to_remove.each do |path|
|
28
|
+
print "Removing #{path}.\n"
|
29
|
+
File.unlink(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
to_remove.size
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def check_exists
|
38
|
+
raise NoLogsError unless File.exists?(log_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def lock_for_write
|
42
|
+
File.open(lockfile_path, File::CREAT) do |lockfile|
|
43
|
+
locked = lockfile.flock(File::LOCK_EX | File::LOCK_NB)
|
44
|
+
raise KafkaRunningError unless locked
|
45
|
+
yield
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def lockfile_path
|
50
|
+
File.join(log_path, '.lock')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Kafkat
|
4
|
+
module Interface
|
5
|
+
class Zookeeper
|
6
|
+
class NotFoundError < StandardError; end
|
7
|
+
class WriteConflictError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :zk_path
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@zk_path = config.zk_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_brokers(ids=nil)
|
16
|
+
brokers = {}
|
17
|
+
ids ||= zk.children(brokers_path)
|
18
|
+
|
19
|
+
threads = ids.map do |id|
|
20
|
+
id = id.to_i
|
21
|
+
Thread.new do
|
22
|
+
begin
|
23
|
+
brokers[id] = get_broker(id)
|
24
|
+
rescue
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
threads.map(&:join)
|
29
|
+
|
30
|
+
brokers
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_broker(id)
|
34
|
+
path = broker_path(id)
|
35
|
+
string = zk.get(path).first
|
36
|
+
json = JSON.parse(string)
|
37
|
+
host, port = json['host'], json['port']
|
38
|
+
Broker.new(id, host, port)
|
39
|
+
rescue ZK::Exceptions::NoNode
|
40
|
+
raise NotFoundError
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_topics(names=nil)
|
44
|
+
topics = {}
|
45
|
+
names ||= zk.children(topics_path)
|
46
|
+
|
47
|
+
threads = names.map do |name|
|
48
|
+
Thread.new do
|
49
|
+
begin
|
50
|
+
topics[name] = get_topic(name)
|
51
|
+
rescue => e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
threads.map(&:join)
|
56
|
+
|
57
|
+
topics
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_topic(name)
|
61
|
+
path1 = topic_path(name)
|
62
|
+
topic_string = zk.get(path1).first
|
63
|
+
topic_json = JSON.parse(topic_string)
|
64
|
+
|
65
|
+
partitions = []
|
66
|
+
path2 = topic_partitions_path(name)
|
67
|
+
|
68
|
+
threads = zk.children(path2).map do |id|
|
69
|
+
id = id.to_i
|
70
|
+
Thread.new do
|
71
|
+
path3 = topic_partition_state_path(name, id)
|
72
|
+
partition_string = zk.get(path3).first
|
73
|
+
partition_json = JSON.parse(partition_string)
|
74
|
+
|
75
|
+
replicas = topic_json['partitions'][id.to_s]
|
76
|
+
leader = partition_json['leader']
|
77
|
+
isr = partition_json['isr']
|
78
|
+
|
79
|
+
partitions << Partition.new(name, id, replicas, leader, isr)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
threads.map(&:join)
|
83
|
+
|
84
|
+
partitions.sort_by!(&:id)
|
85
|
+
Topic.new(name, partitions)
|
86
|
+
rescue ZK::Exceptions::NoNode
|
87
|
+
raise NotFoundError
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_controller
|
91
|
+
string = zk.get(controller_path).first
|
92
|
+
controller_json = JSON.parse(string)
|
93
|
+
controller_id = controller_json['brokerid']
|
94
|
+
get_broker(controller_id)
|
95
|
+
rescue ZK::Exceptions::NoNode
|
96
|
+
raise NotFoundError
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_leader(partition, broker_id)
|
100
|
+
path = topic_partition_state_path(partition.topic_name, partition.id)
|
101
|
+
string, stat = zk.get(path)
|
102
|
+
|
103
|
+
partition_json = JSON.parse(string)
|
104
|
+
partition_json['leader'] = broker_id
|
105
|
+
new_string = JSON.dump(partition_json)
|
106
|
+
|
107
|
+
unless zk.set(path, new_string, version: stat.version)
|
108
|
+
raise ChangedDuringUpdateError
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def zk
|
115
|
+
@zk ||= ZK.new(zk_path)
|
116
|
+
end
|
117
|
+
|
118
|
+
def brokers_path
|
119
|
+
'/brokers/ids'
|
120
|
+
end
|
121
|
+
|
122
|
+
def broker_path(id)
|
123
|
+
"/brokers/ids/#{id}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def topics_path
|
127
|
+
'/brokers/topics'
|
128
|
+
end
|
129
|
+
|
130
|
+
def topic_path(name)
|
131
|
+
"/brokers/topics/#{name}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def topic_partitions_path(name)
|
135
|
+
"/brokers/topics/#{name}/partitions"
|
136
|
+
end
|
137
|
+
|
138
|
+
def topic_partition_state_path(name, id)
|
139
|
+
"/brokers/topics/#{name}/partitions/#{id}/state"
|
140
|
+
end
|
141
|
+
|
142
|
+
def controller_path
|
143
|
+
"/controller"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Kafkat
|
2
|
+
module Formatting
|
3
|
+
def justify(field, width=2)
|
4
|
+
field = field.to_s
|
5
|
+
count = [width - (field.length / 8), 0].max
|
6
|
+
field + "\t" * count
|
7
|
+
end
|
8
|
+
|
9
|
+
def print_broker(broker)
|
10
|
+
print justify(broker.id)
|
11
|
+
print justify("#{broker.host}:#{broker.port}")
|
12
|
+
print "\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
def print_broker_header
|
16
|
+
print justify('Broker')
|
17
|
+
print justify('Socket')
|
18
|
+
print "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
def print_topic(topic)
|
22
|
+
print justify(topic.name)
|
23
|
+
print "\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
def print_topic_header
|
27
|
+
print justify('Topic')
|
28
|
+
print "\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_partition(partition)
|
32
|
+
print justify(partition.topic_name)
|
33
|
+
print justify(partition.id)
|
34
|
+
print justify(partition.leader || 'none')
|
35
|
+
print justify(partition.replicas.inspect, 7)
|
36
|
+
print justify(partition.isr.inspect, 7)
|
37
|
+
print "\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def print_partition_header
|
41
|
+
print justify('Topic')
|
42
|
+
print justify('Partition')
|
43
|
+
print justify('Leader')
|
44
|
+
print justify('Replicas', 7)
|
45
|
+
print justify('ISRs', 7)
|
46
|
+
print "\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def print_assignment(assignment)
|
50
|
+
print justify(assignment.topic_name)
|
51
|
+
print justify(assignment.partition_id)
|
52
|
+
print justify(assignment.replicas.inspect)
|
53
|
+
print "\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
def print_assignment_header
|
57
|
+
print justify('Topic')
|
58
|
+
print justify('Partition')
|
59
|
+
print justify('Replicas')
|
60
|
+
print "\n"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'kafkat/utility/formatting'
|