beaker-aws 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,15 @@
1
- require 'aws/ec2'
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() : Hash.new
35
+ creds = options[:use_fog_credentials] ? load_credentials() : nil
27
36
 
28
37
  config = {
29
- :access_key_id => creds[:access_key],
30
- :secret_access_key => creds[:secret_key],
31
- :session_token => creds[:session_token],
32
- :logger => Logger.new($stdout),
33
- :log_level => :debug,
34
- :log_formatter => AWS::Core::LogFormatter.colored,
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
- AWS.config(config)
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
- # Provision all hosts on EC2 using the AWS::EC2 API
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.each do |instance|
80
- if !instance.nil? and instance.exists?
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 AWS::EC2 library
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
- @ec2.regions.each do |region|
110
- @logger.debug "Reviewing: #{region.name}"
111
- @ec2.regions[region.name].instances.each do |instance|
112
- if (instance.key_name =~ /#{key}/) and (instance.status.to_s =~ status)
113
- instances << instance
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
- output << "#{instance.id} keyname: #{instance.key_name}, dns name: #{instance.dns_name}, private ip: #{instance.private_ip_address}, ip: #{instance.ip_address}, launch time #{instance.launch_time}, status: #{instance.status}\n"
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 [AWS::EC2::Instance] An AWS::EC2 instance object
155
+ # @return [Aws::EC2::Types::Instance] An Aws::EC2 instance object
129
156
  def instance_by_id(id)
130
- @ec2.instances[id]
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 [AWS::EC2::InstanceCollection] An array of AWS::EC2 instance objects
162
+ # @return [Array<Aws::Ec2::Types::Instance>] An array of Aws::EC2 instance objects
136
163
  def instances
137
- @ec2.instances
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 [AWS::EC2::VPC] An AWS::EC2 vpc object
170
+ # @return [Aws::EC2::Types::Vpc] An Aws::EC2 vpc object
144
171
  def vpc_by_id(id)
145
- @ec2.vpcs[id]
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 [AWS::EC2::VPCCollection] An array of AWS::EC2 vpc objects
177
+ # @return [Array<Aws::EC2::Types::Vpc>] An array of Aws::EC2 vpc objects
151
178
  def vpcs
152
- @ec2.vpcs
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 [AWS::EC2::SecurityGroup] An AWS::EC2 security group object
185
+ # @return [Aws::EC2::Types::SecurityGroup] An Aws::EC2 security group object
159
186
  def security_group_by_id(id)
160
- @ec2.security_groups[id]
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 [AWS::EC2::SecurityGroupCollection] An array of AWS::EC2 security group objects
192
+ # @return [Array<Aws::EC2::Types::SecurityGroup>] An array of Aws::EC2 security group objects
166
193
  def security_groups
167
- @ec2.security_groups
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
- #examine all available regions
178
- kill_count = 0
204
+
205
+ instances_to_kill = []
206
+
179
207
  time_now = Time.now.getgm #ec2 uses GM time
180
- @ec2.regions.each do |region|
181
- @logger.debug "Reviewing: #{region.name}"
182
- # Note: don't use instances.each here as that funtion doesn't allow proper rescue from error states
183
- instances = @ec2.regions[region.name].instances
184
- instances.each do |instance|
185
- begin
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.id} (keyname: #{instance.key_name}, launch time: #{instance.launch_time}, status: #{instance.status})"
188
- if ((time_now - instance.launch_time) > max_age*60*60) and instance.status.to_s !~ /terminated/
189
- @logger.debug "Kill! #{instance.id}: #{instance.key_name} (Current status: #{instance.status})"
190
- instance.terminate()
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 #{kill_count} instance(s)"
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
- @ec2.regions.each do |region|
212
- @logger.debug "Reviewing: #{region.name}"
213
- volumes = @ec2.regions[region.name].volumes.map { |vol| vol.id }
214
- volumes.each do |vol|
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
- vol = @ec2.regions[region.name].volumes[vol]
217
- if ( vol.status.to_s =~ /available/ )
218
- @logger.debug "Tear down available volume: #{vol.id}"
219
- vol.delete()
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 and !subnet_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
- if not image_type
247
- raise RuntimeError, "No snapshot/image_type provided for EC2 provisioning"
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 = @ec2.regions[ami_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
- if !vpc_id
257
- @logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault")
258
- filtered_vpcs = region.client.describe_vpcs(:filters => [{:name => 'isDefault', :values => ['true']}])
259
- if !filtered_vpcs[:vpc_set].empty?
260
- vpc_id = filtered_vpcs[:vpc_set].first[:vpc_id]
261
- else #there's no default vpc, use nil
262
- vpc_id = nil
263
- end
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.vpcs[vpc_id] : nil
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.images[image_id]
273
- if image.nil? and not image.exists?
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.to_hash}")
285
- orig_bdm.each do |device_name, rest|
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'] || rest[: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
- :count => 1,
310
- :image_id => image_id,
311
- :monitoring_enabled => true,
312
- :key_pair => ensure_key_pair(region),
313
- :security_groups => [security_group, ping_security_group],
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
- :subnet => subnet_id,
347
+ :subnet_id => subnet_id,
318
348
  }
319
349
  config[:block_device_mappings] = block_device_mappings if image.root_device_type == :ebs
320
- region.instances.create(config)
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 AWS::EC2::Errors::InsufficientInstanceCapacity => ex
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 status [Symbol] EC2 state to wait for, :running :stopped etc.
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
- def wait_for_status(status, instances, &block)
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 #{status}")
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 #{status}")
444
- # Here we keep waiting for the machine state to reach ':running' with an
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
- begin
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 instance
486
+ test_result = yield refreshed_instance
451
487
  else
452
- test_result = instance.status == status
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 #{status}"
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.id]})
483
- first_instance = instance_status_collection[:instance_status_set].first
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
- instance.add_tag("jenkins_build_url", :value => @options[:jenkins_build_url])
503
- instance.add_tag("Name", :value => host.name)
504
- instance.add_tag("department", :value => @options[:department])
505
- instance.add_tag("project", :value => @options[:project])
506
- instance.add_tag("created_by", :value => @options[:created_by])
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
- instance.add_tag(name.to_s, :value => val)
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.ip_address ? instance.ip_address : instance.private_ip_address
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.dns_name
528
- @logger.notify("aws-sdk: name: #{host.name} ip: #{host['ip']} private_ip: #{host['private_ip']} dns_name: #{instance.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'].id}
632
- @logger.notify("netscaler: nsroot password is #{host['instance'].id}")
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 [AWS::EC2::Region] region to create the key pair in
748
- # @return [AWS::EC2::KeyPair] created key_pair
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{AWS::EC2::Region=>Array[String]}] a hash of region instance to
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
- keyname_default = key_name()
785
- keyname_filtered = "#{name_filter}-*"
786
- @ec2.regions.each do |region|
787
- if name_filter
788
- aws_name_filter = keyname_filtered
789
- else
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 [AWS::EC2::Region] region the region the key belongs to
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.key_pairs[pair_name]
809
- if kp.exists?
810
- @logger.debug("aws-sdk: delete key pair in region: #{region.name}")
811
- kp.delete()
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 [AWS::EC2::Region] region the region the key pair will be imported into
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 [AWS::EC2::KeyPair] key pair created
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
- ssh_string = public_key()
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
- if exists
839
- @logger.debug("aws-sdk: key pair #{pair_name} imported")
840
- kp
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 [AWS::EC2::VPC] the AWS vpc control object
869
- # @return [AWS::EC2::SecurityGroup] created security group
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 = vpc.security_groups.filter('group-name', PING_SECURITY_GROUP_NAME).first
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 [AWS::EC2::VPC] the AWS vpc control object
934
+ # @param vpc [Aws::EC2::VPC] the AWS vpc control object
888
935
  # @param ports [Array<Number>] an array of port numbers
889
- # @return [AWS::EC2::SecurityGroup] created security group
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 = vpc.security_groups.filter('group-name', name).first
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 [AWS::EC2::Region, AWS::EC2::VPC] the AWS region or vpc control object
909
- # @return [AWS::EC2::SecurityGroup] created security group
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(rv)
963
+ def create_ping_group(region_or_vpc)
912
964
  @logger.notify("aws-sdk: Creating group #{PING_SECURITY_GROUP_NAME}")
913
- group = rv.security_groups.create(PING_SECURITY_GROUP_NAME,
914
- :description => "Custom Beaker security group to enable ping")
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
- group.allow_ping
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 [AWS::EC2::Region, AWS::EC2::VPC] the AWS region or vpc control object
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 [AWS::EC2::SecurityGroup] created security group
992
+ # @return [Aws::EC2::SecurityGroup] created security group
928
993
  # @api private
929
- def create_group(rv, ports)
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
- group = rv.security_groups.create(name,
933
- :description => "Custom Beaker security group for #{ports.to_a}")
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
- group.authorize_ingress(:tcp, port)
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 unless load_env_credentials.empty?
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 [Hash<Symbol, String>] ec2 credentials
1033
+ # @return [Aws::Credentials] ec2 credentials
959
1034
  # @api private
960
1035
  def load_env_credentials(prefix='AWS')
961
- provider = AWS::Core::CredentialProviders::ENVProvider.new prefix
962
-
963
- if provider.set?
964
- {
965
- :access_key => provider.access_key_id,
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 [Hash<Symbol, String>] ec2 credentials
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
- :access_key => default[:aws_access_key_id],
987
- :secret_key => default[:aws_secret_access_key],
988
- :session_token => default[:aws_session_token],
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]