beaker 0.0.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 (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +14 -0
  5. data/DOCUMENTING.md +167 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +17 -0
  8. data/README.md +332 -0
  9. data/Rakefile +121 -0
  10. data/beaker.gemspec +42 -0
  11. data/beaker.rb +10 -0
  12. data/bin/beaker +9 -0
  13. data/lib/beaker.rb +36 -0
  14. data/lib/beaker/answers.rb +29 -0
  15. data/lib/beaker/answers/version28.rb +104 -0
  16. data/lib/beaker/answers/version30.rb +194 -0
  17. data/lib/beaker/cli.rb +113 -0
  18. data/lib/beaker/command.rb +241 -0
  19. data/lib/beaker/command_factory.rb +21 -0
  20. data/lib/beaker/dsl.rb +85 -0
  21. data/lib/beaker/dsl/assertions.rb +87 -0
  22. data/lib/beaker/dsl/helpers.rb +625 -0
  23. data/lib/beaker/dsl/install_utils.rb +299 -0
  24. data/lib/beaker/dsl/outcomes.rb +99 -0
  25. data/lib/beaker/dsl/roles.rb +97 -0
  26. data/lib/beaker/dsl/structure.rb +63 -0
  27. data/lib/beaker/dsl/wrappers.rb +100 -0
  28. data/lib/beaker/host.rb +193 -0
  29. data/lib/beaker/host/aix.rb +15 -0
  30. data/lib/beaker/host/aix/file.rb +16 -0
  31. data/lib/beaker/host/aix/group.rb +35 -0
  32. data/lib/beaker/host/aix/user.rb +32 -0
  33. data/lib/beaker/host/unix.rb +54 -0
  34. data/lib/beaker/host/unix/exec.rb +15 -0
  35. data/lib/beaker/host/unix/file.rb +16 -0
  36. data/lib/beaker/host/unix/group.rb +40 -0
  37. data/lib/beaker/host/unix/pkg.rb +22 -0
  38. data/lib/beaker/host/unix/user.rb +32 -0
  39. data/lib/beaker/host/windows.rb +44 -0
  40. data/lib/beaker/host/windows/exec.rb +18 -0
  41. data/lib/beaker/host/windows/file.rb +15 -0
  42. data/lib/beaker/host/windows/group.rb +36 -0
  43. data/lib/beaker/host/windows/pkg.rb +26 -0
  44. data/lib/beaker/host/windows/user.rb +32 -0
  45. data/lib/beaker/hypervisor.rb +37 -0
  46. data/lib/beaker/hypervisor/aixer.rb +52 -0
  47. data/lib/beaker/hypervisor/blimper.rb +123 -0
  48. data/lib/beaker/hypervisor/fusion.rb +56 -0
  49. data/lib/beaker/hypervisor/solaris.rb +65 -0
  50. data/lib/beaker/hypervisor/vagrant.rb +118 -0
  51. data/lib/beaker/hypervisor/vcloud.rb +175 -0
  52. data/lib/beaker/hypervisor/vsphere.rb +80 -0
  53. data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
  54. data/lib/beaker/logger.rb +167 -0
  55. data/lib/beaker/network_manager.rb +73 -0
  56. data/lib/beaker/options_parsing.rb +323 -0
  57. data/lib/beaker/result.rb +55 -0
  58. data/lib/beaker/shared.rb +15 -0
  59. data/lib/beaker/shared/error_handler.rb +17 -0
  60. data/lib/beaker/shared/host_handler.rb +46 -0
  61. data/lib/beaker/shared/repetition.rb +28 -0
  62. data/lib/beaker/ssh_connection.rb +198 -0
  63. data/lib/beaker/test_case.rb +225 -0
  64. data/lib/beaker/test_config.rb +148 -0
  65. data/lib/beaker/test_suite.rb +288 -0
  66. data/lib/beaker/utils.rb +7 -0
  67. data/lib/beaker/utils/ntp_control.rb +42 -0
  68. data/lib/beaker/utils/repo_control.rb +92 -0
  69. data/lib/beaker/utils/setup_helper.rb +77 -0
  70. data/lib/beaker/utils/validator.rb +27 -0
  71. data/spec/beaker/command_spec.rb +94 -0
  72. data/spec/beaker/dsl/assertions_spec.rb +104 -0
  73. data/spec/beaker/dsl/helpers_spec.rb +230 -0
  74. data/spec/beaker/dsl/install_utils_spec.rb +70 -0
  75. data/spec/beaker/dsl/outcomes_spec.rb +43 -0
  76. data/spec/beaker/dsl/roles_spec.rb +86 -0
  77. data/spec/beaker/dsl/structure_spec.rb +60 -0
  78. data/spec/beaker/dsl/wrappers_spec.rb +52 -0
  79. data/spec/beaker/host_spec.rb +95 -0
  80. data/spec/beaker/logger_spec.rb +117 -0
  81. data/spec/beaker/options_parsing_spec.rb +37 -0
  82. data/spec/beaker/puppet_command_spec.rb +128 -0
  83. data/spec/beaker/ssh_connection_spec.rb +39 -0
  84. data/spec/beaker/test_case_spec.rb +6 -0
  85. data/spec/beaker/test_suite_spec.rb +44 -0
  86. data/spec/mocks_and_helpers.rb +34 -0
  87. data/spec/spec_helper.rb +15 -0
  88. metadata +359 -0
@@ -0,0 +1,87 @@
1
+ require 'test/unit/assertions'
2
+
3
+ module Beaker
4
+ module DSL
5
+ # Any custom assertions for Test::Unit or minitest live here. You may
6
+ # include them in your own testing if you wish, override them, or re-open
7
+ # the class to register new ones for use within
8
+ # {Beaker::TestCase}.
9
+ #
10
+ # You may use any test/unit assertion within your assertion. The
11
+ # assertion below assumes access to the method #result which will
12
+ # contain the output (according to the interface defined in
13
+ # {Beaker::Result}). When writing your own, to make them more
14
+ # portable and less brittle it is recommended that you pass the result
15
+ # or direct object for asserting against into your assertion.
16
+ #
17
+ module Assertions
18
+ include Test::Unit::Assertions
19
+
20
+ # Make assertions about the content of console output.
21
+ #
22
+ # By default, each line of +output+ is assumed to come from STDOUT.
23
+ # You may specify the stream explicitly by annotating the line with a
24
+ # stream marker. (If your line literally requires any stream marker at
25
+ # the beginning of a line, you must prefix the line with an explicit
26
+ # stream marker.) The currently recognized markers are:
27
+ #
28
+ # * "STDOUT> "
29
+ # * "STDERR> "
30
+ # * "OUT> "
31
+ # * "ERR> "
32
+ # * "1> "
33
+ # * "2> "
34
+ #
35
+ # Any leading common indentation is automatically removed from the
36
+ # +output+ parameter. For cases where this matters (e.g. every line
37
+ # should be indented), you should prefix the line with an explicit
38
+ # stream marker.
39
+ #
40
+ # @example Assert order of interleaved output streams
41
+ # !!!plain
42
+ # assert_output <<-CONSOLE
43
+ # STDOUT> 0123456789
44
+ # STDERR> ^- This is left aligned
45
+ # STDOUT> 01234567890
46
+ # STDERR> ^- This is indented 2 characters.
47
+ # CONSOLE
48
+ #
49
+ # @example Assert all content went to STDOUT
50
+ # !!!plain
51
+ # assert_output <<-CONSOLE
52
+ # 0123456789
53
+ # ^- This is left aligned
54
+ # 01234567890
55
+ # ^- This is indented 2 characters.
56
+ # CONSOLE
57
+ #
58
+ # @param [String] exp_out The expected console output, optionally
59
+ # annotated with stream markers.
60
+ # @param [String] msg An explanatory message about why the test
61
+ # failure is relevant.
62
+ def assert_output(exp_out, msg='Output lines did not match')
63
+ # Remove the minimal consistent indentation from the input;
64
+ # useful for clean HEREDOCs.
65
+ indentation = exp_out.lines.map { |line| line[/^ */].length }.min
66
+ cleaned_exp = exp_out.gsub(/^ {#{indentation}}/, '')
67
+
68
+ # Divide output based on expected destination
69
+ out, err = cleaned_exp.lines.partition do |line|
70
+ line !~ /^((STD)?ERR|2)> /
71
+ end
72
+ our_out, our_err, our_output = [
73
+ out.join, err.join, cleaned_exp
74
+ ].map do |str|
75
+ str.gsub(/^((STD)?(ERR|OUT)|[12])> /, '')
76
+ end
77
+
78
+ # Exercise assertions about output
79
+ assert_equal our_output, (result.nil? ? '' : result.output), msg
80
+ assert_equal our_out, (result.nil? ? '' : result.stdout),
81
+ 'The contents of STDOUT did not match expectations'
82
+ assert_equal our_err, (result.nil? ? '' : result.stderr),
83
+ 'The contents of STDERR did not match expectations'
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,625 @@
1
+ require 'resolv'
2
+ require 'beaker/dsl/outcomes'
3
+
4
+ module Beaker
5
+ module DSL
6
+ # This is the heart of the Puppet Acceptance DSL. Here you find a helper
7
+ # to proxy commands to hosts, more commands to move files between hosts
8
+ # and execute remote scripts, confine test cases to certain hosts and
9
+ # prepare the state of a test case.
10
+ #
11
+ # To mix this is into a class you need the following:
12
+ # * a method *hosts* that yields any hosts implementing
13
+ # {Beaker::Host}'s interface to act upon.
14
+ # * a method *logger* that yields a logger implementing
15
+ # {Beaker::Logger}'s interface.
16
+ #
17
+ #
18
+ # @api dsl
19
+ module Helpers
20
+ # @!macro common_opts
21
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
22
+ # @option opts [Boolean] :silent (false) Do not produce log output
23
+ # @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
24
+ # (or range) of integer exit codes that should be considered
25
+ # acceptable. An error will be thrown if the exit code does not
26
+ # match one of the values in this list.
27
+ # @option opts [Hash{String=>String}] :environment ({}) These will be
28
+ # treated as extra environment variables that should be set before
29
+ # running the command.
30
+ #
31
+
32
+ # The primary method for executing commands *on* some set of hosts.
33
+ #
34
+ # @param [Host, Array<Host>] host One or more hosts to act upon.
35
+ # @param [String, Command] command The command to execute on *host*.
36
+ # @param [Proc] block Additional actions or assertions.
37
+ # @!macro common_opts
38
+ #
39
+ # @example Most basic usage
40
+ # on hosts, 'ls /tmp'
41
+ #
42
+ # @example Allowing additional exit codes to pass
43
+ # on agents, 'puppet agent -t', :acceptable_exit_codes => [0,2]
44
+ #
45
+ # @example Using the returned result for any kind of checking
46
+ # if on(host, 'ls -la ~').stdout =~ /\.bin/
47
+ # ...do some action...
48
+ # end
49
+ #
50
+ # @example Using TestCase helpers from within a test.
51
+ # agents.each do |agent|
52
+ # on agent, 'cat /etc/puppet/puppet.conf' do
53
+ # assert_match stdout, /server = #{master}/, 'WTF Mate'
54
+ # end
55
+ # end
56
+ #
57
+ # @return [Result] An object representing the outcome of *command*.
58
+ # @raise [FailTest] Raises an exception if *command* obviously fails.
59
+ def on(host, command, opts = {}, &block)
60
+ unless command.is_a? Command
61
+ cmd_opts = opts[:environment] ? { 'ENV' => opts.delete(:environment) } : Hash.new
62
+ command = Command.new(command.to_s, [], cmd_opts)
63
+ end
64
+ if host.is_a? Array
65
+ host.map { |h| on h, command, opts, &block }
66
+ else
67
+ @result = host.exec(command, opts)
68
+
69
+ # Also, let additional checking be performed by the caller.
70
+ yield self if block_given?
71
+
72
+ return @result
73
+ end
74
+ end
75
+
76
+ # Move a file from a remote to a local path
77
+ # @note If using {Beaker::Host} for the hosts *scp* is not
78
+ # required on the system as it uses Ruby's net/scp library. The
79
+ # net-scp gem however is required (and specified in the gemspec).
80
+ #
81
+ # @param [Host, #do_scp_from] host One or more hosts (or some object
82
+ # that responds like
83
+ # {Beaker::Host#do_scp_from}.
84
+ # @param [String] from_path A remote path to a file.
85
+ # @param [String] to_path A local path to copy *from_path* to.
86
+ # @!macro common_opts
87
+ #
88
+ # @return [Result] Returns the result of the SCP operation
89
+ def scp_from host, from_path, to_path, opts = {}
90
+ if host.is_a? Array
91
+ host.each { |h| scp_from h, from_path, to_path, opts }
92
+ else
93
+ @result = host.do_scp_from(from_path, to_path, opts)
94
+ @result.log logger
95
+ end
96
+ end
97
+
98
+ # Move a local file to a remote host
99
+ # @note If using {Beaker::Host} for the hosts *scp* is not
100
+ # required on the system as it uses Ruby's net/scp library. The
101
+ # net-scp gem however is required (and specified in the gemspec.
102
+ #
103
+ # @param [Host, #do_scp_to] host One or more hosts (or some object
104
+ # that responds like
105
+ # {Beaker::Host#do_scp_to}.
106
+ # @param [String] from_path A local path to a file.
107
+ # @param [String] to_path A remote path to copy *from_path* to.
108
+ # @!macro common_opts
109
+ #
110
+ # @return [Result] Returns the result of the SCP operation
111
+ def scp_to host, from_path, to_path, opts = {}
112
+ if host.is_a? Array
113
+ host.each { |h| scp_to h, from_path, to_path, opts }
114
+ else
115
+ @result = host.do_scp_to(from_path, to_path, opts)
116
+ @result.log logger
117
+ end
118
+ end
119
+
120
+ # Check to see if a package is installed on a remote host
121
+ #
122
+ # @param [Host] host A host object
123
+ # @param [String] package_name Name of the package to check for.
124
+ #
125
+ # @return [Boolean] true/false if the package is found
126
+ def check_for_package host, package_name
127
+ host.check_for_package package_name
128
+ end
129
+
130
+ # Install a package on a host
131
+ #
132
+ # @param [Host] host A host object
133
+ # @param [String] package_name Name of the package to install
134
+ #
135
+ # @return [Result] An object representing the outcome of *install command*.
136
+ def install_package host, package_name
137
+ host.install_package package_name
138
+ end
139
+
140
+ # Create a remote file out of a string
141
+ # @note This method uses Tempfile in Ruby's STDLIB as well as {#scp_to}.
142
+ #
143
+ # @param [Host, #do_scp_to] hosts One or more hosts (or some object
144
+ # that responds like
145
+ # {Beaker::Host#do_scp_from}.
146
+ # @param [String] file_path A remote path to place *file_content* at.
147
+ # @param [String] file_content The contents of the file to be placed.
148
+ # @!macro common_opts
149
+ #
150
+ # @return [Result] Returns the result of the underlying SCP operation.
151
+ def create_remote_file(hosts, file_path, file_content, opts = {})
152
+ Tempfile.open 'beaker' do |tempfile|
153
+ File.open(tempfile.path, 'w') {|file| file.puts file_content }
154
+
155
+ scp_to hosts, tempfile.path, file_path, opts
156
+ end
157
+ end
158
+
159
+ # Move a local script to a remote host and execute it
160
+ # @note this relies on {#on} and {#scp_to}
161
+ #
162
+ # @param [Host, #do_scp_to] host One or more hosts (or some object
163
+ # that responds like
164
+ # {Beaker::Host#do_scp_from}.
165
+ # @param [String] script A local path to find an executable script at.
166
+ # @!macro common_opts
167
+ # @param [Proc] block Additional tests to run after script has executed
168
+ #
169
+ # @return [Result] Returns the result of the underlying SCP operation.
170
+ def run_script_on(host, script, opts = {}, &block)
171
+ # this is unsafe as it uses the File::SEPARATOR will be set to that
172
+ # of the coordinator node. This works for us because we use cygwin
173
+ # which will properly convert the paths. Otherwise this would not
174
+ # work for running tests on a windows machine when the coordinator
175
+ # that the harness is running on is *nix. We should use
176
+ # {Beaker::Host#temp_path} instead. TODO
177
+ remote_path = File.join("", "tmp", File.basename(script))
178
+
179
+ scp_to host, script, remote_path
180
+ on host, remote_path, opts, &block
181
+ end
182
+
183
+ # Limit the hosts a test case is run against
184
+ # @note This will modify the {Beaker::TestCase#hosts} member
185
+ # in place unless an array of hosts is passed into it and
186
+ # {Beaker::TestCase#logger} yielding an object that responds
187
+ # like {Beaker::Logger#warn}, as well as
188
+ # {Beaker::DSL::Outcomes#skip_test}, and optionally
189
+ # {Beaker::TestCase#hosts}.
190
+ #
191
+ # @param [Symbol] type The type of confinement to do. Valid parameters
192
+ # are *:to* to confine the hosts to only those that
193
+ # match *criteria* or *:except* to confine the test
194
+ # case to only those hosts that do not match
195
+ # criteria.
196
+ # @param [Hash{Symbol,String=>String,Regexp,Array<String,Regexp>}]
197
+ # criteria Specify the criteria with which a host should be
198
+ # considered for inclusion or exclusion. The key is any attribute
199
+ # of the host that will be yielded by {Beaker::Host#[]}.
200
+ # The value can be any string/regex or array of strings/regexp.
201
+ # The values are compared using {Enumerable#any?} so that if one
202
+ # value of an array matches the host is considered a match for that
203
+ # criteria.
204
+ # @param [Array<Host>] host_array This creatively named parameter is
205
+ # an optional array of hosts to confine to. If not passed in, this
206
+ # method will modify {Beaker::TestCase#hosts} in place.
207
+ # @param [Proc] block Addition checks to determine suitability of hosts
208
+ # for confinement. Each host that is still valid after checking
209
+ # *criteria* is then passed in turn into this block. The block
210
+ # should return true if the host matches this additional criteria.
211
+ #
212
+ # @example Basic usage to confine to debian OSes.
213
+ # confine :to, :platform => 'debian'
214
+ #
215
+ # @example Confining to anything but Windows and Solaris
216
+ # confine :except, :platform => ['windows', 'solaris']
217
+ #
218
+ # @example Using additional block to confine to Solaris global zone.
219
+ # confine :to, :platform => 'solaris' do |solaris|
220
+ # on( solaris, 'zonename' ) =~ /global/
221
+ # end
222
+ #
223
+ # @return [Array<Host>] Returns an array of hosts that are still valid
224
+ # targets for this tests case.
225
+ # @raise [SkipTest] Raises skip test if there are no valid hosts for
226
+ # this test case after confinement.
227
+ def confine(type, criteria, host_array = nil, &block)
228
+ provided_hosts = host_array ? true : false
229
+ hosts_to_modify = host_array || hosts
230
+ criteria.each_pair do |property, value|
231
+ case type
232
+ when :except
233
+ hosts_to_modify = hosts_to_modify.reject do |host|
234
+ inspect_host host, property, value
235
+ end
236
+ if block_given?
237
+ hosts_to_modify = hosts_to_modify.reject do |host|
238
+ yield host
239
+ end
240
+ end
241
+ when :to
242
+ hosts_to_modify = hosts_to_modify.select do |host|
243
+ inspect_host host, property, value
244
+ end
245
+ if block_given?
246
+ hosts_to_modify = hosts_to_modify.select do |host|
247
+ yield host
248
+ end
249
+ end
250
+ else
251
+ raise "Unknown option #{type}"
252
+ end
253
+ end
254
+ if hosts_to_modify.empty?
255
+ logger.warn "No suitable hosts with: #{criteria.inspect}"
256
+ skip_test 'No suitable hosts found'
257
+ end
258
+ self.hosts = hosts_to_modify
259
+ hosts_to_modify
260
+ end
261
+
262
+ # @!visibility private
263
+ def inspect_host(host, property, one_or_more_values)
264
+ values = Array(one_or_more_values)
265
+ return values.any? do |value|
266
+ true_false = false
267
+ case value
268
+ when String
269
+ true_false = host[property.to_s].include? value
270
+ when Regexp
271
+ true_false = host[property.to_s] =~ value
272
+ end
273
+ true_false
274
+ end
275
+ end
276
+
277
+
278
+ # Test Puppet running in a certain run mode with specific options.
279
+ # This ensures the following steps are performed:
280
+ # 1. The pre-test Puppet configuration is backed up
281
+ # 2. A new Puppet configuraton file is layed down
282
+ # 3. Puppet is started or restarted in the specified run mode
283
+ # 4. Ensure Puppet has started correctly
284
+ # 5. Further tests are yielded to
285
+ # 6. Revert Puppet to the pre-test state
286
+ # 7. Testing artifacts are saved in a folder named for the test
287
+ #
288
+ # @param [Host] hosts One object that act like Host
289
+ #
290
+ # @param [Hash{Symbol=>String}]
291
+ # config_opts Represent puppet settings.
292
+ # Sections of the puppet.conf may be
293
+ # specified, if no section is specified the
294
+ # a puppet.conf file will be written with the
295
+ # options put in a section named after [mode].
296
+ #
297
+ # @param [Block] block The point of this method, yields so
298
+ # tests may be ran. After the block is finished
299
+ # puppet will revert to a previous state.
300
+ #
301
+ # @example A simple use case to ensure a master is running
302
+ # with_puppet_running_on( master ) do
303
+ # ...tests that require a master...
304
+ # end
305
+ #
306
+ # @example Fully utilizing the possiblities of config options
307
+ # with_puppet_running_on( master,
308
+ # :main => {:logdest => '/var/blah'},
309
+ # :master => {:masterlog => '/elswhere'},
310
+ # :agent => {:server => 'localhost'} ) do
311
+ #
312
+ # ...tests to be ran...
313
+ # end
314
+ #
315
+ # @api dsl
316
+ def with_puppet_running_on host, conf_opts, testdir = host.tmpdir(File.basename(@path)), &block
317
+ begin
318
+ backup_file host, host['puppetpath'], testdir, 'puppet.conf'
319
+ lay_down_new_puppet_conf host, conf_opts, testdir
320
+
321
+ if host.is_pe?
322
+ bounce_service( 'pe-httpd' )
323
+
324
+ else
325
+ start_puppet_from_source_on!( host )
326
+ end
327
+
328
+ yield self if block_given?
329
+ ensure
330
+ restore_puppet_conf_from_backup( host )
331
+
332
+ if host.is_pe?
333
+ bounce_service( 'pe-httpd' )
334
+
335
+ else
336
+ stop_puppet_from_source_on( host )
337
+ end
338
+ end
339
+ end
340
+
341
+ # @!visibility private
342
+ def restore_puppet_conf_from_backup( host )
343
+ puppetpath = host['puppetpath']
344
+
345
+ host.exec( Command.new( "if [ -f #{puppetpath}/puppet.conf.bak ]; then " +
346
+ "cat #{puppetpath}/puppet.conf.bak > " +
347
+ "#{puppetpath}/puppet.conf; " +
348
+ "rm -rf #{puppetpath}/puppet.conf.bak; " +
349
+ "fi" ) )
350
+ end
351
+
352
+ # @!visibility private
353
+ def backup_file host, current_dir, new_dir, filename = 'puppet.conf'
354
+ old_location = current_dir + '/' + filename
355
+ new_location = new_dir + '/' + filename
356
+
357
+ host.exec( Command.new( "cp #{old_location} #{new_location}" ) )
358
+ end
359
+
360
+ # @!visibility private
361
+ def start_puppet_from_source_on! host
362
+ host.exec( Command.new( puppet( 'master' ) ) )
363
+
364
+ logger.debug 'Waiting for the puppet master to start'
365
+ unless port_open_within?( host, 8140, 10 )
366
+ raise Beaker::DSL::FailTest, 'Puppet master did not start in a timely fashion'
367
+ end
368
+ logger.debug 'The puppet master has started'
369
+ end
370
+
371
+ # @!visibility private
372
+ def stop_puppet_from_source_on( host )
373
+ host.exec( Command.new( 'kill $(cat `puppet master --configprint pidfile`)' ) )
374
+ end
375
+
376
+ # @!visibility private
377
+ def lay_down_new_puppet_conf( host, configuration_options, testdir )
378
+ new_conf = puppet_conf_for( host )
379
+ create_remote_file host, "#{testdir}/puppet.conf", new_conf.to_s
380
+
381
+ host.exec( Command.new( "cat #{testdir}/puppet.conf > #{host['puppetpath']}/puppet.conf", :silent => true ) )
382
+ host.exec( Command.new( "cat #{host['puppetpath']}/puppet.conf" ) )
383
+ end
384
+
385
+ # @!visibility private
386
+ def puppet_conf_for host, conf_opts
387
+ puppetconf = host.exec( Command.new( "cat #{host['puppetpath']}/puppet.conf" ) ).stdout
388
+ new_conf = IniFile.new( puppetconf ).merge( conf_opts )
389
+
390
+ new_conf
391
+ end
392
+
393
+ # @!visibility private
394
+ def bounce_service host, service
395
+ # Any reason to not
396
+ # host.exec puppet_resource( 'service', service, 'ensure=stopped' )
397
+ # host.exec puppet_resource( 'service', service, 'ensure=running' )
398
+ host.exec( Command.new( "/etc/init.d/#{service} restart" ) )
399
+ end
400
+
401
+ # Blocks until the port is open on the host specified, returns false
402
+ # on failure
403
+ def port_open_within?( host, port = 8140, seconds = 120 )
404
+ repeat_for( seconds ) do
405
+ host.port_open?( port )
406
+ end
407
+ end
408
+
409
+ # Runs 'puppet apply' on a remote host, piping manifest through stdin
410
+ #
411
+ # @param [Host] host The host that this command should be run on
412
+ #
413
+ # @param [String] manifest The puppet manifest to apply
414
+ #
415
+ # @!macro common_opts
416
+ # @option opts [Boolean] :parseonly (false) If this key is true, the
417
+ # "--parseonly" command line parameter will
418
+ # be passed to the 'puppet apply' command.
419
+ #
420
+ # @option opts [Boolean] :trace (false) If this key exists in the Hash,
421
+ # the "--trace" command line parameter will be
422
+ # passed to the 'puppet apply' command.
423
+ #
424
+ # @option opts [Boolean] :catch_failures (false) By default
425
+ # "puppet --apply" will exit with 0,
426
+ # which does not count as a test
427
+ # failure, even if there were errors applying
428
+ # the manifest. This option enables detailed
429
+ # exit codes and causes a test failure if
430
+ # "puppet --apply" indicates there was a
431
+ # failure during its execution.
432
+ #
433
+ # @param [Block] block This method will yield to a block of code passed
434
+ # by the caller; this can be used for additional
435
+ # validation, etc.
436
+ #
437
+ def apply_manifest_on(host, manifest, opts = {}, &block)
438
+ on_options = {:stdin => manifest + "\n"}
439
+ on_options[:acceptable_exit_codes] = opts.delete(:acceptable_exit_codes)
440
+ args = ["--verbose"]
441
+ args << "--parseonly" if opts[:parseonly]
442
+ args << "--trace" if opts[:trace]
443
+
444
+ if opts[:catch_failures]
445
+ args << '--detailed-exitcodes'
446
+
447
+ # From puppet help:
448
+ # "... an exit code of '2' means there were changes, an exit code of
449
+ # '4' means there were failures during the transaction, and an exit
450
+ # code of '6' means there were both changes and failures."
451
+ # We're after failures specifically so catch exit codes 4 and 6 only.
452
+ on_options[:acceptable_exit_codes] |= [0, 2]
453
+ end
454
+
455
+ # Not really thrilled with this implementation, might want to improve it
456
+ # later. Basically, there is a magic trick in the constructor of
457
+ # PuppetCommand which allows you to pass in a Hash for the last value in
458
+ # the *args Array; if you do so, it will be treated specially. So, here
459
+ # we check to see if our caller passed us a hash of environment variables
460
+ # that they want to set for the puppet command. If so, we set the final
461
+ # value of *args to a new hash with just one entry (the value of which
462
+ # is our environment variables hash)
463
+ if opts.has_key?(:environment)
464
+ args << { :environment => opts[:environment]}
465
+ end
466
+
467
+ on host, puppet( 'apply', *args), on_options, &block
468
+ end
469
+
470
+ # @deprecated
471
+ def run_agent_on(host, arg='--no-daemonize --verbose --onetime --test',
472
+ options={}, &block)
473
+ if host.is_a? Array
474
+ host.each { |h| run_agent_on h, arg, options, &block }
475
+ else
476
+ on host, puppet_agent(arg), options, &block
477
+ end
478
+ end
479
+
480
+ # FIX: this should be moved into host/platform
481
+ # @visibility private
482
+ def run_cron_on(host, action, user, entry="", &block)
483
+ platform = host['platform']
484
+ if platform.include?('solaris') || platform.include?('aix') then
485
+ case action
486
+ when :list then args = '-l'
487
+ when :remove then args = '-r'
488
+ when :add
489
+ on( host,
490
+ "echo '#{entry}' > /var/spool/cron/crontabs/#{user}",
491
+ &block )
492
+ end
493
+
494
+ else # default for GNU/Linux platforms
495
+ case action
496
+ when :list then args = '-l -u'
497
+ when :remove then args = '-r -u'
498
+ when :add
499
+ on( host,
500
+ "echo '#{entry}' > /tmp/#{user}.cron && " +
501
+ "crontab -u #{user} /tmp/#{user}.cron",
502
+ &block )
503
+ end
504
+ end
505
+
506
+ if args
507
+ case action
508
+ when :list, :remove then on(host, "crontab #{args} #{user}", &block)
509
+ end
510
+ end
511
+ end
512
+
513
+ # This method accepts a block and using the puppet resource 'host' will
514
+ # setup host aliases before and after that block.
515
+ #
516
+ # A teardown step is also added to make sure unstubbing of the host is
517
+ # removed always.
518
+ #
519
+ # @param machine [String] the host to execute this stub
520
+ # @param hosts [Hash{String=>String}] a hash containing the host to ip
521
+ # mappings
522
+ # @example Stub puppetlabs.com on the master to 127.0.0.1
523
+ # stub_hosts_on(master, 'puppetlabs.com' => '127.0.0.1')
524
+ def stub_hosts_on(machine, ip_spec)
525
+ ip_spec.each do |host, ip|
526
+ logger.notify("Stubbing host #{host} to IP #{ip} on machine #{machine}")
527
+ on( machine,
528
+ puppet('resource', 'host', host, 'ensure=present', "ip=#{ip}") )
529
+ end
530
+
531
+ teardown do
532
+ ip_spec.each do |host, ip|
533
+ logger.notify("Unstubbing host #{host} to IP #{ip} on machine #{machine}")
534
+ on( machine,
535
+ puppet('resource', 'host', host, 'ensure=absent') )
536
+ end
537
+ end
538
+ end
539
+
540
+ # This wraps the method `stub_hosts_on` and makes the stub specific to
541
+ # the forge alias.
542
+ #
543
+ # @param machine [String] the host to perform the stub on
544
+ def stub_forge_on(machine)
545
+ @forge_ip ||= Resolv.getaddress(forge)
546
+ stub_hosts_on(machine, 'forge.puppetlabs.com' => @forge_ip)
547
+ end
548
+ def sleep_until_puppetdb_started(host)
549
+ curl_with_retries("start puppetdb", host, "http://localhost:8080", 0, 120)
550
+ curl_with_retries("start puppetdb (ssl)",
551
+ host, "https://#{host.node_name}:8081", [35, 60])
552
+ end
553
+
554
+ def curl_with_retries(desc, host, url, desired_exit_codes, max_retries = 60, retry_interval = 1)
555
+ retry_command(desc, host, "curl #{url}", desired_exit_codes, max_retries, retry_interval)
556
+ end
557
+
558
+ def retry_command(desc, host, command, desired_exit_codes = 0, max_retries = 60, retry_interval = 1)
559
+ desired_exit_codes = [desired_exit_codes].flatten
560
+ result = on host, command, :acceptable_exit_codes => (0...127)
561
+ num_retries = 0
562
+ until desired_exit_codes.include?(result.exit_code)
563
+ sleep retry_interval
564
+ result = on host, command, :acceptable_exit_codes => (0...127)
565
+ num_retries += 1
566
+ if (num_retries > max_retries)
567
+ fail("Unable to #{desc}")
568
+ end
569
+ end
570
+ end
571
+
572
+ #stops the puppet agent running on the host
573
+ def stop_agent(agent)
574
+ vardir = agent.puppet['vardir']
575
+ agent_running = true
576
+ while agent_running
577
+ result = on agent, "[ -e '#{vardir}/state/agent_catalog_run.lock' ]", :acceptable_exit_codes => [0,1]
578
+ agent_running = (result.exit_code == 0)
579
+ sleep 2 unless agent_running
580
+ end
581
+
582
+ if agent['platform'].include?('solaris')
583
+ on(agent, '/usr/sbin/svcadm disable -s svc:/network/pe-puppet:default')
584
+ elsif agent['platform'].include?('aix')
585
+ on(agent, '/usr/bin/stopsrc -s pe-puppet')
586
+ elsif agent['platform'].include?('windows')
587
+ on(agent, 'net stop pe-puppet', :acceptable_exit_codes => [0,2])
588
+ else
589
+ # For the sake of not passing the PE version into this method,
590
+ # we just query the system to find out which service we want to
591
+ # stop
592
+ result = on agent, "[ -e /etc/init.d/pe-puppet-agent ]", :acceptable_exit_codes => [0,1]
593
+ service = (result.exit_code == 0) ? 'pe-puppet-agent' : 'pe-puppet'
594
+ on(agent, "/etc/init.d/#{service} stop")
595
+ end
596
+ end
597
+
598
+
599
+ #wait for a given host to appear in the dashboard
600
+ def wait_for_host_in_dashboard(host)
601
+ hostname = host.node_name
602
+ retry_command("Wait for #{hostname} to be in the console", dashboard, "! curl --sslv3 -k -I https://#{dashboard}/nodes/#{hostname} | grep '404 Not Found'")
603
+ end
604
+
605
+
606
+ #prompt the master to sign certs then check to confirm the cert for this host is signed
607
+ def sign_certificate(host)
608
+ return if [master, dashboard, database].include? host
609
+
610
+ hostname = Regexp.escape host.node_name
611
+
612
+ last_sleep = 0
613
+ next_sleep = 1
614
+ (0..10).each do |i|
615
+ fail_test("Failed to sign cert for #{hostname}") if i == 10
616
+
617
+ on master, puppet("cert --sign --all"), :acceptable_exit_codes => [0,24]
618
+ break if on(master, puppet("cert --list --all")).stdout =~ /\+ "?#{hostname}"?/
619
+ sleep next_sleep
620
+ (last_sleep, next_sleep) = next_sleep, last_sleep+next_sleep
621
+ end
622
+ end
623
+ end
624
+ end
625
+ end