beaker 1.9.1 → 1.10.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.
Files changed (36) hide show
  1. checksums.yaml +8 -8
  2. data/.simplecov +1 -0
  3. data/beaker.gemspec +2 -0
  4. data/lib/beaker/dsl/helpers.rb +15 -6
  5. data/lib/beaker/dsl/install_utils.rb +1 -1
  6. data/lib/beaker/host.rb +21 -2
  7. data/lib/beaker/host/aix/exec.rb +7 -0
  8. data/lib/beaker/host/unix.rb +4 -0
  9. data/lib/beaker/host/unix/exec.rb +8 -0
  10. data/lib/beaker/host/windows.rb +2 -0
  11. data/lib/beaker/host/windows/exec.rb +11 -0
  12. data/lib/beaker/host_prebuilt_steps.rb +7 -6
  13. data/lib/beaker/hypervisor.rb +6 -2
  14. data/lib/beaker/hypervisor/aws_sdk.rb +389 -0
  15. data/lib/beaker/hypervisor/blimper.rb +8 -23
  16. data/lib/beaker/hypervisor/ec2_helper.rb +28 -0
  17. data/lib/beaker/hypervisor/google_compute.rb +10 -8
  18. data/lib/beaker/hypervisor/google_compute_helper.rb +1 -1
  19. data/lib/beaker/hypervisor/vagrant.rb +8 -0
  20. data/lib/beaker/options/command_line_parser.rb +6 -0
  21. data/lib/beaker/options/parser.rb +1 -1
  22. data/lib/beaker/options/presets.rb +3 -0
  23. data/lib/beaker/ssh_connection.rb +2 -3
  24. data/lib/beaker/test_case.rb +2 -2
  25. data/lib/beaker/version.rb +1 -1
  26. data/spec/beaker/dsl/helpers_spec.rb +27 -5
  27. data/spec/beaker/dsl/install_utils_spec.rb +2 -2
  28. data/spec/beaker/host_prebuilt_steps_spec.rb +2 -2
  29. data/spec/beaker/hypervisor/aws_sdk_spec.rb +81 -0
  30. data/spec/beaker/hypervisor/blimper_spec.rb +0 -35
  31. data/spec/beaker/hypervisor/ec2_helper_spec.rb +23 -0
  32. data/spec/beaker/hypervisor/vagrant_spec.rb +22 -1
  33. data/spec/beaker/options/parser_spec.rb +4 -1
  34. data/spec/beaker/test_case_spec.rb +85 -1
  35. data/spec/mocks.rb +4 -0
  36. metadata +37 -2
@@ -1,29 +1,10 @@
1
1
  require 'blimpy'
2
2
  require 'yaml' unless defined?(YAML)
3
+ require 'beaker/hypervisor/ec2_helper'
3
4
 
4
- module Beaker
5
+ module Beaker
5
6
  class Blimper < Beaker::Hypervisor
6
7
 
7
- def amiports(host)
8
- roles = host['roles']
9
- ports = [22]
10
-
11
- if roles.include? 'database'
12
- ports << 8080
13
- ports << 8081
14
- end
15
-
16
- if roles.include? 'master'
17
- ports << 8140
18
- end
19
-
20
- if roles.include? 'dashboard'
21
- ports << 443
22
- end
23
-
24
- ports
25
- end
26
-
27
8
  def initialize(blimpy_hosts, options)
28
9
  @options = options
29
10
  @logger = options[:logger]
@@ -46,7 +27,7 @@ module Beaker
46
27
  ami = ami_spec[amitype]
47
28
  fleet.add(:aws) do |ship|
48
29
  ship.name = host.name
49
- ship.ports = amiports(host)
30
+ ship.ports = Beaker::EC2Helper.amiports(host['roles'])
50
31
  ship.image_id = ami[:image][image_type.to_sym]
51
32
  if not ship.image_id
52
33
  raise "No image_id found for host #{ship.name} (#{amitype}:#{amisize}) using snapshot/image_type #{image_type}"
@@ -54,7 +35,11 @@ module Beaker
54
35
  ship.flavor = amisize
55
36
  ship.region = ami[:region]
56
37
  ship.username = 'root'
57
- ship.tags = {:department => @options[:department], :project => @options[:project]}
38
+ ship.tags = {
39
+ :department => @options[:department],
40
+ :project => @options[:project],
41
+ :jenkins_build_url => @options[:jenkins_build_url],
42
+ }
58
43
  end
59
44
  @logger.debug "Added #{host.name} (#{amitype}:#{amisize}) using snapshot/image_type #{image_type} to blimpy fleet"
60
45
  end
@@ -0,0 +1,28 @@
1
+ module Beaker
2
+ class EC2Helper
3
+ # Return a list of open ports for testing based on a hosts role
4
+ #
5
+ # @todo horribly hard-coded
6
+ # @param [Array<String>] an array of roles
7
+ # @return [Array<Number>] array of port numbers
8
+ # @api private
9
+ def self.amiports(roles)
10
+ ports = [22]
11
+
12
+ if roles.include? 'database'
13
+ ports << 8080
14
+ ports << 8081
15
+ end
16
+
17
+ if roles.include? 'master'
18
+ ports << 8140
19
+ end
20
+
21
+ if roles.include? 'dashboard'
22
+ ports << 443
23
+ end
24
+
25
+ ports
26
+ end
27
+ end
28
+ end
@@ -45,7 +45,8 @@ module Beaker
45
45
 
46
46
 
47
47
  @hosts.each do |host|
48
- img = @gce_helper.get_latest_image(host[:platform], start, attempts)
48
+ gplatform = Platform.new(host[:image] || host[:platform])
49
+ img = @gce_helper.get_latest_image(gplatform, start, attempts)
49
50
  host['diskname'] = generate_host_name
50
51
  disk = @gce_helper.create_disk(host['diskname'], img, start, attempts)
51
52
  @logger.debug("Created Google Compute disk for #{host.name}: #{host['diskname']}")
@@ -57,8 +58,9 @@ module Beaker
57
58
  @logger.debug("Created Google Compute instance for #{host.name}: #{host['vmhostname']}")
58
59
  #add metadata to instance
59
60
  @gce_helper.setMetadata_on_instance(host['vmhostname'], instance['metadata']['fingerprint'],
60
- [ {:key => :department, :value => @options[:department]},
61
- {:key => :project, :value => @options[:project]} ],
61
+ [ {:key => :department, :value => @options[:department]},
62
+ {:key => :project, :value => @options[:project]},
63
+ {:key => :jenkins_build_url, :value => @options[:jenkins_build_url]} ],
62
64
  start, attempts)
63
65
  @logger.debug("Added tags to Google Compute instance #{host.name}: #{host['vmhostname']}")
64
66
 
@@ -87,7 +89,7 @@ module Beaker
87
89
  attempts = @options[:timeout].to_i / SLEEPWAIT
88
90
  start = Time.now
89
91
 
90
- @gce_helper.delete_firewall(@firewall, start, attempts)
92
+ @gce_helper.delete_firewall(@firewall, start, attempts)
91
93
 
92
94
  @hosts.each do |host|
93
95
  @gce_helper.delete_instance(host['vmhostname'], start, attempts)
@@ -97,14 +99,14 @@ module Beaker
97
99
  end
98
100
 
99
101
  end
100
-
101
- #Shutdown and destroy Google Compute instances (including their associated disks and firewall rules)
102
+
103
+ #Shutdown and destroy Google Compute instances (including their associated disks and firewall rules)
102
104
  #that have been alive longer than ZOMBIE hours.
103
105
  def kill_zombies(max_age = ZOMBIE)
104
106
  now = start = Time.now
105
107
  attempts = @options[:timeout].to_i / SLEEPWAIT
106
108
 
107
- #get rid of old instances
109
+ #get rid of old instances
108
110
  instances = @gce_helper.list_instances(start, attempts)
109
111
  if instances
110
112
  instances.each do |instance|
@@ -116,7 +118,7 @@ module Beaker
116
118
  @gce_helper.delete_instance( instance['name'], start, attempts )
117
119
  end
118
120
  end
119
- else
121
+ else
120
122
  @logger.debug("No zombie instances found")
121
123
  end
122
124
  #get rid of old disks
@@ -477,7 +477,7 @@ module Beaker
477
477
  { :api_method => @compute.firewalls.insert,
478
478
  :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
479
479
  :body_object => { 'name' => name,
480
- 'allowed'=> [ { 'IPProtocol' => 'tcp', "ports" => [ '443', '8140', '61613' ]} ],
480
+ 'allowed'=> [ { 'IPProtocol' => 'tcp', "ports" => [ '443', '8140', '61613', '8080', '8081' ]} ],
481
481
  'network'=> network,
482
482
  'sourceRanges' => [ "0.0.0.0/0" ] } }
483
483
  end
@@ -30,6 +30,11 @@ module Beaker
30
30
  v_file << " v.vm.box_url = '#{host['box_url']}'\n" unless host['box_url'].nil?
31
31
  v_file << " v.vm.base_mac = '#{randmac}'\n"
32
32
  v_file << " v.vm.network :private_network, ip: \"#{host['ip'].to_s}\", :netmask => \"#{host['netmask'] ||= "255.255.0.0"}\"\n"
33
+ if /windows/i.match(host['platform'])
34
+ v_file << " v.vm.network :forwarded_port, guest: 3389, host: 3389\n"
35
+ v_file << " v.vm.network :forwarded_port, guest: 5985, host: 5985, id: 'winrm', auto_correct: true\n"
36
+ v_file << " v.vm.guest = :windows"
37
+ end
33
38
  v_file << " end\n"
34
39
  @logger.debug "created Vagrantfile for VagrantHost #{host.name}"
35
40
  end
@@ -92,6 +97,9 @@ module Beaker
92
97
  end
93
98
 
94
99
  def provision
100
+ if !@options[:provision] and !File.file?(@vagrant_file)
101
+ raise "Beaker is configured with provision = false but no vagrant file was found at #{@vagrant_file}. You need to enable provision"
102
+ end
95
103
  if @options[:provision]
96
104
  #setting up new vagrant hosts
97
105
  #make sure that any old boxes are dead dead dead
@@ -170,6 +170,12 @@ module Beaker
170
170
  @cmd_options[:add_el_extras] = true
171
171
  end
172
172
 
173
+ opts.on '--[no-]validate',
174
+ 'Validate that SUTs are correctly configured before running tests',
175
+ '(default: true)' do |bool|
176
+ @cmd_options[:validate] = bool
177
+ end
178
+
173
179
  opts.on('--version', 'Report currently running version of beaker' ) do
174
180
  @cmd_options[:version] = true
175
181
  end
@@ -78,7 +78,7 @@ module Beaker
78
78
  if discover_files.empty?
79
79
  parser_error "empty directory used as an option (#{root})!"
80
80
  end
81
- files += discover_files.sort
81
+ files += discover_files.sort_by {|file| [file.count("/"), file]}
82
82
  else #not a file, not a directory, not nothin'
83
83
  parser_error "#{root} used as a file option but is not a file or directory!"
84
84
  end
@@ -23,6 +23,7 @@ module Beaker
23
23
  :pe_ver => ENV['pe_ver'],
24
24
  :project => ENV['BEAKER_project'],
25
25
  :department => ENV['BEAKER_department'],
26
+ :jenkins_build_url => ENV['BUILD_URL'],
26
27
  }.delete_if {|key, value| value.nil? or value.empty? })
27
28
  end
28
29
 
@@ -34,6 +35,8 @@ module Beaker
34
35
  h.merge({
35
36
  :project => 'Beaker',
36
37
  :department => ENV['USER'] || ENV['USERNAME'] || 'unknown',
38
+ :validate => true,
39
+ :jenkins_build_url => nil,
37
40
  :log_level => 'verbose',
38
41
  :trace_limit => 10,
39
42
  :hosts_file => 'sample.cfg',
@@ -14,7 +14,8 @@ module Beaker
14
14
  Errno::ECONNREFUSED,
15
15
  Errno::ECONNRESET,
16
16
  Errno::ENETUNREACH,
17
- Net::SSH::Disconnect
17
+ Net::SSH::Disconnect,
18
+ Net::SSH::AuthenticationFailed,
18
19
  ]
19
20
 
20
21
  def initialize hostname, user = nil, options = {}
@@ -48,8 +49,6 @@ module Beaker
48
49
  puts "Failed to connect to #{@hostname}"
49
50
  raise
50
51
  end
51
- rescue Net::SSH::AuthenticationFailed => e
52
- raise "Unable to authenticate to #{@hostname} with user #{e.message.inspect}"
53
52
  end
54
53
  self
55
54
  end
@@ -131,13 +131,13 @@ module Beaker
131
131
  @test_status = :pending
132
132
  rescue SkipTest
133
133
  @test_status = :skip
134
- rescue StandardError, ScriptError => e
134
+ rescue StandardError, ScriptError, SignalException => e
135
135
  log_and_fail_test(e)
136
136
  ensure
137
137
  @teardown_procs.each do |teardown|
138
138
  begin
139
139
  teardown.call
140
- rescue StandardError => e
140
+ rescue StandardError, SignalException => e
141
141
  log_and_fail_test(e)
142
142
  end
143
143
  end
@@ -1,5 +1,5 @@
1
1
  module Beaker
2
2
  module Version
3
- STRING = '1.9.1'
3
+ STRING = '1.10.0'
4
4
  end
5
5
  end
@@ -447,6 +447,26 @@ describe ClassMixedWithDSLHelpers do
447
447
  :expect_failures => true
448
448
  )
449
449
  end
450
+
451
+ it 'can set the --parser future flag' do
452
+ subject.should_receive( :create_remote_file ).and_return( true )
453
+ subject.should_receive( :puppet ).
454
+ with( 'apply', '--verbose', '--parser future', '--detailed-exitcodes', 'agent' ).
455
+ and_return( 'puppet_command' )
456
+ subject.should_receive( :on ).with(
457
+ agent,
458
+ 'puppet_command',
459
+ :acceptable_exit_codes => [1,2,3,4,5,6]
460
+ )
461
+
462
+ subject.apply_manifest_on(
463
+ agent,
464
+ 'class { "boo": }',
465
+ :acceptable_exit_codes => (1..5),
466
+ :future_parser => true,
467
+ :expect_failures => true
468
+ )
469
+ end
450
470
  end
451
471
 
452
472
  describe "#apply_manifest" do
@@ -654,11 +674,13 @@ describe ClassMixedWithDSLHelpers do
654
674
  let(:test_case_path) { 'testcase/path' }
655
675
  let(:tmpdir_path) { '/tmp/tmpdir' }
656
676
  let(:puppet_path) { '/puppet/path' }
677
+ let(:puppetservice) { @ps || nil }
657
678
  let(:is_pe) { false }
658
679
  let(:host) do
659
680
  FakeHost.new(:pe => is_pe,
660
681
  :options => {
661
682
  'puppetpath' => puppet_path,
683
+ 'puppetservice' => puppetservice,
662
684
  'platform' => 'el'
663
685
  })
664
686
  end
@@ -689,23 +711,23 @@ describe ClassMixedWithDSLHelpers do
689
711
  Tempfile.should_receive(:open).with('beaker')
690
712
  end
691
713
 
692
- context 'as pe' do
693
- let(:is_pe) { true }
714
+ context 'with puppetservice and service-path defined' do
715
+ let(:puppetservice) { 'whatever' }
694
716
 
695
717
  it 'bounces puppet twice' do
696
718
  subject.with_puppet_running_on(host, {})
697
- expect(host).to execute_commands_matching(/pe-httpd restart/).exactly(2).times
719
+ expect(host).to execute_commands_matching(/#{@ps} restart/).exactly(2).times
698
720
  end
699
721
 
700
722
  it 'yield to a block after bouncing service' do
701
723
  execution = 0
702
724
  expect do
703
725
  subject.with_puppet_running_on(host, {}) do
704
- expect(host).to execute_commands_matching(/pe-httpd restart/).once
726
+ expect(host).to execute_commands_matching(/#{@ps} restart/).once
705
727
  execution += 1
706
728
  end
707
729
  end.to change { execution }.by(1)
708
- expect(host).to execute_commands_matching(/pe-httpd restart/).exactly(2).times
730
+ expect(host).to execute_commands_matching(/#{@ps} restart/).exactly(2).times
709
731
  end
710
732
  end
711
733
 
@@ -299,7 +299,7 @@ describe ClassMixedWithDSLInstallUtils do
299
299
  it 'installs' do
300
300
  expect(subject).to receive(:on).with(hosts[0], /puppetlabs-release-\$\(lsb_release -c -s\)\.deb/)
301
301
  expect(subject).to receive(:on).with(hosts[0], 'dpkg -i puppetlabs-release-$(lsb_release -c -s).deb')
302
- expect(subject).to receive(:on).with(hosts[0], 'apt-get -y -f -m update')
302
+ expect(subject).to receive(:on).with(hosts[0], 'apt-get update')
303
303
  expect(subject).to receive(:on).with(hosts[0], 'apt-get install -y puppet')
304
304
  subject.install_puppet
305
305
  end
@@ -309,7 +309,7 @@ describe ClassMixedWithDSLInstallUtils do
309
309
  it 'installs' do
310
310
  expect(subject).to receive(:on).with(hosts[0], /puppetlabs-release-\$\(lsb_release -c -s\)\.deb/)
311
311
  expect(subject).to receive(:on).with(hosts[0], 'dpkg -i puppetlabs-release-$(lsb_release -c -s).deb')
312
- expect(subject).to receive(:on).with(hosts[0], 'apt-get -y -f -m update')
312
+ expect(subject).to receive(:on).with(hosts[0], 'apt-get update')
313
313
  expect(subject).to receive(:on).with(hosts[0], 'apt-get install -y puppet')
314
314
  subject.install_puppet
315
315
  end
@@ -111,7 +111,7 @@ describe Beaker do
111
111
  it "can perform apt-get on ubuntu hosts" do
112
112
  host = make_host( 'testhost', { :platform => 'ubuntu' } )
113
113
 
114
- Beaker::Command.should_receive( :new ).with("apt-get -y -f -m update").once
114
+ Beaker::Command.should_receive( :new ).with("apt-get update").once
115
115
 
116
116
  subject.apt_get_update( host )
117
117
 
@@ -120,7 +120,7 @@ describe Beaker do
120
120
  it "can perform apt-get on debian hosts" do
121
121
  host = make_host( 'testhost', { :platform => 'debian' } )
122
122
 
123
- Beaker::Command.should_receive( :new ).with("apt-get -y -f -m update").once
123
+ Beaker::Command.should_receive( :new ).with("apt-get update").once
124
124
 
125
125
  subject.apt_get_update( host )
126
126
 
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ module Beaker
4
+ describe AwsSdk do
5
+ let(:aws) {
6
+ # Mock out the call to load_fog_credentials
7
+ Beaker::AwsSdk.any_instance.stub(:load_fog_credentials).and_return(fog_file_contents)
8
+
9
+ # This is needed because the EC2 api looks up a local endpoints.json file
10
+ FakeFS.deactivate!
11
+ aws = Beaker::AwsSdk.new(@hosts, make_opts)
12
+ FakeFS.activate!
13
+
14
+ aws
15
+ }
16
+ let(:amispec) {{
17
+ "centos-5-x86-64-west" => {
18
+ :image => {:pe => "ami-sekrit1"},
19
+ :region => "us-west-2",
20
+ },
21
+ "centos-6-x86-64-west" => {
22
+ :image => {:pe => "ami-sekrit2"},
23
+ :region => "us-west-2",
24
+ },
25
+ "centos-7-x86-64-west" => {
26
+ :image => {:pe => "ami-sekrit3"},
27
+ :region => "us-west-2",
28
+ },
29
+ }}
30
+
31
+ before :each do
32
+ @hosts = make_hosts({:snapshot => :pe})
33
+ @hosts[0][:platform] = "centos-5-x86-64-west"
34
+ @hosts[1][:platform] = "centos-6-x86-64-west"
35
+ @hosts[2][:platform] = "centos-7-x86-64-west"
36
+ end
37
+
38
+ context '#backoff_sleep' do
39
+ it "should call sleep 1024 times at attempt 10" do
40
+ Object.any_instance.should_receive(:sleep).with(1024)
41
+ aws.backoff_sleep(10)
42
+ end
43
+ end
44
+
45
+ context '#public_key' do
46
+ it "retrieves contents from local ~/.ssh/ssh_rsa.pub file" do
47
+ # Stub calls to file read/exists
48
+ allow(File).to receive(:exists?).with(/id_rsa.pub/) { true }
49
+ allow(File).to receive(:read).with(/id_rsa.pub/) { "foobar" }
50
+
51
+ # Should return contents of previously stubbed id_rsa.pub
52
+ expect(aws.public_key).to eq("foobar")
53
+ end
54
+
55
+ it "should return an error if the files do not exist" do
56
+ expect { aws.public_key }.to raise_error(RuntimeError, /Expected either/)
57
+ end
58
+ end
59
+
60
+ context '#key_name' do
61
+ it 'returns a key name from the local hostname' do
62
+ # Mock out the hostname and local user calls
63
+ Socket.should_receive(:gethostname) { "foobar" }
64
+ aws.should_receive(:local_user) { "bob" }
65
+
66
+ # Should match the expected composite key name
67
+ expect(aws.key_name).to eq("Beaker-bob-foobar")
68
+ end
69
+ end
70
+
71
+ context '#group_id' do
72
+ it 'should return a predicatable group_id from a port list' do
73
+ expect(aws.group_id([22, 1024])).to eq("Beaker-2799478787")
74
+ end
75
+
76
+ it 'should return a predicatable group_id from an empty list' do
77
+ expect { aws.group_id([]) }.to raise_error(ArgumentError, "Ports list cannot be nil or empty")
78
+ end
79
+ end
80
+ end
81
+ end