#! /usr/bin/perl -w
#
# check_netapp - nagios plugin 
#
# Copyright (C) 2009 Guenther Mair,
# Derived from check_ifoperstatus by Christoph Kron.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#
# Report bugs to:  guenther.mair@hoslo.ch
#

use POSIX;
use strict;
use lib "/usr/local/nagios/libexec"  ;
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use Storable;

use Net::SNMP;
use Getopt::Long;
&Getopt::Long::config('bundling');

my $PROGNAME = 'check_netapp';
my $REVISION = '2';
sub print_help();
sub usage();
sub process_arguments();
sub bailout;
sub raidPDiskName;
sub getValue;

my $DEBUG = 0;

my $timeout;
my $hostname;
my $session;
my $error;
my $response;
my $sub_response;
my $opt_h ;
my $opt_V ;
my $key;
my $value;
my $lastc;
my $name;
my $status;
my $answer = "";
my $snmpkey = 0;
my $community = "public";
my $snmp_version = 1 ;
my $maxmsgsize = 1472 ; # Net::SNMP default is 1472
my ($seclevel, $authproto, $secname, $authpass, $privpass, $auth, $priv, $context);
my $port = 161;
my $state = 'OK';
my $length = 0;

my %raidPStates = (
  '1','active',
  '2','reconstructionInProgress',
  '3','parityReconstructionInProgress',
  '4','parityVerificationInProgress',
  '5','scrubbingInProgress',
  '6','failed');

my $productModel;
my $productModelOID = '1.3.6.1.4.1.789.1.1.5.0';
my $fsOverallStatus;
my $fsOverallStatusOID = '1.3.6.1.4.1.789.1.5.7.1.0';
my $fsMaxUsedBytesPerCent;
my $fsMaxUsedBytesPerCentOID = '1.3.6.1.4.1.789.1.5.7.3.0';
my $raidPStatus = '';
my $raidPStatusOIDTable = '1.3.6.1.4.1.789.1.6.10.1.2';
my $raidPDiskNameOIDTable = '1.3.6.1.4.1.789.1.6.10.1.10';
my $enclPowerSuppliesStatus = '';
my $enclPowerSuppliesFailedOIDTable = '1.3.6.1.4.1.789.1.21.1.2.1.15';
my $enclFansStatus = '';
my $enclFansFailedOIDTable = '1.3.6.1.4.1.789.1.21.1.2.1.18';
my $enclElectronicsStatus = '';
my $enclElectronicsFailedOIDTable = '1.3.6.1.4.1.789.1.21.1.2.1.33';
my $enclTempSensorsOverTempStatus = '';
my $enclTempSensorsOverTempWarnOIDTable = '1.3.6.1.4.1.789.1.21.1.2.1.22';
my $enclTempSensorsOverTempFailOIDTable = '1.3.6.1.4.1.789.1.21.1.2.1.21';

## qrV2Table
my $qrV2TableMessage = '';
my %qrKBytesUsed;
my $qrV2HighKBytesUsedOIDTable = '1.3.6.1.4.1.789.1.4.6.1.4';
my $qrV2LowKBytesUsedOIDTable = '1.3.6.1.4.1.789.1.4.6.1.5';

my %qrKBHardLimit;
my $qrV2QuotaUnlimitedOIDTable = '1.3.6.1.4.1.789.1.4.6.1.6';
my $qrV2HighKBytesLimitOIDTable = '1.3.6.1.4.1.789.1.4.6.1.7';
my $qrV2LowKBytesLimitOIDTable = '1.3.6.1.4.1.789.1.4.6.1.8';

my %qrPathNames;
my $qrV2PathNameOIDTable = '1.3.6.1.4.1.789.1.4.6.1.12';

my %qrKBThreshold;
my $qrV2ThresholdUnlimitedOIDTable = '1.3.6.1.4.1.789.1.4.6.1.17';
my $qrV2HighKBytesThresholdOIDTable = '1.3.6.1.4.1.789.1.4.6.1.18';
my $qrV2LowKBytesThresholdOIDTable = '1.3.6.1.4.1.789.1.4.6.1.19';

my %qrKBSoftLimit;
my $qrV2SoftQuotaUnlimitedOIDTable = '1.3.6.1.4.1.789.1.4.6.1.20';
my $qrV2HighKBytesSoftLimitOIDTable = '1.3.6.1.4.1.789.1.4.6.1.21';
my $qrV2LowKBytesSoftLimitOIDTable = '1.3.6.1.4.1.789.1.4.6.1.22';


## Validate Arguments

process_arguments();

## Just in case of problems, let's not hang Nagios

$SIG{'ALRM'} = sub {
     print ("ERROR: No snmp response from $hostname (alarm)\n");
     exit $ERRORS{"UNKNOWN"};
};

alarm($timeout);



## Main function

# get product model name
$response = $session->get_request($productModelOID);
bailout('unable to get the product model name') unless defined $response->{$productModelOID};
$productModel = $response->{$productModelOID};

# percentage of used fs space
$response = $session->get_request($fsMaxUsedBytesPerCentOID);
bailout('unable to get fsMaxUsedBytesPerCent') unless defined $response->{$fsMaxUsedBytesPerCentOID};
$fsMaxUsedBytesPerCent = $response->{$fsMaxUsedBytesPerCentOID};

# overall fs space status
$response = $session->get_request($fsOverallStatusOID);
bailout('unable to get fsOverallStatus') unless defined $response->{$fsOverallStatusOID};
if ($response->{$fsOverallStatusOID} == 1) {
  $fsOverallStatus = "OK";
} elsif ($response->{$fsOverallStatusOID} == 2) {
  $fsOverallStatus = "nearlyFull";
  $state = "WARNING" if ($state eq "OK");
} elsif ($response->{$fsOverallStatusOID} == 3) {
  $fsOverallStatus = "full";
  $state = "CRITICAL";
} else {
  bailout('got unexpected response for fsOverallStatus: ' . $response->{$fsOverallStatusOID});
}

# check RAID states
undef $response;
if (!defined ($response = $session->get_table($raidPStatusOIDTable))) {
  bailout($session->error);
}
$length = length($raidPStatusOIDTable) + 1;
foreach $key ( keys %{$response}) {
  $snmpkey = substr($key, $length);

  if ( $response->{$key} == 6 ) {
    $state = "CRITICAL";
    $raidPStatus .= " " . raidPDiskName($snmpkey) . " (" . $raidPStates{$response->{$key}} . ")";
  } elsif ( $response->{$key} != 1 ) {
    $state = "WARNING" if ($state eq "OK");
    $raidPStatus .= " " . raidPDiskName($snmpkey) . " (" . $raidPStates{$response->{$key}} . ")";
  }
}
if ( $raidPStatus eq '' ) {
  $raidPStatus = " / raidPStates OK";
} else {
  $raidPStatus = " /" . $raidPStatus;
}

# check power supply failures
undef $response;
if (!defined ($response = $session->get_table($enclPowerSuppliesFailedOIDTable))) {
  bailout($session->error);
}
foreach $key ( keys %{$response}) {
  if ( $response->{$key} ne '' ) {
    $enclPowerSuppliesStatus .= ' power supply failure: ' . $response->{$key};
    $state = "CRITICAL";
  }
}
if ( $enclPowerSuppliesStatus eq '' ) {
  $enclPowerSuppliesStatus = " / power supplies OK";
} else {
  $enclPowerSuppliesStatus = " /" . $enclPowerSuppliesStatus;
}

# check fan failures
undef $response;
if (!defined ($response = $session->get_table($enclFansFailedOIDTable))) {
  bailout($session->error);
}
foreach $key ( keys %{$response}) {
  if ( $response->{$key} ne '' ) {
    $enclFansStatus .= ' fan failure: ' . $response->{$key};
    $state = "CRITICAL";
  }
}
if ( $enclFansStatus eq '' ) {
  $enclFansStatus = " / fans OK";
} else {
  $enclFansStatus = " /" . $enclFansStatus;
}

# check electronic failures
undef $response;
if (!defined ($response = $session->get_table($enclElectronicsFailedOIDTable))) {
  bailout($session->error);
}
foreach $key ( keys %{$response}) {
  if ( $response->{$key} ne '' ) {
    $enclElectronicsStatus .= ' electronic failure: ' . $response->{$key};
    $state = "CRITICAL";
  }
}
if ( $enclElectronicsStatus eq '' ) {
  $enclElectronicsStatus = " / electronics OK";
} else {
  $enclElectronicsStatus = " /" . $enclElectronicsStatus;
}

# check temperatures
undef $response;
if (!defined ($response = $session->get_table($enclTempSensorsOverTempWarnOIDTable))) {
  bailout($session->error);
}
foreach $key ( keys %{$response}) {
  if ( $response->{$key} ne '' ) {
    $enclTempSensorsOverTempStatus .= ' temperature warning: ' . $response->{$key};
    $state = "WARNING" if ($state eq "OK");
  }
}
undef $response;
if (!defined ($response = $session->get_table($enclTempSensorsOverTempFailOIDTable))) {
  bailout($session->error);
}
foreach $key ( keys %{$response}) {
  if ( $response->{$key} ne '' ) {
    $enclTempSensorsOverTempStatus .= ' temperature failure: ' . $response->{$key};
    $state = "CRITICAL";
  }
}
if ( $enclTempSensorsOverTempStatus eq '' ) {
  $enclTempSensorsOverTempStatus = " / temperature OK";
} else {
  $enclTempSensorsOverTempStatus = " /" . $enclTempSensorsOverTempStatus;
}



## Check Disk Usage

# get PathNames
if (defined ($response = $session->get_table($qrV2PathNameOIDTable))) {
  $length = length($qrV2PathNameOIDTable) + 1;
  foreach $key ( keys %{$response}) {
    $snmpkey = substr($key, $length);

    $qrPathNames{$snmpkey} = $response->{$key};
  }

  # get KBytesUsed
  if (!defined ($response = $session->get_table($qrV2HighKBytesUsedOIDTable))) {
    bailout($session->error);
  }
  $length = length($qrV2HighKBytesUsedOIDTable) + 1;
  foreach $key ( keys %{$response}) {
    $snmpkey = substr($key, $length);

    $qrKBytesUsed{$snmpkey} = $response->{$key}*4294967296;
  }
  if (!defined ($response = $session->get_table($qrV2LowKBytesUsedOIDTable))) {
    bailout($session->error);
  }
  $length = length($qrV2LowKBytesUsedOIDTable) + 1;
  foreach $key ( keys %{$response}) {
    $snmpkey = substr($key, $length);

    $qrKBytesUsed{$snmpkey} += $response->{$key};
  }

  # get hard limit for entries with configured hard quota
  if (!defined ($response = $session->get_table($qrV2QuotaUnlimitedOIDTable))) {
    bailout($session->error);
  }
  $length = length($qrV2QuotaUnlimitedOIDTable) + 1;
  foreach $key ( keys %{$response}) {
    $snmpkey = substr($key, $length);

    if ( $response->{$key} == 1 ) {
       $qrKBHardLimit{$snmpkey} = getValue($qrV2HighKBytesLimitOIDTable.".".$snmpkey)*4294967296;
       $qrKBHardLimit{$snmpkey} += getValue($qrV2LowKBytesLimitOIDTable.".".$snmpkey);
    }
  }
  while (($key, $value) = each(%qrKBHardLimit)) {
    print "Quota configured for Volume '" . $qrPathNames{$key} . "' ($key): $value KB.\n" if $DEBUG;
    print "KB used on this Volume: ".$qrKBytesUsed{$key}."\n" if $DEBUG;

    if ( $qrKBytesUsed{$key} > $value ) {
      $qrV2TableMessage .= " / hard quota exceeded for Volume '".$qrPathNames{$key}."' (".$qrKBytesUsed{$key}."/".$value.")";
      $state = "CRITICAL";
    }
  }

  # get soft limit for entries with configured soft quota
  if (!defined ($response = $session->get_table($qrV2SoftQuotaUnlimitedOIDTable))) {
    bailout($session->error);
  }
  $length = length($qrV2SoftQuotaUnlimitedOIDTable) + 1;
  foreach $key ( keys %{$response}) {
    $snmpkey = substr($key, $length);

    if ( $response->{$key} == 1 ) {
       $qrKBSoftLimit{$snmpkey} = getValue($qrV2HighKBytesSoftLimitOIDTable.".".$snmpkey)*4294967296;
       $qrKBSoftLimit{$snmpkey} += getValue($qrV2LowKBytesSoftLimitOIDTable.".".$snmpkey);
    }
  }
  while (($key, $value) = each(%qrKBSoftLimit)) {
    print "Quota configured for Volume '" . $qrPathNames{$key} . "' ($key): $value KB.\n" if $DEBUG;
    print "KB used on this Volume: ".$qrKBytesUsed{$key}."\n" if $DEBUG;

    if ( $qrKBytesUsed{$key} > $value ) {
      $qrV2TableMessage .= " / soft quota exceeded for Volume '".$qrPathNames{$key}."' (".$qrKBytesUsed{$key}."/".$value.")";
      $state = "WARNING" if ($state eq "OK");
    }
  }

  # get KBThreshold for entries with configured quota
  if (!defined ($response = $session->get_table($qrV2ThresholdUnlimitedOIDTable))) {
    bailout($session->error);
  }
  $length = length($qrV2ThresholdUnlimitedOIDTable) + 1;
  foreach $key ( keys %{$response}) {
    $snmpkey = substr($key, $length);

    if ( $response->{$key} == 1 ) {
       $qrKBThreshold{$snmpkey} = getValue($qrV2HighKBytesThresholdOIDTable.".".$snmpkey)*4294967296;
       $qrKBThreshold{$snmpkey} += getValue($qrV2LowKBytesThresholdOIDTable.".".$snmpkey);
    }
  }
  while (($key, $value) = each(%qrKBThreshold)) {
    print "Threshold configured for Volume '" . $qrPathNames{$key} . "' ($key): $value KB.\n" if $DEBUG;
    print "KB used on this Volume: ".$qrKBytesUsed{$key}."\n" if $DEBUG;

    if ( $qrKBytesUsed{$key} > $value ) {
      $qrV2TableMessage .= " / treshold exceeded for Volume '".$qrPathNames{$key}."' (".$qrKBytesUsed{$key}."/".$value.")";
      $state = "WARNING" if ($state eq "OK");
    }
  }
}



print $productModel . " Status: fsOverallStatus " . $fsOverallStatus . " (" . $fsMaxUsedBytesPerCent . "%)".$raidPStatus.$enclPowerSuppliesStatus.$enclFansStatus.$enclElectronicsStatus.$enclTempSensorsOverTempStatus.$qrV2TableMessage."\n";
$session->close;
exit $ERRORS{$state};

### subroutines

sub getValue {
  my $OID = shift;
  my $result;

  $result = $session->get_request($OID);
  bailout('unable to get '.$OID) unless defined $result->{$OID};
  return $result->{$OID};
}

sub raidPDiskName {
  my $snmpkey = shift;
  my $localresponse = $session->get_request($raidPDiskNameOIDTable . "." . $snmpkey);
  return $localresponse->{$raidPDiskNameOIDTable . "." . $snmpkey};
}

sub bailout {
  my $msg = shift;
  $session->close;
  print "An unexpected error occurred: " . $msg . "\n";
  exit $ERRORS{"UNKNOWN"};
}
  
sub usage() {
  printf "\nMissing arguments!\n";
  printf "\n";
  printf "usage: \n";
  printf "check_netapp -H <HOSTNAME> [-C <community>]\n";
  printf "Copyright (C) 2009 Guenther Mair\n";
  printf "\n\n";
  exit $ERRORS{"UNKNOWN"};
}

sub print_help() {
	printf "check_netapp plugin for Nagios monitors the status\n";
  	printf "off a Network Appliance NAS host\n";
	printf "\nUsage:\n";
	printf "   -H (--hostname)   Hostname to query - (required)\n";
	printf "   -C (--community)  SNMP read community (defaults to public,\n";
	printf "                     used with SNMP v1 and v2c\n";
	printf "   -v (--snmp_version)  1 for SNMP v1 (default)\n";
	printf "                        2 for SNMP v2c\n";
	printf "                        SNMP v2c will use get_bulk for less overhead\n";
	printf "                        if monitoring with -d\n";
	printf "   -L (--seclevel)   choice of \"noAuthNoPriv\", \"authNoPriv\", or	\"authPriv\"\n";
	printf "   -U (--secname)    username for SNMPv3 context\n";
	printf "   -A (--authpass)   authentication password (cleartext ascii or localized key\n";
	printf "                     in hex with 0x prefix generated by using	\"snmpkey\" utility\n"; 
	printf "                     auth password and authEngineID\n";
	printf "   -a (--authproto)  Authentication protocol ( MD5 or SHA1)\n";
	printf "   -X (--privpass)   privacy password (cleartext ascii or localized key\n";
	printf "                     in hex with 0x prefix generated by using	\"snmpkey\" utility\n"; 
	printf "                     privacy password and authEngineID\n";
	printf "   -p (--port)       SNMP port (default 161)\n";
	printf "   -M (--maxmsgsize) Max message size - usefull only for v1 or v2c\n";
	printf "   -t (--timeout)    seconds before the plugin times out (default=$TIMEOUT)\n";
	printf "   -V (--version)    Plugin version\n";
	printf "   -h (--help)       usage help \n\n";
	print_revision($PROGNAME, '$Revision: '.$REVISION.' $');
	
}

sub process_arguments() {
	$status = GetOptions(
			"V"   => \$opt_V,        "version"        => \$opt_V,
			"h"   => \$opt_h,        "help"           => \$opt_h,
			"v=i" => \$snmp_version, "snmp_version=i" => \$snmp_version,
			"C=s" => \$community,    "community=s"    => \$community,
			"L=s" => \$seclevel,     "seclevel=s"     => \$seclevel,
			"a=s" => \$authproto,    "authproto=s"    => \$authproto,
			"U=s" => \$secname,      "secname=s"      => \$secname,
			"A=s" => \$authpass,     "authpass=s"     => \$authpass,
			"X=s" => \$privpass,     "privpass=s"     => \$privpass,
			"p=i" => \$port,         "port=i"         => \$port,
			"H=s" => \$hostname,     "hostname=s"     => \$hostname,
			"M=i" => \$maxmsgsize,   "maxmsgsize=i"   => \$maxmsgsize,
			"t=i" => \$timeout,      "timeout=i"      => \$timeout,
			);

	if ($status == 0){
		print_help();
		exit $ERRORS{'OK'};
	}
  
	if ($opt_V) {
		print_revision($PROGNAME,'$Revision: '.$REVISION.' $');
		exit $ERRORS{'OK'};
	}

	if ($opt_h) {
		print_help();
		exit $ERRORS{'OK'};
	}

	if (! utils::is_hostname($hostname)){
		usage();
		exit $ERRORS{"UNKNOWN"};
	}

	unless (defined $timeout) {
		$timeout = $TIMEOUT;
	}

	if ($snmp_version =~ /3/ ) {
		# Must define a security level even though default is noAuthNoPriv
		# v3 requires a security username
		if (defined $seclevel  && defined $secname) {
		
			# Must define a security level even though defualt is noAuthNoPriv
			unless ( grep /^$seclevel$/, qw(noAuthNoPriv authNoPriv authPriv) ) {
				usage();
				exit $ERRORS{"UNKNOWN"};
			}
			
			# Authentication wanted
			if ( $seclevel eq 'authNoPriv' || $seclevel eq 'authPriv' ) {
		
				unless ( $authproto eq 'MD5' || $authproto eq 'SHA1' ) {
					usage();
					exit $ERRORS{"UNKNOWN"};
				}

				if ( !defined $authpass) {
					usage();
					exit $ERRORS{"UNKNOWN"};
				}else{
					if ($authpass =~ /^0x/ ) {
						$auth = "-authkey => $authpass" ;
					}else{
						$auth = "-authpassword => $authpass";
					}
				}
					
			}
			
			# Privacy (DES encryption) wanted
			if ($seclevel eq  'authPriv' ) {
				if (! defined $privpass) {
					usage();
					exit $ERRORS{"UNKNOWN"};
				}else{
					if ($privpass =~ /^0x/){
						$priv = "-privkey => $privpass";
					}else{
						$priv = "-privpassword => $privpass";
					}
				}
			}

			# Context name defined or default

			unless ( defined $context) {
				$context = "";
			}
		
		
		
		}else {
					usage();
					exit $ERRORS{'UNKNOWN'}; ;
		}
	} # end snmpv3


	if ( $snmp_version =~ /[12]/ ) {
  	($session, $error) = Net::SNMP->session(
			-hostname  => $hostname,
			-community => $community,
			-port      => $port,
			-version	=> $snmp_version,
			-maxmsgsize => $maxmsgsize
		);

		if (!defined($session)) {
			$state='UNKNOWN';
			$answer=$error;
			print ("$state: $answer");
			exit $ERRORS{$state};
		}
	
	}elsif ( $snmp_version =~ /3/ ) {

		if ($seclevel eq 'noAuthNoPriv') {
			($session, $error) = Net::SNMP->session(
				-hostname  => $hostname,
				-port      => $port,
				-version  => $snmp_version,
				-username => $secname,
			);

		}elsif ( $seclevel eq 'authNoPriv' ) {
			($session, $error) = Net::SNMP->session(
				-hostname  => $hostname,
				-port      => $port,
				-version  => $snmp_version,
				-username => $secname,
				$auth,
				-authprotocol => $authproto,
			);	
		}elsif ($seclevel eq 'authPriv' ) {
			($session, $error) = Net::SNMP->session(
				-hostname  => $hostname,
				-port      => $port,
				-version  => $snmp_version,
				-username => $secname,
				$auth,
				-authprotocol => $authproto,
				$priv
			);
		}
					
		if (!defined($session)) {
					$state='UNKNOWN';
					$answer=$error;
					print ("$state: $answer");
					exit $ERRORS{$state};
		}

	}else{
		$state='UNKNOWN';
		print ("$state: No support for SNMP v$snmp_version yet\n");
		exit $ERRORS{$state};
	}
}
## End validation
