gizzmo 0.12.1 → 0.13.0

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.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