beaker 0.0.0

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