gizzmo 0.11.0 → 0.11.1

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