dd_spacecadet 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ # Copyright 2016 DoubleDutch, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'dd_spacecadet/config'
16
+
17
+ module DoubleDutch
18
+ module SpaceCadet
19
+ # Util is a grouping of utility/helper methods
20
+ module Util
21
+ def self._lbs_client(env)
22
+ DoubleDutch::SpaceCadet::Config.lbs_client[env]
23
+ end
24
+
25
+ # method for finding an LB based on its name
26
+ def self.find_lb(env, search)
27
+ lookup = search.downcase
28
+ _get_lbs(env).select { |lb| lb[:name].include?(lookup) }
29
+ end
30
+
31
+ # get the LBs from Rackspace and parse them to the
32
+ # information we care about: Name & ID
33
+ def self._get_lbs(env)
34
+ _lbs_client(env).list_load_balancers.data[:body]['loadBalancers'].map do |lb|
35
+ {
36
+ name: lb['name'].downcase,
37
+ id: lb['id']
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2016 DoubleDutch, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DoubleDutch
16
+ module SpaceCadet
17
+ VERSION = '0.2.0'.freeze
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright 2016 DoubleDutch, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rspec'
16
+ require 'dd_spacecadet'
@@ -0,0 +1,75 @@
1
+ # Copyright 2016 DoubleDutch, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ describe DoubleDutch::SpaceCadet::Config do
18
+ before do
19
+ allow(Fog::Compute).to receive(:new)
20
+ .with(
21
+ provider: 'rackspace',
22
+ rackspace_username: 'testUsername',
23
+ rackspace_api_key: 'abc123',
24
+ rackspace_region: 'DFW'
25
+ )
26
+ .and_return(:compute)
27
+
28
+ allow(Fog::Rackspace::LoadBalancers).to receive(:new)
29
+ .with(
30
+ rackspace_username: 'testUsername',
31
+ rackspace_api_key: 'abc123',
32
+ rackspace_region: 'DFW'
33
+ )
34
+ .and_return(:loadbalancers)
35
+ end
36
+
37
+ after do
38
+ allow(Fog::Compute).to receive(:new).and_call_original
39
+ allow(Fog::Rackspace::LoadBalancers).to receive(:new).and_call_original
40
+ end
41
+
42
+ describe '.register' do
43
+ it 'should register without error' do
44
+ expect do
45
+ DoubleDutch::SpaceCadet::Config.register(
46
+ 'dfw-test', 'testUsername', 'abc123', 'DFW'
47
+ )
48
+ end.not_to raise_error
49
+ end
50
+ end
51
+
52
+ describe '.servers_client' do
53
+ it 'should return the value of @@servers_client' do
54
+ expect(DoubleDutch::SpaceCadet::Config.servers_client['dfw-test2']).to be_nil
55
+
56
+ DoubleDutch::SpaceCadet::Config.register(
57
+ 'dfw-test2', 'testUsername', 'abc123', 'DFW'
58
+ )
59
+
60
+ expect(DoubleDutch::SpaceCadet::Config.servers_client['dfw-test2']).to eql(:compute)
61
+ end
62
+ end
63
+
64
+ describe '.lbs_client' do
65
+ it 'should return the value of @@servers_client' do
66
+ expect(DoubleDutch::SpaceCadet::Config.lbs_client['dfw-test3']).to be_nil
67
+
68
+ DoubleDutch::SpaceCadet::Config.register(
69
+ 'dfw-test3', 'testUsername', 'abc123', 'DFW'
70
+ )
71
+
72
+ expect(DoubleDutch::SpaceCadet::Config.lbs_client['dfw-test3']).to eql(:loadbalancers)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,390 @@
1
+ # Copyright 2016 DoubleDutch, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ def mock_lbs_summary
18
+ [
19
+ { name: 'test-lb01', id: 42 },
20
+ { name: 'test-lb02', id: 84 }
21
+ ]
22
+ end
23
+
24
+ def mock_lbs_resp
25
+ {
26
+ 'loadBalancer' => {
27
+ 'name' => 'test-lb01',
28
+ 'id' => 42,
29
+ 'nodes' => [
30
+ {
31
+ 'id' => 142,
32
+ 'address' => '10.0.0.142',
33
+ 'condition' => 'ENABLED'
34
+ },
35
+ {
36
+ 'id' => 184,
37
+ 'address' => '10.0.0.184',
38
+ 'condition' => 'ENABLED'
39
+ }
40
+ ]
41
+ }
42
+ }
43
+ end
44
+
45
+ def mock_server_data
46
+ {
47
+ body: {
48
+ 'servers' => [
49
+ {
50
+ 'name' => 'test-app01',
51
+ 'addresses' => {
52
+ 'private' => [
53
+ { 'addr' => '127.0.0.1' }
54
+ ]
55
+ }
56
+ },
57
+ {
58
+ 'name' => 'tEst-APP02',
59
+ 'addresses' => {
60
+ 'private' => [
61
+ { 'addr' => '127.0.0.2' }
62
+ ]
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ end
69
+
70
+ def mock_healthy_status
71
+ [
72
+ {
73
+ name: 'test-lb01',
74
+ id: 42,
75
+ nodes_enabled: 2,
76
+ nodes: [
77
+ {
78
+ name: 'test-app01',
79
+ ip: '10.0.0.142',
80
+ id: 142,
81
+ condition: 'ENABLED'
82
+ },
83
+ {
84
+ name: 'test-app02',
85
+ ip: '10.0.0.142',
86
+ id: 142,
87
+ condition: 'ENABLED'
88
+ }
89
+ ]
90
+ },
91
+ {
92
+ name: 'test-lb02',
93
+ id: 84,
94
+ nodes_enabled: 2,
95
+ nodes: [
96
+ {
97
+ name: 'test-app01',
98
+ ip: '10.0.0.142',
99
+ id: 242,
100
+ condition: 'ENABLED'
101
+ },
102
+ {
103
+ name: 'test-app02',
104
+ ip: '10.0.0.142',
105
+ id: 284,
106
+ condition: 'ENABLED'
107
+ }
108
+ ]
109
+ }
110
+ ]
111
+ end
112
+
113
+ def mock_draining_status
114
+ [
115
+ {
116
+ name: 'test-lb01',
117
+ id: 42,
118
+ nodes_enabled: 1,
119
+ nodes: [
120
+ {
121
+ name: 'test-app01',
122
+ ip: '10.0.0.142',
123
+ id: 142,
124
+ condition: 'ENABLED'
125
+ },
126
+ {
127
+ name: 'test-app02',
128
+ ip: '10.0.0.142',
129
+ id: 142,
130
+ condition: 'DRAINING'
131
+ }
132
+ ]
133
+ },
134
+ {
135
+ name: 'test-lb02',
136
+ id: 84,
137
+ nodes_enabled: 1,
138
+ nodes: [
139
+ {
140
+ name: 'test-app01',
141
+ ip: '10.0.0.142',
142
+ id: 242,
143
+ condition: 'ENABLED'
144
+ },
145
+ {
146
+ name: 'test-app02',
147
+ ip: '10.0.0.142',
148
+ id: 284,
149
+ condition: 'DRAINING'
150
+ }
151
+ ]
152
+ }
153
+ ]
154
+ end
155
+
156
+ describe DoubleDutch::SpaceCadet::LB do
157
+ before do
158
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:get_lbs).and_return(mock_lbs_summary)
159
+ end
160
+ after do
161
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:get_lbs).and_call_original
162
+ end
163
+
164
+ let(:env) { 'test-env' }
165
+ let(:lb_i) { DoubleDutch::SpaceCadet::LB.new(env) }
166
+
167
+ describe '.new' do
168
+ it 'should set the environment and lbs instance variables' do
169
+ i = DoubleDutch::SpaceCadet::LB.new(env)
170
+ expect(i).not_to be_nil
171
+ expect(i.env).to eql('test-env')
172
+ expect(i.lbs).to eql([])
173
+ end
174
+ end
175
+
176
+ describe '.find_lb' do
177
+ before do
178
+ allow(DoubleDutch::SpaceCadet::Util).to receive(:_get_lbs).with(env)
179
+ .and_return(mock_lbs_summary)
180
+ end
181
+
182
+ after { allow(DoubleDutch::SpaceCadet::Util).to receive(:_get_lbs).and_call_original }
183
+
184
+ it 'should find all load balancers containing the search string' do
185
+ r = lb_i.find_lb('lb')
186
+ expect(r.size).to eql(2)
187
+
188
+ expect(r[0][:name]).to eql('test-lb01')
189
+ expect(r[0][:id]).to eql(42)
190
+
191
+ expect(r[1][:name]).to eql('test-lb02')
192
+ expect(r[1][:id]).to eql(84)
193
+ end
194
+
195
+ it 'should return an empty array for a non-matching value' do
196
+ expect(lb_i.find_lb('RANDOM_NOT_FOUND_STRING')).to be_empty
197
+ end
198
+ end
199
+
200
+ describe '.find_lb_and_use' do
201
+ before do
202
+ allow(DoubleDutch::SpaceCadet::Util).to receive(:_get_lbs).with(env)
203
+ .and_return(mock_lbs_summary)
204
+ end
205
+
206
+ after { allow(DoubleDutch::SpaceCadet::Util).to receive(:_get_lbs).and_call_original }
207
+
208
+ it 'should find all load balancers containing the search string and add them' do
209
+ i = DoubleDutch::SpaceCadet::LB.new(env)
210
+ r = i.find_lb_and_use('lb')
211
+ expect(r.size).to eql(2)
212
+
213
+ expect(r[0][:name]).to eql('test-lb01')
214
+ expect(r[0][:id]).to eql(42)
215
+
216
+ expect(r[1][:name]).to eql('test-lb02')
217
+ expect(r[1][:id]).to eql(84)
218
+
219
+ expect(i.lbs.size).to eql(2)
220
+ expect(i.lbs[0]).to eql(r[0][:id])
221
+ expect(i.lbs[1]).to eql(r[1][:id])
222
+ end
223
+ end
224
+
225
+ describe '.add_lb' do
226
+ it 'should add the LB ID to the list of LBs' do
227
+ i = DoubleDutch::SpaceCadet::LB.new(env)
228
+
229
+ r = i.add_lb(42)
230
+ expect(r.size).to eql(1)
231
+ expect(r[0]).to eql(42)
232
+
233
+ r = i.add_lb(84)
234
+ expect(r.size).to eql(2)
235
+ expect(r[0]).to eql(42)
236
+ expect(r[1]).to eql(84)
237
+ end
238
+
239
+ it 'should not allow duplicate entries' do
240
+ i = DoubleDutch::SpaceCadet::LB.new(env)
241
+
242
+ r = i.add_lb(42)
243
+ expect(r.size).to eql(1)
244
+ expect(r[0]).to eql(42)
245
+
246
+ r = i.add_lb(84)
247
+ expect(r.size).to eql(2)
248
+ expect(r[0]).to eql(42)
249
+ expect(r[1]).to eql(84)
250
+
251
+ r = i.add_lb(42)
252
+ expect(r.size).to eql(2)
253
+ expect(r[0]).to eql(42)
254
+ expect(r[1]).to eql(84)
255
+ end
256
+ end
257
+
258
+ describe '.status' do
259
+ before do
260
+ client_resp = double('ClientResponse', data: mock_server_data)
261
+ client = double('Client', list_servers: client_resp)
262
+ client_hash = { env => client }
263
+
264
+ allow(DoubleDutch::SpaceCadet::Config).to receive(:servers_client)
265
+ .and_return(client_hash)
266
+
267
+ allow(DoubleDutch::SpaceCadet::NodeIP).to receive(:get_name_for).with(
268
+ env, '10.0.0.142'
269
+ ).and_return('test-app01')
270
+
271
+ allow(DoubleDutch::SpaceCadet::NodeIP).to receive(:get_name_for).with(
272
+ env, '10.0.0.184'
273
+ ).and_return('test-app02')
274
+
275
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:get_lb_details)
276
+ .with(42)
277
+ .and_return(mock_lbs_resp)
278
+ end
279
+
280
+ after do
281
+ allow(DoubleDutch::SpaceCadet::NodeIP).to receive(:get_name_for).and_call_original
282
+ allow(DoubleDutch::SpaceCadet::NodeIP).to receive(:get_lb_details).and_call_original
283
+ allow(DoubleDutch::SpaceCadet::Config).to receive(:servers_client).and_call_original
284
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:get_lb_details).and_call_original
285
+ end
286
+
287
+ let(:lb_i) do
288
+ i = DoubleDutch::SpaceCadet::LB.new(env)
289
+ expect(i.add_lb(42)).to eql([42])
290
+ i
291
+ end
292
+
293
+ it 'should include the name, ID, and enabled nodes count of the load balancer' do
294
+ r = lb_i.status
295
+ expect(r).to be_an(Array)
296
+ expect(r.size).to eql(1)
297
+
298
+ expect(r[0][:name]).to eql('test-lb01')
299
+ expect(r[0][:id]).to eql(42)
300
+ end
301
+
302
+ it 'should include all backend nodes, their names, and their statuses' do
303
+ r = lb_i.status
304
+ expect(r).to be_an(Array)
305
+ expect(r.size).to eql(1)
306
+
307
+ expect(r[0][:nodes]).to be_an(Array)
308
+ expect(r[0][:nodes].size).to eql(2)
309
+
310
+ expect(r[0][:nodes][0][:name]).to eql('test-app01')
311
+ expect(r[0][:nodes][0][:id]).to eql(142)
312
+ expect(r[0][:nodes][0][:ip]).to eql('10.0.0.142')
313
+ expect(r[0][:nodes][0][:condition]).to eql('ENABLED')
314
+
315
+ expect(r[0][:nodes][1][:name]).to eql('test-app02')
316
+ expect(r[0][:nodes][1][:id]).to eql(184)
317
+ expect(r[0][:nodes][1][:ip]).to eql('10.0.0.184')
318
+ expect(r[0][:nodes][1][:condition]).to eql('ENABLED')
319
+ end
320
+ end
321
+
322
+ describe '.update_node' do
323
+ before do
324
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:status)
325
+ .and_return(mock_healthy_status)
326
+ end
327
+
328
+ after do
329
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:status).and_call_original
330
+ end
331
+
332
+ let(:lb_i) do
333
+ i = DoubleDutch::SpaceCadet::LB.new(env)
334
+ expect(i.add_lb(42)).to eql([42])
335
+ expect(i.add_lb(84)).to eql([42, 84])
336
+ i
337
+ end
338
+
339
+ context 'input validation' do
340
+ before do
341
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:flush_updates)
342
+ .and_return(nil)
343
+ end
344
+
345
+ after do
346
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:flush_updates)
347
+ .and_call_original
348
+ end
349
+
350
+ it 'should only allow the :draining and :enabled conditions' do
351
+ expect { lb_i.update_node('test-app01', :enabled) }.not_to raise_error
352
+ expect { lb_i.update_node('test-app01', :draining) }.not_to raise_error
353
+ expect { lb_i.update_node('test-app01', :invalid) }.to raise_error(ArgumentError)
354
+ end
355
+
356
+ it 'should raise if the node was not found on all LBs' do
357
+ expect { lb_i.update_node('test-app', :enabled) }.to raise_error(DoubleDutch::SpaceCadet::LBInconsistentState)
358
+ end
359
+ end
360
+
361
+ context 'when normal' do
362
+ before do
363
+ @lbs_client = double('Fog::Rackspace::LoadBalancers', update_node: nil)
364
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:lbs_client)
365
+ .and_return(@lbs_client)
366
+ end
367
+
368
+ it 'should allow setting the node to drain on all LBs if no others are' do
369
+ expect(@lbs_client).to receive(:update_node)
370
+ .with(42, 142, condition: 'DRAINING').and_return(nil)
371
+
372
+ expect(@lbs_client).to receive(:update_node)
373
+ .with(84, 242, condition: 'DRAINING')
374
+
375
+ lb_i.update_node('test-app01', :draining)
376
+ end
377
+ end
378
+
379
+ context 'when already draining' do
380
+ before do
381
+ allow_any_instance_of(DoubleDutch::SpaceCadet::LB).to receive(:status)
382
+ .and_return(mock_draining_status)
383
+ end
384
+
385
+ it 'should actively prevent you from draining multiple backends' do
386
+ expect { lb_i.update_node('test-app01', :draining) }.to raise_error(DoubleDutch::SpaceCadet::LBUnsafe)
387
+ end
388
+ end
389
+ end
390
+ end