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