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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/bin/droonga-engine-absorb-data +200 -86
- data/bin/droonga-engine-configure +14 -11
- data/bin/droonga-engine-join +178 -59
- data/droonga-engine.gemspec +1 -1
- data/install.sh +1 -0
- data/lib/droonga/buffered_tcp_socket.rb +2 -2
- data/lib/droonga/catalog/base.rb +18 -0
- data/lib/droonga/catalog/dataset.rb +8 -0
- data/lib/droonga/catalog/version1.rb +3 -12
- data/lib/droonga/catalog/version2.rb +5 -12
- data/lib/droonga/command/droonga_engine.rb +6 -61
- data/lib/droonga/command/droonga_engine_service.rb +1 -0
- data/lib/droonga/command/droonga_engine_worker.rb +1 -0
- data/lib/droonga/command/remote.rb +18 -1
- data/lib/droonga/command/serf_event_handler.rb +3 -0
- data/lib/droonga/data_absorber.rb +234 -44
- data/lib/droonga/distributed_command_planner.rb +5 -5
- data/lib/droonga/engine.rb +27 -15
- data/lib/droonga/engine/version.rb +1 -1
- data/lib/droonga/handler_runner.rb +4 -0
- data/lib/droonga/node_status.rb +6 -2
- data/lib/droonga/path.rb +8 -14
- data/lib/droonga/planner.rb +4 -3
- data/lib/droonga/plugin/metadata/handler_action.rb +8 -0
- data/lib/droonga/plugins/groonga/column_create.rb +1 -1
- data/lib/droonga/plugins/groonga/column_list.rb +23 -1
- data/lib/droonga/plugins/groonga/column_remove.rb +1 -1
- data/lib/droonga/plugins/groonga/column_rename.rb +1 -1
- data/lib/droonga/plugins/groonga/delete.rb +1 -1
- data/lib/droonga/plugins/groonga/select.rb +17 -2
- data/lib/droonga/plugins/groonga/table_create.rb +26 -1
- data/lib/droonga/plugins/groonga/table_remove.rb +1 -1
- data/lib/droonga/plugins/search.rb +1 -1
- data/lib/droonga/plugins/search/distributed_search_planner.rb +15 -7
- data/lib/droonga/processor.rb +3 -2
- data/lib/droonga/searcher.rb +31 -15
- data/lib/droonga/serf.rb +1 -0
- data/lib/droonga/service_installation.rb +2 -2
- data/lib/droonga/single_step.rb +2 -2
- data/test/command/fixture/event.jsons +3 -2
- data/test/command/fixture/user-table.jsons +3 -2
- data/test/command/fixture/users.jsons +25 -0
- data/test/command/run-test.rb +13 -1
- data/test/command/suite/groonga/column_create/scalar.test +3 -2
- data/test/command/suite/groonga/column_create/vector.test +3 -2
- data/test/command/suite/groonga/column_list/{success.expected → no-key.expected} +0 -0
- data/test/command/suite/groonga/column_list/{success.test → no-key.test} +1 -1
- data/test/command/suite/groonga/column_list/with-key.expected +96 -0
- data/test/command/suite/groonga/column_list/with-key.test +25 -0
- data/test/command/suite/groonga/column_remove/success.test +3 -2
- data/test/command/suite/groonga/column_remove/unknown-column.test +3 -2
- data/test/command/suite/groonga/column_rename/success.test +3 -2
- data/test/command/suite/groonga/column_rename/unknown-column.test +3 -2
- data/test/command/suite/groonga/delete/duplicated-identifiers.test +3 -2
- data/test/command/suite/groonga/delete/no-identifier.test +3 -2
- data/test/command/suite/groonga/select/output_columns/default/array.expected +33 -0
- data/test/command/suite/groonga/select/output_columns/default/array.test +38 -0
- data/test/command/suite/groonga/select/output_columns/nonexistent.expected +28 -0
- data/test/command/suite/groonga/select/output_columns/nonexistent.test +26 -0
- data/test/command/suite/groonga/table_create/dat-without-key-type.expected +14 -0
- data/test/command/suite/groonga/table_create/dat-without-key-type.test +8 -0
- data/test/command/suite/groonga/table_create/dat.expected +13 -0
- data/test/command/suite/groonga/table_create/dat.test +9 -0
- data/test/command/suite/groonga/table_create/hash-without-key-type.expected +14 -0
- data/test/command/suite/groonga/table_create/hash-without-key-type.test +8 -0
- data/test/command/suite/groonga/table_create/hash.test +3 -2
- data/test/command/suite/groonga/table_create/pat-without-key-type.expected +14 -0
- data/test/command/suite/groonga/table_create/pat-without-key-type.test +8 -0
- data/test/command/suite/groonga/table_create/pat.expected +13 -0
- data/test/command/suite/groonga/table_create/pat.test +9 -0
- data/test/command/suite/groonga/table_list/success.test +3 -2
- data/test/unit/catalog/test_version1.rb +2 -2
- data/test/unit/catalog/test_version2.rb +3 -3
- data/test/unit/helper.rb +2 -2
- data/test/unit/helper/distributed_search_planner_helper.rb +9 -1
- data/test/unit/plugins/groonga/select/test_adapter_input.rb +15 -2
- data/test/unit/plugins/groonga/test_column_list.rb +119 -4
- data/test/unit/plugins/groonga/test_table_create.rb +29 -0
- data/test/unit/plugins/search/planner/test_basic.rb +2 -2
- data/test/unit/plugins/search/test_planner.rb +10 -2
- metadata +43 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7b344e7b567ecc524d5f5cfb52ed6a67342a672
|
4
|
+
data.tar.gz: 79ecc5c743f0f01f350a1855d55368dc024515a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
17
|
-
- unzip 0.6.
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
43
|
+
if @options.remote
|
44
|
+
absorb_on_remote
|
45
|
+
else
|
46
|
+
absorb_on_local
|
47
|
+
end
|
71
48
|
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
90
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
237
|
+
AbsorbDataCommand.new.run
|
@@ -26,7 +26,7 @@ require "droonga/service_installation"
|
|
26
26
|
require "droonga/logger"
|
27
27
|
|
28
28
|
options = {
|
29
|
-
:
|
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("--
|
38
|
-
options[:
|
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[:
|
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[:
|
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[:
|
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[:
|
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
|
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
|
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
|
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
|
182
|
+
if options[:no_prompt]
|
180
183
|
log_level = configuration.log_level
|
181
184
|
else
|
182
185
|
levels = Droonga::Logger::Level::LABELS
|
data/bin/droonga-engine-join
CHANGED
@@ -26,70 +26,189 @@ require "droonga/safe_file_writer"
|
|
26
26
|
require "droonga/data_absorber"
|
27
27
|
require "droonga/serf"
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
|
214
|
+
JoinCommand.new.run
|