#!/usr/local/bin/perl ### check_tap.pl # By Nathan Vonnahme, n8v at users dot sourceforge dot net, June 2 2011 # Allows Nagios to check the output of anything that emits Test Anything # Protocol. # See http://en.wikipedia.org/wiki/Test_Anything_Protocol ############################################################################## # prologue use strict; use warnings; use Nagios::Plugin ; use TAP::Harness; use vars qw($VERSION $PROGNAME $verbose $warn $critical $timeout $result); $VERSION = '1.0'; # get the base name of this script for use in the examples use File::Basename; $PROGNAME = basename($0); ############################################################################## # define and get the command line options. # see the command line option guidelines at # http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOPTIONS # Instantiate Nagios::Plugin object (the 'usage' parameter is mandatory) my $p = Nagios::Plugin->new( usage => "Usage: %s [ -v|--verbose ] [-t ] [ -c|--critical= ] [ -w|--warning= ] [ -s|--script = '' ] (Required. multiple OK) [ -e|--exec = '/full/path/to/runnable ARGS' [ -l|--lib = '/path/to/perl/libs' ] (Multiple OK) ", version => $VERSION, blurb => "This plugin allows Nagios to check the output of anything that emits Test Anything Protocol output. So you can wed Nagios's monitoring and alerting infrastructure to your unit and functional tests for deep application-level monitoring in development or even in production.", extra => qq{ ## Verbosity Use -v to see a bit more info in the one line, including the first test that failed. This is especially useful because Nagios will include it in the alert/notification. Use -vv to see test summary and failures. Use -vvv to see full test script output. ## Warning and Critical Thresholds THRESHOLDs for -w and -c specify the allowable amount of test failures before the plugin returns WARNING or CRITICAL. Use 'max' or 'min:max'. The default of 0 tolerated failures is good for people like you who have high standards. But you might want to crank up the CRITICAL threshold if you want to differentiate between WARNING and CRITICAL amounts of fail. See more threshold examples at http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT ## Examples: $PROGNAME -s /full/path/to/testfoo.pl will run 'testfoo.pl' and return OK if 0 tests fail, but CRITICAL if any fail. Excluding TODO or SKIPped tests, of course. $PROGNAME -s /full/path/to/testfoo.pl -c 2 will return OK if 0 tests fail, WARNING if more than 0 tests fail, but CRITICAL if more than 2 fail. $PROGNAME -s /full/path/to/testfoo.pl -w 2 c 4 will return OK if 0 tests fail, WARNING if more than 2 tests fail, and CRITICAL if more than 4 fail. ## Non-Perl and remote test scripts $PROGNAME -e '/usr/bin/ruby -w' -s /full/path/to/testfoo.r will run 'testfoo.r' using Ruby with the -w flag. You can use any shell command and argument which produces TAP output, for example: $PROGNAME -e '/usr/bin/curl -sk' -s 'http://url/to/mytest.php' $PROGNAME -e '/usr/bin/cat' -s '/path/to/testoutput.tap' In fact, anything TAP::Harness or `prove` regards as a source or executable. Remember that Nagios or NRPE will likely be running this command as a different, less-privileged user than you're using now. } ); # Define and document the valid command line options # usage, help, version, timeout and verbose are defined by default. $p->add_arg( spec => 'script|s=s@', required => 1, help => qq{-s, --script="/path/to/executable/test.t args" REQUIRED. Defines the path to the test script you want to run. Use multiple -s flags to run multiple tests. } ); $p->add_arg( spec => 'lib|l=s@', help => qq{-l, --lib="/path/to/perl/lib/dir" Optional path for Perl libs to add to \@INC. Use multiple -l flags to specify multiple lib dirs. } ); $p->add_arg( spec => 'exec|e=s', required => 0, help => qq{-e, --exec="/path/to/executable args" Defines a non-Perl executable with which you want to run the --script. } ); $p->add_arg( spec => 'warning|w=s', default => 0, help => qq{-w, --warning=INTEGER:INTEGER Minimum and maximum number of allowable test FAILURES, outside of which a warning will be generated. Default is 0 tolerable failures. }, ); $p->add_arg( spec => 'critical|c=s', default => 0, help => qq{-c, --critical=INTEGER:INTEGER Minimum and maximum number of allowable test FAILURES, outside of which a critical will be generated. Default is 0 tolerable failures. }, ); # Parse arguments and process standard ones (e.g. usage, help, version) $p->getopts; ############################################################################## # Do it, and do it, and do it. my $message = ""; alarm($p->opts->timeout); # Capture STDOUT/STDERR to emit if verbose my $raw_test_output = ""; # opens a virtual filehandle that will write to $raw_test_output open(my $fh, '>', \$raw_test_output); # -v and -vv need Harness verbosity = 0 # -vvv+ need verbosity = 1 my $harness = TAP::Harness->new( { verbosity => ($p->opts->verbose >=3), failures => 1, merge => 1, errors => 1, stdout => $fh, formatter_class => 'TAP::Formatter::File', ignore_exit => 1, lib => $p->opts->lib, } ); if ($p->opts->exec) { # exec => [split /\s+/, $p->opts->exec]; $harness->exec([split /\s+/, $p->opts->exec]); } my $aggregator = $harness->runtests( @{$p->opts->script} ); alarm(0); my $result = $aggregator->failed; $p->add_perfdata( label => "total", value => $aggregator->total, ); $p->add_perfdata( label => "failures", value => $result, threshold => $p->threshold, ); $p->add_perfdata( label => "passed", value => scalar $aggregator->passed, ); if ( $aggregator->exit >= 255 ) { $message = sprintf("Test script failed to exit normally after %d tests (%d failures)", scalar $aggregator->total, scalar $aggregator->failed); $message .= "\n$raw_test_output" if $p->opts->verbose > 0; $p->nagios_exit( return_code => CRITICAL, message => $message ); } $message = $aggregator->get_status; if ($p->opts->verbose > 0) { my $first_failure = ""; if ($aggregator->failed && $raw_test_output =~ /^not ok.*$/m) { $first_failure = " 1st='$&'"; } $message .= sprintf(". %d of %d failed. %s%s", scalar $aggregator->failed, scalar $aggregator->total, ($p->opts->warning || $p->opts->critical ? sprintf "Warning/critical at %d/%d.", $p->opts->warning, $p->opts->critical : ""), $first_failure ); $message .= "\n" . $raw_test_output if $p->opts->verbose > 1; } ############################################################################## # check the result against the defined warning and critical thresholds, # output the result and exit $p->nagios_exit( return_code => $p->check_threshold($result), message => $message );