#! /usr/bin/perl -wT
#
# Check_apc_ups - Check APC UPS status via SNMP
#
# To do:
# - Make the status less verbose.  Maybe we can send an "onLine, time
#   remaining: hh:mm:ss" if all is well, and a list of specific problems
#   if something is broken.

require 5.004;
use strict;
use lib utils.pm ;
use Plugin;
use Plugin::Parameter qw($helpparameterlist $versionparameterlist $hostnameparameter
			 $communityparameter $portparameter $timeoutparameter
			 $verboseparameter);

use Net::SNMP;
use vars qw($opt_V $opt_h $opt_H $opt_T $opt_S $opt_R
	    $opt_r $opt_L $opt_l $xml $opt_C $opt_p
	    $opt_t $PROGNAME);
use utils qw(%ERRORS &usage);

sub get_snmp_int_values ($$$$$);
sub escalate_exitval ($);

$ENV{'PATH'}='';
$ENV{'BASH_ENV'}='';
$ENV{'ENV'}='';
$ENV{'CDPATH'}='';

my $tempcriticalparameter = new Plugin::Parameter(-name => "criticaltemperature", -flags => [ 'T', 'temp-critical' ],
						  -optional => 1, -valueoptional => 0, -type => "TEMPERATURE", -default => 60,
						  -checker => \&Plugin::Parameter::_check_temperature,
						  -description => "Battery degress C above which a CRITICAL status will result");
my $tempwarningparameter = new Plugin::Parameter(-name => "warningtemperature", -flags => [ 'S', 'temp-warning' ],
						 -optional => 1, -valueoptional => 0, -type => "TEMPERATURE", -default => 40,
						  -checker => \&Plugin::Parameter::_check_temperature,
						 -description => "Battery degress C above which a WARNING status will result");
my $runtimecriticalparameter = new Plugin::Parameter(-name => "runtimecritical", -flags => [ 'R', 'runtime-critical' ],
						     -optional => 1, -valueoptional => 0, -type => "MINUTES", -default => 30,
						     -checker => \&Plugin::Parameter::_check_time,
						     -description => "Minutes remaining below which a CRITICAL status will result");
my $runtimewarningparameter = new Plugin::Parameter(-name => "runtimewarning", -flags => [ 'r', 'runtime-warning' ],
						    -optional => 0, -valueoptional => 0, -type => "MINUTES", -default => 60,
						    -checker => \&Plugin::Parameter::_check_time,
						    -description => "Minutes remaining below which a WARNING status will result");
my $loadcriticalparameter = new Plugin::Parameter(-name => "criticalload", -flags => [ 'L', 'load-critical' ],
						  -optional => 0, -valueoptional => 0, -type => "PERCENT", -default => 85,
						  -checker => \&Plugin::Parameter::_check_percent,
						  -description => "Output load pct above which a CRITICAL status will result");
my $loadwarningparameter = new Plugin::Parameter(-name => "warningload", -flags => [ 'l', 'load-warning'],
						 -optional => 0, -valueoptional => 0, -type => "PERCENT", -default => 50,
						  -checker => \&Plugin::Parameter::_check_percent,
						 -description => "Output load pct above which a WARNING status will result");
$portparameter->default("snmp");
my @commandparameterlist = ( $hostnameparameter,
			     $communityparameter,
			     $tempcriticalparameter,
			     $tempwarningparameter,
			     $runtimecriticalparameter,
			     $runtimewarningparameter,
			     $loadcriticalparameter,
			     $loadwarningparameter,
			     $timeoutparameter,
			     $portparameter,
			     $verboseparameter);

my $plugin = new Plugin(-revision             => '$Revision: 1.2 $',
			-copyright            => "2001 Gerald Combs/Jeffrey Blank/Karl DeBisschop, 2004 Howard Wilkinson <howard\@cohtech.com>",
			-shortcomment         => "This plugin reports the status of an APC UPS equipped with an SNMP management module.",
			-parameterlists       => [ \@commandparameterlist, $helpparameterlist, $versionparameterlist ] );


$plugin->init();

# Defaults

$opt_R *= 60 * 100;	# Convert minutes to secs/100
$opt_r *= 60 * 100;

# APC UPS OIDs
# APC MIBs are available at ftp://ftp.apcftp.com/software/pnetmib/mib
my $upsBasicOutputStatus          = ".1.3.6.1.4.1.318.1.1.1.4.1.1.0";
my $upsBasicBatteryStatus         = ".1.3.6.1.4.1.318.1.1.1.2.1.1.0";
my $upsAdvInputLineFailCause      = ".1.3.6.1.4.1.318.1.1.1.3.2.5.0";
my $upsAdvBatteryTemperature      = ".1.3.6.1.4.1.318.1.1.1.2.2.2.0";
my $upsAdvBatteryRunTimeRemaining = ".1.3.6.1.4.1.318.1.1.1.2.2.3.0";
my $upsAdvBatteryReplaceIndicator = ".1.3.6.1.4.1.318.1.1.1.2.2.4.0";
my $upsAdvOutputLoad              = ".1.3.6.1.4.1.318.1.1.1.4.2.3.0";
my $upsAdvTestDiagnosticsResults  = ".1.3.6.1.4.1.318.1.1.1.7.2.3.0";

my @outputStatVals = (
  [ undef, undef ],					# pad 0
  [ undef, undef ],					# pad 1
  [ "onLine",			$ERRORS{'OK'} ],	# 2
  [ "onBattery",		$ERRORS{'WARNING'} ],	# 3
  [ "onSmartBoost",		$ERRORS{'WARNING'} ],	# 4
  [ "timedSleeping",		$ERRORS{'WARNING'} ],	# 5
  [ "softwareBypass",		$ERRORS{'WARNING'} ],	# 6
  [ "off",			$ERRORS{'CRITICAL'} ],	# 7
  [ "rebooting",		$ERRORS{'WARNING'} ],	# 8
  [ "switchedBypass",		$ERRORS{'WARNING'} ],	# 9
  [ "hardwareFailureBypass",	$ERRORS{'CRITICAL'} ],	# 10
  [ "sleepingUntilPowerReturn",	$ERRORS{'CRITICAL'} ],	# 11
  [ "onSmartTrim",		$ERRORS{'WARNING'} ],	# 12
);

my @failCauseVals = (
  undef,
  "noTransfer",
  "highLineVoltage",
  "brownout",
  "blackout",
  "smallMomentarySag",
  "deepMomentarySag",
  "smallMomentarySpike",
  "largeMomentarySpike",
  "selfTest",
  "rateOfVoltageChnage",
);

my @battStatVals = (
  [ undef, undef ],				# pad 0
  [ undef, undef ],				# pad 1
  [ "batteryNormal",	$ERRORS{'OK'} ],	# 2
  [ "batteryLow",	$ERRORS{'CRITICAL'} ],	# 3
);

my @battReplVals = (
  [ undef, undef ],		 			# pad 0
  [ "noBatteryNeedsReplacing",	$ERRORS{'OK'} ],	# 1
  [ "batteryNeedsReplacing",	$ERRORS{'CRITICAL'} ],	# 2
);

my @diagnosticsResultsVals = (
  [ undef, undef ],				# pad 0
  [ "OK",		$ERRORS{'OK'} ],	# 1
  [ "failed",		$ERRORS{'CRITICAL'} ],	# 2
  [ "invalidTest",	$ERRORS{'CRITICAL'} ],	# 3
  [ "testInProgress",	$ERRORS{'OK'} ],	# 4
);

my $sep         = ", ";
my $exitval     = $ERRORS{'OK'};
my $onbattery   = 3;

my $data = &get_snmp_int_values( $opt_H, $opt_C, $opt_t, $opt_p,
				 [ $upsBasicOutputStatus,
				   $upsBasicBatteryStatus,
				   $upsAdvInputLineFailCause,
				   $upsAdvBatteryTemperature,
				   $upsAdvBatteryRunTimeRemaining,
				   $upsAdvBatteryReplaceIndicator,
				   $upsAdvOutputLoad,
				   $upsAdvTestDiagnosticsResults ] );

if (!defined $data) {
  print "Status UNKNOWN\n";
  exit $ERRORS{'UNKNOWN'};
}

my $OutputStatus = $data->{$upsBasicOutputStatus};
my $BatteryStatus = $data->{$upsBasicBatteryStatus};
my $InputLineFailCause = $data->{$upsAdvInputLineFailCause};
my $BatteryTemperature = $data->{$upsAdvBatteryTemperature};
my $BatteryRunTimeRemaining = $data->{$upsAdvBatteryRunTimeRemaining};
my $BatteryReplaceIndicator = $data->{$upsAdvBatteryReplaceIndicator};
my $OutputLoad = $data->{$upsAdvOutputLoad};
my $TestDiagnosticsResults = $data->{$upsAdvTestDiagnosticsResults};

print "Output status: ";
if (defined ($OutputStatus) && defined ($outputStatVals[$OutputStatus][0])) {
  print "$outputStatVals[$OutputStatus][0]$sep";
  escalate_exitval($outputStatVals[$OutputStatus][1]);
} else {
  print "unknown$sep";
}

print "Rem time: ";
if (defined ($BatteryRunTimeRemaining)) {
  my $hrs  = int($BatteryRunTimeRemaining / (60 * 60 * 100)); # Data is hundredths of a second
  my $mins = int($BatteryRunTimeRemaining / (60 * 100)) % 60;
  my $secs = ($BatteryRunTimeRemaining % 100) / 100;
  printf "%d:%02d:%05.2f$sep", $hrs, $mins, $secs;
  if ($BatteryRunTimeRemaining <= $opt_R) {
    escalate_exitval($ERRORS{'CRITICAL'});
  } elsif ($BatteryRunTimeRemaining <= $opt_r) {
    escalate_exitval($ERRORS{'WARNING'});
  } else {
    escalate_exitval($ERRORS{'OK'});
  }
} else {
  print "unknown$sep";
}

print "Battery status: ";
if (defined ($BatteryStatus) && defined ($battStatVals[$BatteryStatus][0])) {
  my $failcause = "unknown";
  if ($BatteryStatus == $onbattery) {
    if (defined ($failCauseVals[$InputLineFailCause])) {
      $failcause = $failCauseVals[$InputLineFailCause];
    }
    print "$battStatVals[$BatteryStatus][0] ($failcause)$sep";
  } else {
    print "$battStatVals[$BatteryStatus][0]$sep";
  }
  escalate_exitval($battStatVals[$BatteryStatus][1]);
} else {
  print "unknown$sep";
}

print "Battery temp(C): ";
if (defined ($BatteryTemperature)) {
  print "$BatteryTemperature$sep";
  if ($BatteryTemperature >= $opt_T) {
    escalate_exitval($ERRORS{'CRITICAL'});
  } elsif ($BatteryTemperature >= $opt_S) {
    escalate_exitval($ERRORS{'WARNING'});
  } else {
    escalate_exitval($ERRORS{'OK'});
  }
} else {
  print "unknown$sep";
}

print "Battery repl: ";
if (defined ($BatteryReplaceIndicator) && defined ($battReplVals[$BatteryReplaceIndicator][0])) {
  print "$battReplVals[$BatteryReplaceIndicator][0]$sep";
  escalate_exitval($battReplVals[$BatteryReplaceIndicator][1]);
} else {
  print "unknown$sep";
}

print "Output load (%): ";
if (defined ($OutputLoad)) {
  print "$OutputLoad$sep";
  if ($OutputLoad >= $opt_L) {
    escalate_exitval($ERRORS{'CRITICAL'});
  } elsif ($OutputLoad >= $opt_l) {
    escalate_exitval($ERRORS{'WARNING'});
  } else {
    escalate_exitval($ERRORS{'OK'});
  }
} else {
  print "unknown$sep";
}

print "Diag result: ";
if (defined ($TestDiagnosticsResults)
    && defined ($diagnosticsResultsVals[$TestDiagnosticsResults][0])) {
  print "$diagnosticsResultsVals[$TestDiagnosticsResults][0]$sep";
  escalate_exitval($diagnosticsResultsVals[$TestDiagnosticsResults][1]);
} else {
  print "unknown$sep";
}

foreach my $key (keys %ERRORS) {
  if ($ERRORS{$key} == $exitval) {
    print "Status $key\n";
    last;
  }
}

exit $exitval;


sub get_snmp_int_values ($$$$$) {

  my ( $host, $community, $timeout, $port, $oids ) = @_;

  my $response = undef;

  my ($session, $error) = Net::SNMP->session(-hostname => $host,
					     -community => $community,
					     -timeout => $timeout,
					     -port => $port,
					     -translate => undef);
  if (defined($session)) {
    $response=$session->get_request(-varbindlist => $oids);
    $session->close;
  }

  return $response
}

sub escalate_exitval ($) {
  my $newval = shift(@_);

  if ($newval > $exitval) { $exitval = $newval; }
}

