beaker 3.20.0 → 3.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +8 -8
  2. data/beaker.gemspec +4 -5
  3. data/docs/how_to/hypervisors/README.md +7 -3
  4. data/docs/tutorials/quick_start_rake_tasks.md +1 -1
  5. data/lib/beaker/command.rb +1 -1
  6. data/lib/beaker/host.rb +6 -4
  7. data/lib/beaker/host/windows/exec.rb +10 -0
  8. data/lib/beaker/host/windows/pkg.rb +1 -29
  9. data/lib/beaker/host_prebuilt_steps.rb +1 -0
  10. data/lib/beaker/hypervisor.rb +26 -37
  11. data/lib/beaker/ssh_connection.rb +8 -15
  12. data/lib/beaker/version.rb +1 -1
  13. data/spec/beaker/dsl/install_utils/windows_utils_spec.rb +1 -1
  14. data/spec/beaker/host/windows/exec_spec.rb +18 -0
  15. data/spec/beaker/host/windows/pkg_spec.rb +0 -7
  16. data/spec/beaker/host_prebuilt_steps_spec.rb +1 -0
  17. data/spec/beaker/host_spec.rb +31 -40
  18. data/spec/beaker/hypervisor/hypervisor_spec.rb +20 -34
  19. data/spec/beaker/ssh_connection_spec.rb +18 -19
  20. data/spec/spec_helper.rb +0 -1
  21. metadata +23 -57
  22. data/docs/how_to/hypervisors/aws.md +0 -149
  23. data/docs/how_to/hypervisors/ec2.md +0 -81
  24. data/docs/how_to/hypervisors/google_compute_engine.md +0 -41
  25. data/docs/how_to/hypervisors/vagrant.md +0 -165
  26. data/docs/how_to/hypervisors/vagrant_hosts_file_examples.md +0 -60
  27. data/docs/how_to/hypervisors/vagrant_libvirt.md +0 -58
  28. data/docs/how_to/hypervisors/vmware_fusion.md +0 -36
  29. data/docs/how_to/hypervisors/vsphere.md +0 -54
  30. data/lib/beaker/hypervisor/aws_sdk.rb +0 -989
  31. data/lib/beaker/hypervisor/ec2_helper.rb +0 -41
  32. data/lib/beaker/hypervisor/fusion.rb +0 -65
  33. data/lib/beaker/hypervisor/google_compute.rb +0 -164
  34. data/lib/beaker/hypervisor/google_compute_helper.rb +0 -577
  35. data/lib/beaker/hypervisor/vagrant.rb +0 -286
  36. data/lib/beaker/hypervisor/vagrant_custom.rb +0 -11
  37. data/lib/beaker/hypervisor/vagrant_fusion.rb +0 -17
  38. data/lib/beaker/hypervisor/vagrant_libvirt.rb +0 -41
  39. data/lib/beaker/hypervisor/vagrant_parallels.rb +0 -18
  40. data/lib/beaker/hypervisor/vagrant_virtualbox.rb +0 -76
  41. data/lib/beaker/hypervisor/vagrant_workstation.rb +0 -13
  42. data/lib/beaker/hypervisor/vsphere.rb +0 -85
  43. data/lib/beaker/hypervisor/vsphere_helper.rb +0 -204
  44. data/spec/beaker/hypervisor/aws_sdk_spec.rb +0 -980
  45. data/spec/beaker/hypervisor/ec2_helper_spec.rb +0 -44
  46. data/spec/beaker/hypervisor/fusion_spec.rb +0 -41
  47. data/spec/beaker/hypervisor/vagrant_custom_spec.rb +0 -46
  48. data/spec/beaker/hypervisor/vagrant_fusion_spec.rb +0 -32
  49. data/spec/beaker/hypervisor/vagrant_libvirt_spec.rb +0 -61
  50. data/spec/beaker/hypervisor/vagrant_parallels_spec.rb +0 -44
  51. data/spec/beaker/hypervisor/vagrant_spec.rb +0 -479
  52. data/spec/beaker/hypervisor/vagrant_virtualbox_spec.rb +0 -44
  53. data/spec/beaker/hypervisor/vagrant_workstation_spec.rb +0 -32
  54. data/spec/beaker/hypervisor/vsphere_helper_spec.rb +0 -163
  55. data/spec/beaker/hypervisor/vsphere_spec.rb +0 -90
@@ -1,989 +0,0 @@
1
- require 'aws/ec2'
2
- require 'set'
3
- require 'zlib'
4
- require 'beaker/hypervisor/ec2_helper'
5
-
6
- module Beaker
7
- # This is an alternate EC2 driver that implements direct API access using
8
- # Amazon's AWS-SDK library: {http://aws.amazon.com/documentation/sdkforruby/ SDK For Ruby}
9
- #
10
- # It is built for full control, to reduce any other layers beyond the pure
11
- # vendor API.
12
- class AwsSdk < Beaker::Hypervisor
13
- ZOMBIE = 3 #anything older than 3 hours is considered a zombie
14
- PING_SECURITY_GROUP_NAME = 'beaker-ping'
15
-
16
- # Initialize AwsSdk hypervisor driver
17
- #
18
- # @param [Array<Beaker::Host>] hosts Array of Beaker::Host objects
19
- # @param [Hash<String, String>] options Options hash
20
- def initialize(hosts, options)
21
- @hosts = hosts
22
- @options = options
23
- @logger = options[:logger]
24
-
25
- # Get AWS credentials
26
- creds = load_credentials()
27
-
28
- config = {
29
- :access_key_id => creds[:access_key],
30
- :secret_access_key => creds[:secret_key],
31
- :logger => Logger.new($stdout),
32
- :log_level => :debug,
33
- :log_formatter => AWS::Core::LogFormatter.colored,
34
- :max_retries => 12,
35
- }
36
- AWS.config(config)
37
-
38
- @ec2 = AWS::EC2.new()
39
- end
40
-
41
- # Provision all hosts on EC2 using the AWS::EC2 API
42
- #
43
- # @return [void]
44
- def provision
45
- start_time = Time.now
46
-
47
- # Perform the main launch work
48
- launch_all_nodes()
49
-
50
- wait_for_status_netdev()
51
-
52
- # Add metadata tags to each instance
53
- add_tags()
54
-
55
- # Grab the ip addresses and dns from EC2 for each instance to use for ssh
56
- populate_dns()
57
-
58
- #enable root if user is not root
59
- enable_root_on_hosts()
60
-
61
- # Set the hostname for each box
62
- set_hostnames()
63
-
64
- # Configure /etc/hosts on each host
65
- configure_hosts()
66
-
67
- @logger.notify("aws-sdk: Provisioning complete in #{Time.now - start_time} seconds")
68
-
69
- nil #void
70
- end
71
-
72
- # Kill all instances.
73
- #
74
- # @param instances [Enumerable<EC2::Instance>]
75
- # @return [void]
76
- def kill_instances(instances)
77
- instances.each do |instance|
78
- if !instance.nil? and instance.exists?
79
- @logger.notify("aws-sdk: killing EC2 instance #{instance.id}")
80
- instance.terminate
81
- end
82
- end
83
- nil
84
- end
85
-
86
- # Cleanup all earlier provisioned hosts on EC2 using the AWS::EC2 library
87
- #
88
- # It goes without saying, but a #cleanup does nothing without a #provision
89
- # method call first.
90
- #
91
- # @return [void]
92
- def cleanup
93
- # Provisioning should have set the host 'instance' values.
94
- kill_instances(@hosts.map{|h| h['instance']}.select{|x| !x.nil?})
95
- delete_key_pair_all_regions()
96
- nil
97
- end
98
-
99
- # Print instances to the logger. Instances will be from all regions
100
- # associated with provided key name and limited by regex compared to
101
- # instance status. Defaults to running instances.
102
- #
103
- # @param [String] key The key_name to match for
104
- # @param [Regex] status The regular expression to match against the instance's status
105
- def log_instances(key = key_name, status = /running/)
106
- instances = []
107
- @ec2.regions.each do |region|
108
- @logger.debug "Reviewing: #{region.name}"
109
- @ec2.regions[region.name].instances.each do |instance|
110
- if (instance.key_name =~ /#{key}/) and (instance.status.to_s =~ status)
111
- instances << instance
112
- end
113
- end
114
- end
115
- output = ""
116
- instances.each do |instance|
117
- 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"
118
- end
119
- @logger.notify("aws-sdk: List instances (keyname: #{key})")
120
- @logger.notify("#{output}")
121
- end
122
-
123
- # Provided an id return an instance object.
124
- # Instance object will respond to methods described here: {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Instance.html AWS Instance Object}.
125
- # @param [String] id The id of the instance to return
126
- # @return [AWS::EC2::Instance] An AWS::EC2 instance object
127
- def instance_by_id(id)
128
- @ec2.instances[id]
129
- end
130
-
131
- # Return all instances currently on ec2.
132
- # @see AwsSdk#instance_by_id
133
- # @return [AWS::EC2::InstanceCollection] An array of AWS::EC2 instance objects
134
- def instances
135
- @ec2.instances
136
- end
137
-
138
- # Provided an id return a VPC object.
139
- # VPC object will respond to methods described here: {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/VPC.html AWS VPC Object}.
140
- # @param [String] id The id of the VPC to return
141
- # @return [AWS::EC2::VPC] An AWS::EC2 vpc object
142
- def vpc_by_id(id)
143
- @ec2.vpcs[id]
144
- end
145
-
146
- # Return all VPCs currently on ec2.
147
- # @see AwsSdk#vpc_by_id
148
- # @return [AWS::EC2::VPCCollection] An array of AWS::EC2 vpc objects
149
- def vpcs
150
- @ec2.vpcs
151
- end
152
-
153
- # Provided an id return a security group object
154
- # Security object will respond to methods described here: {http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/SecurityGroup.html AWS SecurityGroup Object}.
155
- # @param [String] id The id of the security group to return
156
- # @return [AWS::EC2::SecurityGroup] An AWS::EC2 security group object
157
- def security_group_by_id(id)
158
- @ec2.security_groups[id]
159
- end
160
-
161
- # Return all security groups currently on ec2.
162
- # @see AwsSdk#security_goup_by_id
163
- # @return [AWS::EC2::SecurityGroupCollection] An array of AWS::EC2 security group objects
164
- def security_groups
165
- @ec2.security_groups
166
- end
167
-
168
- # Shutdown and destroy ec2 instances idenfitied by key that have been alive
169
- # longer than ZOMBIE hours.
170
- #
171
- # @param [Integer] max_age The age in hours that a machine needs to be older than to be considered a zombie
172
- # @param [String] key The key_name to match for
173
- def kill_zombies(max_age = ZOMBIE, key = key_name)
174
- @logger.notify("aws-sdk: Kill Zombies! (keyname: #{key}, age: #{max_age} hrs)")
175
- #examine all available regions
176
- kill_count = 0
177
- time_now = Time.now.getgm #ec2 uses GM time
178
- @ec2.regions.each do |region|
179
- @logger.debug "Reviewing: #{region.name}"
180
- # Note: don't use instances.each here as that funtion doesn't allow proper rescue from error states
181
- instances = @ec2.regions[region.name].instances
182
- instances.each do |instance|
183
- begin
184
- if (instance.key_name =~ /#{key}/)
185
- @logger.debug "Examining #{instance.id} (keyname: #{instance.key_name}, launch time: #{instance.launch_time}, status: #{instance.status})"
186
- if ((time_now - instance.launch_time) > max_age*60*60) and instance.status.to_s !~ /terminated/
187
- @logger.debug "Kill! #{instance.id}: #{instance.key_name} (Current status: #{instance.status})"
188
- instance.terminate()
189
- kill_count += 1
190
- end
191
- end
192
- rescue AWS::Core::Resource::NotFound, AWS::EC2::Errors => e
193
- @logger.debug "Failed to remove instance: #{instance.id}, #{e}"
194
- end
195
- end
196
- end
197
- delete_key_pair_all_regions(key_name_prefix)
198
-
199
- @logger.notify "#{key}: Killed #{kill_count} instance(s)"
200
- end
201
-
202
- # Destroy any volumes marked 'available', INCLUDING THOSE YOU DON'T OWN! Use with care.
203
- def kill_zombie_volumes
204
- # Occasionaly, tearing down ec2 instances leaves orphaned EBS volumes behind -- these stack up quickly.
205
- # This simply looks for EBS volumes that are not in use
206
- # Note: don't use volumes.each here as that funtion doesn't allow proper rescue from error states
207
- @logger.notify("aws-sdk: Kill Zombie Volumes!")
208
- volume_count = 0
209
- @ec2.regions.each do |region|
210
- @logger.debug "Reviewing: #{region.name}"
211
- volumes = @ec2.regions[region.name].volumes.map { |vol| vol.id }
212
- volumes.each do |vol|
213
- begin
214
- vol = @ec2.regions[region.name].volumes[vol]
215
- if ( vol.status.to_s =~ /available/ )
216
- @logger.debug "Tear down available volume: #{vol.id}"
217
- vol.delete()
218
- volume_count += 1
219
- end
220
- rescue AWS::EC2::Errors::InvalidVolume::NotFound => e
221
- @logger.debug "Failed to remove volume: #{vol.id}, #{e}"
222
- end
223
- end
224
- end
225
- @logger.notify "Freed #{volume_count} volume(s)"
226
-
227
- end
228
-
229
- # Create an EC2 instance for host, tag it, and return it.
230
- #
231
- # @return [void]
232
- # @api private
233
- def create_instance(host, ami_spec, subnet_id)
234
- amitype = host['vmname'] || host['platform']
235
- amisize = host['amisize'] || 'm1.small'
236
- vpc_id = host['vpc_id'] || @options['vpc_id'] || nil
237
-
238
- if vpc_id and !subnet_id
239
- raise RuntimeError, "A subnet_id must be provided with a vpc_id"
240
- end
241
-
242
- # Use snapshot provided for this host
243
- image_type = host['snapshot']
244
- if not image_type
245
- raise RuntimeError, "No snapshot/image_type provided for EC2 provisioning"
246
- end
247
- ami = ami_spec[amitype]
248
- ami_region = ami[:region]
249
-
250
- # Main region object for ec2 operations
251
- region = @ec2.regions[ami_region]
252
-
253
- # If we haven't defined a vpc_id then we use the default vpc for the provided region
254
- if !vpc_id
255
- @logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault")
256
- filtered_vpcs = region.client.describe_vpcs(:filters => [{:name => 'isDefault', :values => ['true']}])
257
- if !filtered_vpcs[:vpc_set].empty?
258
- vpc_id = filtered_vpcs[:vpc_set].first[:vpc_id]
259
- else #there's no default vpc, use nil
260
- vpc_id = nil
261
- end
262
- end
263
-
264
- # Grab the vpc object based upon provided id
265
- vpc = vpc_id ? region.vpcs[vpc_id] : nil
266
-
267
- # Grab image object
268
- image_id = ami[:image][image_type.to_sym]
269
- @logger.notify("aws-sdk: Checking image #{image_id} exists and getting its root device")
270
- image = region.images[image_id]
271
- if image.nil? and not image.exists?
272
- raise RuntimeError, "Image not found: #{image_id}"
273
- end
274
-
275
- @logger.notify("Image Storage Type: #{image.root_device_type}")
276
-
277
- # Transform the images block_device_mappings output into a format
278
- # ready for a create.
279
- block_device_mappings = []
280
- if image.root_device_type == :ebs
281
- orig_bdm = image.block_device_mappings()
282
- @logger.notify("aws-sdk: Image block_device_mappings: #{orig_bdm.to_hash}")
283
- orig_bdm.each do |device_name, rest|
284
- block_device_mappings << {
285
- :device_name => device_name,
286
- :ebs => {
287
- # Change the default size of the root volume.
288
- :volume_size => host['volume_size'] || rest[:volume_size],
289
- # This is required to override the images default for
290
- # delete_on_termination, forcing all volumes to be deleted once the
291
- # instance is terminated.
292
- :delete_on_termination => true,
293
- }
294
- }
295
- end
296
- end
297
-
298
- security_group = ensure_group(vpc || region, Beaker::EC2Helper.amiports(host))
299
- #check if ping is enabled
300
- ping_security_group = ensure_ping_group(vpc || region)
301
-
302
- msg = "aws-sdk: launching %p on %p using %p/%p%s" %
303
- [host.name, amitype, amisize, image_type,
304
- subnet_id ? ("in %p" % subnet_id) : '']
305
- @logger.notify(msg)
306
- config = {
307
- :count => 1,
308
- :image_id => image_id,
309
- :monitoring_enabled => true,
310
- :key_pair => ensure_key_pair(region),
311
- :security_groups => [security_group, ping_security_group],
312
- :instance_type => amisize,
313
- :disable_api_termination => false,
314
- :instance_initiated_shutdown_behavior => "terminate",
315
- :subnet => subnet_id,
316
- }
317
- config[:block_device_mappings] = block_device_mappings if image.root_device_type == :ebs
318
- region.instances.create(config)
319
- end
320
-
321
- # For each host, create an EC2 instance in one of the specified
322
- # subnets and push it onto instances_created. Each subnet will be
323
- # tried at most once for each host, and more than one subnet may
324
- # be tried if capacity constraints are encountered. Each Hash in
325
- # instances_created will contain an :instance and :host value.
326
- #
327
- # @param hosts [Enumerable<Host>]
328
- # @param subnets [Enumerable<String>]
329
- # @param ami_spec [Hash]
330
- # @param instances_created Enumerable<Hash{Symbol=>EC2::Instance,Host}>
331
- # @return [void]
332
- # @api private
333
- def launch_nodes_on_some_subnet(hosts, subnets, ami_spec, instances_created)
334
- # Shuffle the subnets so we don't always hit the same one
335
- # first, and cycle though the subnets independently of the
336
- # host, so we stick with one that's working. Try each subnet
337
- # once per-host.
338
- if subnets.nil? or subnets.empty?
339
- return
340
- end
341
- subnet_i = 0
342
- shuffnets = subnets.shuffle
343
- hosts.each do |host|
344
- instance = nil
345
- shuffnets.length.times do
346
- begin
347
- subnet_id = shuffnets[subnet_i]
348
- instance = create_instance(host, ami_spec, subnet_id)
349
- instances_created.push({:instance => instance, :host => host})
350
- break
351
- rescue AWS::EC2::Errors::InsufficientInstanceCapacity => ex
352
- @logger.notify("aws-sdk: hit #{subnet_id} capacity limit; moving on")
353
- subnet_i = (subnet_i + 1) % shuffnets.length
354
- end
355
- end
356
- if instance.nil?
357
- raise RuntimeError, "unable to launch host in any requested subnet"
358
- end
359
- end
360
- end
361
-
362
- # Create EC2 instances for all hosts, tag them, and wait until
363
- # they're running. When a host provides a subnet_id, create the
364
- # instance in that subnet, otherwise prefer a CONFIG subnet_id.
365
- # If neither are set but there is a CONFIG subnet_ids list,
366
- # attempt to create the host in each specified subnet, which might
367
- # fail due to capacity constraints, for example. Specifying both
368
- # a CONFIG subnet_id and subnet_ids will provoke an error.
369
- #
370
- # @return [void]
371
- # @api private
372
- def launch_all_nodes
373
- @logger.notify("aws-sdk: launch all hosts in configuration")
374
- ami_spec = YAML.load_file(@options[:ec2_yaml])["AMI"]
375
- global_subnet_id = @options['subnet_id']
376
- global_subnets = @options['subnet_ids']
377
- if global_subnet_id and global_subnets
378
- raise RuntimeError, 'Config specifies both subnet_id and subnet_ids'
379
- end
380
- no_subnet_hosts = []
381
- specific_subnet_hosts = []
382
- some_subnet_hosts = []
383
- @hosts.each do |host|
384
- if global_subnet_id or host['subnet_id']
385
- specific_subnet_hosts.push(host)
386
- elsif global_subnets
387
- some_subnet_hosts.push(host)
388
- else
389
- no_subnet_hosts.push(host)
390
- end
391
- end
392
- instances = [] # Each element is {:instance => i, :host => h}
393
- begin
394
- @logger.notify("aws-sdk: launch instances not particular about subnet")
395
- launch_nodes_on_some_subnet(some_subnet_hosts, global_subnets, ami_spec,
396
- instances)
397
- @logger.notify("aws-sdk: launch instances requiring a specific subnet")
398
- specific_subnet_hosts.each do |host|
399
- subnet_id = host['subnet_id'] || global_subnet_id
400
- instance = create_instance(host, ami_spec, subnet_id)
401
- instances.push({:instance => instance, :host => host})
402
- end
403
- @logger.notify("aws-sdk: launch instances requiring no subnet")
404
- no_subnet_hosts.each do |host|
405
- instance = create_instance(host, ami_spec, nil)
406
- instances.push({:instance => instance, :host => host})
407
- end
408
- wait_for_status(:running, instances)
409
- rescue Exception => ex
410
- @logger.notify("aws-sdk: exception #{ex.class}: #{ex}")
411
- kill_instances(instances.map{|x| x[:instance]})
412
- raise ex
413
- end
414
- # At this point, all instances should be running since wait
415
- # either returns on success or throws an exception.
416
- if instances.empty?
417
- raise RuntimeError, "Didn't manage to launch any EC2 instances"
418
- end
419
- # Assign the now known running instances to their hosts.
420
- instances.each {|x| x[:host]['instance'] = x[:instance]}
421
- nil
422
- end
423
-
424
- # Wait until all instances reach the desired state. Each Hash in
425
- # instances must contain an :instance and :host value.
426
- #
427
- # @param status [Symbol] EC2 state to wait for, :running :stopped etc.
428
- # @param instances Enumerable<Hash{Symbol=>EC2::Instance,Host}>
429
- # @param block [Proc] more complex checks can be made by passing a
430
- # block in. This overrides the status parameter.
431
- # EC2::Instance objects from the hosts will be
432
- # yielded to the passed block
433
- # @return [void]
434
- # @api private
435
- def wait_for_status(status, instances, &block)
436
- # Wait for each node to reach status :running
437
- @logger.notify("aws-sdk: Waiting for all hosts to be #{status}")
438
- instances.each do |x|
439
- name = x[:name]
440
- instance = x[:instance]
441
- @logger.notify("aws-sdk: Wait for node #{name} to be #{status}")
442
- # Here we keep waiting for the machine state to reach ':running' with an
443
- # exponential backoff for each poll.
444
- # TODO: should probably be a in a shared method somewhere
445
- for tries in 1..10
446
- begin
447
- if block_given?
448
- test_result = yield instance
449
- else
450
- test_result = instance.status == status
451
- end
452
- if test_result
453
- # Always sleep, so the next command won't cause a throttle
454
- backoff_sleep(tries)
455
- break
456
- elsif tries == 10
457
- raise "Instance never reached state #{status}"
458
- end
459
- rescue AWS::EC2::Errors::InvalidInstanceID::NotFound => e
460
- @logger.debug("Instance #{name} not yet available (#{e})")
461
- end
462
- backoff_sleep(tries)
463
- end
464
- end
465
- end
466
-
467
- # Handles special checks needed for netdev platforms.
468
- #
469
- # @note if any host is an netdev one, these checks will happen once across all
470
- # of the hosts, and then we'll exit
471
- #
472
- # @return [void]
473
- # @api private
474
- def wait_for_status_netdev()
475
- @hosts.each do |host|
476
- if host['platform'] =~ /f5-|netscaler/
477
- wait_for_status(:running, @hosts)
478
-
479
- wait_for_status(nil, @hosts) do |instance|
480
- instance_status_collection = instance.client.describe_instance_status({:instance_ids => [instance.id]})
481
- first_instance = instance_status_collection[:instance_status_set].first
482
- first_instance[:system_status][:status] == "ok"
483
- end
484
-
485
- break
486
- end
487
- end
488
- end
489
-
490
- # Add metadata tags to all instances
491
- #
492
- # @return [void]
493
- # @api private
494
- def add_tags
495
- @hosts.each do |host|
496
- instance = host['instance']
497
-
498
- # Define tags for the instance
499
- @logger.notify("aws-sdk: Add tags for #{host.name}")
500
- instance.add_tag("jenkins_build_url", :value => @options[:jenkins_build_url])
501
- instance.add_tag("Name", :value => host.name)
502
- instance.add_tag("department", :value => @options[:department])
503
- instance.add_tag("project", :value => @options[:project])
504
- instance.add_tag("created_by", :value => @options[:created_by])
505
-
506
- host[:host_tags].each do |name, val|
507
- instance.add_tag(name.to_s, :value => val)
508
- end
509
- end
510
-
511
- nil
512
- end
513
-
514
- # Populate the hosts IP address from the EC2 dns_name
515
- #
516
- # @return [void]
517
- # @api private
518
- def populate_dns
519
- # Obtain the IP addresses and dns_name for each host
520
- @hosts.each do |host|
521
- @logger.notify("aws-sdk: Populate DNS for #{host.name}")
522
- instance = host['instance']
523
- host['ip'] = instance.ip_address ? instance.ip_address : instance.private_ip_address
524
- host['private_ip'] = instance.private_ip_address
525
- host['dns_name'] = instance.dns_name
526
- @logger.notify("aws-sdk: name: #{host.name} ip: #{host['ip']} private_ip: #{host['private_ip']} dns_name: #{instance.dns_name}")
527
- end
528
-
529
- nil
530
- end
531
-
532
- # Return a valid /etc/hosts line for a given host
533
- #
534
- # @param [Beaker::Host] host Beaker::Host object for generating /etc/hosts entry
535
- # @param [Symbol] interface Symbol identifies which ip should be used for host
536
- # @return [String] formatted hosts entry for host
537
- # @api private
538
- def etc_hosts_entry(host, interface = :ip)
539
- name = host.name
540
- domain = get_domain_name(host)
541
- ip = host[interface.to_s]
542
- "#{ip}\t#{name} #{name}.#{domain} #{host['dns_name']}\n"
543
- end
544
-
545
- # Configure /etc/hosts for each node
546
- #
547
- # @note f5 hosts are skipped since this isn't a valid step there
548
- #
549
- # @return [void]
550
- # @api private
551
- def configure_hosts
552
- non_netdev_hosts = @hosts.select{ |h| !(h['platform'] =~ /f5-|netscaler/) }
553
- non_netdev_hosts.each do |host|
554
- host_entries = non_netdev_hosts.map do |h|
555
- h == host ? etc_hosts_entry(h, :private_ip) : etc_hosts_entry(h)
556
- end
557
- host_entries.unshift "127.0.0.1\tlocalhost localhost.localdomain\n"
558
- set_etc_hosts(host, host_entries.join(''))
559
- end
560
- nil
561
- end
562
-
563
- # Enables root for instances with custom username like ubuntu-amis
564
- #
565
- # @return [void]
566
- # @api private
567
- def enable_root_on_hosts
568
- @hosts.each do |host|
569
- enable_root(host)
570
- end
571
- end
572
-
573
- # Enables root access for a host when username is not root
574
- #
575
- # @return [void]
576
- # @api private
577
- def enable_root(host)
578
- if host['user'] != 'root'
579
- if host['platform'] =~ /f5-/
580
- enable_root_f5(host)
581
- elsif host['platform'] =~ /netscaler/
582
- enable_root_netscaler(host)
583
- else
584
- copy_ssh_to_root(host, @options)
585
- enable_root_login(host, @options)
586
- host['user'] = 'root'
587
- end
588
- host.close
589
- end
590
- end
591
-
592
- # Enables root access for a host on an f5 platform
593
- # @note This method does not support other platforms
594
- #
595
- # @return nil
596
- # @api private
597
- def enable_root_f5(host)
598
- for tries in 1..10
599
- begin
600
- #This command is problematic as the F5 is not always done loading
601
- if host.exec(Command.new("modify sys db systemauth.disablerootlogin value false"), :acceptable_exit_codes => [0,1]).exit_code == 0 \
602
- and host.exec(Command.new("modify sys global-settings gui-setup disabled"), :acceptable_exit_codes => [0,1]).exit_code == 0 \
603
- and host.exec(Command.new("save sys config"), :acceptable_exit_codes => [0,1]).exit_code == 0
604
- backoff_sleep(tries)
605
- break
606
- elsif tries == 10
607
- raise "Instance was unable to be configured"
608
- end
609
- rescue Beaker::Host::CommandFailure => e
610
- @logger.debug("Instance not yet configured (#{e})")
611
- end
612
- backoff_sleep(tries)
613
- end
614
- host['user'] = 'root'
615
- host.close
616
- sha256 = Digest::SHA256.new
617
- password = sha256.hexdigest((1..50).map{(rand(86)+40).chr}.join.gsub(/\\/,'\&\&'))
618
- host['ssh'] = {:password => password}
619
- host.exec(Command.new("echo -e '#{password}\\n#{password}' | tmsh modify auth password admin"))
620
- @logger.notify("f5: Configured admin password to be #{password}")
621
- end
622
-
623
- # Enables root access for a host on an netscaler platform
624
- # @note This method does not support other platforms
625
- #
626
- # @return nil
627
- # @api private
628
- def enable_root_netscaler(host)
629
- host['ssh'] = {:password => host['instance'].id}
630
- @logger.notify("netscaler: nsroot password is #{host['instance'].id}")
631
- end
632
-
633
- # Set the :vmhostname for each host object to be the dns_name, which is accessible
634
- # publicly. Then configure each ec2 machine to that dns_name, so that when facter
635
- # is installed the facts for hostname and domain match the dns_name.
636
- #
637
- # if :use_beaker_hostnames: is true, set the :vmhostname and hostname of each ec2
638
- # machine to the host[:name] from the beaker hosts file.
639
- #
640
- # @return [@hosts]
641
- # @api private
642
- def set_hostnames
643
- if @options[:use_beaker_hostnames]
644
- @hosts.each do |host|
645
- host[:vmhostname] = host[:name]
646
- if host['platform'] =~ /el-7/
647
- # on el-7 hosts, the hostname command doesn't "stick" randomly
648
- host.exec(Command.new("hostnamectl set-hostname #{host.name}"))
649
- else
650
- next if host['platform'] =~ /netscaler/
651
- host.exec(Command.new("hostname #{host.name}"))
652
- if host['vmname'] =~ /^amazon/
653
- # Amazon Linux requires this to preserve host name changes across reboots.
654
- # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-hostname.html
655
- # Also note that without an elastic ip set, while this will
656
- # preserve the hostname across a full shutdown/startup of the vm
657
- # (as opposed to a reboot) -- the ip address will have changed.
658
- host.exec(Command.new("sed -ie '/^HOSTNAME/ s/=.*/=#{host.name}/' /etc/sysconfig/network"))
659
- end
660
- end
661
- end
662
- else
663
- @hosts.each do |host|
664
- host[:vmhostname] = host[:dns_name]
665
- if host['platform'] =~ /el-7/
666
- # on el-7 hosts, the hostname command doesn't "stick" randomly
667
- host.exec(Command.new("hostnamectl set-hostname #{host.hostname}"))
668
- else
669
- next if host['platform'] =~ /netscaler/
670
- host.exec(Command.new("hostname #{host.hostname}"))
671
- if host['vmname'] =~ /^amazon/
672
- # See note above
673
- host.exec(Command.new("sed -ie '/^HOSTNAME/ s/=.*/=#{host.hostname}/' /etc/sysconfig/network"))
674
- end
675
- end
676
- end
677
- end
678
- end
679
-
680
- # Calculates and waits a back-off period based on the number of tries
681
- #
682
- # Logs each backupoff time and retry value to the console.
683
- #
684
- # @param tries [Number] number of tries to calculate back-off period
685
- # @return [void]
686
- # @api private
687
- def backoff_sleep(tries)
688
- # Exponential with some randomization
689
- sleep_time = 2 ** tries
690
- @logger.notify("aws-sdk: Sleeping #{sleep_time} seconds for attempt #{tries}.")
691
- sleep sleep_time
692
- nil
693
- end
694
-
695
- # Retrieve the public key locally from the executing users ~/.ssh directory
696
- #
697
- # @return [String] contents of public key
698
- # @api private
699
- def public_key
700
- keys = Array(@options[:ssh][:keys])
701
- keys << '~/.ssh/id_rsa'
702
- keys << '~/.ssh/id_dsa'
703
- key_file = nil
704
- keys.each do |key|
705
- key_filename = File.expand_path(key + '.pub')
706
- key_file = key_filename if File.exists?(key_filename)
707
- end
708
-
709
- if key_file
710
- @logger.debug("Using public key: #{key_file}")
711
- else
712
- raise RuntimeError, "Expected to find a public key, but couldn't in #{keys}"
713
- end
714
- File.read(key_file)
715
- end
716
-
717
- # Generate a key prefix for key pair names
718
- #
719
- # @note This is the part of the key that will stay static between Beaker
720
- # runs on the same host.
721
- #
722
- # @return [String] Beaker key pair name based on sanitized hostname
723
- def key_name_prefix
724
- safe_hostname = Socket.gethostname.gsub('.', '-')
725
- "Beaker-#{local_user}-#{safe_hostname}"
726
- end
727
-
728
- # Generate a reusable key name from the local hosts hostname
729
- #
730
- # @return [String] safe key name for current host
731
- # @api private
732
- def key_name
733
- "#{key_name_prefix}-#{@options[:aws_keyname_modifier]}-#{@options[:timestamp].strftime("%F_%H_%M_%S_%N")}"
734
- end
735
-
736
- # Returns the local user running this tool
737
- #
738
- # @return [String] username of local user
739
- # @api private
740
- def local_user
741
- ENV['USER']
742
- end
743
-
744
- # Creates the KeyPair for this test run
745
- #
746
- # @param region [AWS::EC2::Region] region to create the key pair in
747
- # @return [AWS::EC2::KeyPair] created key_pair
748
- # @api private
749
- def ensure_key_pair(region)
750
- pair_name = key_name()
751
- delete_key_pair(region, pair_name)
752
- create_new_key_pair(region, pair_name)
753
- end
754
-
755
- # Deletes key pairs from all regions
756
- #
757
- # @param [String] keypair_name_filter if given, will get all keypairs that match
758
- # a simple {::String#start_with?} filter. If no filter is given, the basic key
759
- # name returned by {#key_name} will be used.
760
- #
761
- # @return nil
762
- # @api private
763
- def delete_key_pair_all_regions(keypair_name_filter=nil)
764
- region_keypairs_hash = my_key_pairs(keypair_name_filter)
765
- region_keypairs_hash.each_pair do |region, keypair_name_array|
766
- keypair_name_array.each do |keypair_name|
767
- delete_key_pair(region, keypair_name)
768
- end
769
- end
770
- end
771
-
772
- # Gets the Beaker user's keypairs by region
773
- #
774
- # @param [String] name_filter if given, will get all keypairs that match
775
- # a simple {::String#start_with?} filter. If no filter is given, the basic key
776
- # name returned by {#key_name} will be used.
777
- #
778
- # @return [Hash{AWS::EC2::Region=>Array[String]}] a hash of region instance to
779
- # an array of the keypair names that match for the filter
780
- # @api private
781
- def my_key_pairs(name_filter=nil)
782
- keypairs_by_region = {}
783
- keyname_default = key_name()
784
- keyname_filtered = "#{name_filter}-*"
785
- @ec2.regions.each do |region|
786
- if name_filter
787
- aws_name_filter = keyname_filtered
788
- else
789
- aws_name_filter = keyname_default
790
- end
791
- keypair_collection = region.key_pairs.filter('key-name', aws_name_filter)
792
- keypair_collection.each do |keypair|
793
- keypairs_by_region[region] ||= []
794
- keypairs_by_region[region] << keypair.name
795
- end
796
- end
797
- keypairs_by_region
798
- end
799
-
800
- # Deletes a given key pair
801
- #
802
- # @param [AWS::EC2::Region] region the region the key belongs to
803
- # @param [String] pair_name the name of the key to be deleted
804
- #
805
- # @api private
806
- def delete_key_pair(region, pair_name)
807
- kp = region.key_pairs[pair_name]
808
- if kp.exists?
809
- @logger.debug("aws-sdk: delete key pair in region: #{region.name}")
810
- kp.delete()
811
- end
812
- end
813
-
814
- # Create a new key pair for a given Beaker run
815
- #
816
- # @param [AWS::EC2::Region] region the region the key pair will be imported into
817
- # @param [String] pair_name the name of the key to be created
818
- #
819
- # @return [AWS::EC2::KeyPair] key pair created
820
- # @raise [RuntimeError] raised if AWS keypair not created
821
- def create_new_key_pair(region, pair_name)
822
- @logger.debug("aws-sdk: importing new key pair: #{pair_name}")
823
- ssh_string = public_key()
824
- region.key_pairs.import(pair_name, ssh_string)
825
- kp = region.key_pairs[pair_name]
826
-
827
- exists = false
828
- for tries in 1..5
829
- if kp.exists?
830
- exists = true
831
- break
832
- end
833
- @logger.debug("AWS key pair doesn't appear to exist yet, sleeping before retry ")
834
- backoff_sleep(tries)
835
- end
836
-
837
- if exists
838
- @logger.debug("aws-sdk: key pair #{pair_name} imported")
839
- kp
840
- else
841
- raise RuntimeError, "AWS key pair #{pair_name} can not be queried, even after import"
842
- end
843
- end
844
-
845
- # Return a reproducable security group identifier based on input ports
846
- #
847
- # @param ports [Array<Number>] array of port numbers
848
- # @return [String] group identifier
849
- # @api private
850
- def group_id(ports)
851
- if ports.nil? or ports.empty?
852
- raise ArgumentError, "Ports list cannot be nil or empty"
853
- end
854
-
855
- unless ports.is_a? Set
856
- ports = Set.new(ports)
857
- end
858
-
859
- # Lolwut, #hash is inconsistent between ruby processes
860
- "Beaker-#{Zlib.crc32(ports.inspect)}"
861
- end
862
-
863
- # Return an existing group, or create new one
864
- #
865
- # Accepts a VPC as input for checking & creation.
866
- #
867
- # @param vpc [AWS::EC2::VPC] the AWS vpc control object
868
- # @return [AWS::EC2::SecurityGroup] created security group
869
- # @api private
870
- def ensure_ping_group(vpc)
871
- @logger.notify("aws-sdk: Ensure security group exists that enables ping, create if not")
872
-
873
- group = vpc.security_groups.filter('group-name', PING_SECURITY_GROUP_NAME).first
874
-
875
- if group.nil?
876
- group = create_ping_group(vpc)
877
- end
878
-
879
- group
880
- end
881
-
882
- # Return an existing group, or create new one
883
- #
884
- # Accepts a VPC as input for checking & creation.
885
- #
886
- # @param vpc [AWS::EC2::VPC] the AWS vpc control object
887
- # @param ports [Array<Number>] an array of port numbers
888
- # @return [AWS::EC2::SecurityGroup] created security group
889
- # @api private
890
- def ensure_group(vpc, ports)
891
- @logger.notify("aws-sdk: Ensure security group exists for ports #{ports.to_s}, create if not")
892
- name = group_id(ports)
893
-
894
- group = vpc.security_groups.filter('group-name', name).first
895
-
896
- if group.nil?
897
- group = create_group(vpc, ports)
898
- end
899
-
900
- group
901
- end
902
-
903
- # Create a new ping enabled security group
904
- #
905
- # Accepts a region or VPC for group creation.
906
- #
907
- # @param rv [AWS::EC2::Region, AWS::EC2::VPC] the AWS region or vpc control object
908
- # @return [AWS::EC2::SecurityGroup] created security group
909
- # @api private
910
- def create_ping_group(rv)
911
- @logger.notify("aws-sdk: Creating group #{PING_SECURITY_GROUP_NAME}")
912
- group = rv.security_groups.create(PING_SECURITY_GROUP_NAME,
913
- :description => "Custom Beaker security group to enable ping")
914
-
915
- group.allow_ping
916
-
917
- group
918
- end
919
-
920
- # Create a new security group
921
- #
922
- # Accepts a region or VPC for group creation.
923
- #
924
- # @param rv [AWS::EC2::Region, AWS::EC2::VPC] the AWS region or vpc control object
925
- # @param ports [Array<Number>] an array of port numbers
926
- # @return [AWS::EC2::SecurityGroup] created security group
927
- # @api private
928
- def create_group(rv, ports)
929
- name = group_id(ports)
930
- @logger.notify("aws-sdk: Creating group #{name} for ports #{ports.to_s}")
931
- group = rv.security_groups.create(name,
932
- :description => "Custom Beaker security group for #{ports.to_a}")
933
-
934
- unless ports.is_a? Set
935
- ports = Set.new(ports)
936
- end
937
-
938
- ports.each do |port|
939
- group.authorize_ingress(:tcp, port)
940
- end
941
-
942
- group
943
- end
944
-
945
- # Return a hash containing AWS credentials
946
- #
947
- # @return [Hash<Symbol, String>] AWS credentials
948
- # @api private
949
- def load_credentials
950
- return load_env_credentials unless load_env_credentials.empty?
951
- load_fog_credentials(@options[:dot_fog])
952
- end
953
-
954
- # Return AWS credentials loaded from environment variables
955
- #
956
- # @param prefix [String] environment variable prefix
957
- # @return [Hash<Symbol, String>] ec2 credentials
958
- # @api private
959
- def load_env_credentials(prefix='AWS')
960
- provider = AWS::Core::CredentialProviders::ENVProvider.new prefix
961
-
962
- if provider.set?
963
- {
964
- :access_key => provider.access_key_id,
965
- :secret_key => provider.secret_access_key,
966
- }
967
- else
968
- {}
969
- end
970
- end
971
- # Return a hash containing the fog credentials for EC2
972
- #
973
- # @param dot_fog [String] dot fog path
974
- # @return [Hash<Symbol, String>] ec2 credentials
975
- # @api private
976
- def load_fog_credentials(dot_fog = '.fog')
977
- fog = YAML.load_file( dot_fog )
978
- default = fog[:default]
979
-
980
- raise "You must specify an aws_access_key_id in your .fog file (#{dot_fog}) for ec2 instances!" unless default[:aws_access_key_id]
981
- raise "You must specify an aws_secret_access_key in your .fog file (#{dot_fog}) for ec2 instances!" unless default[:aws_secret_access_key]
982
-
983
- {
984
- :access_key => default[:aws_access_key_id],
985
- :secret_key => default[:aws_secret_access_key],
986
- }
987
- end
988
- end
989
- end