beaker 2.7.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/HISTORY.md +121 -2
- data/lib/beaker/dsl.rb +2 -2
- data/lib/beaker/dsl/helpers.rb +13 -1429
- data/lib/beaker/dsl/helpers/facter_helpers.rb +48 -0
- data/lib/beaker/dsl/helpers/hiera_helpers.rb +71 -0
- data/lib/beaker/dsl/helpers/host_helpers.rb +506 -0
- data/lib/beaker/dsl/helpers/puppet_helpers.rb +698 -0
- data/lib/beaker/dsl/helpers/tk_helpers.rb +101 -0
- data/lib/beaker/dsl/helpers/web_helpers.rb +115 -0
- data/lib/beaker/dsl/install_utils.rb +8 -1570
- data/lib/beaker/dsl/install_utils/ezbake_utils.rb +256 -0
- data/lib/beaker/dsl/install_utils/module_utils.rb +237 -0
- data/lib/beaker/dsl/install_utils/pe_utils.rb +518 -0
- data/lib/beaker/dsl/install_utils/puppet_utils.rb +722 -0
- data/lib/beaker/dsl/outcomes.rb +0 -4
- data/lib/beaker/dsl/roles.rb +0 -3
- data/lib/beaker/dsl/structure.rb +127 -4
- data/lib/beaker/dsl/wrappers.rb +0 -4
- data/lib/beaker/host.rb +23 -0
- data/lib/beaker/host/unix/pkg.rb +4 -4
- data/lib/beaker/host_prebuilt_steps.rb +11 -5
- data/lib/beaker/hypervisor/vagrant.rb +1 -0
- data/lib/beaker/hypervisor/vmpooler.rb +38 -0
- data/lib/beaker/logger.rb +10 -4
- data/lib/beaker/network_manager.rb +5 -4
- data/lib/beaker/options/command_line_parser.rb +7 -0
- data/lib/beaker/shared.rb +2 -1
- data/lib/beaker/shared/semvar.rb +41 -0
- data/lib/beaker/test_suite.rb +20 -6
- data/lib/beaker/version.rb +1 -1
- data/spec/beaker/dsl/helpers/facter_helpers_spec.rb +59 -0
- data/spec/beaker/dsl/helpers/hiera_helpers_spec.rb +96 -0
- data/spec/beaker/dsl/helpers/host_helpers_spec.rb +413 -0
- data/spec/beaker/dsl/{helpers_spec.rb → helpers/puppet_helpers_spec.rb} +2 -611
- data/spec/beaker/dsl/helpers/tk_helpers_spec.rb +83 -0
- data/spec/beaker/dsl/helpers/web_helpers_spec.rb +60 -0
- data/spec/beaker/dsl/install_utils/module_utils_spec.rb +241 -0
- data/spec/beaker/dsl/install_utils/pe_utils_spec.rb +475 -0
- data/spec/beaker/dsl/install_utils/puppet_utils_spec.rb +523 -0
- data/spec/beaker/dsl/structure_spec.rb +108 -0
- data/spec/beaker/host_prebuilt_steps_spec.rb +44 -0
- data/spec/beaker/host_spec.rb +41 -0
- data/spec/beaker/hypervisor/vagrant_spec.rb +2 -1
- data/spec/beaker/logger_spec.rb +9 -2
- data/spec/beaker/network_manager_spec.rb +7 -1
- data/spec/beaker/options/command_line_parser_spec.rb +3 -2
- data/spec/beaker/shared/semvar_spec.rb +36 -0
- data/spec/beaker/test_suite_spec.rb +48 -0
- data/spec/mocks.rb +10 -0
- metadata +23 -5
- data/lib/beaker/dsl/ezbake_utils.rb +0 -259
- data/spec/beaker/dsl/install_utils_spec.rb +0 -1242
@@ -0,0 +1,698 @@
|
|
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
|
+
# @!macro common_opts
|
13
|
+
# @param [Hash{Symbol=>String}] opts Options to alter execution.
|
14
|
+
# @option opts [Boolean] :silent (false) Do not produce log output
|
15
|
+
# @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
|
16
|
+
# (or range) of integer exit codes that should be considered
|
17
|
+
# acceptable. An error will be thrown if the exit code does not
|
18
|
+
# match one of the values in this list.
|
19
|
+
# @option opts [Hash{String=>String}] :environment ({}) These will be
|
20
|
+
# treated as extra environment variables that should be set before
|
21
|
+
# running the command.
|
22
|
+
#
|
23
|
+
|
24
|
+
|
25
|
+
# Return the name of the puppet user.
|
26
|
+
#
|
27
|
+
# @param [Host] host One object that acts like a Beaker::Host
|
28
|
+
#
|
29
|
+
# @note This method assumes puppet is installed on the host.
|
30
|
+
#
|
31
|
+
def puppet_user(host)
|
32
|
+
return host.puppet('master')['user']
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the name of the puppet group.
|
36
|
+
#
|
37
|
+
# @param [Host] host One object that acts like a Beaker::Host
|
38
|
+
#
|
39
|
+
# @note This method assumes puppet is installed on the host.
|
40
|
+
#
|
41
|
+
def puppet_group(host)
|
42
|
+
return host.puppet('master')['group']
|
43
|
+
end
|
44
|
+
|
45
|
+
# Test Puppet running in a certain run mode with specific options.
|
46
|
+
# This ensures the following steps are performed:
|
47
|
+
# 1. The pre-test Puppet configuration is backed up
|
48
|
+
# 2. A new Puppet configuraton file is layed down
|
49
|
+
# 3. Puppet is started or restarted in the specified run mode
|
50
|
+
# 4. Ensure Puppet has started correctly
|
51
|
+
# 5. Further tests are yielded to
|
52
|
+
# 6. Revert Puppet to the pre-test state
|
53
|
+
# 7. Testing artifacts are saved in a folder named for the test
|
54
|
+
#
|
55
|
+
# @param [Host] host One object that act like Host
|
56
|
+
#
|
57
|
+
# @param [Hash{Symbol=>String}] conf_opts Represents puppet settings.
|
58
|
+
# Sections of the puppet.conf may be
|
59
|
+
# specified, if no section is specified the
|
60
|
+
# a puppet.conf file will be written with the
|
61
|
+
# options put in a section named after [mode]
|
62
|
+
# @option conf_opts [String] :__commandline_args__ A special setting for
|
63
|
+
# command_line arguments such as --debug or
|
64
|
+
# --logdest, which cannot be set in
|
65
|
+
# puppet.conf. For example:
|
66
|
+
#
|
67
|
+
# :__commandline_args__ => '--logdest /tmp/a.log'
|
68
|
+
#
|
69
|
+
# These will only be applied when starting a FOSS
|
70
|
+
# master, as a pe master is just bounced.
|
71
|
+
# @option conf_opts [Hash] :__service_args__ A special setting of options
|
72
|
+
# for controlling how the puppet master service is
|
73
|
+
# handled. The only setting currently is
|
74
|
+
# :bypass_service_script, which if set true will
|
75
|
+
# force stopping and starting a webrick master
|
76
|
+
# using the start_puppet_from_source_* methods,
|
77
|
+
# even if it seems the host has passenger.
|
78
|
+
# This is needed in FOSS tests to initialize
|
79
|
+
# SSL.
|
80
|
+
# @param [File] testdir The temporary directory which will hold backup
|
81
|
+
# configuration, and other test artifacts.
|
82
|
+
#
|
83
|
+
# @param [Block] block The point of this method, yields so
|
84
|
+
# tests may be ran. After the block is finished
|
85
|
+
# puppet will revert to a previous state.
|
86
|
+
#
|
87
|
+
# @example A simple use case to ensure a master is running
|
88
|
+
# with_puppet_running_on( master ) do
|
89
|
+
# ...tests that require a master...
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# @example Fully utilizing the possiblities of config options
|
93
|
+
# with_puppet_running_on( master,
|
94
|
+
# :main => {:logdest => '/var/blah'},
|
95
|
+
# :master => {:masterlog => '/elswhere'},
|
96
|
+
# :agent => {:server => 'localhost'} ) do
|
97
|
+
#
|
98
|
+
# ...tests to be ran...
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
def with_puppet_running_on host, conf_opts, testdir = host.tmpdir(File.basename(@path)), &block
|
102
|
+
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)
|
103
|
+
cmdline_args = conf_opts[:__commandline_args__]
|
104
|
+
service_args = conf_opts[:__service_args__] || {}
|
105
|
+
conf_opts = conf_opts.reject { |k,v| [:__commandline_args__, :__service_args__].include?(k) }
|
106
|
+
|
107
|
+
curl_retries = host['master-start-curl-retries'] || options['master-start-curl-retries']
|
108
|
+
logger.debug "Setting curl retries to #{curl_retries}"
|
109
|
+
|
110
|
+
if options[:is_puppetserver]
|
111
|
+
confdir = host.puppet('master')['confdir']
|
112
|
+
vardir = host.puppet('master')['vardir']
|
113
|
+
|
114
|
+
if cmdline_args
|
115
|
+
split_args = cmdline_args.split()
|
116
|
+
|
117
|
+
split_args.each do |arg|
|
118
|
+
case arg
|
119
|
+
when /--confdir=(.*)/
|
120
|
+
confdir = $1
|
121
|
+
when /--vardir=(.*)/
|
122
|
+
vardir = $1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
puppetserver_opts = { "jruby-puppet" => {
|
128
|
+
"master-conf-dir" => confdir,
|
129
|
+
"master-var-dir" => vardir,
|
130
|
+
}}
|
131
|
+
|
132
|
+
puppetserver_conf = File.join("#{host['puppetserver-confdir']}", "puppetserver.conf")
|
133
|
+
modify_tk_config(host, puppetserver_conf, puppetserver_opts)
|
134
|
+
end
|
135
|
+
begin
|
136
|
+
backup_file = backup_the_file(host, host.puppet('master')['confdir'], testdir, 'puppet.conf')
|
137
|
+
lay_down_new_puppet_conf host, conf_opts, testdir
|
138
|
+
|
139
|
+
if host.use_service_scripts? && !service_args[:bypass_service_script]
|
140
|
+
bounce_service( host, host['puppetservice'], curl_retries )
|
141
|
+
else
|
142
|
+
puppet_master_started = start_puppet_from_source_on!( host, cmdline_args )
|
143
|
+
end
|
144
|
+
|
145
|
+
yield self if block_given?
|
146
|
+
|
147
|
+
rescue Beaker::DSL::Assertions, Minitest::Assertion => early_assertion
|
148
|
+
fail_test(early_assertion)
|
149
|
+
rescue Exception => early_exception
|
150
|
+
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")
|
151
|
+
raise(original_exception)
|
152
|
+
|
153
|
+
ensure
|
154
|
+
begin
|
155
|
+
|
156
|
+
if host.use_service_scripts? && !service_args[:bypass_service_script]
|
157
|
+
restore_puppet_conf_from_backup( host, backup_file )
|
158
|
+
bounce_service( host, host['puppetservice'], curl_retries )
|
159
|
+
else
|
160
|
+
if puppet_master_started
|
161
|
+
stop_puppet_from_source_on( host )
|
162
|
+
else
|
163
|
+
dump_puppet_log(host)
|
164
|
+
end
|
165
|
+
restore_puppet_conf_from_backup( host, backup_file )
|
166
|
+
end
|
167
|
+
|
168
|
+
rescue Exception => teardown_exception
|
169
|
+
begin
|
170
|
+
if !host.is_pe?
|
171
|
+
dump_puppet_log(host)
|
172
|
+
end
|
173
|
+
rescue Exception => dumping_exception
|
174
|
+
logger.error("Raised during attempt to dump puppet logs: #{dumping_exception}")
|
175
|
+
end
|
176
|
+
|
177
|
+
if original_exception
|
178
|
+
logger.error("Raised during attempt to teardown with_puppet_running_on: #{teardown_exception}\n---\n")
|
179
|
+
raise original_exception
|
180
|
+
else
|
181
|
+
raise teardown_exception
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Test Puppet running in a certain run mode with specific options,
|
188
|
+
# on the default host
|
189
|
+
# @see #with_puppet_running_on
|
190
|
+
def with_puppet_running conf_opts, testdir = host.tmpdir(File.basename(@path)), &block
|
191
|
+
with_puppet_running_on(default, conf_opts, testdir, &block)
|
192
|
+
end
|
193
|
+
|
194
|
+
# @!visibility private
|
195
|
+
def restore_puppet_conf_from_backup( host, backup_file )
|
196
|
+
puppet_conf = host.puppet('master')['config']
|
197
|
+
|
198
|
+
if backup_file
|
199
|
+
host.exec( Command.new( "if [ -f '#{backup_file}' ]; then " +
|
200
|
+
"cat '#{backup_file}' > " +
|
201
|
+
"'#{puppet_conf}'; " +
|
202
|
+
"rm -f '#{backup_file}'; " +
|
203
|
+
"fi" ) )
|
204
|
+
else
|
205
|
+
host.exec( Command.new( "rm -f '#{puppet_conf}'" ))
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
# @!visibility private
|
211
|
+
def start_puppet_from_source_on! host, args = ''
|
212
|
+
host.exec( puppet( 'master', args ) )
|
213
|
+
|
214
|
+
logger.debug 'Waiting for the puppet master to start'
|
215
|
+
unless port_open_within?( host, 8140, 10 )
|
216
|
+
raise Beaker::DSL::FailTest, 'Puppet master did not start in a timely fashion'
|
217
|
+
end
|
218
|
+
logger.debug 'The puppet master has started'
|
219
|
+
return true
|
220
|
+
end
|
221
|
+
|
222
|
+
# @!visibility private
|
223
|
+
def stop_puppet_from_source_on( host )
|
224
|
+
pid = host.exec( Command.new('cat `puppet master --configprint pidfile`') ).stdout.chomp
|
225
|
+
host.exec( Command.new( "kill #{pid}" ) )
|
226
|
+
Timeout.timeout(10) do
|
227
|
+
while host.exec( Command.new( "kill -0 #{pid}"), :acceptable_exit_codes => [0,1] ).exit_code == 0 do
|
228
|
+
# until kill -0 finds no process and we know that puppet has finished cleaning up
|
229
|
+
sleep 1
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# @!visibility private
|
235
|
+
def dump_puppet_log(host)
|
236
|
+
syslogfile = case host['platform']
|
237
|
+
when /fedora|centos|el|redhat|scientific/ then '/var/log/messages'
|
238
|
+
when /ubuntu|debian|cumulus/ then '/var/log/syslog'
|
239
|
+
else return
|
240
|
+
end
|
241
|
+
|
242
|
+
logger.notify "\n*************************"
|
243
|
+
logger.notify "* Dumping master log *"
|
244
|
+
logger.notify "*************************"
|
245
|
+
host.exec( Command.new( "tail -n 100 #{syslogfile}" ), :acceptable_exit_codes => [0,1])
|
246
|
+
logger.notify "*************************\n"
|
247
|
+
end
|
248
|
+
|
249
|
+
# @!visibility private
|
250
|
+
def lay_down_new_puppet_conf( host, configuration_options, testdir )
|
251
|
+
puppetconf_main = host.puppet('master')['config']
|
252
|
+
puppetconf_filename = File.basename(puppetconf_main)
|
253
|
+
puppetconf_test = File.join(testdir, puppetconf_filename)
|
254
|
+
|
255
|
+
new_conf = puppet_conf_for( host, configuration_options )
|
256
|
+
create_remote_file host, puppetconf_test, new_conf.to_s
|
257
|
+
|
258
|
+
host.exec(
|
259
|
+
Command.new( "cat #{puppetconf_test} > #{puppetconf_main}" ),
|
260
|
+
:silent => true
|
261
|
+
)
|
262
|
+
host.exec( Command.new( "cat #{puppetconf_main}" ) )
|
263
|
+
end
|
264
|
+
|
265
|
+
# @!visibility private
|
266
|
+
def puppet_conf_for host, conf_opts
|
267
|
+
puppetconf = host.exec( Command.new( "cat #{host.puppet('master')['config']}" ) ).stdout
|
268
|
+
new_conf = IniFile.new( puppetconf ).merge( conf_opts )
|
269
|
+
|
270
|
+
new_conf
|
271
|
+
end
|
272
|
+
|
273
|
+
# @!visibility private
|
274
|
+
def bounce_service host, service, curl_retries = 120
|
275
|
+
if host.graceful_restarts?
|
276
|
+
apachectl_path = host.is_pe? ? "#{host['puppetsbindir']}/apache2ctl" : 'apache2ctl'
|
277
|
+
host.exec(Command.new("#{apachectl_path} graceful"))
|
278
|
+
else
|
279
|
+
host.exec puppet_resource('service', service, 'ensure=stopped')
|
280
|
+
host.exec puppet_resource('service', service, 'ensure=running')
|
281
|
+
end
|
282
|
+
curl_with_retries(" #{service} ", host, "https://localhost:8140", [35, 60], curl_retries)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Runs 'puppet apply' on a remote host, piping manifest through stdin
|
286
|
+
#
|
287
|
+
# @param [Host] host The host that this command should be run on
|
288
|
+
#
|
289
|
+
# @param [String] manifest The puppet manifest to apply
|
290
|
+
#
|
291
|
+
# @!macro common_opts
|
292
|
+
# @option opts [Boolean] :parseonly (false) If this key is true, the
|
293
|
+
# "--parseonly" command line parameter will
|
294
|
+
# be passed to the 'puppet apply' command.
|
295
|
+
#
|
296
|
+
# @option opts [Boolean] :trace (false) If this key exists in the Hash,
|
297
|
+
# the "--trace" command line parameter will be
|
298
|
+
# passed to the 'puppet apply' command.
|
299
|
+
#
|
300
|
+
# @option opts [Array<Integer>] :acceptable_exit_codes ([0]) The list of exit
|
301
|
+
# codes that will NOT raise an error when found upon
|
302
|
+
# command completion. If provided, these values will
|
303
|
+
# be combined with those used in :catch_failures and
|
304
|
+
# :expect_failures to create the full list of
|
305
|
+
# passing exit codes.
|
306
|
+
#
|
307
|
+
# @option opts [Hash] :environment Additional environment variables to be
|
308
|
+
# passed to the 'puppet apply' command
|
309
|
+
#
|
310
|
+
# @option opts [Boolean] :catch_failures (false) By default `puppet
|
311
|
+
# --apply` will exit with 0, which does not count
|
312
|
+
# as a test failure, even if there were errors or
|
313
|
+
# changes when applying the manifest. This option
|
314
|
+
# enables detailed exit codes and causes a test
|
315
|
+
# failure if `puppet --apply` indicates there was
|
316
|
+
# a failure during its execution.
|
317
|
+
#
|
318
|
+
# @option opts [Boolean] :catch_changes (false) This option enables
|
319
|
+
# detailed exit codes and causes a test failure
|
320
|
+
# if `puppet --apply` indicates that there were
|
321
|
+
# changes or failures during its execution.
|
322
|
+
#
|
323
|
+
# @option opts [Boolean] :expect_changes (false) This option enables
|
324
|
+
# detailed exit codes and causes a test failure
|
325
|
+
# if `puppet --apply` indicates that there were
|
326
|
+
# no resource changes during its execution.
|
327
|
+
#
|
328
|
+
# @option opts [Boolean] :expect_failures (false) This option enables
|
329
|
+
# detailed exit codes and causes a test failure
|
330
|
+
# if `puppet --apply` indicates there were no
|
331
|
+
# failure during its execution.
|
332
|
+
#
|
333
|
+
# @option opts [Boolean] :future_parser (false) This option enables
|
334
|
+
# the future parser option that is available
|
335
|
+
# from Puppet verion 3.2
|
336
|
+
# By default it will use the 'current' parser.
|
337
|
+
#
|
338
|
+
# @option opts [Boolean] :noop (false) If this option exists, the
|
339
|
+
# the "--noop" command line parameter will be
|
340
|
+
# passed to the 'puppet apply' command.
|
341
|
+
#
|
342
|
+
# @option opts [String] :modulepath The search path for modules, as
|
343
|
+
# a list of directories separated by the system
|
344
|
+
# path separator character. (The POSIX path separator
|
345
|
+
# is ‘:’, and the Windows path separator is ‘;’.)
|
346
|
+
#
|
347
|
+
# @option opts [String] :debug (false) If this option exists,
|
348
|
+
# the "--debug" command line parameter
|
349
|
+
# will be passed to the 'puppet apply' command.
|
350
|
+
#
|
351
|
+
# @param [Block] block This method will yield to a block of code passed
|
352
|
+
# by the caller; this can be used for additional
|
353
|
+
# validation, etc.
|
354
|
+
#
|
355
|
+
def apply_manifest_on(host, manifest, opts = {}, &block)
|
356
|
+
block_on host do | host |
|
357
|
+
on_options = {}
|
358
|
+
on_options[:acceptable_exit_codes] = Array(opts[:acceptable_exit_codes])
|
359
|
+
|
360
|
+
puppet_apply_opts = {}
|
361
|
+
if opts[:debug]
|
362
|
+
puppet_apply_opts[:debug] = nil
|
363
|
+
else
|
364
|
+
puppet_apply_opts[:verbose] = nil
|
365
|
+
end
|
366
|
+
puppet_apply_opts[:parseonly] = nil if opts[:parseonly]
|
367
|
+
puppet_apply_opts[:trace] = nil if opts[:trace]
|
368
|
+
puppet_apply_opts[:parser] = 'future' if opts[:future_parser]
|
369
|
+
puppet_apply_opts[:modulepath] = opts[:modulepath] if opts[:modulepath]
|
370
|
+
puppet_apply_opts[:noop] = nil if opts[:noop]
|
371
|
+
|
372
|
+
# From puppet help:
|
373
|
+
# "... an exit code of '2' means there were changes, an exit code of
|
374
|
+
# '4' means there were failures during the transaction, and an exit
|
375
|
+
# code of '6' means there were both changes and failures."
|
376
|
+
if [opts[:catch_changes],opts[:catch_failures],opts[:expect_failures],opts[:expect_changes]].compact.length > 1
|
377
|
+
raise(ArgumentError,
|
378
|
+
'Cannot specify more than one of `catch_failures`, ' +
|
379
|
+
'`catch_changes`, `expect_failures`, or `expect_changes` ' +
|
380
|
+
'for a single manifest')
|
381
|
+
end
|
382
|
+
|
383
|
+
if opts[:catch_changes]
|
384
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
385
|
+
|
386
|
+
# We're after idempotency so allow exit code 0 only.
|
387
|
+
on_options[:acceptable_exit_codes] |= [0]
|
388
|
+
elsif opts[:catch_failures]
|
389
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
390
|
+
|
391
|
+
# We're after only complete success so allow exit codes 0 and 2 only.
|
392
|
+
on_options[:acceptable_exit_codes] |= [0, 2]
|
393
|
+
elsif opts[:expect_failures]
|
394
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
395
|
+
|
396
|
+
# We're after failures specifically so allow exit codes 1, 4, and 6 only.
|
397
|
+
on_options[:acceptable_exit_codes] |= [1, 4, 6]
|
398
|
+
elsif opts[:expect_changes]
|
399
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
400
|
+
|
401
|
+
# We're after changes specifically so allow exit code 2 only.
|
402
|
+
on_options[:acceptable_exit_codes] |= [2]
|
403
|
+
else
|
404
|
+
# Either use the provided acceptable_exit_codes or default to [0]
|
405
|
+
on_options[:acceptable_exit_codes] |= [0]
|
406
|
+
end
|
407
|
+
|
408
|
+
# Not really thrilled with this implementation, might want to improve it
|
409
|
+
# later. Basically, there is a magic trick in the constructor of
|
410
|
+
# PuppetCommand which allows you to pass in a Hash for the last value in
|
411
|
+
# the *args Array; if you do so, it will be treated specially. So, here
|
412
|
+
# we check to see if our caller passed us a hash of environment variables
|
413
|
+
# that they want to set for the puppet command. If so, we set the final
|
414
|
+
# value of *args to a new hash with just one entry (the value of which
|
415
|
+
# is our environment variables hash)
|
416
|
+
if opts.has_key?(:environment)
|
417
|
+
puppet_apply_opts['ENV'] = opts[:environment]
|
418
|
+
end
|
419
|
+
|
420
|
+
file_path = host.tmpfile('apply_manifest.pp')
|
421
|
+
create_remote_file(host, file_path, manifest + "\n")
|
422
|
+
|
423
|
+
if host[:default_apply_opts].respond_to? :merge
|
424
|
+
puppet_apply_opts = host[:default_apply_opts].merge( puppet_apply_opts )
|
425
|
+
end
|
426
|
+
|
427
|
+
on host, puppet('apply', file_path, puppet_apply_opts), on_options, &block
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Runs 'puppet apply' on default host, piping manifest through stdin
|
432
|
+
# @see #apply_manifest_on
|
433
|
+
def apply_manifest(manifest, opts = {}, &block)
|
434
|
+
apply_manifest_on(default, manifest, opts, &block)
|
435
|
+
end
|
436
|
+
|
437
|
+
# @deprecated
|
438
|
+
def run_agent_on(host, arg='--no-daemonize --verbose --onetime --test',
|
439
|
+
options={}, &block)
|
440
|
+
block_on host do | host |
|
441
|
+
on host, puppet_agent(arg), options, &block
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
# This method using the puppet resource 'host' will setup host aliases
|
446
|
+
# and register the remove of host aliases via Beaker::TestCase#teardown
|
447
|
+
#
|
448
|
+
# A teardown step is also added to make sure unstubbing of the host is
|
449
|
+
# removed always.
|
450
|
+
#
|
451
|
+
# @param [Host, Array<Host>, String, Symbol] machine One or more hosts to act upon,
|
452
|
+
# or a role (String or Symbol) that identifies one or more hosts.
|
453
|
+
# @param ip_spec [Hash{String=>String}] a hash containing the host to ip
|
454
|
+
# mappings
|
455
|
+
# @example Stub puppetlabs.com on the master to 127.0.0.1
|
456
|
+
# stub_hosts_on(master, 'puppetlabs.com' => '127.0.0.1')
|
457
|
+
def stub_hosts_on(machine, ip_spec)
|
458
|
+
block_on machine do | host |
|
459
|
+
ip_spec.each do |address, ip|
|
460
|
+
logger.notify("Stubbing address #{address} to IP #{ip} on machine #{host}")
|
461
|
+
on( host, puppet('resource', 'host', address, 'ensure=present', "ip=#{ip}") )
|
462
|
+
end
|
463
|
+
|
464
|
+
teardown do
|
465
|
+
ip_spec.each do |address, ip|
|
466
|
+
logger.notify("Unstubbing address #{address} to IP #{ip} on machine #{host}")
|
467
|
+
on( host, puppet('resource', 'host', address, 'ensure=absent') )
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# This method accepts a block and using the puppet resource 'host' will
|
474
|
+
# setup host aliases before and after that block.
|
475
|
+
#
|
476
|
+
# @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
|
477
|
+
# or a role (String or Symbol) that identifies one or more hosts.
|
478
|
+
# @param ip_spec [Hash{String=>String}] a hash containing the host to ip
|
479
|
+
# mappings
|
480
|
+
# @example Stub puppetlabs.com on the master to 127.0.0.1
|
481
|
+
# with_host_stubbed_on(master, 'forgeapi.puppetlabs.com' => '127.0.0.1') do
|
482
|
+
# puppet( "module install puppetlabs-stdlib" )
|
483
|
+
# end
|
484
|
+
def with_host_stubbed_on(host, ip_spec, &block)
|
485
|
+
begin
|
486
|
+
block_on host do |host|
|
487
|
+
ip_spec.each_pair do |address, ip|
|
488
|
+
logger.notify("Stubbing address #{address} to IP #{ip} on machine #{host}")
|
489
|
+
on( host, puppet('resource', 'host', address, 'ensure=present', "ip=#{ip}") )
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
block.call
|
494
|
+
|
495
|
+
ensure
|
496
|
+
ip_spec.each do |address, ip|
|
497
|
+
logger.notify("Unstubbing address #{address} to IP #{ip} on machine #{host}")
|
498
|
+
on( host, puppet('resource', 'host', address, 'ensure=absent') )
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# This method accepts a block and using the puppet resource 'host' will
|
504
|
+
# setup host aliases before and after that block on the default host
|
505
|
+
#
|
506
|
+
# @example Stub puppetlabs.com on the default host to 127.0.0.1
|
507
|
+
# stub_hosts('puppetlabs.com' => '127.0.0.1')
|
508
|
+
# @see #stub_hosts_on
|
509
|
+
def stub_hosts(ip_spec)
|
510
|
+
stub_hosts_on(default, ip_spec)
|
511
|
+
end
|
512
|
+
|
513
|
+
# This wraps the method `stub_hosts_on` and makes the stub specific to
|
514
|
+
# the forge alias.
|
515
|
+
#
|
516
|
+
# forge api v1 canonical source is forge.puppetlabs.com
|
517
|
+
# forge api v3 canonical source is forgeapi.puppetlabs.com
|
518
|
+
#
|
519
|
+
# @param machine [String] the host to perform the stub on
|
520
|
+
# @param forge_host [String] The URL to use as the forge alias, will default to using :forge_host in the
|
521
|
+
# global options hash
|
522
|
+
def stub_forge_on(machine, forge_host = nil)
|
523
|
+
#use global options hash
|
524
|
+
forge_host ||= options[:forge_host]
|
525
|
+
@forge_ip ||= Resolv.getaddress(forge_host)
|
526
|
+
block_on machine do | host |
|
527
|
+
stub_hosts_on(host, 'forge.puppetlabs.com' => @forge_ip)
|
528
|
+
stub_hosts_on(host, 'forgeapi.puppetlabs.com' => @forge_ip)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# This wraps the method `with_host_stubbed_on` and makes the stub specific to
|
533
|
+
# the forge alias.
|
534
|
+
#
|
535
|
+
# forge api v1 canonical source is forge.puppetlabs.com
|
536
|
+
# forge api v3 canonical source is forgeapi.puppetlabs.com
|
537
|
+
#
|
538
|
+
# @param host [String] the host to perform the stub on
|
539
|
+
# @param forge_host [String] The URL to use as the forge alias, will default to using :forge_host in the
|
540
|
+
# global options hash
|
541
|
+
def with_forge_stubbed_on( host, forge_host = nil, &block )
|
542
|
+
#use global options hash
|
543
|
+
forge_host ||= options[:forge_host]
|
544
|
+
@forge_ip ||= Resolv.getaddress(forge_host)
|
545
|
+
with_host_stubbed_on( host,
|
546
|
+
{'forge.puppetlabs.com' => @forge_ip,
|
547
|
+
'forgeapi.puppetlabs.com' => @forge_ip},
|
548
|
+
&block )
|
549
|
+
end
|
550
|
+
|
551
|
+
# This wraps `with_forge_stubbed_on` and provides it the default host
|
552
|
+
# @see with_forge_stubbed_on
|
553
|
+
def with_forge_stubbed( forge_host = nil, &block )
|
554
|
+
with_forge_stubbed_on( default, forge_host, &block )
|
555
|
+
end
|
556
|
+
|
557
|
+
# This wraps the method `stub_hosts` and makes the stub specific to
|
558
|
+
# the forge alias.
|
559
|
+
#
|
560
|
+
# @see #stub_forge_on
|
561
|
+
def stub_forge(forge_host = nil)
|
562
|
+
#use global options hash
|
563
|
+
forge_host ||= options[:forge_host]
|
564
|
+
stub_forge_on(default, forge_host)
|
565
|
+
end
|
566
|
+
|
567
|
+
def sleep_until_puppetdb_started(host)
|
568
|
+
curl_with_retries("start puppetdb", host, "http://localhost:8080", 0, 120)
|
569
|
+
curl_with_retries("start puppetdb (ssl)",
|
570
|
+
host, "https://#{host.node_name}:8081", [35, 60])
|
571
|
+
end
|
572
|
+
|
573
|
+
def sleep_until_puppetserver_started(host)
|
574
|
+
curl_with_retries("start puppetserver (ssl)",
|
575
|
+
host, "https://#{host.node_name}:8140", [35, 60])
|
576
|
+
end
|
577
|
+
|
578
|
+
def sleep_until_nc_started(host)
|
579
|
+
curl_with_retries("start nodeclassifier (ssl)",
|
580
|
+
host, "https://#{host.node_name}:4433", [35, 60])
|
581
|
+
end
|
582
|
+
#stops the puppet agent running on the host
|
583
|
+
# @param [Host, Array<Host>, String, Symbol] agent One or more hosts to act upon,
|
584
|
+
# or a role (String or Symbol) that identifies one or more hosts.
|
585
|
+
def stop_agent_on(agent)
|
586
|
+
block_on agent do | host |
|
587
|
+
vardir = agent.puppet['vardir']
|
588
|
+
agent_running = true
|
589
|
+
while agent_running
|
590
|
+
agent_running = agent.file_exist?("#{vardir}/state/agent_catalog_run.lock")
|
591
|
+
if agent_running
|
592
|
+
sleep 2
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# The agent service is `pe-puppet` everywhere EXCEPT certain linux distros on PE 2.8
|
597
|
+
# In all the case that it is different, this init script will exist. So we can assume
|
598
|
+
# that if the script doesn't exist, we should just use `pe-puppet`
|
599
|
+
agent_service = 'pe-puppet-agent'
|
600
|
+
agent_service = 'pe-puppet' unless agent.file_exist?('/etc/init.d/pe-puppet-agent')
|
601
|
+
|
602
|
+
# Under a number of stupid circumstances, we can't stop the
|
603
|
+
# agent using puppet. This is usually because of issues with
|
604
|
+
# the init script or system on that particular configuration.
|
605
|
+
avoid_puppet_at_all_costs = false
|
606
|
+
avoid_puppet_at_all_costs ||= agent['platform'] =~ /el-4/
|
607
|
+
avoid_puppet_at_all_costs ||= agent['pe_ver'] && version_is_less(agent['pe_ver'], '3.2') && agent['platform'] =~ /sles/
|
608
|
+
|
609
|
+
if avoid_puppet_at_all_costs
|
610
|
+
# When upgrading, puppet is already stopped. On EL4, this causes an exit code of '1'
|
611
|
+
on agent, "/etc/init.d/#{agent_service} stop", :acceptable_exit_codes => [0, 1]
|
612
|
+
else
|
613
|
+
on agent, puppet_resource('service', agent_service, 'ensure=stopped')
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
#stops the puppet agent running on the default host
|
619
|
+
# @see #stop_agent_on
|
620
|
+
def stop_agent
|
621
|
+
stop_agent_on(default)
|
622
|
+
end
|
623
|
+
|
624
|
+
#wait for a given host to appear in the dashboard
|
625
|
+
def wait_for_host_in_dashboard(host)
|
626
|
+
hostname = host.node_name
|
627
|
+
retry_on(dashboard, "! curl --tlsv1 -k -I https://#{dashboard}/nodes/#{hostname} | grep '404 Not Found'")
|
628
|
+
end
|
629
|
+
|
630
|
+
# Ensure the host has requested a cert, then sign it
|
631
|
+
#
|
632
|
+
# @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
|
633
|
+
# or a role (String or Symbol) that identifies one or more hosts.
|
634
|
+
#
|
635
|
+
# @return nil
|
636
|
+
# @raise [FailTest] if process times out
|
637
|
+
def sign_certificate_for(host)
|
638
|
+
block_on host do | host |
|
639
|
+
if [master, dashboard, database].include? host
|
640
|
+
|
641
|
+
on host, puppet( 'agent -t' ), :acceptable_exit_codes => [0,1,2]
|
642
|
+
on master, puppet( "cert --allow-dns-alt-names sign #{host}" ), :acceptable_exit_codes => [0,24]
|
643
|
+
|
644
|
+
else
|
645
|
+
|
646
|
+
hostname = Regexp.escape host.node_name
|
647
|
+
|
648
|
+
last_sleep = 0
|
649
|
+
next_sleep = 1
|
650
|
+
(0..10).each do |i|
|
651
|
+
fail_test("Failed to sign cert for #{hostname}") if i == 10
|
652
|
+
|
653
|
+
on master, puppet("cert --sign --all --allow-dns-alt-names"), :acceptable_exit_codes => [0,24]
|
654
|
+
break if on(master, puppet("cert --list --all")).stdout =~ /\+ "?#{hostname}"?/
|
655
|
+
sleep next_sleep
|
656
|
+
(last_sleep, next_sleep) = next_sleep, last_sleep+next_sleep
|
657
|
+
end
|
658
|
+
|
659
|
+
end
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
#prompt the master to sign certs then check to confirm the cert for the default host is signed
|
664
|
+
#@see #sign_certificate_for
|
665
|
+
def sign_certificate
|
666
|
+
sign_certificate_for(default)
|
667
|
+
end
|
668
|
+
|
669
|
+
# Create a temp directory on remote host with a user. Default user
|
670
|
+
# is puppet master user.
|
671
|
+
#
|
672
|
+
# @param [Host] host A single remote host on which to create and adjust
|
673
|
+
# the ownership of a temp directory.
|
674
|
+
# @param [String] name A remote path prefix for the new temp
|
675
|
+
# directory. Default value is '/tmp/beaker'
|
676
|
+
# @param [String] user The name of user that should own the temp
|
677
|
+
# directory. If no username is specified, use `puppet master
|
678
|
+
# --configprint user` to obtain username from master. Raise RuntimeError
|
679
|
+
# if this puppet command returns a non-zero exit code.
|
680
|
+
#
|
681
|
+
# @return [String] Returns the name of the newly-created dir.
|
682
|
+
def create_tmpdir_for_user(host, name='/tmp/beaker', user=nil)
|
683
|
+
if not user
|
684
|
+
result = on host, puppet("master --configprint user")
|
685
|
+
if not result.exit_code == 0
|
686
|
+
raise "`puppet master --configprint` failed, check that puppet is installed on #{host} or explicitly pass in a user name."
|
687
|
+
end
|
688
|
+
user = result.stdout.strip
|
689
|
+
end
|
690
|
+
|
691
|
+
create_tmpdir_on(host, name, user)
|
692
|
+
|
693
|
+
end
|
694
|
+
|
695
|
+
end
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|