gizzmo 0.11.0 → 0.11.1
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.
- data/Rakefile +11 -16
- data/VERSION +1 -1
- data/bin/setup_shards +173 -0
- data/lib/gizzard.rb +4 -0
- data/lib/gizzard/commands.rb +286 -152
- data/lib/gizzard/migrator.rb +192 -0
- data/lib/gizzard/nameserver.rb +206 -0
- data/lib/gizzard/shard_template.rb +252 -0
- data/lib/gizzard/thrift.rb +187 -135
- data/lib/gizzard/transformation.rb +230 -0
- data/lib/gizzard/transformation_op.rb +181 -0
- data/lib/gizzard/transformation_scheduler.rb +220 -0
- data/lib/gizzmo.rb +87 -20
- data/test/gizzmo_spec.rb +499 -0
- data/test/nameserver_spec.rb +139 -0
- data/test/scheduler_spec.rb +59 -0
- data/test/shard_template_spec.rb +103 -0
- data/test/spec.opts +7 -0
- data/test/spec_helper.rb +139 -0
- data/test/test_server/.gitignore +13 -0
- data/test/test_server/project/build.properties +8 -0
- data/test/test_server/project/build/Project.scala +13 -0
- data/test/test_server/project/plugins/Plugins.scala +6 -0
- data/test/test_server/src/main/scala/Main.scala +18 -0
- data/test/test_server/src/main/scala/TestServer.scala +247 -0
- data/test/test_server/src/main/thrift/TestServer.thrift +12 -0
- data/test/transformation_spec.rb +181 -0
- metadata +32 -5
- data/gizzmo.gemspec +0 -75
@@ -0,0 +1,181 @@
|
|
1
|
+
module Gizzard
|
2
|
+
module Transformation::Op
|
3
|
+
class BaseOp
|
4
|
+
def inverse?(other)
|
5
|
+
Transformation::OP_INVERSES[self.class] == other.class
|
6
|
+
end
|
7
|
+
|
8
|
+
def eql?(other)
|
9
|
+
self.class == other.class
|
10
|
+
end
|
11
|
+
|
12
|
+
alias == eql?
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
templates = (is_a?(LinkOp) ? [from, to] : [template]).map {|t| t.identifier }.join(" -> ")
|
16
|
+
name = Transformation::OP_NAMES[self.class]
|
17
|
+
"#{name}(#{templates})"
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(other)
|
21
|
+
Transformation::OP_PRIORITIES[self.class] <=> Transformation::OP_PRIORITIES[other.class]
|
22
|
+
end
|
23
|
+
|
24
|
+
def involved_shards(table_prefix, translations)
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class CopyShard < BaseOp
|
30
|
+
BUSY = 1
|
31
|
+
|
32
|
+
attr_reader :from, :to
|
33
|
+
alias template to
|
34
|
+
|
35
|
+
def initialize(from, to)
|
36
|
+
@from = from
|
37
|
+
@to = to
|
38
|
+
end
|
39
|
+
|
40
|
+
def expand(*args); { :copy => [self] } end
|
41
|
+
|
42
|
+
def involved_shards(table_prefix, translations)
|
43
|
+
[to.to_shard_id(table_prefix, translations)]
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
47
|
+
from_shard_id = from.to_shard_id(table_prefix, translations)
|
48
|
+
to_shard_id = to.to_shard_id(table_prefix, translations)
|
49
|
+
|
50
|
+
nameserver.mark_shard_busy(to_shard_id, BUSY)
|
51
|
+
nameserver.copy_shard(from_shard_id, to_shard_id)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class LinkOp < BaseOp
|
56
|
+
attr_reader :from, :to
|
57
|
+
alias template to
|
58
|
+
|
59
|
+
def initialize(from, to)
|
60
|
+
@from = from
|
61
|
+
@to = to
|
62
|
+
end
|
63
|
+
|
64
|
+
def inverse?(other)
|
65
|
+
super && self.from.link_eql?(other.from) && self.to.link_eql?(other.to)
|
66
|
+
end
|
67
|
+
|
68
|
+
def eql?(other)
|
69
|
+
super && self.from.link_eql?(other.from) && self.to.link_eql?(other.to)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class AddLink < LinkOp
|
74
|
+
def expand(copy_source, involved_in_copy, wrapper_type)
|
75
|
+
if involved_in_copy
|
76
|
+
wrapper = ShardTemplate.new(wrapper_type, to.host, to.weight, '', '', [to])
|
77
|
+
{ :prepare => [AddLink.new(from, wrapper)],
|
78
|
+
:cleanup => [self, RemoveLink.new(from, wrapper)] }
|
79
|
+
else
|
80
|
+
{ :prepare => [self] }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
85
|
+
from_shard_id = from.to_shard_id(table_prefix, translations)
|
86
|
+
to_shard_id = to.to_shard_id(table_prefix, translations)
|
87
|
+
|
88
|
+
nameserver.add_link(from_shard_id, to_shard_id, to.weight)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class RemoveLink < LinkOp
|
93
|
+
def expand(copy_source, involved_in_copy, wrapper_type)
|
94
|
+
{ (involved_in_copy ? :cleanup : :prepare) => [self] }
|
95
|
+
end
|
96
|
+
|
97
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
98
|
+
from_shard_id = from.to_shard_id(table_prefix, translations)
|
99
|
+
to_shard_id = to.to_shard_id(table_prefix, translations)
|
100
|
+
|
101
|
+
nameserver.remove_link(from_shard_id, to_shard_id)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ShardOp < BaseOp
|
106
|
+
attr_reader :template
|
107
|
+
|
108
|
+
def initialize(template)
|
109
|
+
@template = template
|
110
|
+
end
|
111
|
+
|
112
|
+
def inverse?(other)
|
113
|
+
super && self.template.shard_eql?(other.template)
|
114
|
+
end
|
115
|
+
|
116
|
+
def eql?(other)
|
117
|
+
super && self.template.shard_eql?(other.template)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class CreateShard < ShardOp
|
122
|
+
def expand(copy_source, involved_in_copy, wrapper_type)
|
123
|
+
if involved_in_copy
|
124
|
+
wrapper = ShardTemplate.new(wrapper_type, template.host, template.weight, '', '', [template])
|
125
|
+
{ :prepare => [self, CreateShard.new(wrapper), AddLink.new(wrapper, template)],
|
126
|
+
:cleanup => [RemoveLink.new(wrapper, template), DeleteShard.new(wrapper)],
|
127
|
+
:copy => [CopyShard.new(copy_source, template)] }
|
128
|
+
else
|
129
|
+
{ :prepare => [self] }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
134
|
+
nameserver.create_shard(template.to_shard_info(table_prefix, translations))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class DeleteShard < ShardOp
|
139
|
+
def expand(copy_source, involved_in_copy, wrapper_type)
|
140
|
+
{ (involved_in_copy ? :cleanup : :prepare) => [self] }
|
141
|
+
end
|
142
|
+
|
143
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
144
|
+
nameserver.delete_shard(template.to_shard_id(table_prefix, translations))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class SetForwarding < ShardOp
|
149
|
+
def expand(copy_source, involved_in_copy, wrapper_type)
|
150
|
+
if involved_in_copy
|
151
|
+
wrapper = ShardTemplate.new(wrapper_type, nil, 0, '', '', [to])
|
152
|
+
{ :prepare => [SetForwarding.new(template, wrapper)],
|
153
|
+
:cleanup => [self] }
|
154
|
+
else
|
155
|
+
{ :prepare => [self] }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
160
|
+
shard_id = template.to_shard_id(table_prefix, translations)
|
161
|
+
forwarding = Forwarding.new(table_id, base_id, shard_id)
|
162
|
+
nameserver.set_forwarding(forwarding)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# XXX: A no-op, but needed for setup/teardown symmetry
|
168
|
+
|
169
|
+
class RemoveForwarding < ShardOp
|
170
|
+
def expand(copy_source, involved_in_copy, wrapper_type)
|
171
|
+
{ (involved_in_copy ? :cleanup : :prepare) => [self] }
|
172
|
+
end
|
173
|
+
|
174
|
+
def apply(nameserver, table_id, base_id, table_prefix, translations)
|
175
|
+
# shard_id = template.to_shard_id(table_prefix, translations)
|
176
|
+
# forwarding = Forwarding.new(table_id, base_id, shard_id)
|
177
|
+
# nameserver.remove_forwarding(forwarding)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
module Gizzard
|
2
|
+
def self.schedule!(*args)
|
3
|
+
Transformation::Scheduler.new(*args).apply!
|
4
|
+
end
|
5
|
+
|
6
|
+
class Transformation::Scheduler
|
7
|
+
|
8
|
+
attr_reader :nameserver, :transformations
|
9
|
+
attr_reader :max_copies, :copies_per_host
|
10
|
+
|
11
|
+
DEFAULT_OPTIONS = {
|
12
|
+
:max_copies => 30,
|
13
|
+
:copies_per_host => 8,
|
14
|
+
:poll_interval => 10
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(nameserver, base_name, transformations, options = {})
|
18
|
+
options = DEFAULT_OPTIONS.merge(options)
|
19
|
+
@nameserver = nameserver
|
20
|
+
@transformations = transformations
|
21
|
+
@max_copies = options[:max_copies]
|
22
|
+
@copies_per_host = options[:copies_per_host]
|
23
|
+
@poll_interval = options[:poll_interval]
|
24
|
+
@be_quiet = options[:quiet]
|
25
|
+
@dont_show_progress = options[:no_progress] || @be_quiet
|
26
|
+
|
27
|
+
@jobs_in_progress = []
|
28
|
+
@jobs_finished = []
|
29
|
+
|
30
|
+
@jobs_pending = transformations.map do |transformation, forwardings_to_shards|
|
31
|
+
transformation.bind(base_name, forwardings_to_shards)
|
32
|
+
end.flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
# to schedule a job:
|
36
|
+
# 1. pull a job that does not involve a disqualified host.
|
37
|
+
# 2. run prepare ops
|
38
|
+
# 3. reload app servers
|
39
|
+
# 4. schedule copy
|
40
|
+
# 5. put in jobs_in_progress
|
41
|
+
|
42
|
+
# on job completion:
|
43
|
+
# 1. run cleanup ops
|
44
|
+
# 2. remove from jobs_in_progress
|
45
|
+
# 3. put in jos_finished
|
46
|
+
# 4. schedule a new job or reload app servers.
|
47
|
+
|
48
|
+
def apply!
|
49
|
+
@start_time = Time.now
|
50
|
+
|
51
|
+
loop do
|
52
|
+
reload_busy_shards
|
53
|
+
|
54
|
+
cleanup_jobs
|
55
|
+
schedule_jobs(max_copies - busy_shards.length)
|
56
|
+
|
57
|
+
break if @jobs_pending.empty? && @jobs_in_progress.empty?
|
58
|
+
|
59
|
+
unless nameserver.dryrun?
|
60
|
+
if @dont_show_progress
|
61
|
+
sleep(@poll_interval)
|
62
|
+
else
|
63
|
+
sleep_with_progress(@poll_interval)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
nameserver.reload_config
|
69
|
+
|
70
|
+
log "#{@jobs_finished.length} transformation#{'s' if @jobs_finished.length > 1} applied. Total time elapsed: #{time_elapsed}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def schedule_jobs(num_to_schedule)
|
74
|
+
to_be_busy_hosts = []
|
75
|
+
|
76
|
+
jobs = (1..num_to_schedule).map do
|
77
|
+
job = @jobs_pending.find do |j|
|
78
|
+
(busy_hosts(to_be_busy_hosts) & j.involved_hosts).empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
if job
|
82
|
+
to_be_busy_hosts.concat job.involved_hosts
|
83
|
+
@jobs_pending.delete(job)
|
84
|
+
end
|
85
|
+
|
86
|
+
job
|
87
|
+
end.compact.sort_by {|t| t.forwarding }
|
88
|
+
|
89
|
+
unless jobs.empty?
|
90
|
+
log "STARTING:"
|
91
|
+
jobs.each do |j|
|
92
|
+
log " #{j.inspect}"
|
93
|
+
j.prepare!(nameserver)
|
94
|
+
end
|
95
|
+
|
96
|
+
nameserver.reload_config
|
97
|
+
|
98
|
+
copy_jobs = jobs.select {|j| j.copy_required? }
|
99
|
+
|
100
|
+
unless copy_jobs.empty?
|
101
|
+
log "COPIES:"
|
102
|
+
copy_jobs.each do |j|
|
103
|
+
j.copy_descs.each {|d| log " #{d}" }
|
104
|
+
j.copy!(nameserver)
|
105
|
+
end
|
106
|
+
|
107
|
+
reload_busy_shards
|
108
|
+
end
|
109
|
+
|
110
|
+
@jobs_in_progress.concat(jobs)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def cleanup_jobs
|
115
|
+
jobs = jobs_completed
|
116
|
+
|
117
|
+
unless jobs.empty?
|
118
|
+
@jobs_in_progress -= jobs
|
119
|
+
|
120
|
+
log "FINISHING:"
|
121
|
+
jobs.each do |j|
|
122
|
+
log " #{j.inspect}"
|
123
|
+
j.cleanup!(nameserver)
|
124
|
+
end
|
125
|
+
|
126
|
+
@jobs_finished.concat(jobs)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def jobs_completed
|
131
|
+
@jobs_in_progress.select {|j| (busy_shards & j.involved_shards).empty? }
|
132
|
+
end
|
133
|
+
|
134
|
+
def reload_busy_shards
|
135
|
+
@busy_shards = nil
|
136
|
+
busy_shards
|
137
|
+
end
|
138
|
+
|
139
|
+
def busy_shards
|
140
|
+
@busy_shards ||=
|
141
|
+
if nameserver.dryrun?
|
142
|
+
[]
|
143
|
+
else
|
144
|
+
nameserver.get_busy_shards.map {|s| s.id }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def busy_hosts(extra_hosts = [])
|
149
|
+
hosts = extra_hosts + busy_shards.map {|s| s.hostname }
|
150
|
+
|
151
|
+
copies_count_map = hosts.inject({}) do |h, host|
|
152
|
+
h.update(host => 1) {|_,a,b| a + b }
|
153
|
+
end
|
154
|
+
|
155
|
+
copies_count_map.select {|_, count| count >= @copies_per_host }.map {|(host, _)| host }
|
156
|
+
end
|
157
|
+
|
158
|
+
def sleep_with_progress(interval)
|
159
|
+
start = Time.now
|
160
|
+
while (Time.now - start) < interval
|
161
|
+
put_copy_progress
|
162
|
+
sleep 0.2
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def clear_progress_string
|
167
|
+
if @progress_string
|
168
|
+
print "\r" + (" " * (@progress_string.length + 10)) + "\r"
|
169
|
+
@progress_string = nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def log(*args)
|
174
|
+
unless @be_quiet
|
175
|
+
clear_progress_string
|
176
|
+
puts *args
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def put_copy_progress
|
181
|
+
@i ||= 0
|
182
|
+
@i += 1
|
183
|
+
|
184
|
+
unless @jobs_in_progress.empty? || busy_shards.empty?
|
185
|
+
spinner = ['-', '\\', '|', '/'][@i % 4]
|
186
|
+
elapsed_txt = "Time elapsed: #{time_elapsed}"
|
187
|
+
pending_txt = "Pending: #{@jobs_pending.length}"
|
188
|
+
finished_txt = "Finished: #{@jobs_finished.length}"
|
189
|
+
in_progress_txt =
|
190
|
+
if busy_shards.length != @jobs_in_progress.length
|
191
|
+
"In progress: #{@jobs_in_progress.length} (Copies: #{busy_shards.length})"
|
192
|
+
else
|
193
|
+
"In progress: #{@jobs_in_progress.length}"
|
194
|
+
end
|
195
|
+
|
196
|
+
clear_progress_string
|
197
|
+
|
198
|
+
@progress_string = "#{spinner} #{in_progress_txt} #{pending_txt} #{finished_txt} #{elapsed_txt}"
|
199
|
+
print @progress_string; $stdout.flush
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def time_elapsed
|
204
|
+
s = (Time.now - @start_time).to_i
|
205
|
+
|
206
|
+
if s == 1
|
207
|
+
"1 second"
|
208
|
+
elsif s < 60
|
209
|
+
"#{s} seconds"
|
210
|
+
else
|
211
|
+
days = s / (60 * 60 * 24) if s >= 60 * 60 * 24
|
212
|
+
hours = (s % (60 * 60 * 24)) / (60 * 60) if s >= 60 * 60
|
213
|
+
minutes = (s % (60 * 60)) / 60 if s >= 60
|
214
|
+
seconds = s % 60
|
215
|
+
|
216
|
+
[days,hours,minutes,seconds].compact.map {|i| "%0.2i" % i }.join(":")
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/gizzmo.rb
CHANGED
@@ -22,7 +22,7 @@ DOC_STRINGS = {
|
|
22
22
|
"lookup" => "Lookup the shard id that holds the record for a given table / source_id.",
|
23
23
|
"markbusy" => "Mark a shard as busy.",
|
24
24
|
"pair" => "Report the replica pairing structure for a list of hosts.",
|
25
|
-
"reload" => "Instruct
|
25
|
+
"reload" => "Instruct application servers to reload the nameserver state.",
|
26
26
|
"report" => "Show each unique replica structure for a given list of shards. Usually this shard list comes from << gizzmo forwardings | awk '{ print $3 }' >>.",
|
27
27
|
"setup-replica" => "Add a replica to be parallel to an existing replica, in write-only mode, ready to be copied to.",
|
28
28
|
"wrap" => "Wrapping creates a new (virtual, e.g. blocking, replicating, etc.) shard, and relinks SHARD_ID_TO_WRAP's parent links to run through the new shard.",
|
@@ -32,9 +32,12 @@ ORIGINAL_ARGV = ARGV.dup
|
|
32
32
|
zero = File.basename($0)
|
33
33
|
|
34
34
|
# Container for parsed options
|
35
|
-
global_options
|
36
|
-
global_options.
|
37
|
-
global_options.
|
35
|
+
global_options = OpenStruct.new
|
36
|
+
global_options.port = 7920
|
37
|
+
global_options.injector_port = 7921
|
38
|
+
global_options.render = []
|
39
|
+
global_options.framed = false
|
40
|
+
|
38
41
|
subcommand_options = OpenStruct.new
|
39
42
|
|
40
43
|
# Leftover arguments
|
@@ -78,10 +81,29 @@ end
|
|
78
81
|
|
79
82
|
def load_config(options, filename)
|
80
83
|
YAML.load(File.open(filename)).each do |k, v|
|
84
|
+
v = v.split(",").map {|h| h.strip } if k == "hosts"
|
81
85
|
options.send("#{k}=", v)
|
82
86
|
end
|
83
87
|
end
|
84
88
|
|
89
|
+
def add_scheduler_opts(subcommand_options, opts)
|
90
|
+
opts.on("--max-copies=COUNT", "Limit max simultaneous copies to COUNT.") do |c|
|
91
|
+
(subcommand_options.scheduler_options ||= {})[:max_copies] = c.to_i
|
92
|
+
end
|
93
|
+
opts.on("--copies-per-host=COUNT", "Limit max copies per individual destination host to COUNT") do |c|
|
94
|
+
(subcommand_options.scheduler_options ||= {})[:copies_per_host] = c.to_i
|
95
|
+
end
|
96
|
+
opts.on("--poll-interval=SECONDS", "Sleep SECONDS between polling for copy status") do |c|
|
97
|
+
(subcommand_options.scheduler_options ||= {})[:poll_interval] = c.to_i
|
98
|
+
end
|
99
|
+
opts.on("--copy-wrapper=TYPE", "Wrap copy destination shards with TYPE. default WriteOnlyShard") do |t|
|
100
|
+
(subcommand_options.scheduler_options ||= {})[:copy_wrapper] = t
|
101
|
+
end
|
102
|
+
opts.on("--no-progress", "Do not show progress bar at bottom.") do
|
103
|
+
(subcommand_options.scheduler_options ||= {})[:no_progress] = true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
85
107
|
subcommands = {
|
86
108
|
'create' => OptionParser.new do |opts|
|
87
109
|
opts.banner = "Usage: #{zero} create [options] CLASS_NAME SHARD_ID [MORE SHARD_IDS...]"
|
@@ -106,7 +128,7 @@ subcommands = {
|
|
106
128
|
opts.on("-w", "--write-only=CLASS") do |w|
|
107
129
|
subcommand_options.write_only_shard = w
|
108
130
|
end
|
109
|
-
opts.on("-h", "--hosts=list") do |h|
|
131
|
+
opts.on("-h", "--shard-hosts=list") do |h|
|
110
132
|
subcommand_options.hosts = h
|
111
133
|
end
|
112
134
|
opts.on("-x", "--exclude-hosts=list") do |x|
|
@@ -149,10 +171,6 @@ subcommands = {
|
|
149
171
|
opts.banner = "Usage: #{zero} addforwarding TABLE_ID BASE_ID SHARD_ID"
|
150
172
|
separators(opts, DOC_STRINGS["addforwarding"])
|
151
173
|
end,
|
152
|
-
'currentforwarding' => OptionParser.new do |opts|
|
153
|
-
opts.banner = "Usage: #{zero} currentforwarding SOURCE_ID [ANOTHER_SOURCE_ID...]"
|
154
|
-
separators(opts, DOC_STRINGS["addforwarding"])
|
155
|
-
end,
|
156
174
|
'forwardings' => OptionParser.new do |opts|
|
157
175
|
opts.banner = "Usage: #{zero} forwardings [options]"
|
158
176
|
separators(opts, DOC_STRINGS["forwardings"])
|
@@ -177,7 +195,7 @@ subcommands = {
|
|
177
195
|
subcommand_options.shard_type = shard_type
|
178
196
|
end
|
179
197
|
|
180
|
-
opts.on("-
|
198
|
+
opts.on("-h", "--shard-host=HOST", "HOST of shard") do |shard_host|
|
181
199
|
subcommand_options.shard_host = shard_host
|
182
200
|
end
|
183
201
|
end,
|
@@ -267,6 +285,46 @@ subcommands = {
|
|
267
285
|
opts.on("--all", "Flush all error queues.") do
|
268
286
|
subcommand_options.flush_all = true
|
269
287
|
end
|
288
|
+
end,
|
289
|
+
'add-host' => OptionParser.new do |opts|
|
290
|
+
opts.banner = "Usage: #{zero} add-host HOSTS"
|
291
|
+
separators(opts, DOC_STRINGS["add-host"])
|
292
|
+
end,
|
293
|
+
'remove-host' => OptionParser.new do |opts|
|
294
|
+
opts.banner = "Usage: #{zero} remove-host HOST"
|
295
|
+
separators(opts, DOC_STRINGS["remove-host"])
|
296
|
+
end,
|
297
|
+
'list-hosts' => OptionParser.new do |opts|
|
298
|
+
opts.banner = "Usage: #{zero} list-hosts"
|
299
|
+
separators(opts, DOC_STRINGS["list-hosts"])
|
300
|
+
end,
|
301
|
+
'topology' => OptionParser.new do |opts|
|
302
|
+
opts.banner = "Usage: #{zero} topology [options]"
|
303
|
+
separators(opts, DOC_STRINGS["topology"])
|
304
|
+
|
305
|
+
opts.on("--forwardings", "Show topology of forwardings instead of counts") do
|
306
|
+
subcommand_options.forwardings = true
|
307
|
+
end
|
308
|
+
end,
|
309
|
+
'transform-tree' => OptionParser.new do |opts|
|
310
|
+
opts.banner = "Usage: #{zero} transform-tree [options] ROOT_SHARD_ID TEMPLATE"
|
311
|
+
separators(opts, DOC_STRINGS['transform-tree'])
|
312
|
+
|
313
|
+
add_scheduler_opts subcommand_options, opts
|
314
|
+
|
315
|
+
opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
|
316
|
+
subcommand_options.quiet = true
|
317
|
+
end
|
318
|
+
end,
|
319
|
+
'transform' => OptionParser.new do |opts|
|
320
|
+
opts.banner = "Usage: #{zero} transform [options] FROM_TEMPLATE TO_TEMPLATE"
|
321
|
+
separators(opts, DOC_STRINGS['transform'])
|
322
|
+
|
323
|
+
add_scheduler_opts subcommand_options, opts
|
324
|
+
|
325
|
+
opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
|
326
|
+
subcommand_options.quiet = true
|
327
|
+
end
|
270
328
|
end
|
271
329
|
}
|
272
330
|
|
@@ -288,7 +346,7 @@ global = OptionParser.new do |opts|
|
|
288
346
|
opts.separator "key/value pairs corresponding to options you want by default. A common .gizzmorc"
|
289
347
|
opts.separator "simply contains:"
|
290
348
|
opts.separator ""
|
291
|
-
opts.separator "
|
349
|
+
opts.separator " hosts: localhost"
|
292
350
|
opts.separator " port: 7917"
|
293
351
|
opts.separator ""
|
294
352
|
opts.separator "Subcommands:"
|
@@ -305,24 +363,32 @@ global = OptionParser.new do |opts|
|
|
305
363
|
opts.separator ""
|
306
364
|
opts.separator ""
|
307
365
|
opts.separator "Global options:"
|
308
|
-
opts.on("-H", "--
|
309
|
-
global_options.
|
366
|
+
opts.on("-H", "--hosts=HOST[,HOST,...]", "HOSTS of application servers") do |hosts|
|
367
|
+
global_options.hosts = hosts.split(",").map {|h| h.strip }
|
310
368
|
end
|
311
369
|
|
312
|
-
opts.on("-P", "--port=PORT", "PORT of remote
|
370
|
+
opts.on("-P", "--port=PORT", "PORT of remote manager service. default 7920") do |port|
|
313
371
|
global_options.port = port.to_i
|
314
372
|
end
|
315
373
|
|
374
|
+
opts.on("-I", "--injector=PORT", "PORT of remote job injector service. default 7921") do |port|
|
375
|
+
global_options.injector_port = port.to_i
|
376
|
+
end
|
377
|
+
|
378
|
+
opts.on("-T", "--tables=TABLE[,TABLE,...]", "TABLE ids of forwardings to affect") do |tables|
|
379
|
+
global_options.tables = tables.split(",").map {|t| t.to_i }
|
380
|
+
end
|
381
|
+
|
316
382
|
opts.on("-F", "--framed", "use the thrift framed transport") do |framed|
|
317
383
|
global_options.framed = true
|
318
384
|
end
|
319
385
|
|
320
386
|
opts.on("-r", "--retry=TIMES", "TIMES to retry the command") do |r|
|
321
|
-
global_options.retry = r
|
387
|
+
global_options.retry = r.to_i
|
322
388
|
end
|
323
389
|
|
324
390
|
opts.on("-t", "--timeout=SECONDS", "SECONDS to let the command run") do |r|
|
325
|
-
global_options.timeout = r
|
391
|
+
global_options.timeout = r.to_i
|
326
392
|
end
|
327
393
|
|
328
394
|
opts.on("--subtree", "Render in subtree mode") do
|
@@ -333,7 +399,7 @@ global = OptionParser.new do |opts|
|
|
333
399
|
global_options.render << "info"
|
334
400
|
end
|
335
401
|
|
336
|
-
opts.on("-D", "--dry-run", "") do
|
402
|
+
opts.on("-D", "--dry-run", "") do
|
337
403
|
global_options.dry = true
|
338
404
|
end
|
339
405
|
|
@@ -401,12 +467,12 @@ def custom_timeout(seconds)
|
|
401
467
|
begin
|
402
468
|
require "rubygems"
|
403
469
|
require "system_timer"
|
404
|
-
SystemTimer.timeout_after(seconds
|
470
|
+
SystemTimer.timeout_after(seconds) do
|
405
471
|
yield
|
406
472
|
end
|
407
473
|
rescue LoadError
|
408
474
|
require "timeout"
|
409
|
-
Timeout.timeout(seconds
|
475
|
+
Timeout.timeout(seconds) do
|
410
476
|
yield
|
411
477
|
end
|
412
478
|
end
|
@@ -427,8 +493,9 @@ rescue HelpNeededError => e
|
|
427
493
|
end
|
428
494
|
STDERR.puts subcommands[subcommand_name]
|
429
495
|
exit 1
|
430
|
-
rescue ThriftClient::Simple::ThriftException, Gizzard::
|
496
|
+
rescue ThriftClient::Simple::ThriftException, Gizzard::GizzardException, Errno::ECONNREFUSED => e
|
431
497
|
STDERR.puts e.message
|
498
|
+
STDERR.puts e.backtrace
|
432
499
|
exit 1
|
433
500
|
rescue Errno::EPIPE
|
434
501
|
# This is just us trying to puts into a closed stdout. For example, if you pipe into
|