droonga-engine 1.0.9 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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