#!/usr/bin/perl
#########################################################################
# Description:  Checks Datacore sansymphony 9 via SNMP.					
#
# Date : 23 March 2015
# Author:       Fabrice Le Dorze
# Licence : GPL - http://www.fsf.org/licenses/gpl.txt
# Purpose:      Monitor Datacore sansymphony 9 with Nagios	
#               Can be used to query status and performance info
#
# Date        : 09 January 2017
# Modified by : Frederic Larive
# Changelog   : add filtering on all parts of retrieved SNMP data 
#               removed 'Available' from FallingThresholdPerfMonitor $message parser regex
#
#########################################################################
#
use strict;
use Getopt::Long;

my $PROGNAME=`basename $0`;
chomp $PROGNAME;

#-----------------------------------------------------
# Usage function
#-----------------------------------------------------
sub Print_Usage() {
	print <<USAGE;

Usage: $PROGNAME -H <host> -C <community> -t <type> [-s <subtype>[,<subtype2>]] [-T <timeout>] [-n <pattern> [-e]] [ -w <warning%>][-c <critical%>] [ -y <latency warning>][-z <latency critical>] [-S]
USAGE
}

#-----------------------------------------------------
# Parameters
#-----------------------------------------------------
our %subtypes=(
    PhysicalDisks => {
	DiskPoolStateMonitor => "Monitors the state of disk pools.",
        FallingThresholdPerfMonitor => "Monitors the disk pools available space percentage.",
	PhysicalDiskStateMonitor => "Monitors the state of physical disks.",
	PoolMemberStateMonitor => "Monitors the state of the relation between a pair of mirrored physical disks in a pool.",
	RisingThresholdPerfMonitor => "Monitors the disk pools latency.",
    },
    Servers => {
        ServeriScsiPortStateMonitor => "Monitors the state of server iSCSI ports.",
	ServerMachineLicenseMonitor => "Monitors the license expiration date of DataCore Servers.",
	ServerMachinePowerStateMonitor => "Monitors the battery power state for DataCore Servers.",
	ServerMachineStateMonitor => "Monitors the state of DataCore Servers.",
	ServerMSiScsiInitiatorStateMonitor => "Monitors the state of MS iSCSI Initiator ports.",
    },
    Scsi => {
	ScsiPortConnectionStateMonitor => "Monitors the connection state of SCSI ports."
    },
    VirtualDisks => {
	LogicalDiskAccessRightsMonitor => "Monitors the access rights of virtual disk sources.",
	LogicalDiskDataStatusMonitor => "Monitors the data status of virtual disk sources.",
        LogicalDiskFailureMonitor => "Monitors the failure status of virtual disk sources.",
        LogicalDiskStateMonitor => "Monitors the state of virtual disk sources.",
	StreamLogicalDiskStateMonitor => "Monitors the state of the data protection of virtual disk sources.",
        VirtualDiskAccessRightsMonitor => "Monitors the access rights of virtual disk sources.",
        VirtualDiskDataStatusMonitor => "Monitors the status of virtual disks.",
    },
    Clients => { ClientMachineStateMonitor => "Monitors the state of hosts."}
);

my $datacore_oid="1.3.6.1.4.1.7652.1.1.1";
my $monitor_oid=$datacore_oid.".2.";
my $state_oid=$datacore_oid.".5.";
my $id_oid=$datacore_oid.".3.";
my $item_oid=$datacore_oid.".4.";
my $message_oid=$datacore_oid.".6.";

#-----------------------------------------------------
# Help function
#-----------------------------------------------------
sub Print_Help {
         print <<HELP;
This plugin checks Datacore SanSymphony 9 via SNMP
HELP
&Print_Usage;
         print <<HELP;

	-H : host
	-C : SNMP-Community (only snmp V2)
HELP
	print "\t-t : type. Can be : ".join(", ",keys %subtypes).".\n";
	print "\t-s : subtypes in the following list:\n";
        for my $t (keys %subtypes)
         {
              print "\t\t=>".$t."\n";
              my %hash=%{$subtypes{$t}};
              for my $key (keys %hash)
              {
                  print "\t\t    ".$key." : ".$hash{$key}."\n";
              }
         }
         print <<HELP;
	-T : timeout. Default is 15s.
	-w and -c : warning and critical % volume usage. Default is 90 and 95%.
	-y and -z : warning and critical for disk pool latency. Default is 10 and 20
	-S : short output. Don't print details.

	FILTERING (logical AND between each filters, <pattern> is a regular expression) :
	 -p  <pattern>  : include only 'item' matching given pattern
	--sp <pattern>  : include only 'state' matching given pattern
	--ip <pattern>  : include only 'id' matching given pattern
	--mp <pattern>  : include only 'message' matching given pattern
	--stp <pattern> : include only 'subtype' matching given pattern

	 -e : use above patterns to EXCLUDE data
	--pdbg : print debug info to identify fields for filtering

HELP

exit 1;
}

#-----------------------------------------------------
# Get user-given variables
#-----------------------------------------------------
my ($host, $community, $type, $subtypes, $pattern, $StatePattern, $IdPattern, $MessagePattern, $SubtypePattern, $patternDebug, $exclude, $warning, $lwarning, $critical, $lcritical, $help, $debug, $short,  $timeout);
$pattern='.*'; $StatePattern='.*'; $IdPattern='.*'; $MessagePattern='.*'; $SubtypePattern='.*';
Getopt::Long::Configure ("bundling");
GetOptions (
'H=s' => \$host,
'C=s' => \$community,
'T=s' => \$timeout,
't=s' => \$type,
's=s' => \$subtypes,
'd' => \$debug,
'S' => \$short,
'w=s' => \$warning,
'c=s' => \$critical,
'y=s' => \$lwarning,
'z=s' => \$lcritical,
'e' => \$exclude,
'p=s' => \$pattern,
'sp=s' => \$StatePattern,
'ip=s' => \$IdPattern,
'mp=s' => \$MessagePattern,
'stp=s' => \$SubtypePattern,
'pdbg' => \$patternDebug,
'h' => \$help
);

if( $pattern eq '.*' && $StatePattern eq '.*' && $IdPattern eq '.*' && $MessagePattern eq '.*' && $SubtypePattern eq '.*') { $pattern=0; $StatePattern=0; $IdPattern=0; $MessagePattern=0; $SubtypePattern=0; }

$timeout="15" unless ($timeout);

&Print_Help if ($help);
&Print_Help unless ($community && $host && $type);
&Print_Help unless ($subtypes{$type});

my @subtype_array=keys %{$subtypes{$type}};
if ($subtypes)
{
    @subtype_array=();
    for my $sub (split(/,/,$subtypes))
    {
        push @subtype_array, $sub if (${$subtypes{$type}}{$sub});
    }
}

if (grep {/FallingThresholdPerfMonitor/} @subtype_array)
{
    $warning="90" unless ($warning);
    $critical="95" unless ($critical);
}
if (grep {/RisingThresholdPerfMonitor/} @subtype_array)
{
    $lwarning="10" unless ($lwarning);
    $lcritical="20" unless ($lcritical);
}


my $STATE_OK=0;
my $STATE_WARNING=1;
my $STATE_CRITICAL=2;
my $STATE_UNKNOWN=3;

#-----------------------------------------------------
# SNMP request
#-----------------------------------------------------
my $command="snmpwalk -v 2c -O n -c  $community $host $datacore_oid";
my $output=`$command`;
print $output if($debug);
#print "No result." and exit $STATE_UNKNOWN if ($?!=0 or $output =~ /No more variables left in this MIB View/);
print "No result." and exit $STATE_UNKNOWN if ($?!=0);
chomp $output;
my @result=split(/\n/,$output);

#-----------------------------------------------------
# Loop on selected subtypes
#-----------------------------------------------------
my $critstate=0;
my $warnstate=0;
my $i=0;
my $failure=0;
my @details;
my @failed;
my @perfs;
my @indexes;
for my $subtype (@subtype_array)
{

    # Look for matching indexes
    @indexes=grep{/\"$subtype/} @result;
    map{s/$monitor_oid//} @indexes;
    map{s/ = STRING.*//} @indexes;
    for my $index (@indexes)
    {
        my @res=grep {/$index =/} @result;
        print join("\n",@res)."\n" if ($debug); 
        my $state=(grep{/$state_oid/} @res)[0];
        my $item=(grep{/$item_oid/} @res)[0];
        my $id=(grep{/$id_oid/} @res)[0];
        my $message=(grep{/$message_oid/} @res)[0];
        map {s/.*$datacore_oid.*= STRING: //} ($state, $item, $id, $message);
        map {s/"|{|}//g} ($state, $item, $id, $message);
        
        # Add Disk ID for some subtypes
        $item.="(".$id.")" if ($subtype =~ /PhysicalDiskStateMonitor|PoolMemberStateMonitor/);

        # Compare item with patterns
        if ($pattern || $StatePattern || $IdPattern || $MessagePattern || $SubtypePattern)
        {
            if ($exclude)
            {
                next if ($item =~ /$pattern/ && $state =~ /$StatePattern/ && $id =~ /$IdPattern/ && $message =~ /$MessagePattern/);
            }
            else
            {
                next unless ($item =~ /$pattern/ && $state =~ /$StatePattern/ && $id =~ /$IdPattern/ && $message =~ /$MessagePattern/);
            }
        }

        if( $patternDebug ) {print "*****\n\tITEM: $item\n\tSTATE: $state\n\tID: $id\n\tMSG: $message\n\tSUBTYPE: $subtype\n*****\n";}

        # Disk Pool usage
        if ( $subtype eq "FallingThresholdPerfMonitor")
        {
            print "$item $state : $message\n" if ($debug);
            my $freeValue=($message=~/ space.* (\d+)%/)[0];
            my $usedvalue=100-$freeValue;
            if ($usedvalue>$critical)
            {
                $critstate=1;
                push @failed, $item."=".$usedvalue."%(>".$critical."%)";
                push @details, $item."=".$usedvalue."%(>".$critical."%) from ".$subtype;
            }
            elsif ($usedvalue>$warning)
            {
                $warnstate=1;
                push @failed, "'".$item."'=".$usedvalue."%(>".$warning."%)";
                push @details, "'".$item."'=".$usedvalue."%(>".$warning."%) from ".$subtype;
            }
            else
            {
                push @details, "'".$item."'=".$usedvalue."%(<=".$warning."%) from ".$subtype;
            }
	    push  @perfs, "'".$item."'=".$usedvalue."%;".$warning.";".$critical.";0;100";
        }
        
        # Disk Pool latency
        elsif ( $subtype eq "RisingThresholdPerfMonitor" )
        {
            print "$item $state : $message\n" if ($debug);
	    my $latency=($message=~/.*latency.* (\d+)s/)[0];
            if ($latency>$lcritical)
            {
                push @failed, "'".$item."'=".$latency."s(>".$lcritical."s)";
                push @details, "'".$item."'=".$latency."s(>".$lcritical."s) from ".$subtype;
		$critstate=1
            }
            elsif ($latency>$lwarning)
            {
                push @failed, "'".$item."'=".$latency."s(>".$lwarning."s)";
                push @details, "'".$item."'=".$latency."s(>".$lwarning."s) from ".$subtype;
		$warnstate=1
            }
            else
            {
                push @details, "'".$item."'=".$latency."s(<=".$lwarning."s) from ".$subtype;
            }
	    push  @perfs, "'".$item."'=".$latency.";".$lwarning.";".$lwarning.";0;";
        }

        # Other cases
        else
        {
            $i++;
            print "$item $state : $message\n" if ($debug);
	    unless ($state=~/Healthy/ && $item)
            {
                push @failed, "'".$item."' ".$state."(".$message.")";
                $failure++;
                $critstate=1;
            }
            push @details, "'".$item."' ".$state."(".$message.") from ".$subtype;
        }
    } 
}

print "No result." and exit $STATE_UNKNOWN if ($#details<0);

my %seen=();

#my @unique=grep { ! $seen{$_} ++ } @failed;
#@failed=@unique;
#@unique=grep { ! $seen{$_} ++ } @details;
#@details=@unique;


###
# Output
###
my $code;
my $status;
if ($critstate==1)
{
    $code=$STATE_CRITICAL;
    $status=$type." CRITICAL : ".join(", ",@failed);
}
elsif ($warnstate==1)
{
    $code=$STATE_WARNING;
    $status=$type." WARNING : ".join(", ",@failed);
}
else
{
    $code=$STATE_OK;
    $status="All ".$type." Monitors OK, see details.";
}
$status.="\n".join("\n",@details) unless ($short);

print $status;
print "|".join(" ",@perfs);
print " Failures=".$failure.";;;0;".$i."\n" if ($i>0);
exit $code;
