elasticsearch-extensions 0.0.3 → 0.0.33
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 +7 -0
- data/Gemfile +29 -0
- data/LICENSE.txt +199 -10
- data/README.md +233 -25
- data/Rakefile +33 -31
- data/elasticsearch-extensions.gemspec +46 -24
- data/lib/elasticsearch/extensions/ansi/actions.rb +45 -1
- data/lib/elasticsearch/extensions/ansi/helpers.rb +17 -0
- data/lib/elasticsearch/extensions/ansi/response.rb +23 -2
- data/lib/elasticsearch/extensions/ansi.rb +17 -0
- data/lib/elasticsearch/extensions/backup.rb +202 -0
- data/lib/elasticsearch/extensions/reindex.rb +187 -0
- data/lib/elasticsearch/extensions/test/cluster/tasks.rb +30 -0
- data/lib/elasticsearch/extensions/test/cluster.rb +701 -0
- data/lib/elasticsearch/extensions/test/profiling.rb +124 -0
- data/lib/elasticsearch/extensions/test/startup_shutdown.rb +71 -0
- data/lib/elasticsearch/extensions/version.rb +18 -1
- data/lib/elasticsearch/extensions.rb +19 -0
- data/lib/elasticsearch-extensions.rb +5 -0
- data/test/ansi/unit/ansi_test.rb +66 -0
- data/test/backup/unit/backup_test.rb +131 -0
- data/test/reindex/integration/reindex_test.rb +107 -0
- data/test/reindex/unit/reindex_test.rb +123 -0
- data/test/test/cluster/integration/cluster_test.rb +66 -0
- data/test/test/cluster/unit/cluster_test.rb +363 -0
- data/test/test_helper.rb +78 -0
- metadata +109 -89
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
require 'timeout'
|
|
19
|
+
require 'net/http'
|
|
20
|
+
require 'fileutils'
|
|
21
|
+
require 'socket'
|
|
22
|
+
require 'uri'
|
|
23
|
+
require 'json'
|
|
24
|
+
require 'ansi'
|
|
25
|
+
|
|
26
|
+
STDOUT.sync = true
|
|
27
|
+
STDERR.sync = true
|
|
28
|
+
|
|
29
|
+
class String
|
|
30
|
+
|
|
31
|
+
# Redefine the ANSI method: do not print ANSI when not running in the terminal
|
|
32
|
+
#
|
|
33
|
+
def ansi(*args)
|
|
34
|
+
STDOUT.tty? ? ANSI.ansi(self, *args) : self
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module Elasticsearch
|
|
39
|
+
module Extensions
|
|
40
|
+
module Test
|
|
41
|
+
# A convenience Ruby class for starting and stopping an Elasticsearch cluster,
|
|
42
|
+
# eg. for integration tests
|
|
43
|
+
#
|
|
44
|
+
# @example Start a cluster with default configuration,
|
|
45
|
+
# assuming `elasticsearch` is on $PATH.
|
|
46
|
+
#
|
|
47
|
+
# require 'elasticsearch/extensions/test/cluster'
|
|
48
|
+
# Elasticsearch::Extensions::Test::Cluster.start
|
|
49
|
+
#
|
|
50
|
+
# @example Start a cluster with a specific Elasticsearch launch script,
|
|
51
|
+
# eg. from a downloaded `.tar.gz` distribution
|
|
52
|
+
#
|
|
53
|
+
# system 'wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.1.1.tar.gz'
|
|
54
|
+
# system 'tar -xvf elasticsearch-5.1.1.tar.gz'
|
|
55
|
+
#
|
|
56
|
+
# require 'elasticsearch/extensions/test/cluster'
|
|
57
|
+
# Elasticsearch::Extensions::Test::Cluster.start command: 'elasticsearch-5.1.1/bin/elasticsearch'
|
|
58
|
+
#
|
|
59
|
+
# @see Cluster#initialize
|
|
60
|
+
#
|
|
61
|
+
module Cluster
|
|
62
|
+
# Starts a cluster
|
|
63
|
+
#
|
|
64
|
+
# @see Cluster#start
|
|
65
|
+
#
|
|
66
|
+
def start(arguments = {})
|
|
67
|
+
Cluster.new(arguments).start
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Stops a cluster
|
|
71
|
+
#
|
|
72
|
+
# @see Cluster#stop
|
|
73
|
+
#
|
|
74
|
+
def stop(arguments = {})
|
|
75
|
+
Cluster.new(arguments).stop
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns true when a specific test node is running within the cluster
|
|
79
|
+
#
|
|
80
|
+
# @see Cluster#running?
|
|
81
|
+
#
|
|
82
|
+
def running?(arguments = {})
|
|
83
|
+
Cluster.new(arguments).running?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Waits until the cluster is green and prints information
|
|
87
|
+
#
|
|
88
|
+
# @see Cluster#wait_for_green
|
|
89
|
+
#
|
|
90
|
+
def wait_for_green(arguments = {})
|
|
91
|
+
Cluster.new(arguments).wait_for_green
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
module_function :start, :stop, :running?, :wait_for_green
|
|
95
|
+
|
|
96
|
+
class Cluster
|
|
97
|
+
attr_reader :arguments
|
|
98
|
+
|
|
99
|
+
COMMANDS = {
|
|
100
|
+
'0.90' => lambda { |arguments, node_number|
|
|
101
|
+
<<-COMMAND.gsub(/ /, '').gsub(/\n$/, '')
|
|
102
|
+
#{arguments[:command]} \
|
|
103
|
+
-f \
|
|
104
|
+
-D es.cluster.name=#{arguments[:cluster_name]} \
|
|
105
|
+
-D es.node.name=#{arguments[:node_name]}-#{node_number} \
|
|
106
|
+
-D es.http.port=#{arguments[:port].to_i + (node_number-1)} \
|
|
107
|
+
-D es.path.data=#{arguments[:path_data]} \
|
|
108
|
+
-D es.path.work=#{arguments[:path_work]} \
|
|
109
|
+
-D es.path.logs=#{arguments[:path_logs]} \
|
|
110
|
+
-D es.cluster.routing.allocation.disk.threshold_enabled=false \
|
|
111
|
+
-D es.network.host=#{arguments[:network_host]} \
|
|
112
|
+
-D es.discovery.zen.ping.multicast.enabled=#{arguments[:multicast_enabled]} \
|
|
113
|
+
-D es.script.inline=true \
|
|
114
|
+
-D es.script.indexed=true \
|
|
115
|
+
-D es.node.test=true \
|
|
116
|
+
-D es.node.testattr=test \
|
|
117
|
+
-D es.node.bench=true \
|
|
118
|
+
-D es.path.repo=/tmp \
|
|
119
|
+
-D es.repositories.url.allowed_urls=http://snapshot.test* \
|
|
120
|
+
-D es.logger.level=#{ENV['DEBUG'] ? 'DEBUG' : 'INFO'} \
|
|
121
|
+
#{arguments[:es_params]}
|
|
122
|
+
COMMAND
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
'1.0' => lambda { |arguments, node_number|
|
|
126
|
+
<<-COMMAND.gsub(/ /, '').gsub(/\n$/, '')
|
|
127
|
+
#{arguments[:command]} \
|
|
128
|
+
-D es.foreground=yes \
|
|
129
|
+
-D es.cluster.name=#{arguments[:cluster_name]} \
|
|
130
|
+
-D es.node.name=#{arguments[:node_name]}-#{node_number} \
|
|
131
|
+
-D es.http.port=#{arguments[:port].to_i + (node_number-1)} \
|
|
132
|
+
-D es.path.data=#{arguments[:path_data]} \
|
|
133
|
+
-D es.path.work=#{arguments[:path_work]} \
|
|
134
|
+
-D es.path.logs=#{arguments[:path_logs]} \
|
|
135
|
+
-D es.cluster.routing.allocation.disk.threshold_enabled=false \
|
|
136
|
+
-D es.network.host=#{arguments[:network_host]} \
|
|
137
|
+
-D es.discovery.zen.ping.multicast.enabled=#{arguments[:multicast_enabled]} \
|
|
138
|
+
-D es.script.inline=on \
|
|
139
|
+
-D es.script.indexed=on \
|
|
140
|
+
-D es.node.test=true \
|
|
141
|
+
-D es.node.testattr=test \
|
|
142
|
+
-D es.node.bench=true \
|
|
143
|
+
-D es.path.repo=/tmp \
|
|
144
|
+
-D es.repositories.url.allowed_urls=http://snapshot.test* \
|
|
145
|
+
-D es.logger.level=#{ENV['DEBUG'] ? 'DEBUG' : 'INFO'} \
|
|
146
|
+
#{arguments[:es_params]}
|
|
147
|
+
COMMAND
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
'2.0' => lambda { |arguments, node_number|
|
|
151
|
+
<<-COMMAND.gsub(/ /, '').gsub(/\n$/, '')
|
|
152
|
+
#{arguments[:command]} \
|
|
153
|
+
-D es.foreground=yes \
|
|
154
|
+
-D es.cluster.name=#{arguments[:cluster_name]} \
|
|
155
|
+
-D es.node.name=#{arguments[:node_name]}-#{node_number} \
|
|
156
|
+
-D es.http.port=#{arguments[:port].to_i + (node_number-1)} \
|
|
157
|
+
-D es.path.data=#{arguments[:path_data]} \
|
|
158
|
+
-D es.path.work=#{arguments[:path_work]} \
|
|
159
|
+
-D es.path.logs=#{arguments[:path_logs]} \
|
|
160
|
+
-D es.cluster.routing.allocation.disk.threshold_enabled=false \
|
|
161
|
+
-D es.network.host=#{arguments[:network_host]} \
|
|
162
|
+
-D es.script.inline=true \
|
|
163
|
+
-D es.script.stored=true \
|
|
164
|
+
-D es.node.attr.testattr=test \
|
|
165
|
+
-D es.path.repo=/tmp \
|
|
166
|
+
-D es.repositories.url.allowed_urls=http://snapshot.test* \
|
|
167
|
+
-D es.logger.level=#{ENV['DEBUG'] ? 'DEBUG' : 'INFO'} \
|
|
168
|
+
#{arguments[:es_params]}
|
|
169
|
+
COMMAND
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
'5.0' => lambda { |arguments, node_number|
|
|
173
|
+
<<-COMMAND.gsub(/ /, '').gsub(/\n$/, '')
|
|
174
|
+
#{arguments[:command]} \
|
|
175
|
+
-E cluster.name=#{arguments[:cluster_name]} \
|
|
176
|
+
-E node.name=#{arguments[:node_name]}-#{node_number} \
|
|
177
|
+
-E http.port=#{arguments[:port].to_i + (node_number-1)} \
|
|
178
|
+
-E path.data=#{arguments[:path_data]} \
|
|
179
|
+
-E path.logs=#{arguments[:path_logs]} \
|
|
180
|
+
-E cluster.routing.allocation.disk.threshold_enabled=false \
|
|
181
|
+
-E network.host=#{arguments[:network_host]} \
|
|
182
|
+
-E script.inline=true \
|
|
183
|
+
-E script.stored=true \
|
|
184
|
+
-E node.attr.testattr=test \
|
|
185
|
+
-E path.repo=/tmp \
|
|
186
|
+
-E repositories.url.allowed_urls=http://snapshot.test* \
|
|
187
|
+
-E discovery.zen.minimum_master_nodes=#{arguments[:number_of_nodes]-1} \
|
|
188
|
+
-E node.max_local_storage_nodes=#{arguments[:number_of_nodes]} \
|
|
189
|
+
-E logger.level=#{ENV['DEBUG'] ? 'DEBUG' : 'INFO'} \
|
|
190
|
+
#{arguments[:es_params]}
|
|
191
|
+
COMMAND
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
'6.0' => lambda { |arguments, node_number|
|
|
195
|
+
<<-COMMAND.gsub(/ /, '').gsub(/\n$/, '')
|
|
196
|
+
#{arguments[:command]} \
|
|
197
|
+
-E cluster.name=#{arguments[:cluster_name]} \
|
|
198
|
+
-E node.name=#{arguments[:node_name]}-#{node_number} \
|
|
199
|
+
-E http.port=#{arguments[:port].to_i + (node_number-1)} \
|
|
200
|
+
-E path.data=#{arguments[:path_data]} \
|
|
201
|
+
-E path.logs=#{arguments[:path_logs]} \
|
|
202
|
+
-E cluster.routing.allocation.disk.threshold_enabled=false \
|
|
203
|
+
-E network.host=#{arguments[:network_host]} \
|
|
204
|
+
-E node.attr.testattr=test \
|
|
205
|
+
-E path.repo=/tmp \
|
|
206
|
+
-E repositories.url.allowed_urls=http://snapshot.test* \
|
|
207
|
+
-E discovery.zen.minimum_master_nodes=#{arguments[:number_of_nodes]-1} \
|
|
208
|
+
#{'-E xpack.security.enabled=false' unless arguments[:dist] == 'oss'} \
|
|
209
|
+
-E node.max_local_storage_nodes=#{arguments[:number_of_nodes]} \
|
|
210
|
+
-E logger.level=#{ENV['DEBUG'] ? 'DEBUG' : 'INFO'} \
|
|
211
|
+
#{arguments[:es_params]}
|
|
212
|
+
COMMAND
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
COMMANDS['7.0'] = COMMANDS['6.0'].clone
|
|
216
|
+
COMMANDS['8.0'] = COMMANDS['7.0'].clone
|
|
217
|
+
COMMANDS.freeze
|
|
218
|
+
|
|
219
|
+
# Create a new instance of the Cluster class
|
|
220
|
+
#
|
|
221
|
+
# @option arguments [String] :cluster_name Cluster name (default: `elasticsearch_test`)
|
|
222
|
+
# @option arguments [Integer] :number_of_nodes Number of desired nodes (default: 2)
|
|
223
|
+
# @option arguments [String] :command Elasticsearch command (default: `elasticsearch`)
|
|
224
|
+
# @option arguments [String] :port Starting port number; will be auto-incremented (default: 9250)
|
|
225
|
+
# @option arguments [String] :node_name The node name (will be appended with a number)
|
|
226
|
+
# @option arguments [String] :path_data Path to the directory to store data in
|
|
227
|
+
# @option arguments [String] :path_work Path to the directory with auxiliary files
|
|
228
|
+
# @option arguments [String] :path_logs Path to the directory with log files
|
|
229
|
+
# @option arguments [Boolean] :multicast_enabled Whether multicast is enabled (default: true)
|
|
230
|
+
# @option arguments [Integer] :timeout Timeout when starting the cluster (default: 60)
|
|
231
|
+
# @option arguments [Integer] :timeout_version Timeout when waiting for `elasticsearch --version` (default: 15)
|
|
232
|
+
# @option arguments [String] :network_host The host that nodes will bind on and publish to
|
|
233
|
+
# @option arguments [Boolean] :clear_cluster Wipe out cluster content on startup (default: true)
|
|
234
|
+
# @option arguments [Boolean] :quiet Disable printing to STDERR (default: false)
|
|
235
|
+
#
|
|
236
|
+
# You can also use environment variables to set the constructor options (see source).
|
|
237
|
+
#
|
|
238
|
+
# @see Cluster#start
|
|
239
|
+
#
|
|
240
|
+
def initialize(arguments={})
|
|
241
|
+
@arguments = arguments.dup
|
|
242
|
+
|
|
243
|
+
@arguments[:command] ||= ENV.fetch('TEST_CLUSTER_COMMAND', 'elasticsearch')
|
|
244
|
+
@arguments[:port] ||= ENV.fetch('TEST_CLUSTER_PORT', 9250).to_i
|
|
245
|
+
@arguments[:cluster_name] ||= ENV.fetch('TEST_CLUSTER_NAME', __default_cluster_name).chomp
|
|
246
|
+
@arguments[:node_name] ||= ENV.fetch('TEST_CLUSTER_NODE_NAME', 'node')
|
|
247
|
+
@arguments[:path_data] ||= ENV.fetch('TEST_CLUSTER_DATA', '/tmp/elasticsearch_test')
|
|
248
|
+
@arguments[:path_work] ||= ENV.fetch('TEST_CLUSTER_TMP', '/tmp')
|
|
249
|
+
@arguments[:path_logs] ||= ENV.fetch('TEST_CLUSTER_LOGS', '/tmp/log/elasticsearch')
|
|
250
|
+
@arguments[:es_params] ||= ENV.fetch('TEST_CLUSTER_PARAMS', '')
|
|
251
|
+
@arguments[:multicast_enabled] ||= ENV.fetch('TEST_CLUSTER_MULTICAST', 'true')
|
|
252
|
+
@arguments[:timeout] ||= ENV.fetch('TEST_CLUSTER_TIMEOUT', 60).to_i
|
|
253
|
+
@arguments[:timeout_version] ||= ENV.fetch('TEST_CLUSTER_TIMEOUT_VERSION', 15).to_i
|
|
254
|
+
@arguments[:number_of_nodes] ||= ENV.fetch('TEST_CLUSTER_NODES', 2).to_i
|
|
255
|
+
@arguments[:network_host] ||= ENV.fetch('TEST_CLUSTER_NETWORK_HOST', __default_network_host)
|
|
256
|
+
@arguments[:quiet] ||= ! ENV.fetch('QUIET', '').empty?
|
|
257
|
+
|
|
258
|
+
@clear_cluster = if @arguments[:clear_cluster].nil?
|
|
259
|
+
(ENV.fetch('TEST_CLUSTER_CLEAR', 'true') != 'false')
|
|
260
|
+
else
|
|
261
|
+
!!@arguments[:clear_cluster]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Make sure `cluster_name` is not dangerous
|
|
265
|
+
raise ArgumentError, "The `cluster_name` argument cannot be empty string or a slash" \
|
|
266
|
+
if @arguments[:cluster_name] =~ /^[\/\\]?$/
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Starts a cluster
|
|
270
|
+
#
|
|
271
|
+
# Launches the specified number of nodes in a test-suitable configuration and prints
|
|
272
|
+
# information about the cluster -- unless this specific cluster is already running.
|
|
273
|
+
#
|
|
274
|
+
# @example Start a cluster with the default configuration (2 nodes, installed version, etc)
|
|
275
|
+
# Elasticsearch::Extensions::Test::Cluster::Cluster.new.start
|
|
276
|
+
#
|
|
277
|
+
# @example Start a cluster with a custom configuration
|
|
278
|
+
# Elasticsearch::Extensions::Test::Cluster::Cluster.new(
|
|
279
|
+
# cluster_name: 'my-cluster',
|
|
280
|
+
# number_of_nodes: 3,
|
|
281
|
+
# node_name: 'my-node',
|
|
282
|
+
# port: 9350
|
|
283
|
+
# ).start
|
|
284
|
+
#
|
|
285
|
+
# @example Start a cluster with a different Elasticsearch version
|
|
286
|
+
# Elasticsearch::Extensions::Test::Cluster::Cluster.new(
|
|
287
|
+
# command: "/usr/local/Cellar/elasticsearch/1.0.0.Beta2/bin/elasticsearch"
|
|
288
|
+
# ).start
|
|
289
|
+
#
|
|
290
|
+
# @return Boolean,Array
|
|
291
|
+
# @see Cluster#stop
|
|
292
|
+
#
|
|
293
|
+
def start
|
|
294
|
+
if self.running?
|
|
295
|
+
__log "[!] Elasticsearch cluster already running".ansi(:red)
|
|
296
|
+
return false
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
__remove_cluster_data if @clear_cluster
|
|
300
|
+
|
|
301
|
+
__log "Starting ".ansi(:faint) + arguments[:number_of_nodes].to_s.ansi(:bold, :faint) +
|
|
302
|
+
" Elasticsearch #{arguments[:number_of_nodes] < 2 ? 'node' : 'nodes'}..".ansi(:faint), :print
|
|
303
|
+
|
|
304
|
+
pids = []
|
|
305
|
+
|
|
306
|
+
__log "\nUsing Elasticsearch version [#{version}]" if ENV['DEBUG']
|
|
307
|
+
|
|
308
|
+
arguments[:number_of_nodes].times do |n|
|
|
309
|
+
n += 1
|
|
310
|
+
|
|
311
|
+
command = __command(version, arguments, n)
|
|
312
|
+
command += '> /dev/null' unless ENV['DEBUG']
|
|
313
|
+
|
|
314
|
+
__log command.gsub(/ {1,}/, ' ').ansi(:bold) if ENV['DEBUG']
|
|
315
|
+
|
|
316
|
+
pid = Process.spawn(command)
|
|
317
|
+
Process.detach pid
|
|
318
|
+
pids << pid
|
|
319
|
+
sleep 1
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
__check_for_running_processes(pids)
|
|
323
|
+
wait_for_green
|
|
324
|
+
__log __cluster_info
|
|
325
|
+
|
|
326
|
+
return true
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Stops the cluster
|
|
330
|
+
#
|
|
331
|
+
# Fetches the PID numbers from "Nodes Info" API and terminates matching nodes.
|
|
332
|
+
#
|
|
333
|
+
# @example Stop the default cluster
|
|
334
|
+
# Elasticsearch::Extensions::Test::Cluster::Cluster.new.stop
|
|
335
|
+
#
|
|
336
|
+
# @example Stop the cluster reachable on specific port
|
|
337
|
+
# Elasticsearch::Extensions::Test::Cluster::Cluster.new(port: 9350).stop
|
|
338
|
+
#
|
|
339
|
+
# @return Boolean,Array
|
|
340
|
+
# @see Cluster#start
|
|
341
|
+
#
|
|
342
|
+
def stop
|
|
343
|
+
begin
|
|
344
|
+
nodes = __get_nodes
|
|
345
|
+
rescue Exception => e
|
|
346
|
+
__log "[!] Exception raised when stopping the cluster: #{e.inspect}".ansi(:red)
|
|
347
|
+
nil
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
return false if nodes.nil? or nodes.empty?
|
|
351
|
+
|
|
352
|
+
pids = nodes['nodes'].map { |id, info| info['process']['id'] }
|
|
353
|
+
|
|
354
|
+
unless pids.empty?
|
|
355
|
+
__log "Stopping Elasticsearch nodes... ".ansi(:faint), :print
|
|
356
|
+
|
|
357
|
+
pids.each_with_index do |pid, i|
|
|
358
|
+
['INT','KILL'].each do |signal|
|
|
359
|
+
begin
|
|
360
|
+
Process.kill signal, pid
|
|
361
|
+
rescue Exception => e
|
|
362
|
+
__log "[#{e.class}] PID #{pid} not found. ".ansi(:red), :print
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Give the system some breathing space to finish...
|
|
366
|
+
Kernel.sleep 1
|
|
367
|
+
|
|
368
|
+
# Check that pid really is dead
|
|
369
|
+
begin
|
|
370
|
+
Process.getpgid pid
|
|
371
|
+
# `getpgid` will raise error if pid is dead, so if we get here, try next signal
|
|
372
|
+
next
|
|
373
|
+
rescue Errno::ESRCH
|
|
374
|
+
__log "Stopped PID #{pid}".ansi(:green) +
|
|
375
|
+
(ENV['DEBUG'] ? " with #{signal} signal".ansi(:green) : '') +
|
|
376
|
+
". ".ansi(:green), :print
|
|
377
|
+
break # pid is dead
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
__log "\n"
|
|
383
|
+
else
|
|
384
|
+
return false
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
return pids
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Returns true when a specific test node is running within the cluster
|
|
391
|
+
#
|
|
392
|
+
# @return Boolean
|
|
393
|
+
#
|
|
394
|
+
def running?
|
|
395
|
+
if cluster_health = Timeout::timeout(0.25) { __get_cluster_health } rescue nil
|
|
396
|
+
return cluster_health['cluster_name'] == arguments[:cluster_name] && \
|
|
397
|
+
cluster_health['number_of_nodes'] == arguments[:number_of_nodes]
|
|
398
|
+
end
|
|
399
|
+
return false
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Waits until the cluster is green and prints information about it
|
|
403
|
+
#
|
|
404
|
+
# @return Boolean
|
|
405
|
+
#
|
|
406
|
+
def wait_for_green
|
|
407
|
+
__wait_for_status('green', arguments[:timeout])
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Returns the major version of Elasticsearch
|
|
411
|
+
#
|
|
412
|
+
# @return String
|
|
413
|
+
# @see __determine_version
|
|
414
|
+
#
|
|
415
|
+
def version
|
|
416
|
+
@version ||= __determine_version
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# Returns default `:network_host` setting based on the version
|
|
421
|
+
#
|
|
422
|
+
# @api private
|
|
423
|
+
#
|
|
424
|
+
# @return String
|
|
425
|
+
#
|
|
426
|
+
def __default_network_host
|
|
427
|
+
case version
|
|
428
|
+
when /^0|^1/
|
|
429
|
+
'0.0.0.0'
|
|
430
|
+
when /^2/
|
|
431
|
+
'_local_'
|
|
432
|
+
when /^5|^6|^7|^8/
|
|
433
|
+
'_local_'
|
|
434
|
+
else
|
|
435
|
+
raise RuntimeError, "Cannot determine default network host from version [#{version}]"
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Returns a reasonably unique cluster name
|
|
440
|
+
#
|
|
441
|
+
# @api private
|
|
442
|
+
#
|
|
443
|
+
# @return String
|
|
444
|
+
#
|
|
445
|
+
def __default_cluster_name
|
|
446
|
+
"elasticsearch-test-#{Socket.gethostname.downcase}"
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Returns the HTTP URL for the cluster based on `:network_host` setting
|
|
450
|
+
#
|
|
451
|
+
# @api private
|
|
452
|
+
#
|
|
453
|
+
# @return String
|
|
454
|
+
#
|
|
455
|
+
def __cluster_url
|
|
456
|
+
if '_local_' == arguments[:network_host]
|
|
457
|
+
"http://localhost:#{arguments[:port]}"
|
|
458
|
+
else
|
|
459
|
+
"http://#{arguments[:network_host]}:#{arguments[:port]}"
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Determine Elasticsearch version to be launched
|
|
464
|
+
#
|
|
465
|
+
# Tries to get the version from the arguments passed,
|
|
466
|
+
# if not available, it parses the version number from the `lib/elasticsearch-X.Y.Z.jar` file,
|
|
467
|
+
# if that is not available, uses `elasticsearch --version` or `elasticsearch -v`
|
|
468
|
+
#
|
|
469
|
+
# @api private
|
|
470
|
+
#
|
|
471
|
+
# @return String
|
|
472
|
+
#
|
|
473
|
+
def __determine_version
|
|
474
|
+
path_to_lib = File.dirname(arguments[:command]) + '/../lib/'
|
|
475
|
+
version = if arguments[:version]
|
|
476
|
+
arguments[:version]
|
|
477
|
+
elsif File.exist?(path_to_lib) && !(jar = Dir.entries(path_to_lib).select { |f| f =~ /^elasticsearch\-\d/ }.first).nil?
|
|
478
|
+
__log "Determining version from [#{jar}]" if ENV['DEBUG']
|
|
479
|
+
if m = jar.match(/elasticsearch\-(\d+\.\d+\.\d+).*/)
|
|
480
|
+
m[1]
|
|
481
|
+
else
|
|
482
|
+
raise RuntimeError, "Cannot determine Elasticsearch version from jar [#{jar}]"
|
|
483
|
+
end
|
|
484
|
+
else
|
|
485
|
+
__log "[!] Cannot find Elasticsearch .jar from path to command [#{arguments[:command]}], using `#{arguments[:command]} --version`" if ENV['DEBUG']
|
|
486
|
+
|
|
487
|
+
unless File.exist? arguments[:command]
|
|
488
|
+
__log "File [#{arguments[:command]}] does not exists, checking full path by `which`: ", :print if ENV['DEBUG']
|
|
489
|
+
|
|
490
|
+
begin
|
|
491
|
+
full_path = `which #{arguments[:command]}`.strip
|
|
492
|
+
__log "#{full_path.inspect}\n", :print if ENV['DEBUG']
|
|
493
|
+
rescue Exception => e
|
|
494
|
+
raise RuntimeError, "Cannot determine full path to [#{arguments[:command]}] with 'which'"
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if full_path.empty?
|
|
498
|
+
raise Errno::ENOENT, "Cannot find Elasticsearch launch script from [#{arguments[:command]}] -- did you pass a correct path?"
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
output = ''
|
|
503
|
+
|
|
504
|
+
begin
|
|
505
|
+
# First, try the new `--version` syntax...
|
|
506
|
+
__log "Running [#{arguments[:command]} --version] to determine version" if ENV['DEBUG']
|
|
507
|
+
io = IO.popen("#{arguments[:command]} --version")
|
|
508
|
+
pid = io.pid
|
|
509
|
+
|
|
510
|
+
Timeout::timeout(arguments[:timeout_version]) do
|
|
511
|
+
Process.wait(pid)
|
|
512
|
+
output = io.read
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
rescue Timeout::Error
|
|
516
|
+
# ...else, the old `-v` syntax
|
|
517
|
+
__log "Running [#{arguments[:command]} -v] to determine version" if ENV['DEBUG']
|
|
518
|
+
output = `#{arguments[:command]} -v`
|
|
519
|
+
ensure
|
|
520
|
+
if pid
|
|
521
|
+
Process.kill('INT', pid) rescue Errno::ESRCH # Most likely the process has terminated already
|
|
522
|
+
end
|
|
523
|
+
io.close unless io.closed?
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
STDERR.puts "> #{output}" if ENV['DEBUG']
|
|
527
|
+
|
|
528
|
+
if output.empty?
|
|
529
|
+
raise RuntimeError, "Cannot determine Elasticsearch version from [#{arguments[:command]} --version] or [#{arguments[:command]} -v]"
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
@dist = output.match(/Build: ([a-z]+)\//)&.[](1)
|
|
533
|
+
|
|
534
|
+
if(m = output.match(/Version: (\d+\.\d+.\d+).*,/))
|
|
535
|
+
m[1]
|
|
536
|
+
else
|
|
537
|
+
raise RuntimeError, "Cannot determine Elasticsearch version from elasticsearch --version output [#{output}]"
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
case version
|
|
542
|
+
when /^0\.90.*/
|
|
543
|
+
'0.90'
|
|
544
|
+
when /^1\..*/
|
|
545
|
+
'1.0'
|
|
546
|
+
when /^2\..*/
|
|
547
|
+
'2.0'
|
|
548
|
+
when /^5\..*/
|
|
549
|
+
'5.0'
|
|
550
|
+
when /^6\..*/
|
|
551
|
+
'6.0'
|
|
552
|
+
when /^7\..*/
|
|
553
|
+
'7.0'
|
|
554
|
+
when /^8\..*/
|
|
555
|
+
'8.0'
|
|
556
|
+
else
|
|
557
|
+
raise RuntimeError, "Cannot determine major version from [#{version}]"
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# Returns the launch command for a specific version
|
|
562
|
+
#
|
|
563
|
+
# @api private
|
|
564
|
+
#
|
|
565
|
+
# @return String
|
|
566
|
+
#
|
|
567
|
+
def __command(version, arguments, node_number)
|
|
568
|
+
raise ArgumentError, "Cannot find command for version [#{version}]" unless (command = COMMANDS[version])
|
|
569
|
+
|
|
570
|
+
arguments.merge!({ dist: @dist })
|
|
571
|
+
command.call(arguments, node_number)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Blocks the process and waits for the cluster to be in a "green" state
|
|
575
|
+
#
|
|
576
|
+
# Prints information about the cluster on STDOUT if the cluster is available.
|
|
577
|
+
#
|
|
578
|
+
# @param status [String] The status to wait for (yellow, green)
|
|
579
|
+
# @param timeout [Integer] The explicit timeout for the operation
|
|
580
|
+
#
|
|
581
|
+
# @api private
|
|
582
|
+
#
|
|
583
|
+
# @return Boolean
|
|
584
|
+
#
|
|
585
|
+
def __wait_for_status(status='green', timeout=30)
|
|
586
|
+
begin
|
|
587
|
+
Timeout::timeout(timeout) do
|
|
588
|
+
loop do
|
|
589
|
+
response = __get_cluster_health(status)
|
|
590
|
+
__log response if ENV['DEBUG']
|
|
591
|
+
|
|
592
|
+
if response && response['status'] == status && ( arguments[:number_of_nodes].nil? || arguments[:number_of_nodes].to_i == response['number_of_nodes'].to_i )
|
|
593
|
+
break
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
__log '.'.ansi(:faint), :print
|
|
597
|
+
sleep 1
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
rescue Timeout::Error => e
|
|
601
|
+
message = "\nTimeout while waiting for cluster status [#{status}]"
|
|
602
|
+
message += " and [#{arguments[:number_of_nodes]}] nodes" if arguments[:number_of_nodes]
|
|
603
|
+
__log message.ansi(:red, :bold)
|
|
604
|
+
raise e
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
return true
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Return information about the cluster
|
|
611
|
+
#
|
|
612
|
+
# @api private
|
|
613
|
+
#
|
|
614
|
+
# @return String
|
|
615
|
+
#
|
|
616
|
+
def __cluster_info
|
|
617
|
+
health = JSON.parse(Net::HTTP.get(URI("#{__cluster_url}/_cluster/health")))
|
|
618
|
+
nodes = if version == '0.90'
|
|
619
|
+
JSON.parse(Net::HTTP.get(URI("#{__cluster_url}/_nodes/?process&http")))
|
|
620
|
+
else
|
|
621
|
+
JSON.parse(Net::HTTP.get(URI("#{__cluster_url}/_nodes/process,http")))
|
|
622
|
+
end
|
|
623
|
+
master = JSON.parse(Net::HTTP.get(URI("#{__cluster_url}/_cluster/state")))['master_node']
|
|
624
|
+
|
|
625
|
+
result = ["\n",
|
|
626
|
+
('-'*80).ansi(:faint),
|
|
627
|
+
'Cluster: '.ljust(20).ansi(:faint) + health['cluster_name'].to_s.ansi(:faint),
|
|
628
|
+
'Status: '.ljust(20).ansi(:faint) + health['status'].to_s.ansi(:faint),
|
|
629
|
+
'Nodes: '.ljust(20).ansi(:faint) + health['number_of_nodes'].to_s.ansi(:faint)].join("\n")
|
|
630
|
+
|
|
631
|
+
nodes['nodes'].each do |id, info|
|
|
632
|
+
m = id == master ? '*' : '-'
|
|
633
|
+
result << "\n" +
|
|
634
|
+
''.ljust(20) +
|
|
635
|
+
"#{m} ".ansi(:faint) +
|
|
636
|
+
"#{info['name'].ansi(:bold)} ".ansi(:faint) +
|
|
637
|
+
"| version: #{info['version'] rescue 'N/A'}, ".ansi(:faint) +
|
|
638
|
+
"pid: #{info['process']['id'] rescue 'N/A'}, ".ansi(:faint) +
|
|
639
|
+
"address: #{info['http']['bound_address'] rescue 'N/A'}".ansi(:faint)
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
result
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# Tries to load cluster health information
|
|
646
|
+
#
|
|
647
|
+
# @api private
|
|
648
|
+
#
|
|
649
|
+
# @return Hash,Nil
|
|
650
|
+
#
|
|
651
|
+
def __get_cluster_health(status=nil)
|
|
652
|
+
uri = URI("#{__cluster_url}/_cluster/health")
|
|
653
|
+
uri.query = "wait_for_status=#{status}" if status
|
|
654
|
+
|
|
655
|
+
begin
|
|
656
|
+
response = Net::HTTP.get(uri)
|
|
657
|
+
rescue Exception => e
|
|
658
|
+
STDERR.puts e.inspect if ENV['DEBUG']
|
|
659
|
+
return nil
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
JSON.parse(response)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Remove the data directory
|
|
666
|
+
#
|
|
667
|
+
# @api private
|
|
668
|
+
#
|
|
669
|
+
def __remove_cluster_data
|
|
670
|
+
FileUtils.rm_rf arguments[:path_data]
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Check whether process for PIDs are running
|
|
674
|
+
#
|
|
675
|
+
# @api private
|
|
676
|
+
#
|
|
677
|
+
def __check_for_running_processes(pids)
|
|
678
|
+
if `ps -p #{pids.join(' ')}`.split("\n").size < arguments[:number_of_nodes]+1
|
|
679
|
+
__log "\n[!!!] Process failed to start (see output above)".ansi(:red)
|
|
680
|
+
exit(1)
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# Get the information about nodes
|
|
685
|
+
#
|
|
686
|
+
# @api private
|
|
687
|
+
#
|
|
688
|
+
def __get_nodes
|
|
689
|
+
JSON.parse(Net::HTTP.get(URI("#{__cluster_url}/_nodes/process")))
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
# Print to STDERR
|
|
693
|
+
#
|
|
694
|
+
def __log(message, mode=:puts)
|
|
695
|
+
STDERR.__send__ mode, message unless @arguments[:quiet]
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
end
|