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