elasticsearch-manager 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 +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
|