cloudfinder-ec2 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.
- data/.gitattributes +29 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/CONTRIBUTING.md +43 -0
- data/Gemfile +8 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +29 -0
- data/README.md +138 -0
- data/Rakefile +18 -0
- data/bin/cloudfinder +63 -0
- data/cloudfinder-ec2.gemspec +28 -0
- data/lib/cloudfinder-ec2.rb +8 -0
- data/lib/cloudfinder-ec2/cluster.rb +88 -0
- data/lib/cloudfinder-ec2/clusterfinder.rb +97 -0
- data/lib/cloudfinder-ec2/command/list.rb +72 -0
- data/lib/cloudfinder-ec2/consts.rb +6 -0
- data/lib/cloudfinder-ec2/detector.rb +69 -0
- data/lib/cloudfinder-ec2/instance.rb +34 -0
- data/lib/cloudfinder-ec2/version.rb +5 -0
- data/spec/cloudfinder-ec2/cluster_spec.rb +164 -0
- data/spec/cloudfinder-ec2/clusterfinder_spec.rb +265 -0
- data/spec/cloudfinder-ec2/command/list_spec.rb +131 -0
- data/spec/cloudfinder-ec2/detector_spec.rb +152 -0
- data/spec/cloudfinder-ec2/instance_spec.rb +30 -0
- data/spec/cloudfinder-ec2_spec.rb +5 -0
- data/spec/spec_helper.rb +15 -0
- metadata +149 -0
@@ -0,0 +1,265 @@
|
|
1
|
+
describe Cloudfinder::EC2::Clusterfinder do
|
2
|
+
|
3
|
+
let (:client) { Aws::EC2::Client.new(stub_responses: true) }
|
4
|
+
let (:reservations) { [] }
|
5
|
+
let (:cluster_name) { 'production' }
|
6
|
+
let (:region) { 'some-region' }
|
7
|
+
|
8
|
+
before (:each) do
|
9
|
+
allow(Aws::EC2::Client).to receive(:new).and_return(client)
|
10
|
+
end
|
11
|
+
|
12
|
+
shared_examples_for 'cluster object response' do
|
13
|
+
it 'provides cluster name in returned cluster' do
|
14
|
+
expect(cluster.cluster_name).to eq cluster_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
shared_examples_for 'empty cluster' do
|
19
|
+
include_examples 'cluster object response'
|
20
|
+
|
21
|
+
it 'returns empty cluster' do
|
22
|
+
expect(cluster).to be_empty
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
shared_examples_for 'running cluster with roles' do |roles|
|
27
|
+
include_examples 'cluster object response'
|
28
|
+
|
29
|
+
it 'returns running cluster' do
|
30
|
+
expect(cluster).to be_running
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has only the expected cluster roles' do
|
34
|
+
expect(cluster.list_roles).to eq roles
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
shared_examples_for 'cluster with instance in role' do |role, instance_index, instance_id|
|
39
|
+
it "has #{role} role in cluster" do
|
40
|
+
expect(cluster).to have_role(role)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "has instance #{instance_id} as #{role}:##{instance_index}" do
|
44
|
+
expect(cluster.list_role_instances(role)[instance_index].instance_id).to eq instance_id
|
45
|
+
end
|
46
|
+
|
47
|
+
it "populates #{instance_id} public IP" do
|
48
|
+
expect(cluster.list_role_instances(role)[instance_index].public_ip).to eq public_ip(instance_id)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "populates #{instance_id} private IP" do
|
52
|
+
expect(cluster.list_role_instances(role)[instance_index].private_ip).to eq private_ip(instance_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "populates #{instance_id} public DNS" do
|
56
|
+
expect(cluster.list_role_instances(role)[instance_index].public_dns).to eq public_dns(instance_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "populates #{instance_id} private DNS" do
|
60
|
+
expect(cluster.list_role_instances(role)[instance_index].private_dns).to eq private_dns(instance_id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#find' do
|
65
|
+
let (:cluster) { subject.find(region: region, cluster_name: cluster_name) }
|
66
|
+
|
67
|
+
context 'with invalid arguments' do
|
68
|
+
it 'throws without region' do
|
69
|
+
expect { subject.find(cluster_name: cluster_name) }.to raise_error(ArgumentError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'throws without cluster name' do
|
73
|
+
expect { subject.find(region: region) }.to raise_error(ArgumentError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when searching for EC2 instances' do
|
78
|
+
let (:client) { spy(Aws::EC2::Client) }
|
79
|
+
|
80
|
+
it 'creates AWS API client for the specified region' do
|
81
|
+
expect(Aws::EC2::Client).to receive(:new).with(region: 'any-region').once.and_return(client)
|
82
|
+
subject.find(region: 'any-region', cluster_name: cluster_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'requests details of running EC2 instances' do
|
86
|
+
subject.find(region: 'us-east-1', cluster_name: 'i12345678')
|
87
|
+
expect(client).to have_received(:describe_instances).once.with(filters: [{ name: 'instance-state-name', values: ['running'] }])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when AWS API throws exceptions' do
|
92
|
+
it 'bubbles exceptions to caller' do
|
93
|
+
client.stub_responses(:describe_instances, Aws::Errors::MissingCredentialsError)
|
94
|
+
expect { subject.find(region: 'us-east-1', cluster_name: 'any') }.to raise_error(Aws::Errors::MissingCredentialsError)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when no instances are running' do
|
99
|
+
before (:each) do
|
100
|
+
client.stub_responses(:describe_instances, reservations: [])
|
101
|
+
end
|
102
|
+
|
103
|
+
include_examples 'empty cluster'
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when one instance is running' do
|
107
|
+
before (:each) do
|
108
|
+
stub_describe_instances(stub_reservation(running_instance))
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with no tags' do
|
112
|
+
let(:running_instance) { stub_instance }
|
113
|
+
include_examples 'empty cluster'
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with irrelevant tags' do
|
117
|
+
let(:running_instance) { stub_instance(tags: { 'name' => 'qa-server' }) }
|
118
|
+
include_examples 'empty cluster'
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'with cloudfinder-cluster tag for another cluster' do
|
122
|
+
let(:running_instance) { stub_instance(cluster_tag: 'another-cluster', role_tag: 'db') }
|
123
|
+
include_examples 'empty cluster'
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'tagged for correct cloudfinder-cluster without cloudfinder-role' do
|
127
|
+
let(:running_instance) { stub_instance(cluster_tag: cluster_name, role_tag: nil) }
|
128
|
+
include_examples 'empty cluster'
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'tagged for correct cloudfinder-cluster with cloudfinder-role=db' do
|
132
|
+
let(:running_instance) { stub_instance(id: 'i-00000001', cluster_tag: cluster_name, role_tag: 'db') }
|
133
|
+
|
134
|
+
include_examples('running cluster with roles', [:db])
|
135
|
+
include_examples('cluster with instance in role', :db, 0, 'i-00000001')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when two instances in single reservation' do
|
140
|
+
before (:each) do
|
141
|
+
stub_describe_instances(stub_reservation(instance_1, instance_2))
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'with only one in this cluster' do
|
145
|
+
let (:instance_1) { stub_instance(id: 'i-00000001', cluster_tag: cluster_name, role_tag: 'db') }
|
146
|
+
let (:instance_2) { stub_instance(id: 'i-00000002', cluster_tag: 'another-cluster', role_tag: 'app') }
|
147
|
+
|
148
|
+
include_examples('running cluster with roles', [:db])
|
149
|
+
include_examples('cluster with instance in role', :db, 0, 'i-00000001')
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'both in this cluster with same role' do
|
153
|
+
let (:instance_1) { stub_instance(id: 'i-00000001', cluster_tag: cluster_name, role_tag: 'db') }
|
154
|
+
let (:instance_2) { stub_instance(id: 'i-00000002', cluster_tag: cluster_name, role_tag: 'db') }
|
155
|
+
|
156
|
+
include_examples('running cluster with roles', [:db])
|
157
|
+
include_examples('cluster with instance in role', :db, 0, 'i-00000001')
|
158
|
+
include_examples('cluster with instance in role', :db, 1, 'i-00000002')
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'both in this cluster with different roles' do
|
162
|
+
let (:instance_1) { stub_instance(id: 'i-00000001', cluster_tag: cluster_name, role_tag: 'db') }
|
163
|
+
let (:instance_2) { stub_instance(id: 'i-00000002', cluster_tag: cluster_name, role_tag: 'app') }
|
164
|
+
|
165
|
+
include_examples('running cluster with roles', [:db, :app])
|
166
|
+
include_examples('cluster with instance in role', :db, 0, 'i-00000001')
|
167
|
+
include_examples('cluster with instance in role', :app, 0, 'i-00000002')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when three instances in two reservations' do
|
172
|
+
before (:each) do
|
173
|
+
stub_describe_instances(
|
174
|
+
stub_reservation(instance_1, instance_2),
|
175
|
+
stub_reservation(instance_3)
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'with only two in this cluster' do
|
180
|
+
let (:instance_1) { stub_instance(id: 'i-00000001', cluster_tag: 'another-cluster', role_tag: 'db') }
|
181
|
+
let (:instance_2) { stub_instance(id: 'i-00000002', cluster_tag: 'another-cluster', role_tag: 'app') }
|
182
|
+
let (:instance_3) { stub_instance(id: 'i-00000003', cluster_tag: cluster_name, role_tag: 'app') }
|
183
|
+
|
184
|
+
include_examples('running cluster with roles', [:app])
|
185
|
+
include_examples('cluster with instance in role', :app, 0, 'i-00000003')
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'all in this cluster with same role' do
|
189
|
+
let (:instance_1) { stub_instance(id: 'i-00000001', cluster_tag: cluster_name, role_tag: 'db') }
|
190
|
+
let (:instance_2) { stub_instance(id: 'i-00000002', cluster_tag: cluster_name, role_tag: 'db') }
|
191
|
+
let (:instance_3) { stub_instance(id: 'i-00000003', cluster_tag: cluster_name, role_tag: 'db') }
|
192
|
+
|
193
|
+
include_examples('running cluster with roles', [:db])
|
194
|
+
include_examples('cluster with instance in role', :db, 0, 'i-00000001')
|
195
|
+
include_examples('cluster with instance in role', :db, 1, 'i-00000002')
|
196
|
+
include_examples('cluster with instance in role', :db, 2, 'i-00000003')
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'all in this cluster with different roles' do
|
200
|
+
let (:instance_1) { stub_instance(id: 'i-00000001', cluster_tag: cluster_name, role_tag: 'db') }
|
201
|
+
let (:instance_2) { stub_instance(id: 'i-00000002', cluster_tag: cluster_name, role_tag: 'app') }
|
202
|
+
let (:instance_3) { stub_instance(id: 'i-00000003', cluster_tag: cluster_name, role_tag: 'cache') }
|
203
|
+
|
204
|
+
include_examples('running cluster with roles', [:db, :app, :cache])
|
205
|
+
include_examples('cluster with instance in role', :db, 0, 'i-00000001')
|
206
|
+
include_examples('cluster with instance in role', :app, 0, 'i-00000002')
|
207
|
+
include_examples('cluster with instance in role', :cache, 0, 'i-00000003')
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def stub_describe_instances(*reservations)
|
213
|
+
client.stub_responses(:describe_instances, reservations: reservations)
|
214
|
+
end
|
215
|
+
|
216
|
+
def stub_reservation(*instances)
|
217
|
+
{ instances: instances }
|
218
|
+
end
|
219
|
+
|
220
|
+
def stub_instance(args = {})
|
221
|
+
id = args[:id] || 'i-00000001'
|
222
|
+
instance = {
|
223
|
+
instance_id: id,
|
224
|
+
public_ip_address: args[:public_ip] || public_ip(id), #46.137.0.1',
|
225
|
+
private_ip_address: args[:private_ip] || private_ip(id), #'10.248.0.1',
|
226
|
+
public_dns_name: args[:public_dns] || public_dns(id), #'ec2-46-137-0-1.eu-west-1.compute.amazonaws.com',
|
227
|
+
private_dns_name: args[:public_dns] || private_dns(id), #'ip-10-248-0-1.eu-west-1.compute.internal',
|
228
|
+
tags: []
|
229
|
+
}
|
230
|
+
(args[:tags] || {}).each do |key, value|
|
231
|
+
instance[:tags] << { key: key, value: value }
|
232
|
+
end
|
233
|
+
|
234
|
+
if args[:cluster_tag]
|
235
|
+
instance[:tags] << { key: Cloudfinder::EC2::CLUSTER_TAG_NAME, value: args[:cluster_tag] }
|
236
|
+
end
|
237
|
+
|
238
|
+
if args[:role_tag]
|
239
|
+
instance[:tags] << { key: Cloudfinder::EC2::ROLE_TAG_NAME, value: args[:role_tag] }
|
240
|
+
end
|
241
|
+
|
242
|
+
instance
|
243
|
+
end
|
244
|
+
|
245
|
+
def numeric_instance_id(instance_id)
|
246
|
+
instance_id[-2, 2].to_i
|
247
|
+
end
|
248
|
+
|
249
|
+
def private_ip(instance_id)
|
250
|
+
"10.248.0.#{numeric_instance_id(instance_id)}"
|
251
|
+
end
|
252
|
+
|
253
|
+
def public_ip(instance_id)
|
254
|
+
"46.137.0.#{numeric_instance_id(instance_id)}"
|
255
|
+
end
|
256
|
+
|
257
|
+
def private_dns(instance_id)
|
258
|
+
"ec2-46-137-0-#{numeric_instance_id(instance_id)}.eu-west-1.compute.amazonaws.com"
|
259
|
+
end
|
260
|
+
|
261
|
+
def public_dns(instance_id)
|
262
|
+
"ip-10-248-0-#{numeric_instance_id(instance_id)}.eu-west-1.compute.internal"
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
describe Cloudfinder::EC2::Command::List, focus: true do
|
2
|
+
let (:detector) { spy(Cloudfinder::EC2::Detector) }
|
3
|
+
let (:detector_result) { { cluster_name: detected_cluster, cluster_role: 'db', region: detected_region } }
|
4
|
+
let (:cluster_finder) { spy(Cloudfinder::EC2::Clusterfinder) }
|
5
|
+
let (:found_cluster) { double(Cloudfinder::EC2::Cluster) }
|
6
|
+
let (:cluster_hash) { { cluster_name: detected_cluster, roles: {} } }
|
7
|
+
let (:standard_out) { StringIO.new }
|
8
|
+
let (:error_out) { StringIO.new }
|
9
|
+
let (:detected_cluster) { 'qa' }
|
10
|
+
let (:detected_region) { 'eu-west-1' }
|
11
|
+
|
12
|
+
subject do
|
13
|
+
Cloudfinder::EC2::Command::List.new(
|
14
|
+
cluster_finder,
|
15
|
+
detector,
|
16
|
+
standard_out,
|
17
|
+
error_out
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
before :each do
|
22
|
+
allow(cluster_finder).to receive(:find).and_return found_cluster
|
23
|
+
allow(found_cluster).to receive(:to_hash).and_return cluster_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
shared_examples_for 'render cluster as JSON' do
|
27
|
+
it 'prints cluster hash to standard out as JSON' do
|
28
|
+
expect(JSON.parse(standard_out.string)).to eq stringify_keys(cluster_hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'prints nothing to standard error' do
|
32
|
+
expect(error_out.string).to eq ''
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
shared_examples_for 'find cluster by specified name' do |name|
|
37
|
+
it 'finds cluster with specified cluster name' do
|
38
|
+
expect(cluster_finder).to have_received(:find).with(hash_including(cluster_name: name))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
shared_examples_for 'find cluster by detected name' do
|
43
|
+
it 'finds cluster with detected cluster name' do
|
44
|
+
expect(cluster_finder).to have_received(:find).with(hash_including(cluster_name: detected_cluster))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
shared_examples_for 'find cluster in specified region' do |region|
|
49
|
+
it 'finds cluster in specified region' do
|
50
|
+
expect(cluster_finder).to have_received(:find).with(hash_including(region: region))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
shared_examples_for 'find cluster in detected region' do
|
55
|
+
it 'finds cluster in detected region' do
|
56
|
+
expect(cluster_finder).to have_received(:find).with(hash_including(region: detected_region))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
describe '#execute' do
|
62
|
+
context 'when cluster name and region are specified' do
|
63
|
+
before :each do
|
64
|
+
subject.execute(cluster_name: 'other-cluster', region: 'custom-region')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does not attempt automatic cluster detection' do
|
68
|
+
expect(detector).not_to have_received(:detect_cluster)
|
69
|
+
end
|
70
|
+
|
71
|
+
include_examples('find cluster by specified name', 'other-cluster')
|
72
|
+
include_examples('find cluster in specified region', 'custom-region')
|
73
|
+
include_examples 'render cluster as JSON'
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when only cluster name is specified' do
|
77
|
+
before :each do
|
78
|
+
expect(detector).to receive(:detect_cluster).and_return detector_result
|
79
|
+
subject.execute(cluster_name: 'other-cluster')
|
80
|
+
end
|
81
|
+
|
82
|
+
include_examples('find cluster by specified name', 'other-cluster')
|
83
|
+
include_examples('find cluster in detected region')
|
84
|
+
include_examples 'render cluster as JSON'
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when region is specified and cluster name can be detected' do
|
88
|
+
before :each do
|
89
|
+
expect(detector).to receive(:detect_cluster).and_return detector_result
|
90
|
+
subject.execute(region: 'custom-region')
|
91
|
+
end
|
92
|
+
|
93
|
+
include_examples('find cluster by detected name')
|
94
|
+
include_examples('find cluster in specified region', 'custom-region')
|
95
|
+
include_examples 'render cluster as JSON'
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when cluster detector throws exception' do
|
99
|
+
before :each do
|
100
|
+
expect(detector).to receive(:detect_cluster).and_raise Errno::ENETUNREACH
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'rethrows the exception' do
|
104
|
+
expect { subject.execute }.to raise_error Errno::ENETUNREACH
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'prints nothing to standard out' do
|
108
|
+
begin
|
109
|
+
subject.execute rescue StandardError
|
110
|
+
end
|
111
|
+
expect(standard_out.string).to eq ''
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'prints exception header to stderr' do
|
115
|
+
begin
|
116
|
+
subject.execute rescue StandardError
|
117
|
+
end
|
118
|
+
expect(error_out.string).to match /This instance may not be running on EC2/
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def stringify_keys(hash)
|
124
|
+
string_hash = {}
|
125
|
+
hash.each do |key, value|
|
126
|
+
string_hash[key.to_s] = value
|
127
|
+
end
|
128
|
+
string_hash
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
describe Cloudfinder::EC2::Detector do
|
2
|
+
describe '#detect' do
|
3
|
+
let (:ec2_client) { Aws::EC2::Client.new(stub_responses: true) }
|
4
|
+
let (:tags) { [] }
|
5
|
+
|
6
|
+
before (:each) do
|
7
|
+
allow(Aws::EC2::Client).to receive(:new).and_return(ec2_client)
|
8
|
+
ec2_client.stub_responses(:describe_tags, tags: tags)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'when instance metadata not available' do
|
12
|
+
it 'throws exception on timeout' do
|
13
|
+
allow(subject).to receive(:open).and_raise TimeoutError
|
14
|
+
expect { subject.detect_cluster }.to raise_error(TimeoutError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'throws exception on connection refused' do
|
18
|
+
allow(subject).to receive(:open).and_raise Errno::ECONNREFUSED
|
19
|
+
expect { subject.detect_cluster }.to raise_error(Errno::ECONNREFUSED)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'throws exception on 404' do
|
23
|
+
allow(subject).to receive(:open).and_raise OpenURI::HTTPError.new('404', double('io'))
|
24
|
+
expect { subject.detect_cluster }.to raise_error(OpenURI::HTTPError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'throws exception on network unreachable' do
|
28
|
+
allow(subject).to receive(:open).and_raise Errno::ENETUNREACH
|
29
|
+
expect { subject.detect_cluster }.to raise_error(Errno::ENETUNREACH)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
shared_examples_for 'instance outside cluster' do
|
35
|
+
include_examples 'instance with metadata'
|
36
|
+
|
37
|
+
it 'returns a nil cluster name' do
|
38
|
+
expect(subject.detect_cluster[:cluster_name]).to be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns a nil cluster role' do
|
42
|
+
expect(subject.detect_cluster[:cluster_name]).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
shared_examples_for 'instance with metadata' do
|
47
|
+
it 'returns the instance id' do
|
48
|
+
expect(subject.detect_cluster[:instance_id]).to eq instance_id
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns the AWS region' do
|
52
|
+
expect(subject.detect_cluster[:region]).to eq region
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when instance metadata available' do
|
57
|
+
let (:instance_id) { 'i-00000001' }
|
58
|
+
let (:availability_zone) { 'eu-west-1c' }
|
59
|
+
let (:region) { 'eu-west-1' }
|
60
|
+
|
61
|
+
before :each do
|
62
|
+
stub_metadata('/placement/availability-zone', availability_zone)
|
63
|
+
stub_metadata('/instance-id', instance_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when searching for EC2 instances' do
|
67
|
+
let (:ec2_client) { spy(Aws::EC2::Client) }
|
68
|
+
|
69
|
+
it 'creates AWS API client for the specified region' do
|
70
|
+
expect(Aws::EC2::Client).to receive(:new).with(region: region).once
|
71
|
+
subject.detect_cluster
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'requests the tags for this instance' do
|
75
|
+
subject.detect_cluster
|
76
|
+
expect(ec2_client).to have_received(:describe_tags).once.with(filters: [{ name: 'resource-id', values: [instance_id] }])
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when AWS API throws exceptions' do
|
82
|
+
it 'bubbles exceptions to caller' do
|
83
|
+
ec2_client.stub_responses(:describe_tags, Aws::Errors::MissingCredentialsError)
|
84
|
+
expect { subject.detect_cluster }.to raise_error(Aws::Errors::MissingCredentialsError)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when not part of cluster' do
|
89
|
+
context 'when instance has no tags' do
|
90
|
+
let (:tags) { [] }
|
91
|
+
include_examples 'instance outside cluster'
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when instance only has irrelevant tags' do
|
95
|
+
let (:tags) { [stub_tag('Name', 'anything')] }
|
96
|
+
include_examples 'instance outside cluster'
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when instance has no cloudfinder-cluster tag' do
|
100
|
+
let (:tags) { [stub_tag(Cloudfinder::EC2::ROLE_TAG_NAME, 'anything')] }
|
101
|
+
include_examples 'instance outside cluster'
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when instance has no cloudfinder-role tag' do
|
105
|
+
let (:tags) { [stub_tag(Cloudfinder::EC2::CLUSTER_TAG_NAME, 'anything')] }
|
106
|
+
include_examples 'instance outside cluster'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when part of cluster' do
|
111
|
+
let (:cluster_name) { 'qa' }
|
112
|
+
let (:cluster_role) { 'app' }
|
113
|
+
let (:tags) { [
|
114
|
+
stub_tag(Cloudfinder::EC2::CLUSTER_TAG_NAME, cluster_name),
|
115
|
+
stub_tag(Cloudfinder::EC2::ROLE_TAG_NAME, cluster_role)
|
116
|
+
] }
|
117
|
+
|
118
|
+
include_examples 'instance with metadata'
|
119
|
+
|
120
|
+
it 'returns cluster name' do
|
121
|
+
expect(subject.detect_cluster[:cluster_name]).to eq cluster_name
|
122
|
+
end
|
123
|
+
it 'returns cluster role' do
|
124
|
+
expect(subject.detect_cluster[:cluster_role]).to eq cluster_role.to_sym
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def stub_metadata(path, response)
|
131
|
+
url = "http://169.254.169.254/latest/meta-data#{path}"
|
132
|
+
timeout = Cloudfinder::EC2::Detector::EC2_METADATA_TIMEOUT
|
133
|
+
allow(subject).to receive(:open).with(url, { read_timeout: timeout }).and_return FakeResponse.new(response)
|
134
|
+
end
|
135
|
+
|
136
|
+
def stub_tag(key, value)
|
137
|
+
{
|
138
|
+
key: key,
|
139
|
+
value: value
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
class FakeResponse
|
144
|
+
def initialize(body)
|
145
|
+
@body = body
|
146
|
+
end
|
147
|
+
|
148
|
+
def read
|
149
|
+
@body
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|