elasticsearch-manager 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +81 -0
- data/LICENSE +201 -0
- data/README.md +55 -0
- data/REALEASES.md +0 -0
- data/bin/elasticsearch-manager +56 -0
- data/elasticsearch-manager.gemspec +33 -0
- data/lib/elasticsearch/client.rb +8 -0
- data/lib/elasticsearch/client/base.rb +71 -0
- data/lib/elasticsearch/client/elasticsearch.rb +43 -0
- data/lib/elasticsearch/manager.rb +8 -0
- data/lib/elasticsearch/manager/cmd.rb +62 -0
- data/lib/elasticsearch/manager/errors.rb +12 -0
- data/lib/elasticsearch/manager/manager.rb +60 -0
- data/lib/elasticsearch/manager/rollingrestart.rb +79 -0
- data/lib/elasticsearch/manager/version.rb +5 -0
- data/lib/elasticsearch/model.rb +5 -0
- data/lib/elasticsearch/model/health.rb +26 -0
- data/lib/elasticsearch/model/node.rb +40 -0
- data/lib/elasticsearch/model/routing_nodes.rb +28 -0
- data/lib/elasticsearch/model/shard.rb +22 -0
- data/lib/elasticsearch/model/state.rb +23 -0
- data/spec/client_spec.rb +33 -0
- data/spec/cmd_spec.rb +123 -0
- data/spec/esclient_spec.rb +64 -0
- data/spec/fixtures/health.json +12 -0
- data/spec/fixtures/health_initializing.json +12 -0
- data/spec/fixtures/health_realocating.json +12 -0
- data/spec/fixtures/health_red.json +12 -0
- data/spec/fixtures/health_unassigned.json +12 -0
- data/spec/fixtures/health_yellow.json +13 -0
- data/spec/fixtures/nodes_.json +837 -0
- data/spec/fixtures/state-node-initializing.json +193010 -0
- data/spec/fixtures/state.json +193018 -0
- data/spec/manager_spec.rb +234 -0
- data/spec/spec_helper.rb +226 -0
- metadata +266 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'rest-client'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Client
|
6
|
+
class Base
|
7
|
+
def initialize(host = 'localhost', port = 9200, logger = Logger.new(STDOUT))
|
8
|
+
@host = host
|
9
|
+
@port = port
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(uri, params = nil)
|
14
|
+
raw = _get(uri, params)
|
15
|
+
if !raw.headers[:content_type].nil? && raw.headers[:content_type][/json/]
|
16
|
+
JSON.parse(raw)
|
17
|
+
else
|
18
|
+
raw
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def put(uri, body, params = nil)
|
23
|
+
raw = _put(uri, body, params)
|
24
|
+
if !raw.headers[:content_type].nil? && raw.headers[:content_type][/json/]
|
25
|
+
JSON.parse(raw)
|
26
|
+
else
|
27
|
+
raw
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def _get(uri, params = nil)
|
32
|
+
url = _build_url(uri)
|
33
|
+
opts = _prep_opts(params)
|
34
|
+
|
35
|
+
begin
|
36
|
+
return RestClient.get url, opts
|
37
|
+
rescue Exception => e
|
38
|
+
@logger.error(e.message)
|
39
|
+
raise e
|
40
|
+
raise IOError.new "Unable to complete get request: #{e}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def _put(uri, body, params = nil)
|
45
|
+
url = _build_url(uri)
|
46
|
+
opts = _prep_opts(params)
|
47
|
+
|
48
|
+
begin
|
49
|
+
return RestClient.put url, body, opts
|
50
|
+
rescue Exception => e
|
51
|
+
@logger.error(e.message)
|
52
|
+
raise IOError.new "Unable to complete put request: #{e}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def _prep_opts(params)
|
57
|
+
unless params.nil?
|
58
|
+
{:params => params}
|
59
|
+
else
|
60
|
+
{}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def _build_url(uri)
|
67
|
+
"http://#{@host}:#{@port}#{uri}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Client
|
6
|
+
class ESClient < Base
|
7
|
+
def status
|
8
|
+
data = get('/_cluster/health')
|
9
|
+
data['status']
|
10
|
+
end
|
11
|
+
|
12
|
+
def green?
|
13
|
+
status == 'green'
|
14
|
+
end
|
15
|
+
|
16
|
+
def health
|
17
|
+
get('/_cluster/health')
|
18
|
+
end
|
19
|
+
|
20
|
+
def nodes
|
21
|
+
get('/_nodes')
|
22
|
+
end
|
23
|
+
|
24
|
+
def state
|
25
|
+
get('/_cluster/state')
|
26
|
+
end
|
27
|
+
|
28
|
+
def settings
|
29
|
+
get('/_cluster/settings')
|
30
|
+
end
|
31
|
+
|
32
|
+
def routing(disable = true)
|
33
|
+
data = {
|
34
|
+
'transient' => {
|
35
|
+
'cluster.routing.allocation.enable' => disable ? 'none' : 'all'
|
36
|
+
}
|
37
|
+
}.to_json
|
38
|
+
|
39
|
+
put('/_cluster/settings', data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'net/ssh'
|
3
|
+
|
4
|
+
require 'elasticsearch/manager/manager'
|
5
|
+
require 'elasticsearch/manager/rollingrestart'
|
6
|
+
|
7
|
+
|
8
|
+
module Elasticsearch
|
9
|
+
module Manager
|
10
|
+
module CMD
|
11
|
+
include Elasticsearch::Manager
|
12
|
+
|
13
|
+
def self.rolling_restart(opts)
|
14
|
+
manager = _manager(opts)
|
15
|
+
# Check that the cluster is stable?
|
16
|
+
unless manager.cluster_stable?
|
17
|
+
print_cluster_stable(manager)
|
18
|
+
return 2
|
19
|
+
end
|
20
|
+
puts "Discovering cluster members...\n"
|
21
|
+
manager.cluster_members!
|
22
|
+
timeout = opts[:timeout] || 600
|
23
|
+
sleep_interval = opts[:sleep_interval] || 30
|
24
|
+
begin
|
25
|
+
manager.rolling_restart(timeout, sleep_interval)
|
26
|
+
rescue Exception => e
|
27
|
+
puts e
|
28
|
+
return 2
|
29
|
+
end
|
30
|
+
puts 'Rolling restart complete.'
|
31
|
+
return 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.status(opts)
|
35
|
+
manager = _manager(opts)
|
36
|
+
status = manager.cluster_status
|
37
|
+
puts "The Elasticsearch cluster is currently: #{status.colorize(status.to_sym)}"
|
38
|
+
return 0
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def self._manager(opts)
|
43
|
+
ESManager.new(opts[:hostname], opts[:port])
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.print_cluster_stable(manager)
|
47
|
+
health = manager.cluster_health
|
48
|
+
puts 'The cluster is currently unstable! Not proceeding with rolling-restart'
|
49
|
+
puts "\tCluster status: #{health.status.colorize(health.status.to_sym)}"
|
50
|
+
|
51
|
+
relocating = health.relocating_shards == 0 ? :green : :red
|
52
|
+
puts "\tRelocating shards: #{health.relocating_shards.to_s.colorize(relocating)}"
|
53
|
+
|
54
|
+
initializing = health.initializing_shards == 0 ? :green : :red
|
55
|
+
puts "\tInitializing shards: #{health.relocating_shards.to_s.colorize(relocating)}"
|
56
|
+
|
57
|
+
unassigned = health.unassigned_shards == 0 ? :green : :red
|
58
|
+
puts "\tUnassigned shards: #{health.relocating_shards.to_s.colorize(relocating)}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'elasticsearch/client'
|
2
|
+
require 'elasticsearch/model'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Manager
|
6
|
+
class ESManager
|
7
|
+
include Elasticsearch::Model
|
8
|
+
|
9
|
+
attr_accessor :leader, :members, :nodes
|
10
|
+
|
11
|
+
def initialize(cluster_host = 'localhost', port = 9200)
|
12
|
+
@client = Elasticsearch::Client::ESClient.new(cluster_host, port)
|
13
|
+
@leader = nil
|
14
|
+
@nodes = nil
|
15
|
+
@members = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def cluster_green?
|
19
|
+
@client.green?
|
20
|
+
end
|
21
|
+
|
22
|
+
def cluster_status
|
23
|
+
@client.status
|
24
|
+
end
|
25
|
+
|
26
|
+
def cluster_stable?
|
27
|
+
health = cluster_health
|
28
|
+
moving = [health.relocating_shards, health.initializing_shards, health.unassigned_shards]
|
29
|
+
cluster_green? && moving.all? { |x| x == 0 }
|
30
|
+
end
|
31
|
+
|
32
|
+
def cluster_members!
|
33
|
+
state = cluster_state
|
34
|
+
@nodes = state.nodes
|
35
|
+
@leader = @nodes.select { |n| n.master }[0].ip
|
36
|
+
@members = @nodes.map { |n| n.ip }
|
37
|
+
end
|
38
|
+
|
39
|
+
def cluster_health
|
40
|
+
health = @client.health
|
41
|
+
Health.new.extend(Health::Representer).from_hash(health)
|
42
|
+
end
|
43
|
+
|
44
|
+
def cluster_state
|
45
|
+
state = @client.state
|
46
|
+
ClusterState.new.extend(ClusterState::Representer).from_hash(state)
|
47
|
+
end
|
48
|
+
|
49
|
+
def disable_routing
|
50
|
+
ret = @client.routing(true)
|
51
|
+
ret['transient']['cluster']['routing']['allocation']['enable'] == 'none'
|
52
|
+
end
|
53
|
+
|
54
|
+
def enable_routing
|
55
|
+
ret = @client.routing(false)
|
56
|
+
ret['transient']['cluster']['routing']['allocation']['enable'] == 'all'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'timeout'
|
4
|
+
require 'highline'
|
5
|
+
|
6
|
+
require 'elasticsearch/manager/errors'
|
7
|
+
require 'elasticsearch/manager/manager'
|
8
|
+
|
9
|
+
|
10
|
+
module Elasticsearch
|
11
|
+
module Manager
|
12
|
+
|
13
|
+
class ESManager
|
14
|
+
def rolling_restart(timeout = 600, sleep_interval = 30)
|
15
|
+
highline = HighLine.new
|
16
|
+
@members.each do |m|
|
17
|
+
restart_node(m, timeout, sleep_interval) unless m == @leader
|
18
|
+
if m != @members[-1]
|
19
|
+
unless highline.agree('Continue with rolling restart of cluster? (y/n) ')
|
20
|
+
raise UserRequestedStop, "Stopping rolling restart at user request!".colorize(:red)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
unless highline.agree("\nRestarting current cluster master, continue? (y/n) ")
|
25
|
+
raise UserRequestedStop, "Stopping rolling restart at user request before restarting master node!".colorize(:red)
|
26
|
+
end
|
27
|
+
restart_node(@leader, timeout, sleep_interval)
|
28
|
+
end
|
29
|
+
|
30
|
+
def restart_node(node_ip, timeout, sleep_interval)
|
31
|
+
puts "\nRestarting Elasticsearch on node: #{node_ip}"
|
32
|
+
raise "Could not disable shard routing prior to restarting node: #{node_ip}".colorize(:red) unless disable_routing
|
33
|
+
|
34
|
+
Net::SSH.start(node_ip, ENV['USER']) do |ssh|
|
35
|
+
ssh.exec 'sudo service elasticsearch restart'
|
36
|
+
end
|
37
|
+
puts "Elasticsearch restarted on node: #{node_ip}"
|
38
|
+
|
39
|
+
begin
|
40
|
+
wait_for_node_available(node_ip, timeout, sleep_interval)
|
41
|
+
puts "Node back up!".colorize(:green)
|
42
|
+
rescue Timeout::Error
|
43
|
+
raise NodeAvailableTimeout, "Node did not become available after waiting #{timeout} seconds...".colorize(:red)
|
44
|
+
end
|
45
|
+
|
46
|
+
raise "Could not re-enable shard routing following restart of node: #{node_ip}".colorize(:red) unless enable_routing
|
47
|
+
|
48
|
+
begin
|
49
|
+
wait_for_stable(timeout, sleep_interval)
|
50
|
+
puts "Cluster stabilized!".colorize(:green)
|
51
|
+
rescue Timeout::Error
|
52
|
+
raise StabalizationTimeout, "Cluster not re-stabilize after waiting #{timeout} seconds...".colorize(:red)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def wait_for_stable(timeout = 600, sleep_interval = 30)
|
59
|
+
Timeout.timeout(timeout) do
|
60
|
+
while !cluster_stable?
|
61
|
+
puts "Waiting for cluster to stabilize...".colorize(:yellow)
|
62
|
+
sleep(sleep_interval)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def wait_for_node_available(node_ip, timeout = 600, sleep_interval = 30)
|
68
|
+
Timeout.timeout(timeout) do
|
69
|
+
state = cluster_state
|
70
|
+
while !state.nodes.map { |n| n.ip }.include?(node_ip)
|
71
|
+
puts "Waiting for node to become available...".colorize(:yellow)
|
72
|
+
sleep(sleep_interval)
|
73
|
+
state = cluster_state
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'representable/json'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Model
|
6
|
+
class Health < OpenStruct
|
7
|
+
module Representer
|
8
|
+
include Representable::JSON
|
9
|
+
include Representable::Hash
|
10
|
+
include Representable::Hash::AllowSymbols
|
11
|
+
|
12
|
+
property :cluster_name
|
13
|
+
property :status
|
14
|
+
property :timed_out
|
15
|
+
property :number_of_nodes
|
16
|
+
property :number_of_data_nodes
|
17
|
+
property :active_primary_shards
|
18
|
+
property :active_shards
|
19
|
+
property :relocating_shards
|
20
|
+
property :initializing_shards
|
21
|
+
property :unassigned_shards
|
22
|
+
end
|
23
|
+
extend Representer
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'representable/json'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Model
|
6
|
+
class Node < OpenStruct
|
7
|
+
module Representer
|
8
|
+
include Representable::JSON
|
9
|
+
include Representable::Hash
|
10
|
+
include Representable::Hash::AllowSymbols
|
11
|
+
|
12
|
+
property :name
|
13
|
+
property :transport_address
|
14
|
+
property :host
|
15
|
+
property :ip
|
16
|
+
property :version
|
17
|
+
property :build
|
18
|
+
property :http_address
|
19
|
+
|
20
|
+
nested :settings do
|
21
|
+
nested :gateway do
|
22
|
+
property :gateway_expected_nodes, as: :expected_nodes
|
23
|
+
end
|
24
|
+
|
25
|
+
nested :http do
|
26
|
+
property :http_port, as: :port
|
27
|
+
end
|
28
|
+
|
29
|
+
nested :path do
|
30
|
+
property :data_path, as: :data
|
31
|
+
property :home_path, as: :home
|
32
|
+
property :logs_path, as: :logs
|
33
|
+
property :conf_path, as: :conf
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
extend Representer
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'representable/json'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Model
|
6
|
+
class RoutingNodes < OpenStruct
|
7
|
+
module Representer
|
8
|
+
include Representable::JSON
|
9
|
+
include Representable::Hash
|
10
|
+
include Representable::Hash::AllowSymbols
|
11
|
+
|
12
|
+
property :cluster_name
|
13
|
+
property :master_node
|
14
|
+
nested :routing_nodes do
|
15
|
+
property :unassigned, setter: (lambda do |v,args|
|
16
|
+
self.unassigned = v.map {|s| Elasticsearch::Manager::Model::Shard.new.extend(Elastiman::Model::Shard::Representer).from_hash(s) }
|
17
|
+
end)
|
18
|
+
property :nodes, setter: (lambda do |v,args|
|
19
|
+
self.nodes = v.map do |id, shards|
|
20
|
+
shards.each { |shard| Elasticsearch::Manager::Model::Shard.new.extend(Elastiman::Model::Shard::Representer).from_hash(shard) }
|
21
|
+
end.flatten
|
22
|
+
end)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
extend Representer
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|