droonga-engine 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +7 -0
  5. data/Rakefile +6 -2
  6. data/bin/droonga-engine +2 -2
  7. data/bin/{droonga-catalog-generate → droonga-engine-catalog-generate} +15 -3
  8. data/bin/droonga-engine-serf-event-handler +20 -0
  9. data/bin/droonga-engine-service +2 -2
  10. data/doc/text/news.md +21 -1
  11. data/droonga-engine.gemspec +5 -2
  12. data/lib/droonga/catalog/collection_volume.rb +12 -0
  13. data/lib/droonga/catalog/dataset.rb +25 -0
  14. data/lib/droonga/catalog/single_volume.rb +10 -0
  15. data/lib/droonga/catalog/slice.rb +4 -0
  16. data/lib/droonga/catalog/version1.rb +59 -48
  17. data/lib/droonga/catalog/version2.rb +10 -20
  18. data/lib/droonga/catalog/volume_collection.rb +27 -4
  19. data/lib/droonga/catalog_generator.rb +12 -5
  20. data/lib/droonga/catalog_observer.rb +17 -35
  21. data/lib/droonga/command/droonga_engine.rb +436 -0
  22. data/lib/droonga/command/droonga_engine_service.rb +273 -0
  23. data/lib/droonga/command/serf_event_handler.rb +85 -0
  24. data/lib/droonga/dispatcher.rb +8 -8
  25. data/lib/droonga/engine.rb +90 -26
  26. data/lib/droonga/engine/version.rb +1 -1
  27. data/lib/droonga/engine_state.rb +29 -3
  28. data/lib/droonga/internal_fluent_message_receiver.rb +100 -0
  29. data/lib/droonga/live_nodes_list_loader.rb +48 -0
  30. data/lib/droonga/live_nodes_list_observer.rb +72 -0
  31. data/lib/droonga/path.rb +47 -0
  32. data/lib/droonga/plugins/dump.rb +279 -38
  33. data/lib/droonga/plugins/groonga/select.rb +26 -14
  34. data/lib/droonga/plugins/search.rb +30 -2
  35. data/lib/droonga/plugins/search/distributed_search_planner.rb +28 -11
  36. data/lib/droonga/processor.rb +4 -0
  37. data/lib/droonga/searcher.rb +26 -0
  38. data/lib/droonga/serf.rb +119 -0
  39. data/lib/droonga/serf_downloader.rb +90 -0
  40. data/lib/droonga/server.rb +2 -2
  41. data/lib/droonga/service_control_protocol.rb +26 -0
  42. data/sample/cluster/catalog.json +1 -1
  43. data/test/command/config/default/catalog.json +2 -2
  44. data/test/command/config/version1/catalog.json +1 -1
  45. data/test/command/fixture/documents.jsons +18 -18
  46. data/test/command/fixture/event.jsons +4 -4
  47. data/test/command/fixture/user-table-array.jsons +4 -4
  48. data/test/command/fixture/user-table.jsons +5 -5
  49. data/test/command/suite/add/dimension/column.catalog.json +1 -1
  50. data/test/command/suite/add/dimension/column.test +4 -4
  51. data/test/command/suite/add/dimension/integer.catalog.json +1 -1
  52. data/test/command/suite/add/dimension/integer.test +4 -4
  53. data/test/command/suite/add/error/invalid-integer.test +1 -1
  54. data/test/command/suite/add/error/invalid-time.test +1 -1
  55. data/test/command/suite/add/error/missing-key.test +1 -1
  56. data/test/command/suite/add/error/missing-table.test +1 -1
  57. data/test/command/suite/add/error/unknown-column.test +1 -1
  58. data/test/command/suite/add/error/unknown-table.test +1 -1
  59. data/test/command/suite/add/minimum.test +1 -1
  60. data/test/command/suite/add/vector/short_text.catalog.json +26 -0
  61. data/test/command/suite/add/vector/short_text.expected +42 -0
  62. data/test/command/suite/add/vector/short_text.test +35 -0
  63. data/test/command/suite/add/with-values.test +1 -1
  64. data/test/command/suite/add/without-key.test +1 -1
  65. data/test/command/suite/dump/column/index.catalog.json +40 -0
  66. data/test/command/suite/dump/column/index.expected +195 -0
  67. data/test/command/suite/dump/column/index.test +5 -0
  68. data/test/command/suite/dump/column/scalar.catalog.json +19 -0
  69. data/test/command/suite/dump/column/scalar.expected +99 -0
  70. data/test/command/suite/dump/column/scalar.test +5 -0
  71. data/test/command/suite/dump/column/vector.catalog.json +22 -0
  72. data/test/command/suite/dump/column/vector.expected +108 -0
  73. data/test/command/suite/dump/column/vector.test +5 -0
  74. data/test/command/suite/dump/record/vector/reference.catalog.json +27 -0
  75. data/test/command/suite/dump/record/vector/reference.expected +213 -0
  76. data/test/command/suite/dump/record/vector/reference.test +21 -0
  77. data/test/command/suite/dump/table/array.catalog.json +13 -0
  78. data/test/command/suite/dump/table/array.expected +63 -0
  79. data/test/command/suite/dump/table/array.test +5 -0
  80. data/test/command/suite/dump/table/double_array_trie.catalog.json +14 -0
  81. data/test/command/suite/dump/table/double_array_trie.expected +66 -0
  82. data/test/command/suite/dump/table/double_array_trie.test +5 -0
  83. data/test/command/suite/dump/table/hash.catalog.json +14 -0
  84. data/test/command/suite/dump/table/hash.expected +66 -0
  85. data/test/command/suite/dump/table/hash.test +5 -0
  86. data/test/command/suite/dump/table/patricia_trie.catalog.json +14 -0
  87. data/test/command/suite/dump/table/patricia_trie.expected +66 -0
  88. data/test/command/suite/dump/table/patricia_trie.test +5 -0
  89. data/test/command/suite/groonga/column_create/scalar.test +2 -2
  90. data/test/command/suite/groonga/column_create/unknown-table.test +1 -1
  91. data/test/command/suite/groonga/column_create/vector.test +2 -2
  92. data/test/command/suite/groonga/column_list/success.test +3 -3
  93. data/test/command/suite/groonga/column_list/unknown-table.test +1 -1
  94. data/test/command/suite/groonga/column_remove/success.test +3 -3
  95. data/test/command/suite/groonga/column_remove/unknown-column.test +2 -2
  96. data/test/command/suite/groonga/column_remove/unknown-table.test +1 -1
  97. data/test/command/suite/groonga/column_rename/success.test +3 -3
  98. data/test/command/suite/groonga/column_rename/unknown-column.test +2 -2
  99. data/test/command/suite/groonga/column_rename/unknown-table.test +1 -1
  100. data/test/command/suite/groonga/delete/duplicated-identifiers.test +2 -2
  101. data/test/command/suite/groonga/delete/filter.test +2 -2
  102. data/test/command/suite/groonga/delete/invalid-filter.test +1 -1
  103. data/test/command/suite/groonga/delete/no-identifier.test +2 -2
  104. data/test/command/suite/groonga/delete/success.test +2 -2
  105. data/test/command/suite/groonga/delete/unknown-table.test +1 -1
  106. data/test/command/suite/groonga/select/minimum.expected +24 -1
  107. data/test/command/suite/groonga/select/minimum.test +1 -1
  108. data/test/command/suite/groonga/select/type/time.catalog.json +19 -0
  109. data/test/command/suite/groonga/select/type/time.expected +37 -0
  110. data/test/command/suite/groonga/select/type/time.test +35 -0
  111. data/test/command/suite/groonga/table_create/array.test +1 -1
  112. data/test/command/suite/groonga/table_create/hash.test +1 -1
  113. data/test/command/suite/groonga/table_list/success.test +2 -2
  114. data/test/command/suite/groonga/table_remove/success.test +1 -1
  115. data/test/command/suite/groonga/table_remove/unknown-table.test +1 -1
  116. data/test/command/suite/message/error/unknown-type.expected +1 -1
  117. data/test/command/suite/message/error/unknown-type.test +1 -1
  118. data/test/command/suite/search/adjusters/multiple.catalog.json +1 -1
  119. data/test/command/suite/search/adjusters/multiple.test +3 -3
  120. data/test/command/suite/search/adjusters/one.catalog.json +1 -1
  121. data/test/command/suite/search/adjusters/one.test +3 -3
  122. data/test/command/suite/search/attributes/array.expected +7 -0
  123. data/test/command/suite/search/attributes/array.test +1 -1
  124. data/test/command/suite/search/attributes/hash.expected +18 -0
  125. data/test/command/suite/search/attributes/hash.test +1 -1
  126. data/test/command/suite/search/complex.expected +12 -0
  127. data/test/command/suite/search/complex.test +1 -1
  128. data/test/command/suite/search/condition/nested.catalog.json +37 -0
  129. data/test/command/suite/search/condition/nested.expected +7 -0
  130. data/test/command/suite/search/condition/nested.test +103 -2
  131. data/test/command/suite/search/condition/query.catalog.json +37 -0
  132. data/test/command/suite/search/condition/query.expected +7 -0
  133. data/test/command/suite/search/condition/query.test +103 -2
  134. data/test/command/suite/search/condition/query/nonexistent_column.catalog.json +1 -1
  135. data/test/command/suite/search/condition/query/nonexistent_column.test +2 -2
  136. data/test/command/suite/search/condition/query/syntax_error.catalog.json +1 -1
  137. data/test/command/suite/search/condition/query/syntax_error.test +2 -2
  138. data/test/command/suite/search/condition/script.catalog.json +37 -0
  139. data/test/command/suite/search/condition/script.expected +7 -0
  140. data/test/command/suite/search/condition/script.test +103 -2
  141. data/test/command/suite/search/error/cyclic-source.test +1 -1
  142. data/test/command/suite/search/error/deeply-cyclic-source.test +1 -1
  143. data/test/command/suite/search/error/missing-source-parameter.test +1 -1
  144. data/test/command/suite/search/error/no-query.test +1 -1
  145. data/test/command/suite/search/error/unknown-source.test +1 -1
  146. data/test/command/suite/search/group/count.test +1 -1
  147. data/test/command/suite/search/group/limit.test +1 -1
  148. data/test/command/suite/search/group/string.catalog.json +41 -0
  149. data/test/command/suite/search/group/string.expected +18 -18
  150. data/test/command/suite/search/group/string.test +67 -22
  151. data/test/command/suite/search/group/subrecord/with-sort.catalog.json +1 -1
  152. data/test/command/suite/search/group/subrecord/with-sort.test +5 -5
  153. data/test/command/suite/search/multiple/chained.catalog.json +37 -0
  154. data/test/command/suite/search/multiple/chained.expected +14 -0
  155. data/test/command/suite/search/multiple/chained.test +103 -2
  156. data/test/command/suite/search/multiple/parallel.expected +14 -0
  157. data/test/command/suite/search/multiple/parallel.test +1 -1
  158. data/test/command/suite/search/output/attributes/invalid.catalog.json +1 -1
  159. data/test/command/suite/search/output/attributes/invalid.test +2 -2
  160. data/test/command/suite/search/output/attributes/star.catalog.json +23 -0
  161. data/test/command/suite/search/output/attributes/star.expected +27 -0
  162. data/test/command/suite/search/output/attributes/star.test +32 -0
  163. data/test/command/suite/search/range/only-output.expected +7 -0
  164. data/test/command/suite/search/range/only-output.test +1 -1
  165. data/test/command/suite/search/range/only-sort.expected +7 -0
  166. data/test/command/suite/search/range/only-sort.test +1 -1
  167. data/test/command/suite/search/range/sort-and-output.expected +7 -0
  168. data/test/command/suite/search/range/sort-and-output.test +1 -1
  169. data/test/command/suite/search/range/too-large-output-offset.expected +8 -0
  170. data/test/command/suite/search/range/too-large-output-offset.test +1 -1
  171. data/test/command/suite/search/range/too-large-sort-offset.expected +8 -0
  172. data/test/command/suite/search/range/too-large-sort-offset.test +1 -1
  173. data/test/command/suite/search/response/elapsed_time.catalog.json +1 -1
  174. data/test/command/suite/search/response/elapsed_time.test +2 -2
  175. data/test/command/suite/search/response/records/value/time.expected +12 -0
  176. data/test/command/suite/search/response/records/value/time.test +1 -1
  177. data/test/command/suite/search/simple.expected +12 -0
  178. data/test/command/suite/search/simple.test +1 -1
  179. data/test/command/suite/search/sort/default-offset-limit.expected +7 -0
  180. data/test/command/suite/search/sort/default-offset-limit.test +1 -1
  181. data/test/command/suite/search/sort/invisible-column.expected +7 -0
  182. data/test/command/suite/search/sort/invisible-column.test +1 -1
  183. data/test/unit/catalog/test_collection_volume.rb +16 -0
  184. data/test/unit/catalog/test_dataset.rb +36 -0
  185. data/test/unit/catalog/test_single_volume.rb +9 -0
  186. data/test/unit/catalog/test_slice.rb +11 -0
  187. data/test/unit/catalog/test_version1.rb +7 -12
  188. data/test/unit/catalog/test_version2.rb +7 -0
  189. data/test/unit/catalog/test_volume_collection.rb +28 -0
  190. data/test/unit/fixtures/catalog/version1.json +10 -3
  191. data/test/unit/fixtures/catalog/version2.json +2 -2
  192. data/test/unit/plugins/groonga/select/test_adapter_output.rb +8 -14
  193. data/test/unit/plugins/groonga/test_column_create.rb +5 -5
  194. data/test/unit/plugins/groonga/test_column_remove.rb +2 -2
  195. data/test/unit/plugins/groonga/test_column_rename.rb +2 -2
  196. data/test/unit/plugins/groonga/test_delete.rb +2 -2
  197. data/test/unit/plugins/groonga/test_table_create.rb +9 -9
  198. data/test/unit/plugins/groonga/test_table_remove.rb +1 -1
  199. data/test/unit/test_catalog_generator.rb +1 -1
  200. data/test/unit/test_schema_applier.rb +2 -2
  201. data/test/unit/test_watch_schema.rb +4 -4
  202. metadata +241 -72
  203. data/lib/droonga/engine/command/droonga_engine.rb +0 -441
@@ -0,0 +1,273 @@
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 "optparse"
17
+
18
+ require "coolio"
19
+
20
+ require "droonga/service_control_protocol"
21
+ require "droonga/engine"
22
+ require "droonga/fluent_message_receiver"
23
+ require "droonga/internal_fluent_message_receiver"
24
+ require "droonga/plugin_loader"
25
+
26
+ module Droonga
27
+ module Command
28
+ class DroongaEngineService
29
+ class << self
30
+ def run(command_line_arguments)
31
+ new.run(command_line_arguments)
32
+ end
33
+ end
34
+
35
+ include Loggable
36
+ include ServiceControlProtocol
37
+
38
+ def initialize
39
+ @engine_name = nil
40
+ @listen_fd = nil
41
+ @heartbeat_fd = nil
42
+ @contrtol_read_fd = nil
43
+ @contrtol_write_fd = nil
44
+ @contrtol_write_closed = false
45
+ end
46
+
47
+ def run(command_line_arguments)
48
+ create_new_process_group
49
+
50
+ parse_command_line_arguments!(command_line_arguments)
51
+ PluginLoader.load_all
52
+
53
+ control_write_io = IO.new(@control_write_fd)
54
+ success = true
55
+ begin
56
+ run_services
57
+ rescue
58
+ logger.exception("failed to run services", $!)
59
+ success = false
60
+ ensure
61
+ unless @control_write_closed
62
+ control_write_io.write(Messages::FINISH)
63
+ control_write_io.close
64
+ end
65
+ end
66
+
67
+ success
68
+ end
69
+
70
+ private
71
+ def create_new_process_group
72
+ begin
73
+ Process.setsid
74
+ rescue SystemCallError, NotImplementedError
75
+ end
76
+ end
77
+
78
+ def parse_command_line_arguments!(command_line_arguments)
79
+ parser = OptionParser.new
80
+ add_internal_options(parser)
81
+ parser.parse!(command_line_arguments)
82
+ end
83
+
84
+ def add_internal_options(parser)
85
+ parser.separator("")
86
+ parser.separator("Internal:")
87
+ parser.on("--engine-name=NAME",
88
+ "Use NAME as the name of the engine") do |name|
89
+ @engine_name = name
90
+ end
91
+ parser.on("--listen-fd=FD", Integer,
92
+ "Use FD as the listen file descriptor") do |fd|
93
+ @listen_fd = fd
94
+ end
95
+ parser.on("--heartbeat-fd=FD", Integer,
96
+ "Use FD as the heartbeat file descriptor") do |fd|
97
+ @heartbeat_fd = fd
98
+ end
99
+ parser.on("--control-read-fd=FD", Integer,
100
+ "Use FD to read control messages from the service") do |fd|
101
+ @control_read_fd = fd
102
+ end
103
+ parser.on("--control-write-fd=FD", Integer,
104
+ "Use FD to write control messages from the service") do |fd|
105
+ @control_write_fd = fd
106
+ end
107
+ end
108
+
109
+ def host
110
+ @engine_name.split(":", 2).first
111
+ end
112
+
113
+ def run_services
114
+ @stopping = false
115
+ @engine = nil
116
+ @receiver = nil
117
+ @loop = Coolio::Loop.default
118
+
119
+ run_internal_message_receiver
120
+ run_engine
121
+ run_receiver
122
+ run_control_io
123
+ @loop.run
124
+ end
125
+
126
+ def run_internal_message_receiver
127
+ @internal_message_receiver = create_internal_message_receiver
128
+ host, port = @internal_message_receiver.start
129
+ tag = @engine_name.split("/", 2).last.split(".", 2).first
130
+ @internal_engine_name = "#{host}:#{port}/#{tag}"
131
+ end
132
+
133
+ def create_internal_message_receiver
134
+ InternalFluentMessageReceiver.new(@loop, host) do |tag, time, record|
135
+ on_message(tag, time, record)
136
+ end
137
+ end
138
+
139
+ def shutdown_internal_message_receiver
140
+ return if @internal_message_receiver.nil?
141
+ @internal_message_receiver, receiver = nil, @internal_message_receiver
142
+ receiver.shutdown
143
+ end
144
+
145
+ def run_engine
146
+ @engine = Engine.new(@loop, @engine_name, @internal_engine_name)
147
+ @engine.start
148
+ end
149
+
150
+ def run_receiver
151
+ @receiver = create_receiver
152
+ @receiver.start
153
+ end
154
+
155
+ def shutdown_receiver
156
+ return if @receiver.nil?
157
+ @receiver, receiver = nil, @receiver
158
+ receiver.shutdown
159
+ end
160
+
161
+ def run_control_io
162
+ @control_read = Coolio::IO.new(IO.new(@control_read_fd))
163
+ @control_read_fd = nil
164
+ on_read = lambda do |data|
165
+ # TODO: should buffer data to handle half line received case
166
+ data.each_line do |line|
167
+ case line
168
+ when Messages::STOP_GRACEFUL
169
+ stop_gracefully
170
+ when Messages::STOP_IMMEDIATELY
171
+ stop_immediately
172
+ end
173
+ end
174
+ end
175
+ @control_read.on_read do |data|
176
+ on_read.call(data)
177
+ end
178
+ read_on_close = lambda do
179
+ if @control_read
180
+ @control_read = nil
181
+ stop_immediately
182
+ end
183
+ end
184
+ @control_read.on_close do
185
+ read_on_close.call
186
+ end
187
+ @loop.attach(@control_read)
188
+
189
+ @control_write = Coolio::IO.new(IO.new(@control_write_fd))
190
+ @control_write_fd = nil
191
+ write_on_close = lambda do
192
+ if @control_write
193
+ @control_write = nil
194
+ stop_immediately
195
+ end
196
+ @control_write_closed = true
197
+ end
198
+ @control_write.on_close do
199
+ write_on_close.call
200
+ end
201
+ @loop.attach(@control_write)
202
+
203
+ @control_write.write(Messages::READY)
204
+ end
205
+
206
+ def shutdown_control_io
207
+ if @control_write
208
+ @control_write, control_write = nil, @control_write
209
+ control_write.detach
210
+ end
211
+ if @control_read
212
+ @control_read, control_read = nil, @control_read
213
+ control_read.close
214
+ end
215
+ end
216
+
217
+ def create_receiver
218
+ options = {
219
+ :listen_fd => @listen_fd,
220
+ :heartbeat_fd => @heartbeat_fd,
221
+ }
222
+ FluentMessageReceiver.new(@loop, options) do |tag, time, record|
223
+ on_message(tag, time, record)
224
+ end
225
+ end
226
+
227
+ def on_message(tag, time, record)
228
+ prefix, type, *arguments = tag.split(/\./)
229
+ if type.nil? or type.empty? or type == "message"
230
+ message = record
231
+ else
232
+ message = {
233
+ "type" => type,
234
+ "arguments" => arguments,
235
+ "body" => record
236
+ }
237
+ end
238
+ reply_to = message["replyTo"]
239
+ if reply_to.is_a? String
240
+ message["replyTo"] = {
241
+ "type" => "#{message["type"]}.result",
242
+ "to" => reply_to
243
+ }
244
+ end
245
+
246
+ @engine.process(message)
247
+ end
248
+
249
+ def stop_gracefully
250
+ return if @stopping
251
+ @stopping = true
252
+ shutdown_receiver
253
+ @engine.stop_gracefully do
254
+ shutdown_control_io
255
+ shutdown_internal_message_receiver
256
+ end
257
+ end
258
+
259
+ # It may be called after stop_gracefully.
260
+ def stop_immediately
261
+ shutdown_control_io
262
+ shutdown_receiver if @receiver
263
+ shutdown_internal_message_receiver
264
+ @engine.stop_immediately
265
+ @loop.stop
266
+ end
267
+
268
+ def log_tag
269
+ "droonga-engine-service"
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,85 @@
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 "optparse"
17
+ require "pathname"
18
+ require "json"
19
+ require "fileutils"
20
+ require "tempfile"
21
+
22
+ require "droonga/path"
23
+ require "droonga/serf"
24
+ require "droonga/live_nodes_list_observer"
25
+
26
+ module Droonga
27
+ module Command
28
+ class SerfEventHandler
29
+ class << self
30
+ def run
31
+ new.run
32
+ end
33
+ end
34
+
35
+ def initialize
36
+ @serf = ENV["SERF"] || Serf.path
37
+ @serf_rpc_address = ENV["SERF_RPC_ADDRESS"] || "127.0.0.1:7373"
38
+ end
39
+
40
+ def run
41
+ parse_event
42
+
43
+ output_live_nodes
44
+ true
45
+ end
46
+
47
+ private
48
+ def parse_event
49
+ @event_name = ENV["SERF_EVENT"]
50
+ case @event_name
51
+ when "user"
52
+ @event_name += ":#{ENV["SERF_USER_EVENT"]}"
53
+ when "query"
54
+ @event_name += ":#{ENV["SERF_USER_QUERY"]}"
55
+ end
56
+ end
57
+
58
+ def live_nodes
59
+ nodes = {}
60
+ members = `#{@serf} members -rpc-addr #{@serf_rpc_address}`
61
+ members.each_line do |member|
62
+ name, address, status, = member.strip.split(/\s+/)
63
+ if status == "alive"
64
+ nodes[name] = {
65
+ "serfAddress" => address,
66
+ }
67
+ end
68
+ end
69
+ nodes
70
+ end
71
+
72
+ def output_live_nodes
73
+ list_path = LiveNodesListObserver.path
74
+ nodes = live_nodes
75
+ 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
82
+ end
83
+ end
84
+ end
85
+ end
@@ -13,7 +13,6 @@
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 "English"
17
16
  require "tsort"
18
17
 
19
18
  require "droonga/loggable"
@@ -47,9 +46,12 @@ module Droonga
47
46
  end
48
47
  end
49
48
 
49
+ attr_accessor :live_nodes
50
+
50
51
  def initialize(engine_state, catalog)
51
52
  @engine_state = engine_state
52
53
  @catalog = catalog
54
+ @live_nodes = catalog.all_nodes
53
55
  @adapter_runners = create_adapter_runners
54
56
  @farm = Farm.new(@engine_state.name, @catalog, @engine_state.loop,
55
57
  :dispatcher => self)
@@ -146,6 +148,8 @@ module Droonga
146
148
  session = session_planner.create_session(id, collector_runner)
147
149
  @engine_state.register_session(id, session)
148
150
  else
151
+ logger.error("no steps error: id=#{id}, message=#{message}")
152
+ return
149
153
  #todo: take cases receiving result before its query into account
150
154
  end
151
155
  session.start
@@ -167,9 +171,9 @@ module Droonga
167
171
  id = @engine_state.generate_id
168
172
  destinations = {}
169
173
  steps.each do |step|
170
- dataset = step["dataset"]
174
+ dataset = @catalog.dataset(step["dataset"])
171
175
  if dataset
172
- routes = @catalog.get_routes(dataset, step)
176
+ routes = dataset.get_routes(step, @live_nodes)
173
177
  step["routes"] = routes
174
178
  else
175
179
  step["routes"] ||= [id]
@@ -208,11 +212,7 @@ module Droonga
208
212
 
209
213
  private
210
214
  def farm_path(route)
211
- if route =~ /\A.*:\d+\/[^\.]+/
212
- $MATCH
213
- else
214
- route
215
- end
215
+ @engine_state.farm_path(route)
216
216
  end
217
217
 
218
218
  def process_input_message(message)
@@ -15,62 +15,126 @@
15
15
  # License along with this library; if not, write to the Free Software
16
16
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
 
18
+ require "time"
19
+ require "fileutils"
18
20
  require "droonga/engine/version"
19
21
  require "droonga/loggable"
20
22
  require "droonga/engine_state"
21
- require "droonga/catalog_observer"
23
+ require "droonga/catalog_loader"
22
24
  require "droonga/dispatcher"
25
+ require "droonga/live_nodes_list_observer"
23
26
 
24
27
  module Droonga
25
28
  class Engine
26
29
  include Loggable
27
30
 
28
- def initialize(loop, name)
29
- @state = EngineState.new(loop, name)
30
- @catalog_observer = Droonga::CatalogObserver.new(@state.loop)
31
- @catalog_observer.on_reload = lambda do |catalog|
32
- graceful_restart(catalog)
33
- logger.info("restarted")
31
+ LAST_PROCESSED_TIMESTAMP = "last-processed.timestamp"
32
+ EFFECTIVE_MESSAGE_TIMESTAMP = "effective-message.timestamp"
33
+
34
+ def initialize(loop, name, internal_name)
35
+ @state = EngineState.new(loop, name, internal_name)
36
+ @catalog = load_catalog
37
+ @live_nodes = @catalog.all_nodes
38
+ @dispatcher = create_dispatcher
39
+ @live_nodes_list_observer = LiveNodesListObserver.new
40
+ @live_nodes_list_observer.on_update = lambda do |live_nodes|
41
+ @live_nodes = live_nodes
42
+ @dispatcher.live_nodes = live_nodes if @dispatcher
34
43
  end
35
44
  end
36
45
 
37
46
  def start
38
47
  logger.trace("start: start")
39
48
  @state.start
40
- @catalog_observer.start
41
- catalog = @catalog_observer.catalog
42
- @dispatcher = create_dispatcher(catalog)
49
+ @live_nodes_list_observer.start
43
50
  @dispatcher.start
44
51
  logger.trace("start: done")
45
52
  end
46
53
 
47
- def shutdown
48
- logger.trace("shutdown: start")
49
- @catalog_observer.stop
54
+ def stop_gracefully
55
+ logger.trace("stop_gracefully: start")
56
+ @live_nodes_list_observer.stop
57
+ on_finish = lambda do
58
+ output_last_processed_timestamp
59
+ @dispatcher.shutdown
60
+ @state.shutdown
61
+ yield
62
+ end
63
+ if @state.have_session?
64
+ @state.on_finish = on_finish
65
+ else
66
+ on_finish.call
67
+ end
68
+ logger.trace("stop_gracefully: done")
69
+ end
70
+
71
+ # It may be called after stop_gracefully.
72
+ def stop_immediately
73
+ logger.trace("stop_immediately: start")
74
+ output_last_processed_timestamp
75
+ @live_nodes_list_observer.stop
50
76
  @dispatcher.shutdown
51
77
  @state.shutdown
52
- logger.trace("shutdown: done")
78
+ logger.trace("stop_immediately: done")
53
79
  end
54
80
 
55
81
  def process(message)
82
+ return unless effective_message?(message)
83
+ @last_processed_timestamp = message["date"]
56
84
  @dispatcher.process_message(message)
57
85
  end
58
86
 
59
87
  private
60
- def create_dispatcher(catalog)
61
- Dispatcher.new(@state, catalog)
88
+ def load_catalog
89
+ catalog_path = Path.catalog
90
+ loader = CatalogLoader.new(catalog_path.to_s)
91
+ catalog = loader.load
92
+ logger.info("catalog loaded",
93
+ :path => catalog_path,
94
+ :mtime => catalog_path.mtime)
95
+ catalog
96
+ end
97
+
98
+ def create_dispatcher
99
+ dispatcher = Dispatcher.new(@state, @catalog)
100
+ dispatcher.live_nodes = @live_nodes
101
+ dispatcher
102
+ end
103
+
104
+ def output_last_processed_timestamp
105
+ File.open(last_processed_timestamp_file, "w") do |file|
106
+ file.write(@last_processed_timestamp)
107
+ end
108
+ end
109
+
110
+ def last_processed_timestamp_file
111
+ @last_processed_timestamp_file ||= File.join(Droonga::Path.state, LAST_PROCESSED_TIMESTAMP)
112
+ end
113
+
114
+ def effective_message?(message)
115
+ effective_timestamp = effective_message_timestamp
116
+ return true if effective_timestamp.nil?
117
+
118
+ message_timestamp = Time.parse(message["date"])
119
+ return false if effective_timestamp >= message_timestamp
120
+
121
+ FileUtils.rm(effective_message_timestamp_file)
122
+ true
123
+ end
124
+
125
+ def effective_message_timestamp
126
+ return nil unless File.exist?(effective_message_timestamp_file)
127
+
128
+ timestamp = File.read(effective_message_timestamp_file)
129
+ begin
130
+ Time.parse(timestamp)
131
+ rescue ArgumentError
132
+ nil
133
+ end
62
134
  end
63
135
 
64
- def graceful_restart(catalog)
65
- logger.trace("graceful_restart: start")
66
- old_dispatcher = @dispatcher
67
- logger.trace("graceful_restart: creating new dispatcher")
68
- new_dispatcher = create_dispatcher(catalog)
69
- new_dispatcher.start
70
- @dispatcher = new_dispatcher
71
- logger.trace("graceful_restart: shutdown old dispatcher")
72
- old_dispatcher.shutdown
73
- logger.trace("graceful_restart: done")
136
+ def effective_message_timestamp_file
137
+ @effective_message_timestamp_file ||= File.join(Droonga::Path.state, EFFECTIVE_MESSAGE_TIMESTAMP)
74
138
  end
75
139
 
76
140
  def log_tag