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