fluent-plugin-droonga 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +40 -0
  5. data/LICENSE.txt +14 -0
  6. data/README.md +18 -0
  7. data/Rakefile +25 -0
  8. data/benchmark/benchmark.rb +123 -0
  9. data/benchmark/utils.rb +243 -0
  10. data/benchmark/watch/benchmark-notify.rb +143 -0
  11. data/benchmark/watch/benchmark-notify.sh +19 -0
  12. data/benchmark/watch/benchmark-publish.rb +120 -0
  13. data/benchmark/watch/benchmark-scan.rb +210 -0
  14. data/benchmark/watch/catalog.json +32 -0
  15. data/benchmark/watch/fluentd.conf +12 -0
  16. data/bin/grn2jsons +85 -0
  17. data/fluent-plugin-droonga.gemspec +41 -0
  18. data/lib/droonga/adapter.rb +156 -0
  19. data/lib/droonga/catalog.rb +153 -0
  20. data/lib/droonga/command_mapper.rb +45 -0
  21. data/lib/droonga/engine.rb +83 -0
  22. data/lib/droonga/executor.rb +289 -0
  23. data/lib/droonga/handler.rb +140 -0
  24. data/lib/droonga/handler_plugin.rb +35 -0
  25. data/lib/droonga/job_queue.rb +83 -0
  26. data/lib/droonga/job_queue_schema.rb +65 -0
  27. data/lib/droonga/logger.rb +34 -0
  28. data/lib/droonga/plugin.rb +41 -0
  29. data/lib/droonga/plugin/adapter/groonga/select.rb +88 -0
  30. data/lib/droonga/plugin/adapter_groonga.rb +40 -0
  31. data/lib/droonga/plugin/handler/groonga/column_create.rb +103 -0
  32. data/lib/droonga/plugin/handler/groonga/table_create.rb +100 -0
  33. data/lib/droonga/plugin/handler_add.rb +44 -0
  34. data/lib/droonga/plugin/handler_forward.rb +70 -0
  35. data/lib/droonga/plugin/handler_groonga.rb +52 -0
  36. data/lib/droonga/plugin/handler_proxy.rb +82 -0
  37. data/lib/droonga/plugin/handler_search.rb +33 -0
  38. data/lib/droonga/plugin/handler_watch.rb +102 -0
  39. data/lib/droonga/proxy.rb +371 -0
  40. data/lib/droonga/searcher.rb +415 -0
  41. data/lib/droonga/server.rb +112 -0
  42. data/lib/droonga/sweeper.rb +42 -0
  43. data/lib/droonga/watch_schema.rb +88 -0
  44. data/lib/droonga/watcher.rb +256 -0
  45. data/lib/droonga/worker.rb +51 -0
  46. data/lib/fluent/plugin/out_droonga.rb +56 -0
  47. data/lib/groonga_command_converter.rb +137 -0
  48. data/sample/cluster/catalog.json +43 -0
  49. data/sample/cluster/fluentd.conf +12 -0
  50. data/sample/fluentd.conf +8 -0
  51. data/test/fixtures/catalog.json +43 -0
  52. data/test/fixtures/document.grn +23 -0
  53. data/test/helper.rb +24 -0
  54. data/test/helper/fixture.rb +28 -0
  55. data/test/helper/sandbox.rb +73 -0
  56. data/test/helper/stub_worker.rb +27 -0
  57. data/test/helper/watch_helper.rb +35 -0
  58. data/test/plugin/adapter/groonga/test_select.rb +176 -0
  59. data/test/plugin/handler/groonga/test_column_create.rb +127 -0
  60. data/test/plugin/handler/groonga/test_table_create.rb +140 -0
  61. data/test/plugin/handler/test_handler_add.rb +135 -0
  62. data/test/plugin/handler/test_handler_groonga.rb +64 -0
  63. data/test/plugin/handler/test_handler_search.rb +512 -0
  64. data/test/plugin/handler/test_handler_watch.rb +168 -0
  65. data/test/run-test.rb +55 -0
  66. data/test/test_adapter.rb +48 -0
  67. data/test/test_catalog.rb +59 -0
  68. data/test/test_command_mapper.rb +44 -0
  69. data/test/test_groonga_command_converter.rb +242 -0
  70. data/test/test_handler.rb +53 -0
  71. data/test/test_job_queue_schema.rb +45 -0
  72. data/test/test_output.rb +99 -0
  73. data/test/test_sweeper.rb +95 -0
  74. data/test/test_watch_schema.rb +57 -0
  75. data/test/test_watcher.rb +336 -0
  76. data/test/test_worker.rb +144 -0
  77. metadata +299 -0
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ module Droonga
19
+ class CommandMapper
20
+ def initialize
21
+ @commands = {}
22
+ end
23
+
24
+ def register(name_or_map)
25
+ if name_or_map.is_a?(Hash)
26
+ command_map = name_or_map
27
+ command_map.each do |command_name, method_name|
28
+ @commands[command_name.to_s] = method_name
29
+ end
30
+ else
31
+ name = name_or_map
32
+ method_name = name
33
+ @commands[name.to_s] = method_name
34
+ end
35
+ end
36
+
37
+ def [](command)
38
+ @commands[command.to_s]
39
+ end
40
+
41
+ def commands
42
+ @commands.keys
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "serverengine"
19
+ require "msgpack"
20
+ require "cool.io"
21
+
22
+ require "droonga/server"
23
+ require "droonga/worker"
24
+ require "droonga/executor"
25
+
26
+ module Droonga
27
+ class Engine
28
+ DEFAULT_OPTIONS = {
29
+ :queue_name => "DroongaQueue",
30
+ :n_workers => 0,
31
+ }
32
+
33
+ def initialize(options={})
34
+ @options = DEFAULT_OPTIONS.merge(options)
35
+ end
36
+
37
+ def start
38
+ if @options[:database] && !@options[:database].empty?
39
+ Droonga::JobQueue.ensure_schema(@options[:database],
40
+ @options[:queue_name])
41
+ end
42
+ start_supervisor if @options[:n_workers] > 0
43
+ @executor = Executor.new(@options)
44
+ end
45
+
46
+ def shutdown
47
+ $log.trace("engine: shutdown: start")
48
+ @executor.shutdown if @executor
49
+ shutdown_supervisor if @supervisor
50
+ $log.trace("engine: shutdown: done")
51
+ end
52
+
53
+ def emit(tag, time, record, synchronous=nil)
54
+ $log.trace("[#{Process.pid}] tag: <#{tag}> caller: <#{caller.first}>")
55
+ @executor.dispatch(tag, time, record, synchronous)
56
+ end
57
+
58
+ private
59
+ def start_supervisor
60
+ @supervisor = ServerEngine::Supervisor.new(Server, Worker) do
61
+ force_options = {
62
+ :worker_type => "process",
63
+ :workers => @options[:n_workers],
64
+ :log_level => $log.level,
65
+ :server_process_name => "Server[#{@options[:database]}] #$0",
66
+ :worker_process_name => "Worker[#{@options[:database]}] #$0"
67
+ }
68
+ @options.merge(force_options)
69
+ end
70
+ @supervisor_thread = Thread.new do
71
+ @supervisor.main
72
+ end
73
+ end
74
+
75
+ def shutdown_supervisor
76
+ $log.trace("supervisor: shutdown: start")
77
+ @supervisor.stop(true)
78
+ $log.trace("supervisor: shutdown: stopped")
79
+ @supervisor_thread.join
80
+ $log.trace("supervisor: shutdown: done")
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,289 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "fluent-logger"
19
+ require "groonga"
20
+
21
+ require "droonga/job_queue"
22
+ require "droonga/handler_plugin"
23
+ require "droonga/plugin"
24
+ require "droonga/proxy"
25
+
26
+ module Droonga
27
+ class Executor
28
+ attr_reader :context, :envelope, :name
29
+
30
+ def initialize(options={})
31
+ @handlers = []
32
+ @outputs = {}
33
+ @options = options
34
+ @name = options[:name]
35
+ @database_name = options[:database]
36
+ @queue_name = options[:queue_name]
37
+ @handler_names = options[:handlers]
38
+ @pool_size = options[:n_workers]
39
+ # load_handlers
40
+ Droonga::Plugin.load_all
41
+ prepare
42
+ end
43
+
44
+ def shutdown
45
+ $log.trace("#{log_tag}: shutdown: start")
46
+ @handlers.each do |handler|
47
+ handler.shutdown
48
+ end
49
+ @outputs.each do |dest, output|
50
+ output[:logger].close if output[:logger]
51
+ end
52
+ if @database
53
+ @database.close
54
+ @context.close
55
+ @database = @context = nil
56
+ end
57
+ if @job_queue
58
+ @job_queue.close
59
+ @job_queue = nil
60
+ end
61
+ $log.trace("#{log_tag}: shutdown: done")
62
+ end
63
+
64
+ def add_handler(name)
65
+ plugin = HandlerPlugin.new(name)
66
+ @handlers << plugin.instantiate(self)
67
+ end
68
+
69
+ def add_route(route)
70
+ envelope["via"].push(route)
71
+ end
72
+
73
+ def dispatch(tag, time, record, synchronous=nil)
74
+ $log.trace("#{log_tag}: dispatch: start")
75
+ message = [tag, time, record]
76
+ body, type, arguments = parse_message([tag, time, record])
77
+ reply_to = envelope["replyTo"]
78
+ if reply_to.is_a? String
79
+ envelope["replyTo"] = {
80
+ "type" => type + ".result",
81
+ "to" => reply_to
82
+ }
83
+ end
84
+ post_or_push(message, body,
85
+ "type" => type,
86
+ "arguments" => arguments,
87
+ "synchronous" => synchronous)
88
+ $log.trace("#{log_tag}: dispatch: done")
89
+ end
90
+
91
+ def execute_one
92
+ $log.trace("#{log_tag}: execute_one: start")
93
+ message = @job_queue.pull_message
94
+ unless message
95
+ $log.trace("#{log_tag}: execute_one: abort: no message")
96
+ return
97
+ end
98
+ body, command, arguments = parse_message(message)
99
+ handler = find_handler(command)
100
+ if handler
101
+ $log.trace("#{log_tag}: execute_one: handle: start",
102
+ :hander => handler.class)
103
+ handler.handle(command, body, *arguments)
104
+ $log.trace("#{log_tag}: execute_one: handle: done",
105
+ :hander => handler.class)
106
+ end
107
+ $log.trace("#{log_tag}: execute_one: done")
108
+ end
109
+
110
+ def post(body, destination=nil)
111
+ $log.trace("#{log_tag}: post: start")
112
+ post_or_push(nil, body, destination)
113
+ $log.trace("#{log_tag}: post: done")
114
+ end
115
+
116
+ private
117
+ def post_or_push(message, body, destination)
118
+ route = nil
119
+ unless is_route?(destination)
120
+ route = envelope["via"].pop
121
+ destination = route
122
+ end
123
+ unless is_route?(destination)
124
+ destination = envelope["replyTo"]
125
+ end
126
+ command = nil
127
+ receiver = nil
128
+ arguments = nil
129
+ synchronous = nil
130
+ case destination
131
+ when String
132
+ command = destination
133
+ when Hash
134
+ command = destination["type"]
135
+ receiver = destination["to"]
136
+ arguments = destination["arguments"]
137
+ synchronous = destination["synchronous"]
138
+ end
139
+ if receiver
140
+ output(receiver, body, command, arguments)
141
+ else
142
+ handler = find_handler(command)
143
+ if handler
144
+ if synchronous.nil?
145
+ synchronous = handler.prefer_synchronous?(command)
146
+ end
147
+ if route || @pool_size.zero? || synchronous
148
+ $log.trace("#{log_tag}: post_or_push: handle: start")
149
+ handler.handle(command, body, *arguments)
150
+ $log.trace("#{log_tag}: post_or_push: handle: done")
151
+ else
152
+ unless message
153
+ envelope["body"] = body
154
+ envelope["type"] = command
155
+ envelope["arguments"] = arguments
156
+ message = ['', Time.now.to_f, envelope]
157
+ end
158
+ @job_queue.push_message(message)
159
+ end
160
+ end
161
+ end
162
+ add_route(route) if route
163
+ end
164
+
165
+ def is_route?(route)
166
+ route.is_a?(String) || route.is_a?(Hash)
167
+ end
168
+
169
+ def output(receiver, body, command, arguments)
170
+ $log.trace("#{log_tag}: output: start")
171
+ unless receiver.is_a?(String) && command.is_a?(String)
172
+ $log.trace("#{log_tag}: output: abort: invalid argument",
173
+ :receiver => receiver,
174
+ :command => command)
175
+ return
176
+ end
177
+ unless receiver =~ /\A(.*):(\d+)\/(.*?)(\?.+)?\z/
178
+ raise "format: hostname:port/tag(?params)"
179
+ end
180
+ host = $1
181
+ port = $2
182
+ tag = $3
183
+ params = $4
184
+ output = get_output(host, port, params)
185
+ unless output
186
+ $log.trace("#{log_tag}: output: abort: no output",
187
+ :host => host,
188
+ :port => port,
189
+ :params => params)
190
+ return
191
+ end
192
+ if command =~ /\.result$/
193
+ message = {
194
+ inReplyTo: envelope["id"],
195
+ statusCode: 200,
196
+ type: command,
197
+ body: body
198
+ }
199
+ else
200
+ message = envelope.merge(
201
+ body: body,
202
+ type: command,
203
+ arguments: arguments
204
+ )
205
+ end
206
+ output_tag = "#{tag}.message"
207
+ $log.trace("#{log_tag}: output: post: start: <#{output_tag}>")
208
+ output.post(output_tag, message)
209
+ $log.trace("#{log_tag}: output: post: done: <#{output_tag}>")
210
+ $log.trace("#{log_tag}: output: done")
211
+ end
212
+
213
+ def parse_message(message)
214
+ tag, time, record = message
215
+ prefix, type, *arguments = tag.split(/\./)
216
+ if type.nil? || type.empty? || type == 'message'
217
+ @envelope = record
218
+ else
219
+ @envelope = {
220
+ "type" => type,
221
+ "arguments" => arguments,
222
+ "body" => record
223
+ }
224
+ end
225
+ envelope["via"] ||= []
226
+ [envelope["body"], envelope["type"], envelope["arguments"]]
227
+ end
228
+
229
+ def load_handlers
230
+ @handler_names.each do |handler_name|
231
+ plugin = Droonga::Plugin.new("handler", handler_name)
232
+ plugin.load
233
+ end
234
+ end
235
+
236
+ def prepare
237
+ if @database_name && !@database_name.empty?
238
+ @context = Groonga::Context.new
239
+ @database = @context.open_database(@database_name)
240
+ @job_queue = JobQueue.open(@database_name, @queue_name)
241
+ end
242
+ @handler_names.each do |handler_name|
243
+ add_handler(handler_name)
244
+ end
245
+ add_handler("proxy_message") if @options[:proxy]
246
+ end
247
+
248
+ def find_handler(command)
249
+ @handlers.find do |handler|
250
+ handler.handlable?(command)
251
+ end
252
+ end
253
+
254
+ def get_output(host, port, params)
255
+ host_port = "#{host}:#{port}"
256
+ @outputs[host_port] ||= {}
257
+ output = @outputs[host_port]
258
+
259
+ has_connection_id = (not params.nil? \
260
+ and params =~ /[\?&;]connection_id=([^&;]+)/)
261
+ if output[:logger].nil? or has_connection_id
262
+ connection_id = $1
263
+ if not has_connection_id or output[:connection_id] != connection_id
264
+ output[:connection_id] = connection_id
265
+ logger = create_logger(:host => host, :port => port.to_i)
266
+ # output[:logger] should be closed if it exists beforehand?
267
+ output[:logger] = logger
268
+ end
269
+ end
270
+
271
+ has_client_session_id = (not params.nil? \
272
+ and params =~ /[\?&;]client_session_id=([^&;]+)/)
273
+ if has_client_session_id
274
+ client_session_id = $1
275
+ # some generic way to handle client_session_id is expected
276
+ end
277
+
278
+ output[:logger]
279
+ end
280
+
281
+ def create_logger(options)
282
+ Fluent::Logger::FluentLogger.new(nil, options)
283
+ end
284
+
285
+ def log_tag
286
+ "[#{Process.ppid}][#{Process.pid}] executor"
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,140 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "droonga/handler_plugin"
19
+ require "droonga/command_mapper"
20
+ require "droonga/logger"
21
+
22
+ module Droonga
23
+ class Handler
24
+ class << self
25
+ def inherited(sub_class)
26
+ super
27
+ sub_class.instance_variable_set(:@command_mapper, CommandMapper.new)
28
+ end
29
+
30
+ def command(name_or_map)
31
+ @command_mapper.register(name_or_map)
32
+ end
33
+
34
+ def method_name(command)
35
+ @command_mapper[command]
36
+ end
37
+
38
+ def handlable?(command)
39
+ not method_name(command).nil?
40
+ end
41
+ end
42
+
43
+ def initialize(worker)
44
+ @worker = worker
45
+ @context = @worker.context
46
+ end
47
+
48
+ def post(body, destination=nil)
49
+ @worker.post(body, destination)
50
+ end
51
+
52
+ def envelope
53
+ @worker.envelope
54
+ end
55
+
56
+ def add_route(route)
57
+ @worker.add_route(route)
58
+ end
59
+
60
+ def shutdown
61
+ end
62
+
63
+ def handlable?(command)
64
+ self.class.handlable?(command)
65
+ end
66
+
67
+ def invoke(command, request, *arguments)
68
+ __send__(self.class.method_name(command), request, *arguments)
69
+ rescue => exception
70
+ Logger.error("error while handling #{command}",
71
+ request: request,
72
+ arguments: arguments,
73
+ exception: exception)
74
+ end
75
+
76
+ def handle(command, request, *arguments)
77
+ unless try_handle_as_internal_message(command, request, arguments)
78
+ @task = {}
79
+ @output_values = {}
80
+ invoke(command, request, *arguments)
81
+ post(@output_values) unless @output_values.empty?
82
+ end
83
+ end
84
+
85
+ def prefer_synchronous?(command)
86
+ return false
87
+ end
88
+
89
+ def emit(value, name = nil)
90
+ unless name
91
+ if @output_names
92
+ name = @output_names.first
93
+ else
94
+ @output_values = @task["values"] = value
95
+ return
96
+ end
97
+ end
98
+ @output_values[name] = value
99
+ end
100
+
101
+ def try_handle_as_internal_message(command, request, arguments)
102
+ return false unless request.is_a? Hash
103
+
104
+ @task = request["task"]
105
+ return false unless @task.is_a? Hash
106
+
107
+ @component = @task["component"]
108
+ return false unless @component.is_a? Hash
109
+
110
+ @output_values = @task["values"]
111
+ @body = @component["body"]
112
+ @output_names = @component["outputs"]
113
+ @id = request["id"]
114
+ @value = request["value"]
115
+ @input_name = request["name"]
116
+ @descendants = request["descendants"]
117
+
118
+ invoke(command, @body, *arguments)
119
+ output if @descendants
120
+ true
121
+ end
122
+
123
+ def output
124
+ result = @task["values"]
125
+ post(result, @component["post"]) if @component["post"]
126
+ @descendants.each do |name, dests|
127
+ message = {
128
+ "id" => @id,
129
+ "input" => name,
130
+ "value" => result[name]
131
+ }
132
+ dests.each do |routes|
133
+ routes.each do |route|
134
+ post(message, "to"=>route, "type"=>"proxy")
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end