droonga-engine 1.0.7 → 1.0.8

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/bin/droonga-engine-absorb-data +200 -86
  4. data/bin/droonga-engine-configure +14 -11
  5. data/bin/droonga-engine-join +178 -59
  6. data/droonga-engine.gemspec +1 -1
  7. data/install.sh +1 -0
  8. data/lib/droonga/buffered_tcp_socket.rb +2 -2
  9. data/lib/droonga/catalog/base.rb +18 -0
  10. data/lib/droonga/catalog/dataset.rb +8 -0
  11. data/lib/droonga/catalog/version1.rb +3 -12
  12. data/lib/droonga/catalog/version2.rb +5 -12
  13. data/lib/droonga/command/droonga_engine.rb +6 -61
  14. data/lib/droonga/command/droonga_engine_service.rb +1 -0
  15. data/lib/droonga/command/droonga_engine_worker.rb +1 -0
  16. data/lib/droonga/command/remote.rb +18 -1
  17. data/lib/droonga/command/serf_event_handler.rb +3 -0
  18. data/lib/droonga/data_absorber.rb +234 -44
  19. data/lib/droonga/distributed_command_planner.rb +5 -5
  20. data/lib/droonga/engine.rb +27 -15
  21. data/lib/droonga/engine/version.rb +1 -1
  22. data/lib/droonga/handler_runner.rb +4 -0
  23. data/lib/droonga/node_status.rb +6 -2
  24. data/lib/droonga/path.rb +8 -14
  25. data/lib/droonga/planner.rb +4 -3
  26. data/lib/droonga/plugin/metadata/handler_action.rb +8 -0
  27. data/lib/droonga/plugins/groonga/column_create.rb +1 -1
  28. data/lib/droonga/plugins/groonga/column_list.rb +23 -1
  29. data/lib/droonga/plugins/groonga/column_remove.rb +1 -1
  30. data/lib/droonga/plugins/groonga/column_rename.rb +1 -1
  31. data/lib/droonga/plugins/groonga/delete.rb +1 -1
  32. data/lib/droonga/plugins/groonga/select.rb +17 -2
  33. data/lib/droonga/plugins/groonga/table_create.rb +26 -1
  34. data/lib/droonga/plugins/groonga/table_remove.rb +1 -1
  35. data/lib/droonga/plugins/search.rb +1 -1
  36. data/lib/droonga/plugins/search/distributed_search_planner.rb +15 -7
  37. data/lib/droonga/processor.rb +3 -2
  38. data/lib/droonga/searcher.rb +31 -15
  39. data/lib/droonga/serf.rb +1 -0
  40. data/lib/droonga/service_installation.rb +2 -2
  41. data/lib/droonga/single_step.rb +2 -2
  42. data/test/command/fixture/event.jsons +3 -2
  43. data/test/command/fixture/user-table.jsons +3 -2
  44. data/test/command/fixture/users.jsons +25 -0
  45. data/test/command/run-test.rb +13 -1
  46. data/test/command/suite/groonga/column_create/scalar.test +3 -2
  47. data/test/command/suite/groonga/column_create/vector.test +3 -2
  48. data/test/command/suite/groonga/column_list/{success.expected → no-key.expected} +0 -0
  49. data/test/command/suite/groonga/column_list/{success.test → no-key.test} +1 -1
  50. data/test/command/suite/groonga/column_list/with-key.expected +96 -0
  51. data/test/command/suite/groonga/column_list/with-key.test +25 -0
  52. data/test/command/suite/groonga/column_remove/success.test +3 -2
  53. data/test/command/suite/groonga/column_remove/unknown-column.test +3 -2
  54. data/test/command/suite/groonga/column_rename/success.test +3 -2
  55. data/test/command/suite/groonga/column_rename/unknown-column.test +3 -2
  56. data/test/command/suite/groonga/delete/duplicated-identifiers.test +3 -2
  57. data/test/command/suite/groonga/delete/no-identifier.test +3 -2
  58. data/test/command/suite/groonga/select/output_columns/default/array.expected +33 -0
  59. data/test/command/suite/groonga/select/output_columns/default/array.test +38 -0
  60. data/test/command/suite/groonga/select/output_columns/nonexistent.expected +28 -0
  61. data/test/command/suite/groonga/select/output_columns/nonexistent.test +26 -0
  62. data/test/command/suite/groonga/table_create/dat-without-key-type.expected +14 -0
  63. data/test/command/suite/groonga/table_create/dat-without-key-type.test +8 -0
  64. data/test/command/suite/groonga/table_create/dat.expected +13 -0
  65. data/test/command/suite/groonga/table_create/dat.test +9 -0
  66. data/test/command/suite/groonga/table_create/hash-without-key-type.expected +14 -0
  67. data/test/command/suite/groonga/table_create/hash-without-key-type.test +8 -0
  68. data/test/command/suite/groonga/table_create/hash.test +3 -2
  69. data/test/command/suite/groonga/table_create/pat-without-key-type.expected +14 -0
  70. data/test/command/suite/groonga/table_create/pat-without-key-type.test +8 -0
  71. data/test/command/suite/groonga/table_create/pat.expected +13 -0
  72. data/test/command/suite/groonga/table_create/pat.test +9 -0
  73. data/test/command/suite/groonga/table_list/success.test +3 -2
  74. data/test/unit/catalog/test_version1.rb +2 -2
  75. data/test/unit/catalog/test_version2.rb +3 -3
  76. data/test/unit/helper.rb +2 -2
  77. data/test/unit/helper/distributed_search_planner_helper.rb +9 -1
  78. data/test/unit/plugins/groonga/select/test_adapter_input.rb +15 -2
  79. data/test/unit/plugins/groonga/test_column_list.rb +119 -4
  80. data/test/unit/plugins/groonga/test_table_create.rb +29 -0
  81. data/test/unit/plugins/search/planner/test_basic.rb +2 -2
  82. data/test/unit/plugins/search/test_planner.rb +10 -2
  83. metadata +43 -8
@@ -38,7 +38,7 @@ Gem::Specification.new do |gem|
38
38
  gem.add_dependency "archive-zip"
39
39
  gem.add_dependency "cool.io"
40
40
  gem.add_dependency "drndump"
41
- gem.add_dependency "droonga-client", ">= 0.1.9"
41
+ gem.add_dependency "droonga-client", ">= 0.2.0"
42
42
  gem.add_dependency "droonga-message-pack-packer", ">= 1.0.2"
43
43
  gem.add_dependency "groonga-command-parser"
44
44
  gem.add_dependency "faraday"
data/install.sh CHANGED
@@ -133,6 +133,7 @@ setup_configuration_directory() {
133
133
  echo "This node is configured with a hostname $HOST."
134
134
  fi
135
135
 
136
+ # we should use --no-prompt instead of --quiet, for droonga-engine 1.0.8 and later.
136
137
  droonga-engine-configure --quiet \
137
138
  --host=$HOST --port=$PORT
138
139
  if [ $? -ne 0 ]; then
@@ -140,14 +140,14 @@ module Droonga
140
140
 
141
141
  def written_partial(size)
142
142
  written
143
- @data = @data[size..-1]
143
+ @data = data[size..-1]
144
144
  @revision += 1
145
145
  buffering
146
146
  end
147
147
 
148
148
  private
149
149
  def path
150
- @data_directory + "#{@time_stamp.iso8601(6)}.#{@revision}.chunk"
150
+ @data_directory + "#{@time_stamp.iso8601(6)}.#{@revision}.#{SUFFIX}"
151
151
  end
152
152
  end
153
153
  end
@@ -36,6 +36,24 @@ module Droonga
36
36
  def dataset(name)
37
37
  datasets[name]
38
38
  end
39
+
40
+ private
41
+ def migrate_database_location(current_db_path, device, name)
42
+ return if current_db_path.exist?
43
+
44
+ common_base_path = Pathname(@base_path)
45
+ old_db_paths = {
46
+ :top_level => common_base_path + device + name + "db",
47
+ :singular_form => common_base_path + device + "database" + name + "db",
48
+ }
49
+ old_db_paths.each do |type, old_db_path|
50
+ if old_db_path.exist?
51
+ FileUtils.mkdir_p(current_db_path.parent.parent)
52
+ FileUtils.move(old_db_path.parent, current_db_path.parent)
53
+ return
54
+ end
55
+ end
56
+ end
39
57
  end
40
58
  end
41
59
  end
@@ -85,6 +85,14 @@ module Droonga
85
85
  routes
86
86
  end
87
87
 
88
+ def single_slice?
89
+ # TODO: Support slice key
90
+ replicas.all? do |volume|
91
+ volume.is_a?(SingleVolume) or
92
+ volume.slices.size == 1
93
+ end
94
+ end
95
+
88
96
  private
89
97
  def create_volumes(raw_volumes)
90
98
  raw_volumes.collect do |raw_volume|
@@ -54,10 +54,9 @@ module Droonga
54
54
  partitions.each do |partition|
55
55
  if partition =~ pattern
56
56
  database_name = $POSTMATCH
57
- path = File.join([device, Path.databases.basename.to_s, database_name, "db"])
58
- path = Pathname(path).expand_path(base_path)
59
- migrate_database_location(path, :device => device,
60
- :name => database_name)
57
+ path = Path.databases(base_path) +
58
+ device + database_name + "db"
59
+ migrate_database_location(path, device, database_name)
61
60
  options = {
62
61
  :dataset => dataset_name,
63
62
  :database => path.to_s,
@@ -389,14 +388,6 @@ module Droonga
389
388
  end
390
389
  end
391
390
 
392
- def migrate_database_location(path, params)
393
- old_path = File.join([params[:device], params[:name], "db"])
394
- old_path = Pathname(old_path).expand_path(base_path)
395
- if old_path.exist? and not path.exist?
396
- FileUtils.move(old_path.to_s, path.to_s)
397
- end
398
- end
399
-
400
391
  class Dataset < Catalog::Dataset
401
392
  def compute_routes(args, live_nodes=nil)
402
393
  routes = []
@@ -44,10 +44,11 @@ module Droonga
44
44
  volume.slices.each do |slice|
45
45
  volume_address = slice.volume.address
46
46
  if volume_address.node == node
47
- path = File.join([device, Path.databases.basename.to_s, volume_address.name, "db"])
48
- path = Pathname(path).expand_path(base_path)
49
- migrate_database_location(path, :device => device,
50
- :name => volume_address.name)
47
+ name = volume_address.name
48
+ path = Path.databases(base_path) +
49
+ device + name + "db"
50
+ migrate_database_location(path, device, name)
51
+
51
52
  options = {
52
53
  :dataset => dataset_name,
53
54
  :database => path.to_s,
@@ -86,14 +87,6 @@ module Droonga
86
87
  end
87
88
  nodes.sort.uniq
88
89
  end
89
-
90
- def migrate_database_location(path, params)
91
- old_path = File.join([params[:device], params[:name], "db"])
92
- old_path = Pathname(old_path).expand_path(base_path)
93
- if old_path.exist? and not path.exist?
94
- FileUtils.move(old_path.to_s, path.to_s)
95
- end
96
- end
97
90
  end
98
91
  end
99
92
  end
@@ -20,7 +20,7 @@ require "fileutils"
20
20
  require "yaml"
21
21
 
22
22
  require "coolio"
23
- require "sigdump"
23
+ require "sigdump/setup"
24
24
 
25
25
  require "droonga/engine/version"
26
26
  require "droonga/path"
@@ -114,7 +114,7 @@ module Droonga
114
114
 
115
115
  class Configuration
116
116
  attr_reader :host, :port, :tag, :log_file, :pid_file_path
117
- attr_reader :ready_notify_fd, :orchestrated
117
+ attr_reader :ready_notify_fd
118
118
  def initialize
119
119
  config = load_config
120
120
 
@@ -122,23 +122,13 @@ module Droonga
122
122
  @port = config["port"] || Address::DEFAULT_PORT
123
123
  @tag = config["tag"] || Address::DEFAULT_TAG
124
124
 
125
- @given_values = {}
126
-
127
125
  @log_file = nil
128
126
  @daemon = false
129
127
  @pid_file_path = nil
130
128
  @ready_notify_fd = nil
131
- @orchestrated = true
132
129
 
133
130
  if have_config_file?
134
- if config.include?("daemon")
135
- @daemon = config["daemon"]
136
- else
137
- @daemon = true
138
- end
139
- if @daemon
140
- self.pid_file_path = config["pid_file"] || Path.default_pid_file
141
- end
131
+ self.pid_file_path = config["pid_file"] if config["pid_file"]
142
132
  self.log_file = config["log_file"] || Path.default_log_file
143
133
  self.log_level = config["log_level"] if config.include?("log_level")
144
134
  end
@@ -148,34 +138,6 @@ module Droonga
148
138
  File.exist?(Path.config)
149
139
  end
150
140
 
151
- def have_given_host?
152
- @given_values.include?(:host)
153
- end
154
-
155
- def have_given_port?
156
- @given_values.include?(:port)
157
- end
158
-
159
- def have_given_tag?
160
- @given_values.include?(:tag)
161
- end
162
-
163
- def have_given_daemon?
164
- @given_values.include?(:daemon)
165
- end
166
-
167
- def have_given_log_file?
168
- @given_values.include?(:log_file)
169
- end
170
-
171
- def have_given_log_level?
172
- @given_values.include?(:log_level)
173
- end
174
-
175
- def have_given_pid_file?
176
- @given_values.include?(:pid_file)
177
- end
178
-
179
141
  def load_config
180
142
  if have_config_file?
181
143
  YAML.load_file(Path.config)
@@ -225,7 +187,6 @@ module Droonga
225
187
  add_log_options(parser)
226
188
  add_process_options(parser)
227
189
  add_path_options(parser)
228
- add_orchestration_options(parser)
229
190
  add_notification_options(parser)
230
191
  end
231
192
 
@@ -245,19 +206,16 @@ module Droonga
245
206
  "The host name of the Droonga engine",
246
207
  "(#{@host})") do |host|
247
208
  @host = host
248
- @given_values[:host] = host
249
209
  end
250
210
  parser.on("--port=PORT", Integer,
251
211
  "The port number of the Droonga engine",
252
212
  "(#{@port})") do |port|
253
213
  @port = port
254
- @given_values[:port] = port
255
214
  end
256
215
  parser.on("--tag=TAG",
257
216
  "The tag of the Droonga engine",
258
217
  "(#{@tag})") do |tag|
259
218
  @tag = tag
260
- @given_values[:tag] = tag
261
219
  end
262
220
  end
263
221
 
@@ -271,12 +229,10 @@ module Droonga
271
229
  "[#{levels_label}]",
272
230
  "(#{log_level})") do |level|
273
231
  self.log_level = level
274
- @given_values[:log_level] = log_level
275
232
  end
276
233
  parser.on("--log-file=FILE",
277
234
  "Output logs to FILE") do |file|
278
235
  self.log_file = file
279
- @given_values[:log_file] = file
280
236
  end
281
237
  end
282
238
 
@@ -286,17 +242,14 @@ module Droonga
286
242
  parser.on("--daemon",
287
243
  "Run as a daemon") do
288
244
  @daemon = true
289
- @given_values[:daemon] = true
290
245
  end
291
246
  parser.on("--no-daemon",
292
247
  "Run as a regular process") do
293
248
  @daemon = false
294
- @given_values[:daemon] = false
295
249
  end
296
250
  parser.on("--pid-file=PATH",
297
251
  "Put PID to PATH") do |path|
298
252
  self.pid_file_path = path
299
- @given_values[:pid_file] = path
300
253
  end
301
254
  end
302
255
 
@@ -310,13 +263,6 @@ module Droonga
310
263
  end
311
264
  end
312
265
 
313
- def add_orchestration_options(parser)
314
- parser.on("--no-orchestration",
315
- "Disable orchestration with other nodes") do
316
- @orchestrated = false
317
- end
318
- end
319
-
320
266
  def add_notification_options(parser)
321
267
  parser.separator("")
322
268
  parser.separator("Notification:")
@@ -348,7 +294,7 @@ module Droonga
348
294
 
349
295
  trap_signals
350
296
  @loop.run
351
- @serf.stop if @serf and @serf.running?
297
+ @serf.stop if @serf.running?
352
298
 
353
299
  @service_runner.success?
354
300
  end
@@ -389,14 +335,14 @@ module Droonga
389
335
 
390
336
  def stop_gracefully
391
337
  @command_runner.stop
392
- @serf.stop if @serf
338
+ @serf.stop
393
339
  @catalog_observer.stop
394
340
  @service_runner.stop_gracefully
395
341
  end
396
342
 
397
343
  def stop_immediately
398
344
  @command_runner.stop
399
- @serf.stop if @serf
345
+ @serf.stop
400
346
  @catalog_observer.stop
401
347
  @service_runner.stop_immediately
402
348
  end
@@ -427,7 +373,6 @@ module Droonga
427
373
  end
428
374
 
429
375
  def run_serf
430
- return nil unless @configuration.orchestrated
431
376
  serf = Serf.new(@loop, @configuration.engine_name)
432
377
  serf.start
433
378
  serf
@@ -16,6 +16,7 @@
16
16
  require "optparse"
17
17
 
18
18
  require "coolio"
19
+ require "sigdump/setup"
19
20
 
20
21
  require "droonga/worker_process_agent"
21
22
  require "droonga/engine"
@@ -17,6 +17,7 @@ require "optparse"
17
17
  require "fileutils"
18
18
 
19
19
  require "coolio"
20
+ require "sigdump/setup"
20
21
 
21
22
  require "droonga/job_receiver"
22
23
  require "droonga/plugin_loader"
@@ -89,6 +89,13 @@ module Droonga
89
89
  end
90
90
  end
91
91
 
92
+ class SetStatus < Base
93
+ def process
94
+ status = NodeStatus.new
95
+ status.set(@params["key"], @params["value"])
96
+ end
97
+ end
98
+
92
99
  class Join < Base
93
100
  def process
94
101
  log("type = #{type}")
@@ -115,6 +122,10 @@ module Droonga
115
122
  @params["dataset"]
116
123
  end
117
124
 
125
+ def messages_per_second
126
+ @params["messages_per_second"]
127
+ end
128
+
118
129
  def valid_params?
119
130
  have_required_params? and
120
131
  valid_node?(source_node) and
@@ -220,7 +231,8 @@ module Droonga
220
231
  :source_host => source_host,
221
232
  :destination_host => joining_host,
222
233
  :port => port,
223
- :tag => tag)
234
+ :tag => tag,
235
+ :messages_per_second => messages_per_second)
224
236
  status.delete(:absorbing)
225
237
  sleep(1)
226
238
  end
@@ -258,6 +270,7 @@ module Droonga
258
270
  :destination_host => host,
259
271
  :port => port,
260
272
  :tag => tag,
273
+ :messages_per_second => messages_per_second,
261
274
  :client => "droonga-send")
262
275
  status.delete(:absorbing)
263
276
  end
@@ -278,6 +291,10 @@ module Droonga
278
291
  def tag
279
292
  @tag ||= @params["tag"]
280
293
  end
294
+
295
+ def messages_per_second
296
+ @messages_per_second ||= @params["messages_per_second"]
297
+ end
281
298
  end
282
299
 
283
300
  class ModifyReplicasBase < Base
@@ -46,6 +46,7 @@ module Droonga
46
46
  FileUtils.mkdir_p(Path.serf_event_handler_errors)
47
47
  File.open(Path.serf_event_handler_error_file, "w") do |file|
48
48
  file.write(exception.inspect)
49
+ file.write(exception.backtrace)
49
50
  end
50
51
  true
51
52
  end
@@ -72,6 +73,8 @@ module Droonga
72
73
  Remote::ChangeRole
73
74
  when "report_status"
74
75
  Remote::ReportStatus
76
+ when "set_status"
77
+ Remote::SetStatus
75
78
  when "join"
76
79
  Remote::Join
77
80
  when "set_replicas"
@@ -15,59 +15,249 @@
15
15
 
16
16
  require "open3"
17
17
 
18
+ require "droonga/loggable"
19
+ require "droonga/client"
20
+ require "droonga/catalog_generator"
21
+ require "droonga/catalog_fetcher"
22
+
18
23
  module Droonga
19
24
  class DataAbsorber
25
+ include Loggable
26
+
27
+ DEFAULT_MESSAGES_PER_SECOND = 100
28
+
29
+ TIME_UNKNOWN = -1
30
+ PROGRESS_UNKNOWN = -1
31
+
20
32
  class << self
21
33
  def absorb(params)
22
- drndump = params[:drndump] || "drndump"
23
- drndump_options = []
24
- drndump_options += ["--host", params[:source_host]] if params[:source_host]
25
- drndump_options += ["--port", params[:port].to_s] if params[:port]
26
- drndump_options += ["--tag", params[:tag]] if params[:tag]
27
- drndump_options += ["--dataset", params[:dataset]] if params[:dataset]
28
- drndump_options += ["--receiver-host", params[:destination_host]]
29
- drndump_options += ["--receiver-port", params[:receiver_port].to_s] if params[:receiver_port]
30
-
31
- #TODO: We should use droonga-send instead of droonga-request,
32
- # because droonga-request is too slow.
33
- # However, to do it, we have to implement an API to know
34
- # that all messages sent by droonga-send are completely
35
- # processed.
36
- client = params[:client] || "droonga-request"
37
- client_options = []
38
- if client.include?("droonga-request")
39
- client_options += ["--host", params[:destination_host]]
40
- client_options += ["--port", params[:port].to_s] if params[:port]
41
- client_options += ["--tag", params[:tag]] if params[:tag]
42
- client_options += ["--receiver-host", params[:destination_host]]
43
- client_options += ["--receiver-port", params[:receiver_port].to_s] if params[:receiver_port]
44
- elsif client.include?("droonga-send")
45
- #XXX Don't use round-robin with multiple endpoints
46
- # even if there are too much data.
47
- # Schema and indexes must be sent to just one endpoint
48
- # to keep their order, but currently there is no way to
49
- # extract only schema and indexes via drndump.
50
- # So, we always use just one endpoint for now,
51
- # even if there are too much data.
52
- server = "droonga:#{params[:destination_host]}"
53
- server = "#{server}:#{params[:port].to_s}" if params[:port]
54
- server = "#{server}/#{params[:tag].to_s}" if params[:tag]
55
- client_options += ["--server", server]
56
- else
57
- raise ArgumentError.new("Unknwon type client: #{client}")
58
- end
34
+ new(params).absorb
35
+ end
36
+ end
37
+
38
+ attr_reader :params
39
+ def initialize(params)
40
+ @params = params
59
41
 
60
- drndump_command_line = [drndump] + drndump_options
61
- client_command_line = [client] + client_options
42
+ @messages_per_second = @params[:messages_per_second] || DEFAULT_MESSAGES_PER_SECOND
62
43
 
63
- env = {}
64
- Open3.pipeline_r([env, *drndump_command_line],
65
- [env, *client_command_line]) do |last_stdout, thread|
66
- last_stdout.each do |output|
67
- yield output if block_given?
44
+ @drndump = @params[:drndump] || "drndump"
45
+ # We should use droonga-send instead of droonga-request,
46
+ # because droonga-request is too slow.
47
+ @client = @params[:client] || "droonga-send"
48
+
49
+ @dataset = @params[:dataset] || CatalogGenerator::DEFAULT_DATASET
50
+ @port = @params[:port] || CatalogGenerator::DEFAULT_PORT
51
+ @tag = @params[:tag] || CatalogGenerator::DEFAULT_TAG
52
+
53
+ @source_host = @params[:source_host]
54
+ @destination_host = @params[:destination_host]
55
+
56
+ @receiver_port = @params[:receiver_port]
57
+ end
58
+
59
+ MESSAGES_PER_SECOND_MATCHER = /(\d+(\.\d+)?) messages\/second/
60
+
61
+ def absorb
62
+ drndump_command_line = [@drndump] + drndump_options
63
+ client_command_line = [@client] + client_options(@client)
64
+
65
+ start_time_in_seconds = Time.new.to_i
66
+ env = {}
67
+ Open3.pipeline_r([env, *drndump_command_line],
68
+ [env, *client_command_line]) do |last_stdout, thread|
69
+ last_stdout.each do |output|
70
+ if block_given?
71
+ messages_per_second = nil
72
+ if output =~ MESSAGES_PER_SECOND_MATCHER
73
+ messages_per_second = $1.to_f
74
+ end
75
+ yield(:progress => report_progress(start_time_in_seconds),
76
+ :output => output,
77
+ :messages_per_second => messages_per_second)
68
78
  end
69
79
  end
70
80
  end
71
81
  end
82
+
83
+ def can_report_remaining_time?
84
+ required_time_in_seconds != Droonga::DataAbsorber::TIME_UNKNOWN and
85
+ required_time_in_seconds > 0
86
+ end
87
+
88
+ def required_time_in_seconds
89
+ @required_time_in_seconds ||= calculate_required_time_in_seconds
90
+ end
91
+
92
+ ONE_MINUTE_IN_SECONDS = 60
93
+ ONE_HOUR_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 60
94
+
95
+ def report_progress(start_time_in_seconds)
96
+ return nil unless can_report_remaining_time?
97
+
98
+ elapsed_time = Time.new.to_i - start_time_in_seconds
99
+ progress = elapsed_time.to_f / required_time_in_seconds
100
+ progress = [(progress * 100).to_i, 100].min
101
+
102
+ remaining_seconds = [required_time_in_seconds - elapsed_time, 0].max
103
+ remaining_hours = (remaining_seconds / ONE_HOUR_IN_SECONDS).floor
104
+ remaining_seconds -= remaining_hours * ONE_HOUR_IN_SECONDS
105
+ remaining_minutes = (remaining_seconds / ONE_MINUTE_IN_SECONDS).floor
106
+ remaining_seconds -= remaining_minutes * ONE_MINUTE_IN_SECONDS
107
+ remaining_time = sprintf("%02i:%02i:%02i", remaining_hours, remaining_minutes, remaining_seconds)
108
+
109
+ "#{progress}% done (maybe #{remaining_time} remaining)"
110
+ end
111
+
112
+ def source_client
113
+ options = {
114
+ :host => @source_host,
115
+ :port => @port,
116
+ :tag => @tag,
117
+ :progocol => :droonga,
118
+ :receiver_host => @destination_host,
119
+ :receiver_port => 0,
120
+ }
121
+ @source_client ||= Droonga::Client.new(options)
122
+ end
123
+
124
+ def destination_client
125
+ options = {
126
+ :host => @destination_host,
127
+ :port => @port,
128
+ :tag => @tag,
129
+ :progocol => :droonga,
130
+ :receiver_host => @destination_host,
131
+ :receiver_port => 0,
132
+ }
133
+ @destination_client ||= Droonga::Client.new(options)
134
+ end
135
+
136
+ def source_node_suspendable?
137
+ (source_replica_hosts - [@source_host]).size > 1
138
+ end
139
+
140
+ private
141
+ def calculate_required_time_in_seconds
142
+ if @client.include?("droonga-send")
143
+ total_n_source_records / @messages_per_second
144
+ else
145
+ TIME_UNKNOWN
146
+ end
147
+ end
148
+
149
+ def drndump_options
150
+ options = []
151
+ options += ["--host", @source_host] if @source_host
152
+ options += ["--port", @port]
153
+ options += ["--tag", @tag]
154
+ options += ["--dataset", @dataset]
155
+ options += ["--receiver-host", @destination_host]
156
+ options += ["--receiver-port", @receiver_port] if @receiver_port
157
+ options.collect(&:to_s)
158
+ end
159
+
160
+ def droonga_request_options
161
+ options = []
162
+ options += ["--host", @destination_host]
163
+ options += ["--port", @port]
164
+ options += ["--tag", @tag]
165
+ options += ["--receiver-host", @destination_host]
166
+ options += ["--receiver-port", @receiver_port] if @receiver_port
167
+ options.collect(&:to_s)
168
+ end
169
+
170
+ def droonga_send_options
171
+ options = []
172
+
173
+ #XXX Don't use round-robin with multiple endpoints
174
+ # even if there are too much data.
175
+ # Schema and indexes must be sent to just one endpoint
176
+ # to keep their order, but currently there is no way to
177
+ # extract only schema and indexes via drndump.
178
+ # So, we always use just one endpoint for now,
179
+ # even if there are too much data.
180
+ server = "droonga:#{params[:destination_host]}"
181
+ server = "#{server}:#{params[:port].to_s}"
182
+ server = "#{server}/#{params[:tag].to_s}"
183
+ options += ["--server", server]
184
+
185
+ #XXX We should restrict the traffic to avoid overflowing!
186
+ options += ["--messages-per-second", @messages_per_second]
187
+
188
+ options += ["--report-throughput"]
189
+
190
+ options.collect(&:to_s)
191
+ end
192
+
193
+ def client_options(client)
194
+ if client.include?("droonga-request")
195
+ droonga_request_options
196
+ elsif client.include?("droonga-send")
197
+ droonga_send_options
198
+ else
199
+ raise ArgumentError.new("Unknwon type client: #{client}")
200
+ end
201
+ end
202
+
203
+ def source_tables
204
+ response = source_client.request("dataset" => @dataset,
205
+ "type" => "table_list")
206
+ body = response["body"][1]
207
+ tables = body[1..-1]
208
+ tables.collect do |table|
209
+ table[1]
210
+ end
211
+ end
212
+
213
+ def total_n_source_records
214
+ queries = {}
215
+ source_tables.each do |table|
216
+ queries["n_records_of_#{table}"] = {
217
+ "source" => table,
218
+ "output" => {
219
+ "elements" => ["count"],
220
+ },
221
+ }
222
+ end
223
+ response = source_client.request("dataset" => @dataset,
224
+ "type" => "search",
225
+ "body" => {
226
+ "queries" => queries,
227
+ })
228
+ n_records = 0
229
+ response["body"].each do |query_name, result|
230
+ n_records += result["count"]
231
+ end
232
+ n_records
233
+ end
234
+
235
+ def source_replica_hosts
236
+ @source_replica_hosts ||= get_source_replica_hosts
237
+ end
238
+
239
+ def get_source_replica_hosts
240
+ generator = CatalogGenerator.new
241
+ generator.load(source_catalog)
242
+ dataset = generator.dataset_for_host(@source_host)
243
+ return [] unless dataset
244
+ dataset.replicas.hosts
245
+ end
246
+
247
+ def source_catalog
248
+ @source_catalog ||= fetch_source_catalog
249
+ end
250
+
251
+ def fetch_source_catalog
252
+ fetcher = CatalogFetcher.new(:host => @source_host,
253
+ :port => @port,
254
+ :tag => @tag,
255
+ :receiver_host => @destination_host)
256
+ fetcher.fetch(:dataset => @dataset)
257
+ end
258
+
259
+ def log_tag
260
+ "data-absorber"
261
+ end
72
262
  end
73
263
  end