elasticsearch-embedded 0.1.0
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/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Changelog.md +3 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +132 -0
- data/Rakefile +7 -0
- data/bin/embedded-elasticsearch +66 -0
- data/elasticsearch-embedded.gemspec +31 -0
- data/lib/elasticsearch-embedded.rb +1 -0
- data/lib/elasticsearch/embedded.rb +15 -0
- data/lib/elasticsearch/embedded/cluster.rb +303 -0
- data/lib/elasticsearch/embedded/downloader.rb +104 -0
- data/lib/elasticsearch/embedded/logger_configuration.rb +62 -0
- data/lib/elasticsearch/embedded/rspec_configuration.rb +70 -0
- data/lib/elasticsearch/embedded/version.rb +5 -0
- data/spec/lib/elasticsearch/embedded/cluster_spec.rb +93 -0
- data/spec/lib/elasticsearch/embedded/downloader_spec.rb +131 -0
- data/spec/lib/elasticsearch/embedded/rspec_configuration_spec.rb +51 -0
- data/spec/lib/elasticsearch/embedded_spec.rb +39 -0
- data/spec/spec_helper.rb +66 -0
- metadata +184 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f7265763b028eb234cf4ab3a26648a29822075a2
|
4
|
+
data.tar.gz: 9d4ec60f426bbcd6d268df09e75a3cc107a4ad16
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c002d9517dd909df2e743f3c4197dc7843d2f65ba3eb1e424f38c39fada1149e6f44bd75a2005b9026d2d828b1c4c3597aa5e42248f899e2ed7ca1a955be1244
|
7
|
+
data.tar.gz: 9fba9f55a6b0b1ad49419b3d0be76ebd9dbad5efc22503e09945c65fb060480c35edb8baa058543825ea1099200674b058efd46f94e254f5ea8b7380f3c3488c
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Changelog.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in elasticsearch-embedded.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# Contain a fix for open-uri
|
7
|
+
gem 'fakefs', github: 'defunkt/fakefs'
|
8
|
+
|
9
|
+
# used for code coverage reports
|
10
|
+
gem 'coveralls', require: false
|
11
|
+
|
12
|
+
# Useful for debugging
|
13
|
+
gem 'pry'
|
14
|
+
gem 'awesome_print'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Fabio Napoleoni
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Elasticsearch::Embedded
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/elasticsearch-embedded) [](https://travis-ci.org/fabn/elasticsearch-embedded) [](https://coveralls.io/r/fabn/elasticsearch-embedded)
|
4
|
+
|
5
|
+
This gem allows to download and execute elasticsearch (single node or local cluster) within a local folder.
|
6
|
+
|
7
|
+
It also provides some utilities for usage in RSpec integration tests.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'elasticsearch-embedded'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install elasticsearch-embedded
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Standalone mode
|
26
|
+
|
27
|
+
After gem installation you can run `embedded-elasticsearch` executable, it accepts some options for cluster configuration
|
28
|
+
|
29
|
+
```
|
30
|
+
$ embedded-elasticsearch -h
|
31
|
+
Usage: embedded-elasticsearch [options]
|
32
|
+
-w, --working-dir=WORKING_DIR Elasticsearch working directory (default: `Dir.tmpdir` or `Rails.root.join("tmp")` within rails applications)
|
33
|
+
-p, --port=PORT Port on which to run elasticsearch (default: 9250)
|
34
|
+
-c, --cluster-name=NAME Cluster name (default: elasticsearch_test)
|
35
|
+
-n, --nodes=NODES Number of nodes started in the cluster (default: 1)
|
36
|
+
--timeout=TIMEOUT Timeout when starting the cluster (default: 30)
|
37
|
+
-l, --log-level=LEVEL Logger verbosity, numbers allowed (1..5) or level names (debug, info, warn, error, fatal)
|
38
|
+
-q, --quiet Disable stdout logging
|
39
|
+
-S, --show-es-output Enable elasticsearch output in stdout
|
40
|
+
-V VERSION Elasticsearch version to use (default 1.2.1)
|
41
|
+
-P Configure cluster to persist data across restarts
|
42
|
+
-h, --help Show this message
|
43
|
+
-v, --version Show gem version
|
44
|
+
```
|
45
|
+
|
46
|
+
In order to start a single node cluster (with in memory indices) just run
|
47
|
+
|
48
|
+
```
|
49
|
+
$ embedded-elasticsearch -w tmp
|
50
|
+
Starting ES 1.2.1 cluster with working directory set to /Users/fabio/work/elasticsearch-embedded/tmp. Process pid is 57245
|
51
|
+
Downloading elasticsearch 1.2.1 | ᗧ| 100% (648 KB/sec) Time: 00:00:34
|
52
|
+
Starting 1 Elasticsearch nodes........
|
53
|
+
--------------------------------------------------------------------------------
|
54
|
+
Cluster: elasticsearch_test
|
55
|
+
Status: green
|
56
|
+
Nodes: 1
|
57
|
+
+ node-1 | version: 1.2.1, pid: 57254, address: inet[/0:0:0:0:0:0:0:0:9250]
|
58
|
+
|
59
|
+
# Your cluster is running and listening to port 9250
|
60
|
+
```
|
61
|
+
|
62
|
+
### Usage with foreman
|
63
|
+
|
64
|
+
```
|
65
|
+
$ cat Procfile
|
66
|
+
elasticsearch: embedded-elasticsearch -w tmp
|
67
|
+
$ foreman start
|
68
|
+
14:53:51 elasticsearch.1 | started with pid 57524
|
69
|
+
14:53:51 elasticsearch.1 | Starting ES 1.2.1 cluster with working directory set to /Users/fabionapoleoni/Desktop/work/RubyMine/elasticsearch-embedded/tmp. Process pid is 57524
|
70
|
+
14:53:57 elasticsearch.1 | Starting 1 Elasticsearch nodes........
|
71
|
+
14:53:57 elasticsearch.1 | --------------------------------------------------------------------------------
|
72
|
+
14:53:57 elasticsearch.1 | Cluster: elasticsearch_test
|
73
|
+
14:53:57 elasticsearch.1 | Status: green
|
74
|
+
14:53:57 elasticsearch.1 | Nodes: 1
|
75
|
+
14:53:57 elasticsearch.1 | + node-1 | version: 1.2.1, pid: 57528, address: inet[/0:0:0:0:0:0:0:0:9250]
|
76
|
+
^CSIGINT received
|
77
|
+
14:54:02 system | sending SIGTERM to all processes
|
78
|
+
14:54:02 elasticsearch.1 | exited with code 0%
|
79
|
+
```
|
80
|
+
|
81
|
+
### With RSpec
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# In spec/spec_helper.rb
|
85
|
+
require 'elasticsearch-embedded'
|
86
|
+
# Activate gem behavior with :elasticsearch tagged specs
|
87
|
+
Elasticsearch::Embedded::RSpec.configure
|
88
|
+
# Alternatively you could specify for which tags you want gem support
|
89
|
+
Elasticsearch::Embedded::RSpec.configure_with :search
|
90
|
+
|
91
|
+
# In tagged specs
|
92
|
+
describe Something, :elasticsearch do
|
93
|
+
|
94
|
+
before(:each) do
|
95
|
+
# all indices are deleted automatically at beginning of each spec
|
96
|
+
expect(client.indices.get_settings).to be_empty
|
97
|
+
end
|
98
|
+
|
99
|
+
# If elastic search client is defined (i.e. with gem 'elasticsearch' or require 'elasticsearch')
|
100
|
+
it 'should make elastic search client available' do
|
101
|
+
expect(client).to be_an_instance_of(::Elasticsearch::Transport::Client)
|
102
|
+
# Create a document into test cluster
|
103
|
+
client.index index: 'test', type: 'test-type', id: 1, body: {title: 'Test'}
|
104
|
+
end
|
105
|
+
|
106
|
+
# Cluster object is also exposed in a helper
|
107
|
+
it 'should make cluster object available' do
|
108
|
+
expect(cluster).to be_an_instance_of(::Elasticsearch::Embedded::Cluster)
|
109
|
+
end
|
110
|
+
|
111
|
+
# If elastic search client is not defined client return an URI instance with base url for the cluster
|
112
|
+
it 'should return cluster uri when elasticsearch is not defined' do
|
113
|
+
::Elasticsearch.send(:remove_const, :Client)
|
114
|
+
expect(client).to be_an_instance_of(::URI::HTTP)
|
115
|
+
expect(Net::HTTP.get(client)).to include('You Know, for Search')
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
```
|
121
|
+
|
122
|
+
### With Test::Unit/minitest
|
123
|
+
|
124
|
+
Pull requests are welcome.
|
125
|
+
|
126
|
+
## Contributing
|
127
|
+
|
128
|
+
1. Fork it ( https://github.com/fabn/elasticsearch-embedded/fork )
|
129
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
130
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
131
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
132
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'elasticsearch-embedded'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
cluster = Elasticsearch::Embedded::Cluster.new
|
7
|
+
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
|
10
|
+
opts.on '-w', '--working-dir=WORKING_DIR', 'Elasticsearch working directory (default: `Dir.tmpdir` or `Rails.root.join("tmp")` within rails applications)' do |wd|
|
11
|
+
cluster.working_dir = File.expand_path(wd)
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on '-p', '--port=PORT', Integer, 'Port on which to run elasticsearch (default: 9250)' do |p|
|
15
|
+
cluster.port = p
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on '-c', '--cluster-name=NAME', 'Cluster name (default: elasticsearch_test)' do |cn|
|
19
|
+
cluster.cluster_name = cn
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on '-n', '--nodes=NODES', Integer, 'Number of nodes started in the cluster (default: 1)' do |n|
|
23
|
+
cluster.nodes = n
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on '--timeout=TIMEOUT', Integer, 'Timeout when starting the cluster (default: 30)' do |t|
|
27
|
+
cluster.timeout = t
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on '-l', '--log-level=LEVEL', "Logger verbosity, numbers allowed (1..5) or level names (#{Logging::LEVELS.keys.join(', ')})" do |l|
|
31
|
+
::Elasticsearch::Embedded.verbosity(l)
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on '-q', '--quiet', 'Disable stdout logging' do
|
35
|
+
::Elasticsearch::Embedded.mute!
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on '-S', '--show-es-output', 'Enable elasticsearch output in stdout' do
|
39
|
+
cluster.verbose = true
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on '-V VERSION', "Elasticsearch version to use (default #{Elasticsearch::Embedded::Downloader::DEFAULT_VERSION})" do |v|
|
43
|
+
cluster.version = v
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on '-P', 'Configure cluster to persist data across restarts' do |p|
|
47
|
+
cluster.persistent = !!p
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on_tail '-h', '--help', 'Show this message' do
|
51
|
+
puts opts
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on_tail '-v', '--version', 'Show gem version' do
|
56
|
+
puts "Gem version: #{Elasticsearch::Embedded::VERSION}"
|
57
|
+
puts "Elasticsearch version: #{cluster.version}"
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
|
61
|
+
end.parse!
|
62
|
+
|
63
|
+
# Forward additional arguments to elasticsearch
|
64
|
+
cluster.additional_options = ARGV.join(' ') unless ARGV.empty?
|
65
|
+
# Start the cluster
|
66
|
+
cluster.start_and_wait!
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elasticsearch/embedded/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'elasticsearch-embedded'
|
8
|
+
spec.version = Elasticsearch::Embedded::VERSION
|
9
|
+
spec.authors = ['Fabio Napoleoni']
|
10
|
+
spec.email = ['f.napoleoni@gmail.com']
|
11
|
+
spec.summary = %q{Install an embedded version of elasticsearch into your project}
|
12
|
+
spec.homepage = 'https://github.com/fabn/elasticsearch-embedded'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.required_ruby_version = '>= 1.9.2'
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'ruby-progressbar', '~> 1.5.1'
|
23
|
+
spec.add_runtime_dependency 'rubyzip', '~> 1.0.0'
|
24
|
+
spec.add_runtime_dependency 'logging', '~> 1.8.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
29
|
+
spec.add_development_dependency 'fakefs', '~> 0.5.0'
|
30
|
+
spec.add_development_dependency 'elasticsearch', '~> 1.0.2'
|
31
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'elasticsearch/embedded'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'elasticsearch/embedded/version'
|
2
|
+
require 'elasticsearch/embedded/logger_configuration'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Embedded
|
6
|
+
|
7
|
+
autoload :Downloader, 'elasticsearch/embedded/downloader'
|
8
|
+
autoload :Cluster, 'elasticsearch/embedded/cluster'
|
9
|
+
autoload :RSpec, 'elasticsearch/embedded/rspec_configuration'
|
10
|
+
|
11
|
+
# Configure logging for this module
|
12
|
+
extend LoggerConfiguration
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Elasticsearch
|
7
|
+
module Embedded
|
8
|
+
|
9
|
+
# Class used to manage a local cluster of elasticsearch nodes
|
10
|
+
class Cluster
|
11
|
+
|
12
|
+
# Make logger method available
|
13
|
+
include Logging.globally
|
14
|
+
|
15
|
+
# Options for cluster
|
16
|
+
attr_accessor :port, :cluster_name, :nodes, :timeout, :persistent, :additional_options, :verbose
|
17
|
+
|
18
|
+
# Options for downloader
|
19
|
+
attr_accessor :downloader, :version, :working_dir
|
20
|
+
|
21
|
+
# Assign default values to options
|
22
|
+
def initialize
|
23
|
+
@nodes = 1
|
24
|
+
@port = 9250
|
25
|
+
@version = Downloader::DEFAULT_VERSION
|
26
|
+
@working_dir = Downloader::TEMPORARY_PATH
|
27
|
+
@timeout = 30
|
28
|
+
@cluster_name = 'elasticsearch_test'
|
29
|
+
@pids = []
|
30
|
+
@pids_lock = Mutex.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Start an elasticsearch cluster and return immediately
|
34
|
+
def start
|
35
|
+
@downloader = Downloader.download(version: version, path: working_dir)
|
36
|
+
start_cluster
|
37
|
+
apply_development_template! if persistent
|
38
|
+
end
|
39
|
+
|
40
|
+
# Start an elasticsearch cluster and wait until running, also register
|
41
|
+
# a signal handler to close the cluster on INT, TERM and QUIT signals
|
42
|
+
def start_and_wait!
|
43
|
+
# register handler before starting cluster
|
44
|
+
register_shutdown_handler
|
45
|
+
# start the cluster
|
46
|
+
start
|
47
|
+
# Wait for all child processes to end then return
|
48
|
+
Process.waitall
|
49
|
+
end
|
50
|
+
|
51
|
+
# Stop the cluster and return after all child processes are dead
|
52
|
+
def stop
|
53
|
+
logger.warn 'Cluster is still starting, wait until startup is complete before sending shutdown command' if @pids_lock.locked?
|
54
|
+
@pids_lock.synchronize do
|
55
|
+
http_object.post('/_shutdown', nil)
|
56
|
+
logger.debug 'Cluster stopped succesfully using shutdown api'
|
57
|
+
Timeout.timeout(2) { Process.waitall }
|
58
|
+
# Reset running pids reader
|
59
|
+
@pids = []
|
60
|
+
end
|
61
|
+
rescue
|
62
|
+
logger.warn "Following processes are still alive #{pids}, kill them with signals"
|
63
|
+
# Send term signal if post request fails to all processes still alive after 2 seconds
|
64
|
+
pids.each { |pid| wait_or_kill(pid) }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Thread safe access to all spawned process pids
|
68
|
+
def pids
|
69
|
+
@pids_lock.synchronize { @pids }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Start server unless it's running
|
73
|
+
def ensure_started!
|
74
|
+
start unless running?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns true when started cluster is running
|
78
|
+
#
|
79
|
+
# @return Boolean
|
80
|
+
def running?
|
81
|
+
cluster_health = Timeout::timeout(0.25) { __get_cluster_health } rescue nil
|
82
|
+
# Response is present, cluster name is the same and number of nodes is the same
|
83
|
+
!!cluster_health && cluster_health['cluster_name'] == cluster_name && cluster_health['number_of_nodes'] == nodes
|
84
|
+
end
|
85
|
+
|
86
|
+
# Remove all indices in the cluster
|
87
|
+
#
|
88
|
+
# @return [Array<Net::HTTPResponse>] raw http responses
|
89
|
+
def delete_all_indices!
|
90
|
+
delete_index! :_all
|
91
|
+
end
|
92
|
+
|
93
|
+
# Remove the indices given as args
|
94
|
+
#
|
95
|
+
# @param [Array<String,Symbol>] args list of indices to delet
|
96
|
+
# @return [Array<Net::HTTPResponse>] raw http responses
|
97
|
+
def delete_index!(*args)
|
98
|
+
args.map { |index| http_object.request(Net::HTTP::Delete.new("/#{index}")) }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Used for persistent clusters, otherwise cluster won't get green state because of missing replicas
|
102
|
+
def apply_development_template!
|
103
|
+
development_settings = {
|
104
|
+
template: '*',
|
105
|
+
settings: {
|
106
|
+
number_of_shards: 1,
|
107
|
+
number_of_replicas: 0,
|
108
|
+
}
|
109
|
+
}
|
110
|
+
# Create the template on cluster
|
111
|
+
http_object.put('/_template/development_template', JSON.dump(development_settings))
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Build command line to launch an instance
|
117
|
+
def build_command_line(instance_number)
|
118
|
+
[
|
119
|
+
downloader.executable,
|
120
|
+
'-D es.foreground=yes',
|
121
|
+
"-D es.cluster.name=#{cluster_name}",
|
122
|
+
"-D es.node.name=node-#{instance_number}",
|
123
|
+
"-D es.http.port=#{port + (instance_number - 1)}",
|
124
|
+
"-D es.gateway.type=#{cluster_options[:gateway_type]}",
|
125
|
+
"-D es.index.store.type=#{cluster_options[:index_store]}",
|
126
|
+
"-D es.path.data=#{cluster_options[:path_data]}-#{instance_number}",
|
127
|
+
"-D es.path.work=#{cluster_options[:path_work]}-#{instance_number}",
|
128
|
+
'-D es.network.host=0.0.0.0',
|
129
|
+
'-D es.discovery.zen.ping.multicast.enabled=true',
|
130
|
+
'-D es.script.disable_dynamic=false',
|
131
|
+
'-D es.node.test=true',
|
132
|
+
'-D es.node.bench=true',
|
133
|
+
additional_options,
|
134
|
+
verbose ? nil : '> /dev/null'
|
135
|
+
].compact.join(' ')
|
136
|
+
end
|
137
|
+
|
138
|
+
# Spawn an elasticsearch process and return its pid
|
139
|
+
def launch_instance(instance_number = 1)
|
140
|
+
# Start the process within a new process group to avoid signal propagation
|
141
|
+
Process.spawn(build_command_line(instance_number), pgroup: true).tap do |pid|
|
142
|
+
logger.debug "Launched elasticsearch process with pid #{pid}, detaching it"
|
143
|
+
Process.detach pid
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return running instances pids, borrowed from code in Elasticsearch::Extensions::Test::Cluster.
|
148
|
+
# This method returns elasticsearch nodes pids and not spawned command pids, they are different because of
|
149
|
+
# elasticsearch shell wrapper used to launch the daemon
|
150
|
+
def nodes_pids
|
151
|
+
# Try to fetch node info from running cluster
|
152
|
+
nodes = JSON.parse(http_object.get('/_nodes/?process').body) rescue []
|
153
|
+
# Fetch pids from returned data
|
154
|
+
nodes.empty? ? nodes : nodes['nodes'].map { |_, info| info['process']['id'] }
|
155
|
+
end
|
156
|
+
|
157
|
+
def start_cluster
|
158
|
+
logger.info "Starting ES #{version} cluster with working directory set to #{working_dir}. Process pid is #{$$}"
|
159
|
+
if running?
|
160
|
+
logger.warn "Elasticsearch cluster already running on port #{port}"
|
161
|
+
wait_for_green(timeout)
|
162
|
+
return
|
163
|
+
end
|
164
|
+
# Launch single node instances of elasticsearch with synchronization
|
165
|
+
@pids_lock.synchronize do
|
166
|
+
1.upto(nodes).each do |i|
|
167
|
+
@pids << launch_instance(i)
|
168
|
+
end
|
169
|
+
# Wait for cluster green state before releasing lock
|
170
|
+
wait_for_green(timeout)
|
171
|
+
# Add started nodes pids to pid array
|
172
|
+
@pids.concat(nodes_pids)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def wait_or_kill(pid)
|
177
|
+
begin
|
178
|
+
Timeout::timeout(2) do
|
179
|
+
Process.kill(:TERM, pid)
|
180
|
+
logger.debug "Sent SIGTERM to process #{pid}"
|
181
|
+
Process.waitpid(pid)
|
182
|
+
logger.info "Process #{pid} exited successfully"
|
183
|
+
end
|
184
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
185
|
+
# No such process or no child => process is already dead
|
186
|
+
logger.debug "Process with pid #{pid} is already dead"
|
187
|
+
rescue Timeout::Error
|
188
|
+
logger.info "Process #{pid} still running after 2 seconds, sending SIGKILL to it"
|
189
|
+
Process.kill(:KILL, pid) rescue nil
|
190
|
+
ensure
|
191
|
+
logger.debug "Removing #{pid} from running pids"
|
192
|
+
@pids_lock.synchronize { @pids.delete(pid) }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Used as arguments for building command line to launch elasticsearch
|
197
|
+
def cluster_options
|
198
|
+
{
|
199
|
+
port: port,
|
200
|
+
nodes: nodes,
|
201
|
+
cluster_name: cluster_name,
|
202
|
+
timeout: timeout,
|
203
|
+
# command to run is taken from downloader object
|
204
|
+
command: downloader.executable,
|
205
|
+
# persistency options
|
206
|
+
gateway_type: persistent ? 'local' : 'none',
|
207
|
+
index_store: persistent ? 'mmapfs' : 'memory',
|
208
|
+
path_data: File.join(persistent ? downloader.working_dir : Dir.tmpdir, 'cluster_data'),
|
209
|
+
path_work: File.join(persistent ? downloader.working_dir : Dir.tmpdir, 'cluster_workdir'),
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
# Return an http object to make requests
|
214
|
+
def http_object
|
215
|
+
@http ||= Net::HTTP.new('localhost', port)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Register a shutdown proc which handles INT, TERM and QUIT signals
|
219
|
+
def register_shutdown_handler
|
220
|
+
stopper = ->(sig) do
|
221
|
+
Thread.new do
|
222
|
+
logger.info "Received SIG#{Signal.signame(sig)}, quitting"
|
223
|
+
stop
|
224
|
+
end
|
225
|
+
end
|
226
|
+
# Stop cluster on Ctrl+C, TERM (foreman) or QUIT (other)
|
227
|
+
[:TERM, :INT, :QUIT].each { |sig| Signal.trap(sig, &stopper) }
|
228
|
+
end
|
229
|
+
|
230
|
+
# Waits until the cluster is green and prints information
|
231
|
+
#
|
232
|
+
# @example Print the information about the default cluster
|
233
|
+
# Elasticsearch::Extensions::Test::Cluster.wait_for_green
|
234
|
+
#
|
235
|
+
# @param (see #__wait_for_status)
|
236
|
+
#
|
237
|
+
# @return Boolean
|
238
|
+
#
|
239
|
+
def wait_for_green(timeout = 60)
|
240
|
+
__wait_for_status('green', timeout)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Blocks the process and waits for the cluster to be in a "green" state.
|
244
|
+
#
|
245
|
+
# Prints information about the cluster on STDOUT if the cluster is available.
|
246
|
+
#
|
247
|
+
# @param status [String] The status to wait for (yellow, green)
|
248
|
+
# @param timeout [Integer] The explicit timeout for the operation
|
249
|
+
#
|
250
|
+
# @api private
|
251
|
+
#
|
252
|
+
# @return Boolean
|
253
|
+
#
|
254
|
+
def __wait_for_status(status='green', timeout = 30)
|
255
|
+
Timeout::timeout(timeout) do
|
256
|
+
loop do
|
257
|
+
response = JSON.parse(http_object.get("/_cluster/health?wait_for_status=#{status}").body) rescue {}
|
258
|
+
|
259
|
+
# check response and return if ok
|
260
|
+
if response['status'] == status && nodes == response['number_of_nodes'].to_i
|
261
|
+
__print_cluster_info and break
|
262
|
+
end
|
263
|
+
|
264
|
+
logger.debug "Still waiting for #{status} status in #{cluster_name}"
|
265
|
+
sleep 1
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
true
|
270
|
+
end
|
271
|
+
|
272
|
+
# Print information about the cluster on STDOUT
|
273
|
+
#
|
274
|
+
# @api private
|
275
|
+
#
|
276
|
+
def __print_cluster_info
|
277
|
+
health = JSON.parse(http_object.get('/_cluster/health').body)
|
278
|
+
nodes = JSON.parse(http_object.get('/_nodes/process,http').body)
|
279
|
+
master = JSON.parse(http_object.get('/_cluster/state').body)['master_node']
|
280
|
+
|
281
|
+
logger.info '-'*80
|
282
|
+
logger.info 'Cluster: '.ljust(12) + health['cluster_name'].to_s
|
283
|
+
logger.info 'Status: '.ljust(12) + health['status'].to_s
|
284
|
+
logger.info 'Nodes: '.ljust(12) + health['number_of_nodes'].to_s
|
285
|
+
|
286
|
+
nodes['nodes'].each do |id, info|
|
287
|
+
m = id == master ? '+' : '-'
|
288
|
+
logger.info ''.ljust(12) + "#{m} #{info['name']} | version: #{info['version']}, pid: #{info['process']['id']}, address: #{info['http']['bound_address']}"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Tries to load cluster health information
|
293
|
+
#
|
294
|
+
# @api private
|
295
|
+
#
|
296
|
+
def __get_cluster_health
|
297
|
+
JSON.parse(http_object.get('/_cluster/health').body) rescue nil
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|