beaker 0.0.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 (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