cloudfinder-ec2 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|