ktl 1.0.0-java
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 +7 -0
- data/LICENSE.txt +12 -0
- data/README.md +106 -0
- data/bin/ktl +8 -0
- data/lib/ext/kafka.rb +145 -0
- data/lib/ext/thor.rb +14 -0
- data/lib/ktl.rb +51 -0
- data/lib/ktl/cli.rb +11 -0
- data/lib/ktl/cluster.rb +108 -0
- data/lib/ktl/cluster_stats_task.rb +41 -0
- data/lib/ktl/command.rb +33 -0
- data/lib/ktl/decommission_plan.rb +68 -0
- data/lib/ktl/migration_plan.rb +27 -0
- data/lib/ktl/reassigner.rb +133 -0
- data/lib/ktl/reassignment_progress.rb +57 -0
- data/lib/ktl/reassignment_task.rb +46 -0
- data/lib/ktl/shell_formatter.rb +32 -0
- data/lib/ktl/shuffle_plan.rb +145 -0
- data/lib/ktl/topic.rb +123 -0
- data/lib/ktl/version.rb +5 -0
- data/lib/ktl/zookeeper_client.rb +111 -0
- metadata +101 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Ktl
|
4
|
+
class ShufflePlan
|
5
|
+
def initialize(zk_client, options = {})
|
6
|
+
@zk_client = zk_client
|
7
|
+
@options = options
|
8
|
+
@logger = options[:logger] || NullLogger.new
|
9
|
+
@log_plan = !!options[:log_plan]
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate
|
13
|
+
topics = @zk_client.all_topics
|
14
|
+
if (filter = @options[:filter])
|
15
|
+
topics = topics.filter { |t| !!t.match(filter) }
|
16
|
+
end
|
17
|
+
topics_partitions = ScalaEnumerable.new(@zk_client.partitions_for_topics(topics))
|
18
|
+
topics_partitions = topics_partitions.sort_by(&:first)
|
19
|
+
replica_assignments = @zk_client.replica_assignment_for_topics(topics)
|
20
|
+
brokers = select_brokers
|
21
|
+
reassignment_plan = Scala::Collection::Map.empty
|
22
|
+
topics_partitions.each do |tp|
|
23
|
+
topic, partitions = tp.elements
|
24
|
+
nr_replicas = @options[:replication_factor] || replica_assignments.apply(Kafka::TopicAndPartition.new(topic, 0)).size
|
25
|
+
assignment = assign_replicas_to_brokers(topic, brokers, partitions.size, nr_replicas)
|
26
|
+
assignment.each do |pr|
|
27
|
+
partition, replicas = pr.elements
|
28
|
+
topic_partition = Kafka::TopicAndPartition.new(topic, partition)
|
29
|
+
current_assignment = replica_assignments.apply(topic_partition)
|
30
|
+
unless current_assignment == replicas
|
31
|
+
@logger.info "Moving #{topic_partition.topic},#{topic_partition.partition} from #{current_assignment} to #{replicas}" if @log_plan
|
32
|
+
reassignment_plan += Scala::Tuple.new(topic_partition, replicas)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
reassignment_plan
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_for_new_topic(topic, partition_count)
|
40
|
+
brokers = select_brokers
|
41
|
+
nr_replicas = @options[:replication_factor] || 1
|
42
|
+
assignment = assign_replicas_to_brokers(topic, brokers, partition_count, nr_replicas)
|
43
|
+
assignment.map do |pr|
|
44
|
+
partition, replicas = pr.elements
|
45
|
+
Scala::Collection::JavaConversions.as_java_iterable(replicas).to_a
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def select_brokers
|
52
|
+
brokers = @options[:brokers] ? Array(@options[:brokers]).map(&:to_i) : ScalaEnumerable.new(@zk_client.broker_ids).to_a
|
53
|
+
brokers -= Array(@options[:blacklist]).map(&:to_i) if @options[:blacklist]
|
54
|
+
brokers
|
55
|
+
end
|
56
|
+
|
57
|
+
def assign_replicas_to_brokers(topic, brokers, partition_count, replica_count)
|
58
|
+
broker_metadatas = brokers.map { |x| Kafka::Admin::BrokerMetadata.new(x.to_java(:int), Scala::Option[nil]) }
|
59
|
+
broker_metadatas = Scala::Collection::JavaConversions.as_scala_iterable(broker_metadatas).to_seq
|
60
|
+
Kafka::Admin.assign_replicas_to_brokers(broker_metadatas, partition_count, replica_count)
|
61
|
+
rescue Kafka::Admin::AdminOperationException => e
|
62
|
+
raise ArgumentError, sprintf('%s (%s)', e.message, e.class.name), e.backtrace
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class RendezvousShufflePlan < ShufflePlan
|
67
|
+
def assign_replicas_to_brokers(topic, brokers, partition_count, replica_count)
|
68
|
+
if replica_count > brokers.size
|
69
|
+
raise ArgumentError, sprintf('replication factor: %i larger than available brokers: %i', replica_count, brokers.size)
|
70
|
+
end
|
71
|
+
result = []
|
72
|
+
partition_count.times do |partition|
|
73
|
+
sorted = brokers.sort_by do |broker|
|
74
|
+
key = [partition, topic, broker].pack('l<a*l<')
|
75
|
+
Java::OrgJrubyUtil::MurmurHash.hash32(key.to_java_bytes, 0, key.bytesize, SEED)
|
76
|
+
end
|
77
|
+
selected = sorted.take(replica_count)
|
78
|
+
result.push(Scala::Tuple.new(partition, Scala::Collection::JavaConversions.as_scala_iterable(selected).to_list))
|
79
|
+
end
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
SEED = 1683520333
|
86
|
+
end
|
87
|
+
|
88
|
+
class RackAwareShufflePlan < RendezvousShufflePlan
|
89
|
+
def initialize(*args)
|
90
|
+
super
|
91
|
+
@rack_mappings = {}
|
92
|
+
end
|
93
|
+
|
94
|
+
def assign_replicas_to_brokers(topic, brokers, partition_count, replica_count)
|
95
|
+
if replica_count > brokers.size
|
96
|
+
raise ArgumentError, sprintf('replication factor: %i larger than available brokers: %i', replica_count, brokers.size)
|
97
|
+
end
|
98
|
+
result = []
|
99
|
+
racks = brokers.each_with_object({}) do |broker, acc|
|
100
|
+
rack = rack_for(broker)
|
101
|
+
acc[rack] ||= []
|
102
|
+
acc[rack] << broker
|
103
|
+
end
|
104
|
+
partition_count.times do |partition|
|
105
|
+
first_sorted = racks.flat_map do |rack, rack_brokers|
|
106
|
+
hashed_brokers = rack_brokers.map do |broker|
|
107
|
+
key = [partition, topic, broker].pack('l<a*l<')
|
108
|
+
{id: broker, hash: Java::OrgJrubyUtil::MurmurHash.hash32(key.to_java_bytes, 0, key.bytesize, SEED)}
|
109
|
+
end.sort_by do |broker|
|
110
|
+
broker[:hash]
|
111
|
+
end
|
112
|
+
hashed_brokers.each_with_index do |broker, index|
|
113
|
+
broker[:index] = index
|
114
|
+
end
|
115
|
+
end
|
116
|
+
sorted = first_sorted.sort_by do |broker|
|
117
|
+
[broker[:index], broker[:hash], broker[:id]]
|
118
|
+
end
|
119
|
+
selected = sorted.take(replica_count).map {|broker| broker[:id]}
|
120
|
+
result.push(Scala::Tuple.new(partition, Scala::Collection::JavaConversions.as_scala_iterable(selected).to_list))
|
121
|
+
end
|
122
|
+
result
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def rack_for(broker_id)
|
128
|
+
unless @rack_mappings[broker_id]
|
129
|
+
broker_metadata = Kafka::Admin.get_broker_metadatas(@zk_client, [broker_id]).first
|
130
|
+
rack = broker_metadata.rack
|
131
|
+
unless rack.isDefined
|
132
|
+
raise "Broker #{broker_metadata.id} is missing rack information, unable to create rack aware shuffle plan."
|
133
|
+
end
|
134
|
+
@rack_mappings[broker_id] = rack.get
|
135
|
+
end
|
136
|
+
@rack_mappings[broker_id]
|
137
|
+
rescue Java::KafkaAdmin::AdminOperationException => e
|
138
|
+
if e.message.match '--disable-rack-aware'
|
139
|
+
raise "Not all brokers have rack information. Unable to create rack aware shuffle plan."
|
140
|
+
else
|
141
|
+
raise e
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/ktl/topic.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Ktl
|
4
|
+
class Topic < Command
|
5
|
+
desc 'list', 'List current topics'
|
6
|
+
option :zookeeper, aliases: %w[-z], required: true, desc: 'ZooKeeper URI'
|
7
|
+
def list
|
8
|
+
with_zk_client do |zk_client|
|
9
|
+
topic_options = Kafka::Admin.to_topic_options(options.merge(list: nil))
|
10
|
+
Kafka::Admin::TopicCommand.list_topics(zk_client.raw_client, topic_options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'describe', 'Describe (optionally filtered) topics'
|
15
|
+
option :unavailable, aliases: '-u', desc: 'Describe unavailable partitions for topic(s)'
|
16
|
+
option :with_overrides, aliases: '-w', desc: 'Describe topics with config. overrides'
|
17
|
+
option :under_replicated, aliases: '-r', desc: 'Describe under-replicated partitions for topic(s)'
|
18
|
+
option :zookeeper, aliases: %w[-z], required: true, desc: 'ZooKeeper URI'
|
19
|
+
def describe(regexp=nil)
|
20
|
+
with_zk_client do |zk_client|
|
21
|
+
opts = {describe: nil}
|
22
|
+
opts[:topic] = regexp if regexp
|
23
|
+
opts[:topics_with_overrides] = nil if options.with_overrides?
|
24
|
+
opts[:unavailable_partitions] = nil if options.unavailable?
|
25
|
+
opts[:under_replicated_partitions] = nil if options.under_replicated?
|
26
|
+
topic_options = Kafka::Admin.to_topic_options(opts)
|
27
|
+
Kafka::Admin::TopicCommand.describe_topic(zk_client.raw_client, topic_options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'create NAMES..', 'Create one or more new topics'
|
32
|
+
option :partitions, aliases: %w[-p], default: '1', desc: 'Partitions for new topic(s)'
|
33
|
+
option :replication_factor, aliases: %w[-r], default: '1', desc: 'Replication factor for new topic(s)'
|
34
|
+
option :replica_assignment, aliases: %w[-a], desc: 'Manual replica assignment'
|
35
|
+
option :disable_rack_aware, desc: 'Disable rack awareness'
|
36
|
+
option :rack_aware_allocation, desc: 'Create partitions with Rack aware + Rendezvous-hashing based shuffle'
|
37
|
+
option :rendezvous_allocation, desc: 'Create partitions with Rendezvous-hashing based shuffle'
|
38
|
+
option :config, aliases: %w[-c], desc: 'Key-value pairs of configuration options', type: :hash, default: {}
|
39
|
+
option :zookeeper, aliases: %w[-z], required: true, desc: 'ZooKeeper URI'
|
40
|
+
def create(*names)
|
41
|
+
with_zk_client do |zk_client|
|
42
|
+
names.each do |name|
|
43
|
+
opts = options.merge(create: nil, topic: name)
|
44
|
+
if options.rack_aware_allocation || options.rendezvous_allocation
|
45
|
+
plan_factory = if options.rack_aware_allocation
|
46
|
+
RackAwareShufflePlan
|
47
|
+
else
|
48
|
+
RendezvousShufflePlan
|
49
|
+
end
|
50
|
+
|
51
|
+
plan = plan_factory.new(zk_client, replication_factor: options.replication_factor.to_i)
|
52
|
+
zk_utils = Kafka::Utils::ZkUtils.new(nil, nil, false)
|
53
|
+
opts.delete(:rack_aware_allocation)
|
54
|
+
opts.delete(:rendezvous_allocation)
|
55
|
+
plan = plan.generate_for_new_topic(name, options.partitions.to_i)
|
56
|
+
opts[:replica_assignment] = plan.map {|broker_list| broker_list.join(':')}.join(',')
|
57
|
+
end
|
58
|
+
topic_options = Kafka::Admin.to_topic_options(opts)
|
59
|
+
silence_scala do
|
60
|
+
Kafka::Admin::TopicCommand.create_topic(zk_client.raw_client, topic_options)
|
61
|
+
end
|
62
|
+
message = %(created topic "#{name}" with #{opts[:partitions]} partition(s))
|
63
|
+
message << %(, and replication factor #{opts[:replication_factor]})
|
64
|
+
message << %(, with replica assignment: #{opts[:replica_assignment]}) if opts[:replica_assignment]
|
65
|
+
message << %(, with config: #{opts[:config]}) unless opts[:config].empty?
|
66
|
+
logger.info(message)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
desc 'add-partitions NAMES..', 'Add partitions to one or more existing topics'
|
72
|
+
option :partitions, aliases: %w[-p], required: true, desc: 'New number of partitions'
|
73
|
+
option :zookeeper, aliases: %w[-z], required: true, desc: 'ZooKeeper URI'
|
74
|
+
def add_partitions(*names)
|
75
|
+
with_zk_client do |zk_client|
|
76
|
+
names.each do |name|
|
77
|
+
opts = options.merge(alter: nil, topic: name)
|
78
|
+
topic_options = Kafka::Admin.to_topic_options(opts)
|
79
|
+
logger.warn %(if "#{name}" uses keyed messages, the partition logic or ordering of the messages will be affected)
|
80
|
+
silence_scala do
|
81
|
+
Kafka::Admin::TopicCommand.alter_topic(zk_client.raw_client, topic_options)
|
82
|
+
end
|
83
|
+
logger.info %(increased partitions to #{options.partitions} for "#{name}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'delete REGEXP', 'Delete topics matching given regexp'
|
89
|
+
option :zookeeper, aliases: %w[-z], required: true, desc: 'ZooKeeper URI'
|
90
|
+
def delete(regexp)
|
91
|
+
with_zk_client do |zk_client|
|
92
|
+
topics = zk_client.all_topics
|
93
|
+
topics = topics.filter { |t| !!t.match(regexp) }
|
94
|
+
logger.info %(about to mark #{topics.size} topics for deletion)
|
95
|
+
topics.foreach do |topic|
|
96
|
+
Kafka::Utils.delete_topic(zk_client.raw_client, topic)
|
97
|
+
logger.debug %(successfully marked "#{topic}" for deletion)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc 'alter REGEXP', 'Alter topic configuration'
|
103
|
+
option :add, aliases: %w[-a], desc: 'Key-value pairs of config options to add', type: :hash, default: {}
|
104
|
+
option :remove, aliases: %w[-r], desc: 'Key-value pairs of config options to remove', type: :array, default: []
|
105
|
+
option :zookeeper, aliases: %w[-z], required: true, desc: 'ZooKeeper URI'
|
106
|
+
def alter(regexp)
|
107
|
+
with_zk_client do |zk_client|
|
108
|
+
opts = {zookeeper: options.zookeeper, topic: regexp}
|
109
|
+
opts[:config] = options.add.dup unless options.add.empty?
|
110
|
+
opts[:delete_config] = options.remove.dup unless options.remove.empty?
|
111
|
+
if opts[:config] || opts[:delete_config]
|
112
|
+
topic_options = Kafka::Admin.to_topic_options(opts)
|
113
|
+
silence_scala do
|
114
|
+
Kafka::Admin::TopicCommand.alter_topic(zk_client.raw_client, topic_options)
|
115
|
+
end
|
116
|
+
logger.info %(updated configuration for topics matching "#{regexp}")
|
117
|
+
else
|
118
|
+
raise ArgumentError, 'missing --add or --remove option'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/ktl/version.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Ktl
|
4
|
+
class ZookeeperClient
|
5
|
+
attr_reader :utils
|
6
|
+
|
7
|
+
def initialize(uri, options={})
|
8
|
+
@uri = uri
|
9
|
+
@threadpool = options[:threadpool] || JavaConcurrent::Executors.new_fixed_thread_pool(CONCURRENCY)
|
10
|
+
@utils = options[:utils] || Kafka::Utils::ZkUtils.apply(@uri, 5000, 5000, false)
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@submit = @threadpool.java_method(:submit, [java.lang.Class.for_name('java.util.concurrent.Callable')])
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
@threadpool.shutdown_now if @threadpool
|
20
|
+
@utils.close
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw_client
|
24
|
+
@utils
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_partitions
|
28
|
+
@utils.get_all_partitions
|
29
|
+
end
|
30
|
+
|
31
|
+
def all_topics
|
32
|
+
@utils.get_all_topics
|
33
|
+
end
|
34
|
+
|
35
|
+
def brokers
|
36
|
+
@utils.get_all_brokers_in_cluster
|
37
|
+
end
|
38
|
+
|
39
|
+
def broker_ids
|
40
|
+
@utils.get_sorted_broker_list
|
41
|
+
end
|
42
|
+
|
43
|
+
def leader_and_isr_for(partitions)
|
44
|
+
@utils.get_partition_leader_and_isr_for_topics(@utils.class.create_zk_client(@uri, 5_000, 5_000), partitions)
|
45
|
+
end
|
46
|
+
|
47
|
+
def partitions_for_topics(topics)
|
48
|
+
request(:get_partitions_for_topics, topics)
|
49
|
+
end
|
50
|
+
|
51
|
+
def replica_assignment_for_topics(topics)
|
52
|
+
request(:get_replica_assignment_for_topics, topics)
|
53
|
+
end
|
54
|
+
|
55
|
+
def partitions_being_reassigned
|
56
|
+
@utils.get_partitions_being_reassigned
|
57
|
+
end
|
58
|
+
|
59
|
+
def reassign_partitions(json)
|
60
|
+
@utils.create_persistent_path(@utils.class.reassign_partitions_path, json, no_acl)
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_znode(path, data='')
|
64
|
+
@utils.create_persistent_path(path, data, no_acl)
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_znode(path, options={})
|
68
|
+
if options[:recursive]
|
69
|
+
@utils.delete_path_recursive(path)
|
70
|
+
else
|
71
|
+
@utils.delete_path(path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_data(path)
|
76
|
+
@utils.read_data(path)
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_children(path)
|
80
|
+
@utils.get_children(path)
|
81
|
+
end
|
82
|
+
|
83
|
+
def exists?(path)
|
84
|
+
@utils.path_exists(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
CONCURRENCY = 8
|
90
|
+
|
91
|
+
def no_acl
|
92
|
+
Kafka::Utils::ZkUtils::DefaultAcls(false)
|
93
|
+
end
|
94
|
+
|
95
|
+
def request(method, input)
|
96
|
+
chunk_size = [(input.size.to_f / CONCURRENCY).round, 1].max
|
97
|
+
groups = ScalaEnumerable.new(input.grouped(chunk_size).to_seq)
|
98
|
+
futures = groups.map do |slice|
|
99
|
+
@submit.call { @utils.send(method, slice) }
|
100
|
+
end
|
101
|
+
merge(futures.map(&:get))
|
102
|
+
end
|
103
|
+
|
104
|
+
def merge(results)
|
105
|
+
result = Scala::Collection::Map.empty
|
106
|
+
results.reduce(result) do |acc, v|
|
107
|
+
acc.send('++', v)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ktl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: java
|
6
|
+
authors:
|
7
|
+
- Burt Platform Team
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - '='
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.10.0.1
|
19
|
+
name: kafka-jars
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.10.0.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
- - <
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '1.0'
|
36
|
+
name: thor
|
37
|
+
prerelease: false
|
38
|
+
type: :runtime
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- - <
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.0'
|
47
|
+
description: |-
|
48
|
+
ktl is a tool that attempts to make it easier
|
49
|
+
to manage Kafka clusers that host a lot of topics
|
50
|
+
email:
|
51
|
+
- david@burtcorp.com
|
52
|
+
executables:
|
53
|
+
- ktl
|
54
|
+
extensions: []
|
55
|
+
extra_rdoc_files: []
|
56
|
+
files:
|
57
|
+
- LICENSE.txt
|
58
|
+
- README.md
|
59
|
+
- bin/ktl
|
60
|
+
- lib/ext/kafka.rb
|
61
|
+
- lib/ext/thor.rb
|
62
|
+
- lib/ktl.rb
|
63
|
+
- lib/ktl/cli.rb
|
64
|
+
- lib/ktl/cluster.rb
|
65
|
+
- lib/ktl/cluster_stats_task.rb
|
66
|
+
- lib/ktl/command.rb
|
67
|
+
- lib/ktl/decommission_plan.rb
|
68
|
+
- lib/ktl/migration_plan.rb
|
69
|
+
- lib/ktl/reassigner.rb
|
70
|
+
- lib/ktl/reassignment_progress.rb
|
71
|
+
- lib/ktl/reassignment_task.rb
|
72
|
+
- lib/ktl/shell_formatter.rb
|
73
|
+
- lib/ktl/shuffle_plan.rb
|
74
|
+
- lib/ktl/topic.rb
|
75
|
+
- lib/ktl/version.rb
|
76
|
+
- lib/ktl/zookeeper_client.rb
|
77
|
+
homepage: http://github.com/burtcorp/ktl
|
78
|
+
licenses:
|
79
|
+
- BSD-3-Clause
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.4.5
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Management tool for Kafka clusters
|
101
|
+
test_files: []
|