elasticsearch-manager 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +81 -0
  7. data/LICENSE +201 -0
  8. data/README.md +55 -0
  9. data/REALEASES.md +0 -0
  10. data/bin/elasticsearch-manager +56 -0
  11. data/elasticsearch-manager.gemspec +33 -0
  12. data/lib/elasticsearch/client.rb +8 -0
  13. data/lib/elasticsearch/client/base.rb +71 -0
  14. data/lib/elasticsearch/client/elasticsearch.rb +43 -0
  15. data/lib/elasticsearch/manager.rb +8 -0
  16. data/lib/elasticsearch/manager/cmd.rb +62 -0
  17. data/lib/elasticsearch/manager/errors.rb +12 -0
  18. data/lib/elasticsearch/manager/manager.rb +60 -0
  19. data/lib/elasticsearch/manager/rollingrestart.rb +79 -0
  20. data/lib/elasticsearch/manager/version.rb +5 -0
  21. data/lib/elasticsearch/model.rb +5 -0
  22. data/lib/elasticsearch/model/health.rb +26 -0
  23. data/lib/elasticsearch/model/node.rb +40 -0
  24. data/lib/elasticsearch/model/routing_nodes.rb +28 -0
  25. data/lib/elasticsearch/model/shard.rb +22 -0
  26. data/lib/elasticsearch/model/state.rb +23 -0
  27. data/spec/client_spec.rb +33 -0
  28. data/spec/cmd_spec.rb +123 -0
  29. data/spec/esclient_spec.rb +64 -0
  30. data/spec/fixtures/health.json +12 -0
  31. data/spec/fixtures/health_initializing.json +12 -0
  32. data/spec/fixtures/health_realocating.json +12 -0
  33. data/spec/fixtures/health_red.json +12 -0
  34. data/spec/fixtures/health_unassigned.json +12 -0
  35. data/spec/fixtures/health_yellow.json +13 -0
  36. data/spec/fixtures/nodes_.json +837 -0
  37. data/spec/fixtures/state-node-initializing.json +193010 -0
  38. data/spec/fixtures/state.json +193018 -0
  39. data/spec/manager_spec.rb +234 -0
  40. data/spec/spec_helper.rb +226 -0
  41. metadata +266 -0
@@ -0,0 +1,234 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ require 'elasticsearch/manager'
5
+ require 'elasticsearch/model'
6
+
7
+ include Elasticsearch::Manager
8
+
9
+ describe 'Elasticsearch::Manager::ESManager' '#cluster_green?' do
10
+ context 'check status' do
11
+ it 'returns green' do
12
+ c = ESManager.new
13
+ expect(c.cluster_green?).to be true
14
+ end
15
+
16
+ it 'does not return green (yellow)' do
17
+ c = ESManager.new('localhost-yellow')
18
+ expect(c.cluster_green?).to be false
19
+ end
20
+
21
+ it 'does not return green (red)' do
22
+ c = ESManager.new('localhost-red')
23
+ expect(c.cluster_green?).to be false
24
+ end
25
+ end
26
+ end
27
+
28
+ describe 'Elasticsearch::Manager::ESManager' '#cluster_health' do
29
+ context 'health model' do
30
+ it 'returns valid model' do
31
+ c = ESManager.new
32
+ health = c.cluster_health
33
+
34
+ expect(health.cluster_name).to eql('test_es_cluster')
35
+ expect(health.status).to eql('green')
36
+ expect(health.timed_out).to be(false)
37
+ expect(health.number_of_nodes).to eql(3)
38
+ expect(health.number_of_data_nodes).to eql(3)
39
+ expect(health.active_primary_shards).to eql(8)
40
+ expect(health.active_shards).to eql(16)
41
+ expect(health.relocating_shards).to eql(0)
42
+ expect(health.initializing_shards).to eql(0)
43
+ expect(health.unassigned_shards).to eql(0)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe 'Elasticsearch::Manager::ESManager' '#cluster_members!' do
49
+ context 'cluster members' do
50
+ it 'retrieves cluster members' do
51
+ c = ESManager.new
52
+ c.cluster_members!
53
+ expect(c.leader).to eql('10.110.38.153')
54
+ expect(c.members).to include('10.110.38.153')
55
+ expect(c.members).to include('10.110.33.218')
56
+ expect(c.members).to include('10.110.40.133')
57
+ expect(c.members.length).to eql(3)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe 'Elasticsearch::Manager::ESManager' '#cluster_stable?' do
63
+ context 'stable cluster' do
64
+ it 'cluster green no rebalancing' do
65
+ c = ESManager.new
66
+ expect(c.cluster_stable?).to be true
67
+ end
68
+
69
+ it 'cluster yellow no rebalancing' do
70
+ c = ESManager.new('localhost-yellow')
71
+ expect(c.cluster_stable?).to be false
72
+ end
73
+
74
+ it 'cluster red no rebalancing' do
75
+ c = ESManager.new('localhost-red')
76
+ expect(c.cluster_stable?).to be false
77
+ end
78
+
79
+ it 'cluster green shards realocating' do
80
+ c = ESManager.new('localhost-realocating')
81
+ expect(c.cluster_stable?).to be false
82
+ end
83
+
84
+ it 'cluster green shards initializing' do
85
+ c = ESManager.new('localhost-initializing')
86
+ expect(c.cluster_stable?).to be false
87
+ end
88
+
89
+ it 'cluster green shards unassigned' do
90
+ c = ESManager.new('localhost-unassigned')
91
+ expect(c.cluster_stable?).to be false
92
+ end
93
+ end
94
+ end
95
+
96
+ describe 'Elasticsearch::Manager::ESManager' 'routing' do
97
+ context 'disable shard routing' do
98
+ it 'succeeds with 200' do
99
+ c = ESManager.new('localhost-route-disabled')
100
+ expect(c.disable_routing).to be true
101
+ end
102
+
103
+ it 'fails with 200' do
104
+ c = ESManager.new('localhost-route-enabled')
105
+ expect(c.disable_routing).to be false
106
+ end
107
+ end
108
+
109
+ context 'enable shard routing' do
110
+ it 'succeeds with 200' do
111
+ c = ESManager.new('localhost-route-enabled')
112
+ expect(c.enable_routing).to be true
113
+ end
114
+
115
+ it 'fails with 200' do
116
+ c = ESManager.new('localhost-route-disabled')
117
+ expect(c.enable_routing).to be false
118
+ end
119
+ end
120
+ end
121
+
122
+ describe 'Elasticsearch::Manager::ESManager' 'routing' do
123
+ let (:ssh_connection) { double("SSH Connection") }
124
+
125
+ before do
126
+ allow(Net::SSH).to receive(:start).and_yield(ssh_connection)
127
+
128
+ @input = StringIO.new
129
+ @output = StringIO.new
130
+ @terminal = HighLine.new(@input, @output)
131
+ allow(HighLine).to receive(:new).and_return(@terminal)
132
+ end
133
+
134
+ context 'rolling restart' do
135
+
136
+ it 'does a clean restart' do
137
+ expect(Net::SSH).to receive(:start).with('10.110.33.218', ENV['USER']).ordered
138
+ expect(Net::SSH).to receive(:start).with('10.110.40.133', ENV['USER']).ordered
139
+ expect(Net::SSH).to receive(:start).with('10.110.38.153', ENV['USER']).ordered
140
+
141
+ allow(ssh_connection).to receive(:exec) do |arg|
142
+ expect(arg).to eql('sudo service elasticsearch restart')
143
+ end
144
+ expect(ssh_connection).to receive(:exec).exactly(3).times
145
+
146
+ manager = ESManager.new('localhost', '9200')
147
+ manager.cluster_members!
148
+
149
+ @input << "y\ny\ny\n"
150
+ @input.rewind
151
+
152
+ capture_stdout do
153
+ manager.rolling_restart(5, 1)
154
+ end
155
+ end
156
+
157
+ it 'throws stabilization timeout' do
158
+ manager = ESManager.new('localhost-restart-timeout', 9200)
159
+ manager.cluster_members!
160
+ allow(ssh_connection).to receive(:exec) do |arg|
161
+ expect(arg).to eql('sudo service elasticsearch restart')
162
+ end
163
+
164
+ @input << "y\ny\ny\n"
165
+ @input.rewind
166
+
167
+ output = capture_stdout do
168
+ expect { manager.rolling_restart(2, 1) }.to raise_error(Elasticsearch::Manager::StabalizationTimeout)
169
+ end
170
+ end
171
+
172
+ it 'throws node available timeout' do
173
+ manager = ESManager.new('localhost-restart-not-available', 9200)
174
+ manager.cluster_members!
175
+ allow(ssh_connection).to receive(:exec) do |arg|
176
+ expect(arg).to eql('sudo service elasticsearch restart')
177
+ end
178
+
179
+ @input << "y\ny\ny\n"
180
+ @input.rewind
181
+
182
+ output = capture_stdout do
183
+ expect { manager.rolling_restart(2, 1) }.to raise_error(Elasticsearch::Manager::NodeAvailableTimeout)
184
+ end
185
+ end
186
+
187
+ it 'handles eventual stabilization' do
188
+ manager = ESManager.new('localhost-restart-stabilization', 9200)
189
+ manager.cluster_members!
190
+ allow(ssh_connection).to receive(:exec) do |arg|
191
+ expect(arg).to eql('sudo service elasticsearch restart')
192
+ end
193
+
194
+ @input << "y\ny\ny\n"
195
+ @input.rewind
196
+
197
+ output = capture_stdout do
198
+ expect { manager.rolling_restart(3, 1) }.not_to raise_error
199
+ end
200
+ end
201
+
202
+ it 'Allows user to bail' do
203
+ manager = ESManager.new('localhost', 9200)
204
+ manager.cluster_members!
205
+ allow(ssh_connection).to receive(:exec) do |arg|
206
+ expect(arg).to eql('sudo service elasticsearch restart')
207
+ end
208
+ opts = {:hostname => 'localhost', :port => '9200'}
209
+
210
+ @input << "n\n"
211
+ @input.rewind
212
+
213
+ output = capture_stdout do
214
+ expect { manager.rolling_restart(2, 1) }.to raise_error(Elasticsearch::Manager::UserRequestedStop)
215
+ end
216
+ end
217
+
218
+ it 'Allows user to bail at master restart' do
219
+ manager = ESManager.new('localhost', 9200)
220
+ manager.cluster_members!
221
+ allow(ssh_connection).to receive(:exec) do |arg|
222
+ expect(arg).to eql('sudo service elasticsearch restart')
223
+ end
224
+ opts = {:hostname => 'localhost', :port => '9200'}
225
+
226
+ @input << "y\ny\nn\n"
227
+ @input.rewind
228
+
229
+ output = capture_stdout do
230
+ expect { manager.rolling_restart(2, 1) }.to raise_error(Elasticsearch::Manager::UserRequestedStop)
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,226 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ require 'webmock/rspec'
20
+ require 'rack'
21
+
22
+ WebMock.disable_net_connect!(allow_localhost: false)
23
+ DIR = File.expand_path(File.dirname(__FILE__))
24
+
25
+ class RestartTimeoutRack
26
+ def initialize(state_success_count = 10)
27
+ @health_call_count = 0
28
+ @state_call_count = 0
29
+
30
+ @state_success_count = state_success_count
31
+ end
32
+
33
+ def call(env)
34
+ case env['PATH_INFO']
35
+ when '/_cluster/health'
36
+ case @health_call_count
37
+ when 1..4
38
+ ret = File.read(DIR + '/fixtures/health_initializing.json')
39
+ else
40
+ ret = File.read(DIR + '/fixtures/health.json')
41
+ end
42
+ @health_call_count += 1
43
+ when '/_cluster/settings'
44
+ if env['rack.input'].read[/none/].nil?
45
+ ret = '{"transient":{"cluster":{"routing":{"allocation":{"enable":"all"}}}}}'
46
+ else
47
+ ret = '{"transient":{"cluster":{"routing":{"allocation":{"enable":"none"}}}}}'
48
+ end
49
+ when '/_cluster/state'
50
+ if @state_call_count < @state_success_count
51
+ ret = File.read(DIR + '/fixtures/state.json')
52
+ else
53
+ ret = File.read(DIR + '/fixtures/state-node-initializing.json')
54
+ end
55
+ @state_call_count += 1
56
+ end
57
+ [200, { 'Content-Type' => 'application/json' }, [ret]]
58
+ end
59
+ end
60
+
61
+ RSpec.configure do |config|
62
+ # rspec-expectations config goes here. You can use an alternate
63
+ # assertion/expectation library such as wrong or the stdlib/minitest
64
+ # assertions if you prefer.
65
+ config.expect_with :rspec do |expectations|
66
+ # This option will default to `true` in RSpec 4. It makes the `description`
67
+ # and `failure_message` of custom matchers include text for helper methods
68
+ # defined using `chain`, e.g.:
69
+ # be_bigger_than(2).and_smaller_than(4).description
70
+ # # => "be bigger than 2 and smaller than 4"
71
+ # ...rather than:
72
+ # # => "be bigger than 2"
73
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
74
+ end
75
+
76
+ # rspec-mocks config goes here. You can use an alternate test double
77
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
78
+ config.mock_with :rspec do |mocks|
79
+ # Prevents you from mocking or stubbing a method that does not exist on
80
+ # a real object. This is generally recommended, and will default to
81
+ # `true` in RSpec 4.
82
+ mocks.verify_partial_doubles = true
83
+ end
84
+
85
+ # The settings below are suggested to provide a good initial experience
86
+ # with RSpec, but feel free to customize to your heart's content.
87
+ =begin
88
+ # These two settings work together to allow you to limit a spec run
89
+ # to individual examples or groups you care about by tagging them with
90
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
91
+ # get run.
92
+ config.filter_run :focus
93
+ config.run_all_when_everything_filtered = true
94
+
95
+ # Limits the available syntax to the non-monkey patched syntax that is
96
+ # recommended. For more details, see:
97
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
98
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
99
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
100
+ config.disable_monkey_patching!
101
+
102
+ # This setting enables warnings. It's recommended, but in some cases may
103
+ # be too noisy due to issues in dependencies.
104
+ config.warnings = true
105
+
106
+ # Many RSpec users commonly either run the entire suite or an individual
107
+ # file, and it's useful to allow more verbose output when running an
108
+ # individual spec file.
109
+ if config.files_to_run.one?
110
+ # Use the documentation formatter for detailed output,
111
+ # unless a formatter has already been configured
112
+ # (e.g. via a command-line flag).
113
+ config.default_formatter = 'doc'
114
+ end
115
+
116
+ # Print the 10 slowest examples and example groups at the
117
+ # end of the spec run, to help surface which specs are running
118
+ # particularly slow.
119
+ config.profile_examples = 10
120
+
121
+ # Run specs in random order to surface order dependencies. If you find an
122
+ # order dependency and want to debug it, you can fix the order by providing
123
+ # the seed, which is printed after each run.
124
+ # --seed 1234
125
+ config.order = :random
126
+
127
+ # Seed global randomization in this process using the `--seed` CLI option.
128
+ # Setting this allows you to use `--seed` to deterministically reproduce
129
+ # test failures related to randomization by passing the same `--seed` value
130
+ # as the one that triggered the failure.
131
+ Kernel.srand config.seed
132
+ =end
133
+ config.before(:each) do
134
+ stub_request(:get, /localhost:9200\/base\/client/).
135
+ to_return(status: 200, body: 'requested: /base/client', headers: {})
136
+
137
+ stub_request(:get, /localhost:9200\/_cluster\/health/).
138
+ to_return(status: 200,
139
+ body: File.read(DIR + '/fixtures/health.json'),
140
+ headers: {'Content-Type' => 'application/json'})
141
+
142
+ stub_request(:get, /localhost-yellow:9200\/_cluster\/health/).
143
+ to_return(status: 200,
144
+ body: File.read(DIR + '/fixtures/health_yellow.json'),
145
+ headers: {'Content-Type' => 'application/json'})
146
+
147
+ stub_request(:get, /localhost-red:9200\/_cluster\/health/).
148
+ to_return(status: 200,
149
+ body: File.read(DIR + '/fixtures/health_red.json'),
150
+ headers: {'Content-Type' => 'application/json'})
151
+
152
+ stub_request(:get, /localhost-realocating:9200\/_cluster\/health/).
153
+ to_return(status: 200,
154
+ body: File.read(DIR + '/fixtures/health_realocating.json'),
155
+ headers: {'Content-Type' => 'application/json'})
156
+
157
+ stub_request(:get, /localhost-initializing:9200\/_cluster\/health/).
158
+ to_return(status: 200,
159
+ body: File.read(DIR + '/fixtures/health_initializing.json'),
160
+ headers: {'Content-Type' => 'application/json'})
161
+
162
+ stub_request(:get, /localhost-unassigned:9200\/_cluster\/health/).
163
+ to_return(status: 200,
164
+ body: File.read(DIR + '/fixtures/health_unassigned.json'),
165
+ headers: {'Content-Type' => 'application/json'})
166
+
167
+ stub_request(:get, /localhost:9200\/_cluster\/state/).
168
+ to_return(status: 200,
169
+ body: File.read(DIR + '/fixtures/state.json'),
170
+ headers: {'Content-Type' => 'application/json'})
171
+
172
+ stub_request(:get, /localhost:9200\/_nodes/).
173
+ to_return(status: 200,
174
+ body: File.read(DIR + '/fixtures/nodes_.json'),
175
+ headers: {'Content-Type' => 'application/json'})
176
+
177
+ stub_request(:put, /localhost-route-disabled:9200\/_cluster\/settings/).
178
+ to_return(status: 200,
179
+ body: '{"transient":{"cluster":{"routing":{"allocation":{"enable":"none"}}}}}',
180
+ headers: {'Content-Type' => 'application/json'})
181
+
182
+ stub_request(:put, /localhost-route-enabled:9200\/_cluster\/settings/).
183
+ to_return(status: 200,
184
+ body: '{"transient":{"cluster":{"routing":{"allocation":{"enable":"all"}}}}}',
185
+ headers: {'Content-Type' => 'application/json'})
186
+
187
+ stub_request(:put, /localhost:9200\/_cluster\/settings/).
188
+ with(:body => '{"transient":{"cluster.routing.allocation.enable":"none"}}').
189
+ to_return(status: 200,
190
+ body: '{"transient":{"cluster":{"routing":{"allocation":{"enable":"none"}}}}}',
191
+ headers: {'Content-Type' => 'application/json'})
192
+
193
+ stub_request(:put, /localhost:9200\/_cluster\/settings/).
194
+ with(:body => '{"transient":{"cluster.routing.allocation.enable":"all"}}').
195
+ to_return(status: 200,
196
+ body: '{"transient":{"cluster":{"routing":{"allocation":{"enable":"all"}}}}}',
197
+ headers: {'Content-Type' => 'application/json'})
198
+
199
+
200
+ stub_request(:any, /localhost-restart-timeout:9200\//).
201
+ to_rack(RestartTimeoutRack.new)
202
+ stub_request(:any, /localhost-cmd-restart-timeout:9200\//).
203
+ to_rack(RestartTimeoutRack.new)
204
+
205
+ stub_request(:any, /localhost-restart-stabilization:9200\//).
206
+ to_rack(RestartTimeoutRack.new)
207
+ stub_request(:any, /localhost-cmd-restart-stabilization:9200\//).
208
+ to_rack(RestartTimeoutRack.new)
209
+
210
+ stub_request(:any, /localhost-restart-not-available:9200\//).
211
+ to_rack(RestartTimeoutRack.new(1))
212
+ stub_request(:any, /localhost-cmd-restart-not-available:9200\//).
213
+ to_rack(RestartTimeoutRack.new(1))
214
+ end
215
+ end
216
+
217
+ def capture_stdout(&block)
218
+ original_stdout = $stdout
219
+ $stdout = fake = StringIO.new
220
+ begin
221
+ yield
222
+ ensure
223
+ $stdout = original_stdout
224
+ end
225
+ fake.string
226
+ end