gizzmo 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,177 +1,229 @@
1
- #!/usr/bin/env ruby
2
1
  require 'vendor/thrift_client/simple'
3
2
 
4
3
  module Gizzard
5
- module Thrift
6
- T = ThriftClient::Simple
4
+ T = ThriftClient::Simple
7
5
 
8
- def self.struct(*args)
9
- T::StructType.new(*args)
6
+ def self.struct(*args)
7
+ T::StructType.new(*args)
8
+ end
9
+
10
+ def self.list(*args)
11
+ T::ListType.new(*args)
12
+ end
13
+
14
+ GizzardException = T.make_exception(:GizzardException,
15
+ T::Field.new(:description, T::STRING, 1)
16
+ )
17
+
18
+ ShardId = T.make_struct(:ShardId,
19
+ T::Field.new(:hostname, T::STRING, 1),
20
+ T::Field.new(:table_prefix, T::STRING, 2)
21
+ )
22
+
23
+ class ShardId
24
+ def inspect
25
+ "#{hostname}/#{table_prefix}"
10
26
  end
11
27
 
12
- ShardException = T.make_exception(:ShardException,
13
- T::Field.new(:description, T::STRING, 1)
14
- )
28
+ def <=>(o)
29
+ self.hostname <=> o.hostname
30
+ end
15
31
 
16
- ShardId = T.make_struct(:ShardId,
17
- T::Field.new(:hostname, T::STRING, 1),
18
- T::Field.new(:table_prefix, T::STRING, 2)
19
- )
32
+ alias_method :to_unix, :inspect
20
33
 
21
- class ShardId
22
- def inspect
23
- "#{hostname}/#{table_prefix}"
24
- end
34
+ def self.parse(string)
35
+ new(*string.match("(.*)/(.*)").values_at(1, 2))
36
+ end
37
+ end
25
38
 
26
- def <=>(o)
27
- self.hostname <=> o.hostname
28
- end
39
+ ShardInfo = T.make_struct(:ShardInfo,
40
+ T::Field.new(:id, struct(ShardId), 1),
41
+ T::Field.new(:class_name, T::STRING, 2),
42
+ T::Field.new(:source_type, T::STRING, 3),
43
+ T::Field.new(:destination_type, T::STRING, 4),
44
+ T::Field.new(:busy, T::I32, 5)
45
+ )
46
+
47
+ class ShardInfo
48
+ def busy?
49
+ busy && busy > 0
50
+ end
29
51
 
30
- alias_method :to_unix, :inspect
52
+ def inspect(short = false)
53
+ "#{id.inspect}" + (busy? ? " (BUSY)" : "")
54
+ end
31
55
 
32
- def self.parse(string)
33
- new(*string.split("/"))
34
- end
56
+ def to_unix
57
+ [id.to_unix, class_name, busy? ? "busy" : "ok"].join("\t")
35
58
  end
59
+ end
36
60
 
37
- ShardInfo = T.make_struct(:ShardInfo,
38
- T::Field.new(:id, struct(ShardId), 1),
39
- T::Field.new(:class_name, T::STRING, 2),
40
- T::Field.new(:source_type, T::STRING, 3),
41
- T::Field.new(:destination_type, T::STRING, 4),
42
- T::Field.new(:busy, T::I32, 5)
43
- )
44
-
45
- class ShardInfo
46
- def busy?
47
- busy && busy > 0
48
- end
61
+ LinkInfo = T.make_struct(:LinkInfo,
62
+ T::Field.new(:up_id, struct(ShardId), 1),
63
+ T::Field.new(:down_id, struct(ShardId), 2),
64
+ T::Field.new(:weight, T::I32, 3)
65
+ )
49
66
 
50
- def inspect(short = false)
51
- "#{id.inspect}" + (busy? ? " (BUSY)" : "")
52
- end
67
+ class LinkInfo
68
+ def inspect
69
+ "#{up_id.inspect} -> #{down_id.inspect}" + (weight == 1 ? "" : " <#{weight}>")
70
+ end
53
71
 
54
- def to_unix
55
- [id.to_unix, class_name, busy? ? "busy" : "ok"].join("\t")
56
- end
72
+ def to_unix
73
+ [up_id.to_unix, down_id.to_unix, weight].join("\t")
57
74
  end
75
+ end
58
76
 
59
- LinkInfo = T.make_struct(:LinkInfo,
60
- T::Field.new(:up_id, struct(ShardId), 1),
61
- T::Field.new(:down_id, struct(ShardId), 2),
62
- T::Field.new(:weight, T::I32, 3)
63
- )
77
+ Forwarding = T.make_struct(:Forwarding,
78
+ T::Field.new(:table_id, T::I32, 1),
79
+ T::Field.new(:base_id, T::I64, 2),
80
+ T::Field.new(:shard_id, struct(ShardId), 3)
81
+ )
64
82
 
65
- class LinkInfo
66
- def inspect
67
- "#{up_id.inspect} -> #{down_id.inspect}" + (weight == 1 ? "" : " <#{weight}>")
68
- end
83
+ class Forwarding
84
+ def <=>(o)
85
+ [self.table_id, self.base_id] <=> [o.table_id, o.base_id]
86
+ end
69
87
 
70
- def to_unix
71
- [up_id.to_unix, down_id.to_unix, weight].join("\t")
72
- end
88
+ def inspect
89
+ "[#{table_id}] #{base_id.to_s(16)} = #{shard_id.inspect}"
73
90
  end
91
+ end
74
92
 
75
- ShardMigration = T.make_struct(:ShardMigration,
76
- T::Field.new(:source_id, struct(ShardId), 1),
77
- T::Field.new(:destination_id, struct(ShardId), 2)
78
- )
79
-
80
- Forwarding = T.make_struct(:Forwarding,
81
- T::Field.new(:table_id, T::I32, 1),
82
- T::Field.new(:base_id, T::I64, 2),
83
- T::Field.new(:shard_id, struct(ShardId), 3)
84
- )
85
-
86
- class Forwarding
87
- #FIXME table_id is not human-readable
88
- def inspect
89
- "[#{table_id}] #{base_id.to_s(16)} -> #{shard_id.inspect}"
90
- end
93
+ NameServerState = T.make_struct(:NameserverState,
94
+ T::Field.new(:shards, list(struct(ShardInfo)), 1),
95
+ T::Field.new(:links, list(struct(LinkInfo)), 2),
96
+ T::Field.new(:forwardings, list(struct(Forwarding)), 3),
97
+ T::Field.new(:table_id, T::I32, 4)
98
+ )
99
+
100
+
101
+ module HostStatus
102
+ Normal = 0
103
+ Offline = 1
104
+ Blocked = 2
105
+ end
106
+
107
+ Host = T.make_struct(:Host,
108
+ T::Field.new(:hostname, T::STRING, 1),
109
+ T::Field.new(:port, T::I32, 2),
110
+ T::Field.new(:cluster, T::STRING, 3),
111
+ T::Field.new(:status, T::I32, 4)
112
+ )
113
+
114
+ class Host
115
+ def inspect
116
+ "(#{hostname}:#{port} - #{cluster} (#{status})"
91
117
  end
118
+ end
92
119
 
93
- class GizzmoService < T::ThriftService
94
- def initialize(host, port, log_path, framed, dry_run = false)
95
- super(host, port, framed)
96
- @dry = dry_run
97
- begin
98
- @log = File.open(log_path, "a")
99
- rescue
100
- STDERR.puts "Error opening log file at #{log_path}. Continuing..."
101
- end
120
+ class GizzmoService < T::ThriftService
121
+ def initialize(host, port, log_path, framed, dry_run = false)
122
+ super(host, port, framed)
123
+ @dry = dry_run
124
+ begin
125
+ @log = File.open(log_path, "a")
126
+ rescue
127
+ STDERR.puts "Error opening log file at #{log_path}. Continuing..."
102
128
  end
129
+ end
103
130
 
104
- def _proxy(method_name, *args)
105
- cls = self.class.ancestors.find { |cls| cls.respond_to?(:_arg_structs) and cls._arg_structs[method_name.to_sym] }
106
- arg_class, rv_class = cls._arg_structs[method_name.to_sym]
107
-
108
- # Writing methods return void. Methods should never both read and write. If this assumption
109
- # is violated in the future, dry-run will fail!!
110
- is_writing_method = rv_class._fields.first.type == ThriftClient::Simple::VOID
111
- if @dry && is_writing_method
112
- puts "Skipped writing: #{printable(method_name, args)}"
113
- else
114
- @log.puts printable(method_name, args, true)
115
- super(method_name, *args)
116
- end
117
- rescue ThriftClient::Simple::ThriftException
118
- if @dry
119
- puts "Skipped reading: #{printable(method_name, args)}"
120
- else
121
- raise
122
- end
131
+ def _proxy(method_name, *args)
132
+ cls = self.class.ancestors.find { |cls| cls.respond_to?(:_arg_structs) and cls._arg_structs[method_name.to_sym] }
133
+ arg_class, rv_class = cls._arg_structs[method_name.to_sym]
134
+
135
+ # Writing methods return void. Methods should never both read and write. If this assumption
136
+ # is violated in the future, dry-run will fail!!
137
+ is_writing_method = rv_class._fields.first.type == ThriftClient::Simple::VOID
138
+ if @dry && is_writing_method
139
+ puts "Skipped writing: #{printable(method_name, args)}"
140
+ else
141
+ @log.puts printable(method_name, args, true)
142
+ super(method_name, *args)
123
143
  end
144
+ end
124
145
 
125
- def printable(method_name, args, timestamp = false)
126
- ts = timestamp ? "#{Time.now}\t" : ""
127
- "#{ts}#{method_name}(#{args.map{|a| a.inspect}.join(', ')})"
128
- end
146
+ def printable(method_name, args, timestamp = false)
147
+ ts = timestamp ? "#{Time.now}\t" : ""
148
+ "#{ts}#{method_name}(#{args.map{|a| a.inspect}.join(', ')})"
129
149
  end
150
+ end
130
151
 
131
- class ShardManager < GizzmoService
132
- thrift_method :create_shard, void, field(:shard, struct(ShardInfo), 1), :throws => exception(ShardException)
133
- thrift_method :delete_shard, void, field(:id, struct(ShardId), 1), :throws => exception(ShardException)
134
- thrift_method :get_shard, struct(ShardInfo), field(:id, struct(ShardId), 1), :throws => exception(ShardException)
152
+ class Manager < GizzmoService
153
+ thrift_method :reload_config, void, :throws => exception(GizzardException)
154
+ thrift_method :rebuild_schema, void, :throws => exception(GizzardException)
135
155
 
136
- thrift_method :add_link, void, field(:up_id, struct(ShardId), 1), field(:down_id, struct(ShardId), 2), field(:weight, i32, 3), :throws => exception(ShardException)
137
- thrift_method :remove_link, void, field(:up_id, struct(ShardId), 1), field(:down_id, struct(ShardId), 2), :throws => exception(ShardException)
156
+ thrift_method :find_current_forwarding, struct(ShardInfo), field(:table_id, i32, 1), field(:id, i64, 2), :throws => exception(GizzardException)
138
157
 
139
- thrift_method :list_upward_links, list(struct(LinkInfo)), field(:id, struct(ShardId), 1), :throws => exception(ShardException)
140
- thrift_method :list_downward_links, list(struct(LinkInfo)), field(:id, struct(ShardId), 1), :throws => exception(ShardException)
141
158
 
142
- thrift_method :get_child_shards_of_class, list(struct(ShardInfo)), field(:parent_id, struct(ShardId), 1), field(:class_name, string, 2), :throws => exception(ShardException)
159
+ # Shard Tree Management
143
160
 
144
- thrift_method :mark_shard_busy, void, field(:id, struct(ShardId), 1), field(:busy, i32, 2), :throws => exception(ShardException)
145
- thrift_method :copy_shard, void, field(:source_id, struct(ShardId), 1), field(:destination_id, struct(ShardId), 2), :throws => exception(ShardException)
161
+ thrift_method :create_shard, void, field(:shard, struct(ShardInfo), 1), :throws => exception(GizzardException)
162
+ thrift_method :delete_shard, void, field(:id, struct(ShardId), 1), :throws => exception(GizzardException)
146
163
 
147
- thrift_method :set_forwarding, void, field(:forwarding, struct(Forwarding), 1), :throws => exception(ShardException)
148
- thrift_method :replace_forwarding, void, field(:old_id, struct(ShardId), 1), field(:new_id, struct(ShardId), 2), :throws => exception(ShardException)
149
- thrift_method :remove_forwarding, void, field(:forwarding, struct(Forwarding), 1), :throws => exception(ShardException)
164
+ thrift_method :add_link, void, field(:up_id, struct(ShardId), 1), field(:down_id, struct(ShardId), 2), field(:weight, i32, 3), :throws => exception(GizzardException)
165
+ thrift_method :remove_link, void, field(:up_id, struct(ShardId), 1), field(:down_id, struct(ShardId), 2), :throws => exception(GizzardException)
150
166
 
151
- thrift_method :get_forwarding, struct(Forwarding), field(:table_id, i32, 1), field(:base_id, i64, 2), :throws => exception(ShardException)
152
- thrift_method :get_forwarding_for_shard, struct(Forwarding), field(:shard_id, struct(ShardId), 1), :throws => exception(ShardException)
167
+ thrift_method :set_forwarding, void, field(:forwarding, struct(Forwarding), 1), :throws => exception(GizzardException)
168
+ thrift_method :replace_forwarding, void, field(:old_id, struct(ShardId), 1), field(:new_id, struct(ShardId), 2), :throws => exception(GizzardException)
169
+ thrift_method :remove_forwarding, void, field(:forwarding, struct(Forwarding), 1), :throws => exception(GizzardException)
153
170
 
154
- thrift_method :get_forwardings, list(struct(Forwarding)), :throws => exception(ShardException)
155
- thrift_method :reload_forwardings, void, :throws => exception(ShardException)
171
+ thrift_method :get_shard, struct(ShardInfo), field(:id, struct(ShardId), 1), :throws => exception(GizzardException)
172
+ thrift_method :shards_for_hostname, list(struct(ShardInfo)), field(:hostname, string, 1), :throws => exception(GizzardException)
173
+ thrift_method :get_busy_shards, list(struct(ShardInfo)), :throws => exception(GizzardException)
156
174
 
157
- thrift_method :find_current_forwarding, struct(ShardInfo), field(:table_id, i32, 1), field(:id, i64, 2), :throws => exception(ShardException)
175
+ thrift_method :list_upward_links, list(struct(LinkInfo)), field(:id, struct(ShardId), 1), :throws => exception(GizzardException)
176
+ thrift_method :list_downward_links, list(struct(LinkInfo)), field(:id, struct(ShardId), 1), :throws => exception(GizzardException)
177
+ thrift_method :get_forwarding, struct(Forwarding), field(:table_id, i32, 1), field(:base_id, i64, 2), :throws => exception(GizzardException)
178
+ thrift_method :get_forwarding_for_shard, struct(Forwarding), field(:shard_id, struct(ShardId), 1), :throws => exception(GizzardException)
179
+ thrift_method :get_forwardings, list(struct(Forwarding)), :throws => exception(GizzardException)
158
180
 
159
- thrift_method :shards_for_hostname, list(struct(ShardInfo)), field(:hostname, string, 1), :throws => exception(ShardException)
160
- thrift_method :get_busy_shards, list(struct(ShardInfo)), :throws => exception(ShardException)
161
- thrift_method :list_hostnames, list(string), :throws => exception(ShardException)
181
+ thrift_method :list_hostnames, list(string), :throws => exception(GizzardException)
162
182
 
163
- thrift_method :rebuild_schema, void, :throws => exception(ShardException)
164
- end
183
+ thrift_method :mark_shard_busy, void, field(:id, struct(ShardId), 1), field(:busy, i32, 2), :throws => exception(GizzardException)
184
+ thrift_method :copy_shard, void, field(:source_id, struct(ShardId), 1), field(:destination_id, struct(ShardId), 2), :throws => exception(GizzardException)
165
185
 
166
- class JobManager < GizzmoService
167
- thrift_method :retry_errors, void
168
- thrift_method :stop_writes, void
169
- thrift_method :resume_writes, void
170
- thrift_method :retry_errors_for, void, field(:priority, i32, 1)
171
- thrift_method :stop_writes_for, void, field(:priority, i32, 1)
172
- thrift_method :resume_writes_for, void, field(:priority, i32, 1)
173
- thrift_method :is_writing, bool, field(:priority, i32, 1)
174
- thrift_method :inject_job, void, field(:priority, i32, 1), field(:job, string, 2)
175
- end
186
+ thrift_method :dump_nameserver, struct(NameServerState), field(:table_id, i32, 1), :throws => exception(GizzardException)
187
+
188
+
189
+ # Job Scheduler Management
190
+
191
+ thrift_method :retry_errors, void
192
+ thrift_method :stop_writes, void
193
+ thrift_method :resume_writes, void
194
+
195
+ thrift_method :retry_errors_for, void, field(:priority, i32, 1)
196
+ thrift_method :stop_writes_for, void, field(:priority, i32, 1)
197
+ thrift_method :resume_writes_for, void, field(:priority, i32, 1)
198
+
199
+ thrift_method :is_writing, bool, field(:priority, i32, 1)
200
+
201
+
202
+ # Remote Host Cluster Management
203
+
204
+ thrift_method :add_remote_host, void, field(:host, struct(Host), 1)#, :throws => exception(GizzardException)
205
+ thrift_method :remove_remote_host, void, field(:hostname, string, 1), field(:port, i32, 2), :throws => exception(GizzardException)
206
+ thrift_method :set_remote_host_status, void, field(:hostname, string, 1), field(:port, i32, 2), field(:status, i32, 3), :throws => exception(GizzardException)
207
+ thrift_method :set_remote_cluster_status, void, field(:cluster, string, 1), field(:status, i32, 2), :throws => exception(GizzardException)
208
+
209
+ thrift_method :get_remote_host, struct(Host), field(:hostname, string, 1), field(:port, i32, 2), :throws => exception(GizzardException)
210
+ thrift_method :list_remote_clusters, list(string), :throws => exception(GizzardException)
211
+ thrift_method :list_remote_hosts, list(struct(Host)), :throws => exception(GizzardException)
212
+ thrift_method :list_remote_hosts_in_cluster, list(struct(Host)), field(:cluster, string, 1), :throws => exception(GizzardException)
213
+ end
214
+
215
+
216
+
217
+ Job = T.make_struct(:Job,
218
+ T::Field.new(:priority, T::I32, 1),
219
+ T::Field.new(:contents, T::STRING, 2)
220
+ )
221
+
222
+ JobException = T.make_exception(:JobException,
223
+ T::Field.new(:description, T::STRING, 1)
224
+ )
225
+
226
+ class JobInjector < GizzmoService
227
+ thrift_method :inject_jobs, void, field(:priority, list(struct(Job)), 1), :throws => exception(JobException)
176
228
  end
177
229
  end
@@ -0,0 +1,230 @@
1
+ module Gizzard
2
+ class Transformation
3
+ require 'gizzard/transformation_op'
4
+ require 'gizzard/transformation_scheduler'
5
+
6
+ OP_NAMES = {
7
+ Op::RemoveForwarding => "remove_forwarding",
8
+ Op::RemoveLink => "remove_link",
9
+ Op::DeleteShard => "delete_shard",
10
+ Op::CreateShard => "create_shard",
11
+ Op::AddLink => "add_link",
12
+ Op::SetForwarding => "set_forwarding",
13
+ Op::CopyShard => "copy_shard"
14
+ }
15
+
16
+ OP_INVERSES = {
17
+ Op::AddLink => Op::RemoveLink,
18
+ Op::CreateShard => Op::DeleteShard,
19
+ Op::SetForwarding => Op::RemoveForwarding
20
+ }
21
+
22
+ OP_INVERSES.keys.each {|k| v = OP_INVERSES[k]; OP_INVERSES[v] = k }
23
+
24
+ OP_PRIORITIES = {
25
+ Op::CreateShard => 1,
26
+ Op::AddLink => 2,
27
+ Op::SetForwarding => 3,
28
+ Op::RemoveForwarding => 4,
29
+ Op::RemoveLink => 5,
30
+ Op::DeleteShard => 6,
31
+ Op::CopyShard => 7
32
+ }
33
+
34
+ DEFAULT_DEST_WRAPPER = 'WriteOnlyShard'
35
+
36
+ attr_reader :from, :to
37
+
38
+ def initialize(from_template, to_template, copy_dest_wrapper = nil)
39
+ copy_dest_wrapper ||= DEFAULT_DEST_WRAPPER
40
+
41
+ unless Shard::VIRTUAL_SHARD_TYPES.include? copy_dest_wrapper
42
+ raise ArgumentError, "#{copy_dest_wrapper} is not a valid virtual shard type."
43
+ end
44
+
45
+ @from = from_template
46
+ @to = to_template
47
+ @copy_dest_wrapper = copy_dest_wrapper
48
+
49
+ if copies_required? && copy_source.nil?
50
+ raise ArgumentError, "copy required without a valid copy source"
51
+ end
52
+ end
53
+
54
+ def bind(base_name, forwardings_to_shards)
55
+ raise ArgumentError unless forwardings_to_shards.is_a? Hash
56
+
57
+ forwardings_to_shards.map do |forwarding, shard|
58
+ BoundTransformation.new(self, base_name, forwarding, shard)
59
+ end
60
+ end
61
+
62
+ def inspect
63
+ op_inspect = operations.inject({}) do |h, (phase, ops)|
64
+ h.update phase => ops.map {|job| " #{job.inspect}" }.join("\n")
65
+ end
66
+
67
+ prepare_inspect = op_inspect[:prepare].empty? ? "" : " PREPARE\n#{op_inspect[:prepare]}\n"
68
+ copy_inspect = op_inspect[:copy].empty? ? "" : " COPY\n#{op_inspect[:copy]}\n"
69
+ cleanup_inspect = op_inspect[:cleanup].empty? ? "" : " CLEANUP\n#{op_inspect[:cleanup]}\n"
70
+
71
+ op_inspect = [prepare_inspect, copy_inspect, cleanup_inspect].join
72
+
73
+ "#{from.inspect} => #{to.inspect} :\n#{op_inspect}"
74
+ end
75
+
76
+ def operations
77
+ return @operations if @operations
78
+
79
+ log = []
80
+ log.concat destroy_tree(from) if from
81
+ log.concat create_tree(to) if to
82
+
83
+ # compact
84
+ log = collapse_jobs(log)
85
+
86
+ @operations = expand_jobs(log)
87
+
88
+ @operations.each do |(phase, jobs)|
89
+ jobs.sort!
90
+ end
91
+
92
+ @operations
93
+ end
94
+
95
+ def collapse_jobs(jobs)
96
+ jobs.reject do |job1|
97
+ jobs.find do |job2|
98
+ job1.inverse? job2
99
+ end
100
+ end
101
+ end
102
+
103
+ def expand_jobs(jobs)
104
+ expanded = jobs.inject({:prepare => [], :copy => [], :cleanup => []}) do |ops, job|
105
+ job_ops = job.expand(self.copy_source, involved_in_copy?(job.template), @copy_dest_wrapper)
106
+ ops.update(job_ops) {|k,a,b| a + b }
107
+ end
108
+
109
+ # if there are no copies that need to take place, we can do all
110
+ # nameserver changes in one step
111
+ if expanded[:copy].empty?
112
+ expanded[:prepare].concat expanded[:cleanup]
113
+ expanded[:cleanup] = []
114
+ end
115
+
116
+ expanded
117
+ end
118
+
119
+ def copies_required?
120
+ return @copies_required unless @copies_required.nil?
121
+ @copies_required = !from.nil? &&
122
+ to.concrete_descendants.reject {|d| from.shared_host? d }.length > 0
123
+ end
124
+
125
+ def involved_in_copy?(template)
126
+ copy_source?(template) || copy_destination?(template)
127
+ end
128
+
129
+ def copy_destination?(template)
130
+ copies_required? && template.concrete? && !from.shared_host?(template)
131
+ end
132
+
133
+ def copy_source?(template)
134
+ copies_required? && !!from.copy_sources.find {|s| s.shard_eql? template }
135
+ end
136
+
137
+ def copy_source
138
+ from.copy_sources.first if copies_required?
139
+ end
140
+
141
+ def create_tree(root)
142
+ jobs = visit_collect(root) do |parent, child|
143
+ [Op::CreateShard.new(child), Op::AddLink.new(parent, child)]
144
+ end
145
+ [Op::CreateShard.new(root)].concat jobs << Op::SetForwarding.new(root)
146
+ end
147
+
148
+ def destroy_tree(root)
149
+ jobs = visit_collect(root) do |parent, child|
150
+ [Op::RemoveLink.new(parent, child), Op::DeleteShard.new(child)]
151
+ end
152
+ [Op::RemoveForwarding.new(root)].concat jobs << Op::DeleteShard.new(root)
153
+ end
154
+
155
+ private
156
+
157
+ def visit_collect(parent, &block)
158
+ parent.children.inject([]) do |acc, child|
159
+ visit_collect(child, &block).concat(acc.concat(block.call(parent, child)))
160
+ end
161
+ end
162
+ end
163
+
164
+
165
+ class BoundTransformation
166
+ attr_reader :transformation, :base_name, :forwarding, :shard
167
+
168
+ def from; transformation.from end
169
+ def to; transformation.to end
170
+
171
+ def initialize(transformation, base_name, forwarding, shard)
172
+ @transformation = transformation
173
+ @base_name = base_name
174
+ @forwarding = forwarding
175
+ @shard = shard
176
+
177
+ @table_id = forwarding.table_id
178
+ @base_id = forwarding.base_id
179
+ @enum = shard.enumeration
180
+ @table_prefix = Shard.canonical_table_prefix(@enum, @table_id, base_name)
181
+ @translations = shard.canonical_shard_id_map(base_name, @table_id, @enum)
182
+ end
183
+
184
+ def prepare!(nameserver)
185
+ apply_ops(nameserver, transformation.operations[:prepare])
186
+ end
187
+
188
+ def copy_required?
189
+ !transformation.operations[:copy].empty?
190
+ end
191
+
192
+ def copy!(nameserver)
193
+ apply_ops(nameserver, transformation.operations[:copy])
194
+ end
195
+
196
+ def cleanup!(nameserver)
197
+ apply_ops(nameserver, transformation.operations[:cleanup])
198
+ end
199
+
200
+ def involved_shards(phase = :copy)
201
+ transformation.operations[phase].map do |op|
202
+ op.involved_shards(@table_prefix, @translations)
203
+ end.flatten.compact.uniq
204
+ end
205
+
206
+ def involved_hosts(phase = :copy)
207
+ involved_shards(phase).map {|s| s.hostname }.uniq
208
+ end
209
+
210
+ def inspect
211
+ "#{@forwarding.inspect}: #{from.inspect} => #{to.inspect}"
212
+ end
213
+
214
+ def copy_descs
215
+ transformation.operations[:copy].map do |copy|
216
+ from_id = copy.from.to_shard_id(@table_prefix, @translations)
217
+ to_id = copy.to.to_shard_id(@table_prefix, @translations)
218
+ "#{from_id.inspect} -> #{to_id.inspect}"
219
+ end
220
+ end
221
+
222
+ private
223
+
224
+ def apply_ops(nameserver, ops)
225
+ ops.each do |op|
226
+ op.apply(nameserver, @table_id, @base_id, @table_prefix, @translations)
227
+ end
228
+ end
229
+ end
230
+ end