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
@@ -37,6 +37,7 @@ module Droonga
37
37
 
38
38
  def initialize
39
39
  @engine_name = nil
40
+ @internal_connection_lifetime = nil
40
41
  @listen_fd = nil
41
42
  @heartbeat_fd = nil
42
43
  @contrtol_read_fd = nil
@@ -85,6 +86,10 @@ module Droonga
85
86
  "Use NAME as the name of the engine") do |name|
86
87
  @engine_name = name
87
88
  end
89
+ parser.on("--internal-connection-lifetime=SECONDS", Float,
90
+ "The time to expire internal connections, in seconds") do |seconds|
91
+ @internal_connection_lifetime = seconds
92
+ end
88
93
  parser.on("--listen-fd=FD", Integer,
89
94
  "Use FD as the listen file descriptor") do |fd|
90
95
  @listen_fd = fd
@@ -129,18 +134,31 @@ module Droonga
129
134
 
130
135
  def create_internal_message_receiver
131
136
  InternalFluentMessageReceiver.new(@loop, host) do |tag, time, record|
137
+ logger.trace("InternalFluentMessageReceiver receive")
132
138
  on_message(tag, time, record)
133
139
  end
134
140
  end
135
141
 
136
- def shutdown_internal_message_receiver
142
+ def shutdown_internal_message_receiver_gracefully
143
+ if @internal_message_receiver.nil?
144
+ yield
145
+ return
146
+ end
147
+ @internal_message_receiver, receiver = nil, @internal_message_receiver
148
+ receiver.shutdown_gracefully do
149
+ yield
150
+ end
151
+ end
152
+
153
+ def shutdown_internal_message_receiver_immediately
137
154
  return if @internal_message_receiver.nil?
138
155
  @internal_message_receiver, receiver = nil, @internal_message_receiver
139
- receiver.shutdown
156
+ receiver.shutdown_immediately
140
157
  end
141
158
 
142
159
  def run_engine
143
- @engine = Engine.new(@loop, @engine_name, @internal_engine_name)
160
+ @engine = Engine.new(@loop, @engine_name, @internal_engine_name,
161
+ :internal_connection_lifetime => @internal_connection_lifetime)
144
162
  @engine.on_ready = lambda do
145
163
  @worker_process_agent.ready
146
164
  end
@@ -164,6 +182,9 @@ module Droonga
164
182
  @worker_process_agent.on_stop_immediately = lambda do
165
183
  stop_immediately
166
184
  end
185
+ @worker_process_agent.on_refresh_self_reference = lambda do
186
+ @engine.refresh_self_reference
187
+ end
167
188
  @worker_process_agent.start
168
189
  end
169
190
 
@@ -177,11 +198,14 @@ module Droonga
177
198
  :heartbeat_fd => @heartbeat_fd,
178
199
  }
179
200
  FluentMessageReceiver.new(@loop, options) do |tag, time, record|
201
+ logger.trace("FluentMessageReceiver receive")
180
202
  on_message(tag, time, record)
181
203
  end
182
204
  end
183
205
 
184
206
  def on_message(tag, time, record)
207
+ logger.trace("on_message: start", :record => record)
208
+
185
209
  prefix, type, *arguments = tag.split(/\./)
186
210
  if type.nil? or type.empty? or type == "message"
187
211
  message = record
@@ -201,6 +225,8 @@ module Droonga
201
225
  end
202
226
 
203
227
  @engine.process(message)
228
+
229
+ logger.trace("on_message: done")
204
230
  end
205
231
 
206
232
  def stop_gracefully
@@ -208,13 +234,23 @@ module Droonga
208
234
  logger.trace("stop_gracefully: start")
209
235
  @stopping = true
210
236
  @receiver.stop_gracefully
211
- @engine.stop_gracefully do
212
- shutdown_worker_process_agent
213
- shutdown_internal_message_receiver
214
- @receiver.shutdown_clients
215
- logger.trace("stop_gracefully: done",
216
- :n_rest_watchers => @loop.watchers.size,
217
- :rest_watchers => @loop.watchers)
237
+ #XXX To disconnect all clients to myself (old service),
238
+ # we must refresh the connection via EngineNode
239
+ # and Forwarder.
240
+ # However, connections from workers can be still
241
+ # there. Then we have to wait for their timeout.
242
+ @engine.refresh_self_reference
243
+ @receiver.ensure_no_client do
244
+ logger.trace("stop_gracefully: ready to stop service")
245
+ @engine.stop_gracefully do
246
+ logger.trace("stop_gracefully: ready to stop workers")
247
+ shutdown_worker_process_agent
248
+ shutdown_internal_message_receiver_gracefully do
249
+ logger.trace("stop_gracefully: done",
250
+ :n_rest_watchers => @loop.watchers.size,
251
+ :rest_watchers => @loop.watchers)
252
+ end
253
+ end
218
254
  end
219
255
  end
220
256
 
@@ -222,7 +258,7 @@ module Droonga
222
258
  def stop_immediately
223
259
  shutdown_worker_process_agent
224
260
  @receiver.stop_immediately
225
- shutdown_internal_message_receiver
261
+ shutdown_internal_message_receiver_immediately
226
262
  @engine.stop_immediately
227
263
  @loop.stop
228
264
  end
@@ -40,9 +40,11 @@ module Droonga
40
40
  @contrtol_read_fd = nil
41
41
  @contrtol_write_fd = nil
42
42
  @pid_file_path = nil
43
+ @label = nil
43
44
  @dataset = nil
44
45
  @database_path = nil
45
46
  @plugins = []
47
+ @internal_connection_lifetime = nil
46
48
  @worker_process_agent = nil
47
49
  end
48
50
 
@@ -90,6 +92,10 @@ module Droonga
90
92
  "Put PID to PATH") do |path|
91
93
  @pid_file_path = Pathname.new(path)
92
94
  end
95
+ parser.on("--label=LABEL",
96
+ "Use given value as the label") do |label|
97
+ @label = label
98
+ end
93
99
  parser.on("--dataset=DATASET",
94
100
  "Process DATASET") do |dataset|
95
101
  @dataset = dataset
@@ -102,6 +108,10 @@ module Droonga
102
108
  "Use PLUGINs") do |plugins|
103
109
  @plugins = plugins
104
110
  end
111
+ parser.on("--internal-connection-lifetime=SECONDS", Float,
112
+ "The time to expire internal connections, in seconds") do |seconds|
113
+ @internal_connection_lifetime = seconds
114
+ end
105
115
  end
106
116
 
107
117
  def write_pid_file
@@ -159,8 +169,14 @@ module Droonga
159
169
  @loop.stop
160
170
  end
161
171
 
172
+ def refresh_node_reference
173
+ @forwarder.refresh_all_connections
174
+ end
175
+
162
176
  def start_forwarder
163
- @forwarder = Forwarder.new(@loop)
177
+ @forwarder = Forwarder.new(@loop,
178
+ :auto_close_timeout =>
179
+ @internal_connection_lifetime)
164
180
  @forwarder.start
165
181
  end
166
182
 
@@ -170,6 +186,7 @@ module Droonga
170
186
 
171
187
  def start_handler_runner
172
188
  options = {
189
+ :label => @label,
173
190
  :forwarder => @forwarder,
174
191
  :dataset => @dataset,
175
192
  :database => @database_path.to_s,
@@ -216,6 +233,9 @@ module Droonga
216
233
  @worker_process_agent.on_stop_immediately = lambda do
217
234
  stop_immediately
218
235
  end
236
+ @worker_process_agent.on_refresh_node_reference = lambda do
237
+ refresh_node_reference
238
+ end
219
239
  @worker_process_agent.start
220
240
  @worker_process_agent.ready
221
241
  end
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require "slop"
19
+
20
+ require "droonga/engine/version"
21
+ require "droonga/node_name"
22
+ require "droonga/serf"
23
+
24
+ module Droonga
25
+ module Command
26
+ class RemoteCommandBase
27
+ private
28
+ def parse_options(&block)
29
+ options = Slop.parse(:help => true) do |option|
30
+ yield(option) if block_given?
31
+
32
+ option.separator("Connections:")
33
+ option.on(:host=,
34
+ "Host name of the target node.",
35
+ :required => true)
36
+ option.on(:port=,
37
+ "Port number of the source cluster to be connected.",
38
+ :as => Integer,
39
+ :default => NodeName::DEFAULT_PORT)
40
+ option.on(:tag=,
41
+ "Tag name of the soruce cluster to be connected.",
42
+ :default => NodeName::DEFAULT_TAG)
43
+
44
+ option.separator("Miscellaneous:")
45
+ option.on(:verbose, "Output details for internal operations.",
46
+ :default => false)
47
+ end
48
+ @options = options
49
+ rescue Slop::MissingOptionError => error
50
+ $stderr.puts(error)
51
+ exit(false)
52
+ end
53
+
54
+ def host
55
+ @options[:host]
56
+ end
57
+
58
+ def port
59
+ @options[:port]
60
+ end
61
+
62
+ def tag
63
+ @options[:tag]
64
+ end
65
+
66
+ def node
67
+ @node ||= NodeName.new(:host => host,
68
+ :port => port,
69
+ :tag => tag)
70
+ end
71
+
72
+ def serf
73
+ @serf ||= Serf.new(node.to_s,
74
+ :verbose => @options[:verbose])
75
+ end
76
+ end
77
+ end
78
+ end
@@ -16,7 +16,7 @@
16
16
  require "json"
17
17
  require "fileutils"
18
18
 
19
- require "droonga/command/remote"
19
+ require "droonga/serf/remote_command"
20
20
 
21
21
  module Droonga
22
22
  module Command
@@ -37,17 +37,28 @@ module Droonga
37
37
 
38
38
  serf_name = ENV["SERF_SELF_NAME"]
39
39
  command = command_class.new(serf_name, @payload)
40
- command.process if command.should_process?
41
- output_response(command.response)
40
+ begin
41
+ command.process if command.should_process?
42
+ rescue Exception => exception
43
+ command.log("Exception: #{exception.inspect}, #{exception.message}, #{exception.backtrace.join(", ")}")
44
+ raise exception
45
+ ensure
46
+ output_response(command.response)
47
+ end
42
48
  true
43
49
  rescue Exception => exception
44
50
  #XXX Any exception blocks following serf operations.
45
51
  # To keep it working, I rescue any exception for now.
46
- FileUtils.mkdir_p(Path.serf_event_handler_errors)
47
- File.open(Path.serf_event_handler_error_file, "w") do |file|
48
- file.write(exception.inspect)
49
- file.write(exception.backtrace)
52
+ begin
53
+ FileUtils.mkdir_p(Path.serf_event_handler_errors)
54
+ File.open(Path.serf_event_handler_error_file, "w") do |file|
55
+ file.write(exception.inspect)
56
+ file.write(exception.backtrace)
57
+ end
58
+ rescue Errno::EACCES => permission_denied_exception
50
59
  end
60
+ puts exception.inspect
61
+ puts exception.backtrace
51
62
  true
52
63
  end
53
64
 
@@ -61,7 +72,7 @@ module Droonga
61
72
  @payload = JSON.parse($stdin.gets)
62
73
  detect_command_class_from_custom_event(ENV["SERF_QUERY_NAME"])
63
74
  when "member-join", "member-leave", "member-update", "member-reap"
64
- Remote::UpdateLiveNodes
75
+ Serf::RemoteCommand::UpdateClusterState
65
76
  else
66
77
  nil
67
78
  end
@@ -70,23 +81,21 @@ module Droonga
70
81
  def detect_command_class_from_custom_event(event_name)
71
82
  case event_name
72
83
  when "change_role"
73
- Remote::ChangeRole
74
- when "report_status"
75
- Remote::ReportStatus
76
- when "set_status"
77
- Remote::SetStatus
84
+ Serf::RemoteCommand::ChangeRole
85
+ when "report_last_message_timestamp"
86
+ Serf::RemoteCommand::ReportLastMessageTimestamp
87
+ when "accept_messages_newer_than"
88
+ Serf::RemoteCommand::AcceptMessagesNewerThan
78
89
  when "join"
79
- Remote::Join
90
+ Serf::RemoteCommand::Join
80
91
  when "unjoin"
81
- Remote::Unjoin
92
+ Serf::RemoteCommand::Unjoin
82
93
  when "set_replicas"
83
- Remote::SetReplicas
94
+ Serf::RemoteCommand::SetReplicas
84
95
  when "add_replicas"
85
- Remote::AddReplicas
96
+ Serf::RemoteCommand::AddReplicas
86
97
  when "remove_replicas"
87
- Remote::RemoveReplicas
88
- when "absorb_data"
89
- Remote::AbsorbData
98
+ Serf::RemoteCommand::RemoveReplicas
90
99
  else
91
100
  nil
92
101
  end
@@ -0,0 +1,222 @@
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 "droonga/loggable"
17
+ require "droonga/node_name"
18
+ require "droonga/catalog/dataset"
19
+ require "droonga/client"
20
+ require "droonga/catalog/generator"
21
+ require "droonga/catalog/fetcher"
22
+
23
+ module Droonga
24
+ class DataAbsorberClient
25
+ include Loggable
26
+
27
+ class DestinationEqualsToSource < StandardError
28
+ def initialize(params)
29
+ super("The source and the destination are same", params)
30
+ end
31
+ end
32
+
33
+ class EmptyResponse < StandardError
34
+ end
35
+
36
+ class EmptyBody < StandardError
37
+ end
38
+
39
+ DEFAULT_MESSAGES_PER_SECOND = 100
40
+ DEFAULT_PROGRESS_INTERVAL_SECONDS = 3
41
+
42
+ DEFAULT_HOST = NodeName::DEFAULT_HOST
43
+ DEFAULT_PORT = NodeName::DEFAULT_PORT
44
+ DEFAULT_TAG = NodeName::DEFAULT_TAG
45
+ DEFAULT_DATASET = Catalog::Dataset::DEFAULT_NAME
46
+
47
+ attr_reader :params
48
+ attr_reader :host, :port, :tag, :dataset
49
+ attr_reader :messages_per_second, :progress_interval_seconds
50
+ attr_reader :source_host, :source_port, :source_tag, :source_dataset
51
+ attr_reader :error_message
52
+
53
+ def initialize(params)
54
+ @params = params
55
+
56
+ @messages_per_second = @params[:messages_per_second] ||
57
+ DEFAULT_MESSAGES_PER_SECOND
58
+ @progress_interval_seconds = @params[:progress_interval_seconds] ||
59
+ DEFAULT_PROGRESS_INTERVAL_SECONDS
60
+ @target_role = @params[:target_role]
61
+
62
+ @host = @params[:host] || DEFAULT_HOST
63
+ @port = @params[:port] || DEFAULT_PORT
64
+ @tag = @params[:tag] || DEFAULT_TAG
65
+ @dataset = @params[:dataset] || DEFAULT_DATASET
66
+
67
+ @source_host = @params[:source_host] || @host || DEFAULT_HOST
68
+ @source_port = @params[:source_port] || @port || DEFAULT_PORT
69
+ @source_tag = @params[:source_tag] || @tag || DEFAULT_TAG
70
+ @source_dataset = @params[:source_dataset] || @dataset || DEFAULT_DATASET
71
+
72
+ @receiver_host = @params[:receiver_host] || @host
73
+ @receiver_port = @params[:receiver_port] || 0
74
+
75
+ @client_options = @params[:client_options] || {}
76
+
77
+ @error_message = nil
78
+
79
+ validate_params
80
+ end
81
+
82
+ def run
83
+ n_absorbers = 0
84
+
85
+ absorb_message = {
86
+ "type" => "system.absorb-data",
87
+ "dataset" => @dataset,
88
+ "body" => {
89
+ "host" => @source_host,
90
+ "port" => @source_port,
91
+ "tag" => @source_tag,
92
+ "dataset" => @source_dataset,
93
+ "messagesPerSecond" => @messages_per_second,
94
+ "progressIntervalSeconds" => @progress_interval_seconds,
95
+ "targetRole" => @target_role,
96
+ },
97
+ }
98
+ client = Droonga::Client.new(destination_client_options)
99
+ client.subscribe(absorb_message) do |message|
100
+ case message
101
+ when Droonga::Client::Error
102
+ client.close
103
+ @error_message = message.to_s
104
+ else
105
+ case message["type"]
106
+ when "system.absorb-data.result", "system.absorb-data.error"
107
+ if message["statusCode"] != 200
108
+ client.close
109
+ error = message["body"]
110
+ @error_message = "#{error['name']}: #{error['message']}"
111
+ end
112
+ when "system.absorb-data.progress"
113
+ body = message["body"]
114
+ yield(:n_processed_messages => body["nProcessedMessages"],
115
+ :percentage => body["percentage"],
116
+ :message => body["message"])
117
+ when "system.absorb-data.start"
118
+ n_absorbers += 1
119
+ when "system.absorb-data.end"
120
+ n_absorbers -= 1
121
+ client.close if n_absorbers <= 0
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def source_node_suspendable?
128
+ (source_replica_hosts - [@source_host]).size >= 1
129
+ end
130
+
131
+ def empty_destination?
132
+ table_names_in_destination_node.empty?
133
+ end
134
+
135
+ private
136
+ def validate_params
137
+ source_node_name = NodeName.new(:host => @source_host,
138
+ :port => @source_port,
139
+ :tag => @source_tag)
140
+ destination_node_name = NodeName.new(:host => @host,
141
+ :port => @port,
142
+ :tag => @tag)
143
+ if source_node_name == destination_node_name and
144
+ @source_dataset == @dataset
145
+ raise DestinationEqualsToSource.new(:host => @host,
146
+ :port => @port,
147
+ :tag => @tag,
148
+ :dataset => @dataset)
149
+ end
150
+ end
151
+
152
+ def destination_client_options
153
+ {
154
+ :host => @host,
155
+ :port => @port,
156
+ :tag => @tag,
157
+ :protocol => :droonga,
158
+ :receiver_host => @receiver_host,
159
+ :receiver_port => @receiver_port,
160
+ }.merge(@client_options)
161
+ end
162
+
163
+ def synchronous_destination_client_options
164
+ destination_client_options.merge(:backend => :thread,
165
+ :loop => nil)
166
+ end
167
+
168
+ def table_names_in_destination_node
169
+ @table_names_in_destination_node ||= get_table_names_in_destination_node
170
+ end
171
+
172
+ def get_table_names_in_destination_node
173
+ response = nil
174
+ Droonga::Client.open(synchronous_destination_client_options) do |client|
175
+ response = client.request("dataset" => @source_dataset,
176
+ "type" => "table_list")
177
+ end
178
+
179
+ unless response
180
+ raise EmptyResponse.new("table_list returns nil response")
181
+ end
182
+ unless response["body"]
183
+ raise EmptyBody.new("table_list returns nil result")
184
+ end
185
+
186
+ message_body = response["body"]
187
+ body = message_body[1]
188
+ tables = body[1..-1]
189
+ tables.collect do |table|
190
+ table[1]
191
+ end
192
+ end
193
+
194
+ def source_replica_hosts
195
+ @source_replica_hosts ||= get_source_replica_hosts
196
+ end
197
+
198
+ def get_source_replica_hosts
199
+ generator = Catalog::Generator.new
200
+ generator.load(source_catalog)
201
+ dataset = generator.dataset_for_host(@source_host)
202
+ return [] unless dataset
203
+ dataset.replicas.hosts
204
+ end
205
+
206
+ def source_catalog
207
+ @source_catalog ||= fetch_source_catalog
208
+ end
209
+
210
+ def fetch_source_catalog
211
+ fetcher = Catalog::Fetcher.new(:host => @source_host,
212
+ :port => @source_port,
213
+ :tag => @source_tag,
214
+ :receiver_host => @receiver_host)
215
+ fetcher.fetch(:dataset => @source_dataset)
216
+ end
217
+
218
+ def log_tag
219
+ "data-absorber"
220
+ end
221
+ end
222
+ end