dd_spacecadet 0.2.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.
@@ -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