#!/usr/bin/perl
#
# "mon" monitor to detect thermal/fan/psu failures
# for Compaq Proliant machines which run the "Compaq Insight Agent"
#
# arguments are "[-c community] [-f] [-p] [-t] host [host...]"
# 
# -f       do not query the fan table
# -p       do not query the PSU table
# -t       do not query the temperature table
#
# Jim Trocki
#
# $Id: cpqhealth.monitor,v 1.1.1.1 2004/06/09 05:18:05 trockij Exp $
#
#    Copyright (C) 2000, Jim Trocki
#
#    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
#
use SNMP;
use Getopt::Std;
use strict;

sub get_status;
sub get_table;
sub get_vars;
sub fancy_psu_table;

$ENV{"MIBS"} = "CPQHLTH-MIB";

my %opt;
getopts ('c:pft', \%opt);

my $COMM = $opt{"c"} || "public";

my @failures = ();
my $detail = "";
my $detail_head = "";
my $fan_detail = "";
my $psu_detail = "";

foreach my $host (@ARGV)
{
    my %status = get_status ($host, $COMM);

    if ($status{"error"} ne "")
    {
    	push (@failures, $host);
	$detail .= "could not retrieve status from $host: $status{error}\n\n";
	next;
    }

    elsif ($status{"failure"})
    {
    	push (@failures, $host);
    }

    if ($status{"overall_status"}->{"redundant_fans"} ne "ok")
    {
    	$detail_head .= "$host has fan problem\n";
    }

    if ($status{"overall_status"}->{"redundant_psus"} ne "ok")
    {
    	$detail_head .= "$host has PSU problem\n";
    }

    if ($status{"overall_status"}->{"temperature"} ne "ok")
    {
    	$detail_head .= "$host has temperature problem\n";
    }

    #
    # Fan
    #
    $fan_detail .= fancy_fan_table ($host, $status{"flt_tol_fan_table"}) . "\n";

    #
    # PSU
    #
    $psu_detail .= fancy_psu_table ($host, $status{"psu_status_table"}) . "\n";
}


#
# output returned to mon
#
if (@failures != 0)
{
    print join (" ", sort @failures), "\n";
}

else
{
    print "\n";
}

print "$detail_head";

if (!$opt{"f"})
{
    print <<EOF;
hostname         iid chas locale    present    cond    redundant    partner speed   type
----------------------------------------------------------------------------------------------
$fan_detail
EOF
}

if (!$opt{"p"})
{
    print <<EOF;
host             iid present    status      redundant    condition   bay  chassis
----------------------------------------------------------------------------------------------
$psu_detail
EOF
}

print "$detail";

exit 1 if (@failures != 0);

exit 0;



#
# params: (hostname, community)
#
# returns:
# (
#  "error" => "error name, empty string means no error",
# )
#
sub get_status
{
    my ($host, $comm) = @_;

    my $s;
    if (!defined ($s = new SNMP::Session (
    	"DestHost" => $host,
	"Community" => $comm,
	"UseEnums" => 1,
	"Version" => 2,
	)))
    {
    	return ("error" => "cannot create session");
    }

    my $error;
    my $failure_detected = 0;
    my $psu_status_table;
    my $flt_tol_fan_table;
    my $temp_sensor_table;
    my $overall_status;

    #
    # is this really a compaq box w/the insight agent?
    #
    my $sys_oid = $s->get (["sysObjectID", 0]);
    
    return ("error" => $s->{"ErrorStr"}) if ($s->{"ErrorStr"} ne "");

    return ("error" => "not Compaq device")
    	if ($sys_oid ne ".1.3.6.1.4.1.311.1.1.3.1.2");

    if (0)
    {
	#
	# get overall status
	#
	# from what i can tell, this is totally useless, and
	# it doesn't tell you about the things you actually care about,
	# like whether the PSUs are running in a redundant configuration
	# or not, or PSUs which used to be in order but are now
	# out of order.
	#

	($error, $overall_status) = get_vars ($s,
	    ["cpqHeThermalCondition", 0],
	    ["cpqHeThermalTempStatus", 0],
	    ["cpqHeThermalSystemFanStatus", 0],
	    ["cpqHeThermalCpuFanStatus", 0],
	    ["cpqHeFltTolPwrSupplyCondition", 0],
	);

	return ("error" => "$error while retrieving overall status")
	    if ($error ne "");
    }

    $overall_status = {
    	"redundant_fans"		=> "ok",
	"temperature"			=> "ok",
	"redundant_psus"		=> "ok",
    };

    if (!$opt{"p"})
    {
	#
	# PSU table
	#
	($error, $psu_status_table) = get_table ($s,
	    ["cpqHeFltTolPowerSupplyChassis"],
	    ["cpqHeFltTolPowerSupplyBay"],
	    ["cpqHeFltTolPowerSupplyPresent"],
	    ["cpqHeFltTolPowerSupplyCondition"],
	    ["cpqHeFltTolPowerSupplyStatus"],
	    ["cpqHeFltTolPowerSupplyMainVoltage"],
	    ["cpqHeFltTolPowerSupplyCapacityUsed"],
	    ["cpqHeFltTolPowerSupplyCapacityMaximum"],
	    ["cpqHeFltTolPowerSupplyRedundant"],
	    ["cpqHeFltTolPowerSupplyModel"],
	    ["cpqHeFltTolPowerSupplySerialNumber"],
	    ["cpqHeFltTolPowerSupplyAutoRev"],
	);

	return ("error" => $error) if ($error ne "");

	foreach my $r (@{$psu_status_table})
	{
	    next if ($r->{"cpqHeFltTolPowerSupplyPresent"} eq "absent");

	    if ($r->{"cpqHeFltTolPowerSupplyCondition"} ne "ok" ||
		$r->{"cpqHeFltTolPowerSupplyStatus"} ne "noError" ||
		$r->{"cpqHeFltTolPowerSupplyRedundant"} eq "notRedundant")
	    {
		$failure_detected = 1;
		$overall_status->{"redundant_psus"} = "fail";
		last;
	    }
	}
    }

    if (!$opt{"f"})
    {
	#
	# Fan chassis table
	#
	($error, $flt_tol_fan_table) = get_table ($s,
	    ["cpqHeFltTolFanChassis"],
	    ["cpqHeFltTolFanIndex"],
	    ["cpqHeFltTolFanLocale"],
	    ["cpqHeFltTolFanPresent"],
	    ["cpqHeFltTolFanType"],
	    ["cpqHeFltTolFanSpeed"],
	    ["cpqHeFltTolFanRedundant"],
	    ["cpqHeFltTolFanRedundantPartner"],
	    ["cpqHeFltTolFanCondition"],
	);

	return ("error" => $error) if ($error ne "");

	foreach my $r (@{$flt_tol_fan_table})
	{
	    next if ($r->{"cpqHeFltTolFanPresent"} ne "present");

	    if ($r->{"cpqHeFltTolFanRedundant"} ne "redundant" ||
		$r->{"cpqHeFltTolFanCondition"} ne "ok")
	    {
		$failure_detected = 1;
		$overall_status->{"redundant_fans"} = "fail";
		last;
	    }
	}
    }

    if (!$opt{"t"})
    {
	#
	# chassis temp table
	#
	($error, $temp_sensor_table) = get_table ($s,
	    ["cpqHeTemperatureChassis"],
	    ["cpqHeTemperatureIndex"],
	    ["cpqHeTemperatureLocale"],
	    ["cpqHeTemperatureCelsius"],
	    ["cpqHeTemperatureThreshold"],
	    ["cpqHeTemperatureCondition"],
	);

	return ("error" => $error) if ($error ne "");

	foreach my $r (@{$temp_sensor_table})
	{
	    #
	    # cpqHeTemperatureCelsius == -1 if not present
	    # (as far as i can tell)
	    #
	    next if ($r->{"cpqHeTemperatureCelsius"} == -1);

	    if ($r->{"cpqHeTemperatureCondition"} ne "ok")
	    {
		$failure_detected = 1;
		$overall_status->{"temperature"} = "fail";
		last;
	    }
	}
    }

    (
    	"error"			=> "",
	"failure"		=> $failure_detected,
	"overall_status"	=> $overall_status,
	"temp_sensor_table"	=> $temp_sensor_table,
	"flt_tol_fan_table"	=> $flt_tol_fan_table,
	"psu_status_table"	=> $psu_status_table,
    );
}


sub get_table
{
    my ($s, @tbl) = @_;

    my $table = [];
    my $tblid = $tbl[0]->[0];
    my $i = 0;

    my $row = new SNMP::VarList (@tbl);

    return ("MIB problem") if (!defined $row);

    while (defined ($s->getnext ($row)))
    {
    	last if ($s->{"ErrorStr"} ne "");

	my $r = $row->[0]->[0];

 	last if ($r ne $tblid);

	foreach my $col (@{$row})
	{
	    $table->[$i]->{"iid"} = $col->[1];
	    $table->[$i]->{$col->[0]} = $col->[2];
	}

	$i++;
    }

    return ($s->{"ErrorStr"}) if ($s->{"ErrorStr"} ne "");

    (
    	"",
	$table,
    );
}


sub get_vars
{
    my ($s, @vars) = @_;

    my $r = new SNMP::VarList ( @vars );

    return ("MIB problem") if (!defined $r);

    return ($s->ErrorStr) if (!defined ($s->get ($r)));

    my $v;

    foreach my $element (@{$r})
    {
    	$v->{$element->[0]} = $element->[2];
    }

    ("", $v);
}


sub fancy_psu_table
{
    my ($host, $psu_status_table) = @_;

    my $detail;

    foreach my $r (@{$psu_status_table})
    {
	$detail .= sprintf ("%-12s psu %-3s %-10s %-11s %-12s %-11s %-4s %s\n",
		$host,
		$r->{"iid"},
		$r->{"cpqHeFltTolPowerSupplyPresent"},
		$r->{"cpqHeFltTolPowerSupplyStatus"},
		$r->{"cpqHeFltTolPowerSupplyRedundant"},
		$r->{"cpqHeFltTolPowerSupplyCondition"},
		$r->{"cpqHeFltTolPowerSupplyBay"},
		$r->{"cpqHeFltTolPowerSupplyChassis"},
		);
    }

    $detail;
}


sub fancy_fan_table
{
    my ($host, $fan_table) = @_;

    my $detail;

    foreach my $r (@{$fan_table})
    {
	$detail .= sprintf ("%-12s fan %-3s %-4s %-9s %-10s %-7s %-12s %-7s %-7s %s\n",
		$host,
		$r->{"iid"},
		$r->{"cpqHeFltTolFanChassis"},
		$r->{"cpqHeFltTolFanLocale"},
		$r->{"cpqHeFltTolFanPresent"},
		$r->{"cpqHeFltTolFanCondition"},
		$r->{"cpqHeFltTolFanRedundant"},
		$r->{"cpqHeFltTolFanRedundantPartner"},
		$r->{"cpqHeFltTolFanSpeed"},
		$r->{"cpqHeFltTolFanType"},
		);
    }

    $detail;
}
