droonga-engine 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -0
  3. data/bin/droonga-engine-absorb-data +82 -0
  4. data/bin/droonga-engine-catalog-generate +16 -13
  5. data/bin/droonga-engine-catalog-modify +108 -0
  6. data/bin/droonga-engine-join +115 -0
  7. data/bin/droonga-engine-unjoin +90 -0
  8. data/doc/text/news.md +8 -0
  9. data/droonga-engine.gemspec +2 -1
  10. data/lib/droonga/buffered_tcp_socket.rb +132 -0
  11. data/lib/droonga/catalog_generator.rb +87 -4
  12. data/lib/droonga/command/droonga_engine.rb +27 -7
  13. data/lib/droonga/command/droonga_engine_service.rb +3 -2
  14. data/lib/droonga/command/serf_event_handler.rb +211 -14
  15. data/lib/droonga/data_absorber.rb +55 -0
  16. data/lib/droonga/dispatcher.rb +25 -11
  17. data/lib/droonga/engine/version.rb +1 -1
  18. data/lib/droonga/engine.rb +24 -24
  19. data/lib/droonga/engine_state.rb +23 -0
  20. data/lib/droonga/{catalog_observer.rb → file_observer.rb} +12 -7
  21. data/lib/droonga/fluent_message_sender.rb +24 -37
  22. data/lib/droonga/forwarder.rb +30 -5
  23. data/lib/droonga/handler_messenger.rb +3 -2
  24. data/lib/droonga/handler_runner.rb +29 -16
  25. data/lib/droonga/job_pusher.rb +12 -0
  26. data/lib/droonga/line_buffer.rb +42 -0
  27. data/lib/droonga/logger.rb +10 -6
  28. data/lib/droonga/path.rb +16 -0
  29. data/lib/droonga/plugins/search/distributed_search_planner.rb +1 -1
  30. data/lib/droonga/plugins/system.rb +50 -0
  31. data/lib/droonga/processor.rb +9 -4
  32. data/lib/droonga/safe_file_writer.rb +39 -0
  33. data/lib/droonga/serf.rb +212 -14
  34. data/lib/droonga/test/stub_handler_messenger.rb +3 -0
  35. data/lib/droonga/worker.rb +6 -1
  36. data/test/command/config/default/catalog.json +1 -1
  37. data/test/command/config/version1/catalog.json +2 -2
  38. data/test/command/suite/system/status.expected +12 -0
  39. data/test/command/suite/system/status.test +5 -0
  40. data/test/unit/plugins/system/test_status.rb +79 -0
  41. data/test/unit/test_catalog_generator.rb +1 -1
  42. data/test/unit/test_line_buffer.rb +62 -0
  43. metadata +46 -12
  44. data/lib/droonga/live_nodes_list_observer.rb +0 -72
@@ -20,10 +20,23 @@ module Droonga
20
20
  DEFAULT_DATASET = "Default"
21
21
  DEFAULT_HOSTS = ["127.0.0.1"]
22
22
  DEFAULT_N_WORKERS = 4
23
- DEFAULT_PLUGINS = ["groonga", "search", "crud", "dump"]
23
+ DEFAULT_N_SLICES = 1
24
+ DEFAULT_PLUGINS = ["groonga", "search", "crud", "dump", "system"]
24
25
  DEFAULT_PORT = 10031
25
26
  DEFAULT_TAG = "droonga"
26
27
 
28
+ attr_reader :datasets
29
+
30
+ class << self
31
+ def generate(datasets_params)
32
+ generator = new
33
+ datasets_params.each do |name, params|
34
+ generator.add_dataset(name, params)
35
+ end
36
+ generator.generate
37
+ end
38
+ end
39
+
27
40
  def initialize
28
41
  @version = 2
29
42
  @effective_date = Time.now
@@ -42,6 +55,44 @@ module Droonga
42
55
  }
43
56
  end
44
57
 
58
+ def load(catalog)
59
+ catalog["datasets"].each do |name, dataset|
60
+ add_dataset(name, dataset_to_params(dataset))
61
+ end
62
+ self
63
+ end
64
+
65
+ def dataset_for_host(host)
66
+ @datasets.each do |name, dataset|
67
+ if dataset.replicas.hosts.include?(host)
68
+ return dataset
69
+ end
70
+ end
71
+ nil
72
+ end
73
+
74
+ def modify(dataset_modifications)
75
+ dataset_modifications.each do |name, modification|
76
+ dataset = @datasets[name]
77
+ next unless dataset
78
+
79
+ replicas = dataset.replicas
80
+
81
+ if modification[:replica_hosts]
82
+ replicas.hosts = modification[:replica_hosts]
83
+ end
84
+
85
+ if modification[:add_replica_hosts]
86
+ replicas.hosts += modification[:add_replica_hosts]
87
+ replicas.hosts.uniq!
88
+ end
89
+
90
+ if modification[:remove_replica_hosts]
91
+ replicas.hosts -= modification[:remove_replica_hosts]
92
+ end
93
+ end
94
+ end
95
+
45
96
  private
46
97
  def catalog_datasets
47
98
  catalog_datasets = {}
@@ -52,6 +103,8 @@ module Droonga
52
103
  end
53
104
 
54
105
  class Dataset
106
+ attr_reader :name
107
+
55
108
  def initialize(name, options)
56
109
  @name = name
57
110
  @options = options
@@ -75,7 +128,7 @@ module Droonga
75
128
 
76
129
  def replicas
77
130
  return @options[:replicas] if @options[:replicas]
78
- @generated_replicas ||= Replicas.new(@options).to_json
131
+ @generated_replicas ||= Replicas.new(@options)
79
132
  end
80
133
 
81
134
  def to_catalog
@@ -83,7 +136,7 @@ module Droonga
83
136
  "nWorkers" => n_workers,
84
137
  "plugins" => plugins,
85
138
  "schema" => schema,
86
- "replicas" => replicas,
139
+ "replicas" => replicas.to_json,
87
140
  }
88
141
  catalog["fact"] = fact if fact
89
142
  catalog
@@ -93,6 +146,9 @@ module Droonga
93
146
  end
94
147
 
95
148
  class Replicas
149
+ attr_accessor :hosts
150
+ attr_reader :port, :tag, :n_slices
151
+
96
152
  def initialize(options={})
97
153
  @hosts = options[:hosts] || DEFAULT_HOSTS
98
154
  @port = options[:port]
@@ -122,7 +178,7 @@ module Droonga
122
178
  @host = host
123
179
  @port = options[:port] || DEFAULT_PORT
124
180
  @tag = options[:tag] || DEFAULT_TAG
125
- @n_slices = options[:n_slices] || 1
181
+ @n_slices = options[:n_slices] || DEFAULT_N_SLICES
126
182
 
127
183
  @n_volumes = 0
128
184
  end
@@ -159,5 +215,32 @@ module Droonga
159
215
  @weight ||= 100 / @n_slices
160
216
  end
161
217
  end
218
+
219
+ ADDRESS_MATCHER = /\A(.*):(\d+)\/([^\.]+)\.(.+)\z/
220
+
221
+ def dataset_to_params(dataset)
222
+ params = {}
223
+ params[:n_workers] = dataset["nWorkers"]
224
+ params[:n_slices] = dataset["replicas"].first["slices"].size
225
+ params[:plugins] = dataset["plugins"]
226
+ params[:schema] = dataset["schema"] if dataset["schema"]
227
+ params[:fact] = dataset["fact"] if dataset["fact"]
228
+
229
+ nodes = dataset["replicas"].collect do |replica|
230
+ ADDRESS_MATCHER =~ replica["slices"].first["volume"]["address"]
231
+ {
232
+ :host => $1,
233
+ :port => $2.to_i,
234
+ :tag => $3,
235
+ :path => $4,
236
+ }
237
+ end
238
+ params[:tag] = nodes.first[:tag]
239
+ params[:port] = nodes.first[:port].to_i
240
+ params[:hosts] = nodes.collect do |node|
241
+ node[:host]
242
+ end
243
+ params
244
+ end
162
245
  end
163
246
  end
@@ -19,11 +19,13 @@ require "ipaddr"
19
19
  require "fileutils"
20
20
 
21
21
  require "coolio"
22
+ require "sigdump"
22
23
 
23
24
  require "droonga/path"
24
25
  require "droonga/serf"
25
- require "droonga/catalog_observer"
26
+ require "droonga/file_observer"
26
27
  require "droonga/service_control_protocol"
28
+ require "droonga/line_buffer"
27
29
 
28
30
  module Droonga
29
31
  module Command
@@ -130,7 +132,7 @@ module Droonga
130
132
  end
131
133
 
132
134
  def log_level
133
- ENV["DROONGA_LOG_LEVEL"] || Logger::Level.default_label
135
+ ENV["DROONGA_LOG_LEVEL"] || Logger::Level.default
134
136
  end
135
137
 
136
138
  def daemon?
@@ -235,6 +237,7 @@ module Droonga
235
237
 
236
238
  def run
237
239
  @serf = run_serf
240
+ @serf_status_observer = run_serf_status_observer
238
241
  @service_runner = run_service
239
242
  @catalog_observer = run_catalog_observer
240
243
  @loop_breaker = Coolio::AsyncWatcher.new
@@ -267,12 +270,16 @@ module Droonga
267
270
  trap(:HUP) do
268
271
  restart_immediately
269
272
  end
273
+ trap(:USR2) do
274
+ Sigdump.dump
275
+ end
270
276
  end
271
277
 
272
278
  def stop_gracefully
273
279
  @loop_breaker.signal
274
280
  @loop_breaker.detach
275
281
  @serf.shutdown
282
+ @serf_status_observer.stop
276
283
  @catalog_observer.stop
277
284
  @service_runner.stop_gracefully
278
285
  end
@@ -281,6 +288,7 @@ module Droonga
281
288
  @loop_breaker.signal
282
289
  @loop_breaker.detach
283
290
  @serf.shutdown
291
+ @serf_status_observer.stop
284
292
  @catalog_observer.stop
285
293
  @service_runner.stop_immediately
286
294
  end
@@ -290,7 +298,6 @@ module Droonga
290
298
  old_service_runner = @service_runner
291
299
  @service_runner = run_service
292
300
  @service_runner.on_ready = lambda do
293
- @serf.restart if @serf.running?
294
301
  @service_runner.on_failure = nil
295
302
  old_service_runner.stop_gracefully
296
303
  end
@@ -304,7 +311,6 @@ module Droonga
304
311
  @loop_breaker.signal
305
312
  old_service_runner = @service_runner
306
313
  @service_runner = run_service
307
- @serf.restart if @serf.running?
308
314
  old_service_runner.stop_immediately
309
315
  end
310
316
 
@@ -320,8 +326,22 @@ module Droonga
320
326
  serf
321
327
  end
322
328
 
329
+ def restart_serf
330
+ @serf.shutdown if @serf
331
+ @serf = run_serf
332
+ end
333
+
334
+ def run_serf_status_observer
335
+ serf_status_observer = FileObserver.new(@loop, Serf.status_file)
336
+ serf_status_observer.on_change = lambda do
337
+ restart_serf
338
+ end
339
+ serf_status_observer.start
340
+ serf_status_observer
341
+ end
342
+
323
343
  def run_catalog_observer
324
- catalog_observer = CatalogObserver.new(@loop)
344
+ catalog_observer = FileObserver.new(@loop, Path.catalog)
325
345
  catalog_observer.on_change = lambda do
326
346
  restart_graceful
327
347
  end
@@ -414,9 +434,9 @@ module Droonga
414
434
 
415
435
  def attach_control_read_in(control_read_in)
416
436
  @control_read_in = Coolio::IO.new(control_read_in)
437
+ line_buffer = LineBuffer.new
417
438
  on_read = lambda do |data|
418
- # TODO: should buffer data to handle half line received case
419
- data.each_line do |line|
439
+ line_buffer.feed(data) do |line|
420
440
  case line
421
441
  when Messages::READY
422
442
  on_ready
@@ -18,6 +18,7 @@ require "optparse"
18
18
  require "coolio"
19
19
 
20
20
  require "droonga/service_control_protocol"
21
+ require "droonga/line_buffer"
21
22
  require "droonga/engine"
22
23
  require "droonga/fluent_message_receiver"
23
24
  require "droonga/internal_fluent_message_receiver"
@@ -162,8 +163,8 @@ module Droonga
162
163
  @control_read = Coolio::IO.new(IO.new(@control_read_fd))
163
164
  @control_read_fd = nil
164
165
  on_read = lambda do |data|
165
- # TODO: should buffer data to handle half line received case
166
- data.each_line do |line|
166
+ line_buffer = LineBuffer.new
167
+ line_buffer.feed(data) do |line|
167
168
  case line
168
169
  when Messages::STOP_GRACEFUL
169
170
  stop_gracefully
@@ -13,15 +13,13 @@
13
13
  # License along with this library; if not, write to the Free Software
14
14
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
15
 
16
- require "optparse"
17
- require "pathname"
18
16
  require "json"
19
- require "fileutils"
20
- require "tempfile"
21
17
 
22
18
  require "droonga/path"
23
19
  require "droonga/serf"
24
- require "droonga/live_nodes_list_observer"
20
+ require "droonga/catalog_generator"
21
+ require "droonga/data_absorber"
22
+ require "droonga/safe_file_writer"
25
23
 
26
24
  module Droonga
27
25
  module Command
@@ -35,11 +33,14 @@ module Droonga
35
33
  def initialize
36
34
  @serf = ENV["SERF"] || Serf.path
37
35
  @serf_rpc_address = ENV["SERF_RPC_ADDRESS"] || "127.0.0.1:7373"
36
+ @serf_name = ENV["SERF_SELF_NAME"]
38
37
  end
39
38
 
40
39
  def run
41
40
  parse_event
41
+ return true unless event_for_me?
42
42
 
43
+ process_event
43
44
  output_live_nodes
44
45
  true
45
46
  end
@@ -47,14 +48,209 @@ module Droonga
47
48
  private
48
49
  def parse_event
49
50
  @event_name = ENV["SERF_EVENT"]
51
+ @payload = nil
50
52
  case @event_name
51
53
  when "user"
52
- @event_name += ":#{ENV["SERF_USER_EVENT"]}"
54
+ @event_sub_name = ENV["SERF_USER_EVENT"]
55
+ @payload = JSON.parse($stdin.gets)
56
+ puts "event sub name = #{@event_sub_name}"
53
57
  when "query"
54
- @event_name += ":#{ENV["SERF_USER_QUERY"]}"
58
+ @event_sub_name = ENV["SERF_QUERY_NAME"]
59
+ @payload = JSON.parse($stdin.gets)
60
+ puts "event sub name = #{@event_sub_name}"
55
61
  end
56
62
  end
57
63
 
64
+ def event_for_me?
65
+ return true unless @payload
66
+ return true unless @payload["node"]
67
+
68
+ @payload["node"] == @serf_name
69
+ end
70
+
71
+ def process_event
72
+ case @event_sub_name
73
+ when "change_role"
74
+ save_status(:role, @payload["role"])
75
+ when "join"
76
+ join
77
+ when "set_replicas"
78
+ set_replicas
79
+ when "add_replicas"
80
+ add_replicas
81
+ when "remove_replicas"
82
+ remove_replicas
83
+ when "absorb_data"
84
+ absorb_data
85
+ end
86
+ end
87
+
88
+ def host
89
+ @serf_name.split(":").first
90
+ end
91
+
92
+ def given_hosts
93
+ hosts = @payload["hosts"]
94
+ return nil unless hosts
95
+ hosts = [hosts] if hosts.is_a?(String)
96
+ hosts
97
+ end
98
+
99
+ def join
100
+ type = @payload["type"]
101
+ puts "type = #{type}"
102
+ case type
103
+ when "replica"
104
+ join_as_replica
105
+ end
106
+ end
107
+
108
+ def join_as_replica
109
+ source = @payload["source"]
110
+ return unless source
111
+
112
+ puts "source = #{source}"
113
+
114
+ generator = create_current_catalog_generator
115
+ dataset = generator.dataset_for_host(source) ||
116
+ generator.dataset_for_host(host)
117
+ return unless dataset
118
+
119
+ dataset_name = dataset.name
120
+ tag = dataset.replicas.tag
121
+ port = dataset.replicas.port
122
+ other_hosts = dataset.replicas.hosts
123
+
124
+ puts "dataset = #{dataset_name}"
125
+ puts "port = #{port}"
126
+ puts "tag = #{tag}"
127
+
128
+ if @payload["copy"]
129
+ puts "starting to copy data from #{source}"
130
+
131
+ modify_catalog do |modifier|
132
+ modifier.datasets[dataset_name].replicas.hosts = [host]
133
+ end
134
+ sleep(1) # wait for restart
135
+
136
+ DataAbsorber.absorb(:dataset => dataset_name,
137
+ :source_host => source,
138
+ :destination_host => host,
139
+ :port => port,
140
+ :tag => tag)
141
+ sleep(1)
142
+ end
143
+
144
+ puts "joining to the cluster: update myself"
145
+
146
+ modify_catalog do |modifier|
147
+ modifier.datasets[dataset_name].replicas.hosts += other_hosts
148
+ modifier.datasets[dataset_name].replicas.hosts.uniq!
149
+ end
150
+ sleep(1) # wait for restart
151
+
152
+ puts "joining to the cluster: update others"
153
+
154
+ source_node = "#{source}:#{port}/#{tag}"
155
+ Serf.send_query(source_node, "add_replicas",
156
+ "dataset" => dataset_name,
157
+ "hosts" => [host])
158
+ end
159
+
160
+ def set_replicas
161
+ dataset = @payload["dataset"]
162
+ return unless dataset
163
+
164
+ hosts = given_hosts
165
+ return unless hosts
166
+
167
+ puts "new replicas: #{hosts.join(",")}"
168
+
169
+ modify_catalog do |modifier|
170
+ modifier.datasets[dataset].replicas.hosts = hosts
171
+ end
172
+ end
173
+
174
+ def add_replicas
175
+ dataset = @payload["dataset"]
176
+ return unless dataset
177
+
178
+ hosts = given_hosts
179
+ return unless hosts
180
+
181
+ hosts -= [host]
182
+ return if hosts.empty?
183
+
184
+ puts "adding replicas: #{hosts.join(",")}"
185
+
186
+ modify_catalog do |modifier|
187
+ modifier.datasets[dataset].replicas.hosts += hosts
188
+ modifier.datasets[dataset].replicas.hosts.uniq!
189
+ end
190
+ end
191
+
192
+ def remove_replicas
193
+ dataset = @payload["dataset"]
194
+ return unless dataset
195
+
196
+ hosts = given_hosts
197
+ return unless hosts
198
+
199
+ puts "removing replicas: #{hosts.join(",")}"
200
+
201
+ modify_catalog do |modifier|
202
+ modifier.datasets[dataset].replicas.hosts -= hosts
203
+ end
204
+ end
205
+
206
+ def modify_catalog
207
+ generator = create_current_catalog_generator
208
+ yield(generator)
209
+ SafeFileWriter.write(Path.catalog, JSON.pretty_generate(generator.generate))
210
+ end
211
+
212
+ def create_current_catalog_generator
213
+ current_catalog = JSON.parse(Path.catalog.read)
214
+ generator = CatalogGenerator.new
215
+ generator.load(current_catalog)
216
+ end
217
+
218
+ def absorb_data
219
+ return unless event_for_me?
220
+
221
+ source = @payload["source"]
222
+ return unless source
223
+
224
+ puts "start to absorb data from #{source}"
225
+
226
+ dataset_name = @payload["dataset"]
227
+ port = @payload["port"]
228
+ tag = @payload["tag"]
229
+
230
+ if dataset_name.nil? or port.nil? or tag.nil?
231
+ current_catalog = JSON.parse(Path.catalog.read)
232
+ generator = CatalogGenerator.new
233
+ generator.load(current_catalog)
234
+
235
+ dataset = generator.dataset_for_host(source)
236
+ return unless dataset
237
+
238
+ dataset_name = dataset.name
239
+ port = dataset.replicas.port
240
+ tag = dataset.replicas.tag
241
+ end
242
+
243
+ puts "dataset = #{dataset_name}"
244
+ puts "port = #{port}"
245
+ puts "tag = #{tag}"
246
+
247
+ DataAbsorber.absorb(:dataset => dataset_name,
248
+ :source_host => source,
249
+ :destination_host => host,
250
+ :port => port,
251
+ :tag => tag)
252
+ end
253
+
58
254
  def live_nodes
59
255
  nodes = {}
60
256
  members = `#{@serf} members -rpc-addr #{@serf_rpc_address}`
@@ -70,15 +266,16 @@ module Droonga
70
266
  end
71
267
 
72
268
  def output_live_nodes
73
- list_path = LiveNodesListObserver.path
269
+ path = Path.live_nodes
74
270
  nodes = live_nodes
75
271
  file_contents = JSON.pretty_generate(nodes)
76
- # Don't output the file directly to prevent loading of incomplete file!
77
- Tempfile.open(list_path.basename.to_s, list_path.parent.to_s, "w") do |output|
78
- output.write(file_contents)
79
- output.flush
80
- File.rename(output.path, list_path.to_s)
81
- end
272
+ SafeFileWriter.write(path, file_contents)
273
+ end
274
+
275
+ def save_status(key, value)
276
+ status = Serf.load_status
277
+ status[key] = value
278
+ SafeFileWriter.write(Serf.status_file, JSON.pretty_generate(status))
82
279
  end
83
280
  end
84
281
  end
@@ -0,0 +1,55 @@
1
+ # Copyright (C) 2014 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ require "open3"
17
+
18
+ module Droonga
19
+ class DataAbsorber
20
+ class << self
21
+ def absorb(params)
22
+ drndump = params[:drndump] || "drndump"
23
+ drndump_options = []
24
+ drndump_options += ["--host", params[:source_host]] if params[:source_host]
25
+ drndump_options += ["--port", params[:port].to_s] if params[:port]
26
+ drndump_options += ["--tag", params[:tag]] if params[:tag]
27
+ drndump_options += ["--dataset", params[:dataset]] if params[:dataset]
28
+ drndump_options += ["--receiver-host", params[:destination_host]]
29
+ drndump_options += ["--receiver-port", params[:receiver_port].to_s] if params[:receiver_port]
30
+
31
+ client = params[:client] || "droonga-request"
32
+ client_options = []
33
+ client_options += ["--host", params[:destination_host]]
34
+ client_options += ["--port", params[:port].to_s] if params[:port]
35
+ client_options += ["--tag", params[:tag]] if params[:tag]
36
+ client_options += ["--receiver-host", params[:destination_host]]
37
+ client_options += ["--receiver-port", params[:receiver_port].to_s] if params[:receiver_port]
38
+
39
+ drndump_command_line = [drndump] + drndump_options
40
+ client_command_line = [client] + client_options
41
+
42
+ Open3.popen3(*drndump_command_line) do |dump_in, dump_out, dump_error, dump_thread|
43
+ dump_in.close
44
+ Open3.popen3(*client_command_line) do |client_in, client_out, client_error, client_thread|
45
+ client_out.close
46
+ dump_out.each do |dump|
47
+ yield dump if block_given?
48
+ client_in.puts(dump)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -46,19 +46,20 @@ module Droonga
46
46
  end
47
47
  end
48
48
 
49
- attr_accessor :live_nodes
49
+ attr_reader :engine_state
50
50
 
51
51
  def initialize(engine_state, catalog)
52
52
  @engine_state = engine_state
53
+ @forwarder = @engine_state.forwarder
54
+ @replier = @engine_state.replier
53
55
  @catalog = catalog
54
- @live_nodes = catalog.all_nodes
55
56
  @adapter_runners = create_adapter_runners
56
57
  @farm = Farm.new(@engine_state.name, @catalog, @engine_state.loop,
57
- :dispatcher => self)
58
+ :engine_state => @engine_state,
59
+ :dispatcher => self,
60
+ :forwarder => @forwarder)
58
61
  @collector_runners = create_collector_runners
59
62
  @step_runners = create_step_runners
60
- @forwarder = @engine_state.forwarder
61
- @replier = @engine_state.replier
62
63
  end
63
64
 
64
65
  def start
@@ -169,22 +170,23 @@ module Droonga
169
170
 
170
171
  def dispatch_steps(steps)
171
172
  id = @engine_state.generate_id
172
- destinations = {}
173
+
174
+ destinations = []
173
175
  steps.each do |step|
174
176
  dataset = @catalog.dataset(step["dataset"])
175
177
  if dataset
176
- routes = dataset.get_routes(step, @live_nodes)
178
+ routes = dataset.get_routes(step, @engine_state.live_nodes)
177
179
  step["routes"] = routes
178
180
  else
179
181
  step["routes"] ||= [id]
180
182
  end
181
- routes = step["routes"]
182
- routes.each do |route|
183
- destinations[farm_path(route)] = true
183
+ destinations += step["routes"].collect do |route|
184
+ farm_path(route)
184
185
  end
185
186
  end
187
+
186
188
  dispatch_message = { "id" => id, "steps" => steps }
187
- destinations.each_key do |destination|
189
+ destinations.uniq.each do |destination|
188
190
  dispatch(dispatch_message, destination)
189
191
  end
190
192
  end
@@ -210,6 +212,18 @@ module Droonga
210
212
  @engine_state.local_route?(route)
211
213
  end
212
214
 
215
+ def write_step?(step)
216
+ return false unless step["dataset"]
217
+
218
+ step_runner = @step_runners[step["dataset"]]
219
+ return false unless step_runner
220
+
221
+ step_definition = step_runner.find(step["command"])
222
+ return false unless step_definition
223
+
224
+ step_definition.write?
225
+ end
226
+
213
227
  private
214
228
  def farm_path(route)
215
229
  @engine_state.farm_path(route)
@@ -15,6 +15,6 @@
15
15
 
16
16
  module Droonga
17
17
  class Engine
18
- VERSION = "1.0.3"
18
+ VERSION = "1.0.4"
19
19
  end
20
20
  end