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,323 @@
1
+ module Beaker
2
+ class Options
3
+ GITREPO = 'git://github.com/puppetlabs'
4
+
5
+ def self.options
6
+ return @options
7
+ end
8
+
9
+ def self.repo?
10
+ GITREPO
11
+ end
12
+
13
+ def self.parse_install_options(install_opts)
14
+ install_opts.map! { |opt|
15
+ case opt
16
+ when /^PUPPET\//
17
+ opt = "#{GITREPO}/puppet.git##{opt.split('/', 2)[1]}"
18
+ when /^FACTER\//
19
+ opt = "#{GITREPO}/facter.git##{opt.split('/', 2)[1]}"
20
+ when /^HIERA\//
21
+ opt = "#{GITREPO}/hiera.git##{opt.split('/', 2)[1]}"
22
+ when /^HIERA-PUPPET\//
23
+ opt = "#{GITREPO}/hiera-puppet.git##{opt.split('/', 2)[1]}"
24
+ end
25
+ opt
26
+ }
27
+ install_opts
28
+ end
29
+
30
+ def self.file_list(paths)
31
+ files = []
32
+ if not paths.empty?
33
+ paths.each do |root|
34
+ if File.file? root then
35
+ files << root
36
+ else
37
+ discover_files = Dir.glob(
38
+ File.join(root, "**/*.rb")
39
+ ).select { |f| File.file?(f) }
40
+ if discover_files.empty?
41
+ raise ArgumentError, "Empty directory used as an option (#{root})!"
42
+ end
43
+ files += discover_files
44
+ end
45
+ end
46
+ end
47
+ files
48
+ end
49
+
50
+ def self.parse_args
51
+ return @options if @options
52
+
53
+ @no_args = ARGV.empty? ? true : false
54
+
55
+ @defaults = {}
56
+ @options = {}
57
+ @options_from_file = {}
58
+
59
+ optparse = OptionParser.new do|opts|
60
+ # Set a banner
61
+ opts.banner = "Usage: #{File.basename($0)} [options...]"
62
+
63
+ @defaults[:config] = nil
64
+ opts.on '-c', '--config FILE',
65
+ 'Use configuration FILE' do |file|
66
+ @options[:config] = file
67
+ end
68
+
69
+ @defaults[:options_file] = nil
70
+ opts.on '-o', '--options-file FILE',
71
+ 'Read options from FILE',
72
+ 'This should evaluate to a ruby hash.',
73
+ 'CLI optons are given precedence.' do |file|
74
+ @options_from_file = parse_options_file file
75
+ end
76
+
77
+ @defaults[:type] = 'pe'
78
+ opts.on '--type TYPE',
79
+ 'one of git or pe',
80
+ 'used to determine underlying path structure of puppet install',
81
+ 'defaults to pe' do |type|
82
+ @options[:type] = type
83
+ end
84
+
85
+ @defaults[:helper] = []
86
+ opts.on '--helper PATH/TO/SCRIPT',
87
+ 'Ruby file evaluated prior to tests',
88
+ '(a la spec_helper)' do |script|
89
+ @options[:helper] = []
90
+ if script.is_a?(Array)
91
+ @options[:helper] += script
92
+ elsif script =~ /,/
93
+ @options[:helper] += script.split(',')
94
+ else
95
+ @options[:helper] << script
96
+ end
97
+ end
98
+
99
+ @defaults[:load_path] = []
100
+ opts.on '--load-path /PATH/TO/DIR,/ADDITIONAL/DIR/PATHS',
101
+ 'Add paths to LOAD_PATH' do |value|
102
+ @options[:load_path] = []
103
+ if value.is_a?(Array)
104
+ @options[:load_path] += value
105
+ elsif value =~ /,/
106
+ @options[:load_path] += value.split(',')
107
+ else
108
+ @options[:load_path] << value
109
+ end
110
+ end
111
+
112
+ @defaults[:tests] = []
113
+ opts.on '-t', '--tests /PATH/TO/DIR,/ADDITIONA/DIR/PATHS,/PATH/TO/FILE.rb',
114
+ 'Execute tests from paths and files' do |value|
115
+ @options[:tests] = []
116
+ if value.is_a?(Array)
117
+ @options[:tests] += value
118
+ elsif value =~ /,/
119
+ @options[:tests] += value.split(',')
120
+ else
121
+ @options[:tests] << value
122
+ end
123
+ @options[:tests] = file_list(@options[:tests])
124
+ if @options[:tests].empty?
125
+ raise ArgumentError, "No tests to run!"
126
+ end
127
+ end
128
+
129
+ @defaults[:pre_suite] = []
130
+ opts.on '--pre-suite /PRE-SUITE/DIR/PATH,/ADDITIONAL/DIR/PATHS,/PATH/TO/FILE.rb',
131
+ 'Path to project specific steps to be run BEFORE testing' do |value|
132
+ @options[:pre_suite] = []
133
+ if value.is_a?(Array)
134
+ @options[:pre_suite] += value
135
+ elsif value =~ /,/
136
+ @options[:pre_suite] += value.split(',')
137
+ else
138
+ @options[:pre_suite] << value
139
+ end
140
+ @options[:pre_suite] = file_list(@options[:pre_suite])
141
+ if @options[:pre_suite].empty?
142
+ raise ArgumentError, "Empty pre-suite!"
143
+ end
144
+ end
145
+
146
+ @defaults[:post_suite] = []
147
+ opts.on '--post-suite /POST-SUITE/DIR/PATH,/OPTIONAL/ADDITONAL/DIR/PATHS,/PATH/TO/FILE.rb',
148
+ 'Path to project specific steps to be run AFTER testing' do |value|
149
+ @options[:post_suite] = []
150
+ if value.is_a?(Array)
151
+ @options[:post_suite] += value
152
+ elsif value =~ /,/
153
+ @options[:post_suite] += value.split(',')
154
+ else
155
+ @options[:post_suite] << value
156
+ end
157
+ @options[:post_suite] = file_list(@options[:post_suite])
158
+ if @options[:post_suite].empty?
159
+ raise ArgumentError, "Empty post-suite!"
160
+ end
161
+ end
162
+
163
+ @defaults[:provision] = true
164
+ opts.on '--[no-]provision',
165
+ 'Do not provision vm images before testing',
166
+ '(default: true)' do |bool|
167
+ @options[:provision] = bool
168
+ end
169
+
170
+ @defaults[:preserve_hosts] = false
171
+ opts.on '--[no-]preserve-hosts',
172
+ 'Preserve cloud instances' do |value|
173
+ @options[:preserve_hosts] = value
174
+ end
175
+
176
+ @defaults[:root_keys] = false
177
+ opts.on '--root-keys',
178
+ 'Install puppetlabs pubkeys for superuser',
179
+ '(default: false)' do |bool|
180
+ @options[:root_keys] = bool
181
+ end
182
+
183
+ @defaults[:keyfile] = "#{ENV['HOME']}/.ssh/id_rsa"
184
+ opts.on '--keyfile /PATH/TO/SSH/KEY',
185
+ 'Specify alternate SSH key',
186
+ '(default: ~/.ssh/id_rsa)' do |key|
187
+ @options[:keyfile] = key
188
+ end
189
+
190
+
191
+ @defaults[:install] = []
192
+ opts.on '-i URI', '--install URI',
193
+ 'Install a project repo/app on the SUTs',
194
+ 'Provide full git URI or use short form KEYWORD/name',
195
+ 'supported keywords: PUPPET, FACTER, HIERA, HIERA-PUPPET' do |value|
196
+ @options[:install] = []
197
+ if value.is_a?(Array)
198
+ @options[:install] += value
199
+ elsif value =~ /,/
200
+ @options[:install] += value.split(',')
201
+ else
202
+ @options[:install] << value
203
+ end
204
+ @options[:install] = parse_install_options(@options[:install])
205
+ end
206
+
207
+ @defaults[:modules] = []
208
+ opts.on('-m', '--modules URI', 'Select puppet module git install URI') do |value|
209
+ @options[:modules] ||= []
210
+ @options[:modules] << value
211
+ end
212
+
213
+ @defaults[:quiet] = false
214
+ opts.on '-q', '--[no-]quiet',
215
+ 'Do not log output to STDOUT',
216
+ '(default: false)' do |bool|
217
+ @options[:quiet] = bool
218
+ end
219
+
220
+ @defaults[:xml] = false
221
+ opts.on '-x', '--[no-]xml',
222
+ 'Emit JUnit XML reports on tests',
223
+ '(default: false)' do |bool|
224
+ @options[:xml] = bool
225
+ end
226
+
227
+ @defaults[:color] = true
228
+ opts.on '--[no-]color',
229
+ 'Do not display color in log output',
230
+ '(default: true)' do |bool|
231
+ @options[:color] = bool
232
+ end
233
+
234
+ @defaults[:debug] = false
235
+ opts.on '--[no-]debug',
236
+ 'Enable full debugging',
237
+ '(default: false)' do |bool|
238
+ @options[:debug] = bool
239
+ end
240
+
241
+ @defaults[:dry_run] = false
242
+ opts.on '-d', '--[no-]dry-run',
243
+ 'Report what would happen on targets',
244
+ '(default: false)' do |bool|
245
+ @options[:dry_run] = bool
246
+ $dry_run = bool
247
+ end
248
+
249
+ @defaults[:fail_mode] = nil
250
+ opts.on '--fail-mode [MODE]',
251
+ 'How should the harness react to errors/failures',
252
+ 'Possible values:',
253
+ 'fast (skip all subsequent tests, cleanup, exit)',
254
+ 'stop (skip all subsequent tests, do no cleanup, exit immediately)' do |mode|
255
+ @options[:fail_mode] = mode
256
+ end
257
+
258
+ @defaults[:timesync] = false
259
+ opts.on '--[no-]ntp',
260
+ 'Sync time on SUTs before testing',
261
+ '(default: false)' do |bool|
262
+ @options[:timesync] = bool
263
+ end
264
+
265
+ @defaults[:repo_proxy] = false
266
+ opts.on '--repo-proxy',
267
+ 'Proxy packaging repositories on ubuntu, debian and solaris-11',
268
+ '(default: false)' do
269
+ @options[:repo_proxy] = true
270
+ end
271
+
272
+ @defaults[:add_el_extras] = false
273
+ opts.on '--add-el-extras',
274
+ 'Add Extra Packages for Enterprise Linux (EPEL) repository to el-* hosts',
275
+ '(default: false)' do
276
+ @options[:add_el_extras] = true
277
+ end
278
+
279
+ opts.on('--help', 'Display this screen' ) do |yes|
280
+ puts opts
281
+ exit
282
+ end
283
+ end
284
+
285
+ optparse.parse!
286
+
287
+ # We have use the @no_args var because OptParse consumes ARGV as it parses
288
+ # so we have to check the value of ARGV at the begining of the method,
289
+ # let the options be set, then output usage.
290
+ puts optparse if @no_args
291
+
292
+ # merge in the options that we read from the file
293
+ @options = @options_from_file.merge(@options)
294
+ # merge in defaults
295
+ @options = @defaults.merge(@options)
296
+
297
+ if @options[:type] !~ /(pe)|(git)/
298
+ raise ArgumentError.new("--type must be one of pe or git, not '#{@options[:type]}'")
299
+ end
300
+
301
+ raise ArgumentError.new("--fail-mode must be one of fast, stop") unless ["fast", "stop", nil].include?(@options[:fail_mode])
302
+
303
+ @options
304
+ end
305
+
306
+ def self.parse_options_file(options_file_path)
307
+ options_file_path = File.expand_path(options_file_path)
308
+ unless File.exists?(options_file_path)
309
+ raise ArgumentError, "Specified options file '#{options_file_path}' does not exist!"
310
+ end
311
+ # This eval will allow the specified options file to have access to our
312
+ # scope. It is important that the variable 'options_file_path' is
313
+ # accessible, because some existing options files (e.g. puppetdb) rely on
314
+ # that variable to determine their own location (for use in 'require's, etc.)
315
+ result = eval(File.read(options_file_path))
316
+ unless result.is_a? Hash
317
+ raise ArgumentError, "Options file '#{options_file_path}' must return a hash!"
318
+ end
319
+
320
+ result
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,55 @@
1
+ module Beaker
2
+ class Result
3
+ attr_accessor :host, :cmd, :exit_code, :stdout, :stderr, :output,
4
+ :raw_stdout, :raw_stderr, :raw_output
5
+ def initialize(host, cmd)
6
+ @host = host
7
+ @cmd = cmd
8
+ @stdout = ''
9
+ @stderr = ''
10
+ @output = ''
11
+ @exit_code = nil
12
+ end
13
+
14
+ # Ruby assumes chunked data (like something it receives from Net::SSH)
15
+ # to be binary (ASCII-8BIT). We need to gather all chunked data and then
16
+ # re-encode it as the default encoding it assumes for external text
17
+ # (ie our test files and the strings they're trying to match Net::SSH's
18
+ # output from)
19
+ # This is also the lowest overhead place to normalize line endings, IIRC
20
+ def finalize!
21
+ @raw_stdout = @stdout
22
+ @stdout = normalize_line_endings( convert( @stdout ) )
23
+ @raw_stderr = @stderr
24
+ @stderr = normalize_line_endings( convert( @stderr ) )
25
+ @raw_output = @output
26
+ @output = normalize_line_endings( convert( @output ) )
27
+ end
28
+
29
+ def normalize_line_endings string
30
+ return string.gsub(/\r\n?/, "\n")
31
+ end
32
+
33
+ def convert string
34
+ if string.respond_to?( :force_encoding ) and defined?( Encoding )
35
+ # We're running in >= 1.9 and we'll need to convert
36
+ return string.force_encoding( Encoding.default_external )
37
+ else
38
+ # We're running in < 1.9 and Ruby doesn't care
39
+ return string
40
+ end
41
+ end
42
+
43
+ def log(logger)
44
+ logger.debug "Exited: #{exit_code}" unless exit_code == 0
45
+ end
46
+
47
+ def formatted_output(limit=10)
48
+ @output.split("\n").last(limit).collect {|x| "\t" + x}.join("\n")
49
+ end
50
+
51
+ def exit_code_in?(range)
52
+ range.include?(@exit_code)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ [ 'repetition', 'error_handler', 'host_handler' ].each do |file|
2
+ begin
3
+ require "beaker/shared/#{file}"
4
+ rescue LoadError
5
+ require File.expand_path(File.join(File.dirname(__FILE__), 'shared', file))
6
+ end
7
+ end
8
+ module Beaker
9
+ module Shared
10
+ include Beaker::Shared::ErrorHandler
11
+ include Beaker::Shared::HostHandler
12
+ include Beaker::Shared::Repetition
13
+ end
14
+ end
15
+ include Beaker::Shared
@@ -0,0 +1,17 @@
1
+ module Beaker
2
+ module Shared
3
+ module ErrorHandler
4
+
5
+ def report_and_raise(logger, e, msg)
6
+ logger.error "Failed: errored in #{msg}"
7
+ logger.error(e.inspect)
8
+ bt = e.backtrace
9
+ logger.pretty_backtrace(bt).each_line do |line|
10
+ logger.error(line)
11
+ end
12
+ raise e
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ module Beaker
2
+ module Shared
3
+ module HostHandler
4
+
5
+ # NOTE: this code is shamelessly stolen from facter's 'domain' fact, but
6
+ # we don't have access to facter at this point in the run. Also, this
7
+ # utility method should perhaps be moved to a more central location in the
8
+ # framework.
9
+ def get_domain_name(host)
10
+ domain = nil
11
+ search = nil
12
+ resolv_conf = host.exec(Command.new("cat /etc/resolv.conf")).stdout
13
+ resolv_conf.each_line { |line|
14
+ if line =~ /^\s*domain\s+(\S+)/
15
+ domain = $1
16
+ elsif line =~ /^\s*search\s+(\S+)/
17
+ search = $1
18
+ end
19
+ }
20
+ return domain if domain
21
+ return search if search
22
+ end
23
+
24
+ def get_ip(host)
25
+ host.exec(Command.new("ip a|awk '/g/{print$2}' | cut -d/ -f1 | head -1")).stdout.chomp
26
+ end
27
+
28
+ def set_etc_hosts(host, etc_hosts)
29
+ host.exec(Command.new("echo '#{etc_hosts}' > /etc/hosts"))
30
+ end
31
+
32
+ def hosts_with_role(hosts, desired_role = nil)
33
+ hosts.select do |host|
34
+ desired_role.nil? or host['roles'].include?(desired_role.to_s)
35
+ end
36
+ end
37
+
38
+ def only_host_with_role(hosts, role)
39
+ a_host = hosts_with_role(hosts, role)
40
+ raise "There can be only one #{role}, but I found:" +
41
+ "#{a_host.map {|h| h.to_s } }" unless a_host.length == 1
42
+ a_host.first
43
+ end
44
+ end
45
+ end
46
+ end