beaker-aws 0.4.0 → 0.5.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.
- checksums.yaml +5 -13
- data/CHANGELOG.md +42 -0
- data/Rakefile +31 -0
- data/beaker-aws.gemspec +4 -2
- data/ec2.md +13 -0
- data/lib/beaker/hypervisor/aws_sdk.rb +271 -201
- data/lib/beaker-aws/version.rb +1 -1
- data/spec/beaker/hypervisor/aws_sdk_spec.rb +381 -272
- metadata +67 -32
@@ -1,8 +1,15 @@
|
|
1
|
-
require 'aws
|
1
|
+
require 'aws-sdk-ec2'
|
2
|
+
require 'aws-sdk-core/waiters'
|
2
3
|
require 'set'
|
3
4
|
require 'zlib'
|
4
5
|
require 'beaker/hypervisor/ec2_helper'
|
5
6
|
|
7
|
+
class Aws::EC2::Types::Instance
|
8
|
+
def ip_address
|
9
|
+
public_ip_address || private_ip_address
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
6
13
|
module Beaker
|
7
14
|
# This is an alternate EC2 driver that implements direct API access using
|
8
15
|
# Amazon's AWS-SDK library: {http://aws.amazon.com/documentation/sdkforruby/ SDK For Ruby}
|
@@ -12,6 +19,7 @@ module Beaker
|
|
12
19
|
class AwsSdk < Beaker::Hypervisor
|
13
20
|
ZOMBIE = 3 #anything older than 3 hours is considered a zombie
|
14
21
|
PING_SECURITY_GROUP_NAME = 'beaker-ping'
|
22
|
+
attr_reader :default_region
|
15
23
|
|
16
24
|
# Initialize AwsSdk hypervisor driver
|
17
25
|
#
|
@@ -21,26 +29,34 @@ module Beaker
|
|
21
29
|
@hosts = hosts
|
22
30
|
@options = options
|
23
31
|
@logger = options[:logger]
|
32
|
+
@default_region = ENV['AWS_REGION'] || 'us-west-2'
|
24
33
|
|
25
34
|
# Get AWS credentials
|
26
|
-
creds = options[:use_fog_credentials] ? load_credentials() :
|
35
|
+
creds = options[:use_fog_credentials] ? load_credentials() : nil
|
27
36
|
|
28
37
|
config = {
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
35
|
-
:max_retries => 12,
|
38
|
+
:credentials => creds,
|
39
|
+
:logger => Logger.new($stdout),
|
40
|
+
:log_level => :debug,
|
41
|
+
:log_formatter => Aws::Log::Formatter.colored,
|
42
|
+
:retry_limit => 12,
|
43
|
+
:region => ENV['AWS_REGION'] || 'us-west-2'
|
36
44
|
}.delete_if{ |k,v| v.nil? }
|
37
|
-
|
45
|
+
Aws.config.update(config)
|
46
|
+
|
47
|
+
@client = {}
|
48
|
+
@client.default_proc = proc do |hash, key|
|
49
|
+
hash[key] = Aws::EC2::Client.new(:region => key)
|
50
|
+
end
|
38
51
|
|
39
|
-
@ec2 = AWS::EC2.new()
|
40
52
|
test_split_install()
|
41
53
|
end
|
42
54
|
|
43
|
-
|
55
|
+
def client(region = default_region)
|
56
|
+
@client[region]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Provision all hosts on EC2 using the Aws::EC2 API
|
44
60
|
#
|
45
61
|
# @return [void]
|
46
62
|
def provision
|
@@ -71,21 +87,29 @@ module Beaker
|
|
71
87
|
nil #void
|
72
88
|
end
|
73
89
|
|
90
|
+
def regions
|
91
|
+
@regions ||= client.describe_regions.regions.map(&:region_name)
|
92
|
+
end
|
93
|
+
|
74
94
|
# Kill all instances.
|
75
95
|
#
|
76
|
-
# @param instances [Enumerable<EC2::Instance>]
|
96
|
+
# @param instances [Enumerable<Aws::EC2::Types::Instance>]
|
77
97
|
# @return [void]
|
78
98
|
def kill_instances(instances)
|
79
|
-
instances.
|
80
|
-
|
81
|
-
@logger.notify("aws-sdk: killing EC2 instance #{instance.id}")
|
82
|
-
instance.terminate
|
83
|
-
end
|
99
|
+
running_instances = instances.compact.select do |instance|
|
100
|
+
instance_by_id(instance.instance_id).state.name == 'running'
|
84
101
|
end
|
102
|
+
instance_ids = running_instances.map(&:instance_id)
|
103
|
+
|
104
|
+
return nil if instance_ids.empty?
|
105
|
+
|
106
|
+
@logger.notify("aws-sdk: killing EC2 instance(s) #{instance_ids.join(', ')}")
|
107
|
+
client.terminate_instances(:instance_ids => instance_ids)
|
108
|
+
|
85
109
|
nil
|
86
110
|
end
|
87
111
|
|
88
|
-
# Cleanup all earlier provisioned hosts on EC2 using the
|
112
|
+
# Cleanup all earlier provisioned hosts on EC2 using the Aws::EC2 library
|
89
113
|
#
|
90
114
|
# It goes without saying, but a #cleanup does nothing without a #provision
|
91
115
|
# method call first.
|
@@ -93,7 +117,7 @@ module Beaker
|
|
93
117
|
# @return [void]
|
94
118
|
def cleanup
|
95
119
|
# Provisioning should have set the host 'instance' values.
|
96
|
-
kill_instances(@hosts.map{|h| h['instance']}.select{|x| !x.nil?})
|
120
|
+
kill_instances(@hosts.map{ |h| h['instance'] }.select{ |x| !x.nil? })
|
97
121
|
delete_key_pair_all_regions()
|
98
122
|
nil
|
99
123
|
end
|
@@ -106,17 +130,20 @@ module Beaker
|
|
106
130
|
# @param [Regex] status The regular expression to match against the instance's status
|
107
131
|
def log_instances(key = key_name, status = /running/)
|
108
132
|
instances = []
|
109
|
-
|
110
|
-
@logger.debug "Reviewing: #{region
|
111
|
-
|
112
|
-
|
113
|
-
|
133
|
+
regions.each do |region|
|
134
|
+
@logger.debug "Reviewing: #{region}"
|
135
|
+
client(region).describe_instances.reservations.each do |reservation|
|
136
|
+
reservation.instances.each do |instance|
|
137
|
+
if (instance.key_name =~ /#{key}/) and (instance.state.name =~ status)
|
138
|
+
instances << instance
|
139
|
+
end
|
114
140
|
end
|
115
141
|
end
|
116
142
|
end
|
117
143
|
output = ""
|
118
144
|
instances.each do |instance|
|
119
|
-
|
145
|
+
dns_name = instance.public_dns_name || instance.private_dns_name
|
146
|
+
output << "#{instance.instance_id} keyname: #{instance.key_name}, dns name: #{dns_name}, private ip: #{instance.private_ip_address}, ip: #{instance.public_ip_address}, launch time #{instance.launch_time}, status: #{instance.state.name}\n"
|
120
147
|
end
|
121
148
|
@logger.notify("aws-sdk: List instances (keyname: #{key})")
|
122
149
|
@logger.notify("#{output}")
|
@@ -125,46 +152,46 @@ module Beaker
|
|
125
152
|
# Provided an id return an instance object.
|
126
153
|
# Instance object will respond to methods described here: {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Instance.html AWS Instance Object}.
|
127
154
|
# @param [String] id The id of the instance to return
|
128
|
-
# @return [
|
155
|
+
# @return [Aws::EC2::Types::Instance] An Aws::EC2 instance object
|
129
156
|
def instance_by_id(id)
|
130
|
-
|
157
|
+
client.describe_instances(:instance_ids => [id]).reservations.first.instances.first
|
131
158
|
end
|
132
159
|
|
133
160
|
# Return all instances currently on ec2.
|
134
161
|
# @see AwsSdk#instance_by_id
|
135
|
-
# @return [
|
162
|
+
# @return [Array<Aws::Ec2::Types::Instance>] An array of Aws::EC2 instance objects
|
136
163
|
def instances
|
137
|
-
|
164
|
+
client.describe_instances.reservations.map(&:instances).flatten
|
138
165
|
end
|
139
166
|
|
140
167
|
# Provided an id return a VPC object.
|
141
168
|
# VPC object will respond to methods described here: {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/VPC.html AWS VPC Object}.
|
142
169
|
# @param [String] id The id of the VPC to return
|
143
|
-
# @return [
|
170
|
+
# @return [Aws::EC2::Types::Vpc] An Aws::EC2 vpc object
|
144
171
|
def vpc_by_id(id)
|
145
|
-
|
172
|
+
client.describe_vpcs(:vpc_ids => [id]).vpcs.first
|
146
173
|
end
|
147
174
|
|
148
175
|
# Return all VPCs currently on ec2.
|
149
176
|
# @see AwsSdk#vpc_by_id
|
150
|
-
# @return [
|
177
|
+
# @return [Array<Aws::EC2::Types::Vpc>] An array of Aws::EC2 vpc objects
|
151
178
|
def vpcs
|
152
|
-
|
179
|
+
client.describe_vpcs.vpcs
|
153
180
|
end
|
154
181
|
|
155
182
|
# Provided an id return a security group object
|
156
183
|
# Security object will respond to methods described here: {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/SecurityGroup.html AWS SecurityGroup Object}.
|
157
184
|
# @param [String] id The id of the security group to return
|
158
|
-
# @return [
|
185
|
+
# @return [Aws::EC2::Types::SecurityGroup] An Aws::EC2 security group object
|
159
186
|
def security_group_by_id(id)
|
160
|
-
|
187
|
+
client.describe_security_groups(:group_ids => [id]).security_groups.first
|
161
188
|
end
|
162
189
|
|
163
190
|
# Return all security groups currently on ec2.
|
164
191
|
# @see AwsSdk#security_goup_by_id
|
165
|
-
# @return [
|
192
|
+
# @return [Array<Aws::EC2::Types::SecurityGroup>] An array of Aws::EC2 security group objects
|
166
193
|
def security_groups
|
167
|
-
|
194
|
+
client.describe_security_groups.security_groups
|
168
195
|
end
|
169
196
|
|
170
197
|
# Shutdown and destroy ec2 instances idenfitied by key that have been alive
|
@@ -174,58 +201,60 @@ module Beaker
|
|
174
201
|
# @param [String] key The key_name to match for
|
175
202
|
def kill_zombies(max_age = ZOMBIE, key = key_name)
|
176
203
|
@logger.notify("aws-sdk: Kill Zombies! (keyname: #{key}, age: #{max_age} hrs)")
|
177
|
-
|
178
|
-
|
204
|
+
|
205
|
+
instances_to_kill = []
|
206
|
+
|
179
207
|
time_now = Time.now.getgm #ec2 uses GM time
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
208
|
+
|
209
|
+
#examine all available regions
|
210
|
+
regions.each do |region|
|
211
|
+
@logger.debug "Reviewing: #{region}"
|
212
|
+
|
213
|
+
client(region).describe_instances.reservations.each do |reservation|
|
214
|
+
reservation.instances.each do |instance|
|
186
215
|
if (instance.key_name =~ /#{key}/)
|
187
|
-
@logger.debug "Examining #{instance.
|
188
|
-
if ((time_now - instance.launch_time) > max_age*60*60) and instance.
|
189
|
-
@logger.debug "Kill! #{instance.
|
190
|
-
instance
|
191
|
-
kill_count += 1
|
216
|
+
@logger.debug "Examining #{instance.instance_id} (keyname: #{instance.key_name}, launch time: #{instance.launch_time}, state: #{instance.state.name})"
|
217
|
+
if ((time_now - instance.launch_time) > max_age*60*60) and instance.state.name !~ /terminated/
|
218
|
+
@logger.debug "Kill! #{instance.instance_id}: #{instance.key_name} (Current status: #{instance.state.name})"
|
219
|
+
instances_to_kill << instance
|
192
220
|
end
|
193
221
|
end
|
194
|
-
rescue AWS::Core::Resource::NotFound, AWS::EC2::Errors => e
|
195
|
-
@logger.debug "Failed to remove instance: #{instance.id}, #{e}"
|
196
222
|
end
|
197
223
|
end
|
198
224
|
end
|
225
|
+
|
226
|
+
kill_instances(instances_to_kill)
|
199
227
|
delete_key_pair_all_regions(key_name_prefix)
|
200
228
|
|
201
|
-
@logger.notify "#{key}: Killed #{
|
229
|
+
@logger.notify "#{key}: Killed #{instances_to_kill.length} instance(s)"
|
202
230
|
end
|
203
231
|
|
204
232
|
# Destroy any volumes marked 'available', INCLUDING THOSE YOU DON'T OWN! Use with care.
|
205
233
|
def kill_zombie_volumes
|
206
234
|
# Occasionaly, tearing down ec2 instances leaves orphaned EBS volumes behind -- these stack up quickly.
|
207
235
|
# This simply looks for EBS volumes that are not in use
|
208
|
-
# Note: don't use volumes.each here as that funtion doesn't allow proper rescue from error states
|
209
236
|
@logger.notify("aws-sdk: Kill Zombie Volumes!")
|
210
237
|
volume_count = 0
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
238
|
+
|
239
|
+
regions.each do |region|
|
240
|
+
@logger.debug "Reviewing: #{region}"
|
241
|
+
available_volumes = client(region).describe_volumes(
|
242
|
+
:filters => [
|
243
|
+
{ :name => 'status', :values => ['available'], }
|
244
|
+
]
|
245
|
+
).volumes
|
246
|
+
|
247
|
+
available_volumes.each do |volume|
|
215
248
|
begin
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
volume_count += 1
|
221
|
-
end
|
222
|
-
rescue AWS::EC2::Errors::InvalidVolume::NotFound => e
|
223
|
-
@logger.debug "Failed to remove volume: #{vol.id}, #{e}"
|
249
|
+
client(region).delete_volume(:volume_id => volume.id)
|
250
|
+
volume_count += 1
|
251
|
+
rescue Aws::EC2::Errors::InvalidVolume::NotFound => e
|
252
|
+
@logger.debug "Failed to remove volume: #{volume.id} #{e}"
|
224
253
|
end
|
225
254
|
end
|
226
255
|
end
|
227
|
-
@logger.notify "Freed #{volume_count} volume(s)"
|
228
256
|
|
257
|
+
@logger.notify "Freed #{volume_count} volume(s)"
|
229
258
|
end
|
230
259
|
|
231
260
|
# Create an EC2 instance for host, tag it, and return it.
|
@@ -237,42 +266,40 @@ module Beaker
|
|
237
266
|
amisize = host['amisize'] || 'm1.small'
|
238
267
|
vpc_id = host['vpc_id'] || @options['vpc_id'] || nil
|
239
268
|
|
240
|
-
if vpc_id
|
269
|
+
if vpc_id && !subnet_id
|
241
270
|
raise RuntimeError, "A subnet_id must be provided with a vpc_id"
|
242
271
|
end
|
243
272
|
|
244
273
|
# Use snapshot provided for this host
|
245
274
|
image_type = host['snapshot']
|
246
|
-
|
247
|
-
|
248
|
-
end
|
275
|
+
raise RuntimeError, "No snapshot/image_type provided for EC2 provisioning" unless image_type
|
276
|
+
|
249
277
|
ami = ami_spec[amitype]
|
250
278
|
ami_region = ami[:region]
|
251
279
|
|
252
280
|
# Main region object for ec2 operations
|
253
|
-
region =
|
281
|
+
region = ami_region
|
254
282
|
|
255
283
|
# If we haven't defined a vpc_id then we use the default vpc for the provided region
|
256
|
-
|
257
|
-
@logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault")
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
284
|
+
unless vpc_id
|
285
|
+
@logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault'")
|
286
|
+
|
287
|
+
default_vpcs = client(region).describe_vpcs(:filters => [{:name => 'isDefault', :values => ['true']}])
|
288
|
+
vpc_id = if default_vpcs.vpcs.empty?
|
289
|
+
nil
|
290
|
+
else
|
291
|
+
default_vpcs.vpcs.first.vpc_id
|
292
|
+
end
|
264
293
|
end
|
265
294
|
|
266
295
|
# Grab the vpc object based upon provided id
|
267
|
-
vpc = vpc_id ? region.
|
296
|
+
vpc = vpc_id ? client(region).describe_vpcs(:vpc_ids => [vpc_id]).vpcs.first : nil
|
268
297
|
|
269
298
|
# Grab image object
|
270
299
|
image_id = ami[:image][image_type.to_sym]
|
271
300
|
@logger.notify("aws-sdk: Checking image #{image_id} exists and getting its root device")
|
272
|
-
image = region.
|
273
|
-
|
274
|
-
raise RuntimeError, "Image not found: #{image_id}"
|
275
|
-
end
|
301
|
+
image = client(region).describe_images(:image_ids => [image_id]).images.first
|
302
|
+
raise RuntimeError, "Image not found: #{image_id}" if image.nil?
|
276
303
|
|
277
304
|
@logger.notify("Image Storage Type: #{image.root_device_type}")
|
278
305
|
|
@@ -280,14 +307,14 @@ module Beaker
|
|
280
307
|
# ready for a create.
|
281
308
|
block_device_mappings = []
|
282
309
|
if image.root_device_type == :ebs
|
283
|
-
orig_bdm = image.block_device_mappings
|
284
|
-
@logger.notify("aws-sdk: Image block_device_mappings: #{orig_bdm
|
285
|
-
orig_bdm.each do |
|
310
|
+
orig_bdm = image.block_device_mappings
|
311
|
+
@logger.notify("aws-sdk: Image block_device_mappings: #{orig_bdm}")
|
312
|
+
orig_bdm.each do |block_device|
|
286
313
|
block_device_mappings << {
|
287
|
-
:device_name => device_name,
|
314
|
+
:device_name => block_device.device_name,
|
288
315
|
:ebs => {
|
289
316
|
# Change the default size of the root volume.
|
290
|
-
:volume_size => host['volume_size'] ||
|
317
|
+
:volume_size => host['volume_size'] || block_device.ebs.volume_size,
|
291
318
|
# This is required to override the images default for
|
292
319
|
# delete_on_termination, forcing all volumes to be deleted once the
|
293
320
|
# instance is terminated.
|
@@ -306,18 +333,22 @@ module Beaker
|
|
306
333
|
subnet_id ? ("in %p" % subnet_id) : '']
|
307
334
|
@logger.notify(msg)
|
308
335
|
config = {
|
309
|
-
:
|
310
|
-
:
|
311
|
-
:
|
312
|
-
:
|
313
|
-
|
336
|
+
:max_count => 1,
|
337
|
+
:min_count => 1,
|
338
|
+
:image_id => image_id,
|
339
|
+
:monitoring => {
|
340
|
+
:enabled => true,
|
341
|
+
},
|
342
|
+
:key_name => ensure_key_pair(region).key_pairs.first.key_name,
|
343
|
+
:security_groups => [security_group.group_name, ping_security_group.group_name],
|
314
344
|
:instance_type => amisize,
|
315
345
|
:disable_api_termination => false,
|
316
346
|
:instance_initiated_shutdown_behavior => "terminate",
|
317
|
-
:
|
347
|
+
:subnet_id => subnet_id,
|
318
348
|
}
|
319
349
|
config[:block_device_mappings] = block_device_mappings if image.root_device_type == :ebs
|
320
|
-
region.
|
350
|
+
reservation = client(region).run_instances(config)
|
351
|
+
reservation.instances.first
|
321
352
|
end
|
322
353
|
|
323
354
|
# For each host, create an EC2 instance in one of the specified
|
@@ -350,7 +381,7 @@ module Beaker
|
|
350
381
|
instance = create_instance(host, ami_spec, subnet_id)
|
351
382
|
instances_created.push({:instance => instance, :host => host})
|
352
383
|
break
|
353
|
-
rescue
|
384
|
+
rescue Aws::EC2::Errors::InsufficientInstanceCapacity
|
354
385
|
@logger.notify("aws-sdk: hit #{subnet_id} capacity limit; moving on")
|
355
386
|
subnet_i = (subnet_i + 1) % shuffnets.length
|
356
387
|
end
|
@@ -426,7 +457,7 @@ module Beaker
|
|
426
457
|
# Wait until all instances reach the desired state. Each Hash in
|
427
458
|
# instances must contain an :instance and :host value.
|
428
459
|
#
|
429
|
-
# @param
|
460
|
+
# @param state_name [String] EC2 state to wait for, 'running', 'stopped', etc.
|
430
461
|
# @param instances Enumerable<Hash{Symbol=>EC2::Instance,Host}>
|
431
462
|
# @param block [Proc] more complex checks can be made by passing a
|
432
463
|
# block in. This overrides the status parameter.
|
@@ -434,33 +465,38 @@ module Beaker
|
|
434
465
|
# yielded to the passed block
|
435
466
|
# @return [void]
|
436
467
|
# @api private
|
437
|
-
|
468
|
+
# FIXME: rename to #wait_for_state
|
469
|
+
def wait_for_status(state_name, instances, &block)
|
438
470
|
# Wait for each node to reach status :running
|
439
|
-
@logger.notify("aws-sdk: Waiting for all hosts to be #{
|
471
|
+
@logger.notify("aws-sdk: Waiting for all hosts to be #{state_name}")
|
440
472
|
instances.each do |x|
|
441
|
-
name = x[:name
|
473
|
+
name = x[:host].name
|
442
474
|
instance = x[:instance]
|
443
|
-
@logger.notify("aws-sdk: Wait for node #{name} to be #{
|
444
|
-
# Here we keep waiting for the machine state to reach '
|
475
|
+
@logger.notify("aws-sdk: Wait for node #{name} to be #{state_name}")
|
476
|
+
# Here we keep waiting for the machine state to reach 'running' with an
|
445
477
|
# exponential backoff for each poll.
|
446
478
|
# TODO: should probably be a in a shared method somewhere
|
447
479
|
for tries in 1..10
|
448
|
-
|
480
|
+
refreshed_instance = instance_by_id(instance.instance_id)
|
481
|
+
|
482
|
+
if refreshed_instance.nil?
|
483
|
+
@logger.debug("Instance #{name} not yet available (#{e})")
|
484
|
+
else
|
449
485
|
if block_given?
|
450
|
-
test_result = yield
|
486
|
+
test_result = yield refreshed_instance
|
451
487
|
else
|
452
|
-
test_result =
|
488
|
+
test_result = refreshed_instance.state.name.to_s == state_name.to_s
|
453
489
|
end
|
454
490
|
if test_result
|
491
|
+
x[:instance] = refreshed_instance
|
455
492
|
# Always sleep, so the next command won't cause a throttle
|
456
493
|
backoff_sleep(tries)
|
457
494
|
break
|
458
495
|
elsif tries == 10
|
459
|
-
raise "Instance never reached state #{
|
496
|
+
raise "Instance never reached state #{state_name}"
|
460
497
|
end
|
461
|
-
rescue AWS::EC2::Errors::InvalidInstanceID::NotFound => e
|
462
|
-
@logger.debug("Instance #{name} not yet available (#{e})")
|
463
498
|
end
|
499
|
+
|
464
500
|
backoff_sleep(tries)
|
465
501
|
end
|
466
502
|
end
|
@@ -479,8 +515,8 @@ module Beaker
|
|
479
515
|
wait_for_status(:running, @hosts)
|
480
516
|
|
481
517
|
wait_for_status(nil, @hosts) do |instance|
|
482
|
-
instance_status_collection = instance.client.describe_instance_status({:instance_ids => [instance.
|
483
|
-
first_instance = instance_status_collection
|
518
|
+
instance_status_collection = instance.client.describe_instance_status({:instance_ids => [instance.instance_id]})
|
519
|
+
first_instance = instance_status_collection.reservations.first.instances.first
|
484
520
|
first_instance[:system_status][:status] == "ok"
|
485
521
|
end
|
486
522
|
|
@@ -499,15 +535,38 @@ module Beaker
|
|
499
535
|
|
500
536
|
# Define tags for the instance
|
501
537
|
@logger.notify("aws-sdk: Add tags for #{host.name}")
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
538
|
+
|
539
|
+
tags = [
|
540
|
+
{
|
541
|
+
:key => 'jenkins_build_url',
|
542
|
+
:value => @options[:jenkins_build_url],
|
543
|
+
},
|
544
|
+
{
|
545
|
+
:key => 'Name',
|
546
|
+
:value => host.name,
|
547
|
+
},
|
548
|
+
{
|
549
|
+
:key => 'department',
|
550
|
+
:value => @options[:department],
|
551
|
+
},
|
552
|
+
{
|
553
|
+
:key => 'project',
|
554
|
+
:value => @options[:project],
|
555
|
+
},
|
556
|
+
{
|
557
|
+
:key => 'created_by',
|
558
|
+
:value => @options[:created_by],
|
559
|
+
},
|
560
|
+
]
|
507
561
|
|
508
562
|
host[:host_tags].each do |name, val|
|
509
|
-
|
563
|
+
tags << { :key => name.to_s, :value => val }
|
510
564
|
end
|
565
|
+
|
566
|
+
client.create_tags(
|
567
|
+
:resources => [instance.instance_id],
|
568
|
+
:tags => tags.reject { |r| r[:value].nil? },
|
569
|
+
)
|
511
570
|
end
|
512
571
|
|
513
572
|
nil
|
@@ -522,10 +581,10 @@ module Beaker
|
|
522
581
|
@hosts.each do |host|
|
523
582
|
@logger.notify("aws-sdk: Populate DNS for #{host.name}")
|
524
583
|
instance = host['instance']
|
525
|
-
host['ip'] = instance.
|
584
|
+
host['ip'] = instance.public_ip_address || instance.private_ip_address
|
526
585
|
host['private_ip'] = instance.private_ip_address
|
527
|
-
host['dns_name'] = instance.
|
528
|
-
@logger.notify("aws-sdk: name: #{host.name} ip: #{host['ip']} private_ip: #{host['private_ip']} dns_name: #{
|
586
|
+
host['dns_name'] = instance.public_dns_name || instance.private_dns_name
|
587
|
+
@logger.notify("aws-sdk: name: #{host.name} ip: #{host['ip']} private_ip: #{host['private_ip']} dns_name: #{host['dns_name']}")
|
529
588
|
end
|
530
589
|
|
531
590
|
nil
|
@@ -628,8 +687,8 @@ module Beaker
|
|
628
687
|
# @return nil
|
629
688
|
# @api private
|
630
689
|
def enable_root_netscaler(host)
|
631
|
-
host['ssh'] = {:password => host['instance'].
|
632
|
-
@logger.notify("netscaler: nsroot password is #{host['instance'].
|
690
|
+
host['ssh'] = {:password => host['instance'].instance_id}
|
691
|
+
@logger.notify("netscaler: nsroot password is #{host['instance'].instance_id}")
|
633
692
|
end
|
634
693
|
|
635
694
|
# Set the :vmhostname for each host object to be the dns_name, which is accessible
|
@@ -744,8 +803,8 @@ module Beaker
|
|
744
803
|
|
745
804
|
# Creates the KeyPair for this test run
|
746
805
|
#
|
747
|
-
# @param region [
|
748
|
-
# @return [
|
806
|
+
# @param region [Aws::EC2::Region] region to create the key pair in
|
807
|
+
# @return [Aws::EC2::KeyPair] created key_pair
|
749
808
|
# @api private
|
750
809
|
def ensure_key_pair(region)
|
751
810
|
pair_name = key_name()
|
@@ -776,69 +835,52 @@ module Beaker
|
|
776
835
|
# a simple {::String#start_with?} filter. If no filter is given, the basic key
|
777
836
|
# name returned by {#key_name} will be used.
|
778
837
|
#
|
779
|
-
# @return [Hash{
|
838
|
+
# @return [Hash{String=>Array[String]}] a hash of region name to
|
780
839
|
# an array of the keypair names that match for the filter
|
781
840
|
# @api private
|
782
841
|
def my_key_pairs(name_filter=nil)
|
783
842
|
keypairs_by_region = {}
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
aws_name_filter = keyname_default
|
791
|
-
end
|
792
|
-
keypair_collection = region.key_pairs.filter('key-name', aws_name_filter)
|
793
|
-
keypair_collection.each do |keypair|
|
794
|
-
keypairs_by_region[region] ||= []
|
795
|
-
keypairs_by_region[region] << keypair.name
|
796
|
-
end
|
843
|
+
key_name_filter = name_filter ? "#{name_filter}-*" : key_name
|
844
|
+
|
845
|
+
regions.each do |region|
|
846
|
+
keypairs_by_region[region] = client(region).describe_key_pairs(
|
847
|
+
:filters => [{ :name => 'key-name', :values => [key_name_filter] }]
|
848
|
+
).key_pairs.map(&:key_name)
|
797
849
|
end
|
850
|
+
|
798
851
|
keypairs_by_region
|
799
852
|
end
|
800
853
|
|
801
854
|
# Deletes a given key pair
|
802
855
|
#
|
803
|
-
# @param [
|
856
|
+
# @param [Aws::EC2::Region] region the region the key belongs to
|
804
857
|
# @param [String] pair_name the name of the key to be deleted
|
805
858
|
#
|
806
859
|
# @api private
|
807
860
|
def delete_key_pair(region, pair_name)
|
808
|
-
kp = region.
|
809
|
-
|
810
|
-
@logger.debug("aws-sdk: delete key pair in region: #{region
|
811
|
-
|
861
|
+
kp = client(region).describe_key_pairs(:key_names => [pair_name]).key_pairs.first
|
862
|
+
unless kp.nil?
|
863
|
+
@logger.debug("aws-sdk: delete key pair in region: #{region}")
|
864
|
+
client(region).delete_key_pair(:key_name => pair_name)
|
812
865
|
end
|
866
|
+
rescue Aws::EC2::Errors::InvalidKeyPairNotFound
|
867
|
+
nil
|
813
868
|
end
|
814
869
|
|
815
870
|
# Create a new key pair for a given Beaker run
|
816
871
|
#
|
817
|
-
# @param [
|
872
|
+
# @param [Aws::EC2::Region] region the region the key pair will be imported into
|
818
873
|
# @param [String] pair_name the name of the key to be created
|
819
874
|
#
|
820
|
-
# @return [
|
875
|
+
# @return [Aws::EC2::KeyPair] key pair created
|
821
876
|
# @raise [RuntimeError] raised if AWS keypair not created
|
822
877
|
def create_new_key_pair(region, pair_name)
|
823
878
|
@logger.debug("aws-sdk: importing new key pair: #{pair_name}")
|
824
|
-
|
825
|
-
region.key_pairs.import(pair_name, ssh_string)
|
826
|
-
kp = region.key_pairs[pair_name]
|
827
|
-
|
828
|
-
exists = false
|
829
|
-
for tries in 1..5
|
830
|
-
if kp.exists?
|
831
|
-
exists = true
|
832
|
-
break
|
833
|
-
end
|
834
|
-
@logger.debug("AWS key pair doesn't appear to exist yet, sleeping before retry ")
|
835
|
-
backoff_sleep(tries)
|
836
|
-
end
|
879
|
+
client(region).import_key_pair(:key_name => pair_name, :public_key_material => public_key)
|
837
880
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
else
|
881
|
+
begin
|
882
|
+
client(region).wait_until(:key_pair_exists, { :key_names => [pair_name] }, :max_attempts => 5, :delay => 2)
|
883
|
+
rescue Aws::Waiters::Errors::WaiterFailed
|
842
884
|
raise RuntimeError, "AWS key pair #{pair_name} can not be queried, even after import"
|
843
885
|
end
|
844
886
|
end
|
@@ -865,13 +907,18 @@ module Beaker
|
|
865
907
|
#
|
866
908
|
# Accepts a VPC as input for checking & creation.
|
867
909
|
#
|
868
|
-
# @param vpc [
|
869
|
-
# @return [
|
910
|
+
# @param vpc [Aws::EC2::VPC] the AWS vpc control object
|
911
|
+
# @return [Aws::EC2::SecurityGroup] created security group
|
870
912
|
# @api private
|
871
913
|
def ensure_ping_group(vpc)
|
872
914
|
@logger.notify("aws-sdk: Ensure security group exists that enables ping, create if not")
|
873
915
|
|
874
|
-
group =
|
916
|
+
group = client.describe_security_groups(
|
917
|
+
:filters => [
|
918
|
+
{ :name => 'group-name', :values => [PING_SECURITY_GROUP_NAME] },
|
919
|
+
{ :name => 'vpc-id', :values => [vpc.vpc_id] },
|
920
|
+
]
|
921
|
+
).security_groups.first
|
875
922
|
|
876
923
|
if group.nil?
|
877
924
|
group = create_ping_group(vpc)
|
@@ -884,15 +931,20 @@ module Beaker
|
|
884
931
|
#
|
885
932
|
# Accepts a VPC as input for checking & creation.
|
886
933
|
#
|
887
|
-
# @param vpc [
|
934
|
+
# @param vpc [Aws::EC2::VPC] the AWS vpc control object
|
888
935
|
# @param ports [Array<Number>] an array of port numbers
|
889
|
-
# @return [
|
936
|
+
# @return [Aws::EC2::SecurityGroup] created security group
|
890
937
|
# @api private
|
891
938
|
def ensure_group(vpc, ports)
|
892
939
|
@logger.notify("aws-sdk: Ensure security group exists for ports #{ports.to_s}, create if not")
|
893
940
|
name = group_id(ports)
|
894
941
|
|
895
|
-
group =
|
942
|
+
group = client.describe_security_groups(
|
943
|
+
:filters => [
|
944
|
+
{ :name => 'group-name', :values => [name] },
|
945
|
+
{ :name => 'vpc-id', :values => [vpc.vpc_id] },
|
946
|
+
]
|
947
|
+
).security_groups.first
|
896
948
|
|
897
949
|
if group.nil?
|
898
950
|
group = create_group(vpc, ports)
|
@@ -905,15 +957,28 @@ module Beaker
|
|
905
957
|
#
|
906
958
|
# Accepts a region or VPC for group creation.
|
907
959
|
#
|
908
|
-
# @param rv [
|
909
|
-
# @return [
|
960
|
+
# @param rv [Aws::EC2::Region, Aws::EC2::VPC] the AWS region or vpc control object
|
961
|
+
# @return [Aws::EC2::SecurityGroup] created security group
|
910
962
|
# @api private
|
911
|
-
def create_ping_group(
|
963
|
+
def create_ping_group(region_or_vpc)
|
912
964
|
@logger.notify("aws-sdk: Creating group #{PING_SECURITY_GROUP_NAME}")
|
913
|
-
|
914
|
-
|
965
|
+
cl = region_or_vpc.is_a?(String) ? client(region_or_vpc) : client
|
966
|
+
|
967
|
+
params = {
|
968
|
+
:description => 'Custom Beaker security group to enable ping',
|
969
|
+
:group_name => PING_SECURITY_GROUP_NAME,
|
970
|
+
}
|
971
|
+
params[:vpc_id] = region_or_vpc.vpc_id if region_or_vpc.is_a?(Aws::EC2::Types::Vpc)
|
972
|
+
|
973
|
+
group = cl.create_security_group(params)
|
915
974
|
|
916
|
-
|
975
|
+
cl.authorize_security_group_ingress(
|
976
|
+
:cidr_ip => '0.0.0.0/0',
|
977
|
+
:ip_protocol => 'icmp',
|
978
|
+
:from_port => '8', # 8 == ICMPv4 ECHO request
|
979
|
+
:to_port => '-1', # -1 == All ICMP codes
|
980
|
+
:group_id => group.group_id,
|
981
|
+
)
|
917
982
|
|
918
983
|
group
|
919
984
|
end
|
@@ -922,22 +987,32 @@ module Beaker
|
|
922
987
|
#
|
923
988
|
# Accepts a region or VPC for group creation.
|
924
989
|
#
|
925
|
-
# @param rv [
|
990
|
+
# @param rv [Aws::EC2::Region, Aws::EC2::VPC] the AWS region or vpc control object
|
926
991
|
# @param ports [Array<Number>] an array of port numbers
|
927
|
-
# @return [
|
992
|
+
# @return [Aws::EC2::SecurityGroup] created security group
|
928
993
|
# @api private
|
929
|
-
def create_group(
|
994
|
+
def create_group(region_or_vpc, ports)
|
930
995
|
name = group_id(ports)
|
931
996
|
@logger.notify("aws-sdk: Creating group #{name} for ports #{ports.to_s}")
|
932
|
-
|
933
|
-
|
997
|
+
cl = region_or_vpc.is_a?(String) ? client(region_or_vpc) : client
|
998
|
+
|
999
|
+
group = cl.create_security_group(
|
1000
|
+
:group_name => name,
|
1001
|
+
:description => "Custom Beaker security group for #{ports.to_a}"
|
1002
|
+
)
|
934
1003
|
|
935
1004
|
unless ports.is_a? Set
|
936
1005
|
ports = Set.new(ports)
|
937
1006
|
end
|
938
1007
|
|
939
1008
|
ports.each do |port|
|
940
|
-
|
1009
|
+
cl.authorize_security_group_ingress(
|
1010
|
+
:cidr_ip => '0.0.0.0/0',
|
1011
|
+
:ip_protocol => 'tcp',
|
1012
|
+
:from_port => port,
|
1013
|
+
:to_port => port,
|
1014
|
+
:group_id => group.group_id,
|
1015
|
+
)
|
941
1016
|
end
|
942
1017
|
|
943
1018
|
group
|
@@ -948,32 +1023,27 @@ module Beaker
|
|
948
1023
|
# @return [Hash<Symbol, String>] AWS credentials
|
949
1024
|
# @api private
|
950
1025
|
def load_credentials
|
951
|
-
return load_env_credentials
|
1026
|
+
return load_env_credentials if load_env_credentials.set?
|
952
1027
|
load_fog_credentials(@options[:dot_fog])
|
953
1028
|
end
|
954
1029
|
|
955
1030
|
# Return AWS credentials loaded from environment variables
|
956
1031
|
#
|
957
1032
|
# @param prefix [String] environment variable prefix
|
958
|
-
# @return [
|
1033
|
+
# @return [Aws::Credentials] ec2 credentials
|
959
1034
|
# @api private
|
960
1035
|
def load_env_credentials(prefix='AWS')
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
{
|
965
|
-
|
966
|
-
:secret_key => provider.secret_access_key,
|
967
|
-
:session_token => provider.session_token,
|
968
|
-
}
|
969
|
-
else
|
970
|
-
{}
|
971
|
-
end
|
1036
|
+
Aws::Credentials.new(
|
1037
|
+
ENV["#{prefix}_ACCESS_KEY_ID"],
|
1038
|
+
ENV["#{prefix}_SECRET_ACCESS_KEY"],
|
1039
|
+
ENV["#{prefix}_SESSION_TOKEN"]
|
1040
|
+
)
|
972
1041
|
end
|
1042
|
+
|
973
1043
|
# Return a hash containing the fog credentials for EC2
|
974
1044
|
#
|
975
1045
|
# @param dot_fog [String] dot fog path
|
976
|
-
# @return [
|
1046
|
+
# @return [Aws::Credentials] ec2 credentials
|
977
1047
|
# @api private
|
978
1048
|
def load_fog_credentials(dot_fog = '.fog')
|
979
1049
|
fog = YAML.load_file( dot_fog )
|
@@ -982,11 +1052,11 @@ module Beaker
|
|
982
1052
|
raise "You must specify an aws_access_key_id in your .fog file (#{dot_fog}) for ec2 instances!" unless default[:aws_access_key_id]
|
983
1053
|
raise "You must specify an aws_secret_access_key in your .fog file (#{dot_fog}) for ec2 instances!" unless default[:aws_secret_access_key]
|
984
1054
|
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
1055
|
+
Aws::Credentials.new(
|
1056
|
+
default[:aws_access_key_id],
|
1057
|
+
default[:aws_secret_access_key],
|
1058
|
+
default[:aws_session_token]
|
1059
|
+
)
|
990
1060
|
end
|
991
1061
|
|
992
1062
|
# Adds port 8143 to host[:additional_ports]
|