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