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 +1 -1
- data/gizzmo.gemspec +2 -2
- data/lib/gizzard/commands.rb +230 -78
- data/lib/gizzard/nameserver.rb +13 -3
- data/lib/gizzard/shard_template.rb +89 -6
- data/lib/gizzard/thrift.rb +1 -1
- data/lib/gizzard/transformation.rb +37 -14
- data/lib/gizzard/transformation_op.rb +27 -26
- data/lib/gizzard/transformation_scheduler.rb +4 -3
- data/lib/gizzmo.rb +111 -26
- metadata +5 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.13.0
|
data/gizzmo.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{gizzmo}
|
8
|
-
s.version = "0.
|
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{
|
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"]
|
data/lib/gizzard/commands.rb
CHANGED
@@ -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.
|
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
|
557
|
+
class RepairTablesCommand < Command
|
516
558
|
def run
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
manager.
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
844
|
+
transformations,
|
785
845
|
scheduler_options
|
786
846
|
end
|
787
847
|
end
|
788
848
|
|
789
|
-
class
|
790
|
-
def
|
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
|
-
|
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
|
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
|
977
|
+
base_name = get_base_name(transformations)
|
818
978
|
|
819
979
|
unless be_quiet
|
820
|
-
transformations.
|
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
|
-
|
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
|
997
|
+
class RemovePartitionCommand < Command
|
842
998
|
def run
|
843
|
-
|
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
|
-
|
1009
|
+
puts "Note: All partitions will be weighted evenly." unless be_quiet
|
854
1010
|
|
855
|
-
|
856
|
-
|
857
|
-
|
1011
|
+
dest_templates_and_weights = manifest.templates.inject({}) do |h, (template, forwardings)|
|
1012
|
+
h[template] = ShardTemplate::DEFAULT_WEIGHT; h
|
1013
|
+
end
|
858
1014
|
|
859
|
-
|
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
|
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
|
-
|
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
|
-
|
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|
|
data/lib/gizzard/nameserver.rb
CHANGED
@@ -37,7 +37,7 @@ module Gizzard
|
|
37
37
|
|
38
38
|
REPLICATING_SHARD_TYPES = ["ReplicatingShard", "FailingOverShard"]
|
39
39
|
|
40
|
-
INVALID_COPY_TYPES = ["ReadOnlyShard", "
|
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(
|
129
|
+
def copy_shard(*shards)
|
122
130
|
c = random_client
|
123
|
-
with_retry { c.copy_shard(
|
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.
|
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|
|
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 =
|
278
|
+
template_args = parse_complex_definition(definition_s) << children
|
194
279
|
ShardTemplate.new(*template_args)
|
195
280
|
end
|
196
281
|
|
197
|
-
|
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 =
|
data/lib/gizzard/thrift.rb
CHANGED
@@ -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(:
|
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 = '
|
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 = [
|
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)
|
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
|
-
|
173
|
-
|
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
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
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
|
32
|
+
attr_reader :from, :to, :shards
|
33
|
+
alias template shards
|
34
34
|
|
35
|
-
def initialize(
|
36
|
-
@
|
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
|
-
|
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
|
-
|
48
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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?
|
data/lib/gizzmo.rb
CHANGED
@@ -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
|
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
|
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
|
-
"
|
28
|
-
"
|
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=
|
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
|
303
|
+
opts.banner = "Usage: #{zero} copy SHARD_IDS..."
|
249
304
|
separators(opts, DOC_STRINGS["copy"])
|
250
305
|
end,
|
251
|
-
'repair-
|
252
|
-
opts.banner = "Usage: #{zero} repair-
|
253
|
-
separators(opts, DOC_STRINGS["repair-
|
254
|
-
|
255
|
-
|
256
|
-
|
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
|
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
|
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,...]", "
|
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
|
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
|
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", "
|
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:
|
4
|
+
hash: 43
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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:
|
18
|
+
date: 2012-01-10 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|