fluent-plugin-droonga 0.0.2

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