droonga-engine 1.0.7 → 1.0.8

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