diff --git a/lib/puppet/server/report.rb b/lib/puppet/server/report.rb index 719ab3a16..027c78f9d 100755 --- a/lib/puppet/server/report.rb +++ b/lib/puppet/server/report.rb @@ -1,178 +1,191 @@ module Puppet class Server # A simple server for triggering a new run on a Puppet client. class Report < Handler @interface = XMLRPC::Service::Interface.new("puppetreports") { |iface| iface.add_method("string report(array)") } Puppet.setdefaults(:reporting, :reportdirectory => {:default => "$vardir/reports", :mode => 0750, :owner => "$user", :group => "$group", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, :reports => ["none", "The list of reports to generate. All reports are looked for in puppet/reports/.rb, and multiple report names should be comma-separated (whitespace is okay)." ] ) @reports = {} @reportloader = Puppet::Autoload.new(self, "puppet/reports") class << self attr_reader :hooks end def self.reportmethod(report) "report_" + report.to_s end # Add a hook for processing reports. def self.newreport(name, &block) name = name.intern if name.is_a? String method = reportmethod(name) # We want to define a method so that reports can use 'return'. define_method(method, &block) @reports[name] = method end # Load a report. def self.report(name) name = name.intern if name.is_a? String unless @reports.include? name - @reportloader.load(name) - unless @reports.include? name - Puppet.warning( - "Loaded report file for %s but report was not defined" % - name - ) + if @reportloader.load(name) + unless @reports.include? name + Puppet.warning( + "Loaded report file for %s but report was not defined" % + name + ) + return nil + end + else return nil end end @reports[name] end def initialize(*args) super Puppet.config.use(:reporting) Puppet.config.use(:metrics) end # Dynamically create the report methods as necessary. def method_missing(name, *args) if name.to_s =~ /^report_(.+)$/ if self.class.report($1) send(name, *args) else super end else super end end # Accept a report from a client. def report(report, client = nil, clientip = nil) # We need the client name for storing files. client ||= Facter["hostname"].value # Unescape the report unless @local report = CGI.unescape(report) end - process(report) + begin + process(report) + rescue => detail + Puppet.err "Could not process report %s: %s" % [$1, detail] + if Puppet[:trace] + puts detail.backtrace + end + end # We don't want any tracking back in the fs. Unlikely, but there # you go. client.gsub("..",".") dir = File.join(Puppet[:reportdirectory], client) unless FileTest.exists?(dir) mkclientdir(client, dir) end # Now store the report. now = Time.now.gmtime name = %w{year month day hour min}.collect do |method| # Make sure we're at least two digits everywhere "%02d" % now.send(method).to_s end.join("") + ".yaml" file = File.join(dir, name) begin File.open(file, "w", 0640) do |f| f.puts report end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.warning "Could not write report for %s at %s: %s" % [client, file, detail] end # Our report is in YAML return file end private def mkclientdir(client, dir) Puppet.config.setdefaults("reportclient-#{client}", "clientdir-#{client}" => { :default => dir, :mode => 0750, :owner => "$user", :group => "$group" } ) Puppet.config.use("reportclient-#{client}") end # Process the report using all of the existing hooks. def process(report) return if Puppet[:reports] == "none" # First convert the report to real objects begin report = YAML.load(report) rescue => detail Puppet.warning "Could not load report: %s" % detail return end - Puppet[:reports].split(/\s*,\s*/).each do |name| - method = self.class.report(name) - - if respond_to? method + reports().each do |name| + if method = self.class.report(name) and respond_to? method Puppet.info "Processing report %s" % name begin send(method, report) rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Report %s failed: %s" % [name, detail] end else Puppet.warning "No report named '%s'" % name end end end + + # Handle the parsing of the reports attribute. + def reports + Puppet[:reports].gsub(/(^\s+)|(\s+$)/, '').split(/\s*,\s*/) + end end end end # $Id$ diff --git a/test/server/report.rb b/test/server/report.rb index 69ade8526..f5f1c1412 100755 --- a/test/server/report.rb +++ b/test/server/report.rb @@ -1,128 +1,147 @@ require 'puppet' require 'puppet/server/report' require 'puppet/client/reporter' require 'puppettest' class TestReportServer < Test::Unit::TestCase include PuppetTest Puppet::Util.logmethods(self) def mkserver server = nil assert_nothing_raised { server = Puppet::Server::Report.new() } server end def mkclient(server = nil) server ||= mkserver() client = nil assert_nothing_raised { client = Puppet::Client::Reporter.new(:Report => server) } client end def test_report # Create a bunch of log messages in an array. report = Puppet::Transaction::Report.new 10.times { |i| log = warning("Report test message %s" % i) log.tags = %w{a list of tags} log.tags << "tag%s" % i report.newlog(log) } # Now make our reporting client client = mkclient() # Now send the report file = nil assert_nothing_raised("Reporting failed") { file = client.report(report) } # And make sure our YAML file exists. assert(FileTest.exists?(file), "Report file did not get created") # And then try to reconstitute the report. newreport = nil assert_nothing_raised("Failed to load report file") { newreport = YAML.load(File.read(file)) } # Make sure our report is valid and stuff. report.logs.zip(newreport.logs).each do |ol,nl| %w{level message time tags source}.each do |method| assert_equal(ol.send(method).to_s, nl.send(method).to_s, "%s got changed" % method) end end end # Make sure we don't have problems with calling mkclientdir multiple # times. def test_multiple_clients server ||= mkserver() %w{hostA hostB hostC}.each do |host| dir = tempfile() assert_nothing_raised("Could not create multiple host report dirs") { server.send(:mkclientdir, host, dir) } assert(FileTest.directory?(dir), "Directory was not created") end end def test_report_autoloading # Create a fake report fakedir = tempfile() $: << fakedir cleanup do $:.delete(fakedir) end libdir = File.join(fakedir, "puppet", "reports") FileUtils.mkdir_p(libdir) $myreportrun = false file = File.join(libdir, "myreport.rb") File.open(file, "w") { |f| f.puts %{ Puppet::Server::Report.newreport(:myreport) do |report| $myreportrun = true return report end } } Puppet[:reports] = "myreport" # Create a server server = Puppet::Server::Report.new method = nil assert_nothing_raised { method = Puppet::Server::Report.reportmethod(:myreport) } assert(method, "Did not get report method") assert(! server.respond_to?(method), "Server already responds to report method") retval = nil assert_nothing_raised { retval = server.send(:process, YAML.dump("a string")) } assert($myreportrun, "Did not run report") assert(server.respond_to?(method), "Server does not respond to report method") + # Now make sure our server doesn't die on missing reports + Puppet[:reports] = "fakereport" + assert_nothing_raised { + retval = server.send(:process, YAML.dump("a string")) + } + end + + def test_reports + Puppet[:reports] = "myreport" + + # Create a server + server = Puppet::Server::Report.new + + {"myreport" => ["myreport"], + " fake, another, yay " => ["fake", "another", "yay"] + }.each do |str, ary| + Puppet[:reports] = str + assert_equal(ary, server.send(:reports)) + end end end # $Id$