#!/usr/bin/perl -w
#
# health check for the Sun StorEdge T3/T300/T3b via telnet
# requires mainly the Perl Net::Telnet Lib to be installed
#
# The plugin takes the arguments and logs via telnet, then asking 
# for the vol stat/fru stat 
#
# It is not in a very sophisticated state yet, but basically checks for
# the attributes most important to our environment
#
# TODO: - needs much more testing
#       - check takes too long at the moment
#       - probably, console output needs to be shorter for complex scenerios
#
# AUTHOR: Steffen Roegner (steffen@sroegner.de)
# RELEASE: 0.7.1
############################################################################################

use strict;
use Getopt::Long;
# Net:Telnet is not part of the perl standard distributions, so we want to do a more elaborate check here
# setup our values
my %hash = ('module' => 'Net::Telnet', 'method' => 'new');

# attempt to load module, catch failures
eval "use $hash{'module'};";
if ($@) {
        print "For this plugin to work, You need to install the perl module $hash{'module'}\n";
        print "  On RedHat this is typically achived by up2date -i perl-Net-Telnet\n";
        print "  On debian-like systems this would be apt-get install libnet-telnet-perl\n";
        die "Hope that helped, aborting now ...\n";
}

use Net::Telnet;
use lib ".";
use utils qw(%ERRORS &print_revision &support &usage);
use vars qw($opt_U $opt_V $opt_h $opt_P $opt_C $opt_H $opt_t $opt_partner $PROGNAME %ERRORS);

$PROGNAME = "check_t3_telnet";
%ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);

sub print_help ();
sub print_usage ();
sub analyzeVolStat($);
sub analyzeFruStat($);
sub shortDiskCheck($);
sub setReturnValue($);

Getopt::Long::Configure('bundling');
GetOptions
        ("V"       => \$opt_V, "version"     => \$opt_V,
         "h"       => \$opt_h, "help"        => \$opt_h,
         "partner" => \$opt_partner,
         "T=i"     => \$opt_t, "timeout"     => \$opt_t,
         "U=s"     => \$opt_U, "Username=s"  => \$opt_U,
         "P=s"     => \$opt_P, "Password=s"  => \$opt_P,
         "H=s"     => \$opt_H, "hostname=s"  => \$opt_H );

my $rev = "0.7.1";

if ($opt_V) {
	print($PROGNAME . ", " . $rev . "\n");
	exit $ERRORS{'OK'};
}

if($opt_h) {
	print_help();
	exit $ERRORS{'OK'};
}elsif ((! $opt_P) or (! $opt_H)) {
	print_usage();
	exit $ERRORS{'OK'};
}

my $timeout = 5;

# an easy way to set a safe timeout for partnered (and therefore slooow) arrays
if($opt_partner){
    $timeout = 20;
}

# the explicit timeout setting will allways overwrite everything else
if($opt_t){
    $timeout = $opt_t;
}

# I don't know of any System not using root als the administrators login name
# Please correct me if I'm wrong

my $username = "root";
my $passwd   = $opt_P;
my $hostname = $opt_H;
my $returnvalue = $ERRORS{'UNKNOWN'};

# here we go ...
my $t = new Net::Telnet (Timeout => $timeout, Prompt => '/.+\:\<[0-9]+\>$/');
$t->open($hostname);
$t->login($username, $passwd);
my @vol_statlines = $t->cmd("vol stat");
my @fru_statlines = $t->cmd("fru stat");

my $vs = analyzeVolStat(\@vol_statlines);
my $fs = analyzeFruStat(\@fru_statlines);
my $is = $vs . " " . $fs;
print $is."\n";
exit $returnvalue;

#-- functions ----------------------------------------------------------------------

# setReturnValue
# tries to enforce the rules for the return code (a lower level severity shall never overwrite a higher one)

sub setReturnValue($){
    my $r = shift;
    if (($returnvalue == $ERRORS{'UNKNOWN'}) or ($r > $returnvalue)){ $returnvalue = $r;}
}

# analyzeFruStat
# looks after stats of non-disk items (pcus, loops cards and the controller)

sub analyzeFruStat($){
    my @stat = @{(shift)};
    my $line = "";
    my $infostring = "";
    my $errorcount = 0;
    while ($line = shift(@stat)){
        # sort out empty lines, and header lines (starting with capital letters
        if($line =~ /^$/){next;}
        if($line =~ /^\-/){next;}
        if($line =~ /^[A-Z]/){next;}
        my @l = split(" ", $line);
        my $fru    = $l[0];
        if($fru =~ /.+d[0-9]/){next;}
        my $state  = $l[1];
        my $status = $l[2];
        if (($state !~ /ready/) or ($status !~ /enabled/)){
            ++$errorcount;
            if($fru =~ /.+ctr/){
                setReturnValue($ERRORS{'CRITICAL'});
            }else{
                setReturnValue($ERRORS{'WARNING'});
            }
            if ($errorcount > 1){$infostring .= ", ";}
            $infostring .= $fru . " is faulty";
        }
    }
    return $infostring;
}

# analyzeVolStat
# checks volumes and disks

sub analyzeVolStat($){
    my @stat = @{(shift)};
    my $line = "";
    my @disks;
    my @diskstats;
    my $volume;
    my $status;
    my $infostring = "";
    my $diskinfostring = "";
    my $volcount = 0;
    while ($line = shift(@stat)){
        
        if($line =~ /^$/){next;}
        my @l = split(" ", $line);
        # volume status consists of 2 lines: header (vol name and disk ids) and the status line (mounted/unmounted + disk stats)
        if($l[0] !~ /.*mounted/){ 
            $volume = $l[0];
            @disks = @l[1 .. $#l];
        }
        else{
            ++$volcount;
            $status = $l[0];
            # this is the volume status and SHOULD be "mounted"
            if($status !~ /^mounted/){
                setReturnValue($ERRORS{'CRITICAL'});
            }else{
                setReturnValue($ERRORS{'OK'});
            }
            @diskstats = @l[1 .. $#l];
            my %stat;
            for(my $i=0; $i<(scalar @diskstats); $i++){
                $stat{$disks[$i]} = $diskstats[$i];
            }
            $diskinfostring = shortDiskCheck(\%stat);
            if($volcount>1){$infostring .= ", ";}
            $infostring .= $volume . " is " . $status . " (" . $diskinfostring . ")";
        }
    }
    return $infostring;
}

# shortDiskCheck
# takes a status hash and returns a string containing all defective disks and according message

sub shortDiskCheck($){
    my %stat = %{(shift)};
    my $errorcount=0;
    my $returnstring = "All drives are ok";
    my $errorstring = "";
    for(keys(%stat)){
        my $disk = $_;
        if($stat{$disk} !~ "0"){
            ++$errorcount;
            setReturnValue($ERRORS{'WARNING'});
            my $msg = analyzeDriveStatus($stat{$disk});
            if ($errorcount == 1){
                $errorstring = "Drive " . $disk . " is " . $msg;
            }else{
                $errorstring .= ", Drive " . $disk . " is " . $msg;
            }
        }
    }
    if ($errorcount == 0){
        return $returnstring;
    }else{
        return "disk report: " . $errorstring;
    }
}

sub analyzeDriveStatus($){
    my %drivestatus = ("0", "mounted", 
                "2", "present", 
                "3", "spun up", 
                "4", "disabled",
                "5", "has been replaced",
                "7", "Invalid system area on drive",
                "9", "not present",
                "D", "disabled and being reconstructed",
# This might not be clean (doc says otherwise), but 9D is 
# what you get for a failed disc, so just copied that from "D" 
                "9D", "disabled and being reconstructed",
                "4D", "disabled and being reconstructed",
                "S", "substituted");
    my $s = shift;
    if($drivestatus{$s}){ return $drivestatus{$s};}
    return "unknown";
}

## utility "PrettyPrint" function for hashes
sub printHash ($){
    my %ref = %{(shift)};
    for (keys %ref){ print ($_.": ".$ref{$_},"\n") }
}

sub print_usage () {
	print "Usage: $PROGNAME [-U <username>] -P <password> -H <host>\n";
}

sub print_help () {
        print($PROGNAME . ", " . $rev . "\n");
	print "Copyright (c) 2006 Steffen Roegner

This plugin checks the most important health parameters of Sun StorEdge T3 Arrays via telnet.
It heavily depends on the validity of the specified machine prompt, which I tried to ease a little
by using a rather loose regex, but it still is possible that you just get timeouts if your T3s 
prompt looks totally different from the expected format.";

	print_usage();

print "
-H, --hostname=HOST
   Name or IP address of host to check
-U, --username=USERNAME
   defaults to root
-P, --password=PASSWORD
   mandatory Parameter
-T, --timeout=Time in seconds
   optional Parameter, default is 5
--partner
   An optional Switch for use with the master array of a partner group (increases timeout)
"
}
