droonga-engine 1.0.9 → 1.1.0

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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/benchmark/timer-watcher/benchmark.rb +44 -0
  4. data/bin/droonga-engine-absorb-data +246 -187
  5. data/bin/droonga-engine-catalog-generate +12 -12
  6. data/bin/droonga-engine-catalog-modify +4 -4
  7. data/bin/droonga-engine-join +352 -171
  8. data/bin/droonga-engine-set-role +54 -0
  9. data/bin/droonga-engine-unjoin +107 -112
  10. data/droonga-engine.gemspec +3 -3
  11. data/install.sh +55 -36
  12. data/install/centos/functions.sh +2 -2
  13. data/install/debian/functions.sh +2 -2
  14. data/lib/droonga/address.rb +26 -24
  15. data/lib/droonga/buffered_tcp_socket.rb +65 -10
  16. data/lib/droonga/catalog/base.rb +9 -6
  17. data/lib/droonga/catalog/dataset.rb +17 -41
  18. data/lib/droonga/catalog/fetcher.rb +64 -0
  19. data/lib/droonga/catalog/generator.rb +245 -0
  20. data/lib/droonga/catalog/loader.rb +66 -0
  21. data/lib/droonga/{catalog_modifier.rb → catalog/modifier.rb} +11 -18
  22. data/lib/droonga/catalog/replicas_volume.rb +123 -0
  23. data/lib/droonga/catalog/schema.rb +37 -37
  24. data/lib/droonga/catalog/single_volume.rb +11 -3
  25. data/lib/droonga/catalog/slice.rb +10 -6
  26. data/lib/droonga/catalog/{collection_volume.rb → slices_volume.rb} +47 -11
  27. data/lib/droonga/catalog/version1.rb +47 -19
  28. data/lib/droonga/catalog/version2.rb +11 -10
  29. data/lib/droonga/catalog/version2_validator.rb +4 -4
  30. data/lib/droonga/catalog/volume.rb +17 -5
  31. data/lib/droonga/changable.rb +25 -0
  32. data/lib/droonga/cluster.rb +237 -0
  33. data/lib/droonga/collector_runner.rb +4 -0
  34. data/lib/droonga/collectors.rb +2 -1
  35. data/lib/droonga/collectors/recursive_sum.rb +26 -0
  36. data/lib/droonga/command/droonga_engine.rb +404 -127
  37. data/lib/droonga/command/droonga_engine_service.rb +47 -11
  38. data/lib/droonga/command/droonga_engine_worker.rb +21 -1
  39. data/lib/droonga/command/remote_command_base.rb +78 -0
  40. data/lib/droonga/command/serf_event_handler.rb +29 -20
  41. data/lib/droonga/data_absorber_client.rb +222 -0
  42. data/lib/droonga/database_scanner.rb +106 -0
  43. data/lib/droonga/{live_nodes_list_loader.rb → deferrable.rb} +11 -24
  44. data/lib/droonga/differ.rb +58 -0
  45. data/lib/droonga/dispatcher.rb +155 -32
  46. data/lib/droonga/distributed_command_planner.rb +9 -11
  47. data/lib/droonga/engine.rb +83 -78
  48. data/lib/droonga/engine/version.rb +1 -1
  49. data/lib/droonga/engine_node.rb +301 -0
  50. data/lib/droonga/engine_state.rb +62 -40
  51. data/lib/droonga/farm.rb +44 -5
  52. data/lib/droonga/file_observer.rb +16 -12
  53. data/lib/droonga/fluent_message_receiver.rb +98 -29
  54. data/lib/droonga/fluent_message_sender.rb +30 -23
  55. data/lib/droonga/forward_buffer.rb +160 -0
  56. data/lib/droonga/forwarder.rb +73 -40
  57. data/lib/droonga/handler.rb +7 -6
  58. data/lib/droonga/handler_messenger.rb +15 -6
  59. data/lib/droonga/handler_runner.rb +6 -1
  60. data/lib/droonga/internal_fluent_message_receiver.rb +28 -8
  61. data/lib/droonga/job_pusher.rb +10 -7
  62. data/lib/droonga/job_receiver.rb +6 -4
  63. data/lib/droonga/logger.rb +7 -1
  64. data/lib/droonga/node_name.rb +90 -0
  65. data/lib/droonga/node_role.rb +72 -0
  66. data/lib/droonga/path.rb +34 -9
  67. data/lib/droonga/planner.rb +73 -7
  68. data/lib/droonga/plugin/async_command.rb +154 -0
  69. data/lib/droonga/plugins/catalog.rb +1 -0
  70. data/lib/droonga/plugins/crud.rb +22 -6
  71. data/lib/droonga/plugins/dump.rb +66 -135
  72. data/lib/droonga/plugins/groonga/delete.rb +13 -0
  73. data/lib/droonga/plugins/search/distributed_search_planner.rb +4 -4
  74. data/lib/droonga/plugins/system.rb +5 -26
  75. data/lib/droonga/plugins/system/absorb_data.rb +405 -0
  76. data/lib/droonga/plugins/system/statistics.rb +71 -0
  77. data/lib/droonga/plugins/system/status.rb +53 -0
  78. data/lib/droonga/process_control_protocol.rb +3 -1
  79. data/lib/droonga/process_supervisor.rb +32 -15
  80. data/lib/droonga/reducer.rb +69 -0
  81. data/lib/droonga/safe_file_writer.rb +1 -1
  82. data/lib/droonga/serf.rb +207 -276
  83. data/lib/droonga/serf/agent.rb +228 -0
  84. data/lib/droonga/serf/command.rb +94 -0
  85. data/lib/droonga/serf/downloader.rb +120 -0
  86. data/lib/droonga/serf/remote_command.rb +348 -0
  87. data/lib/droonga/serf/tag.rb +56 -0
  88. data/lib/droonga/service_installation.rb +2 -2
  89. data/lib/droonga/session.rb +49 -1
  90. data/lib/droonga/single_step.rb +6 -11
  91. data/lib/droonga/single_step_definition.rb +32 -1
  92. data/lib/droonga/slice.rb +14 -9
  93. data/lib/droonga/supervisor.rb +27 -20
  94. data/lib/droonga/test/stub_handler_messenger.rb +2 -1
  95. data/lib/droonga/timestamp.rb +69 -0
  96. data/lib/droonga/worker_process_agent.rb +33 -15
  97. data/sample/cluster-state.json +8 -0
  98. data/sample/cluster/Rakefile +30 -6
  99. data/test/command/fixture/integer-key-table.jsons +11 -0
  100. data/test/command/fixture/string-key-table.jsons +11 -0
  101. data/test/command/run-test.rb +4 -0
  102. data/test/command/suite/add/error/invalid-integer.expected +3 -3
  103. data/test/command/suite/add/error/invalid-time.expected +3 -3
  104. data/test/command/suite/add/{minimum.expected → key-integer.expected} +0 -0
  105. data/test/command/suite/add/{minimum.test → key-integer.test} +0 -0
  106. data/test/command/suite/add/key-string.expected +6 -0
  107. data/test/command/suite/add/key-string.test +9 -0
  108. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.expected +6 -0
  109. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.test +9 -0
  110. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.expected +6 -0
  111. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.test +9 -0
  112. data/test/command/suite/add/without-values.expected +6 -0
  113. data/test/command/suite/add/without-values.test +11 -0
  114. data/test/command/suite/dump/column/index.expected +33 -1
  115. data/test/command/suite/dump/column/index.test +1 -0
  116. data/test/command/suite/dump/column/scalar.expected +29 -1
  117. data/test/command/suite/dump/column/scalar.test +1 -0
  118. data/test/command/suite/dump/column/vector.expected +29 -1
  119. data/test/command/suite/dump/column/vector.test +1 -0
  120. data/test/command/suite/dump/record/scalar.catalog.json +12 -0
  121. data/test/command/suite/dump/record/scalar.expected +84 -0
  122. data/test/command/suite/dump/record/scalar.test +16 -0
  123. data/test/command/suite/dump/record/vector/reference.expected +83 -1
  124. data/test/command/suite/dump/record/vector/reference.test +1 -0
  125. data/test/command/suite/dump/table/array.expected +27 -1
  126. data/test/command/suite/dump/table/array.test +1 -0
  127. data/test/command/suite/dump/table/double_array_trie.expected +27 -1
  128. data/test/command/suite/dump/table/double_array_trie.test +1 -0
  129. data/test/command/suite/dump/table/hash.expected +27 -1
  130. data/test/command/suite/dump/table/hash.test +1 -0
  131. data/test/command/suite/dump/table/patricia_trie.expected +27 -1
  132. data/test/command/suite/dump/table/patricia_trie.test +1 -0
  133. data/test/command/suite/groonga/delete/{success.expected → key-integer.expected} +0 -0
  134. data/test/command/suite/groonga/delete/key-integer.test +17 -0
  135. data/test/command/suite/groonga/delete/key-string.expected +19 -0
  136. data/test/command/suite/groonga/delete/{success.test → key-string.test} +4 -6
  137. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.expected +19 -0
  138. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.test +17 -0
  139. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.expected +19 -0
  140. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.test +17 -0
  141. data/test/command/suite/message/error/missing-dataset.test +1 -0
  142. data/test/command/suite/system/absorb-data/records.catalog.json +58 -0
  143. data/test/command/suite/system/absorb-data/records.expected +32 -0
  144. data/test/command/suite/system/absorb-data/records.test +24 -0
  145. data/test/command/suite/system/statistics/object/count/empty.expected +11 -0
  146. data/test/command/suite/system/statistics/object/count/empty.test +12 -0
  147. data/test/command/suite/system/statistics/object/count/per-volume/empty.catalog.json +36 -0
  148. data/test/command/suite/system/statistics/object/count/per-volume/empty.expected +19 -0
  149. data/test/command/suite/system/statistics/object/count/per-volume/empty.test +12 -0
  150. data/test/command/suite/system/statistics/object/count/per-volume/record.catalog.json +40 -0
  151. data/test/command/suite/system/statistics/object/count/per-volume/record.expected +19 -0
  152. data/test/command/suite/system/statistics/object/count/per-volume/record.test +23 -0
  153. data/test/command/suite/system/statistics/object/count/per-volume/schema.catalog.json +40 -0
  154. data/test/command/suite/system/statistics/object/count/per-volume/schema.expected +19 -0
  155. data/test/command/suite/system/statistics/object/count/per-volume/schema.test +13 -0
  156. data/test/command/suite/system/statistics/object/count/record.catalog.json +12 -0
  157. data/test/command/suite/system/statistics/object/count/record.expected +11 -0
  158. data/test/command/suite/system/statistics/object/count/record.test +23 -0
  159. data/test/command/suite/system/statistics/object/count/schema.catalog.json +12 -0
  160. data/test/command/suite/system/statistics/object/count/schema.expected +11 -0
  161. data/test/command/suite/system/statistics/object/count/schema.test +13 -0
  162. data/test/command/suite/system/status.expected +3 -2
  163. data/test/unit/catalog/test_dataset.rb +4 -1
  164. data/test/unit/{test_catalog_generator.rb → catalog/test_generator.rb} +2 -2
  165. data/test/unit/catalog/test_replicas_volume.rb +79 -0
  166. data/test/unit/catalog/test_single_volume.rb +2 -2
  167. data/test/unit/catalog/test_slice.rb +33 -1
  168. data/test/unit/catalog/{test_collection_volume.rb → test_slices_volume.rb} +72 -11
  169. data/test/unit/catalog/test_version2.rb +3 -0
  170. data/test/unit/helper/distributed_search_planner_helper.rb +2 -2
  171. data/test/unit/plugins/catalog/test_fetch.rb +4 -4
  172. data/test/unit/plugins/crud/test_add.rb +44 -4
  173. data/test/unit/plugins/groonga/test_column_create.rb +4 -4
  174. data/test/unit/plugins/groonga/test_column_list.rb +4 -4
  175. data/test/unit/plugins/groonga/test_column_remove.rb +4 -4
  176. data/test/unit/plugins/groonga/test_column_rename.rb +4 -4
  177. data/test/unit/plugins/groonga/test_delete.rb +73 -10
  178. data/test/unit/plugins/groonga/test_table_create.rb +4 -4
  179. data/test/unit/plugins/groonga/test_table_list.rb +4 -4
  180. data/test/unit/plugins/groonga/test_table_remove.rb +4 -4
  181. data/test/unit/plugins/search/test_handler.rb +4 -4
  182. data/test/unit/plugins/search/test_planner.rb +4 -2
  183. data/test/unit/plugins/system/test_status.rb +31 -15
  184. data/test/unit/plugins/test_watch.rb +16 -16
  185. data/test/unit/test_address.rb +4 -4
  186. metadata +134 -35
  187. data/lib/droonga/catalog/volume_collection.rb +0 -79
  188. data/lib/droonga/catalog_fetcher.rb +0 -53
  189. data/lib/droonga/catalog_generator.rb +0 -243
  190. data/lib/droonga/catalog_loader.rb +0 -56
  191. data/lib/droonga/command/remote.rb +0 -404
  192. data/lib/droonga/data_absorber.rb +0 -264
  193. data/lib/droonga/node_status.rb +0 -71
  194. data/lib/droonga/serf_downloader.rb +0 -115
  195. data/test/unit/catalog/test_volume_collection.rb +0 -78
@@ -0,0 +1,228 @@
1
+ # Copyright (C) 2014-2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "English"
17
+
18
+ require "coolio"
19
+
20
+ require "droonga/loggable"
21
+ require "droonga/deferrable"
22
+
23
+ module Droonga
24
+ class Serf
25
+ class Agent
26
+ # the port must be different from droonga-http-server's agent!
27
+ PORT = 7946
28
+
29
+ include Loggable
30
+ include Deferrable
31
+
32
+ MAX_N_READ_CHECKS = 10
33
+
34
+ def initialize(loop, serf, host, bind_port, rpc_port, *options)
35
+ @loop = loop
36
+ @serf = serf
37
+ @host = host
38
+ @bind_port = bind_port
39
+ @rpc_port = rpc_port
40
+ @options = options
41
+ @pid = nil
42
+ @n_ready_checks = 0
43
+ end
44
+
45
+ def start
46
+ capture_output do |output_write, error_write|
47
+ env = {}
48
+ spawn_options = {
49
+ :out => output_write,
50
+ :err => error_write,
51
+ }
52
+ @pid = spawn(env, @serf, "agent",
53
+ "-bind", "#{@host}:#{@bind_port}",
54
+ "-rpc-addr", "#{@host}:#{@rpc_port}",
55
+ "-log-level", serf_log_level,
56
+ *@options, spawn_options)
57
+ end
58
+ start_ready_check
59
+ end
60
+
61
+ def stop(&block)
62
+ if @pid.nil?
63
+ yield if block_given?
64
+ return
65
+ end
66
+ Process.waitpid(@pid)
67
+ @output_io.close
68
+ # logger.trace("stop: output_io watcher detached",
69
+ # :watcher => @output_io)
70
+ @error_io.close
71
+ # logger.trace("stop: error_io watcher detached",
72
+ # :watcher => @error_io)
73
+ @pid = nil
74
+ yield if block_given?
75
+ end
76
+
77
+ def running?
78
+ not @pid.nil?
79
+ end
80
+
81
+ private
82
+ def serf_log_level
83
+ level = Logger::Level.default
84
+ case level
85
+ when "trace", "debug", "info", "warn"
86
+ level
87
+ when "error", "fatal"
88
+ "err"
89
+ else
90
+ level # Or error?
91
+ end
92
+ end
93
+
94
+ def capture_output
95
+ result = nil
96
+ output_read, output_write = IO.pipe
97
+ error_read, error_write = IO.pipe
98
+
99
+ begin
100
+ result = yield(output_write, error_write)
101
+ rescue
102
+ output_read.close unless output_read.closed?
103
+ output_write.close unless output_write.closed?
104
+ error_read.close unless error_read.closed?
105
+ error_write.close unless error_write.closed?
106
+ raise
107
+ end
108
+
109
+ output_line_buffer = LineBuffer.new
110
+ @output_io = Coolio::IO.new(output_read)
111
+ @output_io.on_read do |data|
112
+ on_standard_output(output_line_buffer, data)
113
+ end
114
+ @loop.attach(@output_io)
115
+ # logger.trace("capture_output: new output_io watcher attached",
116
+ # :watcher => @output_io)
117
+
118
+ error_line_buffer = LineBuffer.new
119
+ @error_io = Coolio::IO.new(error_read)
120
+ @error_io.on_read do |data|
121
+ on_error_output(error_line_buffer, data)
122
+ end
123
+ @loop.attach(@error_io)
124
+ # logger.trace("capture_output: new error_io watcher attached",
125
+ # :watcher => @error_io)
126
+
127
+ result
128
+ end
129
+
130
+ def on_standard_output(line_buffer, data)
131
+ line_buffer.feed(data) do |line|
132
+ line = line.chomp
133
+ case line
134
+ when /\A==> /
135
+ content = $POSTMATCH
136
+ logger.info(content)
137
+ when /\A /
138
+ content = $POSTMATCH
139
+ case content
140
+ when /\A(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2}) \[(\w+)\] /
141
+ # year, month, day = $1, $2, $3
142
+ # hour, minute, second = $4, $5, $6
143
+ level = $7
144
+ content = $POSTMATCH
145
+ return unless needed_log_message?(content)
146
+ level = normalize_level(level)
147
+ logger.send(level, content)
148
+ else
149
+ logger.info(content)
150
+ end
151
+ else
152
+ logger.info(line)
153
+ end
154
+ end
155
+ end
156
+
157
+ def needed_log_message?(content)
158
+ case content
159
+ when /\Amemberlist: Failed to receive remote state: EOF\z/
160
+ # See also: https://github.com/hashicorp/consul/issues/598#issuecomment-71576948
161
+ false
162
+ when /\Aagent: Script .*droonga-engine-serf-event-handler.* slow, execution exceeding/
163
+ # Droonga's serf event handler can be slow for absorbing or some operations.
164
+ false
165
+ else
166
+ true
167
+ end
168
+ end
169
+
170
+ def normalize_level(level)
171
+ level = level.downcase
172
+ case level
173
+ when "err"
174
+ "error"
175
+ else
176
+ level
177
+ end
178
+ end
179
+
180
+ def on_error_output(line_buffer, data)
181
+ line_buffer.feed(data) do |line|
182
+ line = line.chomp
183
+ logger.error(line.gsub(/\A==> /, ""))
184
+ end
185
+ end
186
+
187
+ def start_ready_check
188
+ @n_ready_checks += 1
189
+
190
+ checker = Coolio::TCPSocket.connect(@host, @bind_port)
191
+
192
+ checker.on_connect do
193
+ on_ready
194
+ checker.close
195
+ logger.trace("start_ready_check: socket detached",
196
+ :watcher => checker)
197
+ end
198
+
199
+ checker.on_connect_failed do
200
+ if @n_ready_checks >= MAX_N_READ_CHECKS
201
+ on_failure
202
+ else
203
+ timer = Coolio::TimerWatcher.new(1)
204
+ timer.on_timer do
205
+ start_ready_check
206
+ timer.detach
207
+ logger.trace("start_ready_check: timer detached",
208
+ :watcher => timer)
209
+ end
210
+ @loop.attach(timer)
211
+ logger.trace("start_ready_check: timer attached",
212
+ :watcher => timer)
213
+ end
214
+ end
215
+
216
+ @loop.attach(checker)
217
+ logger.trace("start_ready_check: socket attached",
218
+ :watcher => checker)
219
+ end
220
+
221
+ def log_tag
222
+ tag = "serf-agent"
223
+ tag << "[#{@pid}]" if @pid
224
+ tag
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,94 @@
1
+ # Copyright (C) 2014-2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "open3"
17
+ require "pp"
18
+
19
+ require "droonga/loggable"
20
+
21
+ module Droonga
22
+ class Serf
23
+ class Command
24
+ class Failure < Error
25
+ attr_reader :command_line, :exit_status, :output, :error
26
+ def initialize(command_line, exit_status, output, error)
27
+ @command_line = command_line
28
+ @exit_status = exit_status
29
+ @output = output
30
+ @error = error
31
+ message = "Failed to run serf: (#{@exit_status}): "
32
+ message << "#{@error.strip}[#{@output.strip}]: "
33
+ message << @command_line.join(" ")
34
+ super(message)
35
+ end
36
+ end
37
+
38
+ class ForbiddenCommandInEventHandler < Error
39
+ def initialize(command)
40
+ message = "#{command} is forbidden in an event handler script."
41
+ super(message)
42
+ end
43
+ end
44
+
45
+ DANGEROUS_COMMANDS_IN_EVENT_HANDLER = [
46
+ "event",
47
+ "query",
48
+ ]
49
+
50
+ include Loggable
51
+
52
+ attr_accessor :verbose
53
+
54
+ def initialize(serf, command, *options)
55
+ assert_safe_command(command)
56
+ @serf = serf
57
+ @command = command
58
+ @options = options
59
+ @verbose = false
60
+ end
61
+
62
+ def run
63
+ command_line = [@serf, @command] + @options
64
+ p command_line if @verbose
65
+ stdout, stderror, status = Open3.capture3(*command_line,
66
+ :pgroup => true)
67
+ unless status.success?
68
+ raise Failure.new(command_line, status.to_i, stdout, stderror)
69
+ end
70
+ logger.error("run: #{stderror}") unless stderror.empty?
71
+ if @verbose
72
+ begin
73
+ pp JSON.parse(stdout)
74
+ rescue JSON::ParserError
75
+ p stdout
76
+ end
77
+ end
78
+ stdout
79
+ end
80
+
81
+ private
82
+ def assert_safe_command(command)
83
+ if ENV.key?("SERF_EVENT") and
84
+ DANGEROUS_COMMANDS_IN_EVENT_HANDLER.include?(command)
85
+ raise ForbiddenCommandInEventHandler.new(command)
86
+ end
87
+ end
88
+
89
+ def log_tag
90
+ "serf[#{@command}]"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,120 @@
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "stringio"
17
+ require "tmpdir"
18
+ require "fileutils"
19
+
20
+ require "faraday"
21
+ require "faraday_middleware"
22
+ require "archive/zip"
23
+
24
+ require "droonga/loggable"
25
+
26
+ module Droonga
27
+ class Serf
28
+ class Downloader
29
+ include Loggable
30
+
31
+ class DownloadFailed < StandardError
32
+ end
33
+
34
+ MAX_RETRY_COUNT = 5
35
+ RETRY_INTERVAL = 10
36
+
37
+ TARGET_VERSION = "0.6.3"
38
+
39
+ def initialize(output_path)
40
+ @output_path = output_path
41
+ @retry_count = 0
42
+ end
43
+
44
+ def download
45
+ detect_platform
46
+ url_base = "https://dl.bintray.com/mitchellh/serf"
47
+ base_name = "#{TARGET_VERSION}_#{@os}_#{@architecture}.zip"
48
+ connection = Faraday.new(url_base) do |builder|
49
+ builder.response(:follow_redirects)
50
+ builder.adapter(Faraday.default_adapter)
51
+ end
52
+ response = connection.get(base_name)
53
+ absolete_output_path = @output_path.expand_path
54
+ Dir.mktmpdir do |dir|
55
+ Archive::Zip.extract(StringIO.new(response.body),
56
+ dir,
57
+ :directories => false)
58
+ FileUtils.mv("#{dir}/serf", absolete_output_path.to_s)
59
+ FileUtils.chmod(0755, absolete_output_path.to_s)
60
+ end
61
+ rescue Archive::Zip::UnzipError => archive_error
62
+ logger.warn("Downloaded zip file is broken.",
63
+ :detail => archive_error)
64
+ if @retry_count < MAX_RETRY_COUNT
65
+ @retry_count += 1
66
+ sleep(RETRY_INTERVAL * @retry_count)
67
+ download
68
+ else
69
+ raise DownloadFailed.new("Couldn't download serf executable. Try it later.")
70
+ end
71
+ rescue Faraday::ConnectionFailed => network_error
72
+ logger.warn("Connection failed.",
73
+ :detail => network_error)
74
+ if @retry_count < MAX_RETRY_COUNT
75
+ @retry_count += 1
76
+ sleep(RETRY_INTERVAL * @retry_count)
77
+ download
78
+ else
79
+ raise DownloadFailed.new("Couldn't download serf executable. Try it later.")
80
+ end
81
+ end
82
+
83
+ private
84
+ def detect_platform
85
+ detect_os
86
+ detect_architecture
87
+ end
88
+
89
+ def detect_os
90
+ case RUBY_PLATFORM
91
+ when /linux/
92
+ @os = "linux"
93
+ when /freebsd/
94
+ @os = "freebsd"
95
+ when /darwin/
96
+ @os = "darwin"
97
+ when /mswin|mingw/
98
+ @os = "windows"
99
+ else
100
+ raise "Unsupported OS: #{RUBY_PLATFORM}"
101
+ end
102
+ end
103
+
104
+ def detect_architecture
105
+ case RUBY_PLATFORM
106
+ when /x86_64|x64/
107
+ @architecture = "amd64"
108
+ when /i\d86/
109
+ @architecture = "386"
110
+ else
111
+ raise "Unsupported architecture: #{RUBY_PLATFORM}"
112
+ end
113
+ end
114
+
115
+ def log_tag
116
+ "serf-downloader"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,348 @@
1
+ # Copyright (C) 2014-2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "json"
17
+
18
+ require "droonga/path"
19
+ require "droonga/serf"
20
+ require "droonga/node_name"
21
+ require "droonga/catalog/generator"
22
+ require "droonga/catalog/modifier"
23
+ require "droonga/catalog/fetcher"
24
+ require "droonga/safe_file_writer"
25
+ require "droonga/timestamp"
26
+ require "droonga/service_installation"
27
+
28
+ module Droonga
29
+ class Serf
30
+ module RemoteCommand
31
+ class Base
32
+ attr_reader :response
33
+
34
+ def initialize(serf_name, params)
35
+ @serf_name = serf_name
36
+ @params = params
37
+ @response = {
38
+ "log" => []
39
+ }
40
+ @serf = Serf.new(@serf_name)
41
+
42
+ @service_installation = ServiceInstallation.new
43
+ @service_installation.ensure_using_service_base_directory
44
+
45
+ log("params = #{params}")
46
+ end
47
+
48
+ def process
49
+ # override me!
50
+ end
51
+
52
+ def should_process?
53
+ if @params.nil?
54
+ log("anonymous query (to be processed)")
55
+ return true
56
+ end
57
+ unless for_this_cluster?
58
+ log("query for different cluster (mine: #{cluster_id}, to be ignroed)")
59
+ return false
60
+ end
61
+
62
+ unless @params.include?("node")
63
+ log("anonymous node query (to be processed)")
64
+ return true
65
+ end
66
+ unless for_me?
67
+ log("query for different node (me: #{@serf_name}, to be ignored)")
68
+ return false
69
+ end
70
+
71
+ log("query for this node (to be processed)")
72
+ true
73
+ end
74
+
75
+ def log(message)
76
+ @response["log"] << message
77
+ end
78
+
79
+ private
80
+ def node
81
+ @node ||= NodeName.parse(@serf_name)
82
+ end
83
+
84
+ def host
85
+ node.host
86
+ end
87
+
88
+ def cluster_id
89
+ @serf.cluster_id
90
+ end
91
+
92
+ def target_cluster
93
+ return nil unless @params
94
+ @params["cluster_id"]
95
+ end
96
+
97
+ def target_node
98
+ return nil unless @params
99
+ @target_node ||= NodeName.parse(@params["node"] || "")
100
+ rescue ArgumentError
101
+ nil
102
+ end
103
+
104
+ def for_this_cluster?
105
+ target_cluster.nil? or target_cluster == cluster_id
106
+ end
107
+
108
+ def for_me?
109
+ target_node == node
110
+ end
111
+
112
+ def catalog
113
+ @catalog ||= JSON.parse(Path.catalog.read)
114
+ end
115
+ end
116
+
117
+ class ChangeRole < Base
118
+ def process
119
+ log("old role: #{@serf.role}")
120
+ @serf.role = @params["role"]
121
+ log("new role: #{@serf.role}")
122
+ end
123
+ end
124
+
125
+ class ReportLastMessageTimestamp < Base
126
+ def process
127
+ timestamp = Timestamp.last_message_timestamp
128
+ if timestamp
129
+ @response["timestamp"] = Timestamp.stringify(timestamp)
130
+ else
131
+ @response["timestamp"] = nil
132
+ end
133
+ end
134
+ end
135
+
136
+ class AcceptMessagesNewerThan < Base
137
+ def process
138
+ log("old timestamp: #{@serf.accept_messages_newer_than_timestamp}")
139
+ @serf.accept_messages_newer_than(@params["timestamp"])
140
+ log("new timestamp: #{@serf.accept_messages_newer_than_timestamp}")
141
+ end
142
+ end
143
+
144
+ class CrossNodeCommandBase < Base
145
+ private
146
+ def source_node
147
+ return nil unless @params
148
+ @source_node ||= NodeName.parse(@params["source"] || "")
149
+ rescue ArgumentError
150
+ nil
151
+ end
152
+
153
+ def dataset
154
+ @dataset ||= @params["dataset"]
155
+ end
156
+
157
+ def source_host
158
+ source_node.host
159
+ end
160
+
161
+ def port
162
+ @port ||= @params["port"] || source_node.port
163
+ end
164
+
165
+ def tag
166
+ @tag ||= @params["tag"] || source_node.tag
167
+ end
168
+ end
169
+
170
+ class Join < CrossNodeCommandBase
171
+ def process
172
+ log("type = #{type}")
173
+ case type
174
+ when "replica"
175
+ join_as_replica
176
+ end
177
+ end
178
+
179
+ private
180
+ def type
181
+ @params["type"]
182
+ end
183
+
184
+ def joining_node
185
+ target_node
186
+ end
187
+
188
+ def joining_host
189
+ joining_node.host
190
+ end
191
+
192
+ def valid_params?
193
+ not dataset.nil? and
194
+ not source_node.nil? and
195
+ not joining_node.nil?
196
+ end
197
+
198
+ def join_as_replica
199
+ return unless valid_params?
200
+
201
+ log("source_node = #{source_node}")
202
+
203
+ @catalog = fetch_catalog
204
+
205
+ @other_hosts = replica_hosts
206
+ log("other_hosts = #{@other_hosts}")
207
+ return if @other_hosts.empty?
208
+
209
+ join_to_cluster
210
+ end
211
+
212
+ def replica_hosts
213
+ generator = Catalog::Generator.new
214
+ generator.load(catalog)
215
+ dataset = generator.dataset_for_host(source_host) ||
216
+ generator.dataset_for_host(host)
217
+ return [] unless dataset
218
+ dataset.replicas.hosts
219
+ end
220
+
221
+ def fetch_catalog
222
+ fetcher = Catalog::Fetcher.new(:host => source_host,
223
+ :port => port,
224
+ :tag => tag,
225
+ :receiver_host => host)
226
+ fetcher.fetch(:dataset => dataset)
227
+ end
228
+
229
+ def join_to_cluster
230
+ log("joining to the cluster")
231
+ @serf.join(*@other_hosts)
232
+
233
+ log("update catalog.json from fetched catalog")
234
+ Catalog::Modifier.new(catalog).modify do |modifier, file|
235
+ modifier.datasets[dataset].replicas.hosts += [joining_host]
236
+ modifier.datasets[dataset].replicas.hosts.uniq!
237
+ @service_installation.ensure_correct_file_permission(file)
238
+ end
239
+ log("done")
240
+ end
241
+ end
242
+
243
+ class ModifyReplicasBase < Base
244
+ private
245
+ def dataset
246
+ @params["dataset"]
247
+ end
248
+
249
+ def hosts
250
+ @hosts ||= prepare_hosts
251
+ end
252
+
253
+ def prepare_hosts
254
+ hosts = @params["hosts"]
255
+ return nil unless hosts
256
+ hosts = [hosts] if hosts.is_a?(String)
257
+ hosts
258
+ end
259
+ end
260
+
261
+ class SetReplicas < ModifyReplicasBase
262
+ def process
263
+ return if dataset.nil? or hosts.nil?
264
+
265
+ log("new replicas: #{hosts.join(",")}")
266
+
267
+ log("joining to the cluster")
268
+ @serf.join(*hosts)
269
+
270
+ log("setting replicas to the cluster")
271
+ Catalog::Modifier.new(catalog).modify do |modifier, file|
272
+ modifier.datasets[dataset].replicas.hosts = hosts
273
+ @service_installation.ensure_correct_file_permission(file)
274
+ end
275
+ log("done")
276
+ end
277
+ end
278
+
279
+ class AddReplicas < ModifyReplicasBase
280
+ def process
281
+ return if dataset.nil? or hosts.nil?
282
+
283
+ added_hosts = hosts - [host]
284
+ log("adding replicas: #{added_hosts.join(",")}")
285
+ return if added_hosts.empty?
286
+
287
+ log("joining to the cluster")
288
+ @serf.join(*added_hosts)
289
+
290
+ log("adding replicas to the cluster")
291
+ Catalog::Modifier.new(catalog).modify do |modifier, file|
292
+ modifier.datasets[dataset].replicas.hosts += added_hosts
293
+ modifier.datasets[dataset].replicas.hosts.uniq!
294
+ @service_installation.ensure_correct_file_permission(file)
295
+ end
296
+ log("done")
297
+ end
298
+ end
299
+
300
+ class RemoveReplicas < ModifyReplicasBase
301
+ def process
302
+ return if dataset.nil? or hosts.nil?
303
+
304
+ log("removing replicas: #{hosts.join(",")}")
305
+
306
+ log("removing replicas from the cluster")
307
+ Catalog::Modifier.new(catalog).modify do |modifier, file|
308
+ modifier.datasets[dataset].replicas.hosts -= hosts
309
+ @service_installation.ensure_correct_file_permission(file)
310
+ end
311
+ log("done")
312
+ end
313
+ end
314
+
315
+ class Unjoin < ModifyReplicasBase
316
+ def process
317
+ return if dataset.nil? or hosts.nil?
318
+
319
+ log("unjoining replicas: #{hosts.join(",")}")
320
+
321
+ log("unjoining from the cluster")
322
+ Catalog::Modifier.new(catalog).modify do |modifier, file|
323
+ if unjoining_node?
324
+ modifier.datasets[dataset].replicas.hosts = hosts
325
+ else
326
+ modifier.datasets[dataset].replicas.hosts -= hosts
327
+ end
328
+ @service_installation.ensure_correct_file_permission(file)
329
+ end
330
+ log("done")
331
+ end
332
+
333
+ private
334
+ def unjoining_node?
335
+ hosts.include?(host)
336
+ end
337
+ end
338
+
339
+ class UpdateClusterState < Base
340
+ def process
341
+ log("updating cluster state")
342
+ @serf.update_cluster_state
343
+ log("done")
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end