beaker 3.12.0 → 3.13.0

Sign up to get free protection for your applications and to get access to all the features.
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"