beaker 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +14 -0
  5. data/DOCUMENTING.md +167 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +17 -0
  8. data/README.md +332 -0
  9. data/Rakefile +121 -0
  10. data/beaker.gemspec +42 -0
  11. data/beaker.rb +10 -0
  12. data/bin/beaker +9 -0
  13. data/lib/beaker.rb +36 -0
  14. data/lib/beaker/answers.rb +29 -0
  15. data/lib/beaker/answers/version28.rb +104 -0
  16. data/lib/beaker/answers/version30.rb +194 -0
  17. data/lib/beaker/cli.rb +113 -0
  18. data/lib/beaker/command.rb +241 -0
  19. data/lib/beaker/command_factory.rb +21 -0
  20. data/lib/beaker/dsl.rb +85 -0
  21. data/lib/beaker/dsl/assertions.rb +87 -0
  22. data/lib/beaker/dsl/helpers.rb +625 -0
  23. data/lib/beaker/dsl/install_utils.rb +299 -0
  24. data/lib/beaker/dsl/outcomes.rb +99 -0
  25. data/lib/beaker/dsl/roles.rb +97 -0
  26. data/lib/beaker/dsl/structure.rb +63 -0
  27. data/lib/beaker/dsl/wrappers.rb +100 -0
  28. data/lib/beaker/host.rb +193 -0
  29. data/lib/beaker/host/aix.rb +15 -0
  30. data/lib/beaker/host/aix/file.rb +16 -0
  31. data/lib/beaker/host/aix/group.rb +35 -0
  32. data/lib/beaker/host/aix/user.rb +32 -0
  33. data/lib/beaker/host/unix.rb +54 -0
  34. data/lib/beaker/host/unix/exec.rb +15 -0
  35. data/lib/beaker/host/unix/file.rb +16 -0
  36. data/lib/beaker/host/unix/group.rb +40 -0
  37. data/lib/beaker/host/unix/pkg.rb +22 -0
  38. data/lib/beaker/host/unix/user.rb +32 -0
  39. data/lib/beaker/host/windows.rb +44 -0
  40. data/lib/beaker/host/windows/exec.rb +18 -0
  41. data/lib/beaker/host/windows/file.rb +15 -0
  42. data/lib/beaker/host/windows/group.rb +36 -0
  43. data/lib/beaker/host/windows/pkg.rb +26 -0
  44. data/lib/beaker/host/windows/user.rb +32 -0
  45. data/lib/beaker/hypervisor.rb +37 -0
  46. data/lib/beaker/hypervisor/aixer.rb +52 -0
  47. data/lib/beaker/hypervisor/blimper.rb +123 -0
  48. data/lib/beaker/hypervisor/fusion.rb +56 -0
  49. data/lib/beaker/hypervisor/solaris.rb +65 -0
  50. data/lib/beaker/hypervisor/vagrant.rb +118 -0
  51. data/lib/beaker/hypervisor/vcloud.rb +175 -0
  52. data/lib/beaker/hypervisor/vsphere.rb +80 -0
  53. data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
  54. data/lib/beaker/logger.rb +167 -0
  55. data/lib/beaker/network_manager.rb +73 -0
  56. data/lib/beaker/options_parsing.rb +323 -0
  57. data/lib/beaker/result.rb +55 -0
  58. data/lib/beaker/shared.rb +15 -0
  59. data/lib/beaker/shared/error_handler.rb +17 -0
  60. data/lib/beaker/shared/host_handler.rb +46 -0
  61. data/lib/beaker/shared/repetition.rb +28 -0
  62. data/lib/beaker/ssh_connection.rb +198 -0
  63. data/lib/beaker/test_case.rb +225 -0
  64. data/lib/beaker/test_config.rb +148 -0
  65. data/lib/beaker/test_suite.rb +288 -0
  66. data/lib/beaker/utils.rb +7 -0
  67. data/lib/beaker/utils/ntp_control.rb +42 -0
  68. data/lib/beaker/utils/repo_control.rb +92 -0
  69. data/lib/beaker/utils/setup_helper.rb +77 -0
  70. data/lib/beaker/utils/validator.rb +27 -0
  71. data/spec/beaker/command_spec.rb +94 -0
  72. data/spec/beaker/dsl/assertions_spec.rb +104 -0
  73. data/spec/beaker/dsl/helpers_spec.rb +230 -0
  74. data/spec/beaker/dsl/install_utils_spec.rb +70 -0
  75. data/spec/beaker/dsl/outcomes_spec.rb +43 -0
  76. data/spec/beaker/dsl/roles_spec.rb +86 -0
  77. data/spec/beaker/dsl/structure_spec.rb +60 -0
  78. data/spec/beaker/dsl/wrappers_spec.rb +52 -0
  79. data/spec/beaker/host_spec.rb +95 -0
  80. data/spec/beaker/logger_spec.rb +117 -0
  81. data/spec/beaker/options_parsing_spec.rb +37 -0
  82. data/spec/beaker/puppet_command_spec.rb +128 -0
  83. data/spec/beaker/ssh_connection_spec.rb +39 -0
  84. data/spec/beaker/test_case_spec.rb +6 -0
  85. data/spec/beaker/test_suite_spec.rb +44 -0
  86. data/spec/mocks_and_helpers.rb +34 -0
  87. data/spec/spec_helper.rb +15 -0
  88. metadata +359 -0
@@ -0,0 +1,28 @@
1
+ module Beaker
2
+ module Shared
3
+ module Repetition
4
+
5
+ def repeat_for seconds, &block
6
+ timeout = Time.now + seconds
7
+ done = false
8
+ until done or timeout < Time.now do
9
+ done = block.call
10
+ end
11
+ return done
12
+ end
13
+
14
+ def repeat_fibonacci_style_for attempts, &block
15
+ done = false
16
+ attempt = 1
17
+ last_wait, wait = 0, 1
18
+ while not done and attempt <= attempts do
19
+ done = block.call
20
+ sleep wait
21
+ last_wait, wait = wait, last_wait + wait
22
+ end
23
+ return done
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,198 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+ require 'net/scp'
4
+
5
+ module Beaker
6
+ class SshConnection
7
+
8
+ RETRYABLE_EXCEPTIONS = [
9
+ SocketError,
10
+ Timeout::Error,
11
+ Errno::ETIMEDOUT,
12
+ Errno::EHOSTDOWN,
13
+ Errno::EHOSTUNREACH,
14
+ Errno::ECONNREFUSED,
15
+ Errno::ECONNRESET,
16
+ Errno::ENETUNREACH,
17
+ Net::SSH::Disconnect
18
+ ]
19
+
20
+ def initialize hostname, user = nil, options = {}
21
+ @hostname = hostname
22
+ @user = user
23
+ @options = options
24
+ end
25
+
26
+ def self.connect hostname, user = 'root', options = {}
27
+ connection = new hostname, user, options
28
+ connection.connect
29
+ connection
30
+ end
31
+
32
+ def connect
33
+ try = 1
34
+ last_wait = 0
35
+ wait = 1
36
+ @ssh ||= begin
37
+ Net::SSH.start(@hostname, @user, @options)
38
+ rescue *RETRYABLE_EXCEPTIONS => e
39
+ if try <= 11
40
+ puts "Try #{try} -- Host #{@hostname} unreachable: #{e.message}"
41
+ puts "Trying again in #{wait} seconds"
42
+ sleep wait
43
+ (last_wait, wait) = wait, last_wait + wait
44
+ try += 1
45
+ retry
46
+ else
47
+ # why is the logger not passed into this class?
48
+ puts "Failed to connect to #{@hostname}"
49
+ raise
50
+ end
51
+ rescue Net::SSH::AuthenticationFailed => e
52
+ raise "Unable to authenticate to #{@hostname} with user #{e.message.inspect}"
53
+ end
54
+ self
55
+ end
56
+
57
+ def close
58
+ begin
59
+ @ssh.close if @ssh
60
+ rescue
61
+ @ssh.shutdown!
62
+ end
63
+ @ssh = nil
64
+ end
65
+
66
+ def try_to_execute command, options = {}, stdout_callback = nil,
67
+ stderr_callback = stdout_callback
68
+
69
+ result = Result.new(@hostname, command)
70
+ # why are we getting to this point on a dry run anyways?
71
+ # also... the host creates connections through the class method,
72
+ # which automatically connects, so you can't do a dry run unless you also
73
+ # can connect to your hosts?
74
+ return result if options[:dry_run]
75
+
76
+ @ssh.open_channel do |channel|
77
+ request_terminal_for( channel, command ) if options[:pty]
78
+
79
+ channel.exec(command) do |terminal, success|
80
+ abort "FAILED: to execute command on a new channel on #{@hostname}" unless success
81
+ register_stdout_for terminal, result, stdout_callback
82
+ register_stderr_for terminal, result, stderr_callback
83
+ register_exit_code_for terminal, result
84
+
85
+ process_stdin_for( terminal, options[:stdin] ) if options[:stdin]
86
+ end
87
+ end
88
+
89
+ # Process SSH activity until we stop doing that - which is when our
90
+ # channel is finished with...
91
+ @ssh.loop
92
+
93
+ result.finalize!
94
+ result
95
+ end
96
+
97
+ def execute command, options = {}, stdout_callback = nil,
98
+ stderr_callback = stdout_callback
99
+ attempt = true
100
+ begin
101
+ result = try_to_execute(command, options, stdout_callback, stderr_callback)
102
+ rescue *RETRYABLE_EXCEPTIONS => e
103
+ if attempt
104
+ attempt = false
105
+ puts "Command execution failed, attempting to reconnect to #{@hostname}"
106
+ close
107
+ connect
108
+ retry
109
+ else
110
+ raise
111
+ end
112
+ end
113
+
114
+ result
115
+ end
116
+
117
+ def request_terminal_for channel, command
118
+ channel.request_pty do |ch, success|
119
+ if success
120
+ puts "Allocated a PTY on #{@hostname} for #{command.inspect}"
121
+ else
122
+ abort "FAILED: could not allocate a pty when requested on " +
123
+ "#{@hostname} for #{command.inspect}"
124
+ end
125
+ end
126
+ end
127
+
128
+ def register_stdout_for channel, output, callback = nil
129
+ channel.on_data do |ch, data|
130
+ callback[data] if callback
131
+ output.stdout << data
132
+ output.output << data
133
+ end
134
+ end
135
+
136
+ def register_stderr_for channel, output, callback = nil
137
+ channel.on_extended_data do |ch, type, data|
138
+ if type == 1
139
+ callback[data] if callback
140
+ output.stderr << data
141
+ output.output << data
142
+ end
143
+ end
144
+ end
145
+
146
+ def register_exit_code_for channel, output
147
+ channel.on_request("exit-status") do |ch, data|
148
+ output.exit_code = data.read_long
149
+ end
150
+ end
151
+
152
+ def process_stdin_for channel, stdin
153
+ # queue stdin data, force it to packets, and signal eof: this
154
+ # triggers action in many remote commands, notably including
155
+ # 'puppet apply'. It must be sent at some point before the rest
156
+ # of the action.
157
+ channel.send_data stdin.to_s
158
+ channel.process
159
+ channel.eof!
160
+ end
161
+
162
+ def scp_to source, target, options = {}, dry_run = false
163
+ return if dry_run
164
+
165
+ options[:recursive]=File.directory?(source) if options[:recursive].nil?
166
+
167
+ @ssh.scp.upload! source, target, options
168
+
169
+ result = Result.new(@hostname, [source, target])
170
+
171
+ # Setting these values allows reporting via result.log(test_name)
172
+ result.stdout = "SCP'ed file #{source} to #{@hostname}:#{target}"
173
+
174
+ # Net::Scp always returns 0, so just set the return code to 0.
175
+ result.exit_code = 0
176
+
177
+ return result
178
+ end
179
+
180
+ def scp_from source, target, options = {}, dry_run = false
181
+ return if dry_run
182
+
183
+ options[:recursive] = true if options[:recursive].nil?
184
+
185
+ @ssh.scp.download! source, target, options
186
+
187
+ result = Result.new(@hostname, [source, target])
188
+
189
+ # Setting these values allows reporting via result.log(test_name)
190
+ result.stdout = "SCP'ed file #{@hostname}:#{source} to #{target}"
191
+
192
+ # Net::Scp always returns 0, so just set the return code to 0.
193
+ result.exit_code = 0
194
+
195
+ result
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,225 @@
1
+ %w( host answers dsl ).each do |lib|
2
+ begin
3
+ require "beaker/#{lib}"
4
+ rescue LoadError
5
+ require File.expand_path(File.join(File.dirname(__FILE__), lib))
6
+ end
7
+ end
8
+
9
+ require 'tempfile'
10
+ require 'benchmark'
11
+ require 'stringio'
12
+ require 'rbconfig'
13
+ require 'test/unit'
14
+
15
+ module Beaker
16
+ # This class represents a single test case. A test case is necessarily
17
+ # contained all in one file though may have multiple dependent examples.
18
+ # They are executed in order (save for any teardown procs registered
19
+ # through {Beaker::DSL::Structure#teardown}) and once completed
20
+ # the status of the TestCase is saved. Instance readers/accessors provide
21
+ # the test case access to various details of the environment and suite
22
+ # the test case is running within.
23
+ #
24
+ # See {Beaker::DSL} for more information about writing tests
25
+ # using the DSL.
26
+ class TestCase
27
+ include Beaker::DSL
28
+
29
+ rb_config_class = defined?(RbConfig) ? RbConfig : Config
30
+ if rb_config_class::CONFIG['MAJOR'].to_i == 1 &&
31
+ rb_config_class::CONFIG['MINOR'].to_i == 8 then
32
+ Test::Unit.run = true
33
+ # The Exception raised by Ruby's STDLIB's test framework (Ruby 1.8)
34
+ TEST_EXCEPTION_CLASS = Test::Unit::AssertionFailedError
35
+ else
36
+ # The Exception raised by Ruby's STDLIB's test framework (Ruby 1.9)
37
+ TEST_EXCEPTION_CLASS = ::MiniTest::Assertion
38
+ end
39
+
40
+ # Necessary for implementing {Beaker::DSL::Helpers#confine}.
41
+ # Assumed to be an array of valid {Beaker::Host} objects for
42
+ # this test case.
43
+ attr_accessor :hosts
44
+
45
+ # Necessary for many methods in {Beaker::DSL}. Assumed to be
46
+ # an instance of {Beaker::Logger}.
47
+ attr_accessor :logger
48
+
49
+ # A Hash of 'product name' => 'version installed', only set when
50
+ # products are installed via git or PE install steps. See the 'git' or
51
+ # 'pe' directories within 'ROOT/setup' for examples.
52
+ attr_reader :version
53
+
54
+ # A Hash of values taken from host config file.
55
+ attr_reader :config
56
+
57
+ # Parsed command line options.
58
+ attr_reader :options
59
+
60
+ # The path to the file which contains this test case.
61
+ attr_reader :path
62
+
63
+ # I don't know why this is here
64
+ attr_reader :fail_flag
65
+
66
+ # The user that is running this tests home directory, needed by 'net/ssh'.
67
+ attr_reader :usr_home
68
+
69
+ # A Symbol denoting the status of this test (:fail, :pending,
70
+ # :skipped, :pass).
71
+ attr_reader :test_status
72
+
73
+ # The exception that may have stopped this test's execution.
74
+ attr_reader :exception
75
+
76
+ # @deprecated
77
+ # The amount of time taken to execute the test. Unused, probably soon
78
+ # to be removed or refactored.
79
+ attr_reader :runtime
80
+
81
+ # An Array of Procs to be called after test execution has stopped
82
+ # (whether by exception or not).
83
+ attr_reader :teardown_procs
84
+
85
+ # @deprecated
86
+ # Legacy accessor from when test files would only contain one remote
87
+ # action. Contains the Result of the last call to utilize
88
+ # {Beaker::DSL::Helpers#on}. Do not use as it is not safe
89
+ # in test files that use multiple calls to
90
+ # {Beaker::DSL::Helpers#on}.
91
+ attr_accessor :result
92
+
93
+ # @param [Hosts,Array<Host>] these_hosts The hosts to execute this test
94
+ # against/on.
95
+ # @param [Logger] logger A logger that implements
96
+ # {Beaker::Logger}'s interface.
97
+ # @param [Hash{String=>String}] config Clusterfck of various config opts.
98
+ # @param [Hash{Symbol=>String}] options Parsed command line options.
99
+ # @param [String] path The local path to a test file to be executed.
100
+ def initialize(these_hosts, logger, config, options={}, path=nil)
101
+ @config = config['CONFIG']
102
+ @hosts = these_hosts
103
+ @logger = logger
104
+ @options = options
105
+ @path = path
106
+ @usr_home = ENV['HOME']
107
+ @test_status = :pass
108
+ @exception = nil
109
+ @runtime = nil
110
+ @teardown_procs = []
111
+
112
+
113
+ #
114
+ # We put this on each wrapper (rather than the class) so that methods
115
+ # defined in the tests don't leak out to other tests.
116
+ class << self
117
+ def run_test
118
+ @runtime = Benchmark.realtime do
119
+ begin
120
+ test = File.read(path)
121
+ eval test,nil,path,1
122
+ rescue FailTest, TEST_EXCEPTION_CLASS => e
123
+ @test_status = :fail
124
+ @exception = e
125
+ rescue PendingTest
126
+ @test_status = :pending
127
+ rescue SkipTest
128
+ @test_status = :skip
129
+ rescue StandardError, ScriptError => e
130
+ log_and_fail_test(e)
131
+ ensure
132
+ @teardown_procs.each do |teardown|
133
+ begin
134
+ teardown.call
135
+ rescue StandardError => e
136
+ log_and_fail_test(e)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ return self
142
+ end
143
+
144
+ private
145
+
146
+ # Log an error and mark the test as failed, passing through an
147
+ # exception so it can be displayed at the end of the total run.
148
+ #
149
+ # We break out the complete exception backtrace and log each line
150
+ # individually as well.
151
+ #
152
+ # @param exception [Exception] exception to fail with
153
+ def log_and_fail_test(exception)
154
+ logger.error(exception.inspect)
155
+ bt = exception.backtrace
156
+ logger.pretty_backtrace(bt).each_line do |line|
157
+ logger.error(line)
158
+ end
159
+ @test_status = :error
160
+ @exception = exception
161
+ end
162
+ end
163
+ end
164
+
165
+ # The TestCase as a hash
166
+ # @api public
167
+ # @note The visibility and semantics of this method are valid, but the
168
+ # structure of the Hash it returns may change without notice
169
+ #
170
+ # @return [Hash] A Hash representation of this test.
171
+ def to_hash
172
+ hash = {}
173
+ hash['HOSTS'] = {}
174
+ hash['CONFIG'] = @config
175
+ @hosts.each do |host|
176
+ hash['HOSTS'][host.name] = host.overrides
177
+ end
178
+ hash
179
+ end
180
+
181
+ # @deprecated
182
+ # An proxy for the last {Beaker::Result#stdout} returned by
183
+ # a method that makes remote calls. Use the {Beaker::Result}
184
+ # object returned by the method directly instead. For Usage see
185
+ # {Beaker::Result}.
186
+ def stdout
187
+ return nil if @result.nil?
188
+ @result.stdout
189
+ end
190
+
191
+ # @deprecated
192
+ # An proxy for the last {Beaker::Result#stderr} returned by
193
+ # a method that makes remote calls. Use the {Beaker::Result}
194
+ # object returned by the method directly instead. For Usage see
195
+ # {Beaker::Result}.
196
+ def stderr
197
+ return nil if @result.nil?
198
+ @result.stderr
199
+ end
200
+
201
+ # @deprecated
202
+ # An proxy for the last {Beaker::Result#exit_code} returned by
203
+ # a method that makes remote calls. Use the {Beaker::Result}
204
+ # object returned by the method directly instead. For Usage see
205
+ # {Beaker::Result}.
206
+ def exit_code
207
+ return nil if @result.nil?
208
+ @result.exit_code
209
+ end
210
+
211
+ # This method retrieves the forge hostname from either:
212
+ # * The environment variable 'forge_host'
213
+ # * The parameter 'forge_host' from the CONFIG hash in a node definition
214
+ #
215
+ # If none of these are available, it falls back to the static
216
+ # 'vulcan-acceptance.delivery.puppetlabs.net' hostname
217
+ #
218
+ # @return [String] hostname of test forge
219
+ def forge
220
+ ENV['forge_host'] ||
221
+ @config['forge_host'] ||
222
+ 'vulcan-acceptance.delivery.puppetlabs.net'
223
+ end
224
+ end
225
+ end