droonga-engine 1.0.2 → 1.0.3

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