beaker-puppet 0.1.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 (52) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +25 -0
  3. data/.simplecov +9 -0
  4. data/Gemfile +25 -0
  5. data/HISTORY.md +8 -0
  6. data/LICENSE +202 -0
  7. data/README.md +55 -0
  8. data/Rakefile +299 -0
  9. data/acceptance/config/acceptance-options.rb +6 -0
  10. data/acceptance/config/gem/acceptance-options.rb +9 -0
  11. data/acceptance/config/git/acceptance-options.rb +9 -0
  12. data/acceptance/config/nodes/vagrant-ubuntu-1404.yml +8 -0
  13. data/acceptance/config/pkg/acceptance-options.rb +8 -0
  14. data/acceptance/lib/beaker/acceptance/install_utils.rb +58 -0
  15. data/acceptance/pre_suite/gem/install.rb +8 -0
  16. data/acceptance/pre_suite/git/install.rb +97 -0
  17. data/acceptance/pre_suite/pkg/install.rb +9 -0
  18. data/acceptance/tests/README.md +3 -0
  19. data/acceptance/tests/backwards_compatible.rb +19 -0
  20. data/acceptance/tests/install_smoke_test.rb +21 -0
  21. data/acceptance/tests/stub_host.rb +47 -0
  22. data/acceptance/tests/web_helpers_test.rb +54 -0
  23. data/acceptance/tests/with_puppet_running_on.rb +26 -0
  24. data/beaker-puppet.gemspec +38 -0
  25. data/bin/beaker-puppet +32 -0
  26. data/lib/beaker-puppet.rb +46 -0
  27. data/lib/beaker-puppet/helpers/facter_helpers.rb +57 -0
  28. data/lib/beaker-puppet/helpers/puppet_helpers.rb +865 -0
  29. data/lib/beaker-puppet/helpers/tk_helpers.rb +89 -0
  30. data/lib/beaker-puppet/install_utils/aio_defaults.rb +93 -0
  31. data/lib/beaker-puppet/install_utils/ezbake_utils.rb +256 -0
  32. data/lib/beaker-puppet/install_utils/foss_defaults.rb +211 -0
  33. data/lib/beaker-puppet/install_utils/foss_utils.rb +1309 -0
  34. data/lib/beaker-puppet/install_utils/module_utils.rb +244 -0
  35. data/lib/beaker-puppet/install_utils/puppet_utils.rb +157 -0
  36. data/lib/beaker-puppet/version.rb +3 -0
  37. data/lib/beaker-puppet/wrappers.rb +93 -0
  38. data/lib/beaker/dsl/helpers/facter_helpers.rb +1 -0
  39. data/lib/beaker/dsl/helpers/puppet_helpers.rb +1 -0
  40. data/lib/beaker/dsl/helpers/tk_helpers.rb +1 -0
  41. data/lib/beaker/dsl/install_utils/aio_defaults.rb +1 -0
  42. data/lib/beaker/dsl/install_utils/ezbake_utils.rb +1 -0
  43. data/lib/beaker/dsl/install_utils/foss_defaults.rb +1 -0
  44. data/lib/beaker/dsl/install_utils/foss_utils.rb +1 -0
  45. data/lib/beaker/dsl/install_utils/module_utils.rb +1 -0
  46. data/lib/beaker/dsl/install_utils/puppet_utils.rb +1 -0
  47. data/spec/beaker-puppet/helpers/facter_helpers_spec.rb +64 -0
  48. data/spec/beaker-puppet/helpers/puppet_helpers_spec.rb +1287 -0
  49. data/spec/beaker-puppet/helpers/tk_helpers_spec.rb +86 -0
  50. data/spec/helpers.rb +109 -0
  51. data/spec/spec_helper.rb +23 -0
  52. metadata +249 -0
data/bin/beaker-puppet ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems' unless defined?(Gem)
4
+ require 'beaker-puppet'
5
+
6
+ VERSION_STRING =
7
+ "
8
+ _ .--.
9
+ ( ` )
10
+ beaker-puppet .-' `--,
11
+ _..----.. ( )`-.
12
+ .'_|` _|` _|( .__, )
13
+ /_| _| _| _( (_, .-'
14
+ ;| _| _| _| '-'__,--'`--'
15
+ | _| _| _| _| |
16
+ _ || _| _| _| _| %s
17
+ _( `--.\\_| _| _| _|/
18
+ .-' )--,| _| _|.`
19
+ (__, (_ ) )_| _| /
20
+ `-.__.\\ _,--'\\|__|__/
21
+ ;____;
22
+ \\YT/
23
+ ||
24
+ |\"\"|
25
+ '=='
26
+ "
27
+
28
+
29
+
30
+ puts VERSION_STRING % [BeakerPuppet::VERSION]
31
+
32
+ exit 0
@@ -0,0 +1,46 @@
1
+ require 'stringify-hash'
2
+ require 'in_parallel'
3
+ require 'beaker-puppet/version'
4
+ require 'beaker-puppet/wrappers'
5
+
6
+ [ 'aio', 'foss' ].each do |lib|
7
+ require "beaker-puppet/install_utils/#{lib}_defaults"
8
+ end
9
+ [ 'foss', 'puppet', 'ezbake', 'module' ].each do |lib|
10
+ require "beaker-puppet/install_utils/#{lib}_utils"
11
+ end
12
+ [ 'tk', 'facter', 'puppet' ].each do |lib|
13
+ require "beaker-puppet/helpers/#{lib}_helpers"
14
+ end
15
+
16
+
17
+ module BeakerPuppet
18
+ module InstallUtils
19
+ include Beaker::DSL::InstallUtils::FOSSDefaults
20
+ include Beaker::DSL::InstallUtils::AIODefaults
21
+
22
+ include Beaker::DSL::InstallUtils::PuppetUtils
23
+ include Beaker::DSL::InstallUtils::FOSSUtils
24
+ include Beaker::DSL::InstallUtils::EZBakeUtils
25
+ include Beaker::DSL::InstallUtils::ModuleUtils
26
+ end
27
+
28
+ module Helpers
29
+ include Beaker::DSL::Helpers::TKHelpers
30
+ include Beaker::DSL::Helpers::FacterHelpers
31
+ include Beaker::DSL::Helpers::PuppetHelpers
32
+ end
33
+
34
+ include Beaker::DSL::Wrappers
35
+ end
36
+
37
+
38
+ # # Boilerplate DSL inclusion mechanism:
39
+ # # First we register our module with the Beaker DSL
40
+ # Beaker::DSL.register( Beaker::DSL::Puppet )
41
+ #
42
+ # # Modules added into a module which has previously been included are not
43
+ # # retroactively included in the including class.
44
+ # #
45
+ # # https://github.com/adrianomitre/retroactive_module_inclusion
46
+ # Beaker::TestCase.class_eval { include Beaker::DSL }
@@ -0,0 +1,57 @@
1
+ module Beaker
2
+ module DSL
3
+ module Helpers
4
+ # Methods that help you interact with your facter installation, facter must be installed
5
+ # for these methods to execute correctly
6
+ #
7
+ module FacterHelpers
8
+
9
+ # @!macro [new] common_opts
10
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
11
+ # @option opts [Boolean] :silent (false) Do not produce log output
12
+ # @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
13
+ # (or range) of integer exit codes that should be considered
14
+ # acceptable. An error will be thrown if the exit code does not
15
+ # match one of the values in this list.
16
+ # @option opts [Boolean] :accept_all_exit_codes (false) Consider all
17
+ # exit codes as passing.
18
+ # @option opts [Boolean] :dry_run (false) Do not actually execute any
19
+ # commands on the SUT
20
+ # @option opts [String] :stdin (nil) Input to be provided during command
21
+ # execution on the SUT.
22
+ # @option opts [Boolean] :pty (false) Execute this command in a pseudoterminal.
23
+ # @option opts [Boolean] :expect_connection_failure (false) Expect this command
24
+ # to result in a connection failure, reconnect and continue execution.
25
+ # @option opts [Hash{String=>String}] :environment ({}) These will be
26
+ # treated as extra environment variables that should be set before
27
+ # running the command.
28
+ #
29
+
30
+ # Get a facter fact from a provided host
31
+ #
32
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
33
+ # or a role (String or Symbol) that identifies one or more hosts.
34
+ # @param [String] name The name of the fact to query for
35
+ # @!macro common_opts
36
+ #
37
+ # @return String The value of the fact 'name' on the provided host
38
+ # @raise [FailTest] Raises an exception if call to facter fails
39
+ def fact_on(host, name, opts = {})
40
+ result = on host, facter(name, opts)
41
+ if result.kind_of?(Array)
42
+ result.map { |res| res.stdout.chomp }
43
+ else
44
+ result.stdout.chomp
45
+ end
46
+ end
47
+
48
+ # Get a facter fact from the default host
49
+ # @see #fact_on
50
+ def fact(name, opts = {})
51
+ fact_on(default, name, opts)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,865 @@
1
+ require 'timeout'
2
+ require 'inifile'
3
+ require 'resolv'
4
+
5
+ module Beaker
6
+ module DSL
7
+ module Helpers
8
+ # Methods that help you interact with your puppet installation, puppet must be installed
9
+ # for these methods to execute correctly
10
+ module PuppetHelpers
11
+
12
+ # Return the regular expression pattern for an IPv4 address
13
+ def ipv4_regex
14
+ return /(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/
15
+ end
16
+
17
+ # Return the IP address that given hostname returns when resolved on
18
+ # the given host.
19
+ #
20
+ # @ param [Host] host One object that acts like a Beaker::Host
21
+ # @ param [String] hostname The hostname to perform a DNS resolution on
22
+ #
23
+ # @return [String, nil] An IP address, or nil.
24
+ def resolve_hostname_on(host, hostname)
25
+ match = curl_on(host, "--verbose #{hostname}", :accept_all_exit_codes => true).stderr.match(ipv4_regex)
26
+ return match ? match[0] : nil
27
+ end
28
+
29
+ # @!macro [new] common_opts
30
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
31
+ # @option opts [Boolean] :silent (false) Do not produce log output
32
+ # @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
33
+ # (or range) of integer exit codes that should be considered
34
+ # acceptable. An error will be thrown if the exit code does not
35
+ # match one of the values in this list.
36
+ # @option opts [Boolean] :accept_all_exit_codes (false) Consider all
37
+ # exit codes as passing.
38
+ # @option opts [Boolean] :dry_run (false) Do not actually execute any
39
+ # commands on the SUT
40
+ # @option opts [String] :stdin (nil) Input to be provided during command
41
+ # execution on the SUT.
42
+ # @option opts [Boolean] :pty (false) Execute this command in a pseudoterminal.
43
+ # @option opts [Boolean] :expect_connection_failure (false) Expect this command
44
+ # to result in a connection failure, reconnect and continue execution.
45
+ # @option opts [Hash{String=>String}] :environment ({}) These will be
46
+ # treated as extra environment variables that should be set before
47
+ # running the command.
48
+ #
49
+
50
+ # Return the name of the puppet user.
51
+ #
52
+ # @param [Host] host One object that acts like a Beaker::Host
53
+ #
54
+ # @note This method assumes puppet is installed on the host.
55
+ #
56
+ def puppet_user(host)
57
+ return host.puppet('master')['user']
58
+ end
59
+
60
+ # Return the name of the puppet group.
61
+ #
62
+ # @param [Host] host One object that acts like a Beaker::Host
63
+ #
64
+ # @note This method assumes puppet is installed on the host.
65
+ #
66
+ def puppet_group(host)
67
+ return host.puppet('master')['group']
68
+ end
69
+
70
+ # Test Puppet running in a certain run mode with specific options.
71
+ # This ensures the following steps are performed:
72
+ # 1. The pre-test Puppet configuration is backed up
73
+ # 2. A new Puppet configuraton file is layed down
74
+ # 3. Puppet is started or restarted in the specified run mode
75
+ # 4. Ensure Puppet has started correctly
76
+ # 5. Further tests are yielded to
77
+ # 6. Revert Puppet to the pre-test state
78
+ # 7. Testing artifacts are saved in a folder named for the test
79
+ #
80
+ # @note Whether Puppet is started or restarted depends on what kind of
81
+ # server you're running. Passenger and puppetserver are restarted before.
82
+ # Webrick is started before and stopped after yielding, unless you're using
83
+ # service scripts, then it'll behave like passenger & puppetserver.
84
+ # Passenger and puppetserver (or webrick using service scripts)
85
+ # restart after yielding by default. You can stop this from happening
86
+ # by setting the :restart_when_done flag of the conf_opts argument.
87
+ #
88
+ # @param [Host] host One object that act like Host
89
+ #
90
+ # @param [Hash{Symbol=>String}] conf_opts Represents puppet settings.
91
+ # Sections of the puppet.conf may be
92
+ # specified, if no section is specified the
93
+ # a puppet.conf file will be written with the
94
+ # options put in a section named after [mode]
95
+ # @option conf_opts [String] :__commandline_args__ A special setting for
96
+ # command_line arguments such as --debug or
97
+ # --logdest, which cannot be set in
98
+ # puppet.conf. For example:
99
+ #
100
+ # :__commandline_args__ => '--logdest /tmp/a.log'
101
+ #
102
+ # These will only be applied when starting a FOSS
103
+ # master, as a pe master is just bounced.
104
+ # @option conf_opts [Hash] :__service_args__ A special setting of options
105
+ # for controlling how the puppet master service is
106
+ # handled. The only setting currently is
107
+ # :bypass_service_script, which if set true will
108
+ # force stopping and starting a webrick master
109
+ # using the start_puppet_from_source_* methods,
110
+ # even if it seems the host has passenger.
111
+ # This is needed in FOSS tests to initialize
112
+ # SSL.
113
+ # @option conf_opts [Boolean] :restart_when_done determines whether a restart
114
+ # should be run after the test has been yielded to.
115
+ # Will stop puppet if false. Default behavior
116
+ # is to restart, but you can override this on the
117
+ # host or with this option.
118
+ # (Note: only works for passenger & puppetserver
119
+ # masters (or webrick using the service scripts))
120
+ # @param [File] testdir The temporary directory which will hold backup
121
+ # configuration, and other test artifacts.
122
+ #
123
+ # @param [Block] block The point of this method, yields so
124
+ # tests may be ran. After the block is finished
125
+ # puppet will revert to a previous state.
126
+ #
127
+ # @example A simple use case to ensure a master is running
128
+ # with_puppet_running_on( master ) do
129
+ # ...tests that require a master...
130
+ # end
131
+ #
132
+ # @example Fully utilizing the possiblities of config options
133
+ # with_puppet_running_on( master,
134
+ # :main => {:logdest => '/var/blah'},
135
+ # :master => {:masterlog => '/elswhere'},
136
+ # :agent => {:server => 'localhost'} ) do
137
+ #
138
+ # ...tests to be ran...
139
+ # end
140
+ #
141
+ def with_puppet_running_on host, conf_opts, testdir = host.tmpdir(File.basename(@path)), &block
142
+ raise(ArgumentError, "with_puppet_running_on's conf_opts must be a Hash. You provided a #{conf_opts.class}: '#{conf_opts}'") if !conf_opts.kind_of?(Hash)
143
+ cmdline_args = conf_opts[:__commandline_args__]
144
+ service_args = conf_opts[:__service_args__] || {}
145
+ restart_when_done = true
146
+ restart_when_done = host[:restart_when_done] if host.has_key?(:restart_when_done)
147
+ restart_when_done = conf_opts.fetch(:restart_when_done, restart_when_done)
148
+ conf_opts = conf_opts.reject { |k,v| [:__commandline_args__, :__service_args__, :restart_when_done].include?(k) }
149
+
150
+ curl_retries = host['master-start-curl-retries'] || options['master-start-curl-retries']
151
+ logger.debug "Setting curl retries to #{curl_retries}"
152
+
153
+ if options[:is_puppetserver] || host[:is_puppetserver]
154
+ confdir = host.puppet('master')['confdir']
155
+ vardir = host.puppet('master')['vardir']
156
+
157
+ if cmdline_args
158
+ split_args = cmdline_args.split()
159
+
160
+ split_args.each do |arg|
161
+ case arg
162
+ when /--confdir=(.*)/
163
+ confdir = $1
164
+ when /--vardir=(.*)/
165
+ vardir = $1
166
+ end
167
+ end
168
+ end
169
+
170
+ puppetserver_opts = { "jruby-puppet" => {
171
+ "master-conf-dir" => confdir,
172
+ "master-var-dir" => vardir,
173
+ }}
174
+
175
+ puppetserver_conf = File.join("#{host['puppetserver-confdir']}", "puppetserver.conf")
176
+ modify_tk_config(host, puppetserver_conf, puppetserver_opts)
177
+ end
178
+ begin
179
+ backup_file = backup_the_file(host, host.puppet('master')['confdir'], testdir, 'puppet.conf')
180
+ lay_down_new_puppet_conf host, conf_opts, testdir
181
+
182
+ if host.use_service_scripts? && !service_args[:bypass_service_script]
183
+ bounce_service( host, host['puppetservice'], curl_retries )
184
+ else
185
+ puppet_master_started = start_puppet_from_source_on!( host, cmdline_args )
186
+ end
187
+
188
+ yield self if block_given?
189
+
190
+ # FIXME: these test-flow-control exceptions should be using throw
191
+ # they can be caught in test_case. current layout dows not allow it
192
+ rescue Beaker::DSL::Outcomes::PassTest => early_assertion
193
+ pass_test(early_assertion)
194
+ rescue Beaker::DSL::Outcomes::FailTest => early_assertion
195
+ fail_test(early_assertion)
196
+ rescue Beaker::DSL::Outcomes::PendingTest => early_assertion
197
+ pending_test(early_assertion)
198
+ rescue Beaker::DSL::Outcomes::SkipTest => early_assertion
199
+ skip_test(early_assertion)
200
+ rescue Beaker::DSL::Assertions, Minitest::Assertion => early_assertion
201
+ fail_test(early_assertion)
202
+ rescue Exception => early_exception
203
+ original_exception = RuntimeError.new("PuppetAcceptance::DSL::Helpers.with_puppet_running_on failed (check backtrace for location) because: #{early_exception}\n#{early_exception.backtrace.join("\n")}\n")
204
+ raise(original_exception)
205
+
206
+ ensure
207
+ begin
208
+
209
+ if host.use_service_scripts? && !service_args[:bypass_service_script]
210
+ restore_puppet_conf_from_backup( host, backup_file )
211
+ if restart_when_done
212
+ bounce_service( host, host['puppetservice'], curl_retries )
213
+ else
214
+ host.exec puppet_resource('service', host['puppetservice'], 'ensure=stopped')
215
+ end
216
+ else
217
+ if puppet_master_started
218
+ stop_puppet_from_source_on( host )
219
+ else
220
+ dump_puppet_log(host)
221
+ end
222
+ restore_puppet_conf_from_backup( host, backup_file )
223
+ end
224
+
225
+ rescue Exception => teardown_exception
226
+ begin
227
+ if !host.is_pe?
228
+ dump_puppet_log(host)
229
+ end
230
+ rescue Exception => dumping_exception
231
+ logger.error("Raised during attempt to dump puppet logs: #{dumping_exception}")
232
+ end
233
+
234
+ if original_exception
235
+ logger.error("Raised during attempt to teardown with_puppet_running_on: #{teardown_exception}\n---\n")
236
+ raise original_exception
237
+ else
238
+ raise teardown_exception
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ # Test Puppet running in a certain run mode with specific options,
245
+ # on the default host
246
+ # @see #with_puppet_running_on
247
+ def with_puppet_running conf_opts, testdir = host.tmpdir(File.basename(@path)), &block
248
+ with_puppet_running_on(default, conf_opts, testdir, &block)
249
+ end
250
+
251
+ # @!visibility private
252
+ def restore_puppet_conf_from_backup( host, backup_file )
253
+ puppet_conf = host.puppet('master')['config']
254
+
255
+ if backup_file
256
+ host.exec( Command.new( "if [ -f '#{backup_file}' ]; then " +
257
+ "cat '#{backup_file}' > " +
258
+ "'#{puppet_conf}'; " +
259
+ "rm -f '#{backup_file}'; " +
260
+ "fi" ) )
261
+ else
262
+ host.exec( Command.new( "rm -f '#{puppet_conf}'" ))
263
+ end
264
+
265
+ end
266
+
267
+ # @!visibility private
268
+ def start_puppet_from_source_on! host, args = ''
269
+ host.exec( puppet( 'master', args ) )
270
+
271
+ logger.debug 'Waiting for the puppet master to start'
272
+ unless port_open_within?( host, 8140, 10 )
273
+ raise Beaker::DSL::FailTest, 'Puppet master did not start in a timely fashion'
274
+ end
275
+ logger.debug 'The puppet master has started'
276
+ return true
277
+ end
278
+
279
+ # @!visibility private
280
+ def stop_puppet_from_source_on( host )
281
+ pid = host.exec( Command.new('cat `puppet master --configprint pidfile`') ).stdout.chomp
282
+ host.exec( Command.new( "kill #{pid}" ) )
283
+ Timeout.timeout(10) do
284
+ while host.exec( Command.new( "kill -0 #{pid}"), :acceptable_exit_codes => [0,1] ).exit_code == 0 do
285
+ # until kill -0 finds no process and we know that puppet has finished cleaning up
286
+ sleep 1
287
+ end
288
+ end
289
+ end
290
+
291
+ # @!visibility private
292
+ def dump_puppet_log(host)
293
+ syslogfile = case host['platform']
294
+ when /fedora|centos|el|redhat|scientific/ then '/var/log/messages'
295
+ when /ubuntu|debian|cumulus/ then '/var/log/syslog'
296
+ else return
297
+ end
298
+
299
+ logger.notify "\n*************************"
300
+ logger.notify "* Dumping master log *"
301
+ logger.notify "*************************"
302
+ host.exec( Command.new( "tail -n 100 #{syslogfile}" ), :acceptable_exit_codes => [0,1])
303
+ logger.notify "*************************\n"
304
+ end
305
+
306
+ # @!visibility private
307
+ def lay_down_new_puppet_conf( host, configuration_options, testdir )
308
+ puppetconf_main = host.puppet('master')['config']
309
+ puppetconf_filename = File.basename(puppetconf_main)
310
+ puppetconf_test = File.join(testdir, puppetconf_filename)
311
+
312
+ new_conf = puppet_conf_for( host, configuration_options )
313
+ create_remote_file host, puppetconf_test, new_conf.to_s
314
+
315
+ host.exec(
316
+ Command.new( "cat #{puppetconf_test} > #{puppetconf_main}" ),
317
+ :silent => true
318
+ )
319
+ host.exec( Command.new( "cat #{puppetconf_main}" ) )
320
+ end
321
+
322
+ # @!visibility private
323
+ def puppet_conf_for host, conf_opts
324
+ puppetconf = host.exec( Command.new( "cat #{host.puppet('master')['config']}" ) ).stdout
325
+ new_conf = IniFile.new(content: puppetconf).merge( conf_opts )
326
+
327
+ new_conf
328
+ end
329
+
330
+ # Restarts the named puppet service
331
+ #
332
+ # @param [Host] host Host the service runs on
333
+ # @param [String] service Name of the service to restart
334
+ # @param [Fixnum] curl_retries Number of seconds to wait for the restart to complete before failing
335
+ # @param [Fixnum] port Port to check status at
336
+ #
337
+ # @return [Result] Result of last status check
338
+ # @!visibility private
339
+ def bounce_service host, service, curl_retries = nil, port = nil
340
+ curl_retries = 120 if curl_retries.nil?
341
+ port = options[:puppetserver_port] if port.nil?
342
+ if host.graceful_restarts?
343
+ service = host.check_for_command('apache2ctl') ? 'apache2ctl' : 'apachectl'
344
+ apachectl_path = host.is_pe? ? "#{host['puppetsbindir']}/#{service}" : service
345
+ host.exec(Command.new("#{apachectl_path} graceful"))
346
+ else
347
+ result = host.exec(Command.new("service #{service} reload"),
348
+ :acceptable_exit_codes => [0,1,3])
349
+ if result.exit_code == 0
350
+ return result
351
+ else
352
+ host.exec puppet_resource('service', service, 'ensure=stopped')
353
+ host.exec puppet_resource('service', service, 'ensure=running')
354
+ end
355
+ end
356
+ curl_with_retries(" #{service} ", host, "https://localhost:#{port}", [35, 60], curl_retries)
357
+ end
358
+
359
+ # Runs 'puppet apply' on a remote host, piping manifest through stdin
360
+ #
361
+ # @param [Host] host The host that this command should be run on
362
+ #
363
+ # @param [String] manifest The puppet manifest to apply
364
+ #
365
+ # @!macro common_opts
366
+ # @option opts [Boolean] :parseonly (false) If this key is true, the
367
+ # "--parseonly" command line parameter will
368
+ # be passed to the 'puppet apply' command.
369
+ #
370
+ # @option opts [Boolean] :trace (false) If this key exists in the Hash,
371
+ # the "--trace" command line parameter will be
372
+ # passed to the 'puppet apply' command.
373
+ #
374
+ # @option opts [Array<Integer>] :acceptable_exit_codes ([0]) The list of exit
375
+ # codes that will NOT raise an error when found upon
376
+ # command completion. If provided, these values will
377
+ # be combined with those used in :catch_failures and
378
+ # :expect_failures to create the full list of
379
+ # passing exit codes.
380
+ #
381
+ # @option opts [Hash] :environment Additional environment variables to be
382
+ # passed to the 'puppet apply' command
383
+ #
384
+ # @option opts [Boolean] :catch_failures (false) By default `puppet
385
+ # --apply` will exit with 0, which does not count
386
+ # as a test failure, even if there were errors or
387
+ # changes when applying the manifest. This option
388
+ # enables detailed exit codes and causes a test
389
+ # failure if `puppet --apply` indicates there was
390
+ # a failure during its execution.
391
+ #
392
+ # @option opts [Boolean] :catch_changes (false) This option enables
393
+ # detailed exit codes and causes a test failure
394
+ # if `puppet --apply` indicates that there were
395
+ # changes or failures during its execution.
396
+ #
397
+ # @option opts [Boolean] :expect_changes (false) This option enables
398
+ # detailed exit codes and causes a test failure
399
+ # if `puppet --apply` indicates that there were
400
+ # no resource changes during its execution.
401
+ #
402
+ # @option opts [Boolean] :expect_failures (false) This option enables
403
+ # detailed exit codes and causes a test failure
404
+ # if `puppet --apply` indicates there were no
405
+ # failure during its execution.
406
+ #
407
+ # @option opts [Boolean] :future_parser (false) This option enables
408
+ # the future parser option that is available
409
+ # from Puppet verion 3.2
410
+ # By default it will use the 'current' parser.
411
+ #
412
+ # @option opts [Boolean] :noop (false) If this option exists, the
413
+ # the "--noop" command line parameter will be
414
+ # passed to the 'puppet apply' command.
415
+ #
416
+ # @option opts [String] :modulepath The search path for modules, as
417
+ # a list of directories separated by the system
418
+ # path separator character. (The POSIX path separator
419
+ # is ‘:’, and the Windows path separator is ‘;’.)
420
+ #
421
+ # @option opts [String] :debug (false) If this option exists,
422
+ # the "--debug" command line parameter
423
+ # will be passed to the 'puppet apply' command.
424
+ # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel.
425
+ #
426
+ # @param [Block] block This method will yield to a block of code passed
427
+ # by the caller; this can be used for additional
428
+ # validation, etc.
429
+ #
430
+ # @return [Array<Result>, Result, nil] An array of results, a result object,
431
+ # or nil. Check {#run_block_on} for more details on this.
432
+ def apply_manifest_on(host, manifest, opts = {}, &block)
433
+ block_on host, opts do | host |
434
+ on_options = {}
435
+ on_options[:acceptable_exit_codes] = Array(opts[:acceptable_exit_codes])
436
+
437
+ puppet_apply_opts = {}
438
+ if opts[:debug]
439
+ puppet_apply_opts[:debug] = nil
440
+ else
441
+ puppet_apply_opts[:verbose] = nil
442
+ end
443
+ puppet_apply_opts[:parseonly] = nil if opts[:parseonly]
444
+ puppet_apply_opts[:trace] = nil if opts[:trace]
445
+ puppet_apply_opts[:parser] = 'future' if opts[:future_parser]
446
+ puppet_apply_opts[:modulepath] = opts[:modulepath] if opts[:modulepath]
447
+ puppet_apply_opts[:noop] = nil if opts[:noop]
448
+
449
+ # From puppet help:
450
+ # "... an exit code of '2' means there were changes, an exit code of
451
+ # '4' means there were failures during the transaction, and an exit
452
+ # code of '6' means there were both changes and failures."
453
+ if [opts[:catch_changes],opts[:catch_failures],opts[:expect_failures],opts[:expect_changes]].compact.length > 1
454
+ raise(ArgumentError,
455
+ 'Cannot specify more than one of `catch_failures`, ' +
456
+ '`catch_changes`, `expect_failures`, or `expect_changes` ' +
457
+ 'for a single manifest')
458
+ end
459
+
460
+ if opts[:catch_changes]
461
+ puppet_apply_opts['detailed-exitcodes'] = nil
462
+
463
+ # We're after idempotency so allow exit code 0 only.
464
+ on_options[:acceptable_exit_codes] |= [0]
465
+ elsif opts[:catch_failures]
466
+ puppet_apply_opts['detailed-exitcodes'] = nil
467
+
468
+ # We're after only complete success so allow exit codes 0 and 2 only.
469
+ on_options[:acceptable_exit_codes] |= [0, 2]
470
+ elsif opts[:expect_failures]
471
+ puppet_apply_opts['detailed-exitcodes'] = nil
472
+
473
+ # We're after failures specifically so allow exit codes 1, 4, and 6 only.
474
+ on_options[:acceptable_exit_codes] |= [1, 4, 6]
475
+ elsif opts[:expect_changes]
476
+ puppet_apply_opts['detailed-exitcodes'] = nil
477
+
478
+ # We're after changes specifically so allow exit code 2 only.
479
+ on_options[:acceptable_exit_codes] |= [2]
480
+ else
481
+ # Either use the provided acceptable_exit_codes or default to [0]
482
+ on_options[:acceptable_exit_codes] |= [0]
483
+ end
484
+
485
+ # Not really thrilled with this implementation, might want to improve it
486
+ # later. Basically, there is a magic trick in the constructor of
487
+ # PuppetCommand which allows you to pass in a Hash for the last value in
488
+ # the *args Array; if you do so, it will be treated specially. So, here
489
+ # we check to see if our caller passed us a hash of environment variables
490
+ # that they want to set for the puppet command. If so, we set the final
491
+ # value of *args to a new hash with just one entry (the value of which
492
+ # is our environment variables hash)
493
+ if opts.has_key?(:environment)
494
+ puppet_apply_opts['ENV'] = opts[:environment]
495
+ end
496
+
497
+ file_path = host.tmpfile('apply_manifest.pp')
498
+ create_remote_file(host, file_path, manifest + "\n")
499
+
500
+ if host[:default_apply_opts].respond_to? :merge
501
+ puppet_apply_opts = host[:default_apply_opts].merge( puppet_apply_opts )
502
+ end
503
+
504
+ on host, puppet('apply', file_path, puppet_apply_opts), on_options, &block
505
+ end
506
+ end
507
+
508
+ # Runs 'puppet apply' on default host, piping manifest through stdin
509
+ # @see #apply_manifest_on
510
+ def apply_manifest(manifest, opts = {}, &block)
511
+ apply_manifest_on(default, manifest, opts, &block)
512
+ end
513
+
514
+ # @deprecated
515
+ def run_agent_on(host, arg='--no-daemonize --verbose --onetime --test',
516
+ options={}, &block)
517
+ block_on host do | host |
518
+ on host, puppet_agent(arg), options, &block
519
+ end
520
+ end
521
+
522
+ # This method using the puppet resource 'host' will setup host aliases
523
+ # and register the remove of host aliases via Beaker::TestCase#teardown
524
+ #
525
+ # A teardown step is also added to make sure unstubbing of the host is
526
+ # removed always.
527
+ #
528
+ # @param [Host, Array<Host>, String, Symbol] machine One or more hosts to act upon,
529
+ # or a role (String or Symbol) that identifies one or more hosts.
530
+ # @param ip_spec [Hash{String=>String}] a hash containing the host to ip
531
+ # mappings
532
+ # @param alias_spec [Hash{String=>Array[String]] an hash containing the host to alias(es) mappings to apply
533
+ # @example Stub puppetlabs.com on the master to 127.0.0.1 with an alias example.com
534
+ # stub_hosts_on(master, {'puppetlabs.com' => '127.0.0.1'}, {'puppetlabs.com' => ['example.com']})
535
+ def stub_hosts_on(machine, ip_spec, alias_spec={})
536
+ block_on machine do | host |
537
+ ip_spec.each do |address, ip|
538
+ aliases = alias_spec[address] || []
539
+ manifest =<<-EOS.gsub /^\s+/, ""
540
+ host { '#{address}':
541
+ \tensure => present,
542
+ \tip => '#{ip}',
543
+ \thost_aliases => #{aliases},
544
+ }
545
+ EOS
546
+ logger.notify("Stubbing address #{address} to IP #{ip} on machine #{host}")
547
+ apply_manifest_on( host, manifest )
548
+ end
549
+
550
+ teardown do
551
+ ip_spec.each do |address, ip|
552
+ logger.notify("Unstubbing address #{address} to IP #{ip} on machine #{host}")
553
+ on( host, puppet('resource', 'host', address, 'ensure=absent') )
554
+ end
555
+ end
556
+ end
557
+ end
558
+
559
+ # This method accepts a block and using the puppet resource 'host' will
560
+ # setup host aliases before and after that block.
561
+ #
562
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
563
+ # or a role (String or Symbol) that identifies one or more hosts.
564
+ # @param ip_spec [Hash{String=>String}] a hash containing the host to ip
565
+ # mappings
566
+ # @param alias_spec [Hash{String=>Array[String]] an hash containing the host to alias(es) mappings to apply
567
+ # @example Stub forgeapi.puppetlabs.com on the master to 127.0.0.1 with an alias forgeapi.example.com
568
+ # with_host_stubbed_on(master, {'forgeapi.puppetlabs.com' => '127.0.0.1'}, {'forgeapi.puppetlabs.com' => ['forgeapi.example.com']}) do
569
+ # puppet( "module install puppetlabs-stdlib" )
570
+ # end
571
+ def with_host_stubbed_on(host, ip_spec, alias_spec={}, &block)
572
+ begin
573
+ block_on host do |host|
574
+ # this code is duplicated from the `stub_hosts_on` method. The
575
+ # `stub_hosts_on` method itself is not used here because this
576
+ # method is used by modules tests using `beaker-rspec`. Since
577
+ # the `stub_hosts_on` method contains a `teardown` step, it is
578
+ # incompatible with `beaker_rspec`.
579
+ ip_spec.each do |address, ip|
580
+ aliases = alias_spec[address] || []
581
+ manifest =<<-EOS.gsub /^\s+/, ""
582
+ host { '#{address}':
583
+ \tensure => present,
584
+ \tip => '#{ip}',
585
+ \thost_aliases => #{aliases},
586
+ }
587
+ EOS
588
+ logger.notify("Stubbing address #{address} to IP #{ip} on machine #{host}")
589
+ apply_manifest_on( host, manifest )
590
+ end
591
+ end
592
+
593
+ block.call
594
+
595
+ ensure
596
+ ip_spec.each do |address, ip|
597
+ logger.notify("Unstubbing address #{address} to IP #{ip} on machine #{host}")
598
+ on( host, puppet('resource', 'host', address, 'ensure=absent') )
599
+ end
600
+ end
601
+ end
602
+
603
+ # This method accepts a block and using the puppet resource 'host' will
604
+ # setup host aliases before and after that block on the default host
605
+ #
606
+ # @example Stub puppetlabs.com on the default host to 127.0.0.1
607
+ # stub_hosts('puppetlabs.com' => '127.0.0.1')
608
+ # @see #stub_hosts_on
609
+ def stub_hosts(ip_spec)
610
+ stub_hosts_on(default, ip_spec)
611
+ end
612
+
613
+ # This wraps the method `stub_hosts_on` and makes the stub specific to
614
+ # the forge alias.
615
+ #
616
+ # forge api v1 canonical source is forge.puppetlabs.com
617
+ # forge api v3 canonical source is forgeapi.puppetlabs.com
618
+ #
619
+ # @param machine [String] the host to perform the stub on
620
+ # @param forge_host [String] The URL to use as the forge alias, will default to using :forge_host in the
621
+ # global options hash
622
+ def stub_forge_on(machine, forge_host = nil)
623
+ #use global options hash
624
+ primary_forge_name = 'forge.puppetlabs.com'
625
+ forge_host ||= options[:forge_host]
626
+ forge_ip = resolve_hostname_on(machine, forge_host)
627
+ raise "Failed to resolve forge host '#{forge_host}'" unless forge_ip
628
+ @forge_ip ||= forge_ip
629
+ block_on machine do | host |
630
+ stub_hosts_on(host, {primary_forge_name => @forge_ip}, {primary_forge_name => ['forge.puppet.com','forgeapi.puppetlabs.com','forgeapi.puppet.com']})
631
+ end
632
+ end
633
+
634
+ # This wraps the method `with_host_stubbed_on` and makes the stub specific to
635
+ # the forge alias.
636
+ #
637
+ # forge api v1 canonical source is forge.puppetlabs.com
638
+ # forge api v3 canonical source is forgeapi.puppetlabs.com
639
+ #
640
+ # @param host [String] the host to perform the stub on
641
+ # @param forge_host [String] The URL to use as the forge alias, will default to using :forge_host in the
642
+ # global options hash
643
+ def with_forge_stubbed_on( host, forge_host = nil, &block )
644
+ #use global options hash
645
+ primary_forge_name = 'forge.puppetlabs.com'
646
+ forge_host ||= options[:forge_host]
647
+ forge_ip = resolve_hostname_on(host, forge_host)
648
+ raise "Failed to resolve forge host '#{forge_host}'" unless forge_ip
649
+ @forge_ip ||= forge_ip
650
+ with_host_stubbed_on( host, {primary_forge_name => @forge_ip}, {primary_forge_name => ['forge.puppet.com','forgeapi.puppetlabs.com','forgeapi.puppet.com']}, &block )
651
+ end
652
+
653
+ # This wraps `with_forge_stubbed_on` and provides it the default host
654
+ # @see with_forge_stubbed_on
655
+ def with_forge_stubbed( forge_host = nil, &block )
656
+ with_forge_stubbed_on( default, forge_host, &block )
657
+ end
658
+
659
+ # This wraps the method `stub_hosts` and makes the stub specific to
660
+ # the forge alias.
661
+ #
662
+ # @see #stub_forge_on
663
+ def stub_forge(forge_host = nil)
664
+ #use global options hash
665
+ forge_host ||= options[:forge_host]
666
+ stub_forge_on(default, forge_host)
667
+ end
668
+
669
+ # Waits until a successful curl check has happened against puppetdb
670
+ #
671
+ # @param [Host] host Host puppetdb is on
672
+ # @param [Fixnum] nonssl_port Port to make the HTTP status check over
673
+ # @param [Fixnum] ssl_port Port to make the HTTPS status check over
674
+ #
675
+ # @return [Result] Result of the last HTTPS status check
676
+ def sleep_until_puppetdb_started(host, nonssl_port = nil, ssl_port = nil)
677
+ nonssl_port = options[:puppetdb_port_nonssl] if nonssl_port.nil?
678
+ ssl_port = options[:puppetdb_port_ssl] if ssl_port.nil?
679
+ pe_ver = host['pe_ver'] || '0'
680
+ if version_is_less(pe_ver, '2016.1.0') then
681
+ # the status endpoint was introduced in puppetdb 4.0. The earliest
682
+ # PE release with the 4.x pdb version was 2016.1.0
683
+ endpoint = 'pdb/meta/v1/version'
684
+ expected_regex = '\"version\" \{0,\}: \{0,\}\"[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\"'
685
+ else
686
+ endpoint = 'status/v1/services/puppetdb-status'
687
+ expected_regex = '\"state\" \{0,\}: \{0,\}\"running\"'
688
+ end
689
+ retry_on(host,
690
+ "curl -m 1 http://localhost:#{nonssl_port}/#{endpoint} | grep '#{expected_regex}'",
691
+ {:max_retries => 120})
692
+ curl_with_retries("start puppetdb (ssl)",
693
+ host, "https://#{host.node_name}:#{ssl_port}", [35, 60])
694
+ end
695
+
696
+ # Waits until a successful curl check has happened against puppetserver
697
+ #
698
+ # @param [Host] host Host puppetserver is on
699
+ # @param [Fixnum] port Port to make the HTTPS status check over
700
+ #
701
+ # @return [Result] Result of the last HTTPS status check
702
+ def sleep_until_puppetserver_started(host, port = nil)
703
+ port = options[:puppetserver_port] if port.nil?
704
+ curl_with_retries("start puppetserver (ssl)",
705
+ host, "https://#{host.node_name}:#{port}", [35, 60])
706
+ end
707
+
708
+ # Waits until a successful curl check has happaned against node classifier
709
+ #
710
+ # @param [Host] host Host node classifier is on
711
+ # @param [Fixnum] port Port to make the HTTPS status check over
712
+ #
713
+ # @return [Result] Result of the last HTTPS status check
714
+ def sleep_until_nc_started(host, port = nil)
715
+ port = options[:nodeclassifier_port] if port.nil?
716
+ curl_with_retries("start nodeclassifier (ssl)",
717
+ host, "https://#{host.node_name}:#{port}", [35, 60])
718
+ end
719
+
720
+ #stops the puppet agent running on the host
721
+ # @param [Host, Array<Host>, String, Symbol] agent One or more hosts to act upon,
722
+ # or a role (String or Symbol) that identifies one or more hosts.
723
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
724
+ # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel.
725
+ def stop_agent_on(agent, opts = {})
726
+ block_on agent, opts do | host |
727
+ vardir = host.puppet_configprint['vardir']
728
+ agent_running = true
729
+ while agent_running
730
+ agent_running = host.file_exist?("#{vardir}/state/agent_catalog_run.lock")
731
+ if agent_running
732
+ sleep 2
733
+ end
734
+ end
735
+
736
+ # In 4.0 this was changed to just be `puppet`
737
+ agent_service = 'puppet'
738
+ if !aio_version?(host)
739
+ # The agent service is `pe-puppet` everywhere EXCEPT certain linux distros on PE 2.8
740
+ # In all the case that it is different, this init script will exist. So we can assume
741
+ # that if the script doesn't exist, we should just use `pe-puppet`
742
+ agent_service = 'pe-puppet-agent'
743
+ agent_service = 'pe-puppet' unless host.file_exist?('/etc/init.d/pe-puppet-agent')
744
+ end
745
+
746
+ # Under a number of stupid circumstances, we can't stop the
747
+ # agent using puppet. This is usually because of issues with
748
+ # the init script or system on that particular configuration.
749
+ avoid_puppet_at_all_costs = false
750
+ avoid_puppet_at_all_costs ||= host['platform'] =~ /el-4/
751
+ avoid_puppet_at_all_costs ||= host['pe_ver'] && version_is_less(host['pe_ver'], '3.2') && host['platform'] =~ /sles/
752
+
753
+ if avoid_puppet_at_all_costs
754
+ # When upgrading, puppet is already stopped. On EL4, this causes an exit code of '1'
755
+ on host, "/etc/init.d/#{agent_service} stop", :acceptable_exit_codes => [0, 1]
756
+ else
757
+ on host, puppet_resource('service', agent_service, 'ensure=stopped')
758
+ end
759
+ end
760
+ end
761
+
762
+ #stops the puppet agent running on the default host
763
+ # @see #stop_agent_on
764
+ def stop_agent
765
+ stop_agent_on(default)
766
+ end
767
+
768
+ #wait for a given host to appear in the dashboard
769
+ # @deprecated this method should be removed in the next release since we don't believe the check is necessary.
770
+ def wait_for_host_in_dashboard(host)
771
+
772
+ hostname = host.node_name
773
+ hostcert = dashboard.puppet['hostcert']
774
+ key = dashboard.puppet['hostprivkey']
775
+ cacert = dashboard.puppet['localcacert']
776
+ retry_on(dashboard, "curl --cert #{hostcert} --key #{key} --cacert #{cacert}\
777
+ https://#{dashboard}:4433/classifier-api/v1/nodes | grep '\"name\":\"#{hostname}\"'")
778
+ end
779
+
780
+ # Ensure the host has requested a cert, then sign it
781
+ #
782
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts, or a role (String or Symbol)
783
+ # that identifies one or more hosts to validate certificate signing.
784
+ # No argument, or an empty array means no validation of success
785
+ # for specific hosts will be performed. This will always execute
786
+ # 'cert --sign --all --allow-dns-alt-names' even for a single host.
787
+ #
788
+ # @return nil
789
+ # @raise [FailTest] if process times out
790
+ def sign_certificate_for(host = [])
791
+ hostnames = []
792
+ hosts = host.is_a?(Array) ? host : [host]
793
+ hosts.each{ |current_host|
794
+ if [master, dashboard, database].include? current_host
795
+
796
+ on current_host, puppet( 'agent -t' ), :acceptable_exit_codes => [0,1,2]
797
+ on master, puppet( "cert --allow-dns-alt-names sign #{current_host}" ), :acceptable_exit_codes => [0,24]
798
+
799
+ else
800
+ hostnames << Regexp.escape( current_host.node_name )
801
+ end
802
+ }
803
+ if hostnames.size < 1
804
+ on master, puppet("cert --sign --all --allow-dns-alt-names"),
805
+ :acceptable_exit_codes => [0,24]
806
+ return
807
+ end
808
+ while hostnames.size > 0
809
+ last_sleep = 0
810
+ next_sleep = 1
811
+ (0..10).each do |i|
812
+ if i == 10
813
+ fail_test("Failed to sign cert for #{hostnames}")
814
+ hostnames.clear
815
+ end
816
+ on master, puppet("cert --sign --all --allow-dns-alt-names"), :acceptable_exit_codes => [0,24]
817
+ out = on(master, puppet("cert --list --all")).stdout
818
+ if hostnames.all? { |hostname| out =~ /\+ "?#{hostname}"?/ }
819
+ hostnames.clear
820
+ break
821
+ end
822
+
823
+ sleep next_sleep
824
+ (last_sleep, next_sleep) = next_sleep, last_sleep+next_sleep
825
+ end
826
+ end
827
+ host
828
+ end
829
+
830
+ #prompt the master to sign certs then check to confirm the cert for the default host is signed
831
+ #@see #sign_certificate_for
832
+ def sign_certificate
833
+ sign_certificate_for(default)
834
+ end
835
+
836
+ # Create a temp directory on remote host with a user. Default user
837
+ # is puppet master user.
838
+ #
839
+ # @param [Host] host A single remote host on which to create and adjust
840
+ # the ownership of a temp directory.
841
+ # @param [String] name A remote path prefix for the new temp
842
+ # directory. Default value is '/tmp/beaker'
843
+ # @param [String] user The name of user that should own the temp
844
+ # directory. If no username is specified, use `puppet master
845
+ # --configprint user` to obtain username from master. Raise RuntimeError
846
+ # if this puppet command returns a non-zero exit code.
847
+ #
848
+ # @return [String] Returns the name of the newly-created dir.
849
+ def create_tmpdir_for_user(host, name='/tmp/beaker', user=nil)
850
+ if not user
851
+ result = on host, puppet("master --configprint user")
852
+ if not result.exit_code == 0
853
+ raise "`puppet master --configprint` failed, check that puppet is installed on #{host} or explicitly pass in a user name."
854
+ end
855
+ user = result.stdout.strip
856
+ end
857
+
858
+ create_tmpdir_on(host, name, user)
859
+
860
+ end
861
+
862
+ end
863
+ end
864
+ end
865
+ end