beaker-puppet 0.1.0

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