gizzmo 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.1
1
+ 0.13.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{gizzmo}
8
- s.version = "0.12.1"
8
+ s.version = "0.13.0"
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{2011-10-20}
12
+ s.date = %q{2012-01-09}
13
13
  s.description = %q{Gizzmo is a command-line client for managing gizzard clusters.}
14
14
  s.email = %q{kmaxwell@twitter.com}
15
15
  s.executables = ["setup_shards", "gizzmo"]
@@ -64,6 +64,54 @@ module Gizzard
64
64
  puts string
65
65
  end
66
66
  end
67
+
68
+ def require_tables
69
+ if !global_options.tables
70
+ puts "Please specify tables to repair with the --tables flag"
71
+ exit 1
72
+ end
73
+ end
74
+
75
+ def require_template_options
76
+ options = command_options.template_options || {}
77
+ if global_options.template_options && global_options.template_options[:simple]
78
+ fail = false
79
+ if !options[:concrete]
80
+ fail = true
81
+ STDERR.puts "Please specify a concrete shard type with --concrete flag when using --simple"
82
+ end
83
+ if !options[:source_type]
84
+ fail = true
85
+ STDERR.puts "Please specify a source data type with --source-type flag when using --simple"
86
+ end
87
+ if !options[:dest_type]
88
+ fail = true
89
+ STDERR.puts "Please specify a destination data type with --dest-type flag when using --simple"
90
+ end
91
+ exit 1 if fail
92
+ end
93
+ end
94
+
95
+ # Infer the shard base_name from a list of transformations
96
+ def get_base_name(transformations)
97
+ # Gets the first valid tree from the map of transformations -> applicable trees
98
+ # Trees are maps of forwardings -> shards. Gets the first valid shard from the this tree.
99
+ # Gets the ShardId of that shard and pulls the base name out of the table prefix
100
+ transformations.values.find {|v| v.is_a?(Hash) && !v.values.empty? }.values.find {|v| !v.nil?}.id.table_prefix.split('_').first
101
+ end
102
+
103
+ def confirm!(message="Continue?")
104
+ unless global_options.force
105
+ begin
106
+ print "#{message} (y/n) "; $stdout.flush
107
+ resp = $stdin.gets.chomp.downcase
108
+ puts ""
109
+ end while resp != 'y' && resp != 'n'
110
+ exit if resp == 'n'
111
+ end
112
+ end
113
+
114
+
67
115
  end
68
116
 
69
117
  class RetryProxy
@@ -494,30 +542,49 @@ module Gizzard
494
542
  end
495
543
 
496
544
  class CopyCommand < Command
497
- def run
498
- from_shard_id_string, to_shard_id_string = @argv
499
- help!("Requires source & destination shard id") unless from_shard_id_string && to_shard_id_string
500
- from_shard_id = ShardId.parse(from_shard_id_string)
501
- to_shard_id = ShardId.parse(to_shard_id_string)
502
- manager.copy_shard(from_shard_id, to_shard_id)
503
- end
504
- end
505
-
506
- class RepairShardsCommand < Command
507
545
  def run
508
546
  shard_id_strings = @argv
509
547
  help!("Requires at least two shard ids") unless shard_id_strings.size >= 2
510
548
  shard_ids = shard_id_strings.map{|s| ShardId.parse(s)}
511
- manager.repair_shards(shard_ids)
549
+ manager.copy_shard(shard_ids)
550
+ sleep 2
551
+ while manager.get_busy_shards().size > 0
552
+ sleep 5
553
+ end
512
554
  end
513
555
  end
514
556
 
515
- class DiffShardsCommand < Command
557
+ class RepairTablesCommand < Command
516
558
  def run
517
- shard_id_strings = @argv
518
- help!("Requires at least two shard ids") unless shard_id_strings.size >= 2
519
- shard_ids = shard_id_strings.map{|s| ShardId.parse(s)}
520
- manager.diff_shards(shard_ids)
559
+ require_tables
560
+
561
+ table_ids = global_options.tables
562
+ manifest = manager.manifest(*table_ids)
563
+ num_copies = command_options.num_copies || 100
564
+ shard_sets = []
565
+ manifest.trees.values.each do |tree|
566
+ shard_sets << concrete_leaves(tree)
567
+ end
568
+ shard_sets.each do |shard_ids|
569
+ while manager.get_busy_shards().size > num_copies
570
+ sleep 1
571
+ end
572
+ puts "Repairing " + shard_ids.map {|s| s.to_unix }.join(",")
573
+ manager.copy_shard(shard_ids)
574
+ end
575
+
576
+ while manager.get_busy_shards().size > 0
577
+ sleep 5
578
+ end
579
+ end
580
+
581
+ def concrete_leaves(tree)
582
+ list = []
583
+ list << tree.info.id if tree.children.empty? && !tree.info.class_name.include?("BlackHoleShard")
584
+ tree.children.each do |child|
585
+ list += concrete_leaves(child)
586
+ end
587
+ list
521
588
  end
522
589
  end
523
590
 
@@ -714,110 +781,203 @@ module Gizzard
714
781
 
715
782
  class TablesCommand < Command
716
783
  def run
717
- puts manager.list_tables.join(" ")
784
+ puts manager.list_tables.join(",")
718
785
  end
719
786
  end
720
787
 
721
788
  class TopologyCommand < Command
722
789
  def run
790
+ require_tables
791
+
723
792
  manifest = manager.manifest(*global_options.tables)
724
- templates = manifest.templates.inject({}) do |h, (t, fs)|
725
- h.update t.to_config => fs
726
- end
793
+ templates = manifest.templates
727
794
 
728
795
  if command_options.forwardings
729
796
  templates.
730
- inject([]) { |h, (t, fs)| fs.each { |f| h << [f.inspect, t] }; h }.
797
+ inject([]) { |h, (t, fs)| fs.each { |f| h << [f.inspect, t.to_config] }; h }.
731
798
  sort.
732
799
  each { |a| puts "%s\t%s" % a }
733
800
  elsif command_options.root_shards
734
801
  templates.
735
- inject([]) { |a, (t, fs)| fs.each { |f| a << [f.shard_id.inspect, t] }; a }.
802
+ inject([]) { |a, (t, fs)| fs.each { |f| a << [f.shard_id.inspect, t.to_config] }; a }.
736
803
  sort.
737
804
  each { |a| puts "%s\t%s" % a }
738
805
  else
739
806
  templates.
740
- map { |(t, fs)| [fs.length, t] }.
807
+ map { |(t, fs)| [fs.length, t.to_config] }.
741
808
  sort.reverse.
742
809
  each { |a| puts "%4d %s" % a }
743
810
  end
744
811
  end
745
812
  end
746
813
 
747
- class TransformTreeCommand < Command
814
+ class BaseTransformCommand < Command
748
815
  def run
749
- help!("wrong number of arguments") unless @argv.length == 2
750
-
751
816
  scheduler_options = command_options.scheduler_options || {}
752
- template_s, shard_id_s = @argv
753
-
754
- to_template = ShardTemplate.parse(template_s)
755
- shard_id = ShardId.parse(shard_id_s)
756
- base_name = shard_id.table_prefix.split('_').first
757
- forwarding = manager.get_forwarding_for_shard(shard_id)
758
- manifest = manager.manifest(forwarding.table_id)
759
- shard = manifest.trees[forwarding]
760
- copy_wrapper = scheduler_options[:copy_wrapper]
761
- be_quiet = global_options.force && command_options.quiet
762
- transformation = Transformation.new(shard.template, to_template, copy_wrapper)
817
+ be_quiet = global_options.force && command_options.quiet
763
818
 
764
819
  scheduler_options[:quiet] = be_quiet
765
820
 
766
- if transformation.noop?
821
+ transformations = get_transformations
822
+ transformations.reject! {|t,trees| t.noop? or trees.empty? }
823
+
824
+ if transformations.empty?
767
825
  puts "Nothing to do!"
768
826
  exit
769
827
  end
770
828
 
829
+ base_name = get_base_name(transformations)
830
+
771
831
  unless be_quiet
772
- puts transformation.inspect
832
+ transformations.each do |transformation, trees|
833
+ puts transformation.inspect
834
+ puts "Applied to #{trees.length} shards"
835
+ #trees.keys.sort.each {|f| puts " #{f.inspect}" }
836
+ end
773
837
  puts ""
774
838
  end
775
839
 
776
- unless global_options.force
777
- print "Continue? (y/n) "; $stdout.flush
778
- exit unless $stdin.gets.chomp == "y"
779
- puts ""
780
- end
840
+ confirm!
781
841
 
782
842
  Gizzard.schedule! manager,
783
843
  base_name,
784
- { transformation => { forwarding => shard } },
844
+ transformations,
785
845
  scheduler_options
786
846
  end
787
847
  end
788
848
 
789
- class TransformCommand < Command
790
- def run
849
+ class TransformTreeCommand < BaseTransformCommand
850
+ def get_transformations
851
+ help!("must have an even number of arguments") unless @argv.length % 2 == 0
852
+ require_template_options
853
+
854
+ scheduler_options = command_options.scheduler_options || {}
855
+ copy_wrapper = scheduler_options[:copy_wrapper]
856
+ skip_copies = scheduler_options[:skip_copies] || false
857
+ transformations = {}
858
+
859
+ memoized_transforms = {}
860
+ @argv.each_slice(2) do |(template_s, shard_id_s)|
861
+ to_template = ShardTemplate.parse(template_s)
862
+ shard_id = ShardId.parse(shard_id_s)
863
+ base_name = shard_id.table_prefix.split('_').first
864
+ forwarding = manager.get_forwarding_for_shard(shard_id)
865
+ manifest = manager.manifest(forwarding.table_id)
866
+ shard = manifest.trees[forwarding]
867
+
868
+ transform_args = [shard.template, to_template, copy_wrapper, skip_copies]
869
+ transformation = memoized_transforms.fetch(transform_args) do |args|
870
+ memoized_transforms[args] = Transformation.new(*args)
871
+ end
872
+ tree = transformations.fetch(transformation) do |t|
873
+ transformations[t] = {}
874
+ end
875
+ tree[forwarding] = shard
876
+ end
877
+
878
+ transformations
879
+ end
880
+ end
881
+
882
+ class TransformCommand < BaseTransformCommand
883
+ def get_transformations
791
884
  help!("must have an even number of arguments") unless @argv.length % 2 == 0
885
+ require_tables
886
+ require_template_options
792
887
 
793
888
  scheduler_options = command_options.scheduler_options || {}
794
889
  manifest = manager.manifest(*global_options.tables)
795
890
  copy_wrapper = scheduler_options[:copy_wrapper]
796
- be_quiet = global_options.force && command_options.quiet
891
+ skip_copies = scheduler_options[:skip_copies] || false
797
892
  transformations = {}
798
893
 
799
- scheduler_options[:quiet] = be_quiet
800
-
801
894
  @argv.each_slice(2) do |(from_template_s, to_template_s)|
802
895
  from, to = [from_template_s, to_template_s].map {|s| ShardTemplate.parse(s) }
803
- transformation = Transformation.new(from, to, copy_wrapper)
896
+ transformation = Transformation.new(from, to, copy_wrapper, skip_copies)
804
897
  forwardings = Set.new(manifest.templates[from] || [])
805
898
  trees = manifest.trees.reject {|(f, s)| !forwardings.include?(f) }
806
899
 
807
900
  transformations[transformation] = trees
808
901
  end
809
902
 
810
- transformations.reject! {|t,trees| t.noop? or trees.empty? }
903
+ transformations
904
+ end
905
+ end
906
+
907
+ class RebalanceCommand < BaseTransformCommand
908
+ def get_transformations
909
+ help!("must have an even number of arguments") unless @argv.length % 2 == 0
910
+ require_tables
911
+ require_template_options
912
+
913
+ scheduler_options = command_options.scheduler_options || {}
914
+ manifest = manager.manifest(*global_options.tables)
915
+ copy_wrapper = scheduler_options[:copy_wrapper]
916
+ transformations = {}
917
+
918
+ dest_templates_and_weights = {}
919
+
920
+ @argv.each_slice(2) do |(weight_s, to_template_s)|
921
+ to = ShardTemplate.parse(to_template_s)
922
+ weight = weight_s.to_i
923
+
924
+ dest_templates_and_weights[to] = weight
925
+ end
926
+
927
+ global_options.tables.inject({}) do |all, table|
928
+ trees = manifest.trees.reject {|(f, s)| f.table_id != table }
929
+ rebalancer = Rebalancer.new(trees, dest_templates_and_weights, copy_wrapper)
930
+
931
+ all.update(rebalancer.transformations) {|t,a,b| a.merge b }
932
+ end
933
+ end
934
+ end
935
+
936
+ class AddPartitionCommand < Command
937
+ def run
938
+ require_tables
939
+ require_template_options
940
+
941
+ scheduler_options = command_options.scheduler_options || {}
942
+ manifest = manager.manifest(*global_options.tables)
943
+ copy_wrapper = scheduler_options[:copy_wrapper]
944
+ be_quiet = global_options.force && command_options.quiet
945
+ transformations = {}
946
+
947
+ scheduler_options[:quiet] = be_quiet
948
+
949
+ puts "Note: All partitions, including existing ones, will be weighted evenly." unless be_quiet
950
+
951
+ add_templates_and_weights = {}
952
+
953
+ @argv.each do |template_s|
954
+ to = ShardTemplate.parse(template_s)
955
+
956
+ add_templates_and_weights[to] = ShardTemplate::DEFAULT_WEIGHT
957
+ end
958
+
959
+ orig_templates_and_weights = manifest.templates.inject({}) do |h, (template, forwardings)|
960
+ h[template] = ShardTemplate::DEFAULT_WEIGHT; h
961
+ end
962
+
963
+ dest_templates_and_weights = orig_templates_and_weights.merge(add_templates_and_weights)
964
+
965
+ transformations = global_options.tables.inject({}) do |all, table|
966
+ trees = manifest.trees.reject {|(f, s)| f.table_id != table }
967
+ rebalancer = Rebalancer.new(trees, dest_templates_and_weights, copy_wrapper)
968
+
969
+ all.update(rebalancer.transformations) {|t,a,b| a.merge b }
970
+ end
811
971
 
812
972
  if transformations.empty?
813
973
  puts "Nothing to do!"
814
974
  exit
815
975
  end
816
976
 
817
- base_name = transformations.values.find {|v| v.is_a?(Hash) && !v.values.empty? }.values.find {|v| !v.nil?}.id.table_prefix.split('_').first
977
+ base_name = get_base_name(transformations)
818
978
 
819
979
  unless be_quiet
820
- transformations.sort.each do |transformation, trees|
980
+ transformations.each do |transformation, trees|
821
981
  puts transformation.inspect
822
982
  puts "Applied to #{trees.length} shards:"
823
983
  trees.keys.sort.each {|f| puts " #{f.inspect}" }
@@ -825,11 +985,7 @@ module Gizzard
825
985
  puts ""
826
986
  end
827
987
 
828
- unless global_options.force
829
- print "Continue? (y/n) "; $stdout.flush
830
- exit unless $stdin.gets.chomp == "y"
831
- puts ""
832
- end
988
+ confirm!
833
989
 
834
990
  Gizzard.schedule! manager,
835
991
  base_name,
@@ -838,9 +994,9 @@ module Gizzard
838
994
  end
839
995
  end
840
996
 
841
- class RebalanceCommand < Command
997
+ class RemovePartitionCommand < Command
842
998
  def run
843
- help!("must have an even number of arguments") unless @argv.length % 2 == 0
999
+ require_tables
844
1000
 
845
1001
  scheduler_options = command_options.scheduler_options || {}
846
1002
  manifest = manager.manifest(*global_options.tables)
@@ -850,13 +1006,16 @@ module Gizzard
850
1006
 
851
1007
  scheduler_options[:quiet] = be_quiet
852
1008
 
853
- dest_templates_and_weights = {}
1009
+ puts "Note: All partitions will be weighted evenly." unless be_quiet
854
1010
 
855
- @argv.each_slice(2) do |(weight_s, to_template_s)|
856
- to = ShardTemplate.parse(to_template_s)
857
- weight = weight_s.to_i
1011
+ dest_templates_and_weights = manifest.templates.inject({}) do |h, (template, forwardings)|
1012
+ h[template] = ShardTemplate::DEFAULT_WEIGHT; h
1013
+ end
858
1014
 
859
- dest_templates_and_weights[to] = weight
1015
+ @argv.each do |template_s|
1016
+ t = ShardTemplate.parse(template_s)
1017
+
1018
+ dest_templates_and_weights.delete(t)
860
1019
  end
861
1020
 
862
1021
  transformations = global_options.tables.inject({}) do |all, table|
@@ -871,7 +1030,7 @@ module Gizzard
871
1030
  exit
872
1031
  end
873
1032
 
874
- base_name = transformations.values.first.values.first.id.table_prefix.split('_').first
1033
+ base_name = get_base_name(transformations)
875
1034
 
876
1035
  unless be_quiet
877
1036
  transformations.each do |transformation, trees|
@@ -882,11 +1041,7 @@ module Gizzard
882
1041
  puts ""
883
1042
  end
884
1043
 
885
- unless global_options.force
886
- print "Continue? (y/n) "; $stdout.flush
887
- exit unless $stdin.gets.chomp == "y"
888
- puts ""
889
- end
1044
+ confirm!
890
1045
 
891
1046
  Gizzard.schedule! manager,
892
1047
  base_name,
@@ -895,7 +1050,6 @@ module Gizzard
895
1050
  end
896
1051
  end
897
1052
 
898
-
899
1053
  class CreateTableCommand < Command
900
1054
 
901
1055
  DEFAULT_NUM_SHARDS = 1024
@@ -934,6 +1088,8 @@ module Gizzard
934
1088
 
935
1089
  def run
936
1090
  help!("must have an even number of arguments") unless @argv.length % 2 == 0
1091
+ require_tables
1092
+ require_template_options
937
1093
 
938
1094
  base_name = command_options.base_name || DEFAULT_BASE_NAME
939
1095
  num_shards = (command_options.shards || DEFAULT_NUM_SHARDS).to_i
@@ -979,11 +1135,7 @@ module Gizzard
979
1135
  puts ""
980
1136
  end
981
1137
 
982
- unless global_options.force
983
- print "Continue? (y/n) "; $stdout.flush
984
- exit unless $stdin.gets.chomp == "y"
985
- puts ""
986
- end
1138
+ confirm!
987
1139
 
988
1140
  global_options.tables.each do |table_id|
989
1141
  templates_and_base_ids.each do |template, base_ids|
@@ -37,7 +37,7 @@ module Gizzard
37
37
 
38
38
  REPLICATING_SHARD_TYPES = ["ReplicatingShard", "FailingOverShard"]
39
39
 
40
- INVALID_COPY_TYPES = ["ReadOnlyShard", "WriteOnlyShard", "BlockedShard"]
40
+ INVALID_COPY_TYPES = ["ReadOnlyShard", "BlackHoleShard", "BlockedShard"]
41
41
 
42
42
  SHARD_SUFFIXES = {
43
43
  "FailingOverShard" => 'replicating',
@@ -47,6 +47,14 @@ module Gizzard
47
47
  "BlockedShard" => 'blocked'
48
48
  }
49
49
 
50
+ SHARD_TAGS = {
51
+ "ReplicatingShard" => 'replicating',
52
+ "ReadOnlyShard" => 'read_only',
53
+ "WriteOnlyShard" => 'write_only',
54
+ "BlockedShard" => 'blocked',
55
+ "BlackHoleShard" => 'blackhole'
56
+ }
57
+
50
58
  def id; info.id end
51
59
  def hostname; id.hostname end
52
60
  def table_prefix; id.table_prefix end
@@ -118,9 +126,9 @@ module Gizzard
118
126
  end
119
127
  end
120
128
 
121
- def copy_shard(from_shard_id, to_shard_id)
129
+ def copy_shard(*shards)
122
130
  c = random_client
123
- with_retry { c.copy_shard(from_shard_id, to_shard_id) }
131
+ with_retry { c.copy_shard(*shards) }
124
132
  end
125
133
 
126
134
  def repair_shards(*shards)
@@ -175,6 +183,8 @@ module Gizzard
175
183
  times ||= @retries
176
184
  yield
177
185
  rescue Exception => e
186
+ STDERR.puts "\nException: #{e.description rescue "(no description)"}"
187
+ STDERR.puts "Retrying #{times} more time#{'s' if times > 1}..." if times > 0
178
188
  times -= 1
179
189
  (times < 0) ? raise : (sleep 0.1; retry)
180
190
  end
@@ -38,6 +38,10 @@ module Gizzard
38
38
  Shard::SHARD_SUFFIXES[type.split('.').last]
39
39
  end
40
40
 
41
+ def shard_tag
42
+ Shard::SHARD_TAGS[type.split('.').last]
43
+ end
44
+
41
45
  def host
42
46
  if concrete?
43
47
  @host
@@ -135,6 +139,10 @@ module Gizzard
135
139
  false
136
140
  end
137
141
 
142
+ def contains_shard_type?(other)
143
+ descendants.map {|d| d.type }.include? other
144
+ end
145
+
138
146
  def hash
139
147
  weight.hash + host.hash + type.hash + children.hash
140
148
  end
@@ -165,38 +173,113 @@ module Gizzard
165
173
  end
166
174
 
167
175
  def to_config
176
+ if ShardTemplate.options[:simple]
177
+ to_simple_config
178
+ else
179
+ to_complex_config
180
+ end
181
+ end
182
+
183
+ def to_complex_config
168
184
  if children.empty?
169
185
  config_definition
170
186
  else
171
- child_defs = children.map {|c| c.to_config }
187
+ child_defs = children.map {|c| c.to_complex_config }
172
188
  child_defs_s = child_defs.length == 1 ? child_defs.first : "(#{child_defs.join(", ")})"
173
189
  "#{config_definition} -> #{child_defs_s}"
174
190
  end
175
191
  end
176
192
 
193
+ def to_simple_config(tag="")
194
+ if children.empty? && concrete?
195
+ tag += "+#{shard_tag}" if shard_tag
196
+ "#{host}#{tag if !tag.empty?}"
197
+ elsif !children.empty?
198
+ if replicating?
199
+ children.map {|c| c.to_simple_config }.join(", ")
200
+ else
201
+ children.map {|c| c.to_simple_config("#{tag}#{'+' + shard_tag.to_s if shard_tag}")}
202
+ end
203
+ else
204
+ ""
205
+ end
206
+ end
177
207
 
178
208
  # Class Methods
179
209
 
180
210
  class << self
211
+
212
+ def configure(options)
213
+ @@options ||= {}
214
+ @@options.merge!(options)
215
+ end
216
+
217
+ def options
218
+ @@options ||= {}
219
+ @@options[:replicating] ||= "ReplicatingShard"
220
+ #@@options[:source_type] ||= "BIGINT UNSIGNED"
221
+ #@@options[:dest_type] ||= "BIGINT UNSIGNED"
222
+ @@options
223
+ end
224
+
181
225
  def parse(string)
226
+ if options[:simple]
227
+ parse_simple(string)
228
+ else
229
+ parse_complex(string)
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ def parse_simple(definition_s)
236
+ shards = definition_s.split(",")
237
+ templates = []
238
+ shards.each do |s|
239
+ s.strip!
240
+ host, *tags = s.split("+")
241
+ templates << build_nested_template(host, tags)
242
+ end
243
+ ShardTemplate.new(options[:replicating], nil, 1, "", "", templates)
244
+ end
245
+
246
+ def build_nested_template(host, tags)
247
+ if tags.empty?
248
+ return ShardTemplate.new(options[:concrete], host, 1, options[:source_type], options[:dest_type], [])
249
+ end
250
+
251
+ tag = tags.shift
252
+ type = Shard::SHARD_TAGS.invert[tag]
253
+
254
+ if type == "BlackHoleShard" # end at blackhole shards immediately since they're concrete
255
+ return ShardTemplate.new(type, ABSTRACT_HOST, 1, "", "", [])
256
+ end
257
+
258
+ if type
259
+ return ShardTemplate.new(type, nil, 1, "", "", [build_nested_template(host, tags)])
260
+ end
261
+
262
+ []
263
+ end
264
+
265
+
266
+ def parse_complex(string)
182
267
  definition_s, children_s = string.split(/\s*->\s*/, 2)
183
268
 
184
269
  children =
185
270
  if children_s.nil?
186
271
  []
187
272
  else
188
- list = parse_arg_list(children_s).map {|c| parse c }
273
+ list = parse_arg_list(children_s).map {|c| parse_complex c }
189
274
  raise ArgumentError, "invalid shard config. -> given, no children found" if list.empty?
190
275
  list
191
276
  end
192
277
 
193
- template_args = parse_definition(definition_s) << children
278
+ template_args = parse_complex_definition(definition_s) << children
194
279
  ShardTemplate.new(*template_args)
195
280
  end
196
281
 
197
- private
198
-
199
- def parse_definition(definition_s)
282
+ def parse_complex_definition(definition_s)
200
283
  type, arg_list = definition_s.split("(", 2)
201
284
 
202
285
  host, weight, source_type, dest_type =
@@ -183,7 +183,7 @@ module Gizzard
183
183
  thrift_method :list_tables, list(i32), :throws => exception(GizzardException)
184
184
 
185
185
  thrift_method :mark_shard_busy, void, field(:id, struct(ShardId), 1), field(:busy, i32, 2), :throws => exception(GizzardException)
186
- thrift_method :copy_shard, void, field(:source_id, struct(ShardId), 1), field(:destination_id, struct(ShardId), 2), :throws => exception(GizzardException)
186
+ thrift_method :copy_shard, void, field(:shard_ids, list(struct(ShardId)), 1), :throws => exception(GizzardException)
187
187
  thrift_method :repair_shard, void, field(:shard_ids, list(struct(ShardId)), 1), :throws => exception(GizzardException)
188
188
  thrift_method :diff_shards, void, field(:shard_ids, list(struct(ShardId)), 1), :throws => exception(GizzardException)
189
189
 
@@ -37,11 +37,11 @@ module Gizzard
37
37
  Op::DiffShards => 9
38
38
  }
39
39
 
40
- DEFAULT_DEST_WRAPPER = 'WriteOnlyShard'
40
+ DEFAULT_DEST_WRAPPER = 'BlockedShard'
41
41
 
42
- attr_reader :from, :to, :copy_dest_wrapper
42
+ attr_reader :from, :to, :copy_dest_wrapper, :skip_copies
43
43
 
44
- def initialize(from_template, to_template, copy_dest_wrapper = nil)
44
+ def initialize(from_template, to_template, copy_dest_wrapper = nil, skip_copies = false)
45
45
  copy_dest_wrapper ||= DEFAULT_DEST_WRAPPER
46
46
 
47
47
  unless Shard::VIRTUAL_SHARD_TYPES.include? copy_dest_wrapper
@@ -51,6 +51,7 @@ module Gizzard
51
51
  @from = from_template
52
52
  @to = to_template
53
53
  @copy_dest_wrapper = copy_dest_wrapper
54
+ @skip_copies = skip_copies
54
55
 
55
56
  if copies_required? && copy_source.nil?
56
57
  raise ArgumentError, "copy required without a valid copy source"
@@ -87,17 +88,27 @@ module Gizzard
87
88
  end
88
89
 
89
90
  def inspect
91
+ # TODO: Need to limit this to e.g. 10 ops in the list, and show a total
92
+ # count instead of showing the whole thing.
90
93
  op_inspect = operations.inject({}) do |h, (phase, ops)|
91
94
  h.update phase => ops.map {|job| " #{job.inspect}" }.join("\n")
92
95
  end
93
96
 
97
+ # TODO: This seems kind of daft to copy around these long strings.
98
+ # Loop over it once just for display?
94
99
  prepare_inspect = op_inspect[:prepare].empty? ? "" : " PREPARE\n#{op_inspect[:prepare]}\n"
95
100
  copy_inspect = op_inspect[:copy].empty? ? "" : " COPY\n#{op_inspect[:copy]}\n"
96
101
  repair_inspect = op_inspect[:repair].empty? ? "" : " REPAIR\n#{op_inspect[:repair]}\n"
97
102
  diff_inspect = op_inspect[:diff].empty? ? "" : " DIFF\n#{op_inspect[:diff]}\n"
98
103
  cleanup_inspect = op_inspect[:cleanup].empty? ? "" : " CLEANUP\n#{op_inspect[:cleanup]}\n"
99
104
 
100
- op_inspect = [prepare_inspect, copy_inspect, repair_inspect, cleanup_inspect].join
105
+ op_inspect = [
106
+ prepare_inspect,
107
+ copy_inspect,
108
+ repair_inspect,
109
+ diff_inspect,
110
+ cleanup_inspect,
111
+ ].join
101
112
 
102
113
  "#{from.inspect} => #{to.inspect} :\n#{op_inspect}"
103
114
  end
@@ -111,7 +122,6 @@ module Gizzard
111
122
 
112
123
  # compact
113
124
  log = collapse_jobs(log)
114
-
115
125
  @operations = expand_jobs(log)
116
126
 
117
127
  @operations.each do |(phase, jobs)|
@@ -131,7 +141,7 @@ module Gizzard
131
141
 
132
142
  def expand_jobs(jobs)
133
143
  expanded = jobs.inject({:prepare => [], :copy => [], :repair => [], :cleanup => [], :diff => []}) do |ops, job|
134
- job_ops = job.expand(self.copy_source, involved_in_copy?(job.template), @copy_dest_wrapper)
144
+ job_ops = job.expand(self.copy_source, involved_in_copy?(job.template))
135
145
  ops.update(job_ops) {|k,a,b| a + b }
136
146
  end
137
147
 
@@ -146,6 +156,7 @@ module Gizzard
146
156
  end
147
157
 
148
158
  def copies_required?
159
+ return false if skip_copies
149
160
  return @copies_required unless @copies_required.nil?
150
161
 
151
162
  @copies_required = !from.nil? &&
@@ -169,10 +180,19 @@ module Gizzard
169
180
  end
170
181
 
171
182
  def create_tree(root)
172
- jobs = visit_collect(root) do |parent, child|
173
- [Op::CreateShard.new(child), Op::AddLink.new(parent, child)]
183
+ get_wrapper_type = Proc.new { |template, wrapper_type|
184
+ if wrapper_type.nil?
185
+ nil
186
+ elsif template.contains_shard_type? wrapper_type
187
+ nil
188
+ else
189
+ wrapper_type
190
+ end
191
+ }
192
+ jobs = visit_collect(root, get_wrapper_type, @copy_dest_wrapper) do |parent, child, wrapper|
193
+ [Op::CreateShard.new(child, wrapper), Op::AddLink.new(parent, child, wrapper)]
174
194
  end
175
- [Op::CreateShard.new(root)].concat jobs << Op::SetForwarding.new(root)
195
+ [Op::CreateShard.new(root, @copy_dest_wrapper)].concat jobs << Op::SetForwarding.new(root, @copy_dest_wrapper)
176
196
  end
177
197
 
178
198
  def destroy_tree(root)
@@ -184,9 +204,10 @@ module Gizzard
184
204
 
185
205
  private
186
206
 
187
- def visit_collect(parent, &block)
207
+ def visit_collect(parent, pass_down_method=Proc.new{}, pass_down_value=nil, &block)
188
208
  parent.children.inject([]) do |acc, child|
189
- visit_collect(child, &block).concat(acc.concat(block.call(parent, child)))
209
+ pass_down_value = pass_down_method.call(child, pass_down_value)
210
+ visit_collect(child, pass_down_method, pass_down_value, &block).concat(acc.concat(block.call(parent, child, pass_down_value)))
190
211
  end
191
212
  end
192
213
  end
@@ -251,10 +272,12 @@ module Gizzard
251
272
 
252
273
  def copy_descs
253
274
  transformation.operations[:copy].map do |copy|
254
- from_id = copy.from.to_shard_id(@table_prefix, @translations)
255
- to_id = copy.to.to_shard_id(@table_prefix, @translations)
256
- "#{from_id.inspect} -> #{to_id.inspect}"
275
+ desc = copy.shards.inject("") do |d, shard|
276
+ d += shard.to_shard_id(@table_prefix, @translations).inspect + " <-> "
277
+ end
278
+ desc.chomp " <-> "
257
279
  end
280
+
258
281
  end
259
282
 
260
283
  private
@@ -12,7 +12,7 @@ module Gizzard
12
12
  alias == eql?
13
13
 
14
14
  def inspect
15
- templates = (is_a?(LinkOp) ? [from, to] : [template]).map {|t| t.identifier }.join(" -> ")
15
+ templates = (is_a?(LinkOp) ? [from, to] : [*template]).map {|t| t.identifier }.join(" -> ")
16
16
  name = Transformation::OP_NAMES[self.class]
17
17
  "#{name}(#{templates})"
18
18
  end
@@ -29,26 +29,22 @@ module Gizzard
29
29
  class CopyShard < BaseOp
30
30
  BUSY = 1
31
31
 
32
- attr_reader :from, :to
33
- alias template to
32
+ attr_reader :from, :to, :shards
33
+ alias template shards
34
34
 
35
- def initialize(from, to)
36
- @from = from
37
- @to = to
35
+ def initialize(*shards)
36
+ @shards = shards
38
37
  end
39
38
 
40
39
  def expand(*args); { :copy => [self] } end
41
40
 
42
41
  def involved_shards(table_prefix, translations)
43
- [to.to_shard_id(table_prefix, translations)]
42
+ shards.map{|s| s.to_shard_id(table_prefix, translations)}
44
43
  end
45
44
 
46
45
  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)
46
+ involved_shards(table_prefix, translations).each { |sid| nameserver.mark_shard_busy(sid, BUSY) }
47
+ nameserver.copy_shard(involved_shards(table_prefix, translations))
52
48
  end
53
49
  end
54
50
 
@@ -98,9 +94,10 @@ module Gizzard
98
94
  attr_reader :from, :to
99
95
  alias template to
100
96
 
101
- def initialize(from, to)
97
+ def initialize(from, to, wrapper_type=nil)
102
98
  @from = from
103
99
  @to = to
100
+ @wrapper_type = wrapper_type
104
101
  end
105
102
 
106
103
  def inverse?(other)
@@ -113,9 +110,9 @@ module Gizzard
113
110
  end
114
111
 
115
112
  class AddLink < LinkOp
116
- def expand(copy_source, involved_in_copy, wrapper_type)
117
- if involved_in_copy
118
- wrapper = ShardTemplate.new(wrapper_type, to.host, to.weight, '', '', [to])
113
+ def expand(copy_source, involved_in_copy)
114
+ if involved_in_copy && @wrapper_type
115
+ wrapper = ShardTemplate.new(@wrapper_type, to.host, to.weight, '', '', [to])
119
116
  { :prepare => [AddLink.new(from, wrapper)],
120
117
  :cleanup => [self, RemoveLink.new(from, wrapper)] }
121
118
  else
@@ -132,7 +129,7 @@ module Gizzard
132
129
  end
133
130
 
134
131
  class RemoveLink < LinkOp
135
- def expand(copy_source, involved_in_copy, wrapper_type)
132
+ def expand(copy_source, involved_in_copy)
136
133
  { (involved_in_copy ? :cleanup : :prepare) => [self] }
137
134
  end
138
135
 
@@ -147,8 +144,9 @@ module Gizzard
147
144
  class ShardOp < BaseOp
148
145
  attr_reader :template
149
146
 
150
- def initialize(template)
147
+ def initialize(template, wrapper_type=nil)
151
148
  @template = template
149
+ @wrapper_type = wrapper_type
152
150
  end
153
151
 
154
152
  def inverse?(other)
@@ -161,12 +159,15 @@ module Gizzard
161
159
  end
162
160
 
163
161
  class CreateShard < ShardOp
164
- def expand(copy_source, involved_in_copy, wrapper_type)
165
- if involved_in_copy
166
- wrapper = ShardTemplate.new(wrapper_type, template.host, template.weight, '', '', [template])
162
+ def expand(copy_source, involved_in_copy)
163
+ if involved_in_copy && @wrapper_type
164
+ wrapper = ShardTemplate.new(@wrapper_type, template.host, template.weight, '', '', [template])
167
165
  { :prepare => [self, CreateShard.new(wrapper), AddLink.new(wrapper, template)],
168
166
  :cleanup => [RemoveLink.new(wrapper, template), DeleteShard.new(wrapper)],
169
167
  :copy => [CopyShard.new(copy_source, template)] }
168
+ elsif involved_in_copy
169
+ { :prepare => [self],
170
+ :copy => [CopyShard.new(copy_source, template)] }
170
171
  else
171
172
  { :prepare => [self] }
172
173
  end
@@ -178,7 +179,7 @@ module Gizzard
178
179
  end
179
180
 
180
181
  class DeleteShard < ShardOp
181
- def expand(copy_source, involved_in_copy, wrapper_type)
182
+ def expand(copy_source, involved_in_copy)
182
183
  { (involved_in_copy ? :cleanup : :prepare) => [self] }
183
184
  end
184
185
 
@@ -188,9 +189,9 @@ module Gizzard
188
189
  end
189
190
 
190
191
  class SetForwarding < ShardOp
191
- def expand(copy_source, involved_in_copy, wrapper_type)
192
- if involved_in_copy
193
- wrapper = ShardTemplate.new(wrapper_type, nil, 0, '', '', [to])
192
+ def expand(copy_source, involved_in_copy)
193
+ if involved_in_copy && @wrapper_type
194
+ wrapper = ShardTemplate.new(@wrapper_type, nil, 0, '', '', [to])
194
195
  { :prepare => [SetForwarding.new(template, wrapper)],
195
196
  :cleanup => [self] }
196
197
  else
@@ -209,7 +210,7 @@ module Gizzard
209
210
  # XXX: A no-op, but needed for setup/teardown symmetry
210
211
 
211
212
  class RemoveForwarding < ShardOp
212
- def expand(copy_source, involved_in_copy, wrapper_type)
213
+ def expand(copy_source, involved_in_copy)
213
214
  { (involved_in_copy ? :cleanup : :prepare) => [self] }
214
215
  end
215
216
 
@@ -13,7 +13,7 @@ module Gizzard
13
13
  DEFAULT_OPTIONS = {
14
14
  :max_copies => 30,
15
15
  :copies_per_host => 8,
16
- :poll_interval => 10
16
+ :poll_interval => 10,
17
17
  }.freeze
18
18
 
19
19
  def initialize(nameserver, base_name, transformations, options = {})
@@ -25,6 +25,7 @@ module Gizzard
25
25
  @poll_interval = options[:poll_interval]
26
26
  @be_quiet = options[:quiet]
27
27
  @dont_show_progress = options[:no_progress] || @be_quiet
28
+ @batch_finish = options[:batch_finish]
28
29
 
29
30
  @jobs_in_progress = []
30
31
  @jobs_finished = []
@@ -53,10 +54,10 @@ module Gizzard
53
54
 
54
55
  loop do
55
56
  reload_busy_shards
56
-
57
- cleanup_jobs
57
+ cleanup_jobs if !@batch_finish
58
58
  schedule_jobs(max_copies - busy_shards.length)
59
59
 
60
+ cleanup_jobs if @batch_finish && @jobs_pending.empty? && jobs_completed == @jobs_in_progress
60
61
  break if @jobs_pending.empty? && @jobs_in_progress.empty?
61
62
 
62
63
  unless nameserver.dryrun?
@@ -4,33 +4,56 @@ class HelpNeededError < RuntimeError; end
4
4
  require "optparse"
5
5
  require "ostruct"
6
6
  require "gizzard"
7
+ require "shellwords"
7
8
  require "yaml"
8
9
 
9
10
  DOC_STRINGS = {
11
+ "add-host" => "Add a remote cluster host to replicate to. Format: cluster:host:port.",
10
12
  "addforwarding" => "Add a forwarding from a graph_id / base_source_id to a given shard.",
11
13
  "addlink" => "Add a relationship link between two shards.",
14
+ "add-partition" => "Rebalance the cluster by appending new partitions to the current topology.",
15
+ "busy" => "List any shards with a busy flag set.",
16
+ "copy" => "Copy between the given list of shards. Given a set of shards, it will copy and repair to ensure that all shards have the latest data.",
12
17
  "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.",
18
+ "create-table" => "Create tables in an existing cluster.",
19
+ "delete" => "", # TODO: Undocumented
20
+ "deleteforwarding" => "", # TODO: Undocumented
21
+ "diff-shards" => "Log differences between n shards",
13
22
  "drill" => "Show shard trees for replicas of a given structure signature (from 'report').",
14
23
  "dump" => "Show shard trees for given table ids.",
15
24
  "find" => "Show all shards with a given hostname.",
25
+ "finish-migrate" => "", # TODO: Undocumented
16
26
  "finish-replica" => "Remove the write-only barrier in front of a shard that's finished being copied after 'setup-replica'.",
17
27
  "flush" => "Flush error queue for a given priority.",
18
28
  "forwardings" => "Get a list of all forwardings.",
19
29
  "hosts" => "List hosts used in shard names in the forwarding table and replicas.",
20
30
  "info" => "Show id/class/busy for shards.",
21
31
  "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.",
22
- "links" => "List parent & child links for shards.",
32
+ "links" => "List parent and child links for shards.",
33
+ "list-hosts" => "List remote cluster hosts being replicated to.",
23
34
  "lookup" => "Lookup the shard id that holds the record for a given table / source_id.",
24
- "markbusy" => "Mark a shard as busy.",
35
+ "markbusy" => "Mark a list of shards as busy.",
36
+ "markunbusy" => "Mark a list of shards as not busy.",
25
37
  "pair" => "Report the replica pairing structure for a list of hosts.",
38
+ "rebalance" => "Restructure and move shards to reflect a new list of tree structures.",
26
39
  "reload" => "Instruct application servers to reload the nameserver state.",
27
- "repair-shards" => "Reconcile n shards by detecting differences and rescheduling them",
28
- "diff-shards" => "Log differences between n shards",
40
+ "remove-host" => "Remove a remote cluster host being replicate to.",
41
+ "remove-partition" => "Rebalance the cluster by removing the provided partitions from the current topology.",
42
+ "repair-tables" => "Reconcile all the shards in the given tables (supplied with -T) by detecting differences and writing them back to shards as needed.",
29
43
  "report" => "Show each unique replica structure for a given list of shards. Usually this shard list comes from << gizzmo forwardings | awk '{ print $3 }' >>.",
44
+ "setup-migrate" => "", # TODO: Undocumented
30
45
  "setup-replica" => "Add a replica to be parallel to an existing replica, in write-only mode, ready to be copied to.",
46
+ "subtree" => "Show the subtree of replicas given a shard id.",
47
+ "tables" => "List the table IDs known by this nameserver.",
48
+ "topology" => "List the full topologies known for the table IDs provided.",
49
+ "transform" => "Transform from one topology to another.",
50
+ "transform-tree" => "Transforms given forwardings to the corresponding given tree structure.",
51
+ "unlink" => "Remove a link from one shard to another.",
52
+ "unwrap" => "Remove a wrapper created with wrap.",
31
53
  "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
54
  }
33
55
 
56
+
34
57
  ORIGINAL_ARGV = ARGV.dup
35
58
  zero = File.basename($0)
36
59
 
@@ -46,11 +69,12 @@ subcommand_options = OpenStruct.new
46
69
  # Leftover arguments
47
70
  argv = nil
48
71
 
72
+
49
73
  GIZZMO_VERSION = File.read(File.dirname(__FILE__) + "/../VERSION") rescue "unable to read version file"
50
74
 
51
75
  begin
52
76
  YAML.load_file(File.join(ENV["HOME"], ".gizzmorc")).each do |k, v|
53
- global_options.send("#{k}=", v)
77
+ #global_options.send("#{k}=", v)
54
78
  end
55
79
  rescue Errno::ENOENT
56
80
  # Do nothing...
@@ -86,6 +110,13 @@ def load_config(options, filename)
86
110
  YAML.load(File.open(filename)).each do |k, v|
87
111
  k = "hosts" if k == "host"
88
112
  v = v.split(",").map {|h| h.strip } if k == "hosts"
113
+ if k == "template_options"
114
+ opts = {}
115
+ v.each do |k1, v1|
116
+ opts[k1.to_sym] = v1
117
+ end
118
+ v = opts
119
+ end
89
120
  options.send("#{k}=", v)
90
121
  end
91
122
  end
@@ -100,12 +131,36 @@ def add_scheduler_opts(subcommand_options, opts)
100
131
  opts.on("--poll-interval=SECONDS", "Sleep SECONDS between polling for copy status") do |c|
101
132
  (subcommand_options.scheduler_options ||= {})[:poll_interval] = c.to_i
102
133
  end
103
- opts.on("--copy-wrapper=TYPE", "Wrap copy destination shards with TYPE. default WriteOnlyShard") do |t|
134
+ opts.on("--copy-wrapper=SHARD_TYPE", "Wrap copy destination shards with SHARD_TYPE. default BlockedShard") do |t|
104
135
  (subcommand_options.scheduler_options ||= {})[:copy_wrapper] = t
105
136
  end
137
+ opts.on("--skip-copies", "Do transformation without copying. WARNING: This is VERY DANGEROUS if you don't know what you're doing!") do
138
+ (subcommand_options.scheduler_options ||= {})[:skip_copies] = true
139
+ end
106
140
  opts.on("--no-progress", "Do not show progress bar at bottom.") do
107
141
  (subcommand_options.scheduler_options ||= {})[:no_progress] = true
108
142
  end
143
+ opts.on("--batch-finish", "Wait until all copies are complete before cleaning up unneeded links and shards") do
144
+ (subcommand_options.scheduler_options ||= {})[:batch_finish] = true
145
+ end
146
+ end
147
+
148
+ def add_template_opts(subcommand_options, opts)
149
+ opts.on("--virtual=SHARD_TYPE", "Concrete shards will exist behind a virtual shard of this SHARD_TYPE (default ReplicatingShard)") do |t|
150
+ (subcommand_options.template_options ||= {})[:replicating] = t
151
+ end
152
+
153
+ opts.on("-c", "--concrete=SHARD_TYPE", "Concrete shards will be this SHARD_TYPE (REQUIRED when using --simple)") do |t|
154
+ (subcommand_options.template_options ||= {})[:concrete] = t
155
+ end
156
+
157
+ opts.on("--source-type=DATA_TYPE", "The data type for the source column. (REQUIRED when using --simple)") do |t|
158
+ (subcommand_options.template_options ||= {})[:source_type] = t
159
+ end
160
+
161
+ opts.on("--dest-type=DATA_TYPE", "The data type for the destination column. (REQUIRED when using --simple)") do |t|
162
+ (subcommand_options.template_options ||= {})[:dest_type] = t
163
+ end
109
164
  end
110
165
 
111
166
  subcommands = {
@@ -245,20 +300,15 @@ subcommands = {
245
300
  end
246
301
  end,
247
302
  'copy' => OptionParser.new do |opts|
248
- opts.banner = "Usage: #{zero} copy SOURCE_SHARD_ID DESTINATION_SHARD_ID"
303
+ opts.banner = "Usage: #{zero} copy SHARD_IDS..."
249
304
  separators(opts, DOC_STRINGS["copy"])
250
305
  end,
251
- 'repair-shards' => OptionParser.new do |opts|
252
- opts.banner = "Usage: #{zero} repair-shards SHARD_IDS..."
253
- separators(opts, DOC_STRINGS["repair-shards"])
254
- end,
255
- 'diff-shards' => OptionParser.new do |opts|
256
- opts.banner = "Usage: #{zero} diff-shards SHARD_IDS..."
257
- separators(opts, DOC_STRINGS["diff-shards"])
258
- end,
259
- 'diff-shards' => OptionParser.new do |opts|
260
- opts.banner = "Usage: #{zero} diff-shards SOURCE_SHARD_ID DESTINATION_SHARD_ID"
261
- separators(opts, DOC_STRINGS["diff-shards"])
306
+ 'repair-tables' => OptionParser.new do |opts|
307
+ opts.banner = "Usage: #{zero} -T TABLE,... repair-tables [options]"
308
+ separators(opts, DOC_STRINGS["repair-tables"])
309
+ opts.on("--max-copies=COUNT", "Limit max simultaneous copies to COUNT.") do |c|
310
+ subcommand_options.num_copies = c.to_i
311
+ end
262
312
  end,
263
313
  'busy' => OptionParser.new do |opts|
264
314
  opts.banner = "Usage: #{zero} busy"
@@ -317,10 +367,11 @@ subcommands = {
317
367
  end
318
368
  end,
319
369
  'transform-tree' => OptionParser.new do |opts|
320
- opts.banner = "Usage: #{zero} transform-tree [options] TEMPLATE ROOT_SHARD_ID"
370
+ opts.banner = "Usage: #{zero} transform-tree [options] TEMPLATE ROOT_SHARD_ID ..."
321
371
  separators(opts, DOC_STRINGS['transform-tree'])
322
372
 
323
373
  add_scheduler_opts subcommand_options, opts
374
+ add_template_opts subcommand_options, opts
324
375
 
325
376
  opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
326
377
  subcommand_options.quiet = true
@@ -331,6 +382,7 @@ subcommands = {
331
382
  separators(opts, DOC_STRINGS['transform'])
332
383
 
333
384
  add_scheduler_opts subcommand_options, opts
385
+ add_template_opts subcommand_options, opts
334
386
 
335
387
  opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
336
388
  subcommand_options.quiet = true
@@ -340,6 +392,27 @@ subcommands = {
340
392
  opts.banner = "Usage: #{zero} rebalance [options] WEIGHT TO_TEMPLATE ..."
341
393
  separators(opts, DOC_STRINGS["rebalance"])
342
394
 
395
+ add_scheduler_opts subcommand_options, opts
396
+ add_template_opts subcommand_options, opts
397
+
398
+ opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
399
+ subcommand_options.quiet = true
400
+ end
401
+ end,
402
+ 'add-partition' => OptionParser.new do |opts|
403
+ opts.banner = "Usage: #{zero} add-partition [options] TEMPLATE ..."
404
+ separators(opts, DOC_STRINGS["add-partition"])
405
+
406
+ add_scheduler_opts subcommand_options, opts
407
+
408
+ opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
409
+ subcommand_options.quiet = true
410
+ end
411
+ end,
412
+ 'remove-partition' => OptionParser.new do |opts|
413
+ opts.banner = "Usage: #{zero} remove-partition [options] TEMPLATE ..."
414
+ separators(opts, DOC_STRINGS["remove-partition"])
415
+
343
416
  add_scheduler_opts subcommand_options, opts
344
417
 
345
418
  opts.on("-q", "--quiet", "Do not display transformation info (only valid with --force)") do
@@ -350,15 +423,17 @@ subcommands = {
350
423
  opts.banner = "Usage: #{zero} create-table [options] WEIGHT TEMPLATE ..."
351
424
  separators(opts, DOC_STRINGS["create-table"])
352
425
 
426
+ add_template_opts subcommand_options, opts
427
+
353
428
  opts.on("--shards=COUNT", "Create COUNT shards for each table.") do |count|
354
429
  subcommand_options.shards = count.to_i
355
430
  end
356
431
 
357
- opts.on("--min-id=NUM", "Set lower bound on the id space to NUM (default min signed long: -1 * 2^63)") do |min_id|
432
+ opts.on("--min-id=NUM", "Set lower bound on the id space to NUM (default 0)") do |min_id|
358
433
  subcommand_options.min_id = min_id.to_i
359
434
  end
360
435
 
361
- opts.on("--max-id=NUM", "Set upper bound on the id space to NUM (default max signed long: 2^63 - 1)") do |max_id|
436
+ opts.on("--max-id=NUM", "Set upper bound on the id space to NUM (default 2^60 - 1)") do |max_id|
362
437
  subcommand_options.max_id = max_id.to_i
363
438
  end
364
439
 
@@ -387,6 +462,8 @@ global = OptionParser.new do |opts|
387
462
  opts.separator "also useful to remember that global options come *before* the subcommand, while"
388
463
  opts.separator "subcommand options come *after* the subcommand."
389
464
  opts.separator ""
465
+ opts.separator "You can find explanations and example usage on the wiki (go/gizzmo)."
466
+ opts.separator ""
390
467
  opts.separator "You may find it useful to create a ~/.gizzmorc file, which is simply YAML"
391
468
  opts.separator "key/value pairs corresponding to options you want by default. A common .gizzmorc"
392
469
  opts.separator "simply contains:"
@@ -408,15 +485,15 @@ global = OptionParser.new do |opts|
408
485
  opts.separator ""
409
486
  opts.separator ""
410
487
  opts.separator "Global options:"
411
- opts.on("-H", "--hosts=HOST[,HOST,...]", "HOSTS of application servers") do |hosts|
488
+ opts.on("-H", "--hosts=HOST[,HOST,...]", "Comma-delimited list of application servers") do |hosts|
412
489
  global_options.hosts = hosts.split(",").map {|h| h.strip }
413
490
  end
414
491
 
415
- opts.on("-P", "--port=PORT", "PORT of remote manager service. default 7920") do |port|
492
+ opts.on("-P", "--port=PORT", "PORT of remote manager service (default 7920)") do |port|
416
493
  global_options.port = port.to_i
417
494
  end
418
495
 
419
- opts.on("-I", "--injector=PORT", "PORT of remote job injector service. default 7921") do |port|
496
+ opts.on("-I", "--injector=PORT", "PORT of remote job injector service (default 7921)") do |port|
420
497
  global_options.injector_port = port.to_i
421
498
  end
422
499
 
@@ -424,7 +501,7 @@ global = OptionParser.new do |opts|
424
501
  global_options.tables = tables.split(",").map {|t| t.to_i }
425
502
  end
426
503
 
427
- opts.on("-F", "--framed", "use the thrift framed transport") do |framed|
504
+ opts.on("-F", "--framed", "Use the thrift framed transport") do |framed|
428
505
  global_options.framed = true
429
506
  end
430
507
 
@@ -448,6 +525,10 @@ global = OptionParser.new do |opts|
448
525
  global_options.dry = true
449
526
  end
450
527
 
528
+ opts.on("-s", "--simple", "Represent shard templates in a simple format") do
529
+ (global_options.template_options ||= {})[:simple] = true #This is a temporary setting until the nameserver design changes match the simpler format
530
+ end
531
+
451
532
  opts.on("-C", "--config=YAML_FILE", "YAML_FILE of option key/values") do |filename|
452
533
  load_config(global_options, filename)
453
534
  end
@@ -459,6 +540,10 @@ global = OptionParser.new do |opts|
459
540
  opts.on("-f", "--force", "Don't display confirmation dialogs") do |force|
460
541
  global_options.force = force
461
542
  end
543
+
544
+ opts.on("--argv=FILE", "Put the contents of FILE onto the command line") do |f|
545
+ ARGV.push *Shellwords.shellwords(File.read(f))
546
+ end
462
547
 
463
548
  opts.on_tail("-v", "--version", "Show version") do
464
549
  puts GIZZMO_VERSION
@@ -472,7 +557,6 @@ if ARGV.length == 0
472
557
  exit 1
473
558
  end
474
559
 
475
- # This
476
560
  def process_nested_parsers(global, subcommands)
477
561
  begin
478
562
  global.order!(ARGV) do |subcommand_name|
@@ -528,6 +612,7 @@ end
528
612
 
529
613
  begin
530
614
  custom_timeout(global_options.timeout) do
615
+ Gizzard::ShardTemplate.configure((global_options.template_options || {}).merge(subcommand_options.template_options || {}))
531
616
  Gizzard::Command.run(subcommand_name, global_options, argv, subcommand_options, log)
532
617
  end
533
618
  rescue HelpNeededError => e
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gizzmo
3
3
  version: !ruby/object:Gem::Version
4
- hash: 45
4
+ hash: 43
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 12
9
- - 1
10
- version: 0.12.1
8
+ - 13
9
+ - 0
10
+ version: 0.13.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kyle Maxwell
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-20 00:00:00 -07:00
18
+ date: 2012-01-10 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies: []
21
21