beaker 3.12.0 → 3.13.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 (39) hide show
  1. checksums.yaml +8 -8
  2. data/acceptance/tests/subcommands/init.rb +17 -15
  3. data/acceptance/tests/subcommands/provision.rb +45 -0
  4. data/beaker.gemspec +5 -9
  5. data/bin/beaker +1 -1
  6. data/docs/concepts/test_tagging.md +27 -14
  7. data/docs/how_to/archive_sut_files.md +19 -1
  8. data/docs/how_to/hypervisors/README.md +20 -3
  9. data/docs/how_to/hypervisors/ec2.md +4 -0
  10. data/docs/how_to/hypervisors/vmpooler.md +24 -0
  11. data/docs/how_to/hypervisors/vsphere.md +0 -3
  12. data/docs/tutorials/installation.md +22 -7
  13. data/lib/beaker/cli.rb +28 -12
  14. data/lib/beaker/dsl.rb +2 -1
  15. data/lib/beaker/dsl/helpers/puppet_helpers.rb +1 -1
  16. data/lib/beaker/dsl/helpers/tk_helpers.rb +1 -1
  17. data/lib/beaker/dsl/structure.rb +0 -130
  18. data/lib/beaker/dsl/test_tagging.rb +157 -0
  19. data/lib/beaker/host/unix/exec.rb +9 -1
  20. data/lib/beaker/host_prebuilt_steps.rb +1 -1
  21. data/lib/beaker/hypervisor/openstack.rb +8 -9
  22. data/lib/beaker/options/command_line_parser.rb +19 -4
  23. data/lib/beaker/options/parser.rb +18 -9
  24. data/lib/beaker/options/presets.rb +6 -4
  25. data/lib/beaker/options/validator.rb +11 -5
  26. data/lib/beaker/subcommand.rb +84 -6
  27. data/lib/beaker/subcommands/subcommand_util.rb +58 -7
  28. data/lib/beaker/version.rb +1 -1
  29. data/spec/beaker/cli_spec.rb +44 -1
  30. data/spec/beaker/dsl/structure_spec.rb +1 -214
  31. data/spec/beaker/dsl/test_tagging_spec.rb +274 -0
  32. data/spec/beaker/host/cisco_spec.rb +4 -4
  33. data/spec/beaker/host/unix/exec_spec.rb +2 -2
  34. data/spec/beaker/host_prebuilt_steps_spec.rb +1 -1
  35. data/spec/beaker/options/command_line_parser_spec.rb +2 -2
  36. data/spec/beaker/options/parser_spec.rb +33 -24
  37. data/spec/beaker/options/validator_spec.rb +18 -3
  38. data/spec/beaker/subcommand/subcommand_util_spec.rb +121 -10
  39. metadata +12 -8
data/lib/beaker/cli.rb CHANGED
@@ -9,8 +9,16 @@ module Beaker
9
9
  | V |
10
10
  | | | "
11
11
 
12
+ attr_reader :logger
12
13
  def initialize
13
14
  @timestamp = Time.now
15
+ # Initialize a logger object prior to parsing; this should be overwritten whence
16
+ # the options are parsed and replaced with a new logger based on what is passed
17
+ # in to configure the logger.
18
+ @logger = Beaker::Logger.new
19
+ end
20
+
21
+ def parse_options
14
22
  @options_parser = Beaker::Options::Parser.new
15
23
  @options = @options_parser.parse_args
16
24
  @attribution = @options_parser.attribution
@@ -20,28 +28,19 @@ module Beaker
20
28
  @options_parser.update_option(:timestamp, @timestamp, 'runtime')
21
29
  @options_parser.update_option(:beaker_version, Beaker::Version::STRING, 'runtime')
22
30
  beaker_version_string = VERSION_STRING % @options[:beaker_version]
23
- @execute = true
24
31
 
25
32
  if @options[:help]
26
33
  @logger.notify(@options_parser.usage)
27
34
  @execute = false
28
- return
35
+ return self
29
36
  end
30
37
 
31
38
  if @options[:beaker_version_print]
32
39
  @logger.notify(beaker_version_string)
33
40
  @execute = false
34
- return
41
+ return self
35
42
  end
36
43
 
37
- @logger.info("Beaker!")
38
- @logger.info(beaker_version_string)
39
- @logger.info(@options.dump)
40
-
41
- if @options[:parse_only]
42
- @execute = false
43
- return
44
- end
45
44
 
46
45
  #add additional paths to the LOAD_PATH
47
46
  if not @options[:load_path].empty?
@@ -53,6 +52,21 @@ module Beaker
53
52
  require File.expand_path(helper)
54
53
  end
55
54
 
55
+ if @options[:parse_only]
56
+ print_version_and_options
57
+ @execute = false
58
+ return self
59
+ end
60
+
61
+ @execute = true
62
+ self
63
+ end
64
+
65
+ # only call this method after parse_options has been executed.
66
+ def print_version_and_options
67
+ @logger.info("Beaker!")
68
+ @logger.info(VERSION_STRING % @options[:beaker_version])
69
+ @logger.info(@options.dump)
56
70
  end
57
71
 
58
72
  #Provision, validate and configure all hosts as defined in the hosts file
@@ -77,10 +91,12 @@ module Beaker
77
91
  # - run post-suite
78
92
  # - cleanup hosts
79
93
  def execute!
80
-
81
94
  if !@execute
82
95
  return
83
96
  end
97
+
98
+ print_version_and_options
99
+
84
100
  begin
85
101
  trap(:INT) do
86
102
  @logger.warn "Interrupt received; exiting..."
data/lib/beaker/dsl.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  [ 'install_utils', 'roles', 'outcomes', 'assertions', 'patterns',
2
- 'structure', 'helpers', 'wrappers' ].each do |lib|
2
+ 'structure', 'helpers', 'wrappers', 'test_tagging' ].each do |lib|
3
3
  require "beaker/dsl/#{lib}"
4
4
  end
5
5
 
@@ -79,6 +79,7 @@ module Beaker
79
79
  include Beaker::DSL::Helpers
80
80
  include Beaker::DSL::InstallUtils
81
81
  include Beaker::DSL::Patterns
82
+ include Beaker::DSL::TestTagging
82
83
 
83
84
  def self.register(helper_module)
84
85
  include helper_module
@@ -322,7 +322,7 @@ module Beaker
322
322
  # @!visibility private
323
323
  def puppet_conf_for host, conf_opts
324
324
  puppetconf = host.exec( Command.new( "cat #{host.puppet('master')['config']}" ) ).stdout
325
- new_conf = IniFile.new( puppetconf ).merge( conf_opts )
325
+ new_conf = IniFile.new(content: puppetconf).merge( conf_opts )
326
326
 
327
327
  new_conf
328
328
  end
@@ -75,7 +75,7 @@ module Beaker
75
75
  end
76
76
 
77
77
  begin
78
- return IniFile.new(string)
78
+ return IniFile.new(content: string)
79
79
  rescue IniFile::Error
80
80
  nil
81
81
  end
@@ -244,63 +244,6 @@ module Beaker
244
244
  self.hosts = original_hosts
245
245
  end
246
246
 
247
- # Sets tags on the current {Beaker::TestCase}, and skips testing
248
- # if necessary after checking this case's tags against the ones that are
249
- # being included or excluded.
250
- #
251
- # @param [Array<String>] tags Tags to be assigned to the current test
252
- #
253
- # @return nil
254
- # @api public
255
- def tag(*tags)
256
- metadata[:case] ||= {}
257
- metadata[:case][:tags] = []
258
- tags.each do |tag|
259
- metadata[:case][:tags] << tag.downcase
260
- end
261
-
262
- @options[:tag_includes] ||= []
263
- @options[:tag_excludes] ||= []
264
-
265
- tags_needed_to_include_this_test = []
266
- @options[:tag_includes].each do |tag_to_include|
267
- tags_needed_to_include_this_test << tag_to_include \
268
- unless metadata[:case][:tags].include?(tag_to_include)
269
- end
270
- skip_test "#{self.path} does not include necessary tag(s): #{tags_needed_to_include_this_test}" \
271
- if tags_needed_to_include_this_test.length > 0
272
-
273
- tags_to_remove_to_include_this_test = []
274
- @options[:tag_excludes].each do |tag_to_exclude|
275
- tags_to_remove_to_include_this_test << tag_to_exclude \
276
- if metadata[:case][:tags].include?(tag_to_exclude)
277
- end
278
- skip_test "#{self.path} includes excluded tag(s): #{tags_to_remove_to_include_this_test}" \
279
- if tags_to_remove_to_include_this_test.length > 0
280
-
281
- platform_specific_tag_confines
282
- end
283
-
284
- # Handles platform-specific tag confines logic
285
- #
286
- # @return nil
287
- # @!visibility private
288
- def platform_specific_tag_confines
289
- @options[:platform_tag_confines_object] ||= PlatformTagConfiner.new(
290
- @options[:platform_tag_confines]
291
- )
292
- confines = @options[:platform_tag_confines_object].confine_details(
293
- metadata[:case][:tags]
294
- )
295
- confines.each do |confine_details|
296
- logger.notify( confine_details[:log_message] )
297
- confine(
298
- confine_details[:type],
299
- :platform => confine_details[:platform_regex]
300
- )
301
- end
302
- end
303
-
304
247
  #Return a set of hosts that meet the given criteria
305
248
  # @param [Hash{Symbol,String=>String,Regexp,Array<String,Regexp>}]
306
249
  # criteria Specify the criteria with which a host should be
@@ -348,79 +291,6 @@ module Beaker
348
291
  true_false
349
292
  end
350
293
  end
351
-
352
- class PlatformTagConfiner
353
-
354
- # Constructs the PlatformTagConfiner, transforming the user format
355
- # into the internal structure for use by Beaker itself.
356
- #
357
- # @param [Array<Hash{Symbol=>Object}>] platform_tag_confines_array
358
- # The array of PlatformTagConfines objects that specify how these
359
- # confines should behave. See the note below for more info
360
- #
361
- # @note PlatformTagConfines objects come in the form
362
- # [
363
- # {
364
- # :platform => <platform-regex>,
365
- # :tag_reason_hash => {
366
- # <tag> => <reason to confine>,
367
- # <tag> => <reason to confine>,
368
- # ...etc...
369
- # }
370
- # }
371
- # ]
372
- #
373
- # Internally, we want to turn tag matches into platform
374
- # confine statements. So a better internal structure would
375
- # be something of the form:
376
- # {
377
- # <tag> => [{
378
- # :platform => <platform-regex>,
379
- # :reason => <reason to confine>,
380
- # :type => :except,
381
- # }, ... ]
382
- # }
383
- def initialize(platform_tag_confines_array)
384
- platform_tag_confines_array ||= []
385
- @tag_confine_details_hash = {}
386
- platform_tag_confines_array.each do |entry|
387
- entry[:tag_reason_hash].keys.each do |tag|
388
- @tag_confine_details_hash[tag] ||= []
389
- log_msg = "Tag '#{tag}' found, confining: except platforms "
390
- log_msg << "matching regex '#{entry[:platform]}'. Reason: "
391
- log_msg << "'#{entry[:tag_reason_hash][tag]}'"
392
- @tag_confine_details_hash[tag] << {
393
- :platform_regex => entry[:platform],
394
- :log_message => log_msg,
395
- :type => :except
396
- }
397
- end
398
- end
399
- end
400
-
401
- # Gets the confine details needed for a set of tags
402
- #
403
- # @param [Array<String>] tags Tags of the given test
404
- #
405
- # @return [Array<Hash{Symbol=>Object}>] an array of
406
- # Confine details hashes, which are hashes of symbols
407
- # to their properties, which are objects of various
408
- # kinds, depending on the key
409
- def confine_details(tags)
410
- tags ||= []
411
- details = []
412
- tags.each do |tag|
413
- tag_confine_array = @tag_confine_details_hash[tag]
414
- next if tag_confine_array.nil?
415
-
416
- details.push( *tag_confine_array )
417
- # tag_confine_array.each do |confine_details|
418
- # details << confine_details
419
- # end
420
- end
421
- details
422
- end
423
- end
424
294
  end
425
295
  end
426
296
  end
@@ -0,0 +1,157 @@
1
+ module Beaker
2
+ module DSL
3
+ # Test Tagging is about applying meta-data to tests (using the #tag method),
4
+ # so that you can control which tests are executed in a particular beaker
5
+ # run at a more fine-grained level.
6
+ #
7
+ # @note There are a few places where TestTagging-related code is located:
8
+ # - {Beaker::Options::Parser#normalize_tags!} makes sure the test tags
9
+ # are formatted correctly for use in this module
10
+ # - {Beaker::Options::CommandLineParser#initialize} parses test tagging
11
+ # options
12
+ # - {Beaker::Options::Validator#validate_tags} ensures test tag CLI params
13
+ # are valid for use by this module
14
+ module TestTagging
15
+
16
+ # Sets tags on the current {Beaker::TestCase}, and skips testing
17
+ # if necessary after checking this case's tags against the ones that are
18
+ # being included or excluded.
19
+ #
20
+ # @param [Array<String>] tags Tags to be assigned to the current test
21
+ #
22
+ # @return nil
23
+ # @api public
24
+ def tag(*tags)
25
+ metadata[:case] ||= {}
26
+ metadata[:case][:tags] = []
27
+ tags.each do |tag|
28
+ metadata[:case][:tags] << tag.downcase
29
+ end
30
+
31
+ @options[:test_tag_and] ||= []
32
+ @options[:test_tag_or] ||= []
33
+ @options[:test_tag_exclude] ||= []
34
+
35
+ tags_needed_to_include_this_test = []
36
+ @options[:test_tag_and].each do |tag_to_include|
37
+ tags_needed_to_include_this_test << tag_to_include \
38
+ unless metadata[:case][:tags].include?(tag_to_include)
39
+ end
40
+ skip_test "#{self.path} does not include necessary tag(s): #{tags_needed_to_include_this_test}" \
41
+ if tags_needed_to_include_this_test.length > 0
42
+
43
+ found_test_tag = false
44
+ @options[:test_tag_or].each do |tag_to_include|
45
+ found_test_tag = metadata[:case][:tags].include?(tag_to_include)
46
+ break if found_test_tag
47
+ end
48
+ skip_test "#{self.path} does not include any of these tag(s): #{@options[:test_tag_or]}" \
49
+ if @options[:test_tag_or].length > 0 && !found_test_tag
50
+
51
+ tags_to_remove_to_include_this_test = []
52
+ @options[:test_tag_exclude].each do |tag_to_exclude|
53
+ tags_to_remove_to_include_this_test << tag_to_exclude \
54
+ if metadata[:case][:tags].include?(tag_to_exclude)
55
+ end
56
+ skip_test "#{self.path} includes excluded tag(s): #{tags_to_remove_to_include_this_test}" \
57
+ if tags_to_remove_to_include_this_test.length > 0
58
+
59
+ platform_specific_tag_confines
60
+ end
61
+
62
+ # Handles platform-specific tag confines logic
63
+ #
64
+ # @return nil
65
+ # @!visibility private
66
+ def platform_specific_tag_confines
67
+ @options[:platform_tag_confines_object] ||= PlatformTagConfiner.new(
68
+ @options[:platform_tag_confines]
69
+ )
70
+ confines = @options[:platform_tag_confines_object].confine_details(
71
+ metadata[:case][:tags]
72
+ )
73
+ confines.each do |confine_details|
74
+ logger.notify( confine_details[:log_message] )
75
+ confine(
76
+ confine_details[:type],
77
+ :platform => confine_details[:platform_regex]
78
+ )
79
+ end
80
+ end
81
+
82
+ class PlatformTagConfiner
83
+
84
+ # Constructs the PlatformTagConfiner, transforming the user format
85
+ # into the internal structure for use by Beaker itself.
86
+ #
87
+ # @param [Array<Hash{Symbol=>Object}>] platform_tag_confines_array
88
+ # The array of PlatformTagConfines objects that specify how these
89
+ # confines should behave. See the note below for more info
90
+ #
91
+ # @note PlatformTagConfines objects come in the form
92
+ # [
93
+ # {
94
+ # :platform => <platform-regex>,
95
+ # :tag_reason_hash => {
96
+ # <tag> => <reason to confine>,
97
+ # <tag> => <reason to confine>,
98
+ # ...etc...
99
+ # }
100
+ # }
101
+ # ]
102
+ #
103
+ # Internally, we want to turn tag matches into platform
104
+ # confine statements. So a better internal structure would
105
+ # be something of the form:
106
+ # {
107
+ # <tag> => [{
108
+ # :platform => <platform-regex>,
109
+ # :reason => <reason to confine>,
110
+ # :type => :except,
111
+ # }, ... ]
112
+ # }
113
+ def initialize(platform_tag_confines_array)
114
+ platform_tag_confines_array ||= []
115
+ @tag_confine_details_hash = {}
116
+ platform_tag_confines_array.each do |entry|
117
+ entry[:tag_reason_hash].keys.each do |tag|
118
+ @tag_confine_details_hash[tag] ||= []
119
+ log_msg = "Tag '#{tag}' found, confining: except platforms "
120
+ log_msg << "matching regex '#{entry[:platform]}'. Reason: "
121
+ log_msg << "'#{entry[:tag_reason_hash][tag]}'"
122
+ @tag_confine_details_hash[tag] << {
123
+ :platform_regex => entry[:platform],
124
+ :log_message => log_msg,
125
+ :type => :except
126
+ }
127
+ end
128
+ end
129
+ end
130
+
131
+ # Gets the confine details needed for a set of tags
132
+ #
133
+ # @param [Array<String>] tags Tags of the given test
134
+ #
135
+ # @return [Array<Hash{Symbol=>Object}>] an array of
136
+ # Confine details hashes, which are hashes of symbols
137
+ # to their properties, which are objects of various
138
+ # kinds, depending on the key
139
+ def confine_details(tags)
140
+ tags ||= []
141
+ details = []
142
+ tags.each do |tag|
143
+ tag_confine_array = @tag_confine_details_hash[tag]
144
+ next if tag_confine_array.nil?
145
+
146
+ details.push( *tag_confine_array )
147
+ # tag_confine_array.each do |confine_details|
148
+ # details << confine_details
149
+ # end
150
+ end
151
+ details
152
+ end
153
+ end
154
+
155
+ end
156
+ end
157
+ end
@@ -246,7 +246,15 @@ module Unix::Exec
246
246
  else
247
247
  val = val.to_s
248
248
  end
249
- env_array << "#{key.to_s.upcase}=\"#{val}\""
249
+ # doing this for the key itself & the upcase'd version allows us to remain
250
+ # backwards compatible
251
+ # TODO: (Next Major Version) get rid of upcase'd version
252
+ key_str = key.to_s
253
+ keys = [key_str]
254
+ keys << key_str.upcase if key_str.upcase != key_str
255
+ keys.each do |env_key|
256
+ env_array << "#{env_key}=\"#{val}\""
257
+ end
250
258
  end
251
259
  env_array
252
260
  end
@@ -173,7 +173,7 @@ module Beaker
173
173
  elsif host['platform'] =~ /aix/
174
174
  host.exec(Command.new(ROOT_KEYS_SYNC_CMD_AIX % "env PATH=/usr/gnu/bin:$PATH bash"), :accept_all_exit_codes => true)
175
175
  else
176
- host.exec(Command.new(ROOT_KEYS_SYNC_CMD % "env PATH=/usr/gnu/bin:$PATH bash"), :accept_all_exit_codes => true)
176
+ host.exec(Command.new(ROOT_KEYS_SYNC_CMD % "env PATH=\"/usr/gnu/bin:$PATH\" bash"), :accept_all_exit_codes => true)
177
177
  end
178
178
  end
179
179
  rescue => e
@@ -220,16 +220,15 @@ module Beaker
220
220
 
221
221
  @hosts.each do |host|
222
222
  ip = get_ip
223
- host[:vmhostname] = ip.ip.gsub '.','-'
224
- host[:vmfqdn] = host[:vmhostname] + '.rfc1918.puppetlabs.net'
223
+ host[:vmhostname] = ip.ip.gsub('.','-') + '.rfc1918.puppetlabs.net'
225
224
  host[:keyname] = key_name(host)
226
- @logger.debug "Provisioning #{host.name} (#{host[:vmfqdn]})"
225
+ @logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})"
227
226
  options = {
228
227
  :flavor_ref => flavor(host[:flavor]).id,
229
228
  :image_ref => image(host[:image]).id,
230
229
  :nics => [ {'net_id' => network(@options[:openstack_network]).id } ],
231
- :name => host[:vmfqdn],
232
- :hostname => host[:vmfqdn],
230
+ :name => host[:vmhostname],
231
+ :hostname => host[:vmhostname],
233
232
  :user_data => host[:user_data] || "#cloud-config\nmanage_etc_hosts: true\n",
234
233
  :key_name => host[:keyname],
235
234
  }
@@ -247,10 +246,10 @@ module Beaker
247
246
  break
248
247
  rescue Fog::Errors::TimeoutError => e
249
248
  if try >= attempts
250
- @logger.debug "Failed to connect to new OpenStack instance #{host.name} (#{host[:vmfqdn]})"
249
+ @logger.debug "Failed to connect to new OpenStack instance #{host.name} (#{host[:vmhostname]})"
251
250
  raise e
252
251
  end
253
- @logger.debug "Timeout connecting to instance #{host.name} (#{host[:vmfqdn]}), trying again..."
252
+ @logger.debug "Timeout connecting to instance #{host.name} (#{host[:vmhostname]}), trying again..."
254
253
  end
255
254
  sleep SLEEPWAIT
256
255
  try += 1
@@ -260,7 +259,7 @@ module Beaker
260
259
  ip.server = vm
261
260
  host[:ip] = ip.ip
262
261
 
263
- @logger.debug "OpenStack host #{host.name} (#{host[:vmfqdn]}) assigned ip: #{host[:ip]}"
262
+ @logger.debug "OpenStack host #{host.name} (#{host[:vmhostname]}) assigned ip: #{host[:ip]}"
264
263
 
265
264
  #set metadata
266
265
  vm.metadata.update({:jenkins_build_url => @options[:jenkins_build_url].to_s,
@@ -332,7 +331,7 @@ module Beaker
332
331
  #@api private
333
332
  def key_name(host)
334
333
  if @options[:openstack_keyname]
335
- @logger.debug "Adding optional key_name #{@options[:openstack_keyname]} to #{host.name} (#{host[:vmfqdn]})"
334
+ @logger.debug "Adding optional key_name #{@options[:openstack_keyname]} to #{host.name} (#{host[:vmhostname]})"
336
335
  @options[:openstack_keyname]
337
336
  else
338
337
  @logger.debug "Generate a new rsa key"