#!/usr/local/bin/perl


use Getopt::Long;
use vars qw($opt_V $opt_h $opt_w $opt_t $opt_c $opt_d $opt_H $opt_C $PROGNAME);
use lib "/usr/local/nagios/libexec"  ;

$PROGNAME = "check_ilom.pl";
$VERSION = '1.3';

Getopt::Long::Configure('bundling');
GetOptions
        ("V"   => \$opt_V, "version"    => \$opt_V,
         "v"   => \$opt_v, "verbose"    => \$opt_v,
         "h"   => \$opt_h, "help"       => \$opt_h,
         "t=s"   => \$opt_t, "type=s"     => \$opt_t,
         #"w=s" => \$opt_w, "warning=s"  => \$opt_w,
         #"c=s" => \$opt_c, "critical=s" => \$opt_c,
         "H=s" => \$opt_H, "hostname=s" => \$opt_H,
         "c=s" => \$opt_c, "community=s" => \$opt_c);


if ($opt_V) {
   version();
}

sub usage {
   print "$PROGNAME -H <hostname> -t <architecturetype> -c <community>\n";
   print "where 'architecturetype' is either 'x86' or 'sparc'\n\n";
}

#Version message information displayed in both --version and --help
sub version {
    print <<"EOF";
This is version $VERSION of check_ilom.

Copyright (c) 2007 Eric Schoeller (eric.schoeller <at> coloradoDOTedu).
All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License.
See http://www.fsf.org/licensing/licenses/gpl.html

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.

EOF

    exit 1;
}

if (!$opt_H || !$opt_c || !$opt_t) {
  usage(); 
  version();
}

#print $opt_t;
#if ($opt_t ne "x86") {
#   print "Invalid Architecture Type!\n";
#   exit 1;
#}

# Obtain some values from the command line args
$archtype=$opt_t;
$snmpcomm=$opt_c;
$hostname=$opt_H;
$verbose=$opt_v;

# Define where our SNMP utilities are.
$snmpwalk="/usr/bin/snmpwalk";
$snmpget="/usr/bin/snmpget";

# Define the OIDs to use here. Consult the SUN-PLATFORM-MIB file.
# SUN-PLATFORM-MIB::sunPlatAlarmState
$alarmstateoid=".1.3.6.1.4.1.42.2.70.101.1.1.12.1.2";
# SUN-PLATFORM-MIB::sunPlatEquipmentLocationName
$alarmnamesoid=".1.3.6.1.4.1.42.2.70.101.1.1.2.1.5";

# For SPARC architectures ...
# Look at the alarms table ... For now we ignore alarms 3 and 4
# or /SYS/LOCATE and /SYS/ACT. It's silly to report on these ...!
if ($archtype eq "sparc") {
   @ignore_alarms = ("sunPlatAlarmState.3", "sunPlatAlarmState.4");
}

# For x86 architectures ...
# Again, look at the alarms table ... We ignore alarm 113
# or /SYS/POWER. I'm not sure why there is an alarm for POWER on 
# this architecture. If it's ever OFF, the ilom won't work anyway ...
# state 4, or alternating, may be useful to report on for some people
# but it would require a lot of changes to this code, and I'd hope that
# people would be watching the host itself with nagios as well.
elsif ($archtype eq "x86") {
   @ignore_alarms = ("sunPlatAlarmState.113");
}

# Unfortunately, we need to specify by model number when it comes to x86 units. 
# There are just too many variables.

elsif ($archtype eq "x86-4250") {
   # @ignore_alarms = ("sunPlatAlarmState.113");
   # @alternating_alarms = ("sunPlatAlarmState.76", "sunPlatAlarmState.84", "sunPlatAlarmState.92", "sunPlatAlarmState.102", "sunPlatAlarmState.110", "sunPlatAlarmState.118", "sunPlatAlarmState.216");
   @alternating_alarms = ("/SYS/FB0/FM0/OK", "/SYS/FB0/FM1/OK", "/SYS/FB0/FM2/OK", "/SYS/FB1/FM0/OK", "/SYS/FB1/FM1/OK", "/SYS/FB1/FM2/OK", "/SYS/OK");
}

elsif ($archtype eq "x86-4150") {
   # @alternating_alarms = ("sunPlatAlarmState.43", "sunPlatAlarmState.51", "sunPlatAlarmState.59", "sunPlatAlarmState.67", "sunPlatAlarmState.77", "sunPlatAlarmState.85", "sunPlatAlarmState.93", "sunPlatAlarmState.206");
   @alternating_alarms = ("/SYS/FB0/FM0/OK", "/SYS/FB0/FM1/OK", "/SYS/FB0/FM2/OK", "/SYS/FB0/FM3/OK", "/SYS/FB1/FM0/OK", "/SYS/FB1/FM1/OK", "/SYS/FB1/FM2/OK", "/SYS/OK");
   # 20090511 Upgrading to version 3.0 of ilom firmware caused sunPlatEquipmentLocationName table to become useless for our purposes.
   # Now, the ENTITY-MIB::entPhysicalName is more useful. We override the OID defined for alarmnamesoid here, until we upgrade all of our
   # iloms to v3
   # ENTITY-MIB::entPhysicalName
   $alarmnamesoid=".1.3.6.1.2.1.47.1.1.1.1.7";
}

# This should really go somewhere else!
else {
   print "Invalid Architecture!\n\n";
   usage();
   version();
}

# Do a quick sanity check to see if host is responding to SNMP. If not, we will exit with an UNKNOWN state.
# We override the snmp timeout, and use just 1 second, which really doesn't mean 1 second. 
$sanity_check = `$snmpget -t 1 -v2c -c $snmpcomm $hostname .1.3.6.1.2.1.1.5.0 2>&1`;

if ($sanity_check =~ /Timeout: No Response from/ or $? ne 0) {
   print "UNKNOWN - host did not respond to SNMP, or a general error occured";
   exit(3);
}
   
# This is the 'OK' state. Any alarm in this state will be skipped.
# Reference the Alarm state table, this is off(2). 
$ok_state=2;

# This is the 'Alternating' state. Any alarm in this state, and specified
# explicity as an alarm that normall alternates (@alternating_alarms) will
# be skipped.
$alternating_state=3;

# Minimum Expected number of alarms.
# This depends on the number of hard drives, number of fans etc installed in the system.
# I have seen strange differences between our 5120's when it comes to fans already. Systems
# with the exact same fan configuration have been known to report on them differently. Your
# mileage may vary with this. The default of 15 should work for most people.
# Our x4100's seem to have 26 possible alarms. I think it may be overkill to specify separate 
# num_expected_alarms per architecture, or even per server model.
$num_expected_alarms=15;

# Timeout for SNMP calls. 
$snmp_timeout=25;

# Some global arrays are defined here.
my @alarmstates;
my @alarmnames;


# The snmpwalk here uses output 'esq' which will have alarm.INDEX value like "sunPlatAlarmState.2 3"
# This just seemed like the best way to obtain the data for processing in this scenario. 
sub walk_states {
   @alarmstates = `$snmpwalk -t $snmp_timeout -m ALL -v 2c -O esq -c $snmpcomm $hostname $alarmstateoid`;
   return @alarmstates;
}

# This is currently not used, but is included here in case someone may need it in the future.
# This function will grab ALL the LocationNames. This includes other Locations that are NOT alarms.
sub walk_names {
   @alarmnames = `$snmpwalk -t $snmp_timeout -m ALL -v 2c -O vq -c $snmpcomm $hostname $alarmnamesoid`;
   return @alarmnames;
}


# Grab the alarm states.
walk_states();

# Clean up the snmp data
chomp(@alarmstates);

# Determine the number of alarms obtained (will probably always be the same)
# (see comment above on $num_expected_alarms)
$num_alarms = @alarmstates;


ALARM: for ($i=0; $i < $num_alarms; $i++) {

   # Determine if we should ignore this alarm based on the @ignore_alarms.
   foreach $ignore (@ignore_alarms) {
      next ALARM if $alarmstates[$i] =~ /$ignore/; 
   }

   # split the state and the OID apart
   @alarm_split = split(/ /, $alarmstates[$i]);
   $alarm_state = $alarm_split[1];
   $alarm_oid = $alarm_split[0];

   # Let's break out of this now if the state is OK. 
   next ALARM if $alarm_state eq $ok_state;

   # split the sunPlatAlarmState and the alarm ID apart
   @alarm_splitted = split(/\./, $alarm_oid);
   $alarm_id = $alarm_splitted[1];
   
   # well, we have an errored alarm, and it's ID. Now we need the name. 
   # Notice output option is 'vq' This will only print the name/value - nothing else 
   # NOTE: considerable testing is needed here. There is a chance that if there are tons of errored
   # alarms this plugin will timeout. I was only able to test with several alarms and never had significant problems.

   $alarm_name = `$snmpget -t $snmp_timeout -m ALL -v 2c -O vq -c $snmpcomm $hostname ${alarmnamesoid}.${alarm_id}`;
   chomp($alarm_name);

   # Ok, we have an alarm that isn't OK. Let's verify that it's not an alarm
   # that's expected to be alternating. If it is, we will skip it.
   if (@alternating_alarms) {
      foreach $alternating (@alternating_alarms) {
         if ($alarm_name =~ /$alternating/) {
            next ALARM if $alarm_state eq $alternating_state;
         }
      }
   }

   # Push the alarm name into @active_alarms
   push(@active_alarms,$alarm_name);

}

# Here are the number of active alarms in a nice scalar value.
$num_active_alarms = @active_alarms;


# Error Output and script termination

# This is a stub for adding CRITICAL functionality. Currently it is not used.
#if ($num_active_alarms >= CRITVALUE) {
#   print "CRITICAL - $num_active_alarms Alert(s) @active_alarms"; 
#   exit(2);
#}
if ($num_active_alarms) {
   print "WARNING - $num_active_alarms Alarm(s) @active_alarms";
   exit(1);
}
elsif ($num_alarms > $num_expected_alarms) {
   print "OK - No Alarms are active";
   exit(0);
}


# Should never get here - but you WILL if num_expected_alarms is not reached. 
# This will give a heads up that something is wrong.
print "UNKNOWN - unable to obtain alarm states";
exit(3);

__END__

=head1 NAME

check_ilom - Checks the alarm states on a "Sun Integrated Lights Out Manager" interface

=head1 VERSION

This documentation refers to check_ilom version 1.1

=head1 APPLICATION REQUIREMENTS

You _really_ should have the SUN-PLATFORM-MIB file installed. It's absolutely necessary to have this MIB
installed if you want this plugin to work out of the box. It IS possible to use the plugin without
the MIB file. You would need to replace all instances of the named OID (SUN-PLATFORM-MIB ...) with the 
numerical OID equivalent in the code.

This application also relies on the use of SNMP v2c. It is not configured to use SNMP v1 (nor is there an
option to enable v1) but a few modifications to the code should get it to work just fine. There is absolutely
no support for v3. Much more modification to the code would be necessary for v3 to work. I have no plans to 
support v3 because we don't use it.

This plugin does NOT use the perl SNMP module, but it really should. If you have time, please code it!

=head1 USAGE

 check_ilom -H <hostname> -t <architecturetype> -c <community>
 where 'architecturetype' is either 'x86' or 'sparc'

=head1 REQUIRED ARGUMENTS

 hostname = the IP address of the ILOM interface to check
 community = the SNMP community name, by default this is normally set to PUBLIC.
 architecturetype = Wether this ILOM interface is attached to an x86 based system or sparc based system

=head1 MORE INFORMATION

=head2 Available Alarms on Sun Architecutre (May be specific to T5120's)

 Here is the Table of alarms available on the T5120. This shows the IPMI SMASH names of the alarms

 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.2 = STRING: /SYS/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.3 = STRING: /SYS/LOCATE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.4 = STRING: /SYS/ACT
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.5 = STRING: /SYS/PS_FAULT
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.6 = STRING: /SYS/TEMP_FAULT
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.7 = STRING: /SYS/FAN_FAULT
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.46 = STRING: /SYS/MB/CMP0/BR0/CH0/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.54 = STRING: /SYS/MB/CMP0/BR0/CH1/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.63 = STRING: /SYS/MB/CMP0/BR1/CH0/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.71 = STRING: /SYS/MB/CMP0/BR1/CH1/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.80 = STRING: /SYS/MB/CMP0/BR2/CH0/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.88 = STRING: /SYS/MB/CMP0/BR2/CH1/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.97 = STRING: /SYS/MB/CMP0/BR3/CH0/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.105 = STRING: /SYS/MB/CMP0/BR3/CH1/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.244 = STRING: /SYS/HDD0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.245 = STRING: /SYS/HDD0/OK2RM
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.248 = STRING: /SYS/HDD1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.249 = STRING: /SYS/HDD1/OK2RM
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.252 = STRING: /SYS/HDD2/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.253 = STRING: /SYS/HDD2/OK2RM
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.256 = STRING: /SYS/HDD3/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.257 = STRING: /SYS/HDD3/OK2RM
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.269 = STRING: /SYS/FANBD0/FM1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.278 = STRING: /SYS/FANBD1/FM0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.285 = STRING: /SYS/FANBD1/FM1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.292 = STRING: /SYS/FANBD1/FM2/SERVICE

=head2 Available Alarms on x86 Architecture (May be specific to x4100's)

 Here is a Table of alarms available on the x4100. This shows the IPMI SMASH names of the alarms

 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.15 = STRING: /SYS/FT0/FM0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.21 = STRING: /SYS/FT0/FM1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.27 = STRING: /SYS/FT0/FM2/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.34 = STRING: /SYS/FT1/FM0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.40 = STRING: /SYS/FT1/FM1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.46 = STRING: /SYS/FT1/FM2/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.54 = STRING: /SYS/MB/P0/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.56 = STRING: /SYS/MB/P0/D1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.58 = STRING: /SYS/MB/P0/D2/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.60 = STRING: /SYS/MB/P0/D3/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.61 = STRING: /SYS/MB/P0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.68 = STRING: /SYS/MB/P1/D0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.70 = STRING: /SYS/MB/P1/D1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.72 = STRING: /SYS/MB/P1/D2/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.74 = STRING: /SYS/MB/P1/D3/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.75 = STRING: /SYS/MB/P1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.104 = STRING: /SYS/SASBP/HDD0/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.106 = STRING: /SYS/SASBP/HDD1/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.108 = STRING: /SYS/SASBP/HDD2/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.110 = STRING: /SYS/SASBP/HDD3/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.111 = STRING: /SYS/FAN_FAULT
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.112 = STRING: /SYS/LOCATE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.113 = STRING: /SYS/POWER
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.114 = STRING: /SYS/PSU_FAULT
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.115 = STRING: /SYS/SERVICE
 SUN-PLATFORM-MIB::sunPlatEquipmentLocationName.116 = STRING: /SYS/TEMP_FAULT

=head2 Alarm States for either x86 or Sparc Architectures

 The following was taken directly from the SUN-PLATFORM-MIB file.
 They are supposed to be CIM compliant, however the CIM definitions are different than these...

 Alarm State Table 
   unknown(1),
   off(2),
   steady(3),
   alternating(4)

=head2 General design and process structure of check_ilom

 It's simple really! 

 1. Read in the list of available alarms from the ILOM SNMP agent.
 2. Ignore some alarms that aren't worth reporting on
 3. Obtain the real names of each failed alarm
 4. Exit appropriately

=head1 INCOMPATIBILITIES

 None. See Bugs.

=head1 BUGS AND LIMITATIONS

 None.
 If you experience any problems please contact me. (eric.schoeller <at> coloradoDOTedu)

=head1 AUTHOR

Eric Schoeller (eric.schoeller <at> coloradoDOTedu)

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2008 Eric Schoeller (eric.schoeller <at> coloradoDOTedu).
All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License.
See L<http://www.fsf.org/licensing/licenses/gpl.html>.

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. 
