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,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