gizzmo 0.10.0 → 0.10.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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.0
1
+ 0.10.1
data/gizzmo.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{gizzmo}
8
- s.version = "0.10.0"
8
+ s.version = "0.10.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kyle Maxwell"]
12
- s.date = %q{2010-11-02}
12
+ s.date = %q{2011-01-05}
13
13
  s.default_executable = %q{gizzmo}
14
14
  s.description = %q{Gizzmo is a command-line client for managing gizzard clusters.}
15
15
  s.email = %q{kmaxwell@twitter.com}
@@ -19,8 +19,7 @@ Gem::Specification.new do |s|
19
19
  "README.rdoc"
20
20
  ]
21
21
  s.files = [
22
- ".document",
23
- ".gitignore",
22
+ ".gitignore",
24
23
  "LICENSE",
25
24
  "README.rdoc",
26
25
  "Rakefile",
@@ -1,5 +1,6 @@
1
1
  require "pp"
2
2
  require "digest/md5"
3
+
3
4
  module Gizzard
4
5
  class Command
5
6
  include Thrift
@@ -17,7 +18,7 @@ module Gizzard
17
18
  end
18
19
 
19
20
  def self.classify(string)
20
- string.split(/\W+/).map{|s| s.capitalize }.join("")
21
+ string.split(/\W+/).map { |s| s.capitalize }.join("")
21
22
  end
22
23
 
23
24
  attr_reader :service, :global_options, :argv, :command_options
@@ -41,13 +42,13 @@ module Gizzard
41
42
  end
42
43
  end
43
44
  end
44
-
45
+
45
46
  class RetryProxy
46
47
  def initialize(retries, object)
47
48
  @inner = object
48
49
  @retries_left = retries
49
50
  end
50
-
51
+
51
52
  def method_missing(*args)
52
53
  @inner.send(*args)
53
54
  rescue
@@ -63,15 +64,15 @@ module Gizzard
63
64
 
64
65
  class ShardCommand < Command
65
66
  def self.make_service(global_options, log)
66
- RetryProxy.new global_options.retry.to_i,
67
- Gizzard::Thrift::ShardManager.new(global_options.host, global_options.port, log, global_options.dry)
67
+ RetryProxy.new global_options.retry.to_i,
68
+ Gizzard::Thrift::ShardManager.new(global_options.host, global_options.port, log, global_options.framed, global_options.dry)
68
69
  end
69
70
  end
70
71
 
71
72
  class JobCommand < Command
72
73
  def self.make_service(global_options, log)
73
74
  RetryProxy.new global_options.retry.to_i ,
74
- Gizzard::Thrift::JobManager.new(global_options.host, global_options.port + 2, log, global_options.dry)
75
+ Gizzard::Thrift::JobManager.new(global_options.host, global_options.port + 2, log, global_options.framed, global_options.dry)
75
76
  end
76
77
  end
77
78
 
@@ -147,7 +148,18 @@ module Gizzard
147
148
  class ReloadCommand < ShardCommand
148
149
  def run
149
150
  if global_options.force || ask
150
- service.reload_forwardings
151
+ if @argv
152
+ # allow hosts to be given on the command line
153
+ @argv.each do |hostname|
154
+ output hostname
155
+ opts = global_options.dup
156
+ opts.host = hostname
157
+ s = self.class.make_service(opts, global_options.log || "./gizzmo.log")
158
+ s.reload_forwardings
159
+ end
160
+ else
161
+ service.reload_forwardings
162
+ end
151
163
  else
152
164
  STDERR.puts "aborted"
153
165
  end
@@ -201,7 +213,10 @@ module Gizzard
201
213
  upward_links = service.list_upward_links(shard_id)
202
214
  downward_links = service.list_downward_links(shard_id)
203
215
 
204
- help! "Shard must not be a root or leaf" if upward_links.length == 0 or downward_links.length == 0
216
+ if upward_links.length == 0 or downward_links.length == 0
217
+ STDERR.puts "Shard #{shard_id_string} must not be a root or leaf"
218
+ next
219
+ end
205
220
 
206
221
  upward_links.each do |uplink|
207
222
  downward_links.each do |downlink|
@@ -239,11 +254,15 @@ module Gizzard
239
254
  shard_ids.each do |shard_id_text|
240
255
  shard_id = ShardId.parse(shard_id_text)
241
256
  next if !shard_id
242
- service.list_upward_links(shard_id).each do |link_info|
243
- output link_info.to_unix
257
+ unless command_options.down
258
+ service.list_upward_links(shard_id).each do |link_info|
259
+ output command_options.ids ? link_info.up_id.to_unix : link_info.to_unix
260
+ end
244
261
  end
245
- service.list_downward_links(shard_id).each do |link_info|
246
- output link_info.to_unix
262
+ unless command_options.up
263
+ service.list_downward_links(shard_id).each do |link_info|
264
+ output command_options.ids ? link_info.down_id.to_unix : link_info.to_unix
265
+ end
247
266
  end
248
267
  end
249
268
  end
@@ -406,7 +425,7 @@ module Gizzard
406
425
 
407
426
  puts "gizzmo create #{shard_info.class_name} -s '#{shard_info.source_type}' -d '#{shard_info.destination_type}' #{new_shards.join(" ")}"
408
427
  puts "gizzmo wrap #{command_options.write_only_shard} #{new_shards.join(" ")}"
409
- shards.map {|(old, new)| puts "gizzmo copy #{old} #{new}" }
428
+ shards.map { |(old, new)| puts "gizzmo copy #{old} #{new}" }
410
429
  end
411
430
  end
412
431
 
@@ -433,16 +452,16 @@ module Gizzard
433
452
 
434
453
  overlaps = {}
435
454
  ids_by_table.values.each do |arr|
436
- key = arr.map{|id| id.hostname }.sort
455
+ key = arr.map { |id| id.hostname }.sort
437
456
  overlaps[key] ||= 0
438
- overlaps[key] += 1
457
+ overlaps[key] += 1
439
458
  end
440
459
 
441
460
  displayed = {}
442
- overlaps.sort_by{|hosts, count| count }.reverse.each do |(host_a, host_b), count|
461
+ overlaps.sort_by { |hosts, count| count }.reverse.each do |(host_a, host_b), count|
443
462
  next if !host_a || !host_b || displayed[host_a] || displayed[host_b]
444
- id_a = ids_by_host[host_a].find{|id| service.list_upward_links(id).size > 0 }
445
- id_b = ids_by_host[host_b].find{|id| service.list_upward_links(id).size > 0 }
463
+ id_a = ids_by_host[host_a].find { |id| service.list_upward_links(id).size > 0 }
464
+ id_b = ids_by_host[host_b].find { |id| service.list_upward_links(id).size > 0 }
446
465
  next unless id_a && id_b
447
466
  weight_a = service.list_upward_links(id_a).first.weight
448
467
  weight_b = service.list_upward_links(id_b).first.weight
@@ -466,7 +485,6 @@ module Gizzard
466
485
 
467
486
  class ReportCommand < ShardCommand
468
487
  def run
469
-
470
488
  things = @argv.map do |shard|
471
489
  parse(down(ShardId.parse(shard))).join("\n")
472
490
  end
@@ -492,20 +510,20 @@ module Gizzard
492
510
  m[e] ||= []
493
511
  m[e] << e
494
512
  m
495
- end.to_a.sort_by{|k, v| v.length}.reverse
513
+ end.to_a.sort_by { |k, v| v.length }.reverse
496
514
  end
497
515
 
498
516
  def parse(obj, id = nil, depth = 0, sub = true)
499
517
  case obj
500
518
  when Hash
501
519
  id, prefix = parse(obj.keys.first, id, depth, sub)
502
- [prefix] + parse(obj.values.first, id, depth + 1, sub)
520
+ [ prefix ] + parse(obj.values.first, id, depth + 1, sub)
503
521
  when String
504
522
  host, prefix = obj.split("/")
505
523
  host = "db" if host != "localhost" && sub
506
- id ||= prefix[/(\w+ward_)?\d+_\d+(_\w+ward)?/]
524
+ id ||= prefix[/(\w+ward_)?n?\d+_\d+(_\w+ward)?/]
507
525
  prefix = (" " * depth) + host + "/" + ((sub && id) ? prefix.sub(id, "[ID]") : prefix)
508
- [id, prefix]
526
+ [ id, prefix ]
509
527
  when Array
510
528
  obj.map do |e|
511
529
  parse e, id, depth, sub
@@ -517,7 +535,7 @@ module Gizzard
517
535
  vals = service.list_downward_links(id).map do |link|
518
536
  down(link.down_id)
519
537
  end
520
- {id.to_unix => vals}
538
+ { id.to_unix => vals }
521
539
  end
522
540
  end
523
541
 
@@ -575,6 +593,60 @@ module Gizzard
575
593
  end
576
594
  end
577
595
 
596
+ class SetupReplicaCommand < ShardCommand
597
+ def run
598
+ from_shard_id_string, to_shard_id_string = @argv
599
+ help!("Requires source & destination shard id") unless from_shard_id_string && to_shard_id_string
600
+ from_shard_id = ShardId.parse(from_shard_id_string)
601
+ to_shard_id = ShardId.parse(to_shard_id_string)
602
+
603
+ if service.list_upward_links(to_shard_id).size > 0
604
+ STDERR.puts "Destination shard #{to_shard_id} has links to it."
605
+ exit 1
606
+ end
607
+
608
+ link = service.list_upward_links(from_shard_id)[0]
609
+ replica_shard_id = link.up_id
610
+ weight = link.weight
611
+ write_only_shard_id = ShardId.new("localhost", "#{to_shard_id.table_prefix}_copy_write_only")
612
+ service.create_shard(ShardInfo.new(write_only_shard_id, "WriteOnlyShard", "", "", 0))
613
+ service.add_link(replica_shard_id, write_only_shard_id, weight)
614
+ service.add_link(write_only_shard_id, to_shard_id, 1)
615
+ output to_shard_id.to_unix
616
+ end
617
+ end
618
+
619
+ class FinishReplicaCommand < ShardCommand
620
+ def run
621
+ from_shard_id_string, to_shard_id_string = @argv
622
+ help!("Requires source & destination shard id") unless from_shard_id_string && to_shard_id_string
623
+ from_shard_id = ShardId.parse(from_shard_id_string)
624
+ to_shard_id = ShardId.parse(to_shard_id_string)
625
+
626
+ write_only_shard_id = ShardId.new("localhost", "#{to_shard_id.table_prefix}_copy_write_only")
627
+ link = service.list_upward_links(write_only_shard_id)[0]
628
+ replica_shard_id = link.up_id
629
+ weight = link.weight
630
+
631
+ # careful. need to validate some basic assumptions.
632
+ unless global_options.force
633
+ if service.list_upward_links(from_shard_id).map { |link| link.up_id }.to_a != [ replica_shard_id ]
634
+ STDERR.puts "Uplink from #{from_shard_id} is not a migration replica."
635
+ exit 1
636
+ end
637
+ if service.list_upward_links(to_shard_id).map { |link| link.up_id }.to_a != [ write_only_shard_id ]
638
+ STDERR.puts "Uplink from #{to_shard_id} is not a write-only barrier."
639
+ exit 1
640
+ end
641
+ end
642
+
643
+ service.remove_link(write_only_shard_id, to_shard_id)
644
+ service.remove_link(replica_shard_id, write_only_shard_id)
645
+ service.add_link(replica_shard_id, to_shard_id, weight)
646
+ service.delete_shard(write_only_shard_id)
647
+ end
648
+ end
649
+
578
650
  class SetupMigrateCommand < ShardCommand
579
651
  def run
580
652
  from_shard_id_string, to_shard_id_string = @argv
@@ -52,7 +52,7 @@ module Gizzard
52
52
  end
53
53
 
54
54
  def to_unix
55
- [id.to_unix, class_name, busy? ? "busy" : "unbusy"].join("\t")
55
+ [id.to_unix, class_name, busy? ? "busy" : "ok"].join("\t")
56
56
  end
57
57
  end
58
58
 
@@ -91,8 +91,8 @@ module Gizzard
91
91
  end
92
92
 
93
93
  class GizzmoService < T::ThriftService
94
- def initialize(host, port, log_path, dry_run = false)
95
- super(host, port)
94
+ def initialize(host, port, log_path, framed, dry_run = false)
95
+ super(host, port, framed)
96
96
  @dry = dry_run
97
97
  begin
98
98
  @log = File.open(log_path, "a")
data/lib/gizzmo.rb CHANGED
@@ -7,11 +7,25 @@ require "gizzard"
7
7
  require "yaml"
8
8
 
9
9
  DOC_STRINGS = {
10
- "create" => "Create shard(s) of a given Java/Scala class. If you don't know the list of available classes, you can just try a bogus class, and the exception will include a list of valid classes.",
11
- "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.",
10
+ "addforwarding" => "Add a forwarding from a graph_id / base_source_id to a given shard.",
11
+ "addlink" => "Add a relationship link between two shards.",
12
+ "create" => "Create shard(s) of a given Java/Scala class. If you don't know the list of available classes, you can just try a bogus class, and the exception will include a list of valid classes.",
13
+ "drill" => "Show shard trees for replicas of a given structure signature (from 'report').",
14
+ "find" => "Show all shards with a given hostname.",
15
+ "finish-replica" => "Remove the write-only barrier in front of a shard that's finished being copied after 'setup-replica'.",
16
+ "flush" => "Flush error queue for a given priority.",
17
+ "forwardings" => "Get a list of all forwardings.",
18
+ "hosts" => "List hosts used in shard names in the forwarding table and replicas.",
19
+ "info" => "Show id/class/busy for shards.",
12
20
  "inject" => "Inject jobs (as literal json) into the server. Jobs can be linefeed-terminated from stdin, or passed as arguments. Priority is server-defined, but typically lower numbers (like 1) are lower priority.",
21
+ "links" => "List parent & child links for shards.",
13
22
  "lookup" => "Lookup the shard id that holds the record for a given table / source_id.",
14
- "flush" => "Flush error queue for a given priority."
23
+ "markbusy" => "Mark a shard as busy.",
24
+ "pair" => "Report the replica pairing structure for a list of hosts.",
25
+ "reload" => "Instruct an appserver to reload its nameserver state.",
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
+ "setup-replica" => "Add a replica to be parallel to an existing replica, in write-only mode, ready to be copied to.",
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.",
15
29
  }
16
30
 
17
31
  ORIGINAL_ARGV = ARGV.dup
@@ -20,6 +34,7 @@ zero = File.basename($0)
20
34
  # Container for parsed options
21
35
  global_options = OpenStruct.new
22
36
  global_options.render = []
37
+ global_options.framed = false
23
38
  subcommand_options = OpenStruct.new
24
39
 
25
40
  # Leftover arguments
@@ -61,6 +76,12 @@ def separators(opts, string)
61
76
  opts.separator("")
62
77
  end
63
78
 
79
+ def load_config(options, filename)
80
+ YAML.load(File.open(filename)).each do |k, v|
81
+ options.send("#{k}=", v)
82
+ end
83
+ end
84
+
64
85
  subcommands = {
65
86
  'create' => OptionParser.new do |opts|
66
87
  opts.banner = "Usage: #{zero} create [options] CLASS_NAME SHARD_ID [MORE SHARD_IDS...]"
@@ -163,6 +184,16 @@ subcommands = {
163
184
  'links' => OptionParser.new do |opts|
164
185
  opts.banner = "Usage: #{zero} links SHARD_ID [MORE SHARD_IDS...]"
165
186
  separators(opts, DOC_STRINGS["links"])
187
+
188
+ opts.on("--ids", "Show shard ids only") do
189
+ subcommand_options.ids = true
190
+ end
191
+ opts.on("--up", "Show uplinks only") do
192
+ subcommand_options.up = true
193
+ end
194
+ opts.on("--down", "show downlinks only") do
195
+ subcommand_options.down = true
196
+ end
166
197
  end,
167
198
  'info' => OptionParser.new do |opts|
168
199
  opts.banner = "Usage: #{zero} info SHARD_ID [MORE SHARD_IDS...]"
@@ -209,6 +240,14 @@ subcommands = {
209
240
  opts.banner = "Usage: #{zero} busy"
210
241
  separators(opts, DOC_STRINGS["busy"])
211
242
  end,
243
+ 'setup-replica' => OptionParser.new do |opts|
244
+ opts.banner = "Usage: #{zero} setup-replica SOURCE_SHARD_ID DESTINATION_SHARD_ID"
245
+ separators(opts, DOC_STRINGS["setup-replica"])
246
+ end,
247
+ 'finish-replica' => OptionParser.new do |opts|
248
+ opts.banner = "Usage: #{zero} finish-replica SOURCE_SHARD_ID DESTINATION_SHARD_ID"
249
+ separators(opts, DOC_STRINGS["finish-replica"])
250
+ end,
212
251
  'setup-migrate' => OptionParser.new do |opts|
213
252
  opts.banner = "Usage: #{zero} setup-migrate SOURCE_SHARD_ID DESTINATION_SHARD_ID"
214
253
  separators(opts, DOC_STRINGS["setup-migrate"])
@@ -231,6 +270,10 @@ subcommands = {
231
270
  end
232
271
  }
233
272
 
273
+ if ENV['GIZZMORC']
274
+ load_config(global_options, ENV['GIZZMORC'])
275
+ end
276
+
234
277
  global = OptionParser.new do |opts|
235
278
  opts.banner = "Usage: #{zero} [global-options] SUBCOMMAND [subcommand-options]"
236
279
  opts.separator ""
@@ -243,7 +286,7 @@ global = OptionParser.new do |opts|
243
286
  opts.separator ""
244
287
  opts.separator "You may find it useful to create a ~/.gizzmorc file, which is simply YAML"
245
288
  opts.separator "key/value pairs corresponding to options you want by default. A common .gizzmorc"
246
- opts.separator "simply contain:"
289
+ opts.separator "simply contains:"
247
290
  opts.separator ""
248
291
  opts.separator " host: localhost"
249
292
  opts.separator " port: 7917"
@@ -270,6 +313,10 @@ global = OptionParser.new do |opts|
270
313
  global_options.port = port.to_i
271
314
  end
272
315
 
316
+ opts.on("-F", "--framed", "use the thrift framed transport") do |framed|
317
+ global_options.framed = true
318
+ end
319
+
273
320
  opts.on("-r", "--retry=TIMES", "TIMES to retry the command") do |r|
274
321
  global_options.retry = r
275
322
  end
@@ -290,10 +337,8 @@ global = OptionParser.new do |opts|
290
337
  global_options.dry = true
291
338
  end
292
339
 
293
- opts.on("-C", "--config=YAML_FILE", "YAML_FILE of option key/values") do |file|
294
- YAML.load(File.open(file)).each do |k, v|
295
- global_options.send("#{k}=", v)
296
- end
340
+ opts.on("-C", "--config=YAML_FILE", "YAML_FILE of option key/values") do |filename|
341
+ load_config(global_options, filename)
297
342
  end
298
343
 
299
344
  opts.on("-L", "--log=LOG_FILE", "Path to LOG_FILE") do |file|
@@ -370,7 +415,7 @@ def custom_timeout(seconds)
370
415
  end
371
416
  end
372
417
 
373
- begin
418
+ begin
374
419
  custom_timeout(global_options.timeout) do
375
420
  Gizzard::Command.run(subcommand_name, global_options, argv, subcommand_options, log)
376
421
  end
@@ -87,8 +87,14 @@ class ThriftClient
87
87
  end
88
88
  end
89
89
 
90
- def pack_request(method_name, arg_struct, request_id=0)
91
- [ VERSION_1, CALL, method_name.to_s.size, method_name.to_s, request_id, arg_struct._pack ].pack("nnNa*Na*")
90
+ def pack_request(method_name, arg_struct, framed, request_id=0)
91
+ msg = [ VERSION_1, CALL, method_name.to_s.size, method_name.to_s, request_id, arg_struct._pack ].pack("nnNa*Na*")
92
+ if framed
93
+ frame = [ msg.length ].pack("N")
94
+ frame + msg
95
+ else
96
+ msg
97
+ end
92
98
  end
93
99
 
94
100
  def read_value(s, type)
@@ -169,8 +175,13 @@ class ThriftClient
169
175
  end
170
176
  end
171
177
  end
172
-
173
- def read_response(s, rv_class)
178
+
179
+ def read_response(s, rv_class, framed)
180
+ if framed
181
+ # unwrap frame size. dont use for now
182
+ framesize = s.read(4).unpack("N")
183
+ end
184
+
174
185
  version, message_type, method_name_len = s.read(8).unpack("nnN")
175
186
  method_name = s.read(method_name_len)
176
187
  seq_id = s.read(4).unpack("N").first
@@ -280,9 +291,10 @@ class ThriftClient
280
291
  UnknownStruct = make_struct(:Unknown)
281
292
 
282
293
  class ThriftService
283
- def initialize(host, port)
294
+ def initialize(host, port, framed = false)
284
295
  @host = host
285
296
  @port = port
297
+ @framed = framed
286
298
  end
287
299
 
288
300
  def self._arg_structs
@@ -310,8 +322,8 @@ class ThriftClient
310
322
  arg_class, rv_class = cls._arg_structs[method_name.to_sym]
311
323
  arg_struct = arg_class.new(*args)
312
324
  sock = TCPSocket.new(@host, @port)
313
- sock.write(ThriftClient::Simple.pack_request(method_name, arg_struct))
314
- rv = ThriftClient::Simple.read_response(sock, rv_class)
325
+ sock.write(ThriftClient::Simple.pack_request(method_name, arg_struct, @framed))
326
+ rv = ThriftClient::Simple.read_response(sock, rv_class, @framed)
315
327
  sock.close
316
328
  rv[2]
317
329
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 10
8
- - 0
9
- version: 0.10.0
8
+ - 1
9
+ version: 0.10.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Kyle Maxwell
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-02 00:00:00 -07:00
17
+ date: 2011-01-05 00:00:00 -08:00
18
18
  default_executable: gizzmo
19
19
  dependencies: []
20
20
 
@@ -28,7 +28,6 @@ extra_rdoc_files:
28
28
  - LICENSE
29
29
  - README.rdoc
30
30
  files:
31
- - .document
32
31
  - .gitignore
33
32
  - LICENSE
34
33
  - README.rdoc
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE