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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c9d356f5ce7ea305000e8d982a0bacd9c02c1db4
4
- data.tar.gz: 700367d322a8b3df05ed73a6b57782d30568bf8f
3
+ metadata.gz: e7b344e7b567ecc524d5f5cfb52ed6a67342a672
4
+ data.tar.gz: 79ecc5c743f0f01f350a1855d55368dc024515a9
5
5
  SHA512:
6
- metadata.gz: b99cb2c1e4fa3df7d96700499277e37c6c7c37ba3eabeba5eb0721232712911d1cb691fc10f3159196b5633224d96f47fc66f4e279479643042282d2b3bc0654
7
- data.tar.gz: 766e53f931fa1ea0c5ca58614bdaa9627cd382b84be60673cdcd35a9842739d097d9b3b9f9105eb6b675fec6cdbe480994b7ededd7802d287d512cc6234096b5
6
+ metadata.gz: 1f94a76e05f59f76bbd6e92774bd928c10a01c8c731df1e9b548a0b255b61abfab538e3c374d1e1fd74244639cb140d9913ee09649226a2226ad54d458afd0a2
7
+ data.tar.gz: 343bf2deb3d726858be67b61f222689535b939ea1ecce9d564f8f69a474e41cd22190600cf95e0b2e42f3e609ff244f44fcd9fafc946901fb2a89197de33a0b7
data/.travis.yml CHANGED
@@ -13,6 +13,6 @@ rvm:
13
13
  before_install:
14
14
  # - GROONGA_MASTER=yes curl --silent --location https://raw.github.com/groonga/groonga/master/data/travis/setup.sh | sh
15
15
  - curl --silent --location https://raw.github.com/groonga/groonga/master/data/travis/setup.sh | sh
16
- - curl --location --remote-name https://dl.bintray.com/mitchellh/serf/0.6.0_linux_amd64.zip
17
- - unzip 0.6.0_linux_amd64.zip
16
+ - curl --location --remote-name https://dl.bintray.com/mitchellh/serf/0.6.3_linux_amd64.zip
17
+ - unzip 0.6.3_linux_amd64.zip
18
18
  - sudo install serf /usr/local/bin/
@@ -24,100 +24,214 @@ require "droonga/catalog_generator"
24
24
  require "droonga/path"
25
25
  require "droonga/data_absorber"
26
26
  require "droonga/serf"
27
+ require "droonga/client"
27
28
 
28
- options = OpenStruct.new
29
- options.port = Droonga::CatalogGenerator::DEFAULT_PORT
30
- options.tag = Droonga::CatalogGenerator::DEFAULT_TAG
31
- options.dataset = Droonga::CatalogGenerator::DEFAULT_DATASET
32
- options.remote = true
33
- parser = OptionParser.new
34
- parser.version = Droonga::Engine::VERSION
35
-
36
- parser.separator("")
37
- parser.separator("Connection:")
38
- parser.on("--source-host=HOST",
39
- "Host name of the source cluster to be connected.") do |host|
40
- options.source_host = host
41
- end
42
- parser.on("--destination-host=HOST",
43
- "Host name of this cluster to be connected.") do |host|
44
- options.destination_host = host
45
- end
46
- parser.on("--port=PORT", Integer,
47
- "Port number of the source cluster to be connected.",
48
- "(#{options.port})") do |port|
49
- options.port = port
50
- end
51
- parser.on("--[no-]remote",
52
- "Run command in remote node or not.",
53
- "(#{options.remote})") do |remote|
54
- options.remote = remote
55
- end
29
+ class AbsorbDataCommand
30
+ def run
31
+ parse_options
32
+ assert_valid_options
33
+ trap_signals
56
34
 
57
- parser.separator("")
58
- parser.separator("Data:")
59
- parser.on("--tag=TAG",
60
- "Tag name to be used to communicate with Droonga system.",
61
- "(#{options.tag})") do |tag|
62
- options.tag = tag
63
- end
64
- parser.on("--dataset=DATASET",
65
- "Dataset to be absorbed.",
66
- "(#{options.dataset})") do |dataset|
67
- options.dataset = dataset
68
- end
35
+ puts "Start to absorb data from #{@options.source_host}"
36
+ puts " to #{@options.destination_host}"
37
+ puts " dataset = #{@options.dataset}"
38
+ puts " port = #{@options.port}"
39
+ puts " tag = #{@options.tag}"
40
+ puts ""
41
+ puts "Absorbing..."
69
42
 
70
- parser.parse!(ARGV)
43
+ if @options.remote
44
+ absorb_on_remote
45
+ else
46
+ absorb_on_local
47
+ end
71
48
 
72
- unless options.source_host
73
- raise "You must specify the source host via --source-host option."
74
- end
75
- unless options.destination_host
76
- raise "You must specify the destination host via --destination-host option."
77
- end
49
+ puts "Done."
50
+ exit(true)
51
+ end
78
52
 
79
- destination_node = "#{options.destination_host}:#{options.port}/#{options.tag}"
53
+ private
54
+ def parse_options
55
+ options = OpenStruct.new
56
+ options.port = Droonga::CatalogGenerator::DEFAULT_PORT
57
+ options.tag = Droonga::CatalogGenerator::DEFAULT_TAG
58
+ options.dataset = Droonga::CatalogGenerator::DEFAULT_DATASET
59
+ options.remote = true
60
+ options.messages_per_second = Droonga::DataAbsorber::DEFAULT_MESSAGES_PER_SECOND
61
+ parser = OptionParser.new
62
+ parser.version = Droonga::Engine::VERSION
80
63
 
81
- def run_remote_command(target, command, options)
82
- serf = Droonga::Serf.new(nil, target)
83
- result = serf.send_query(command, options)
84
- puts result[:result]
85
- puts result[:error] unless result[:error].empty?
86
- result[:response]
87
- end
64
+ parser.separator("")
65
+ parser.separator("Connection:")
66
+ parser.on("--source-host=HOST",
67
+ "Host name of the source cluster to be connected.") do |host|
68
+ options.source_host = host
69
+ end
70
+ parser.on("--destination-host=HOST",
71
+ "Host name of this cluster to be connected.") do |host|
72
+ options.destination_host = host
73
+ end
74
+ parser.on("--port=PORT", Integer,
75
+ "Port number of the source cluster to be connected.",
76
+ "(#{options.port})") do |port|
77
+ options.port = port
78
+ end
79
+ parser.on("--[no-]remote",
80
+ "Run command in remote node or not.",
81
+ "(#{options.remote})") do |remote|
82
+ options.remote = remote
83
+ end
84
+
85
+ parser.separator("")
86
+ parser.separator("Data:")
87
+ parser.on("--tag=TAG",
88
+ "Tag name to be used to communicate with Droonga system.",
89
+ "(#{options.tag})") do |tag|
90
+ options.tag = tag
91
+ end
92
+ parser.on("--dataset=DATASET",
93
+ "Dataset to be absorbed.",
94
+ "(#{options.dataset})") do |dataset|
95
+ options.dataset = dataset
96
+ end
97
+ parser.on("--records-per-second=N", Integer,
98
+ "Maximum number of records per second to be absorbed.",
99
+ "'#{Droonga::Client::RateLimiter::NO_LIMIT}' means no limit.",
100
+ "(#{options.messages_per_second})") do |n|
101
+ options.messages_per_second = n
102
+ end
103
+
104
+ parser.parse!(ARGV)
105
+ @options = options
106
+ end
107
+
108
+ def assert_valid_options
109
+ unless @options.source_host
110
+ raise "You must specify the source host via --source-host option."
111
+ end
112
+ unless @options.destination_host
113
+ raise "You must specify the destination host via --destination-host option."
114
+ end
115
+ end
116
+
117
+ def source_node
118
+ "#{@options.source_host}:#{@options.port}/#{@options.tag}"
119
+ end
88
120
 
89
- puts "Start to absorb data from #{options.source_host}"
90
- puts " to #{options.destination_host}"
91
- puts " dataset = #{options.dataset}"
92
- puts " port = #{options.port}"
93
- puts " tag = #{options.tag}"
94
- puts ""
95
- puts "Absorbing..."
96
-
97
- if options.remote
98
- run_remote_command(destination_node, "absorb_data",
99
- "node" => destination_node,
100
- "source" => options.source_host,
101
- "port" => options.port,
102
- "tag" => options.tag,
103
- "dataset" => options.dataset)
104
- while true
105
- sleep(3)
106
- response = run_remote_command(destination_node, "report_status",
107
- "node" => destination_node,
108
- "key" => "absorbing")
109
- absorbing = response["value"]
110
- break unless absorbing
121
+ def destination_node
122
+ "#{@options.destination_host}:#{@options.port}/#{@options.tag}"
111
123
  end
112
- else
113
- Droonga::DataAbsorber.absorb(:dataset => options.dataset,
114
- :source_host => options.source_host,
115
- :destination_host => options.destination_host,
116
- :port => options.port,
117
- :tag => options.tag) do |output|
118
- puts output
124
+
125
+ def run_remote_command(target, command, options)
126
+ serf = Droonga::Serf.new(nil, target)
127
+ result = serf.send_query(command, options)
128
+ #puts result[:result]
129
+ puts result[:error] unless result[:error].empty?
130
+ result[:response]
131
+ end
132
+
133
+ def absorber
134
+ @absorber ||= prepare_absorber
135
+ end
136
+
137
+ def prepare_absorber
138
+ absorber_options = {
139
+ :dataset => @options.dataset,
140
+ :source_host => @options.source_host,
141
+ :destination_host => @options.destination_host,
142
+ :port => @options.port,
143
+ :tag => @options.tag,
144
+ :messages_per_second => @options.messages_per_second,
145
+ }
146
+ Droonga::DataAbsorber.new(absorber_options)
147
+ end
148
+
149
+ def absorb_on_remote
150
+ start_time_in_seconds = Time.new.to_i
151
+ run_remote_command(destination_node, "absorb_data",
152
+ "node" => destination_node,
153
+ "source" => @options.source_host,
154
+ "port" => @options.port,
155
+ "tag" => @options.tag,
156
+ "dataset" => @options.dataset,
157
+ "messages_per_second" => @options.messages_per_second)
158
+ last_progress = ""
159
+ while true
160
+ sleep(3)
161
+ response = run_remote_command(destination_node, "report_status",
162
+ "node" => destination_node,
163
+ "key" => "absorbing")
164
+ if response
165
+ absorbing = response["value"]
166
+ break unless absorbing
167
+ end
168
+
169
+ progress = absorber.report_progress(start_time_in_seconds)
170
+ if progress
171
+ printf("%s", "#{" " * last_progress.size}\r")
172
+ printf("%s", "#{progress}\r")
173
+ last_progress = progress
174
+ end
175
+ end
176
+ puts ""
177
+
178
+ response = run_remote_command(source_node, "report_status",
179
+ "node" => source_node,
180
+ "key" => "last_processed_message_timestamp")
181
+ timestamp = response["value"]
182
+ if timestamp and not timestamp.empty?
183
+ puts "The timestamp of the last processed message in the source node: #{timestamp}"
184
+ puts "Setting effective message timestamp for the destination node..."
185
+ response = run_remote_command(destination_node, "set_status",
186
+ "node" => destination_node,
187
+ "key" => "effective_message_timestamp",
188
+ "value" => timestamp)
189
+ end
190
+ end
191
+
192
+ def absorb_on_local
193
+ last_progress = ""
194
+ absorber.absorb do |live_status|
195
+ if live_status[:progress]
196
+ progress = live_status[:progress]
197
+ else
198
+ progress = live_status[:output]
199
+ end
200
+ printf("%s", "#{" " * last_progress.size}\r")
201
+ printf("%s", "#{progress}\r")
202
+ last_progress = progress
203
+ end
204
+ response = run_remote_command(source_node, "report_status",
205
+ "node" => source_node,
206
+ "key" => "last_processed_message_timestamp")
207
+ timestamp = response["value"]
208
+ puts "The timestamp of the last processed message in the source node: #{timestamp}"
209
+ if timestamp and not timestamp.empty?
210
+ status = NodeStatus.new
211
+ status.set(:effective_message_timestamp, timestamp)
212
+ end
213
+ end
214
+
215
+ def trap_signals
216
+ trap(:TERM) do
217
+ do_cancel
218
+ trap(:TERM, "DEFAULT")
219
+ end
220
+
221
+ trap(:INT) do
222
+ do_cancel
223
+ trap(:INT, "DEFAULT")
224
+ end
225
+
226
+ trap(:QUIT) do
227
+ do_cancel
228
+ trap(:QUIT, "DEFAULT")
229
+ end
230
+ end
231
+
232
+ def do_cancel
233
+ #XXX we have to write more codes to cancel remote processes!
119
234
  end
120
235
  end
121
- puts "Done."
122
236
 
123
- exit(true)
237
+ AbsorbDataCommand.new.run
@@ -26,7 +26,7 @@ require "droonga/service_installation"
26
26
  require "droonga/logger"
27
27
 
28
28
  options = {
29
- :quiet => nil,
29
+ :no_prompt => nil,
30
30
  :clear => nil,
31
31
  :reset_config => nil,
32
32
  :reset_catalog => nil,
@@ -34,8 +34,11 @@ options = {
34
34
 
35
35
  configuration = Droonga::Command::DroongaEngine::Configuration.new
36
36
  parser = OptionParser.new
37
- parser.on("--quiet", "Run with no prompt.") do |host|
38
- options[:quiet] = true
37
+ parser.on("--no-prompt", "Run with no prompt.") do |host|
38
+ options[:no_prompt] = true
39
+ end
40
+ parser.on("--quiet", "Same to --no-prompt. For backward compatibility.") do |host|
41
+ options[:no_prompt] = true
39
42
  end
40
43
  parser.on("--clear", "Clear any existing data.") do |host|
41
44
  options[:clear] = true
@@ -97,7 +100,7 @@ service_installation.ensure_using_service_base_directory
97
100
  running = false
98
101
  begin
99
102
  if service_installation.running?
100
- if !options[:quiet]
103
+ if !options[:no_prompt]
101
104
  puts("The droonga-engine service is now running.")
102
105
  puts("Before reconfiguration, the service is going to be stopped " +
103
106
  "and this node will be unjoined from the cluster.")
@@ -126,19 +129,19 @@ data_files = [
126
129
  have_data = data_files.any?(&:exist?)
127
130
  options[:clear] = false unless have_data
128
131
 
129
- if !options[:quiet] and options[:clear].nil?
132
+ if !options[:no_prompt] and options[:clear].nil?
130
133
  options[:clear] = confirmed?("Do you want all data to be cleared?")
131
134
  end
132
135
 
133
136
 
134
137
  options[:reset_config] = true unless Droonga::Path.config.exist?
135
- if !options[:quiet] and options[:reset_config].nil?
138
+ if !options[:no_prompt] and options[:reset_config].nil?
136
139
  options[:reset_config] = confirmed?("Do you want the configuration file " +
137
140
  "\"droonga-engine.yaml\" to be regenerated?")
138
141
  end
139
142
 
140
143
  options[:reset_catalog] = true unless Droonga::Path.catalog.exist?
141
- if !options[:quiet] and options[:reset_catalog].nil?
144
+ if !options[:no_prompt] and options[:reset_catalog].nil?
142
145
  options[:reset_catalog] = confirmed?("Do you want the file \"catalog.json\" " +
143
146
  "to be regenerated?")
144
147
  end
@@ -156,19 +159,19 @@ if options[:clear]
156
159
  end
157
160
 
158
161
  if options[:reset_config] or options[:reset_catalog]
159
- if configuration.have_given_host? or options[:quiet]
162
+ if options[:no_prompt]
160
163
  host = configuration.host
161
164
  else
162
165
  host = input("host", configuration.host)
163
166
  end
164
167
 
165
- if configuration.have_given_port? or options[:quiet]
168
+ if options[:no_prompt]
166
169
  port = configuration.port
167
170
  else
168
171
  port = input("port", configuration.port).to_i
169
172
  end
170
173
 
171
- if configuration.have_given_tag? or options[:quiet]
174
+ if options[:no_prompt]
172
175
  tag = configuration.tag
173
176
  else
174
177
  tag = input("tag", configuration.tag)
@@ -176,7 +179,7 @@ if options[:reset_config] or options[:reset_catalog]
176
179
  end
177
180
 
178
181
  if options[:reset_config]
179
- if configuration.have_given_log_level? or options[:quiet]
182
+ if options[:no_prompt]
180
183
  log_level = configuration.log_level
181
184
  else
182
185
  levels = Droonga::Logger::Level::LABELS
@@ -26,70 +26,189 @@ require "droonga/safe_file_writer"
26
26
  require "droonga/data_absorber"
27
27
  require "droonga/serf"
28
28
 
29
- options = nil
30
- begin
31
- options = Slop.parse(:help => true) do |option|
32
- option.on("no-copy", "Don't copy data from the source cluster.",
33
- :default => false)
34
-
35
- option.separator("Connections:")
36
- option.on(:host=,
37
- "Host name of the node to be joined.",
38
- :required => true)
39
- option.on("replica-source-host=",
40
- "Host name of the soruce cluster to be connected.",
41
- :required => true)
42
- option.on(:dataset=,
43
- "Tag dataset name of the cluster to be joined as a node.",
44
- :default => Droonga::CatalogGenerator::DEFAULT_DATASET)
45
- option.on(:port=,
46
- "Port number of the source cluster to be connected.",
47
- :as => Integer,
48
- :default => Droonga::CatalogGenerator::DEFAULT_PORT)
49
- option.on(:tag=,
50
- "Tag name of the soruce cluster to be connected.",
51
- :default => Droonga::CatalogGenerator::DEFAULT_TAG)
29
+ class JoinCommand
30
+ def run
31
+ parse_options
32
+ trap_signals
33
+ set_node_role
34
+ do_join
35
+ sleep(5) #TODO: wait for restarting of the joining node. this should be done more safely.
36
+ do_copy unless @options["no-copy"]
37
+ set_effective_message_timestamp
38
+ update_other_nodes
39
+ reset_node_role
40
+ puts("Done.")
41
+ exit(true)
52
42
  end
53
- rescue Slop::MissingOptionError => e
54
- $stderr.puts(e)
55
- exit(false)
56
- end
57
43
 
58
- joining_node = "#{options[:host]}:#{options[:port]}/#{options[:tag]}"
59
- source_node = "#{options["replica-source-host"]}:#{options[:port]}/#{options[:tag]}"
44
+ private
45
+ def parse_options
46
+ options = Slop.parse(:help => true) do |option|
47
+ option.on("no-copy", "Don't copy data from the source cluster.",
48
+ :default => false)
60
49
 
61
- def run_remote_command(target, command, options)
62
- serf = Droonga::Serf.new(nil, target)
63
- result = serf.send_query(command, options)
64
- puts(result[:result])
65
- puts(result[:error]) unless result[:error].empty?
66
- result[:response]
67
- end
50
+ option.separator("Connections:")
51
+ option.on(:host=,
52
+ "Host name of the new node to be joined.",
53
+ :required => true)
54
+ option.on("replica-source-host=",
55
+ "Host name of the soruce node in the cluster to be connected.",
56
+ :required => true)
57
+ option.on(:dataset=,
58
+ "Dataset name of for the node to be joined.",
59
+ :default => Droonga::CatalogGenerator::DEFAULT_DATASET)
60
+ option.on(:port=,
61
+ "Port number of the source cluster to be connected.",
62
+ :as => Integer,
63
+ :default => Droonga::CatalogGenerator::DEFAULT_PORT)
64
+ option.on(:tag=,
65
+ "Tag name of the soruce cluster to be connected.",
66
+ :default => Droonga::CatalogGenerator::DEFAULT_TAG)
67
+ option.on("records-per-second=",
68
+ "Maximum number of records per second to be copied. " +
69
+ "'#{Droonga::Client::RateLimiter::NO_LIMIT}' means no limit.",
70
+ :as => Integer,
71
+ :default => Droonga::DataAbsorber::DEFAULT_MESSAGES_PER_SECOND)
72
+ end
73
+ @options = options
74
+ rescue Slop::MissingOptionError => error
75
+ $stderr.puts(error)
76
+ exit(false)
77
+ end
68
78
 
69
- puts("Joining new replica to the cluster...")
70
- run_remote_command(joining_node, "join",
71
- "node" => joining_node,
72
- "type" => "replica",
73
- "source" => source_node,
74
- "dataset" => options[:dataset],
75
- "copy" => !options["no-copy"])
76
- sleep(5) #TODO: wait for restarting of the joining node. this should be done more safely.
77
-
78
- while true
79
- sleep(3)
80
- response = run_remote_command(joining_node, "report_status",
81
- "node" => joining_node,
82
- "key" => "absorbing")
83
- absorbing = response["value"]
84
- break unless absorbing
85
- end
79
+ def joining_node
80
+ "#{@options[:host]}:#{@options[:port]}/#{@options[:tag]}"
81
+ end
82
+
83
+ def source_node
84
+ "#{@options["replica-source-host"]}:#{@options[:port]}/#{@options[:tag]}"
85
+ end
86
+
87
+ def run_remote_command(target, command, options)
88
+ serf = Droonga::Serf.new(nil, target)
89
+ result = serf.send_query(command, options)
90
+ #puts(result[:result])
91
+ puts(result[:error]) unless result[:error].empty?
92
+ result[:response]
93
+ end
94
+
95
+ def absorber
96
+ @absorber ||= prepare_absorber
97
+ end
98
+
99
+ def prepare_absorber
100
+ absorber_options = {
101
+ :dataset => @options[:dataset],
102
+ :source_host => @options["replica-source-host"],
103
+ :destination_host => @options[:host],
104
+ :port => @options[:port],
105
+ :tag => @options[:tag],
106
+ :messages_per_second => @options["records-per-second"],
107
+ }
108
+ Droonga::DataAbsorber.new(absorber_options)
109
+ end
110
+
111
+ def set_node_role
112
+ if absorber.source_node_suspendable?
113
+ run_remote_command(source_node, "change_role",
114
+ "node" => source_node,
115
+ "role" => "source")
116
+ end
117
+ run_remote_command(joining_node, "change_role",
118
+ "node" => joining_node,
119
+ "role" => "destination")
120
+ @node_role_changed = true
121
+ end
86
122
 
87
- puts("Update existing hosts in the cluster...")
88
- run_remote_command(source_node, "add_replicas",
89
- "dataset" => options[:dataset],
90
- "hosts" => [options[:host]])
123
+ def reset_node_role
124
+ if absorber.source_node_suspendable?
125
+ run_remote_command(source_node, "change_role",
126
+ "node" => source_node,
127
+ "role" => "")
128
+ end
129
+ run_remote_command(joining_node, "change_role",
130
+ "node" => joining_node,
131
+ "role" => "")
132
+ @node_role_changed = false
133
+ end
134
+
135
+ def do_join
136
+ puts("Joining new replica to the cluster...")
137
+ run_remote_command(joining_node, "join",
138
+ "node" => joining_node,
139
+ "type" => "replica",
140
+ "source" => source_node,
141
+ "dataset" => @options[:dataset],
142
+ "copy" => !@options["no-copy"])
143
+ end
144
+
145
+ def do_copy
146
+ @start_time_in_seconds = Time.new.to_i
147
+ puts("Copying data from the source node...")
148
+ last_progress = ""
149
+ while true
150
+ sleep(3)
151
+ response = run_remote_command(joining_node, "report_status",
152
+ "node" => joining_node,
153
+ "key" => "absorbing")
154
+ if response
155
+ absorbing = response["value"]
156
+ break unless absorbing
157
+ end
91
158
 
159
+ progress = absorber.report_progress(@start_time_in_seconds)
160
+ if progress
161
+ printf("%s", "#{" " * last_progress.size}\r")
162
+ printf("%s", "#{progress}\r")
163
+ last_progress = progress
164
+ end
165
+ end
166
+ puts ""
167
+ end
168
+
169
+ def set_effective_message_timestamp
170
+ response = run_remote_command(source_node, "report_status",
171
+ "node" => source_node,
172
+ "key" => "last_processed_message_timestamp")
173
+ timestamp = response["value"]
174
+ if timestamp and not timestamp.empty?
175
+ puts "The timestamp of the last processed message in the source node: #{timestamp}"
176
+ puts "Setting effective message timestamp for the destination node..."
177
+ response = run_remote_command(joining_node, "set_status",
178
+ "node" => joining_node,
179
+ "key" => "effective_message_timestamp",
180
+ "value" => timestamp)
181
+ end
182
+ end
92
183
 
93
- puts("Done.")
184
+ def update_other_nodes
185
+ puts("Update existing hosts in the cluster...")
186
+ run_remote_command(source_node, "add_replicas",
187
+ "dataset" => @options[:dataset],
188
+ "hosts" => [@options[:host]])
189
+ end
190
+
191
+ def trap_signals
192
+ trap(:TERM) do
193
+ do_cancel
194
+ trap(:TERM, "DEFAULT")
195
+ end
196
+
197
+ trap(:INT) do
198
+ do_cancel
199
+ trap(:INT, "DEFAULT")
200
+ end
201
+
202
+ trap(:QUIT) do
203
+ do_cancel
204
+ trap(:QUIT, "DEFAULT")
205
+ end
206
+ end
207
+
208
+ def do_cancel
209
+ #XXX we have to write more codes to cancel remote processes!
210
+ reset_node_role if @node_role_changed
211
+ end
212
+ end
94
213
 
95
- exit(true)
214
+ JoinCommand.new.run