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