beaker 1.9.1 → 1.10.0

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