gizzmo 0.12.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +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
|
|