]> git.uio.no Git - check_openmanage.git/blob - check_openmanage
finpussing
[check_openmanage.git] / check_openmanage
1 #!/usr/bin/perl
2 #
3 # Nagios plugin
4 #
5 # Monitor Dell server hardware status using Dell OpenManage Server
6 # Administrator, either locally via NRPE, or remotely via SNMP.
7 #
8 # $Id$
9 #
10 # Copyright (C) 2010 Trond H. Amundsen
11 #
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful, but
18 # WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 # General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 require 5.006;  # Perl v5.6.0 or newer is required
27 use strict;
28 use warnings;
29 use POSIX qw(isatty ceil);
30 use Getopt::Long qw(:config no_ignore_case);
31
32 # Global (package) variables used throughout the code
33 use vars qw( $NAME $VERSION $AUTHOR $CONTACT $E_OK $E_WARNING $E_CRITICAL
34              $E_UNKNOWN $FW_LOCK $USAGE $HELP $LICENSE
35              $snmp_session $snmp_error $omreport $globalstatus $global
36              $linebreak $omopt_chassis $omopt_system $blade
37              $exit_code $snmp
38              %check %opt %reverse_exitcode %status2nagios
39              %snmp_status %snmp_probestatus %probestatus2nagios %sysinfo
40              %blacklist %nagios_alert_count %count %snmp_enclosure %snmp_controller
41              @perl_warnings @controllers @enclosures @perfdata
42              @report_storage @report_chassis @report_other
43           );
44
45 #---------------------------------------------------------------------
46 # Initialization and global variables
47 #---------------------------------------------------------------------
48
49 # Collect perl warnings in an array
50 $SIG{__WARN__} = sub { push @perl_warnings, [@_]; };
51
52 # Version and similar info
53 $NAME    = 'check_openmanage';
54 $VERSION = '3.6.1-beta2';
55 $AUTHOR  = 'Trond H. Amundsen';
56 $CONTACT = 't.h.amundsen@usit.uio.no';
57
58 # Exit codes
59 $E_OK       = 0;
60 $E_WARNING  = 1;
61 $E_CRITICAL = 2;
62 $E_UNKNOWN  = 3;
63
64 # Firmware update lock file [FIXME: location on Windows?]
65 $FW_LOCK = '/var/lock/.spsetup';  # default on Linux
66
67 # Usage text
68 $USAGE = <<"END_USAGE";
69 Usage: $NAME [OPTION]...
70 END_USAGE
71
72 # Help text
73 $HELP = <<'END_HELP';
74
75 GENERAL OPTIONS:
76
77    -p, --perfdata      Output performance data [no]
78    -t, --timeout       Plugin timeout in seconds [30]
79    -c, --critical      Customise temperature critical limits
80    -w, --warning       Customise temperature warning limits
81    -d, --debug         Debug output, reports everything
82    -h, --help          Display this help text
83    -V, --version       Display version info
84
85 SNMP OPTIONS:
86
87    -H, --hostname      Hostname or IP (required for SNMP)
88    -C, --community     SNMP community string [public]
89    -P, --protocol      SNMP protocol version [2]
90    --port              SNMP port number [161]
91
92 OUTPUT OPTIONS:
93
94    -i, --info          Prefix any alerts with the service tag
95    -e, --extinfo       Append system info to alerts
96    -s, --state         Prefix alerts with alert state
97    -S, --short-state   Prefix alerts with alert state (abbreviated)
98    -o, --okinfo        Verbosity when check result is OK
99    -I, --htmlinfo      HTML output with clickable links
100
101 CHECK CONTROL AND BLACKLISTING:
102
103    -a, --all           Check everything, even log content
104    -b, --blacklist     Blacklist missing and/or failed components
105    --only              Only check a certain component or alert type
106    --check             Fine-tune which components are checked
107    --no-storage        Don't check storage (alias for "--check storage=0")
108
109 For more information and advanced options, see the manual page or URL:
110   http://folk.uio.no/trondham/software/check_openmanage.html
111 END_HELP
112
113 # Version and license text
114 $LICENSE = <<"END_LICENSE";
115 $NAME $VERSION
116 Copyright (C) 2010 $AUTHOR
117 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
118 This is free software: you are free to change and redistribute it.
119 There is NO WARRANTY, to the extent permitted by law.
120
121 Written by $AUTHOR <$CONTACT>
122 END_LICENSE
123
124 # Options with default values
125 %opt = ( 'blacklist'         => [],       # blacklisting
126          'check'             => [],       # check control
127          'critical'          => [],       # temperature critical limits
128          'warning'           => [],       # temperature warning limits
129          'timeout'           => 30,       # default timeout is 30 seconds
130          'debug'             => 0,        # debugging / verbose output
131          'help'              => 0,        # display help output
132          'perfdata'          => undef,    # output performance data
133          'info'              => 0,        # display servicetag
134          'extinfo'           => 0,        # display extra info
135          'htmlinfo'          => undef,    # html tags in output
136          'postmsg'           => undef,    # post message
137          'state'             => 0,        # display alert type
138          'short-state'       => 0,        # display alert type (short)
139          'okinfo'            => 0,        # default "ok" output level
140          'linebreak'         => undef,    # specify linebreak
141          'version'           => 0,        # plugin version info
142          'all'               => 0,        # check everything
143          'only'              => undef,    # only one component
144          'no_storage'        => 0,        # don't check storage
145          'omreport'          => undef,    # omreport path
146          'port'              => 161,      # default SNMP port
147          'hostname'          => undef,    # hostname or IP
148          'community'         => 'public', # SMNP v1 or v2c
149          'protocol'          => 2,        # default SNMP protocol 2c
150          'username'          => undef,    # SMNP v3
151          'authpassword'      => undef,    # SMNP v3
152          'authkey'           => undef,    # SMNP v3
153          'authprotocol'      => undef,    # SMNP v3
154          'privpassword'      => undef,    # SMNP v3
155          'privkey'           => undef,    # SMNP v3
156          'privprotocol'      => undef,    # SMNP v3
157          'use_get_table'     => 0,        # hack for SNMPv3 on Windows with net-snmp
158        );
159
160 # Get options
161 GetOptions('b|blacklist=s'      => \@{ $opt{blacklist} },
162            'check=s'            => \@{ $opt{check} },
163            'c|critical=s'       => \@{ $opt{critical} },
164            'w|warning=s'        => \@{ $opt{warning} },
165            't|timeout=i'        => \$opt{timeout},
166            'd|debug'            => \$opt{debug},
167            'h|help'             => \$opt{help},
168            'V|version'          => \$opt{version},
169            'p|perfdata:s'       => \$opt{perfdata},
170            'i|info'             => \$opt{info},
171            'e|extinfo'          => \$opt{extinfo},
172            'I|htmlinfo:s'       => \$opt{htmlinfo},
173            'postmsg=s'          => \$opt{postmsg},
174            's|state'            => \$opt{state},
175            'S|short-state'      => \$opt{shortstate},
176            'o|ok-info=i'        => \$opt{okinfo},
177            'linebreak=s'        => \$opt{linebreak},
178            'a|all'              => \$opt{all},
179            'only=s'             => \$opt{only},
180            'no-storage'         => \$opt{no_storage},
181            'omreport=s'         => \$opt{omreport},
182            'port=i'             => \$opt{port},
183            'H|hostname=s'       => \$opt{hostname},
184            'C|community=s'      => \$opt{community},
185            'P|protocol=i'       => \$opt{protocol},
186            'U|username=s'       => \$opt{username},
187            'authpassword=s'     => \$opt{authpassword},
188            'authkey=s'          => \$opt{authkey},
189            'authprotocol=s'     => \$opt{authprotocol},
190            'privpassword=s'     => \$opt{privpassword},
191            'privkey=s'          => \$opt{privkey},
192            'privprotocol=s'     => \$opt{privprotocol},
193            'use-get_table'      => \$opt{use_get_table},
194           ) or do { print $USAGE; exit $E_UNKNOWN };
195
196 # If user requested help
197 if ($opt{help}) {
198     print $USAGE, $HELP;
199     exit $E_OK;
200 }
201
202 # If user requested version info
203 if ($opt{version}) {
204     print $LICENSE;
205     exit $E_OK;
206 }
207
208 # Setting timeout
209 $SIG{ALRM} = sub {
210     print "PLUGIN TIMEOUT: $NAME timed out after $opt{timeout} seconds\n";
211     exit $E_UNKNOWN;
212 };
213 alarm $opt{timeout};
214
215 # If we're using SNMP
216 $snmp = defined $opt{hostname} ? 1 : 0;
217
218 # SNMP session variables
219 $snmp_session = undef;
220 $snmp_error   = undef;
221
222 # The omreport command
223 $omreport = undef;
224
225 # Check flags, override available with the --check option
226 %check = ( 'storage'     => 1,   # check storage subsystem
227            'memory'      => 1,   # check memory (dimms)
228            'fans'        => 1,   # check fan status
229            'power'       => 1,   # check power supplies
230            'temp'        => 1,   # check temperature
231            'cpu'         => 1,   # check processors
232            'voltage'     => 1,   # check voltage
233            'batteries'   => 1,   # check battery probes
234            'amperage'    => 1,   # check power consumption
235            'intrusion'   => 1,   # check intrusion detection
236            'alertlog'    => 0,   # check the alert log
237            'esmlog'      => 0,   # check the ESM log (hardware log)
238            'esmhealth'   => 1,   # check the ESM log overall health
239          );
240
241 # Default line break
242 $linebreak = isatty(*STDOUT) ? "\n" : '<br/>';
243
244 # Line break from option
245 if (defined $opt{linebreak}) {
246     if ($opt{linebreak} eq 'REG') {
247         $linebreak = "\n";
248     }
249     elsif ($opt{linebreak} eq 'HTML') {
250         $linebreak = '<br/>';
251     }
252     else {
253         $linebreak = $opt{linebreak};
254     }
255 }
256
257 # Exit with status=UNKNOWN if there is firmware upgrade in progress
258 if (!$snmp && -f $FW_LOCK) {
259     print "MONITORING DISABLED - Firmware update in progress ($FW_LOCK exists)\n";
260     exit $E_UNKNOWN;
261 }
262
263 # List of controllers and enclosures
264 @controllers = ();  # controllers
265 @enclosures  = ();  # enclosures
266 %snmp_enclosure   = ();  # enclosures
267
268 # Messages
269 @report_storage = ();  # messages with associated nagios level (storage)
270 @report_chassis = ();  # messages with associated nagios level (chassis)
271 @report_other   = ();  # messages with associated nagios level (other)
272
273 # Counters for everything
274 %count
275   = (
276      'pdisk'  => 0, # number of physical disks
277      'vdisk'  => 0, # number of logical drives (virtual disks)
278      'temp'   => 0, # number of temperature probes
279      'volt'   => 0, # number of voltage probes
280      'amp'    => 0, # number of amperage probes
281      'intr'   => 0, # number of intrusion probes
282      'dimm'   => 0, # number of memory modules
283      'mem'    => 0, # total memory
284      'fan'    => 0, # number of fan probes
285      'cpu'    => 0, # number of CPUs
286      'bat'    => 0, # number of batteries
287      'power'  => 0, # number of power supplies
288      'esm'    => {
289                   'Critical'     => 0, # critical entries in ESM log
290                   'Non-Critical' => 0, # warning entries in ESM log
291                   'Ok'           => 0, # ok entries in ESM log
292                  },
293      'alert'  => {
294                   'Critical'     => 0, # critical entries in alert log
295                   'Non-Critical' => 0, # warning entries in alert log
296                   'Ok'           => 0, # ok entries in alert log
297                  },
298     );
299
300 # Performance data
301 @perfdata = ();
302
303 # Global health status
304 $global         = 1;      # default is to check global status
305 $globalstatus   = $E_OK;  # default global health status is "OK"
306
307 # Nagios error levels reversed
308 %reverse_exitcode
309   = (
310      $E_OK       => 'OK',
311      $E_WARNING  => 'WARNING',
312      $E_CRITICAL => 'CRITICAL',
313      $E_UNKNOWN  => 'UNKNOWN',
314     );
315
316 # OpenManage (omreport) and SNMP error levels
317 %status2nagios
318   = (
319      'Unknown'         => $E_CRITICAL,
320      'Critical'        => $E_CRITICAL,
321      'Non-Critical'    => $E_WARNING,
322      'Ok'              => $E_OK,
323      'Non-Recoverable' => $E_CRITICAL,
324      'Other'           => $E_CRITICAL,
325     );
326
327 # Status via SNMP
328 %snmp_status
329   = (
330      1 => 'Other',
331      2 => 'Unknown',
332      3 => 'Ok',
333      4 => 'Non-Critical',
334      5 => 'Critical',
335      6 => 'Non-Recoverable',
336     );
337
338 # Probe Status via SNMP
339 %snmp_probestatus
340   = (
341      1  => 'Other',               # probe status is not one of the following:
342      2  => 'Unknown',             # probe status is unknown (not known or monitored)
343      3  => 'Ok',                  # probe is reporting a value within the thresholds
344      4  => 'nonCriticalUpper',    # probe has crossed upper noncritical threshold
345      5  => 'criticalUpper',       # probe has crossed upper critical threshold
346      6  => 'nonRecoverableUpper', # probe has crossed upper non-recoverable threshold
347      7  => 'nonCriticalLower',    # probe has crossed lower noncritical threshold
348      8  => 'criticalLower',       # probe has crossed lower critical threshold
349      9  => 'nonRecoverableLower', # probe has crossed lower non-recoverable threshold
350      10 => 'failed',              # probe is not functional
351     );
352
353 # Probe status translated to Nagios alarm levels
354 %probestatus2nagios
355   = (
356      'Other'               => $E_CRITICAL,
357      'Unknown'             => $E_CRITICAL,
358      'Ok'                  => $E_OK,
359      'nonCriticalUpper'    => $E_WARNING,
360      'criticalUpper'       => $E_CRITICAL,
361      'nonRecoverableUpper' => $E_CRITICAL,
362      'nonCriticalLower'    => $E_WARNING,
363      'criticalLower'       => $E_CRITICAL,
364      'nonRecoverableLower' => $E_CRITICAL,
365      'failed'              => $E_CRITICAL,
366     );
367
368 # System information gathered
369 %sysinfo
370   = (
371      'bios'     => 'N/A',  # BIOS version
372      'biosdate' => 'N/A',  # BIOS release date
373      'serial'   => 'N/A',  # serial number (service tag)
374      'model'    => 'N/A',  # system model
375      'rev'      => q{},    # system revision
376      'osname'   => 'N/A',  # OS name
377      'osver'    => 'N/A',  # OS version
378      'om'       => 'N/A',  # OMSA version
379      'bmc'      => 0,      # HAS baseboard management controller (BMC)
380      'rac'      => 0,      # HAS remote access controller (RAC)
381      'rac_name' => 'N/A',  # remote access controller (RAC)
382      'bmc_fw'   => 'N/A',  # BMC firmware
383      'rac_fw'   => 'N/A',  # RAC firmware
384     );
385
386 # Adjust which checks to perform
387 adjust_checks() if defined $opt{check};
388
389 # Blacklisted components
390 %blacklist = defined $opt{blacklist} ? %{ get_blacklist() } : ();
391
392 # If blacklisting is in effect, don't check global health status
393 if (scalar keys %blacklist > 0) {
394     $global = 0;
395 }
396
397 # Take into account new hardware and blades
398 $omopt_chassis = 'chassis';  # default "chassis" option to omreport
399 $omopt_system  = 'system';   # default "system" option to omreport
400 $blade         = 0;          # if this is a blade system
401
402 # Some initializations and checking before we begin
403 if ($snmp) {
404     snmp_initialize();    # initialize SNMP
405     snmp_check();         # check that SNMP works
406     snmp_detect_blade();  # detect blade via SNMP
407 }
408 else {
409     # Find the omreport binary
410     find_omreport();
411     # Check help output from omreport, see which options are available.
412     # Also detecting blade via omreport.
413     check_omreport_options();
414 }
415
416
417 #---------------------------------------------------------------------
418 # Helper functions
419 #---------------------------------------------------------------------
420
421 #
422 # Store a message in one of the message arrays
423 #
424 sub report {
425     my ($type, $msg, $exval, $id) = @_;
426     defined $id or $id = q{};
427
428     my %type2array
429       = (
430          'storage' => \@report_storage,
431          'chassis' => \@report_chassis,
432          'other'   => \@report_other,
433         );
434
435     return push @{ $type2array{$type} }, [ $msg, $exval, $id ];
436 }
437
438
439 #
440 # Run command, put resulting output lines in an array and return a
441 # pointer to that array
442 #
443 sub run_command {
444     my $command = shift;
445
446     open my $CMD, '-|', $command
447       or do { report('other', "Couldn't run command '$command': $!", $E_UNKNOWN)
448                 and return [] };
449     my @lines = <$CMD>;
450     close $CMD
451       or do { report('other', "Couldn't close filehandle for command '$command': $!", $E_UNKNOWN)
452                 and return \@lines };
453     return \@lines;
454 }
455
456 #
457 # Run command, put resulting output in a string variable and return it
458 #
459 sub slurp_command {
460     my $command = shift;
461
462     open my $CMD, '-|', $command
463       or do { report('other', "Couldn't run command '$command': $!", $E_UNKNOWN) and return };
464     my $rawtext = do { local $/ = undef; <$CMD> }; # slurping
465     close $CMD;
466
467     # NOTE: We don't check the return value of close() since omreport
468     # does something weird sometimes.
469
470     return $rawtext;
471 }
472
473 #
474 # Initialize SNMP
475 #
476 sub snmp_initialize {
477     # Legal SNMP v3 protocols
478     my $snmp_v3_privprotocol = qr{\A des|aes|aes128|3des|3desde \z}xms;
479     my $snmp_v3_authprotocol = qr{\A md5|sha \z}xms;
480
481     # Parameters to Net::SNMP->session()
482     my %param
483       = (
484          '-port'     => $opt{port},
485          '-hostname' => $opt{hostname},
486          '-version'  => $opt{protocol},
487         );
488
489     # Parameters for SNMP v3
490     if ($opt{protocol} == 3) {
491
492         # Username is mandatory
493         if (defined $opt{username}) {
494             $param{'-username'} = $opt{username};
495         }
496         else {
497             print "SNMP ERROR: With SNMPv3 the username must be specified\n";
498             exit $E_UNKNOWN;
499         }
500
501         # Authpassword is optional
502         if (defined $opt{authpassword}) {
503             $param{'-authpassword'} = $opt{authpassword};
504         }
505
506         # Authkey is optional
507         if (defined $opt{authkey}) {
508             $param{'-authkey'} = $opt{authkey};
509         }
510
511         # Privpassword is optional
512         if (defined $opt{privpassword}) {
513             $param{'-privpassword'} = $opt{privpassword};
514         }
515
516         # Privkey is optional
517         if (defined $opt{privkey}) {
518             $param{'-privkey'} = $opt{privkey};
519         }
520
521         # Privprotocol is optional
522         if (defined $opt{privprotocol}) {
523             if ($opt{privprotocol} =~ m/$snmp_v3_privprotocol/xms) {
524                 $param{'-privprotocol'} = $opt{privprotocol};
525             }
526             else {
527                 print "SNMP ERROR: Unknown privprotocol '$opt{privprotocol}', "
528                   . "must be one of [des|aes|aes128|3des|3desde]\n";
529                 exit $E_UNKNOWN;
530             }
531         }
532
533         # Authprotocol is optional
534         if (defined $opt{authprotocol}) {
535             if ($opt{authprotocol} =~ m/$snmp_v3_authprotocol/xms) {
536                 $param{'-authprotocol'} = $opt{authprotocol};
537             }
538             else {
539                 print "SNMP ERROR: Unknown authprotocol '$opt{authprotocol}', "
540                   . "must be one of [md5|sha]\n";
541                 exit $E_UNKNOWN;
542             }
543         }
544     }
545     # Parameters for SNMP v2c or v1
546     elsif ($opt{protocol} == 2 or $opt{protocol} == 1) {
547         $param{'-community'} = $opt{community};
548     }
549     else {
550         print "SNMP ERROR: Unknown SNMP version '$opt{protocol}'\n";
551         exit $E_UNKNOWN;
552     }
553
554     # Try to initialize the SNMP session
555     if ( eval { require Net::SNMP; 1 } ) {
556         ($snmp_session, $snmp_error) = Net::SNMP->session( %param );
557         if (!defined $snmp_session) {
558             printf "SNMP: %s\n", $snmp_error;
559             exit $E_UNKNOWN;
560         }
561     }
562     else {
563         print "ERROR: You need perl module Net::SNMP to run $NAME in SNMP mode\n";
564         exit $E_UNKNOWN;
565     }
566     return;
567 }
568
569 #
570 # Checking if SNMP works by probing for "chassisModelName", which all
571 # servers should have
572 #
573 sub snmp_check {
574     my $chassisModelName = '1.3.6.1.4.1.674.10892.1.300.10.1.9.1';
575     my $result = $snmp_session->get_request(-varbindlist => [$chassisModelName]);
576
577     # Typically if remote host isn't responding
578     if (!defined $result) {
579         printf "SNMP CRITICAL: %s\n", $snmp_session->error;
580         exit $E_CRITICAL;
581     }
582
583     # If OpenManage isn't installed or is not working
584     if ($result->{$chassisModelName} =~ m{\A noSuch (Instance|Object) \z}xms) {
585         print "ERROR: (SNMP) OpenManage is not installed or is not working correctly\n";
586         exit $E_UNKNOWN;
587     }
588     return;
589 }
590
591 #
592 # Detecting blade via SNMP
593 #
594 sub snmp_detect_blade {
595     my $DellBaseBoardType = '1.3.6.1.4.1.674.10892.1.300.80.1.7.1.1';
596     my $result = $snmp_session->get_request(-varbindlist => [$DellBaseBoardType]);
597
598     # Identify blade. Older models (4th and 5th gen models) and/or old
599     # OMSA (4.x) don't have this OID. If we get "noSuchInstance" or
600     # similar, we assume that this isn't a blade
601     if (exists $result->{$DellBaseBoardType} && $result->{$DellBaseBoardType} eq '3') {
602         $blade = 1;
603     }
604     return;
605 }
606
607 #
608 # Locate the omreport binary
609 #
610 sub find_omreport {
611     # If user has specified path to omreport
612     if (defined $opt{omreport} and -x $opt{omreport}) {
613         $omreport = qq{"$opt{omreport}"};
614         return;
615     }
616
617     # Possible full paths for omreport
618     my @omreport_paths
619       = (
620          '/opt/dell/srvadmin/bin/omreport',              # default on Linux with OMSA >= 6.2.0
621          '/usr/bin/omreport',                            # default on Linux with OMSA < 6.2.0
622          '/opt/dell/srvadmin/oma/bin/omreport.sh',       # alternate on Linux
623          '/opt/dell/srvadmin/oma/bin/omreport',          # alternate on Linux
624          'C:\Program Files (x86)\Dell\SysMgt\oma\bin\omreport.exe', # default on Windows x64
625          'C:\Program Files\Dell\SysMgt\oma\bin\omreport.exe',       # default on Windows x32
626          'c:\progra~1\dell\sysmgt\oma\bin\omreport.exe', # 8bit legacy default on Windows x32
627          'c:\progra~2\dell\sysmgt\oma\bin\omreport.exe', # 8bit legacy default on Windows x64
628         );
629
630     # Find the one to use
631   OMREPORT_PATH:
632     foreach my $bin (@omreport_paths) {
633         if (-x $bin) {
634             $omreport = qq{"$bin"};
635             last OMREPORT_PATH;
636         }
637     }
638
639     # Exit with status=UNKNOWN if OM is not installed, or we don't
640     # have permission to execute the binary
641     if (!defined $omreport) {
642         print "ERROR: Dell OpenManage Server Administrator (OMSA) is not installed\n";
643         exit $E_UNKNOWN;
644     }
645     return;
646 }
647
648 #
649 # Checks output from 'omreport -?' and searches for arguments to
650 # omreport, to accommodate deprecated options "chassis" and "system"
651 # (on newer hardware), as well as blade servers.
652 #
653 sub check_omreport_options {
654     foreach (@{ run_command("$omreport -? 2>&1") }) {
655        if (m/\A servermodule /xms) {
656            # If "servermodule" argument to omreport exists, use it
657            # instead of argument "system"
658            $omopt_system = 'servermodule';
659        }
660        elsif (m/\A mainsystem /xms) {
661            # If "mainsystem" argument to omreport exists, use it
662            # instead of argument "chassis"
663            $omopt_chassis = 'mainsystem';
664        }
665        elsif (m/\A modularenclosure /xms) {
666            # If "modularenclusure" argument to omreport exists, assume
667            # that this is a blade
668            $blade = 1;
669        }
670     }
671     return;
672 }
673
674 #
675 # Read the blacklist option and return a hash containing the
676 # blacklisted components
677 #
678 sub get_blacklist {
679     my @bl = ();
680     my %blacklist = ();
681
682     if (scalar @{ $opt{blacklist} } >= 0) {
683         foreach my $black (@{ $opt{blacklist} }) {
684             my $tmp = q{};
685             if (-f $black) {
686                 open my $BL, '<', $black
687                   or do { report('other', "Couldn't open blacklist file $black: $!", $E_UNKNOWN)
688                             and return {} };
689                 chomp($tmp = <$BL>);
690                 close $BL;
691             }
692             else {
693                 $tmp = $black;
694             }
695             push @bl, $tmp;
696         }
697     }
698
699     return {} if $#bl < 0;
700
701     # Parse blacklist string, put in hash
702     foreach my $black (@bl) {
703         my @comps = split m{/}xms, $black;
704         foreach my $c (@comps) {
705             next if $c !~ m/=/xms;
706             my ($key, $val) = split /=/xms, $c;
707             my @vals = split /,/xms, $val;
708             $blacklist{$key} = \@vals;
709         }
710     }
711
712     return \%blacklist;
713 }
714
715 #
716 # Read the check option and adjust the hash %check, which is a rough
717 # list of components to be checked
718 #
719 sub adjust_checks {
720     my @cl = ();
721
722     # First, take the '--no-storage' option
723     if ($opt{no_storage}) {
724         $check{storage} = 0;
725     }
726
727     # Adjust checking based on the '--all' option
728     if ($opt{all}) {
729         # Check option usage
730         if (defined $opt{only} and $opt{only} !~ m{\A critical|warning \z}xms) {
731             print qq{ERROR: Wrong simultaneous usage of the "--all" and "--only" options\n};
732             exit $E_UNKNOWN;
733         }
734         if (scalar @{ $opt{check} } > 0) {
735             print qq{ERROR: Wrong simultaneous usage of the "--all" and "--check" options\n};
736             exit $E_UNKNOWN;
737         }
738
739         # set the check hash to check everything
740         map { $_ = 1 } values %check;
741
742         return;
743     }
744
745     # Adjust checking based on the '--only' option
746     if (defined $opt{only} and $opt{only} !~ m{\A critical|warning \z}xms) {
747         # Check option usage
748         if (scalar @{ $opt{check} } > 0) {
749             print qq{ERROR: Wrong simultaneous usage of the "--only" and "--check" options\n};
750             exit $E_UNKNOWN;
751         }
752         if (! exists $check{$opt{only}} && $opt{only} ne 'chassis') {
753             print qq{ERROR: "$opt{only}" is not a known keyword for the "--only" option\n};
754             exit $E_UNKNOWN;
755         }
756
757         # reset the check hash
758         map { $_ = 0 } values %check;
759
760         # adjust the check hash
761         if ($opt{only} eq 'chassis') {
762             map { $check{$_} = 1 } qw(memory fans power temp cpu voltage
763                                       batteries amperage intrusion esmhealth);
764         }
765         else {
766             $check{$opt{only}} = 1;
767         }
768
769         return;
770     }
771
772     # Adjust checking based on the '--check' option
773     if (scalar @{ $opt{check} } >= 0) {
774         foreach my $check (@{ $opt{check} }) {
775             my $tmp = q{};
776             if (-f $check) {
777                 open my $CL, '<', $check
778                   or do { report('other', "Couldn't open check file $check: $!", $E_UNKNOWN) and return };
779                 chomp($tmp = <$CL>);
780                 close $CL;
781             }
782             else {
783                 $tmp = $check;
784             }
785             push @cl, $tmp;
786         }
787     }
788
789     return if $#cl < 0;
790
791     # Parse checklist string, put in hash
792     foreach my $check (@cl) {
793         my @checks = split /,/xms, $check;
794         foreach my $c (@checks) {
795             next if $c !~ m/=/xms;
796             my ($key, $val) = split /=/xms, $c;
797             $check{$key} = $val;
798         }
799     }
800
801     # Check if we should check global health status
802   CHECK_KEY:
803     foreach (keys %check) {
804         next CHECK_KEY if $_ eq 'esmlog';   # not part of global status
805         next CHECK_KEY if $_ eq 'alertlog'; # not part of global status
806
807         if ($check{$_} == 0) { # found something with checking turned off
808             $global = 0;
809             last CHECK_KEY;
810         }
811     }
812
813     return;
814 }
815
816 #
817 # Runs omreport and returns an array of anonymous hashes containing
818 # the output.
819 # Takes one argument: string containing parameters to omreport
820 #
821 sub run_omreport {
822     my $command = shift;
823     my @output  = ();
824     my @keys    = ();
825
826     # Errors that are OK. Some low-end poweredge (and blades) models
827     # don't have RAID controllers, intrusion detection sensor, or
828     # redundant/instrumented power supplies etc.
829     my $ok_errors
830       = qr{
831             Intrusion\sinformation\sis\snot\sfound\sfor\sthis\ssystem  # No intrusion probe
832           | No\sinstrumented\spower\ssupplies\sfound\son\sthis\ssystem # No instrumented PS (blades/low-end)
833 #          | No\scontrollers\sfound                                     # No RAID controller
834           | No\sbattery\sprobes\sfound\son\sthis\ssystem               # No battery probes
835           | Invalid\scommand:\spwrmonitoring                           # Old hardware
836 #          | Current\sprobes\snot\sfound                                # OMSA + RHEL5.4 bug
837         }xms;
838
839     # Errors that are OK on blade servers
840     my $ok_blade_errors
841       = qr{
842               No\sfan\sprobes\sfound\son\sthis\ssystem   # No fan probes
843       }xms;
844
845     # Run omreport and fetch output
846     my $rawtext = slurp_command("$omreport $command -fmt ssv 2>&1");
847     return [] if !defined $rawtext;
848
849     # Workaround for Openmanage BUG introduced in OMSA 5.5.0
850     $rawtext =~ s{\n;}{;}gxms if $command eq 'storage controller';
851
852     # Report if no controllers found
853     if ($command eq 'storage controller' and $rawtext =~ m{No\scontrollers\sfound}xms) {
854         report('storage', 'Storage Error! No controllers found', $E_UNKNOWN);
855     }
856
857     # Openmanage sometimes puts a linebreak between "Error" and the
858     # actual error text
859     $rawtext =~ s{^Error\s*\n}{Error: }xms;
860
861     # Parse output, store in array
862     for ((split m{\n}xms, $rawtext)) {
863         if (m{\AError}xms) {
864             next if m{$ok_errors}xms;
865             next if ($blade and m{$ok_blade_errors}xms);
866             report('other', "Problem running 'omreport $command': $_", $E_UNKNOWN);
867         }
868
869         next if !m/(.*?;){2}/xms;  # ignore lines with less than 3 fields
870         my @vals = split /;/xms;
871         if ($vals[0] =~ m/\A (Index|ID|Severity|Processor|Current\sSpeed) \z/xms) {
872             @keys = @vals;
873         }
874         else {
875             my $i = 0;
876             push @output, { map { $_ => $vals[$i++] } @keys };
877         }
878
879     }
880
881     # Finally, return the collected information
882     return \@output;
883 }
884
885
886 #
887 # Checks if a component is blacklisted. Returns 1 if the component is
888 # blacklisted, 0 otherwise. Takes two arguments:
889 #   arg1: component name
890 #   arg2: component id or index
891 #
892 sub blacklisted {
893     my $name = shift;  # component name
894     my $id   = shift;  # component id
895     my $ret  = 0;      # return value
896
897     if (defined $blacklist{$name}) {
898         foreach my $comp (@{ $blacklist{$name} }) {
899             if (defined $id and ($comp eq $id or uc($comp) eq 'ALL')) {
900                 $ret = 1;
901             }
902         }
903     }
904
905     return $ret;
906 }
907
908 # Converts the NexusID from SNMP to our version
909 sub convert_nexus {
910     my $nexus = shift;
911     $nexus =~ s{\A \\}{}xms;
912     $nexus =~ s{\\}{:}gxms;
913     return $nexus;
914 }
915
916 # Sets custom temperature thresholds based on user supplied options
917 sub custom_temperature_thresholds {
918     my $type   = shift; # type of threshold, either w (warning) or c (critical)
919     my %thres  = ();    # will contain the thresholds
920     my @limits = ();    # holds the input
921
922     my @opt =  $type eq 'w' ? @{ $opt{warning} } : @{ $opt{critical} };
923
924     if (scalar @opt >= 0) {
925         foreach my $t (@opt) {
926             my $tmp = q{};
927             if (-f $t) {
928                 open my $F, '<', $t
929                   or do { report('other', "Couldn't open temperature threshold file $t: $!",
930                                  $E_UNKNOWN) and return {} };
931                 $tmp = <$F>;
932                 close $F;
933             }
934             else {
935                 $tmp = $t;
936             }
937             push @limits, $tmp;
938         }
939     }
940
941     # Parse checklist string, put in hash
942     foreach my $th (@limits) {
943         my @tmp = split m{,}xms, $th;
944         foreach my $t (@tmp) {
945             next if $t !~ m{=}xms;
946             my ($key, $val) = split m{=}xms, $t;
947             if ($val =~ m{/}xms) {
948                 my ($max, $min) = split m{/}xms, $val;
949                 $thres{$key}{max} = $max;
950                 $thres{$key}{min} = $min;
951             }
952             else {
953                 $thres{$key}{max} = $val;
954             }
955         }
956     }
957
958     return \%thres;
959 }
960
961
962 # Gets the output from SNMP result according to the OIDs checked
963 sub get_snmp_output {
964     my ($result,$oidref) = @_;
965     my @temp   = ();
966     my @output = ();
967
968     foreach my $oid (keys %{ $result }) {
969         my $short = $oid;
970         $short =~ s{\s}{}gxms;                   # remove whitespace
971         $short =~ s{\A (.+) \. (\d+) \z}{$1}xms; # remove last number
972         my $id = $2;
973         if (exists $oidref->{$short}) {
974             $temp[$id]{$oidref->{$short}} = $result->{$oid};
975         }
976     }
977
978     # Remove any empty indexes
979     foreach my $out (@temp) {
980         if (defined $out) {
981             push @output, $out;
982         }
983     }
984
985     return \@output;
986 }
987
988
989 # Map the controller or other item in-place
990 sub map_item {
991     my ($key, $val, $list)  = @_;
992
993     foreach my $lst (@{ $list }) {
994         if (!exists $lst->{$key}) {
995             $lst->{$key} = $val;
996         }
997     }
998     return;
999 }
1000
1001 # Return the URL for official Dell documentation for a specific
1002 # PowerEdge server
1003 sub documentation_url {
1004     my $model = shift;
1005
1006     # create model short form, e.g. "r710"
1007     $model =~ s{\A PowerEdge \s (.+?) \z}{lc($1)}exms;
1008
1009     # special case for blades (e.g. M600, M710), they have common
1010     # documentation
1011     $model =~ s{\A m\d+ \z}{m}xms;
1012
1013     return 'http://support.dell.com/support/edocs/systems/pe' . $model . '/';
1014 }
1015
1016 # Return the URL for warranty information for a server with a given
1017 # serial number (servicetag)
1018 sub warranty_url {
1019     my $tag = shift;
1020
1021     # Dell support sites for different parts of the world
1022     my %supportsite
1023       = (
1024          'emea' => 'http://support.euro.dell.com/support/topics/topic.aspx/emea/shared/support/my_systems_info/',
1025          'ap'   => 'http://supportapj.dell.com/support/topics/topic.aspx/ap/shared/support/my_systems_info/en/details?',
1026          'glob' => 'http://support.dell.com/support/topics/global.aspx/support/my_systems_info/details?',
1027         );
1028
1029     # warranty URLs for different country codes
1030     my %url
1031       = (
1032          # EMEA
1033          'at' => $supportsite{emea} . 'de/details?c=at&l=de&ServiceTag=',  # Austria
1034          'be' => $supportsite{emea} . 'nl/details?c=be&l=nl&ServiceTag=',  # Belgium
1035          'cz' => $supportsite{emea} . 'cs/details?c=cz&l=cs&ServiceTag=',  # Czech Republic
1036          'de' => $supportsite{emea} . 'de/details?c=de&l=de&ServiceTag=',  # Germany
1037          'dk' => $supportsite{emea} . 'da/details?c=dk&l=da&ServiceTag=',  # Denmark
1038          'es' => $supportsite{emea} . 'es/details?c=es&l=es&ServiceTag=',  # Spain
1039          'fi' => $supportsite{emea} . 'fi/details?c=fi&l=fi&ServiceTag=',  # Finland
1040          'fr' => $supportsite{emea} . 'fr/details?c=fr&l=fr&ServiceTag=',  # France
1041          'gr' => $supportsite{emea} . 'en/details?c=gr&l=el&ServiceTag=',  # Greece
1042          'it' => $supportsite{emea} . 'it/details?c=it&l=it&ServiceTag=',  # Italy
1043          'il' => $supportsite{emea} . 'en/details?c=il&l=en&ServiceTag=',  # Israel
1044          'me' => $supportsite{emea} . 'en/details?c=me&l=en&ServiceTag=',  # Middle East
1045          'no' => $supportsite{emea} . 'no/details?c=no&l=no&ServiceTag=',  # Norway
1046          'nl' => $supportsite{emea} . 'nl/details?c=nl&l=nl&ServiceTag=',  # The Netherlands
1047          'pl' => $supportsite{emea} . 'pl/details?c=pl&l=pl&ServiceTag=',  # Poland
1048          'pt' => $supportsite{emea} . 'en/details?c=pt&l=pt&ServiceTag=',  # Portugal
1049          'ru' => $supportsite{emea} . 'ru/details?c=ru&l=ru&ServiceTag=',  # Russia
1050          'se' => $supportsite{emea} . 'sv/details?c=se&l=sv&ServiceTag=',  # Sweden
1051          'uk' => $supportsite{emea} . 'en/details?c=uk&l=en&ServiceTag=',  # United Kingdom
1052          'za' => $supportsite{emea} . 'en/details?c=za&l=en&ServiceTag=',  # South Africa
1053          # America
1054          'br' => $supportsite{glob} . 'c=br&l=pt&ServiceTag=',  # Brazil
1055          'ca' => $supportsite{glob} . 'c=ca&l=en&ServiceTag=',  # Canada
1056          'mx' => $supportsite{glob} . 'c=mx&l=es&ServiceTag=',  # Mexico
1057          'us' => $supportsite{glob} . 'c=us&l=en&ServiceTag=',  # USA
1058          # Asia/Pacific
1059          'au' => $supportsite{ap} . 'c=au&l=en&ServiceTag=',  # Australia
1060          'cn' => $supportsite{ap} . 'c=cn&l=zh&ServiceTag=',  # China
1061          'in' => $supportsite{ap} . 'c=in&l=en&ServiceTag=',  # India
1062          # default fallback
1063          'XX' => $supportsite{glob} . 'ServiceTag=',  # default
1064         );
1065
1066     if (exists $url{$opt{htmlinfo}}) {
1067         return $url{$opt{htmlinfo}} . $tag;
1068     }
1069     else {
1070         return $url{XX} . $tag;
1071     }
1072 }
1073
1074
1075 # This helper function returns the corresponding value of a hash key,
1076 # but takes into account that the key may not exist
1077 sub get_hashval {
1078     my $key  = shift || return undef;
1079     my $hash = shift;
1080     return exists $hash->{$key} ? $hash->{$key} : "Undefined value $key";
1081 }
1082
1083
1084
1085 #---------------------------------------------------------------------
1086 # Check functions
1087 #---------------------------------------------------------------------
1088
1089 #-----------------------------------------
1090 # Check global health status
1091 #-----------------------------------------
1092 sub check_global {
1093     my $health = $E_OK;
1094
1095     if ($snmp) {
1096         #
1097         # Checks global status, i.e. both storage and chassis
1098         #
1099         my $systemStateGlobalSystemStatus = '1.3.6.1.4.1.674.10892.1.200.10.1.2.1';
1100         my $result = $snmp_session->get_request(-varbindlist => [$systemStateGlobalSystemStatus]);
1101         if (!defined $result) {
1102             printf "SNMP ERROR [global]: %s\n", $snmp_error;
1103             exit $E_UNKNOWN;
1104         }
1105         $health = $status2nagios{$snmp_status{$result->{$systemStateGlobalSystemStatus}}};
1106     }
1107     else {
1108         #
1109         # NB! This does not check storage, only chassis...
1110         #
1111         foreach (@{ run_command("$omreport $omopt_system -fmt ssv") }) {
1112             next if !m/;/xms;
1113             next if m/\A SEVERITY;COMPONENT/xms;
1114             if (m/\A (.+?);Main\sSystem(\sChassis)? /xms) {
1115                 $health = $status2nagios{$1};
1116                 last;
1117             }
1118         }
1119     }
1120
1121     # Return the status
1122     return $health;
1123 }
1124
1125
1126 #-----------------------------------------
1127 # STORAGE: Check controllers
1128 #-----------------------------------------
1129 sub check_controllers {
1130     return if blacklisted('ctrl', 'all');
1131
1132     my $id       = undef;
1133     my $nexus    = undef;
1134     my $name     = undef;
1135     my $state    = undef;
1136     my $status   = undef;
1137     my $minfw    = undef;
1138     my $mindr    = undef;
1139     my $firmware = undef;
1140     my $driver   = undef;
1141     my $minstdr  = undef;  # Minimum required Storport driver version
1142     my $stdr     = undef;  # Storport driver version
1143     my @output   = ();
1144
1145     if ($snmp) {
1146         my %ctrl_oid
1147           = (
1148              '1.3.6.1.4.1.674.10893.1.20.130.1.1.1'  => 'controllerNumber',
1149              '1.3.6.1.4.1.674.10893.1.20.130.1.1.2'  => 'controllerName',
1150              '1.3.6.1.4.1.674.10893.1.20.130.1.1.5'  => 'controllerState',
1151              '1.3.6.1.4.1.674.10893.1.20.130.1.1.8'  => 'controllerFWVersion',
1152              '1.3.6.1.4.1.674.10893.1.20.130.1.1.38' => 'controllerComponentStatus',
1153              '1.3.6.1.4.1.674.10893.1.20.130.1.1.39' => 'controllerNexusID',
1154              '1.3.6.1.4.1.674.10893.1.20.130.1.1.41' => 'controllerDriverVersion',
1155              '1.3.6.1.4.1.674.10893.1.20.130.1.1.44' => 'controllerMinFWVersion',
1156              '1.3.6.1.4.1.674.10893.1.20.130.1.1.45' => 'controllerMinDriverVersion',
1157              '1.3.6.1.4.1.674.10893.1.20.130.1.1.55' => 'controllerStorportDriverVersion',
1158              '1.3.6.1.4.1.674.10893.1.20.130.1.1.56' => 'controllerMinRequiredStorportVer',
1159             );
1160
1161         # We use get_table() here for the odd case where a server has
1162         # two or more controllers, and where some OIDs are missing on
1163         # one of the controllers.
1164         my $controllerTable = '1.3.6.1.4.1.674.10893.1.20.130.1';
1165         my $result = $snmp_session->get_table(-baseoid => $controllerTable);
1166
1167         if (!defined $result) {
1168             report('storage', 'Storage Error! No controllers found', $E_UNKNOWN);
1169             return;
1170         }
1171
1172         @output = @{ get_snmp_output($result, \%ctrl_oid) };
1173     }
1174     else {
1175         @output = @{ run_omreport('storage controller') };
1176     }
1177
1178     my %ctrl_state
1179       = (
1180          0 => 'Unknown',
1181          1 => 'Ready',
1182          2 => 'Failed',
1183          3 => 'Online',
1184          4 => 'Offline',
1185          6 => 'Degraded',
1186         );
1187
1188   CTRL:
1189     foreach my $out (@output) {
1190         if ($snmp) {
1191             $name     = $out->{controllerName};
1192             $state    = get_hashval($out->{controllerState}, \%ctrl_state);
1193             $status   = $snmp_status{$out->{controllerComponentStatus}};
1194             $minfw    = exists $out->{controllerMinFWVersion}
1195               ? $out->{controllerMinFWVersion} : undef;
1196             $mindr    = exists $out->{controllerMinDriverVersion}
1197               ? $out->{controllerMinDriverVersion} : undef;
1198             $firmware = exists $out->{controllerFWVersion}
1199               ? $out->{controllerFWVersion} : 'N/A';
1200             $driver   = exists $out->{controllerDriverVersion}
1201               ? $out->{controllerDriverVersion} : 'N/A';
1202             $minstdr  = exists $out->{'controllerMinRequiredStorportVer'}
1203               ? $out->{controllerMinRequiredStorportVer} : undef;
1204             $stdr     = exists $out->{controllerStorportDriverVersion}
1205               ? $out->{controllerStorportDriverVersion} : undef;
1206             $nexus    = convert_nexus($out->{controllerNexusID});
1207             $id       = $nexus;
1208         }
1209         else {
1210             $id       = $out->{ID};
1211             $name     = $out->{Name};
1212             $state    = $out->{State};
1213             $status   = $out->{Status};
1214             $minfw    = $out->{'Minimum Required Firmware Version'} ne 'Not Applicable'
1215               ? $out->{'Minimum Required Firmware Version'} : undef;
1216             $mindr    = $out->{'Minimum Required Driver Version'} ne 'Not Applicable'
1217               ? $out->{'Minimum Required Driver Version'} : undef;
1218             $firmware = $out->{'Firmware Version'} ne 'Not Applicable'
1219               ? $out->{'Firmware Version'} : 'N/A';
1220             $driver   = $out->{'Driver Version'} ne 'Not Applicable'
1221               ? $out->{'Driver Version'} : 'N/A';
1222             $minstdr  = (exists $out->{'Minimum Required Storport Driver Version'}
1223                          and $out->{'Minimum Required Storport Driver Version'} ne 'Not Applicable')
1224               ? $out->{'Minimum Required Storport Driver Version'} : undef;
1225             $stdr     = (exists $out->{'Storport Driver Version'}
1226                          and $out->{'Storport Driver Version'} ne 'Not Applicable')
1227               ? $out->{'Storport Driver Version'} : undef;
1228             $nexus    = $id;
1229         }
1230
1231         $name =~ s{\s+\z}{}xms; # remove trailing whitespace
1232         push @controllers, $id;
1233
1234         # Collecting some storage info
1235         $sysinfo{'controller'}{$id}{'id'}       = $nexus;
1236         $sysinfo{'controller'}{$id}{'name'}     = $name;
1237         $sysinfo{'controller'}{$id}{'driver'}   = $driver;
1238         $sysinfo{'controller'}{$id}{'firmware'} = $firmware;
1239         $sysinfo{'controller'}{$id}{'storport'} = $stdr;
1240
1241         # Store controller info for future use (SNMP)
1242         if ($snmp) {
1243             $snmp_controller{$out->{controllerNumber}} = $nexus;
1244         }
1245
1246         next CTRL if blacklisted('ctrl', $nexus);
1247
1248         # Special case: old firmware
1249         if (!blacklisted('ctrl_fw', $id) && defined $minfw) {
1250             chomp $firmware;
1251             my $msg = sprintf q{Controller %d [%s]: Firmware '%s' is out of date},
1252               $id, $name, $firmware;
1253             report('storage', $msg, $E_WARNING, $nexus);
1254         }
1255         # Special case: old driver
1256         if (!blacklisted('ctrl_driver', $id) && defined $mindr) {
1257             chomp $driver;
1258             my $msg = sprintf q{Controller %d [%s]: Driver '%s' is out of date},
1259               $id, $name, $driver;
1260             report('storage', $msg, $E_WARNING, $nexus);
1261         }
1262         # Special case: old storport driver
1263         if (!blacklisted('ctrl_stdr', $id) && defined $minstdr) {
1264             chomp $stdr;
1265             my $msg = sprintf q{Controller %d [%s]: Storport driver '%s' is out of date},
1266               $id, $name, $stdr;
1267             report('storage', $msg, $E_WARNING, $nexus);
1268         }
1269         # Ok
1270         if ($status eq 'Ok' or ($status eq 'Non-Critical'
1271                                 and (defined $minfw or defined $mindr or defined $minstdr))) {
1272             my $msg = sprintf 'Controller %d [%s] is %s',
1273               $id, $name, $state;
1274             report('storage', $msg, $E_OK, $nexus);
1275         }
1276         # Default
1277         else {
1278             my $msg = sprintf 'Controller %d [%s] needs attention: %s',
1279               $id, $name, $state;
1280             report('storage', $msg, $status2nagios{$status}, $nexus);
1281         }
1282     }
1283     return;
1284 }
1285
1286
1287 #-----------------------------------------
1288 # STORAGE: Check physical drives
1289 #-----------------------------------------
1290 sub check_physical_disks {
1291     return if $#controllers == -1;
1292     return if blacklisted('pdisk', 'all');
1293
1294     my $id       = undef;
1295     my $nexus    = undef;
1296     my $name     = undef;
1297     my $state    = undef;
1298     my $status   = undef;
1299     my $fpred    = undef;
1300     my $progr    = undef;
1301     my $ctrl     = undef;
1302     my $vendor   = undef;  # disk vendor
1303     my $product  = undef;  # product ID
1304     my $capacity = undef;  # disk length (size) in bytes
1305     my $media    = undef;  # media type (e.g. HDD, SSD)
1306     my $bus      = undef;  # bus protocol (e.g. SAS, SATA)
1307     my $spare    = undef;  # spare state (e.g. global hotspare)
1308     my @output  = ();
1309
1310     if ($snmp) {
1311         my %pdisk_oid
1312           = (
1313              '1.3.6.1.4.1.674.10893.1.20.130.4.1.1'  => 'arrayDiskNumber',
1314              '1.3.6.1.4.1.674.10893.1.20.130.4.1.2'  => 'arrayDiskName',
1315              '1.3.6.1.4.1.674.10893.1.20.130.4.1.3'  => 'arrayDiskVendor',
1316              '1.3.6.1.4.1.674.10893.1.20.130.4.1.4'  => 'arrayDiskState',
1317              '1.3.6.1.4.1.674.10893.1.20.130.4.1.6'  => 'arrayDiskProductID',
1318              '1.3.6.1.4.1.674.10893.1.20.130.4.1.9'  => 'arrayDiskEnclosureID',
1319              '1.3.6.1.4.1.674.10893.1.20.130.4.1.10' => 'arrayDiskChannel',
1320              '1.3.6.1.4.1.674.10893.1.20.130.4.1.11' => 'arrayDiskLengthInMB',
1321              '1.3.6.1.4.1.674.10893.1.20.130.4.1.15' => 'arrayDiskTargetID',
1322              '1.3.6.1.4.1.674.10893.1.20.130.4.1.21' => 'arrayDiskBusType',
1323              '1.3.6.1.4.1.674.10893.1.20.130.4.1.22' => 'arrayDiskSpareState',
1324              '1.3.6.1.4.1.674.10893.1.20.130.4.1.24' => 'arrayDiskComponentStatus',
1325              '1.3.6.1.4.1.674.10893.1.20.130.4.1.26' => 'arrayDiskNexusID',
1326              '1.3.6.1.4.1.674.10893.1.20.130.4.1.31' => 'arrayDiskSmartAlertIndication',
1327              '1.3.6.1.4.1.674.10893.1.20.130.4.1.35' => 'arrayDiskMediaType',
1328              '1.3.6.1.4.1.674.10893.1.20.130.5.1.7'  => 'arrayDiskEnclosureConnectionControllerNumber',
1329              '1.3.6.1.4.1.674.10893.1.20.130.6.1.7'  => 'arrayDiskChannelConnectionControllerNumber',
1330             );
1331         my $result = undef;
1332         if ($opt{use_get_table}) {
1333             my $arrayDiskTable = '1.3.6.1.4.1.674.10893.1.20.130.4';
1334             my $arrayDiskEnclosureConnectionControllerNumber = '1.3.6.1.4.1.674.10893.1.20.130.5.1.7';
1335             my $arrayDiskChannelConnectionControllerNumber = '1.3.6.1.4.1.674.10893.1.20.130.6.1.7';
1336
1337             $result  = $snmp_session->get_table(-baseoid => $arrayDiskTable);
1338             my $ext1 = $snmp_session->get_table(-baseoid => $arrayDiskEnclosureConnectionControllerNumber);
1339             my $ext2 = $snmp_session->get_table(-baseoid => $arrayDiskChannelConnectionControllerNumber);
1340
1341             if (defined $result) {
1342                 defined $ext1 && map { $$result{$_} = $$ext1{$_} } keys %{ $ext1 };
1343                 defined $ext2 && map { $$result{$_} = $$ext2{$_} } keys %{ $ext2 };
1344             }
1345         }
1346         else {
1347             $result = $snmp_session->get_entries(-columns => [keys %pdisk_oid]);
1348         }
1349
1350         if (!defined $result) {
1351             printf "SNMP ERROR [storage / pdisk]: %s.\n", $snmp_session->error;
1352             $snmp_session->close;
1353             exit $E_UNKNOWN;
1354         }
1355
1356         @output = @{ get_snmp_output($result, \%pdisk_oid) };
1357     }
1358     else {
1359         foreach my $c (@controllers) {
1360             # This blacklists disks with broken firmware, which includes
1361             # illegal XML characters that makes openmanage choke on itself
1362             next if blacklisted('ctrl_pdisk', $c);
1363
1364             push @output, @{ run_omreport("storage pdisk controller=$c") };
1365             map_item('ctrl', $c, \@output);
1366         }
1367     }
1368
1369     my %spare_state
1370       = (
1371          1  => 'VD member',    # disk is a member of a virtual disk
1372          2  => 'DG member',    # disk is a member of a disk group
1373          3  => 'Global HS',    # disk is a global hot spare
1374          4  => 'Dedicated HS', # disk is a dedicated hot spare
1375          5  => 'no',           # not a spare
1376          99 => 'n/a',          # not applicable
1377         );
1378
1379     my %media_type
1380       = (
1381          1 => 'unknown',
1382          2 => 'HDD',
1383          3 => 'SSD',
1384         );
1385
1386     my %bus_type
1387       = (
1388          1 => 'SCSI',
1389          2 => 'IDE',
1390          3 => 'Fibre Channel',
1391          4 => 'SSA',
1392          6 => 'USB',
1393          7 => 'SATA',
1394          8 => 'SAS',
1395         );
1396
1397     my %pdisk_state
1398       = (
1399          0  => 'Unknown',
1400          1  => 'Ready',
1401          2  => 'Failed',
1402          3  => 'Online',
1403          4  => 'Offline',
1404          6  => 'Degraded',
1405          7  => 'Recovering',
1406          11 => 'Removed',
1407          15 => 'Resynching',
1408          22 => 'Replacing', # FIXME: this one is not defined in the OMSA MIBs
1409          24 => 'Rebuilding',
1410          25 => 'No Media',
1411          26 => 'Formatting',
1412          28 => 'Diagnostics',
1413          34 => 'Predictive failure',
1414          35 => 'Initializing',
1415          39 => 'Foreign',
1416          40 => 'Clear',
1417          41 => 'Unsupported',
1418          53 => 'Incompatible',
1419         );
1420
1421     # Check physical disks on each of the controllers
1422   PDISK:
1423     foreach my $out (@output) {
1424         if ($snmp) {
1425             $name   = $out->{arrayDiskName};
1426             if (exists $out->{arrayDiskEnclosureID}) {
1427                 $id = join q{:}, ($out->{arrayDiskChannel}, $out->{arrayDiskEnclosureID},
1428                                   $out->{arrayDiskTargetID});
1429             }
1430             else {
1431                 $id = join q{:}, ($out->{arrayDiskChannel}, $out->{arrayDiskTargetID});
1432             }
1433             $state    = get_hashval($out->{arrayDiskState}, \%pdisk_state);
1434             $status   = $snmp_status{$out->{arrayDiskComponentStatus}};
1435             $fpred    = exists $out->{arrayDiskSmartAlertIndication}
1436               && $out->{arrayDiskSmartAlertIndication} == 2 ? 1 : 0;
1437             $progr    = q{};
1438             $nexus    = convert_nexus($out->{arrayDiskNexusID});
1439             $vendor   = exists $out->{arrayDiskVendor}
1440               ? $out->{arrayDiskVendor} : 'Unknown Vendor';
1441             $product  = exists $out->{arrayDiskProductID}
1442               ? $out->{arrayDiskProductID} : 'Unknown Product ID';
1443             $spare    = get_hashval($out->{arrayDiskSpareState}, \%spare_state);
1444             $bus      = exists $out->{arrayDiskBusType}
1445               ? get_hashval($out->{arrayDiskBusType}, \%bus_type) : undef;
1446             $media    = exists $out->{arrayDiskMediaType}
1447               ? get_hashval($out->{arrayDiskMediaType}, \%media_type) : undef;
1448             $capacity = exists $out->{arrayDiskLengthInMB}
1449               ? $out->{arrayDiskLengthInMB} * 1024**2 : -1;
1450
1451             # try to find the controller where the disk belongs
1452             if (exists $out->{arrayDiskEnclosureConnectionControllerNumber}) {
1453                 # for disks that are attached to an enclosure
1454                 $ctrl = $snmp_controller{$out->{arrayDiskEnclosureConnectionControllerNumber}};
1455             }
1456             elsif (exists $out->{arrayDiskChannelConnectionControllerNumber}) {
1457                 # for disks that are not attached to an enclosure
1458                 $ctrl = $snmp_controller{$out->{arrayDiskChannelConnectionControllerNumber}};
1459             }
1460             else {
1461                 # last resort... use the nexus id (old/broken hardware)
1462                 $ctrl = $nexus;
1463                 $ctrl =~ s{\A (\d+) : .* \z}{$1}xms;
1464             }
1465         }
1466         else {
1467             $id       = $out->{'ID'};
1468             $name     = $out->{'Name'};
1469             $state    = $out->{'State'};
1470             $status   = $out->{'Status'};
1471             $fpred    = lc($out->{'Failure Predicted'}) eq 'yes' ? 1 : 0;
1472             $progr    = ' [' . $out->{'Progress'} . ']';
1473             $ctrl     = $out->{'ctrl'};
1474             $nexus    = join q{:}, $out->{ctrl}, $id;
1475             $vendor   = $out->{'Vendor ID'} ne q{} ? $out->{'Vendor ID'} : 'Unknown Vendor';
1476             $product  = $out->{'Product ID'} ne q{} ? $out->{'Product ID'} : 'Unknown Product ID';
1477             $media    = $out->{'Media'};
1478             $spare    = $out->{'Hot Spare'};
1479             $bus      = $out->{'Bus Protocol'};
1480             $capacity = $out->{'Capacity'};
1481             $capacity =~ s{\A .*? \((\d+) \s bytes\) \z}{$1}xms;
1482             if ($capacity eq 'Unavailable') {
1483                 $capacity = -1;
1484             }
1485         }
1486
1487         next PDISK if blacklisted('pdisk', $nexus);
1488         $count{pdisk}++;
1489
1490         $vendor  =~ s{\s+\z}{}xms; # remove trailing whitespace
1491         $product =~ s{\s+\z}{}xms; # remove trailing whitespace
1492
1493         # Hot spare stuff
1494         if ($spare eq 'Global') { $spare = 'Global HS'; }
1495         elsif ($spare eq 'Dedicated') { $spare = 'Dedicated HS'; }
1496         elsif ($spare !~ m{\A Global|Dedicated}xms) { $spare = undef; }
1497
1498         # Calculate human readable capacity
1499         if ($capacity == -1) {
1500             # capacity is unknown
1501             $capacity = 'Unknown Size';
1502         }
1503         else {
1504             $capacity = ceil($capacity / 1000**3) >= 1000
1505               ? sprintf '%.1fTB', ($capacity / 1000**4)
1506                 : sprintf '%.0fGB', ($capacity / 1000**3);
1507             $capacity = '450GB' if $capacity eq '449GB';  # quick fix for 450GB disks
1508             $capacity = '300GB' if $capacity eq '299GB';  # quick fix for 300GB disks
1509             $capacity = '146GB' if $capacity eq '147GB';  # quick fix for 146GB disks
1510             $capacity = '100GB' if $capacity eq '99GB';   # quick fix for 100GB disks
1511         }
1512
1513         # Capitalize only the first letter of the vendor name
1514         $vendor = (substr $vendor, 0, 1) . lc (substr $vendor, 1, length $vendor);
1515
1516         # Remove unnecessary trademark rubbish from vendor name
1517         $vendor =~ s{\(tm\)\z}{}xms;
1518
1519         # bus and media aren't always defined
1520         my $busmedia = q{};
1521         if    (defined $bus && defined $media)   { $busmedia = "$bus-$media "; }
1522         elsif (defined $bus && ! defined $media) { $busmedia = "$bus ";        }
1523         elsif (! defined $bus && defined $media) { $busmedia = "$media ";      }
1524
1525         # Special case: Failure predicted
1526         if ($fpred) {
1527             my $msg = sprintf '%s [%s %s, %s] on ctrl %d needs attention: Failure Predicted',
1528               $name, $vendor, $product, $capacity, $ctrl;
1529             $msg .= " ($state)" if $state ne 'Predictive failure';
1530             report('storage', $msg,
1531                    ($status2nagios{$status} == $E_CRITICAL ? $E_CRITICAL : $E_WARNING), $nexus);
1532         }
1533         # Special case: Rebuilding / Replacing
1534         elsif ($state =~ m{\A Rebuilding|Replacing \z}xms) {
1535             my $msg = sprintf '%s [%s %s, %s] on ctrl %d is %s%s',
1536               $name, $vendor, $product, $capacity, $ctrl, $state, $progr;
1537             report('storage', $msg, $E_WARNING, $nexus);
1538         }
1539         # Default
1540         elsif ($status ne 'Ok') {
1541             my $msg =  sprintf '%s [%s %s, %s] on ctrl %d needs attention: %s',
1542               $name, $vendor, $product, $capacity, $ctrl, $state;
1543             report('storage', $msg, $status2nagios{$status}, $nexus);
1544         }
1545         # Ok
1546         else {
1547             my $msg = sprintf '%s [%s%s] on ctrl %d is %s',
1548               $name, $busmedia, $capacity, $ctrl, $state;
1549             if (defined $spare) { $msg .= " ($spare)"; }
1550             report('storage', $msg, $E_OK, $nexus);
1551         }
1552     }
1553     return;
1554 }
1555
1556
1557 #-----------------------------------------
1558 # STORAGE: Check logical drives
1559 #-----------------------------------------
1560 sub check_virtual_disks {
1561     return if $#controllers == -1;
1562     return if blacklisted('vdisk', 'all');
1563
1564     my $id     = undef;
1565     my $name   = undef;
1566     my $nexus  = undef;
1567     my $dev    = undef;
1568     my $state  = undef;
1569     my $status = undef;
1570     my $layout = undef;
1571     my $size   = undef;
1572     my $progr  = undef;
1573     my $ctrl   = undef;
1574     my @output = ();
1575
1576     if ($snmp) {
1577         my %vdisk_oid
1578           = (
1579              '1.3.6.1.4.1.674.10893.1.20.140.1.1.3'  => 'virtualDiskDeviceName',
1580              '1.3.6.1.4.1.674.10893.1.20.140.1.1.4'  => 'virtualDiskState',
1581              '1.3.6.1.4.1.674.10893.1.20.140.1.1.6'  => 'virtualDiskLengthInMB',
1582              '1.3.6.1.4.1.674.10893.1.20.140.1.1.13' => 'virtualDiskLayout',
1583              '1.3.6.1.4.1.674.10893.1.20.140.1.1.17' => 'virtualDiskTargetID',
1584              '1.3.6.1.4.1.674.10893.1.20.140.1.1.20' => 'virtualDiskComponentStatus',
1585              '1.3.6.1.4.1.674.10893.1.20.140.1.1.21' => 'virtualDiskNexusID',
1586             );
1587         my $result = undef;
1588         if ($opt{use_get_table}) {
1589             my $virtualDiskTable = '1.3.6.1.4.1.674.10893.1.20.140.1';
1590             $result = $snmp_session->get_table(-baseoid => $virtualDiskTable);
1591         }
1592         else {
1593             $result = $snmp_session->get_entries(-columns => [keys %vdisk_oid]);
1594         }
1595
1596         # No logical drives is OK
1597         return if !defined $result;
1598
1599         @output = @{ get_snmp_output($result, \%vdisk_oid) };
1600     }
1601     else {
1602         foreach my $c (@controllers) {
1603             push @output, @{ run_omreport("storage vdisk controller=$c") };
1604             map_item('ctrl', $c, \@output);
1605         }
1606     }
1607
1608     my %vdisk_state
1609       = (
1610          0  => 'Unknown',
1611          1  => 'Ready',
1612          2  => 'Failed',
1613          3  => 'Online',
1614          4  => 'Offline',
1615          6  => 'Degraded',
1616          15 => 'Resynching',
1617          16 => 'Regenerating',
1618          24 => 'Rebuilding',
1619          26 => 'Formatting',
1620          32 => 'Reconstructing',
1621          35 => 'Initializing',
1622          36 => 'Background Initialization',
1623          38 => 'Resynching Paused',
1624          52 => 'Permanently Degraded',
1625          54 => 'Degraded Redundancy',
1626         );
1627
1628     my %vdisk_layout
1629       = (
1630          1  => 'Concatenated',
1631          2  => 'RAID-0',
1632          3  => 'RAID-1',
1633          7  => 'RAID-5',
1634          8  => 'RAID-6',
1635          10 => 'RAID-10',
1636          12 => 'RAID-50',
1637          19 => 'Concatenated RAID-1',
1638          24 => 'RAID-60',
1639         );
1640
1641     # Check virtual disks on each of the controllers
1642   VDISK:
1643     foreach my $out (@output) {
1644         if ($snmp) {
1645             $id     = $out->{virtualDiskTargetID};
1646             $dev    = $out->{virtualDiskDeviceName};
1647             $state  = get_hashval($out->{virtualDiskState}, \%vdisk_state);
1648             $layout = get_hashval($out->{virtualDiskLayout}, \%vdisk_layout);
1649             $status = $snmp_status{$out->{virtualDiskComponentStatus}};
1650             $size   = sprintf '%.2f GB', $out->{virtualDiskLengthInMB} / 1024;
1651             $progr  = q{};  # can't get this from SNMP(?)
1652             $nexus  = convert_nexus($out->{virtualDiskNexusID});
1653         }
1654         else {
1655             $id     = $out->{ID};
1656             $dev    = $out->{'Device Name'};
1657             $state  = $out->{State};
1658             $status = $out->{Status};
1659             $layout = $out->{Layout};
1660             $size   = $out->{Size};
1661             $progr  = ' [' . $out->{Progress} . ']';
1662             $size   =~ s{\A (.*GB).* \z}{$1}xms;
1663             $nexus  = join q{:}, $out->{ctrl}, $id;
1664             $ctrl   = $out->{ctrl};
1665         }
1666
1667         next VDISK if blacklisted('vdisk', $nexus);
1668         $count{vdisk}++;
1669
1670         # The device name is undefined sometimes
1671         $dev = q{} if !defined $dev;
1672
1673         # Special case: Regenerating
1674         if ($state eq 'Regenerating') {
1675             my $msg = sprintf q{Logical Drive '%s' [%s, %s] is %s%s},
1676               $dev, $layout, $size, $state, $progr;
1677             report('storage', $msg, $E_WARNING, $nexus);
1678         }
1679         # Default
1680         elsif ($status ne 'Ok') {
1681             my $msg = sprintf q{Logical Drive '%s' [%s, %s] needs attention: %s},
1682               $dev, $layout, $size, $state;
1683             report('storage', $msg, $status2nagios{$status}, $nexus);
1684         }
1685         # Ok
1686         else {
1687             my $msg = sprintf q{Logical Drive '%s' [%s, %s] is %s},
1688               $dev, $layout, $size, $state;
1689             report('storage', $msg, $E_OK, $nexus);
1690         }
1691     }
1692     return;
1693 }
1694
1695
1696 #-----------------------------------------
1697 # STORAGE: Check cache batteries
1698 #-----------------------------------------
1699 sub check_cache_battery {
1700     return if $#controllers == -1;
1701     return if blacklisted('bat', 'all');
1702
1703     my $id     = undef;
1704     my $nexus  = undef;
1705     my $state  = undef;
1706     my $status = undef;
1707     my $ctrl   = undef;
1708     my $learn  = undef; # learn state
1709     my $pred   = undef; # battery's ability to be charged
1710     my @output = ();
1711
1712     if ($snmp) {
1713         my %bat_oid
1714           = (
1715              '1.3.6.1.4.1.674.10893.1.20.130.15.1.4'  => 'batteryState',
1716              '1.3.6.1.4.1.674.10893.1.20.130.15.1.6'  => 'batteryComponentStatus',
1717              '1.3.6.1.4.1.674.10893.1.20.130.15.1.9'  => 'batteryNexusID',
1718              '1.3.6.1.4.1.674.10893.1.20.130.15.1.10' => 'batteryPredictedCapacity',
1719              '1.3.6.1.4.1.674.10893.1.20.130.15.1.12' => 'batteryLearnState',
1720              '1.3.6.1.4.1.674.10893.1.20.130.16.1.5'  => 'batteryConnectionControllerNumber',
1721             );
1722         my $result = undef;
1723         if ($opt{use_get_table}) {
1724             my $batteryTable = '1.3.6.1.4.1.674.10893.1.20.130.15';
1725             my $batteryConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.16';
1726
1727             $result = $snmp_session->get_table(-baseoid => $batteryTable);
1728             my $ext = $snmp_session->get_table(-baseoid => $batteryConnectionTable);
1729
1730             if (defined $result) {
1731                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
1732             }
1733         }
1734         else {
1735             $result = $snmp_session->get_entries(-columns => [keys %bat_oid]);
1736         }
1737
1738         # No cache battery is OK
1739         return if !defined $result;
1740
1741         @output = @{ get_snmp_output($result, \%bat_oid) };
1742     }
1743     else {
1744         foreach my $c (@controllers) {
1745             push @output, @{ run_omreport("storage battery controller=$c") };
1746             map_item('ctrl', $c, \@output);
1747         }
1748     }
1749
1750     my %bat_state
1751       = (
1752          0  => 'Unknown',
1753          1  => 'Ready',
1754          2  => 'Failed',
1755          6  => 'Degraded',
1756          7  => 'Reconditioning',
1757          9  => 'High',
1758          10 => 'Power Low',
1759          12 => 'Charging',
1760          21 => 'Missing',
1761          36 => 'Learning',
1762         );
1763
1764     # Specifies the learn state activity of the battery
1765     my %bat_learn_state
1766       = (
1767          1  => 'Failed',
1768          2  => 'Active',
1769          4  => 'Timed out',
1770          8  => 'Requested',
1771          16 => 'Idle',
1772         );
1773
1774     # This property displays the battery's ability to be charged
1775     my %bat_pred_cap
1776       = (
1777          1 => 'Failed',  # The battery cannot be charged and needs to be replaced
1778          2 => 'Ready',   # The battery can be charged to full capacity
1779          4 => 'Unknown', # The battery is completing a Learn cycle. The charge capacity of the
1780                          # battery cannot be determined until the Learn cycle is complete
1781         );
1782
1783     # Check battery on each of the controllers
1784   BATTERY:
1785     foreach my $out (@output) {
1786         if ($snmp) {
1787             $status = $snmp_status{$out->{batteryComponentStatus}};
1788             $state  = get_hashval($out->{batteryState}, \%bat_state);
1789             $learn  = get_hashval($out->{batteryLearnState}, \%bat_learn_state);
1790             $pred   = get_hashval($out->{batteryPredictedCapacity}, \%bat_pred_cap);
1791             $ctrl   = $out->{batteryConnectionControllerNumber} - 1;
1792             $nexus  = convert_nexus($out->{batteryNexusID});
1793             $id     = $nexus;
1794             $id     =~ s{\A \d+:(\d+) \z}{$1}xms;
1795         }
1796         else {
1797             $id     = $out->{'ID'};
1798             $state  = $out->{'State'};
1799             $status = $out->{'Status'};
1800             $learn  = $out->{'Learn State'};
1801             $pred   = $out->{'Predicted Capacity Status'};
1802             $ctrl   = $out->{'ctrl'};
1803             $nexus  = join q{:}, $out->{ctrl}, $id;
1804         }
1805
1806         next BATTERY if blacklisted('bat', $nexus);
1807
1808         # Special case: Charging
1809         if ($state eq 'Charging') {
1810             if ($pred eq 'Failed') {
1811                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [replace battery]',
1812                   $id, $ctrl, $state, $pred;
1813                 report('storage', $msg, $E_CRITICAL, $nexus);
1814             }
1815             else {
1816                 next BATTERY if blacklisted('bat_charge', $nexus);
1817                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [probably harmless]',
1818                   $id, $ctrl, $state, $pred;
1819                 report('storage', $msg, $E_WARNING, $nexus);
1820             }
1821         }
1822         # Special case: Learning (battery learns its capacity)
1823         elsif ($state eq 'Learning') {
1824             if ($learn eq 'Failed') {
1825                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s)',
1826                   $id, $ctrl, $state, $learn;
1827                 report('storage', $msg, $E_CRITICAL, $nexus);
1828             }
1829             else {
1830                 next BATTERY if blacklisted('bat_charge', $nexus);
1831                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [probably harmless]',
1832                   $id, $ctrl, $state, $learn;
1833                 report('storage', $msg, $E_WARNING, $nexus);
1834             }
1835         }
1836         # Special case: Power Low (first part of recharge cycle)
1837         elsif ($state eq 'Power Low') {
1838             next BATTERY if blacklisted('bat_charge', $nexus);
1839             my $msg = sprintf 'Cache Battery %d in controller %d is %s [probably harmless]',
1840               $id, $ctrl, $state;
1841             report('storage', $msg, $E_WARNING, $nexus);
1842         }
1843         # Special case: Degraded and Non-Critical (usually part of recharge cycle)
1844         elsif ($state eq 'Degraded' && $status eq 'Non-Critical') {
1845             next BATTERY if blacklisted('bat_charge', $nexus);
1846             my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [probably harmless]',
1847               $id, $ctrl, $state, $status;
1848             report('storage', $msg, $E_WARNING, $nexus);
1849         }
1850         # Default
1851         elsif ($status ne 'Ok') {
1852             my $msg = sprintf 'Cache Battery %d in controller %d needs attention: %s (%s)',
1853               $id, $ctrl, $state, $status;
1854             report('storage', $msg, $status2nagios{$status}, $nexus);
1855         }
1856         # Ok
1857         else {
1858             my $msg = sprintf 'Cache Battery %d in controller %d is %s',
1859               $id, $ctrl, $state;
1860             report('storage', $msg, $E_OK, $nexus);
1861         }
1862     }
1863     return;
1864 }
1865
1866
1867 #-----------------------------------------
1868 # STORAGE: Check connectors (channels)
1869 #-----------------------------------------
1870 sub check_connectors {
1871     return if $#controllers == -1;
1872     return if blacklisted('conn', 'all');
1873
1874     my $id     = undef;
1875     my $nexus  = undef;
1876     my $name   = undef;
1877     my $state  = undef;
1878     my $status = undef;
1879     my $type   = undef;
1880     my $ctrl   = undef;
1881     my @output = ();
1882
1883     if ($snmp) {
1884         my %conn_oid
1885           = (
1886              '1.3.6.1.4.1.674.10893.1.20.130.2.1.1'  => 'channelNumber',
1887              '1.3.6.1.4.1.674.10893.1.20.130.2.1.2'  => 'channelName',
1888              '1.3.6.1.4.1.674.10893.1.20.130.2.1.3'  => 'channelState',
1889              '1.3.6.1.4.1.674.10893.1.20.130.2.1.8'  => 'channelComponentStatus',
1890              '1.3.6.1.4.1.674.10893.1.20.130.2.1.9'  => 'channelNexusID',
1891              '1.3.6.1.4.1.674.10893.1.20.130.2.1.11' => 'channelBusType',
1892             );
1893         my $result = undef;
1894         if ($opt{use_get_table}) {
1895             my $channelTable = '1.3.6.1.4.1.674.10893.1.20.130.2';
1896             $result = $snmp_session->get_table(-baseoid => $channelTable);
1897         }
1898         else {
1899             $result = $snmp_session->get_entries(-columns => [keys %conn_oid]);
1900         }
1901
1902         if (!defined $result) {
1903             printf "SNMP ERROR [storage / channel]: %s.\n", $snmp_session->error;
1904             $snmp_session->close;
1905             exit $E_UNKNOWN;
1906         }
1907
1908         @output = @{ get_snmp_output($result, \%conn_oid) };
1909     }
1910     else {
1911         foreach my $c (@controllers) {
1912             push @output, @{ run_omreport("storage connector controller=$c") };
1913             map_item('ctrl', $c, \@output);
1914         }
1915     }
1916
1917     my %conn_state
1918       = (
1919          0 => 'Unknown',
1920          1 => 'Ready',
1921          2 => 'Failed',
1922          3 => 'Online',
1923          4 => 'Offline',
1924          6 => 'Degraded',
1925         );
1926
1927     my %conn_bustype
1928       = (
1929          1 => 'SCSI',
1930          2 => 'IDE',
1931          3 => 'Fibre Channel',
1932          4 => 'SSA',
1933          6 => 'USB',
1934          7 => 'SATA',
1935          8 => 'SAS',
1936         );
1937
1938     # Check connectors on each of the controllers
1939   CHANNEL:
1940     foreach my $out (@output) {
1941         if ($snmp) {
1942             $id     = $out->{channelNumber} - 1;
1943             $name   = $out->{channelName};
1944             $status = $snmp_status{$out->{channelComponentStatus}};
1945             $state  = get_hashval($out->{channelState}, \%conn_state);
1946             $type   = get_hashval($out->{channelBusType}, \%conn_bustype);
1947             $nexus  = convert_nexus($out->{channelNexusID});
1948             $ctrl   = $nexus;
1949             $ctrl   =~ s{(\d+):\d+}{$1}xms;
1950             # workaround for ancient OMSA versions
1951             if (! defined $type) { $type = 'n/a'; }
1952         }
1953         else {
1954             $id     = $out->{'ID'};
1955             $name   = $out->{'Name'};
1956             $state  = $out->{'State'};
1957             $status = $out->{'Status'};
1958             $type   = $out->{'Connector Type'};
1959             $ctrl   = $out->{ctrl};
1960             $nexus  = join q{:}, $out->{ctrl}, $id;
1961         }
1962
1963         next CHANNEL if blacklisted('conn', $nexus);
1964
1965         my $msg = sprintf '%s [%s] on controller %d is %s',
1966           $name, $type, $ctrl, $state;
1967         report('storage', $msg, $status2nagios{$status}, $nexus);
1968     }
1969     return;
1970 }
1971
1972
1973 #-----------------------------------------
1974 # STORAGE: Check enclosures
1975 #-----------------------------------------
1976 sub check_enclosures {
1977     return if blacklisted('encl', 'all');
1978
1979     my $id       = undef;
1980     my $nexus    = undef;
1981     my $name     = undef;
1982     my $state    = undef;
1983     my $status   = undef;
1984     my $firmware = undef;
1985     my $ctrl     = undef;
1986     my $occupied_slots = undef; # number of occupied slots
1987     my $total_slots    = undef; # number of total slots
1988     my @output   = ();
1989
1990     if ($snmp) {
1991         my %encl_oid
1992           = (
1993              '1.3.6.1.4.1.674.10893.1.20.130.3.1.1'  => 'enclosureNumber',
1994              '1.3.6.1.4.1.674.10893.1.20.130.3.1.2'  => 'enclosureName',
1995              '1.3.6.1.4.1.674.10893.1.20.130.3.1.4'  => 'enclosureState',
1996              '1.3.6.1.4.1.674.10893.1.20.130.3.1.19' => 'enclosureChannelNumber',
1997              '1.3.6.1.4.1.674.10893.1.20.130.3.1.24' => 'enclosureComponentStatus',
1998              '1.3.6.1.4.1.674.10893.1.20.130.3.1.25' => 'enclosureNexusID',
1999              '1.3.6.1.4.1.674.10893.1.20.130.3.1.26' => 'enclosureFirmwareVersion',
2000              '1.3.6.1.4.1.674.10893.1.20.130.3.1.31' => 'enclosureOccupiedSlotCount', # new in OMSA 6.3.0
2001              '1.3.6.1.4.1.674.10893.1.20.130.3.1.32' => 'enclosureTotalSlots', # new in OMSA 6.3.0
2002             );
2003         my $result = undef;
2004         if ($opt{use_get_table}) {
2005             my $enclosureTable = '1.3.6.1.4.1.674.10893.1.20.130.3';
2006             $result = $snmp_session->get_table(-baseoid => $enclosureTable);
2007         }
2008         else {
2009             $result = $snmp_session->get_entries(-columns => [keys %encl_oid]);
2010         }
2011
2012         # No enclosures is OK
2013         return if !defined $result;
2014
2015         @output = @{ get_snmp_output($result, \%encl_oid) };
2016     }
2017     else {
2018         foreach my $c (@controllers) {
2019             push @output, @{ run_omreport("storage enclosure controller=$c") };
2020             map_item('ctrl', $c, \@output);
2021         }
2022     }
2023
2024     my %encl_state
2025       = (
2026          0 => 'Unknown',
2027          1 => 'Ready',
2028          2 => 'Failed',
2029          3 => 'Online',
2030          4 => 'Offline',
2031          6 => 'Degraded',
2032         );
2033
2034   ENCLOSURE:
2035     foreach my $out (@output) {
2036         if ($snmp) {
2037             $id       = $out->{enclosureNumber} - 1;
2038             $name     = $out->{enclosureName};
2039             $state    = get_hashval($out->{enclosureState}, \%encl_state);
2040             $status   = $snmp_status{$out->{enclosureComponentStatus}};
2041             $firmware = exists $out->{enclosureFirmwareVersion}
2042               ? $out->{enclosureFirmwareVersion} : 'N/A';
2043             $nexus    = convert_nexus($out->{enclosureNexusID});
2044             $ctrl     = $nexus;
2045             $ctrl     =~ s{\A (\d+):.* \z}{$1}xms;
2046             # for the next two, a value of 9999 means feature not available
2047             $occupied_slots = exists $out->{enclosureOccupiedSlotCount}
2048               && $out->{enclosureOccupiedSlotCount} != 9999
2049                 ? $out->{enclosureOccupiedSlotCount} : undef;
2050             $total_slots    = exists $out->{enclosureTotalSlots}
2051               && $out->{enclosureTotalSlots} != 9999
2052                 ? $out->{enclosureTotalSlots} : undef;
2053         }
2054         else {
2055             $id       = $out->{ID};
2056             $name     = $out->{Name};
2057             $state    = $out->{State};
2058             $status   = $out->{Status};
2059             $firmware = $out->{'Firmware Version'} ne 'Not Applicable'
2060               ? $out->{'Firmware Version'} : 'N/A';
2061             $nexus    = join q{:}, $out->{ctrl}, $id;
2062             $ctrl     = $out->{ctrl};
2063         }
2064
2065         $name     =~ s{\s+\z}{}xms; # remove trailing whitespace
2066         $firmware =~ s{\s+\z}{}xms; # remove trailing whitespace
2067
2068         # store enclosure data for future use
2069         if ($snmp) {
2070             $snmp_enclosure{$out->{enclosureNumber}}{id}    = $id;
2071             $snmp_enclosure{$out->{enclosureNumber}}{name}  = $name;
2072             $snmp_enclosure{$out->{enclosureNumber}}{nexus} = $nexus;
2073         }
2074         else {
2075             push @enclosures, { 'id'    => $id,
2076                                 'ctrl'  => $out->{ctrl},
2077                                 'name'  => $name };
2078         }
2079
2080         # Collecting some storage info
2081         $sysinfo{'enclosure'}{$nexus}{'id'}       = $nexus;
2082         $sysinfo{'enclosure'}{$nexus}{'name'}     = $name;
2083         $sysinfo{'enclosure'}{$nexus}{'firmware'} = $firmware;
2084
2085         next ENCLOSURE if blacklisted('encl', $nexus);
2086
2087         my $msg = q{};
2088         if (defined $occupied_slots && defined $total_slots) {
2089             $msg = sprintf 'Enclosure %s [%s, %d/%d slots occupied] on ctrl %d is %s',
2090               $nexus, $name, $occupied_slots, $total_slots, $ctrl, $state;
2091         }
2092         else {
2093             $msg = sprintf 'Enclosure %s [%s] on controller %d is %s',
2094               $nexus, $name, $ctrl, $state;
2095         }
2096         report('storage', $msg, $status2nagios{$status}, $nexus);
2097     }
2098     return;
2099 }
2100
2101
2102 #-----------------------------------------
2103 # STORAGE: Check enclosure fans
2104 #-----------------------------------------
2105 sub check_enclosure_fans {
2106     return if $#controllers == -1;
2107     return if blacklisted('encl_fan', 'all');
2108
2109     my $id        = undef;
2110     my $nexus     = undef;
2111     my $name      = undef;
2112     my $state     = undef;
2113     my $status    = undef;
2114     my $speed     = undef;
2115     my $encl_id   = undef;
2116     my $encl_name = undef;
2117     my @output    = ();
2118
2119     if ($snmp) {
2120         my %fan_oid
2121           = (
2122              '1.3.6.1.4.1.674.10893.1.20.130.7.1.1'  => 'fanNumber',
2123              '1.3.6.1.4.1.674.10893.1.20.130.7.1.2'  => 'fanName',
2124              '1.3.6.1.4.1.674.10893.1.20.130.7.1.4'  => 'fanState',
2125              '1.3.6.1.4.1.674.10893.1.20.130.7.1.11' => 'fanProbeCurrValue',
2126              '1.3.6.1.4.1.674.10893.1.20.130.7.1.15' => 'fanComponentStatus',
2127              '1.3.6.1.4.1.674.10893.1.20.130.7.1.16' => 'fanNexusID',
2128              '1.3.6.1.4.1.674.10893.1.20.130.8.1.4'  => 'fanConnectionEnclosureName',
2129              '1.3.6.1.4.1.674.10893.1.20.130.8.1.5'  => 'fanConnectionEnclosureNumber',
2130             );
2131         my $result = undef;
2132         if ($opt{use_get_table}) {
2133             my $fanTable = '1.3.6.1.4.1.674.10893.1.20.130.7';
2134             my $fanConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.8';
2135
2136             $result = $snmp_session->get_table(-baseoid => $fanTable);
2137             my $ext = $snmp_session->get_table(-baseoid => $fanConnectionTable);
2138
2139             if (defined $result) {
2140                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2141             }
2142         }
2143         else {
2144             $result = $snmp_session->get_entries(-columns => [keys %fan_oid]);
2145         }
2146
2147         # No enclosure fans is OK
2148         return if !defined $result;
2149
2150         @output = @{ get_snmp_output($result, \%fan_oid) };
2151     }
2152     else {
2153         foreach my $enc (@enclosures) {
2154             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=fans") };
2155             map_item('ctrl', $enc->{ctrl}, \@output);
2156             map_item('encl_id', $enc->{id}, \@output);
2157             map_item('encl_name', $enc->{name}, \@output);
2158         }
2159     }
2160
2161     my %fan_state
2162       = (
2163          0  => 'Unknown',
2164          1  => 'Ready',
2165          2  => 'Failed',
2166          3  => 'Online',
2167          4  => 'Offline',
2168          6  => 'Degraded',
2169          21 => 'Missing',
2170         );
2171
2172     # Check fans on each of the enclosures
2173   FAN:
2174     foreach my $out (@output) {
2175         if ($snmp) {
2176             $id        = $out->{fanNumber} - 1;
2177             $name      = $out->{fanName};
2178             $state     = get_hashval($out->{fanState}, \%fan_state);
2179             $status    = $snmp_status{$out->{fanComponentStatus}};
2180             $speed     = $out->{fanProbeCurrValue};
2181             $encl_name = $out->{fanConnectionEnclosureName};
2182             $encl_id   = $snmp_enclosure{$out->{fanConnectionEnclosureNumber}}{nexus};
2183             $nexus     = convert_nexus($out->{fanNexusID});
2184         }
2185         else {
2186             $id        = $out->{'ID'};
2187             $name      = $out->{'Name'};
2188             $state     = $out->{'State'};
2189             $status    = $out->{'Status'};
2190             $speed     = $out->{'Speed'};
2191             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2192             $encl_name = $out->{encl_name};
2193             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, $id;
2194         }
2195
2196         next FAN if blacklisted('encl_fan', $nexus);
2197
2198         # Default
2199         if ($status ne 'Ok') {
2200             my $msg = sprintf '%s in enclosure %s [%s] needs attention: %s',
2201               $name, $encl_id, $encl_name, $state;
2202             report('storage', $msg, $status2nagios{$status}, $nexus);
2203         }
2204         # Ok
2205         else {
2206             my $msg = sprintf '%s in enclosure %s [%s] is %s (speed=%s)',
2207               $name, $encl_id, $encl_name, $state, $speed;
2208             report('storage', $msg, $E_OK, $nexus);
2209         }
2210     }
2211     return;
2212 }
2213
2214
2215 #-----------------------------------------
2216 # STORAGE: Check enclosure power supplies
2217 #-----------------------------------------
2218 sub check_enclosure_pwr {
2219     return if $#controllers == -1;
2220     return if blacklisted('encl_ps', 'all');
2221
2222     my $id        = undef;
2223     my $nexus     = undef;
2224     my $name      = undef;
2225     my $state     = undef;
2226     my $status    = undef;
2227     my $encl_id   = undef;
2228     my $encl_name = undef;
2229     my @output    = ();
2230
2231     if ($snmp) {
2232         my %ps_oid
2233           = (
2234              '1.3.6.1.4.1.674.10893.1.20.130.9.1.1'  => 'powerSupplyNumber',
2235              '1.3.6.1.4.1.674.10893.1.20.130.9.1.2'  => 'powerSupplyName',
2236              '1.3.6.1.4.1.674.10893.1.20.130.9.1.4'  => 'powerSupplyState',
2237              '1.3.6.1.4.1.674.10893.1.20.130.9.1.9'  => 'powerSupplyComponentStatus',
2238              '1.3.6.1.4.1.674.10893.1.20.130.9.1.10' => 'powerSupplyNexusID',
2239              '1.3.6.1.4.1.674.10893.1.20.130.10.1.4' => 'powerSupplyConnectionEnclosureName',
2240              '1.3.6.1.4.1.674.10893.1.20.130.10.1.5' => 'powerSupplyConnectionEnclosureNumber',
2241             );
2242         my $result = undef;
2243         if ($opt{use_get_table}) {
2244             my $powerSupplyTable = '1.3.6.1.4.1.674.10893.1.20.130.9';
2245             my $powerSupplyConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.10';
2246
2247             $result = $snmp_session->get_table(-baseoid => $powerSupplyTable);
2248             my $ext = $snmp_session->get_table(-baseoid => $powerSupplyConnectionTable);
2249
2250             if (defined $result) {
2251                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2252             }
2253         }
2254         else {
2255             $result = $snmp_session->get_entries(-columns => [keys %ps_oid]);
2256         }
2257
2258         # No enclosure power supplies is OK
2259         return if !defined $result;
2260
2261         @output = @{ get_snmp_output($result, \%ps_oid) };
2262     }
2263     else {
2264         foreach my $enc (@enclosures) {
2265             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=pwrsupplies") };
2266             map_item('ctrl', $enc->{ctrl}, \@output);
2267             map_item('encl_id', $enc->{id}, \@output);
2268             map_item('encl_name', $enc->{name}, \@output);
2269         }
2270     }
2271
2272     my %ps_state
2273       = (
2274          0  => 'Unknown',
2275          1  => 'Ready',
2276          2  => 'Failed',
2277          5  => 'Not Installed',
2278          6  => 'Degraded',
2279          11 => 'Removed',
2280          21 => 'Missing',
2281         );
2282
2283     # Check power supplies on each of the enclosures
2284   PS:
2285     foreach my $out (@output) {
2286         if ($snmp) {
2287             $id        = $out->{powerSupplyNumber};
2288             $name      = $out->{powerSupplyName};
2289             $state     = get_hashval($out->{powerSupplyState}, \%ps_state);
2290             $status    = $snmp_status{$out->{powerSupplyComponentStatus}};
2291             $encl_id   = $snmp_enclosure{$out->{powerSupplyConnectionEnclosureNumber}}{nexus};
2292             $encl_name = $out->{powerSupplyConnectionEnclosureName};
2293             $nexus     = convert_nexus($out->{powerSupplyNexusID});
2294         }
2295         else {
2296             $id        = $out->{'ID'};
2297             $name      = $out->{'Name'};
2298             $state     = $out->{'State'};
2299             $status    = $out->{'Status'};
2300             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2301             $encl_name = $out->{encl_name};
2302             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, $id;
2303         }
2304
2305         next PS if blacklisted('encl_ps', $nexus);
2306
2307         # Default
2308         if ($status ne 'Ok') {
2309             my $msg = sprintf '%s in enclosure %s [%s] needs attention: %s',
2310               $name, $encl_id, $encl_name, $state;
2311             report('storage', $msg, $status2nagios{$status}, $nexus);
2312         }
2313         # Ok
2314         else {
2315             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2316               $name, $encl_id, $encl_name, $state;
2317             report('storage', $msg, $E_OK, $nexus);
2318         }
2319     }
2320     return;
2321 }
2322
2323
2324 #-----------------------------------------
2325 # STORAGE: Check enclosure temperatures
2326 #-----------------------------------------
2327 sub check_enclosure_temp {
2328     return if $#controllers == -1;
2329     return if blacklisted('encl_temp', 'all');
2330
2331     my $id        = undef;
2332     my $nexus     = undef;
2333     my $name      = undef;
2334     my $state     = undef;
2335     my $status    = undef;
2336     my $reading   = undef;
2337     my $unit      = undef;
2338     my $max_warn  = undef;
2339     my $max_crit  = undef;
2340     my $min_warn  = undef;
2341     my $min_crit  = undef;
2342     my $encl_id   = undef;
2343     my $encl_name = undef;
2344     my @output    = ();
2345
2346     if ($snmp) {
2347         my %temp_oid
2348           = (
2349              '1.3.6.1.4.1.674.10893.1.20.130.11.1.1'  => 'temperatureProbeNumber',
2350              '1.3.6.1.4.1.674.10893.1.20.130.11.1.2'  => 'temperatureProbeName',
2351              '1.3.6.1.4.1.674.10893.1.20.130.11.1.4'  => 'temperatureProbeState',
2352              '1.3.6.1.4.1.674.10893.1.20.130.11.1.6'  => 'temperatureProbeUnit',
2353              '1.3.6.1.4.1.674.10893.1.20.130.11.1.7'  => 'temperatureProbeMinWarning',
2354              '1.3.6.1.4.1.674.10893.1.20.130.11.1.8'  => 'temperatureProbeMinCritical',
2355              '1.3.6.1.4.1.674.10893.1.20.130.11.1.9'  => 'temperatureProbeMaxWarning',
2356              '1.3.6.1.4.1.674.10893.1.20.130.11.1.10' => 'temperatureProbeMaxCritical',
2357              '1.3.6.1.4.1.674.10893.1.20.130.11.1.11' => 'temperatureProbeCurValue',
2358              '1.3.6.1.4.1.674.10893.1.20.130.11.1.13' => 'temperatureProbeComponentStatus',
2359              '1.3.6.1.4.1.674.10893.1.20.130.11.1.14' => 'temperatureProbeNexusID',
2360              '1.3.6.1.4.1.674.10893.1.20.130.12.1.4'  => 'temperatureConnectionEnclosureName',
2361              '1.3.6.1.4.1.674.10893.1.20.130.12.1.5'  => 'temperatureConnectionEnclosureNumber',
2362             );
2363         my $result = undef;
2364         if ($opt{use_get_table}) {
2365             my $temperatureProbeTable = '1.3.6.1.4.1.674.10893.1.20.130.11';
2366             my $temperatureConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.12';
2367
2368             $result = $snmp_session->get_table(-baseoid => $temperatureProbeTable);
2369             my $ext = $snmp_session->get_table(-baseoid => $temperatureConnectionTable);
2370
2371             if (defined $result) {
2372                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2373             }
2374         }
2375         else {
2376             $result = $snmp_session->get_entries(-columns => [keys %temp_oid]);
2377         }
2378
2379         # No enclosure temperature probes is OK
2380         return if !defined $result;
2381
2382         @output = @{ get_snmp_output($result, \%temp_oid) };
2383     }
2384     else {
2385         foreach my $enc (@enclosures) {
2386             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=temps") };
2387             map_item('ctrl', $enc->{ctrl}, \@output);
2388             map_item('encl_id', $enc->{id}, \@output);
2389             map_item('encl_name', $enc->{name}, \@output);
2390         }
2391     }
2392
2393     my %temp_state
2394       = (
2395          0  => 'Unknown',
2396          1  => 'Ready',
2397          2  => 'Failed',
2398          4  => 'Offline',
2399          6  => 'Degraded',
2400          9  => 'Inactive',
2401          21 => 'Missing',
2402         );
2403
2404     # Check temperature probes on each of the enclosures
2405   TEMP:
2406     foreach my $out (@output) {
2407         if ($snmp) {
2408             $id        = $out->{temperatureProbeNumber} - 1;
2409             $name      = $out->{temperatureProbeName};
2410             $state     = get_hashval($out->{temperatureProbeState}, \%temp_state);
2411             $status    = $snmp_status{$out->{temperatureProbeComponentStatus}};
2412             $unit      = $out->{temperatureProbeUnit};
2413             $reading   = exists $out->{temperatureProbeCurValue}
2414               ? $out->{temperatureProbeCurValue} : '[N/A]';
2415             $max_warn  = exists $out->{temperatureProbeMaxWarning}
2416               ? $out->{temperatureProbeMaxWarning} : '[N/A]';
2417             $max_crit  = exists $out->{temperatureProbeMaxCritical}
2418               ? $out->{temperatureProbeMaxCritical} : '[N/A]';
2419             $min_warn  = exists $out->{temperatureProbeMinWarning}
2420               ? $out->{temperatureProbeMinWarning} : '[N/A]';
2421             $min_crit  = exists $out->{temperatureProbeMinCritical}
2422               ? $out->{temperatureProbeMinCritical} : '[N/A]';
2423             $encl_id   = $snmp_enclosure{$out->{temperatureConnectionEnclosureNumber}}{nexus};
2424             $encl_name = $out->{temperatureConnectionEnclosureName};
2425             $nexus     = convert_nexus($out->{temperatureProbeNexusID});
2426         }
2427         else {
2428             $id        = $out->{'ID'};
2429             $name      = $out->{'Name'};
2430             $state     = $out->{'State'};
2431             $status    = $out->{'Status'};
2432             $unit      = 'FIXME';
2433             $reading   = $out->{'Reading'};
2434             $max_warn  = $out->{'Maximum Warning Threshold'};
2435             $max_crit  = $out->{'Maximum Failure Threshold'};
2436             $min_warn  = $out->{'Minimum Warning Threshold'};
2437             $min_crit  = $out->{'Minimum Failure Threshold'};
2438             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2439             $encl_name = $out->{encl_name};
2440             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, $id;
2441         }
2442
2443         next TEMP if blacklisted('encl_temp', $nexus);
2444
2445         # Make sure these values are integers
2446         $reading  =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $reading  = '[N/A]';
2447         $max_warn =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $max_warn = '[N/A]';
2448         $max_crit =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $max_crit = '[N/A]';
2449         $min_warn =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $min_warn = '[N/A]';
2450         $min_crit =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $min_crit = '[N/A]';
2451
2452         # Inactive temp probes
2453         if ($status eq 'Unknown' and $state eq 'Inactive') {
2454             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2455               $name, $encl_id, $encl_name, $state;
2456             report('storage', $msg, $E_OK, $nexus);
2457         }
2458         elsif ($status ne 'Ok' and $max_crit ne '[N/A]' and $reading > $max_crit) {
2459             my $msg = sprintf '%s in enclosure %s [%s] is critically high at %d C',
2460               $name, $encl_id, $encl_name, $reading;
2461             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2462             report('chassis', $msg, $err, $nexus);
2463         }
2464         elsif ($status ne 'Ok' and $max_warn ne '[N/A]' and $reading > $max_warn) {
2465             my $msg = sprintf '%s in enclosure %s [%s] is too high at %d C',
2466               $name, $encl_id, $encl_name, $reading;
2467             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2468             report('chassis', $msg, $err, $nexus);
2469         }
2470         elsif ($status ne 'Ok' and $min_crit ne '[N/A]' and $reading < $min_crit) {
2471             my $msg = sprintf '%s in enclosure %s [%s] is critically low at %d C',
2472               $name, $encl_id, $encl_name, $reading;
2473             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2474             report('chassis', $msg, $err, $nexus);
2475         }
2476         elsif ($status ne 'Ok' and $min_warn ne '[N/A]' and $reading < $min_warn) {
2477             my $msg = sprintf '%s in enclosure %s [%s] is too low at %d C',
2478               $name, $encl_id, $encl_name, $reading;
2479             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2480             report('chassis', $msg, $err, $nexus);
2481         }
2482         # Default
2483         elsif ($status ne 'Ok') {
2484             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2485               $name, $encl_id, $encl_name, $state;
2486             if (defined $reading && $reading =~ m{\A -?\d+ \z}xms) {
2487                 # take into account that with certain states the
2488                 # reading doesn't exist or is not an integer
2489                 $msg .= sprintf ' at %s C', $reading;
2490                 if ($min_warn eq '[N/A]' or $min_crit eq '[N/A]') {
2491                     $msg .= sprintf ' (max=%s/%s)', $max_warn, $max_crit;
2492                 }
2493                 else {
2494                     $msg .= sprintf ' (min=%s/%s, max=%s/%s)',
2495                       $min_warn, $min_crit, $max_warn, $max_crit;
2496                 }
2497             }
2498             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2499             report('storage', $msg, $err, $nexus);
2500         }
2501         # Ok
2502         else {
2503             my $msg = sprintf '%s in enclosure %s [%s]',
2504               $name, $encl_id, $encl_name;
2505             if (defined $reading && $reading ne '[N/A]') {
2506                 # take into account that with certain states the
2507                 # reading doesn't exist or is not an integer
2508                 $msg .= sprintf ' reads %d C', $reading;
2509                 if ($min_warn eq '[N/A]' or $min_crit eq '[N/A]') {
2510                     $msg .= sprintf ' (max=%s/%s)', $max_warn, $max_crit;
2511                 }
2512                 else {
2513                     $msg .= sprintf ' (min=%s/%s, max=%s/%s)',
2514                       $min_warn, $min_crit, $max_warn, $max_crit;
2515                 }
2516             }
2517             else {
2518                 $msg .= sprintf ' is %s', $state;
2519             }
2520             report('storage', $msg, $E_OK, $nexus);
2521         }
2522
2523         # Collect performance data
2524         if (defined $opt{perfdata} && $reading ne '[N/A]') {
2525             $name =~ s{\A Temperature\sProbe\s(\d+) \z}{temp_$1}gxms;
2526             my $label = "enclosure_${encl_id}_${name}";
2527             my $mini = $label;
2528             $mini =~ s{enclosure_(.+?)_temp_(.+?)}{e$1t$2}xms;
2529             push @perfdata, {
2530                              label => $label,
2531                              mini  => $mini,
2532                              value => $reading,
2533                              warn  => $max_warn,
2534                              crit  => $max_crit,
2535                             };
2536         }
2537     }
2538     return;
2539 }
2540
2541
2542 #-----------------------------------------
2543 # STORAGE: Check enclosure management modules (EMM)
2544 #-----------------------------------------
2545 sub check_enclosure_emms {
2546     return if $#controllers == -1;
2547     return if blacklisted('encl_emm', 'all');
2548
2549     my $id        = undef;
2550     my $nexus     = undef;
2551     my $name      = undef;
2552     my $state     = undef;
2553     my $status    = undef;
2554     my $encl_id   = undef;
2555     my $encl_name = undef;
2556     my @output    = ();
2557
2558     if ($snmp) {
2559         my %emms_oid
2560           = (
2561              '1.3.6.1.4.1.674.10893.1.20.130.13.1.1'  => 'enclosureManagementModuleNumber',
2562              '1.3.6.1.4.1.674.10893.1.20.130.13.1.2'  => 'enclosureManagementModuleName',
2563              '1.3.6.1.4.1.674.10893.1.20.130.13.1.4'  => 'enclosureManagementModuleState',
2564              '1.3.6.1.4.1.674.10893.1.20.130.13.1.11' => 'enclosureManagementModuleComponentStatus',
2565              '1.3.6.1.4.1.674.10893.1.20.130.13.1.12' => 'enclosureManagementModuleNexusID',
2566              '1.3.6.1.4.1.674.10893.1.20.130.14.1.4'  => 'enclosureManagementModuleConnectionEnclosureName',
2567              '1.3.6.1.4.1.674.10893.1.20.130.14.1.5'  => 'enclosureManagementModuleConnectionEnclosureNumber',
2568             );
2569         my $result = undef;
2570         if ($opt{use_get_table}) {
2571             my $enclosureManagementModuleTable = '1.3.6.1.4.1.674.10893.1.20.130.13';
2572             my $enclosureManagementModuleConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.14';
2573
2574             $result = $snmp_session->get_table(-baseoid => $enclosureManagementModuleTable);
2575             my $ext = $snmp_session->get_table(-baseoid => $enclosureManagementModuleConnectionTable);
2576
2577             if (defined $result) {
2578                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2579             }
2580         }
2581         else {
2582             $result = $snmp_session->get_entries(-columns => [keys %emms_oid]);
2583         }
2584
2585         # No enclosure EMMs is OK
2586         return if !defined $result;
2587
2588         @output = @{ get_snmp_output($result, \%emms_oid) };
2589     }
2590     else {
2591         foreach my $enc (@enclosures) {
2592             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=emms") };
2593             map_item('ctrl', $enc->{ctrl}, \@output);
2594             map_item('encl_id', $enc->{id}, \@output);
2595             map_item('encl_name', $enc->{name}, \@output);
2596         }
2597     }
2598
2599     my %emms_state
2600       = (
2601          0  => 'Unknown',
2602          1  => 'Ready',
2603          2  => 'Failed',
2604          3  => 'Online',
2605          4  => 'Offline',
2606          5  => 'Not Installed',
2607          6  => 'Degraded',
2608          21 => 'Missing',
2609         );
2610
2611     # Check EMMs on each of the enclosures
2612   EMM:
2613     foreach my $out (@output) {
2614         if ($snmp) {
2615             $id        = $out->{enclosureManagementModuleNumber} - 1;
2616             $name      = $out->{enclosureManagementModuleName};
2617             $state     = get_hashval($out->{enclosureManagementModuleState}, \%emms_state);
2618             $status    = $snmp_status{$out->{enclosureManagementModuleComponentStatus}};
2619             $encl_id   = $snmp_enclosure{$out->{enclosureManagementModuleConnectionEnclosureNumber}}{nexus};
2620             $encl_name = $out->{enclosureManagementModuleConnectionEnclosureName};
2621             $nexus     = convert_nexus($out->{enclosureManagementModuleNexusID});
2622         }
2623         else {
2624             $id        = $out->{'ID'};
2625             $name      = $out->{'Name'};
2626             $state     = $out->{'State'};
2627             $status    = $out->{'Status'};
2628             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2629             $encl_name = $out->{encl_name};
2630             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, $id;
2631         }
2632
2633         next EMM if blacklisted('encl_emm', $nexus);
2634
2635         # Not installed
2636         if ($status =~ m{\A Other|Unknown \z}xms and $state eq 'Not Installed') {
2637             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2638               $name, $encl_id, $encl_name, $state;
2639             report('storage', $msg, $E_OK, $nexus);
2640         }
2641         # Default
2642         elsif ($status ne 'Ok') {
2643             my $msg = sprintf '%s in enclosure %s [%s] needs attention: %s',
2644               $name, $encl_id, $encl_name, $state;
2645             report('storage', $msg, $status2nagios{$status}, $nexus);
2646         }
2647         # Ok
2648         else {
2649             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2650               $name, $encl_id, $encl_name, $state;
2651             report('storage', $msg, $E_OK, $nexus);
2652         }
2653     }
2654     return;
2655 }
2656
2657
2658 #-----------------------------------------
2659 # CHASSIS: Check memory modules
2660 #-----------------------------------------
2661 sub check_memory {
2662     return if blacklisted('dimm', 'all');
2663
2664     my $index    = undef;
2665     my $status   = undef;
2666     my $location = undef;
2667     my $size     = undef;
2668     my $modes    = undef;
2669     my @failures = ();
2670     my @output   = ();
2671
2672     if ($snmp) {
2673         my %dimm_oid
2674           = (
2675              '1.3.6.1.4.1.674.10892.1.1100.50.1.2.1'  => 'memoryDeviceIndex',
2676              '1.3.6.1.4.1.674.10892.1.1100.50.1.5.1'  => 'memoryDeviceStatus',
2677              '1.3.6.1.4.1.674.10892.1.1100.50.1.8.1'  => 'memoryDeviceLocationName',
2678              '1.3.6.1.4.1.674.10892.1.1100.50.1.14.1' => 'memoryDeviceSize',
2679              '1.3.6.1.4.1.674.10892.1.1100.50.1.20.1' => 'memoryDeviceFailureModes',
2680             );
2681         my $result = undef;
2682         if ($opt{use_get_table}) {
2683             my $memoryDeviceTable = '1.3.6.1.4.1.674.10892.1.1100.50.1';
2684             $result = $snmp_session->get_table(-baseoid => $memoryDeviceTable);
2685         }
2686         else {
2687             $result = $snmp_session->get_entries(-columns => [keys %dimm_oid]);
2688         }
2689
2690         if (!defined $result) {
2691             printf "SNMP ERROR [memory]: %s.\n", $snmp_session->error;
2692             $snmp_session->close;
2693             exit $E_UNKNOWN;
2694         }
2695
2696         @output = @{ get_snmp_output($result, \%dimm_oid) };
2697     }
2698     else {
2699         @output = @{ run_omreport("$omopt_chassis memory") };
2700     }
2701
2702     # Note: These values are bit masks, so combination values are
2703     # possible. If value is 0 (zero), memory device has no faults.
2704     my %failure_mode
2705       = (
2706          1  => 'ECC single bit correction warning rate exceeded',
2707          2  => 'ECC single bit correction failure rate exceeded',
2708          4  => 'ECC multibit fault encountered',
2709          8  => 'ECC single bit correction logging disabled',
2710          16 => 'device disabled because of spare activation',
2711         );
2712
2713   DIMM:
2714     foreach my $out (@output) {
2715         @failures = ();  # Initialize
2716         if ($snmp) {
2717             $index    = $out->{memoryDeviceIndex};
2718             $status   = $snmp_status{$out->{memoryDeviceStatus}};
2719             $location = $out->{memoryDeviceLocationName};
2720             $size     = sprintf '%d MB', $out->{memoryDeviceSize}/1024;
2721             $modes    = $out->{memoryDeviceFailureModes};
2722             if ($modes > 0) {
2723                 foreach my $mask (sort keys %failure_mode) {
2724                     if (($modes & $mask) != 0) { push @failures, $failure_mode{$mask}; }
2725                 }
2726             }
2727         }
2728         else {
2729             $index    = $out->{'Type'} eq '[Not Occupied]' ? undef : $out->{'Index'};
2730             $status   = $out->{'Status'};
2731             $location = $out->{'Connector Name'};
2732             $size     = $out->{'Size'};
2733             if (defined $size) {
2734                 $size =~ s{\s\s}{ }gxms;
2735             }
2736             # Run 'omreport chassis memory index=X' to get the failures
2737             if ($status ne 'Ok' && defined $index) {
2738                 foreach (@{ run_command("$omreport $omopt_chassis memory index=$index -fmt ssv") }) {
2739                     if (m/\A Failures; (.+?) \z/xms) {
2740                         chop(my $fail = $1);
2741                         push @failures, split m{\.}xms, $fail;
2742                     }
2743                 }
2744             }
2745         }
2746         $location =~ s{\A \s*(.*?)\s* \z}{$1}xms;
2747
2748         # calculate total memory
2749         my $msize = defined $size ? $size : 0;
2750         $msize =~ s{\A (\d+) \s MB}{$1}xms;
2751         $count{mem} += $msize;
2752
2753         next DIMM if blacklisted('dimm', $index);
2754
2755         # Ignore empty memory slots
2756         next DIMM if !defined $index;
2757         $count{dimm}++;
2758
2759         if ($status ne 'Ok') {
2760             my $msg = undef;
2761             if (scalar @failures == 0) {
2762                 $msg = sprintf 'Memory module %d [%s, %s] needs attention (%s)',
2763                   $index, $location, $size, $status;
2764             }
2765             else {
2766                 $msg = sprintf 'Memory module %d [%s, %s] needs attention: %s',
2767                   $index, $location, $size, (join q{, }, @failures);
2768             }
2769
2770             report('chassis', $msg, $status2nagios{$status}, $index);
2771         }
2772         # Ok
2773         else {
2774             my $msg = sprintf 'Memory module %d [%s, %s] is %s',
2775               $index, $location, $size, $status;
2776             report('chassis', $msg, $E_OK, $index);
2777         }
2778     }
2779     return;
2780 }
2781
2782
2783 #-----------------------------------------
2784 # CHASSIS: Check fans
2785 #-----------------------------------------
2786 sub check_fans {
2787     return if blacklisted('fan', 'all');
2788
2789     my $index    = undef;
2790     my $status   = undef;
2791     my $reading  = undef;
2792     my $location = undef;
2793     my $max_crit = undef;
2794     my $max_warn = undef;
2795     my @output   = ();
2796
2797     if ($snmp) {
2798         my %cool_oid
2799           = (
2800              '1.3.6.1.4.1.674.10892.1.700.12.1.2.1'  => 'coolingDeviceIndex',
2801              '1.3.6.1.4.1.674.10892.1.700.12.1.5.1'  => 'coolingDeviceStatus',
2802              '1.3.6.1.4.1.674.10892.1.700.12.1.6.1'  => 'coolingDeviceReading',
2803              '1.3.6.1.4.1.674.10892.1.700.12.1.8.1'  => 'coolingDeviceLocationName',
2804              '1.3.6.1.4.1.674.10892.1.700.12.1.10.1' => 'coolingDeviceUpperCriticalThreshold',
2805              '1.3.6.1.4.1.674.10892.1.700.12.1.11.1' => 'coolingDeviceUpperNonCriticalThreshold',
2806             );
2807         my $result = undef;
2808         if ($opt{use_get_table}) {
2809             my $coolingDeviceTable = '1.3.6.1.4.1.674.10892.1.700.12.1';
2810             $result = $snmp_session->get_table(-baseoid => $coolingDeviceTable);
2811         }
2812         else {
2813             $result = $snmp_session->get_entries(-columns => [keys %cool_oid]);
2814         }
2815
2816         if ($blade && !defined $result) {
2817             return 0;
2818         }
2819         elsif (!$blade && !defined $result) {
2820             printf "SNMP ERROR [cooling]: %s.\n", $snmp_session->error;
2821             $snmp_session->close;
2822             exit $E_UNKNOWN;
2823         }
2824
2825         @output = @{ get_snmp_output($result, \%cool_oid) };
2826     }
2827     else {
2828         @output = @{ run_omreport("$omopt_chassis fans") };
2829     }
2830
2831   FAN:
2832     foreach my $out (@output) {
2833         if ($snmp) {
2834             $index    = $out->{coolingDeviceIndex};
2835             $status   = $snmp_probestatus{$out->{coolingDeviceStatus}};
2836             $reading  = $out->{coolingDeviceReading};
2837             $location = $out->{coolingDeviceLocationName};
2838             $max_crit = exists $out->{coolingDeviceUpperCriticalThreshold}
2839               ? $out->{coolingDeviceUpperCriticalThreshold} : 0;
2840             $max_warn = exists $out->{coolingDeviceUpperNonCriticalThreshold}
2841               ? $out->{coolingDeviceUpperNonCriticalThreshold} : 0;
2842         }
2843         else {
2844             $index    = $out->{'Index'};
2845             $status   = $out->{'Status'};
2846             $reading  = $out->{'Reading'};
2847             $location = $out->{'Probe Name'};
2848             $max_crit = $out->{'Maximum Failure Threshold'} ne '[N/A]'
2849               ? $out->{'Maximum Failure Threshold'} : 0;
2850             $max_warn = $out->{'Maximum Warning Threshold'} ne '[N/A]'
2851               ? $out->{'Maximum Warning Threshold'} : 0;
2852             $reading  =~ s{\A (\d+).* \z}{$1}xms;
2853             $max_warn =~ s{\A (\d+).* \z}{$1}xms;
2854             $max_crit =~ s{\A (\d+).* \z}{$1}xms;
2855         }
2856
2857         next FAN if blacklisted('fan', $index);
2858         $count{fan}++;
2859
2860         if ($status ne 'Ok') {
2861             my $msg = sprintf 'Chassis fan %d [%s] needs attention: %s',
2862               $index, $location, $status;
2863             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2864             report('chassis', $msg, $err, $index);
2865         }
2866         else {
2867             my $msg = sprintf 'Chassis fan %d [%s]: %s',
2868               $index, $location, $reading;
2869             report('chassis', $msg, $E_OK, $index);
2870         }
2871
2872         # Collect performance data
2873         if (defined $opt{perfdata}) {
2874             my $pname = lc $location;
2875             $pname =~ s{\s}{_}gxms;
2876             $pname =~ s{proc_}{cpu#}xms;
2877             push @perfdata, {
2878                              label => "fan_${index}_${pname}",
2879                              mini  => "f$index",
2880                              value => $reading,
2881                              warn  => $max_warn,
2882                              crit  => $max_crit,
2883                             };
2884         }
2885     }
2886     return;
2887 }
2888
2889
2890 #-----------------------------------------
2891 # CHASSIS: Check power supplies
2892 #-----------------------------------------
2893 sub check_powersupplies {
2894     return if blacklisted('ps', 'all');
2895
2896     my $index    = undef;
2897     my $status   = undef;
2898     my $type     = undef;
2899     my $err_type = undef;
2900     my $state    = undef;
2901     my @states   = ();
2902     my @output   = ();
2903
2904     if ($snmp) {
2905         my %ps_oid
2906           = (
2907              '1.3.6.1.4.1.674.10892.1.600.12.1.2.1'  => 'powerSupplyIndex',
2908              '1.3.6.1.4.1.674.10892.1.600.12.1.5.1'  => 'powerSupplyStatus',
2909              '1.3.6.1.4.1.674.10892.1.600.12.1.7.1'  => 'powerSupplyType',
2910              '1.3.6.1.4.1.674.10892.1.600.12.1.11.1' => 'powerSupplySensorState',
2911              '1.3.6.1.4.1.674.10892.1.600.12.1.12.1' => 'powerSupplyConfigurationErrorType',
2912             );
2913         my $result = undef;
2914         if ($opt{use_get_table}) {
2915             my $powerDeviceTable = '1.3.6.1.4.1.674.10892.1.600.12.1';
2916             $result = $snmp_session->get_table(-baseoid => $powerDeviceTable);
2917         }
2918         else {
2919             $result = $snmp_session->get_entries(-columns => [keys %ps_oid]);
2920         }
2921
2922         # No instrumented PSU is OK (blades, low-end servers)
2923         return 0 if !defined $result;
2924
2925         @output = @{ get_snmp_output($result, \%ps_oid) };
2926     }
2927     else {
2928         @output = @{ run_omreport("$omopt_chassis pwrsupplies") };
2929     }
2930
2931     my %ps_type
2932       = (
2933          1  => 'Other',
2934          2  => 'Unknown',
2935          3  => 'Linear',
2936          4  => 'Switching',
2937          5  => 'Battery',
2938          6  => 'Uninterruptible Power Supply',
2939          7  => 'Converter',
2940          8  => 'Regulator',
2941          9  => 'AC',
2942          10 => 'DC',
2943          11 => 'VRM',
2944         );
2945
2946     my %ps_state
2947       = (
2948          1  => 'Presence detected',
2949          2  => 'Failure detected',
2950          4  => 'Predictive Failure',
2951          8  => 'AC lost',
2952          16 => 'AC lost or out-of-range',
2953          32 => 'AC out-of-range but present',
2954          64 => 'Configuration error',
2955         );
2956
2957     my %ps_config_error_type
2958       = (
2959          1 => 'Vendor mismatch',
2960          2 => 'Revision mismatch',
2961          3 => 'Processor missing',
2962         );
2963
2964   PS:
2965     foreach my $out (@output) {
2966         if ($snmp) {
2967             @states = ();  # contains states for the PS
2968
2969             $index    = $out->{powerSupplyIndex} - 1;
2970             $status   = $snmp_status{$out->{powerSupplyStatus}};
2971             $type     = get_hashval($out->{powerSupplyType}, \%ps_type);
2972             $err_type = defined $out->{powerSupplyConfigurationErrorType}
2973               ? $ps_config_error_type{$out->{powerSupplyConfigurationErrorType}} : undef;
2974
2975             # get the combined state from the StatusReading OID
2976             foreach my $mask (sort keys %ps_state) {
2977                 if (($out->{powerSupplySensorState} & $mask) != 0) {
2978                     push @states, $ps_state{$mask};
2979                 }
2980             }
2981
2982             # If configuration error, also include the error type
2983             if (defined $err_type) {
2984                 push @states, $err_type;
2985             }
2986
2987             # Finally, construct the state string
2988             $state = join q{, }, @states;
2989         }
2990         else {
2991             $index  = $out->{'Index'};
2992             $status = $out->{'Status'};
2993             $type   = $out->{'Type'};
2994             $state  = $out->{'Online Status'};
2995         }
2996
2997         next PS if blacklisted('ps', $index);
2998         $count{power}++;
2999
3000         if ($status ne 'Ok') {
3001             my $msg = sprintf 'Power Supply %d [%s] needs attention: %s',
3002               $index, $type, $state;
3003             report('chassis', $msg, $status2nagios{$status}, $index);
3004         }
3005         else {
3006             my $msg = sprintf 'Power Supply %d [%s]: %s',
3007               $index, $type, $state;
3008             report('chassis', $msg, $E_OK, $index);
3009         }
3010     }
3011     return;
3012 }
3013
3014
3015 #-----------------------------------------
3016 # CHASSIS: Check temperatures
3017 #-----------------------------------------
3018 sub check_temperatures {
3019     return if blacklisted('temp', 'all');
3020
3021     my $index    = undef;
3022     my $status   = undef;
3023     my $reading  = undef;
3024     my $location = undef;
3025     my $max_crit = undef;
3026     my $max_warn = undef;
3027     my $min_warn = undef;
3028     my $min_crit = undef;
3029     my $type     = undef;
3030     my $discrete = undef;
3031     my @output = ();
3032
3033     # Getting custom temperature thresholds (user option)
3034     my %warn_threshold = %{ custom_temperature_thresholds('w') };
3035     my %crit_threshold = %{ custom_temperature_thresholds('c') };
3036
3037     if ($snmp) {
3038         my %temp_oid
3039           = (
3040              '1.3.6.1.4.1.674.10892.1.700.20.1.2.1'  => 'temperatureProbeIndex',
3041              '1.3.6.1.4.1.674.10892.1.700.20.1.5.1'  => 'temperatureProbeStatus',
3042              '1.3.6.1.4.1.674.10892.1.700.20.1.6.1'  => 'temperatureProbeReading',
3043              '1.3.6.1.4.1.674.10892.1.700.20.1.7.1'  => 'temperatureProbeType',
3044              '1.3.6.1.4.1.674.10892.1.700.20.1.8.1'  => 'temperatureProbeLocationName',
3045              '1.3.6.1.4.1.674.10892.1.700.20.1.10.1' => 'temperatureProbeUpperCriticalThreshold',
3046              '1.3.6.1.4.1.674.10892.1.700.20.1.11.1' => 'temperatureProbeUpperNonCriticalThreshold',
3047              '1.3.6.1.4.1.674.10892.1.700.20.1.12.1' => 'temperatureProbeLowerNonCriticalThreshold',
3048              '1.3.6.1.4.1.674.10892.1.700.20.1.13.1' => 'temperatureProbeLowerCriticalThreshold',
3049              '1.3.6.1.4.1.674.10892.1.700.20.1.16.1' => 'temperatureProbeDiscreteReading',
3050             );
3051         # this didn't work well for some reason
3052         #my $result = $snmp_session->get_entries(-columns => [keys %temp_oid]);
3053
3054         # Getting values using the table
3055         my $temperatureProbeTable = '1.3.6.1.4.1.674.10892.1.700.20';
3056         my $result = $snmp_session->get_table(-baseoid => $temperatureProbeTable);
3057
3058         if (!defined $result) {
3059             printf "SNMP ERROR [temperatures]: %s.\n", $snmp_session->error;
3060             $snmp_session->close;
3061             exit $E_UNKNOWN;
3062         }
3063
3064         @output = @{ get_snmp_output($result, \%temp_oid) };
3065     }
3066     else {
3067         @output = @{ run_omreport("$omopt_chassis temps") };
3068     }
3069
3070     my %probe_type
3071       = (
3072          1  => 'Other',      # type is other than following values
3073          2  => 'Unknown',    # type is unknown
3074          3  => 'AmbientESM', # type is Ambient Embedded Systems Management temperature probe
3075          16 => 'Discrete',   # type is temperature probe with discrete reading
3076         );
3077
3078   TEMP:
3079     foreach my $out (@output) {
3080         if ($snmp) {
3081             $index    = $out->{temperatureProbeIndex} - 1;
3082             $status   = $snmp_probestatus{$out->{temperatureProbeStatus}};
3083             $location = $out->{temperatureProbeLocationName};
3084             $reading  = exists $out->{temperatureProbeReading}
3085               ? $out->{temperatureProbeReading} / 10 : '[N/A]';
3086             $max_crit = exists $out->{temperatureProbeUpperCriticalThreshold}
3087               ? $out->{temperatureProbeUpperCriticalThreshold} / 10 : '[N/A]';
3088             $max_warn = exists $out->{temperatureProbeUpperNonCriticalThreshold}
3089               ? $out->{temperatureProbeUpperNonCriticalThreshold} / 10 : '[N/A]';
3090             $min_crit = exists $out->{temperatureProbeLowerCriticalThreshold}
3091               ? $out->{temperatureProbeLowerCriticalThreshold} / 10 : '[N/A]';
3092             $min_warn = exists $out->{temperatureProbeLowerNonCriticalThreshold}
3093               ? $out->{temperatureProbeLowerNonCriticalThreshold} / 10 : '[N/A]';
3094             $type     = get_hashval($out->{temperatureProbeType}, \%probe_type);
3095             $discrete = exists $out->{temperatureProbeDiscreteReading}
3096               ? $out->{temperatureProbeDiscreteReading} : '[N/A]';
3097             # workaround for bad temp probes
3098             if ($type eq 'AmbientESM' and $reading !~ m{\A \d+(\.\d+)? \z}xms) {
3099                 $type = 'Discrete';
3100             }
3101         }
3102         else {
3103             $index    = $out->{'Index'};
3104             $status   = $out->{'Status'};
3105             $reading  = $out->{'Reading'}; $reading =~ s{\.0\s+C}{}xms;
3106             $location = $out->{'Probe Name'};
3107             $max_crit = $out->{'Maximum Failure Threshold'}; $max_crit =~ s{\.0\s+C}{}xms;
3108             $max_warn = $out->{'Maximum Warning Threshold'}; $max_warn =~ s{\.0\s+C}{}xms;
3109             $min_crit = $out->{'Minimum Failure Threshold'}; $min_crit =~ s{\.0\s+C}{}xms;
3110             $min_warn = $out->{'Minimum Warning Threshold'}; $min_warn =~ s{\.0\s+C}{}xms;
3111             $type     = $reading =~ m{\A\d+\z}xms ? 'AmbientESM' : 'Discrete';
3112             $discrete = $reading;
3113         }
3114
3115         next TEMP if blacklisted('temp', $index);
3116         $count{temp}++;
3117
3118         if ($type eq 'Discrete') {
3119             my $msg = sprintf 'Temperature probe %d [%s] is %s',
3120               $index, $location, $discrete;
3121             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3122             report('chassis', $msg, $err, $index);
3123         }
3124         else {
3125             # First check according to custom thresholds
3126             if (exists $crit_threshold{$index}{max} and $reading > $crit_threshold{$index}{max}) {
3127                 # Custom critical MAX
3128                 my $msg = sprintf 'Temperature Probe %d [%s] reads %d C (custom max=%d)',
3129                   $index, $location, $reading, $crit_threshold{$index}{max};
3130                 report('chassis', $msg, $E_CRITICAL, $index);
3131             }
3132             elsif (exists $warn_threshold{$index}{max} and $reading > $warn_threshold{$index}{max}) {
3133                 # Custom warning MAX
3134                 my $msg = sprintf 'Temperature Probe %d [%s] reads %d C (custom max=%d)',
3135                   $index, $location, $reading, $warn_threshold{$index}{max};
3136                 report('chassis', $msg, $E_WARNING, $index);
3137             }
3138             elsif (exists $crit_threshold{$index}{min} and $reading < $crit_threshold{$index}{min}) {
3139                 # Custom critical MIN
3140                 my $msg = sprintf 'Temperature Probe %d [%s] reads %d C (custom min=%d)',
3141                   $index, $location, $reading, $crit_threshold{$index}{min};
3142                 report('chassis', $msg, $E_CRITICAL, $index);
3143             }
3144             elsif (exists $warn_threshold{$index}{min} and $reading < $warn_threshold{$index}{min}) {
3145                 # Custom warning MIN
3146                 my $msg = sprintf 'Temperature Probe %d [%s] reads %d C (custom min=%d)',
3147                   $index, $location, $reading, $warn_threshold{$index}{min};
3148                 report('chassis', $msg, $E_WARNING, $index);
3149             }
3150             elsif ($status ne 'Ok' and $max_crit ne '[N/A]' and $reading > $max_crit) {
3151                 my $msg = sprintf 'Temperature Probe %d [%s] is critically high at %d C',
3152                   $index, $location, $reading;
3153                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3154                 report('chassis', $msg, $err, $index);
3155             }
3156             elsif ($status ne 'Ok' and $max_warn ne '[N/A]' and $reading > $max_warn) {
3157                 my $msg = sprintf 'Temperature Probe %d [%s] is too high at %d C',
3158                   $index, $location, $reading;
3159                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3160                 report('chassis', $msg, $err, $index);
3161             }
3162             elsif ($status ne 'Ok' and $min_crit ne '[N/A]' and $reading < $min_crit) {
3163                 my $msg = sprintf 'Temperature Probe %d [%s] is critically low at %d C',
3164                   $index, $location, $reading;
3165                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3166                 report('chassis', $msg, $err, $index);
3167             }
3168             elsif ($status ne 'Ok' and $min_warn ne '[N/A]' and $reading < $min_warn) {
3169                 my $msg = sprintf 'Temperature Probe %d [%s] is too low at %d C',
3170                   $index, $location, $reading;
3171                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3172                 report('chassis', $msg, $err, $index);
3173             }
3174             # Ok
3175             else {
3176                 my $msg = sprintf 'Temperature Probe %d [%s] reads %d C',
3177                   $index, $location, $reading;
3178                 if ($min_warn eq '[N/A]' and $min_crit eq '[N/A]') {
3179                     $msg .= sprintf ' (max=%s/%s)', $max_warn, $max_crit;
3180                 }
3181                 else {
3182                     $msg .= sprintf ' (min=%s/%s, max=%s/%s)',
3183                       $min_warn, $min_crit, $max_warn, $max_crit;
3184                 }
3185                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3186                 report('chassis', $msg, $err, $index);
3187             }
3188
3189             # Collect performance data
3190             if (defined $opt{perfdata}) {
3191                 my $pname = lc $location;
3192                 $pname =~ s{\s}{_}gxms;
3193                 $pname =~ s{_temp\z}{}xms;
3194                 $pname =~ s{proc_}{cpu#}xms;
3195                 push @perfdata, {
3196                                  label => "temp_${index}_${pname}",
3197                                  mini  => "t$index",
3198                                  value => $reading,
3199                                  warn  => $max_warn,
3200                                  crit  => $max_crit,
3201                                 };
3202             }
3203         }
3204     }
3205     return;
3206 }
3207
3208
3209 #-----------------------------------------
3210 # CHASSIS: Check processors
3211 #-----------------------------------------
3212 sub check_processors {
3213     return if blacklisted('cpu', 'all');
3214
3215     my $index   = undef;
3216     my $status  = undef;
3217     my $state   = undef;
3218     my $brand   = undef;
3219     my $family  = undef;
3220     my $man     = undef;
3221     my $speed   = undef;
3222     my @output = ();
3223
3224     if ($snmp) {
3225
3226         # NOTE: For some reason, older models don't have the
3227         # "Processor Device Status" OIDs. We check both the newer
3228         # (preferred) OIDs and the old ones.
3229
3230         my %cpu_oid
3231           = (
3232              '1.3.6.1.4.1.674.10892.1.1100.30.1.2.1'  => 'processorDeviceIndex',
3233              '1.3.6.1.4.1.674.10892.1.1100.30.1.5.1'  => 'processorDeviceStatus',
3234              '1.3.6.1.4.1.674.10892.1.1100.30.1.8.1'  => 'processorDeviceManufacturerName',
3235              '1.3.6.1.4.1.674.10892.1.1100.30.1.9.1'  => 'processorDeviceStatusState',
3236              '1.3.6.1.4.1.674.10892.1.1100.30.1.10.1' => 'processorDeviceFamily',
3237              '1.3.6.1.4.1.674.10892.1.1100.30.1.12.1' => 'processorDeviceCurrentSpeed',
3238              '1.3.6.1.4.1.674.10892.1.1100.30.1.23.1' => 'processorDeviceBrandName',
3239              '1.3.6.1.4.1.674.10892.1.1100.32.1.2.1'  => 'processorDeviceStatusIndex',
3240              '1.3.6.1.4.1.674.10892.1.1100.32.1.5.1'  => 'processorDeviceStatusStatus',
3241              '1.3.6.1.4.1.674.10892.1.1100.32.1.6.1'  => 'processorDeviceStatusReading',
3242             );
3243         my $result = undef;
3244         if ($opt{use_get_table}) {
3245             my $processorDeviceTable = '1.3.6.1.4.1.674.10892.1.1100.30.1';
3246             my $processorDeviceStatusTable = '1.3.6.1.4.1.674.10892.1.1100.32.1';
3247
3248             $result = $snmp_session->get_table(-baseoid => $processorDeviceTable);
3249             my $ext = $snmp_session->get_table(-baseoid => $processorDeviceStatusTable);
3250
3251             defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
3252         }
3253         else {
3254             $result = $snmp_session->get_entries(-columns => [keys %cpu_oid]);
3255         }
3256
3257         if (!defined $result) {
3258             printf "SNMP ERROR [processors]: %s.\n", $snmp_session->error;
3259             $snmp_session->close;
3260             exit $E_UNKNOWN;
3261         }
3262
3263         @output = @{ get_snmp_output($result, \%cpu_oid) };
3264     }
3265     else {
3266         @output = @{ run_omreport("$omopt_chassis processors") };
3267     }
3268
3269     my %cpu_state
3270       = (
3271          1 => 'Other',         # other than following values
3272          2 => 'Unknown',       # unknown
3273          3 => 'Enabled',       # enabled
3274          4 => 'User Disabled', # disabled by user via BIOS setup
3275          5 => 'BIOS Disabled', # disabled by BIOS (POST error)
3276          6 => 'Idle',          # idle
3277         );
3278
3279     my %cpu_reading
3280       = (
3281          1    => 'Internal Error',      # Internal Error
3282          2    => 'Thermal Trip',        # Thermal Trip
3283          32   => 'Configuration Error', # Configuration Error
3284          128  => 'Present',             # Processor Present
3285          256  => 'Disabled',            # Processor Disabled
3286          512  => 'Terminator Present',  # Terminator Present
3287          1024 => 'Throttled',           # Processor Throttled
3288         );
3289
3290     # Mapping between family numbers from SNMP and actual CPU family
3291     my %cpu_family
3292       = (
3293          1   => 'Other',                                2   => 'Unknown',
3294          3   => '8086',                                 4   => '80286',
3295          5   => '386',                                  6   => '486',
3296          7   => '8087',                                 8   => '80287',
3297          9   => '80387',                                10  => '80487',
3298          11  => 'Pentium',                              12  => 'Pentium Pro',
3299          13  => 'Pentium II',                           14  => 'Pentium with MMX',
3300          15  => 'Celeron',                              16  => 'Pentium II Xeon',
3301          17  => 'Pentium III',                          18  => 'Pentium III Xeon',
3302          19  => 'Pentium III',                          20  => 'Itanium',
3303          21  => 'Xeon',                                 22  => 'Pentium 4',
3304          23  => 'Xeon MP',                              24  => 'Itanium 2',
3305          25  => 'K5',                                   26  => 'K6',
3306          27  => 'K6-2',                                 28  => 'K6-3',
3307          29  => 'Athlon',                               30  => 'AMD2900',
3308          31  => 'K6-2+',                                32  => 'Power PC',
3309          33  => 'Power PC 601',                         34  => 'Power PC 603',
3310          35  => 'Power PC 603+',                        36  => 'Power PC 604',
3311          37  => 'Power PC 620',                         38  => 'Power PC x704',
3312          39  => 'Power PC 750',                         40  => 'Core Duo',
3313          41  => 'Core Duo mobile',                      42  => 'Core Solo mobile',
3314          43  => 'Intel Atom',                           44  => undef,
3315          45  => undef,                                  46  => undef,
3316          47  => undef,                                  48  => 'Alpha',
3317          49  => 'Alpha 21064',                          50  => 'Alpha 21066',
3318          51  => 'Alpha 21164',                          52  => 'Alpha 21164PC',
3319          53  => 'Alpha 21164a',                         54  => 'Alpha 21264',
3320          55  => 'Alpha 21364',                          56  => 'Turion II Ultra Dual-Core Mobile M',
3321          57  => 'Turion II Dual-Core Mobile M',         58  => 'Athlon II Dual-Core Mobile M ',
3322          59  => 'Opteron 6100',                         60  => 'Opteron 4100',
3323          61  => undef,                                  62  => undef,
3324          63  => undef,                                  64  => 'MIPS',
3325          65  => 'MIPS R4000',                           66  => 'MIPS R4200',
3326          67  => 'MIPS R4400',                           68  => 'MIPS R4600',
3327          69  => 'MIPS R10000',                          70  => undef,
3328          71  => undef,                                  72  => undef,
3329          73  => undef,                                  74  => undef,
3330          75  => undef,                                  76  => undef,
3331          77  => undef,                                  78  => undef,
3332          79  => undef,                                  80  => 'SPARC',
3333          81  => 'SuperSPARC',                           82  => 'microSPARC II',
3334          83  => 'microSPARC IIep',                      84  => 'UltraSPARC',
3335          85  => 'UltraSPARC II',                        86  => 'UltraSPARC IIi',
3336          87  => 'UltraSPARC III',                       88  => 'UltraSPARC IIIi',
3337          89  => undef,                                  90  => undef,
3338          91  => undef,                                  92  => undef,
3339          93  => undef,                                  94  => undef,
3340          95  => undef,                                  96  => '68040',
3341          97  => '68xxx',                                98  => '68000',
3342          99  => '68010',                                100 => '68020',
3343          101 => '68030',                                102 => undef,
3344          103 => undef,                                  104 => undef,
3345          105 => undef,                                  106 => undef,
3346          107 => undef,                                  108 => undef,
3347          109 => undef,                                  110 => undef,
3348          111 => undef,                                  112 => 'Hobbit',
3349          113 => undef,                                  114 => undef,
3350          115 => undef,                                  116 => undef,
3351          117 => undef,                                  118 => undef,
3352          119 => undef,                                  120 => 'Crusoe TM5000',
3353          121 => 'Crusoe TM3000',                        122 => 'Efficeon TM8000',
3354          123 => undef,                                  124 => undef,
3355          125 => undef,                                  126 => undef,
3356          127 => undef,                                  128 => 'Weitek',
3357          129 => undef,                                  130 => 'Celeron M',
3358          131 => 'Athlon 64',                            132 => 'Opteron',
3359          133 => 'Sempron',                              134 => 'Turion 64 Mobile',
3360          135 => 'Dual-Core Opteron',                    136 => 'Athlon 64 X2 DC',
3361          137 => 'Turion 64 X2 M',                       138 => 'Quad-Core Opteron',
3362          139 => '3rd gen Opteron',                      140 => 'AMD Phenom FX Quad-Core',
3363          141 => 'AMD Phenom X4 Quad-Core',              142 => 'AMD Phenom X2 Dual-Core',
3364          143 => 'AMD Athlon X2 Dual-Core',              144 => 'PA-RISC',
3365          145 => 'PA-RISC 8500',                         146 => 'PA-RISC 8000',
3366          147 => 'PA-RISC 7300LC',                       148 => 'PA-RISC 7200',
3367          149 => 'PA-RISC 7100LC',                       150 => 'PA-RISC 7100',
3368          151 => undef,                                  152 => undef,
3369          153 => undef,                                  154 => undef,
3370          155 => undef,                                  156 => undef,
3371          157 => undef,                                  158 => undef,
3372          159 => undef,                                  160 => 'V30',
3373          161 => 'Quad-Core Xeon 3200',                  162 => 'Dual-Core Xeon 3000',
3374          163 => 'Quad-Core Xeon 5300',                  164 => 'Dual-Core Xeon 5100',
3375          165 => 'Dual-Core Xeon 5000',                  166 => 'Dual-Core Xeon LV',
3376          167 => 'Dual-Core Xeon ULV',                   168 => 'Dual-Core Xeon 7100',
3377          169 => 'Quad-Core Xeon 5400',                  170 => 'Quad-Core Xeon',
3378          171 => 'Dual-Core Xeon 5200',                  172 => 'Dual-Core Xeon 7200',
3379          173 => 'Quad-Core Xeon 7300',                  174 => 'Quad-Core Xeon 7400',
3380          175 => 'Multi-Core Xeon 7400',                 176 => 'M1',
3381          177 => 'M2',                                   178 => undef,
3382          179 => 'Pentium 4 HT',                         180 => 'AS400',
3383          181 => undef,                                  182 => 'Athlon XP',
3384          183 => 'Athlon MP',                            184 => 'Duron',
3385          185 => 'Pentium M',                            186 => 'Celeron D',
3386          187 => 'Pentium D',                            188 => 'Pentium Extreme',
3387          189 => 'Core Solo',                            190 => 'Core2',
3388          191 => 'Core2 Duo',                            192 => 'Core2 Solo',
3389          193 => 'Core2 Extreme',                        194 => 'Core2 Quad',
3390          195 => 'Core2 Extreme mobile',                 196 => 'Core2 Duo mobile',
3391          197 => 'Core2 Solo mobile',                    198 => 'Core i7',
3392          199 => 'Dual-Core Celeron',                    200 => 'IBM390',
3393          201 => 'G4',                                   202 => 'G5',
3394          203 => 'ESA/390 G6',                           204 => 'z/Architectur',
3395          205 => 'Core i5',                              206 => 'Core i3',
3396          207 => undef,                                  208 => undef,
3397          209 => undef,                                  210 => 'C7-M',
3398          211 => 'C7-D',                                 212 => 'C7',
3399          213 => 'Eden',                                 214 => 'Multi-Core Xeon',
3400          215 => 'Dual-Core Xeon 3xxx',                  216 => 'Quad-Core Xeon 3xxx',
3401          217 => 'VIA Nano',                             218 => 'Dual-Core Xeon 5xxx',
3402          219 => 'Quad-Core Xeon 5xxx',                  220 => undef,
3403          221 => 'Dual-Core Xeon 7xxx',                  222 => 'Quad-Core Xeon 7xxx',
3404          223 => 'Multi-Core Xeon 7xxx',                 224 => 'Multi-Core Xeon 3400',
3405          225 => undef,                                  226 => undef,
3406          227 => undef,                                  228 => undef,
3407          229 => undef,                                  230 => 'Embedded AMD Opteron Quad-Core',
3408          231 => 'AMD Phenom Triple-Core',               232 => 'AMD Turion Ultra Dual-Core Mobile',
3409          233 => 'AMD Turion Dual-Core Mobile',          234 => 'AMD Athlon Dual-Core',
3410          235 => 'AMD Sempron SI',                       236 => 'AMD Phenom II',
3411          237 => 'AMD Athlon II',                        238 => 'Six-Core AMD Opteron',
3412          239 => 'AMD Sempron M',                        240 => undef,
3413          241 => undef,                                  242 => undef,
3414          243 => undef,                                  244 => undef,
3415          245 => undef,                                  246 => undef,
3416          247 => undef,                                  248 => undef,
3417          249 => undef,                                  250 => 'i860',
3418          251 => 'i960',
3419         );
3420
3421   CPU:
3422     foreach my $out (@output) {
3423         if ($snmp) {
3424             $index  = exists $out->{processorDeviceStatusIndex}
3425               ? $out->{processorDeviceStatusIndex} - 1
3426                 : $out->{processorDeviceIndex} - 1;
3427             $status = exists $out->{processorDeviceStatusStatus}
3428               ? $snmp_status{$out->{processorDeviceStatusStatus}}
3429                 : $snmp_status{$out->{processorDeviceStatus}};
3430             if (exists $out->{processorDeviceStatusReading}) {
3431                 my @states  = ();  # contains states for the CPU
3432
3433                 # get the combined state from the StatusReading OID
3434                 foreach my $mask (sort keys %cpu_reading) {
3435                     if (($out->{processorDeviceStatusReading} & $mask) != 0) {
3436                         push @states, $cpu_reading{$mask};
3437                     }
3438                 }
3439
3440                 # Finally, create the state string
3441                 $state = join q{, }, @states;
3442             }
3443             else {
3444                 $state  = get_hashval($out->{processorDeviceStatusState}, \%cpu_state);
3445             }
3446             $man    = $out->{processorDeviceManufacturerName};
3447             $family = (exists $out->{processorDeviceFamily}
3448                        and exists $cpu_family{$out->{processorDeviceFamily}})
3449               ? $cpu_family{$out->{processorDeviceFamily}} : undef;
3450             $speed  = $out->{processorDeviceCurrentSpeed};
3451             $brand  = $out->{processorDeviceBrandName};
3452         }
3453         else {
3454             $index  = $out->{'Index'};
3455             $status = $out->{'Status'};
3456             $state  = $out->{'State'};
3457             $brand  = exists $out->{'Processor Brand'} ? $out->{'Processor Brand'} : undef;
3458             $family = exists $out->{'Processor Family'} ? $out->{'Processor Family'} : undef;
3459             $man    = exists $out->{'Processor Manufacturer'} ? $out->{'Processor Manufacturer'} : undef;
3460             $speed  = exists $out->{'Current Speed'} ? $out->{'Current Speed'} : undef;
3461         }
3462
3463         next CPU if blacklisted('cpu', $index);
3464
3465         # Ignore unoccupied CPU slots (omreport)
3466         next CPU if (defined $out->{'Processor Manufacturer'}
3467                      and $out->{'Processor Manufacturer'} eq '[Not Occupied]')
3468           or (defined $out->{'Processor Brand'} and $out->{'Processor Brand'} eq '[Not Occupied]');
3469
3470         # Ignore unoccupied CPU slots (snmp)
3471         if ($snmp and exists $out->{processorDeviceStatusReading}
3472             and $out->{processorDeviceStatusReading} == 0) {
3473             next CPU;
3474         }
3475
3476         $count{cpu}++;
3477
3478         if (defined $brand) {
3479             $brand =~ s{\s\s+}{ }gxms;
3480             $brand =~ s{\((R|tm)\)}{}gxms;
3481             $brand =~ s{\s(CPU|Processor)}{}xms;
3482             $brand =~ s{\s\@}{}xms;
3483         }
3484         elsif (defined $family and defined $man and defined $speed) {
3485             $speed =~ s{\A (\d+) .*}{$1}xms;
3486             $brand = sprintf '%s %s %.2fGHz', $man, $family, $speed / 1000;
3487         }
3488         else {
3489             $brand = "unknown";
3490         }
3491
3492         # Default
3493         if ($status ne 'Ok') {
3494             my $msg = sprintf 'Processor %d [%s] needs attention: %s',
3495               $index, $brand, $state;
3496             report('chassis', $msg, $status2nagios{$status}, $index);
3497         }
3498         # Ok
3499         else {
3500             my $msg = sprintf 'Processor %d [%s] is %s',
3501               $index, $brand, $state;
3502             report('chassis', $msg, $E_OK, $index);
3503         }
3504     }
3505     return;
3506 }
3507
3508
3509 #-----------------------------------------
3510 # CHASSIS: Check voltage probes
3511 #-----------------------------------------
3512 sub check_volts {
3513     return if blacklisted('volt', 'all');
3514
3515     my $index    = undef;
3516     my $status   = undef;
3517     my $reading  = undef;
3518     my $location = undef;
3519     my @output = ();
3520
3521     if ($snmp) {
3522         my %volt_oid
3523           = (
3524              '1.3.6.1.4.1.674.10892.1.600.20.1.2.1'  => 'voltageProbeIndex',
3525              '1.3.6.1.4.1.674.10892.1.600.20.1.5.1'  => 'voltageProbeStatus',
3526              '1.3.6.1.4.1.674.10892.1.600.20.1.6.1'  => 'voltageProbeReading',
3527              '1.3.6.1.4.1.674.10892.1.600.20.1.8.1'  => 'voltageProbeLocationName',
3528              '1.3.6.1.4.1.674.10892.1.600.20.1.16.1' => 'voltageProbeDiscreteReading',
3529             );
3530
3531         my $voltageProbeTable = '1.3.6.1.4.1.674.10892.1.600.20.1';
3532         my $result = $snmp_session->get_table(-baseoid => $voltageProbeTable);
3533
3534         if (!defined $result) {
3535             printf "SNMP ERROR [voltage]: %s.\n", $snmp_session->error;
3536             $snmp_session->close;
3537             exit $E_UNKNOWN;
3538         }
3539
3540         @output = @{ get_snmp_output($result, \%volt_oid) };
3541     }
3542     else {
3543         @output = @{ run_omreport("$omopt_chassis volts") };
3544     }
3545
3546     my %volt_discrete_reading
3547       = (
3548          1 => 'Good',
3549          2 => 'Bad',
3550         );
3551
3552   VOLT:
3553     foreach my $out (@output) {
3554         if ($snmp) {
3555             $index    = $out->{voltageProbeIndex} - 1;
3556             $status   = $snmp_probestatus{$out->{voltageProbeStatus}};
3557             $reading  = exists $out->{voltageProbeReading}
3558               ? sprintf('%.3f V', $out->{voltageProbeReading}/1000)
3559                 : get_hashval($out->{voltageProbeDiscreteReading}, \%volt_discrete_reading);
3560             $location = $out->{voltageProbeLocationName};
3561         }
3562         else {
3563             $index    = $out->{'Index'};
3564             $status   = $out->{'Status'};
3565             $reading  = $out->{'Reading'};
3566             $location = $out->{'Probe Name'};
3567         }
3568
3569         next VOLT if blacklisted('volt', $index);
3570         $count{volt}++;
3571
3572         my $msg = sprintf 'Voltage sensor %d [%s] is %s',
3573           $index, $location, $reading;
3574         my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3575         report('chassis', $msg, $err, $index);
3576     }
3577     return;
3578 }
3579
3580
3581 #-----------------------------------------
3582 # CHASSIS: Check batteries
3583 #-----------------------------------------
3584 sub check_batteries {
3585     return if blacklisted('bp', 'all');
3586
3587     my $index    = undef;
3588     my $status   = undef;
3589     my $reading  = undef;
3590     my $location = undef;
3591     my @output = ();
3592
3593     if ($snmp) {
3594         my %bat_oid
3595           = (
3596              '1.3.6.1.4.1.674.10892.1.600.50.1.2.1' => 'batteryIndex',
3597              '1.3.6.1.4.1.674.10892.1.600.50.1.5.1' => 'batteryStatus',
3598              '1.3.6.1.4.1.674.10892.1.600.50.1.6.1' => 'batteryReading',
3599              '1.3.6.1.4.1.674.10892.1.600.50.1.7.1' => 'batteryLocationName',
3600             );
3601         my $result = undef;
3602         if ($opt{use_get_table}) {
3603             my $batteryTable = '1.3.6.1.4.1.674.10892.1.600.50.1';
3604             $result = $snmp_session->get_table(-baseoid => $batteryTable);
3605         }
3606         else {
3607             $result = $snmp_session->get_entries(-columns => [keys %bat_oid]);
3608         }
3609
3610         # No batteries is OK
3611         return 0 if !defined $result;
3612
3613         @output = @{ get_snmp_output($result, \%bat_oid) };
3614     }
3615     else {
3616         @output = @{ run_omreport("$omopt_chassis batteries") };
3617     }
3618
3619     my %bat_reading
3620       = (
3621          1 => 'Predictive Failure',
3622          2 => 'Failed',
3623          4 => 'Presence Detected',
3624         );
3625
3626   BATTERY:
3627     foreach my $out (@output) {
3628         if ($snmp) {
3629             $index    = $out->{batteryIndex} - 1;
3630             $status   = $snmp_status{$out->{batteryStatus}};
3631             $reading  = get_hashval($out->{batteryReading}, \%bat_reading);
3632             $location = $out->{batteryLocationName};
3633         }
3634         else {
3635             $index    = $out->{'Index'};
3636             $status   = $out->{'Status'};
3637             $reading  = $out->{'Reading'};
3638             $location = $out->{'Probe Name'};
3639         }
3640
3641         next BATTERY if blacklisted('bp', $index);
3642         $count{bat}++;
3643
3644         my $msg = sprintf 'Battery probe %d [%s] is %s',
3645           $index, $location, $reading;
3646         report('chassis', $msg, $status2nagios{$status}, $index);
3647     }
3648     return;
3649 }
3650
3651
3652 #-----------------------------------------
3653 # CHASSIS: Check amperage probes (power monitoring)
3654 #-----------------------------------------
3655 sub check_pwrmonitoring {
3656     return if blacklisted('amp', 'all');
3657
3658     my $index    = undef;
3659     my $status   = undef;
3660     my $reading  = undef;
3661     my $location = undef;
3662     my $max_crit = undef;
3663     my $max_warn = undef;
3664     my $unit     = undef;
3665     my $type     = undef;
3666     my @output = ();
3667
3668     if ($snmp) {
3669         my %amp_oid
3670           = (
3671              '1.3.6.1.4.1.674.10892.1.600.30.1.2.1'  => 'amperageProbeIndex',
3672              '1.3.6.1.4.1.674.10892.1.600.30.1.5.1'  => 'amperageProbeStatus',
3673              '1.3.6.1.4.1.674.10892.1.600.30.1.6.1'  => 'amperageProbeReading',
3674              '1.3.6.1.4.1.674.10892.1.600.30.1.7.1'  => 'amperageProbeType',
3675              '1.3.6.1.4.1.674.10892.1.600.30.1.8.1'  => 'amperageProbeLocationName',
3676              '1.3.6.1.4.1.674.10892.1.600.30.1.10.1' => 'amperageProbeUpperCriticalThreshold',
3677              '1.3.6.1.4.1.674.10892.1.600.30.1.11.1' => 'amperageProbeUpperNonCriticalThreshold',
3678              '1.3.6.1.4.1.674.10892.1.600.30.1.16.1' => 'amperageProbeDiscreteReading',
3679             );
3680         my $result = undef;
3681         if ($opt{use_get_table}) {
3682             my $amperageProbeTable = '1.3.6.1.4.1.674.10892.1.600.30.1';
3683             $result = $snmp_session->get_table(-baseoid => $amperageProbeTable);
3684         }
3685         else {
3686             $result = $snmp_session->get_entries(-columns => [keys %amp_oid]);
3687         }
3688
3689         # No pwrmonitoring is OK
3690         return 0 if !defined $result;
3691
3692         @output = @{ get_snmp_output($result, \%amp_oid) };
3693     }
3694     else {
3695         @output = @{ run_omreport("$omopt_chassis pwrmonitoring") };
3696     }
3697
3698     my %amp_type   # Amperage probe types
3699       = (
3700          1  => 'amperageProbeTypeIsOther',            # other than following values
3701          2  => 'amperageProbeTypeIsUnknown',          # unknown
3702          3  => 'amperageProbeTypeIs1Point5Volt',      # 1.5 amperage probe
3703          4  => 'amperageProbeTypeIs3Point3volt',      # 3.3 amperage probe
3704          5  => 'amperageProbeTypeIs5Volt',            # 5 amperage probe
3705          6  => 'amperageProbeTypeIsMinus5Volt',       # -5 amperage probe
3706          7  => 'amperageProbeTypeIs12Volt',           # 12 amperage probe
3707          8  => 'amperageProbeTypeIsMinus12Volt',      # -12 amperage probe
3708          9  => 'amperageProbeTypeIsIO',               # I/O probe
3709          10 => 'amperageProbeTypeIsCore',             # Core probe
3710          11 => 'amperageProbeTypeIsFLEA',             # FLEA (standby) probe
3711          12 => 'amperageProbeTypeIsBattery',          # Battery probe
3712          13 => 'amperageProbeTypeIsTerminator',       # SCSI Termination probe
3713          14 => 'amperageProbeTypeIs2Point5Volt',      # 2.5 amperage probe
3714          15 => 'amperageProbeTypeIsGTL',              # GTL (ground termination logic) probe
3715          16 => 'amperageProbeTypeIsDiscrete',         # amperage probe with discrete reading
3716          23 => 'amperageProbeTypeIsPowerSupplyAmps',  # Power Supply probe with reading in Amps
3717          24 => 'amperageProbeTypeIsPowerSupplyWatts', # Power Supply probe with reading in Watts
3718          25 => 'amperageProbeTypeIsSystemAmps',       # System probe with reading in Amps
3719          26 => 'amperageProbeTypeIsSystemWatts',      # System probe with reading in Watts
3720         );
3721
3722     my %amp_discrete
3723       = (
3724          1 => 'Good',
3725          2 => 'Bad',
3726         );
3727
3728     my %amp_unit
3729       = (
3730          'amperageProbeTypeIsPowerSupplyAmps'  => 'hA',  # tenths of Amps
3731          'amperageProbeTypeIsSystemAmps'       => 'hA',  # tenths of Amps
3732          'amperageProbeTypeIsPowerSupplyWatts' => 'W',   # Watts
3733          'amperageProbeTypeIsSystemWatts'      => 'W',   # Watts
3734          'amperageProbeTypeIsDiscrete'         => q{},   # discrete reading, no unit
3735         );
3736
3737   AMP:
3738     foreach my $out (@output) {
3739         if ($snmp) {
3740             $index    = $out->{amperageProbeIndex} - 1;
3741             $status   = $snmp_status{$out->{amperageProbeStatus}};
3742             $type     = get_hashval($out->{amperageProbeType}, \%amp_type);
3743             $reading  = $type eq 'amperageProbeTypeIsDiscrete'
3744               ? get_hashval($out->{amperageProbeDiscreteReading}, \%amp_discrete)
3745                 : $out->{amperageProbeReading};
3746             $location = $out->{amperageProbeLocationName};
3747             $max_crit = exists $out->{amperageProbeUpperCriticalThreshold}
3748               ? $out->{amperageProbeUpperCriticalThreshold} : 0;
3749             $max_warn = exists $out->{amperageProbeUpperNonCriticalThreshold}
3750               ? $out->{amperageProbeUpperNonCriticalThreshold} : 0;
3751             $unit     = exists $amp_unit{$amp_type{$out->{amperageProbeType}}}
3752               ? $amp_unit{$amp_type{$out->{amperageProbeType}}} : 'mA';
3753
3754             # workaround for broken probes
3755             if (!defined $reading) {
3756                 $type = 'amperageProbeTypeIsDiscrete';
3757                 $reading = '[N/A]';
3758                 $unit = q{};
3759             }
3760
3761             # calculate proper values and set unit for ampere probes
3762             if ($unit eq 'hA' and $type ne 'amperageProbeTypeIsDiscrete') {
3763                 $reading  /= 10;
3764                 $max_crit /= 10;
3765                 $max_warn /= 10;
3766                 $unit      = 'A';
3767             }
3768         }
3769         else {
3770             $index    = $out->{'Index'};
3771             next AMP if (!defined $index || $index !~ m/^\d+$/x);
3772             $status   = $out->{'Status'};
3773             $reading  = $out->{'Reading'};
3774             $location = $out->{'Probe Name'};
3775             $max_crit = $out->{'Failure Threshold'} ne '[N/A]'
3776               ? $out->{'Failure Threshold'} : 0;
3777             $max_warn = $out->{'Warning Threshold'} ne '[N/A]'
3778               ? $out->{'Warning Threshold'} : 0;
3779             $reading  =~ s{\A (\d+.*?)\s+([a-zA-Z]+) \s*\z}{$1}xms;
3780             $unit     = $2;
3781             $max_warn =~ s{\A (\d+.*?)\s+[a-zA-Z]+ \s*\z}{$1}xms;
3782             $max_crit =~ s{\A (\d+.*?)\s+[a-zA-Z]+ \s*\z}{$1}xms;
3783         }
3784
3785         next AMP if blacklisted('amp', $index);
3786         next AMP if $index !~ m{\A \d+ \z}xms;
3787         $count{amp}++;
3788
3789         if (defined $type and $type eq 'amperageProbeTypeIsDiscrete') {
3790             my $msg = sprintf 'Amperage probe %d [%s] is %s',
3791               $index, $location, $reading;
3792             report('chassis', $msg, $status2nagios{$status}, $index);
3793         }
3794         else {
3795             my $msg = sprintf 'Amperage probe %d [%s] reads %s %s',
3796               $index, $location, $reading, $unit;
3797             report('chassis', $msg, $status2nagios{$status}, $index);
3798         }
3799
3800         # Collect performance data
3801         if (defined $opt{perfdata}) {
3802             next AMP if $reading !~ m{\A \d+(\.\d+)? \z}xms; # discrete reading (not number)
3803             #next AMP if $type eq 'amperageProbeTypeIsDiscrete';
3804             my $label = join q{_}, 'pwr_mon', $index, lc $location;
3805             $label =~ s{\s}{_}gxms;
3806             push @perfdata, {
3807                              label => $label,
3808                              mini  => "p${index}" . lc $unit,
3809                              value => $reading,
3810                              warn  => $max_warn,
3811                              crit  => $max_crit,
3812                             };
3813         }
3814     }
3815
3816     # Collect EXTRA performance data not found at first run. This is a
3817     # rather ugly hack
3818     if (defined $opt{perfdata} && !$snmp) {
3819         my $found = 0;
3820         my $index = 0;
3821         my %used  = ();
3822
3823         # find used indexes
3824         foreach (@perfdata) {
3825             if ($_->{label} =~ m/\A pwr_mon_(\d+)/xms) {
3826                 $used{$1} = 1;
3827             }
3828         }
3829
3830       AMP2:
3831         foreach my $line (@{ run_command("$omreport $omopt_chassis pwrmonitoring -fmt ssv") }) {
3832             chop $line;
3833             if ($line eq 'Location;Reading') {
3834                 $found = 1;
3835                 next AMP2;
3836             }
3837             if ($line eq q{}) {
3838                 $found = 0;
3839                 next AMP2;
3840             }
3841             if ($found and $line =~ m/\A ([^;]+?) ; (\d*\.\d+) \s [AW] \z/xms) {
3842                 my $aname = lc $1;
3843                 my $aval = $2;
3844                 $aname =~ s{\s}{_}gxms;
3845
3846                 # don't use an existing index
3847                 while (exists $used{$index}) { ++$index; }
3848
3849                 push @perfdata, {
3850                                  label => "pwr_mon_${index}_${aname}",
3851                                  mini  => "p${index}a",
3852                                  value => $aval,
3853                                  warn  => 0,
3854                                  crit  => 0,
3855                                 };
3856                 ++$index;
3857             }
3858         }
3859     }
3860
3861     return;
3862 }
3863
3864
3865 #-----------------------------------------
3866 # CHASSIS: Check intrusion
3867 #-----------------------------------------
3868 sub check_intrusion {
3869     return if blacklisted('intr', 'all');
3870
3871     my $index    = undef;
3872     my $status   = undef;
3873     my $reading  = undef;
3874     my @output = ();
3875
3876     if ($snmp) {
3877         my %int_oid
3878           = (
3879              '1.3.6.1.4.1.674.10892.1.300.70.1.2.1' => 'intrusionIndex',
3880              '1.3.6.1.4.1.674.10892.1.300.70.1.5.1' => 'intrusionStatus',
3881              '1.3.6.1.4.1.674.10892.1.300.70.1.6.1' => 'intrusionReading',
3882             );
3883         my $result = undef;
3884         if ($opt{use_get_table}) {
3885             my $intrusionTable = '1.3.6.1.4.1.674.10892.1.300.70.1';
3886             $result = $snmp_session->get_table(-baseoid => $intrusionTable);
3887         }
3888         else {
3889             $result = $snmp_session->get_entries(-columns => [keys %int_oid]);
3890         }
3891
3892         # No intrusion is OK
3893         return 0 if !defined $result;
3894
3895         @output = @{ get_snmp_output($result, \%int_oid) };
3896     }
3897     else {
3898         @output = @{ run_omreport("$omopt_chassis intrusion") };
3899     }
3900
3901     my %int_reading
3902       = (
3903          1 => 'Not Breached',          # chassis not breached and no uncleared breaches
3904          2 => 'Breached',              # chassis currently breached
3905          3 => 'Breached Prior',        # chassis breached prior to boot and has not been cleared
3906          4 => 'Breach Sensor Failure', # intrusion sensor has failed
3907         );
3908
3909   INTRUSION:
3910     foreach my $out (@output) {
3911         if ($snmp) {
3912             $index    = $out->{intrusionIndex} - 1;
3913             $status   = $snmp_status{$out->{intrusionStatus}};
3914             $reading  = get_hashval($out->{intrusionReading}, \%int_reading);
3915         }
3916         else {
3917             $index    = $out->{'Index'};
3918             $status   = $out->{'Status'};
3919             $reading  = $out->{'State'};
3920         }
3921
3922         next INTRUSION if blacklisted('intr', $index);
3923         $count{intr}++;
3924
3925         if ($status ne 'Ok') {
3926             my $msg = sprintf 'Chassis intrusion %d detected: %s',
3927               $index, $reading;
3928             report('chassis', $msg, $E_WARNING, $index);
3929         }
3930         # Ok
3931         else {
3932             my $msg = sprintf 'Chassis intrusion %d detection: %s (%s)',
3933               $index, $status, $reading;
3934             report('chassis', $msg, $E_OK, $index);
3935         }
3936     }
3937     return;
3938 }
3939
3940
3941 #-----------------------------------------
3942 # CHASSIS: Check alert log
3943 #-----------------------------------------
3944 sub check_alertlog {
3945     return if $snmp; # Not supported with SNMP
3946
3947     my @output = @{ run_omreport("$omopt_system alertlog") };
3948     foreach my $out (@output) {
3949         ++$count{alert}{$out->{Severity}};
3950     }
3951
3952     # Create error messages and set exit value if appropriate
3953     my $err = 0;
3954     if ($count{alert}{'Critical'} > 0)        { $err = $E_CRITICAL; }
3955     elsif ($count{alert}{'Non-Critical'} > 0) { $err = $E_WARNING;  }
3956
3957     my $msg = sprintf 'Alert log content: %d critical, %d non-critical, %d ok',
3958       $count{alert}{'Critical'}, $count{alert}{'Non-Critical'}, $count{alert}{'Ok'};
3959     report('other', $msg, $err);
3960
3961     return;
3962 }
3963
3964 #-----------------------------------------
3965 # CHASSIS: Check ESM log overall health
3966 #-----------------------------------------
3967 sub check_esmlog_health {
3968     my $health = 'Ok';
3969
3970     if ($snmp) {
3971         my $systemStateEventLogStatus = '1.3.6.1.4.1.674.10892.1.200.10.1.41.1';
3972         my $result = $snmp_session->get_request(-varbindlist => [$systemStateEventLogStatus]);
3973         if (!defined $result) {
3974             my $msg = sprintf 'SNMP ERROR [esmhealth]: %s',
3975               $snmp_session->error;
3976             report('other', $msg, $E_UNKNOWN);
3977         }
3978         $health = $snmp_status{$result->{$systemStateEventLogStatus}};
3979     }
3980     else {
3981         foreach (@{ run_command("$omreport $omopt_system esmlog -fmt ssv") }) {
3982             if (m/\A Health;(.+) \z/xms) {
3983                 $health = $1;
3984                 chop $health;
3985                 last;
3986             }
3987         }
3988     }
3989
3990     # If the overall health of the ESM log is other than "Ok", the
3991     # fill grade of the log is more than 80% and the log should be
3992     # cleared
3993     if ($health eq 'Ok') {
3994         my $msg = sprintf 'ESM log health is Ok (less than 80%% full)';
3995         report('other', $msg, $E_OK);
3996     }
3997     elsif ($health eq 'Critical') {
3998         my $msg = sprintf 'ESM log is 100%% full';
3999         report('other', $msg, $status2nagios{$health});
4000     }
4001     else {
4002         my $msg = sprintf 'ESM log is more than 80%% full';
4003         report('other', $msg, $status2nagios{$health});
4004     }
4005
4006     return;
4007 }
4008
4009 #-----------------------------------------
4010 # CHASSIS: Check ESM log
4011 #-----------------------------------------
4012 sub check_esmlog {
4013     my @output = ();
4014
4015     if ($snmp) {
4016         my %esm_oid
4017           = (
4018              '1.3.6.1.4.1.674.10892.1.300.40.1.7.1'  => 'eventLogSeverityStatus',
4019             );
4020         my $result = $snmp_session->get_entries(-columns => [keys %esm_oid]);
4021
4022         # No entries is OK
4023         return if !defined $result;
4024
4025         @output = @{ get_snmp_output($result, \%esm_oid) };
4026         foreach my $out (@output) {
4027             ++$count{esm}{$snmp_status{$out->{eventLogSeverityStatus}}};
4028         }
4029     }
4030     else {
4031         @output = @{ run_omreport("$omopt_system esmlog") };
4032         foreach my $out (@output) {
4033             ++$count{esm}{$out->{Severity}};
4034         }
4035     }
4036
4037     # Create error messages and set exit value if appropriate
4038     my $err = 0;
4039     if ($count{esm}{'Critical'} > 0)        { $err = $E_CRITICAL; }
4040     elsif ($count{esm}{'Non-Critical'} > 0) { $err = $E_WARNING;  }
4041
4042     my $msg = sprintf 'ESM log content: %d critical, %d non-critical, %d ok',
4043       $count{esm}{'Critical'}, $count{esm}{'Non-Critical'}, $count{esm}{'Ok'};
4044     report('other', $msg, $err);
4045
4046     return;
4047 }
4048
4049 #
4050 # Handy function for checking all storage components
4051 #
4052 sub check_storage {
4053     check_controllers();
4054     check_physical_disks();
4055     check_virtual_disks();
4056     check_cache_battery();
4057     check_connectors();
4058     check_enclosures();
4059     check_enclosure_fans();
4060     check_enclosure_pwr();
4061     check_enclosure_temp();
4062     check_enclosure_emms();
4063     return;
4064 }
4065
4066
4067
4068 #---------------------------------------------------------------------
4069 # Info functions
4070 #---------------------------------------------------------------------
4071
4072 #
4073 # Fetch output from 'omreport chassis info', put in sysinfo hash
4074 #
4075 sub get_omreport_chassis_info {
4076     if (open my $INFO, '-|', "$omreport $omopt_chassis info -fmt ssv") {
4077         my @lines = <$INFO>;
4078         close $INFO;
4079         foreach (@lines) {
4080             next if !m/\A (Chassis\sModel|Chassis\sService\sTag|Model|Service\sTag|System\sRevision)/xms;
4081             my ($key, $val) = split /;/xms;
4082             $key =~ s{\s+\z}{}xms; # remove trailing whitespace
4083             $val =~ s{\s+\z}{}xms; # remove trailing whitespace
4084             if ($key eq 'Chassis Model' or $key eq 'Model') {
4085                 $sysinfo{model}  = $val;
4086             }
4087             if ($key eq 'Chassis Service Tag' or $key eq 'Service Tag') {
4088                 $sysinfo{serial} = $val;
4089             }
4090             if ($key eq 'System Revision') {
4091                 $sysinfo{rev} = q{ } . $val;
4092             }
4093         }
4094     }
4095     return;
4096 }
4097
4098 #
4099 # Fetch output from 'omreport chassis bios', put in sysinfo hash
4100 #
4101 sub get_omreport_chassis_bios {
4102     if (open my $BIOS, '-|', "$omreport $omopt_chassis bios -fmt ssv") {
4103         my @lines = <$BIOS>;
4104         close $BIOS;
4105         foreach (@lines) {
4106             next if !m/;/xms;
4107             my ($key, $val) = split /;/xms;
4108             $key =~ s{\s+\z}{}xms; # remove trailing whitespace
4109             $val =~ s{\s+\z}{}xms; # remove trailing whitespace
4110             $sysinfo{bios}     = $val if $key eq 'Version';
4111             $sysinfo{biosdate} = $val if $key eq 'Release Date';
4112         }
4113     }
4114     return;
4115 }
4116
4117 #
4118 # Fetch output from 'omreport system operatingsystem', put in sysinfo hash
4119 #
4120 sub get_omreport_system_operatingsystem {
4121     if (open my $VER, '-|', "$omreport $omopt_system operatingsystem -fmt ssv") {
4122         my @lines = <$VER>;
4123         close $VER;
4124         foreach (@lines) {
4125             next if !m/;/xms;
4126             my ($key, $val) = split /;/xms;
4127             $key =~ s{\s+\z}{}xms; # remove trailing whitespace
4128             $val =~ s{\s+\z}{}xms; # remove trailing whitespace
4129             if ($key eq 'Operating System') {
4130                 $sysinfo{osname} = $val;
4131             }
4132             elsif ($key eq 'Operating System Version') {
4133                 $sysinfo{osver}  = $val;
4134             }
4135         }
4136     }
4137     return;
4138 }
4139
4140 #
4141 # Fetch output from 'omreport about', put in sysinfo hash
4142 #
4143 sub get_omreport_about {
4144     if (open my $OM, '-|', "$omreport about -fmt ssv") {
4145         my @lines = <$OM>;
4146         close $OM;
4147         foreach (@lines) {
4148             if (m/\A Version;(.+) \z/xms) {
4149                 $sysinfo{om} = $1;
4150                 chomp $sysinfo{om};
4151             }
4152         }
4153     }
4154     return;
4155 }
4156
4157 #
4158 # Fetch chassis info via SNMP, put in sysinfo hash
4159 #
4160 sub get_snmp_chassis_info {
4161     my %chassis_oid
4162       = (
4163          '1.3.6.1.4.1.674.10892.1.300.10.1.9.1'  => 'chassisModelName',
4164          '1.3.6.1.4.1.674.10892.1.300.10.1.11.1' => 'chassisServiceTagName',
4165          '1.3.6.1.4.1.674.10892.1.300.10.1.48.1' => 'chassisSystemRevisionName',
4166         );
4167
4168     my $chassisInformationTable = '1.3.6.1.4.1.674.10892.1.300.10.1';
4169     my $result = $snmp_session->get_table(-baseoid => $chassisInformationTable);
4170
4171     if (defined $result) {
4172         foreach my $oid (keys %{ $result }) {
4173             if (exists $chassis_oid{$oid} and $chassis_oid{$oid} eq 'chassisModelName') {
4174                 $sysinfo{model} = $result->{$oid};
4175                 $sysinfo{model} =~ s{\s+\z}{}xms; # remove trailing whitespace
4176             }
4177             elsif (exists $chassis_oid{$oid} and $chassis_oid{$oid} eq 'chassisServiceTagName') {
4178                 $sysinfo{serial} = $result->{$oid};
4179             }
4180             elsif (exists $chassis_oid{$oid} and $chassis_oid{$oid} eq 'chassisSystemRevisionName') {
4181                 $sysinfo{rev} = q{ } . $result->{$oid};
4182             }
4183         }
4184     }
4185     else {
4186         my $msg = sprintf 'SNMP ERROR getting chassis info: %s',
4187           $snmp_session->error;
4188         report('other', $msg, $E_UNKNOWN);
4189     }
4190     return;
4191 }
4192
4193 #
4194 # Fetch BIOS info via SNMP, put in sysinfo hash
4195 #
4196 sub get_snmp_chassis_bios {
4197     my %bios_oid
4198       = (
4199          '1.3.6.1.4.1.674.10892.1.300.50.1.7.1.1' => 'systemBIOSReleaseDateName',
4200          '1.3.6.1.4.1.674.10892.1.300.50.1.8.1.1' => 'systemBIOSVersionName',
4201         );
4202
4203     my $systemBIOSTable = '1.3.6.1.4.1.674.10892.1.300.50.1';
4204     my $result = $snmp_session->get_table(-baseoid => $systemBIOSTable);
4205
4206     if (defined $result) {
4207         foreach my $oid (keys %{ $result }) {
4208             if (exists $bios_oid{$oid} and $bios_oid{$oid} eq 'systemBIOSReleaseDateName') {
4209                 $sysinfo{biosdate} = $result->{$oid};
4210                 $sysinfo{biosdate} =~ s{\A (\d{4})(\d{2})(\d{2}).*}{$2/$3/$1}xms;
4211             }
4212             elsif (exists $bios_oid{$oid} and $bios_oid{$oid} eq 'systemBIOSVersionName') {
4213                 $sysinfo{bios} = $result->{$oid};
4214             }
4215         }
4216     }
4217     else {
4218         my $msg = sprintf 'SNMP ERROR getting BIOS info: %s',
4219           $snmp_session->error;
4220         report('other', $msg, $E_UNKNOWN);
4221     }
4222     return;
4223 }
4224
4225 #
4226 # Fetch OS info via SNMP, put in sysinfo hash
4227 #
4228 sub get_snmp_system_operatingsystem {
4229     my %os_oid
4230       = (
4231          '1.3.6.1.4.1.674.10892.1.400.10.1.6.1' => 'operatingSystemOperatingSystemName',
4232          '1.3.6.1.4.1.674.10892.1.400.10.1.7.1' => 'operatingSystemOperatingSystemVersionName',
4233         );
4234
4235     my $operatingSystemTable = '1.3.6.1.4.1.674.10892.1.400.10.1';
4236     my $result = $snmp_session->get_table(-baseoid => $operatingSystemTable);
4237
4238     if (defined $result) {
4239         foreach my $oid (keys %{ $result }) {
4240             if (exists $os_oid{$oid} and $os_oid{$oid} eq 'operatingSystemOperatingSystemName') {
4241                 $sysinfo{osname} = ($result->{$oid});
4242             }
4243             elsif (exists $os_oid{$oid} and $os_oid{$oid} eq 'operatingSystemOperatingSystemVersionName') {
4244                 $sysinfo{osver} = $result->{$oid};
4245             }
4246         }
4247     }
4248     else {
4249         my $msg = sprintf 'SNMP ERROR getting OS info: %s',
4250           $snmp_session->error;
4251         report('other', $msg, $E_UNKNOWN);
4252     }
4253     return;
4254 }
4255
4256 #
4257 # Fetch OMSA version via SNMP, put in sysinfo hash
4258 #
4259 sub get_snmp_about {
4260     # systemManagementSoftwareGlobalVersionName
4261     my $oid = '1.3.6.1.4.1.674.10892.1.100.10.0';
4262     my $result = $snmp_session->get_request(-varbindlist => [$oid]);
4263
4264     if (defined $result) {
4265         $sysinfo{om} = exists $result->{$oid} && $result->{$oid} ne q{}
4266           ? $result->{$oid} : 'unknown';
4267     }
4268     else {
4269         my $msg = sprintf 'SNMP ERROR: Getting OMSA version failed: %s', $snmp_session->error;
4270         report('other', $msg, $E_UNKNOWN);
4271     }
4272     return;
4273 }
4274
4275 #
4276 # Collects some information about the system
4277 #
4278 sub get_sysinfo
4279 {
4280     # Get system model and serial number
4281     $snmp ? get_snmp_chassis_info() : get_omreport_chassis_info();
4282
4283     # Get BIOS information. Only if needed
4284     if ( $opt{okinfo} >= 1
4285          or $opt{debug}
4286          or (defined $opt{postmsg} and $opt{postmsg} =~ m/[%][bd]/xms) ) {
4287         $snmp ? get_snmp_chassis_bios() : get_omreport_chassis_bios();
4288     }
4289
4290     # Get OMSA information. Only if needed
4291     if ($opt{okinfo} >= 3 or $opt{debug}) {
4292         $snmp ? get_snmp_about() : get_omreport_about();
4293     }
4294
4295     # Return now if debug
4296     return if $opt{debug};
4297
4298     # Get OS information. Only if needed
4299     if (defined $opt{postmsg} and $opt{postmsg} =~ m/[%][or]/xms) {
4300         $snmp ? get_snmp_system_operatingsystem() : get_omreport_system_operatingsystem();
4301     }
4302
4303     return;
4304 }
4305
4306
4307 # Helper function for running omreport when the results are strictly
4308 # name=value pairs.
4309 sub run_omreport_info {
4310     my $command = shift;
4311     my %output  = ();
4312     my @keys    = ();
4313
4314     # Run omreport and fetch output
4315     my $rawtext = slurp_command("$omreport $command -fmt ssv 2>&1");
4316
4317     # Parse output, store in array
4318     for ((split /\n/xms, $rawtext)) {
4319         if (m/\A Error/xms) {
4320             my $msg = "Problem running 'omreport $command': $_";
4321             report('other', $msg, $E_UNKNOWN);
4322         }
4323         next if !m/;/xms;  # ignore lines with less than two fields
4324         my @vals = split m/;/xms;
4325         $output{$vals[0]} = $vals[1];
4326     }
4327
4328     # Finally, return the collected information
4329     return \%output;
4330 }
4331
4332 # Get various firmware information (BMC, RAC)
4333 sub get_firmware_info {
4334     my @snmp_output = ();
4335     my %nrpe_output = ();
4336
4337     if ($snmp) {
4338         my %fw_oid
4339           = (
4340              '1.3.6.1.4.1.674.10892.1.300.60.1.7.1'  => 'firmwareType',
4341              '1.3.6.1.4.1.674.10892.1.300.60.1.8.1'  => 'firmwareTypeName',
4342              '1.3.6.1.4.1.674.10892.1.300.60.1.11.1' => 'firmwareVersionName',
4343             );
4344
4345         my $firmwareTable = '1.3.6.1.4.1.674.10892.1.300.60.1';
4346         my $result = $snmp_session->get_table(-baseoid => $firmwareTable);
4347
4348         # Some don't have this OID, this is ok
4349         if (!defined $result) {
4350             return;
4351         }
4352
4353         @snmp_output = @{ get_snmp_output($result, \%fw_oid) };
4354     }
4355     else {
4356         %nrpe_output = %{ run_omreport_info("$omopt_chassis info") };
4357     }
4358
4359     my %fw_type  # Firmware types
4360       = (
4361          1  => 'other',                              # other than following values
4362          2  => 'unknown',                            # unknown
4363          3  => 'systemBIOS',                         # System BIOS
4364          4  => 'embeddedSystemManagementController', # Embedded System Management Controller
4365          5  => 'powerSupplyParallelingBoard',        # Power Supply Paralleling Board
4366          6  => 'systemBackPlane',                    # System (Primary) Backplane
4367          7  => 'powerVault2XXSKernel',               # PowerVault 2XXS Kernel
4368          8  => 'powerVault2XXSApplication',          # PowerVault 2XXS Application
4369          9  => 'frontPanel',                         # Front Panel Controller
4370          10 => 'baseboardManagementController',      # Baseboard Management Controller
4371          11 => 'hotPlugPCI',                         # Hot Plug PCI Controller
4372          12 => 'sensorData',                         # Sensor Data Records
4373          13 => 'peripheralBay',                      # Peripheral Bay Backplane
4374          14 => 'secondaryBackPlane',                 # Secondary Backplane for ESM 2 systems
4375          15 => 'secondaryBackPlaneESM3And4',         # Secondary Backplane for ESM 3 and 4 systems
4376          16 => 'rac',                                # Remote Access Controller
4377          17 => 'imc'                                 # Integrated Management Controller
4378         );
4379
4380
4381     if ($snmp) {
4382         foreach my $out (@snmp_output) {
4383             if ($fw_type{$out->{firmwareType}} eq 'baseboardManagementController') {
4384                 $sysinfo{'bmc'} = 1;
4385                 $sysinfo{'bmc_fw'} = $out->{firmwareVersionName};
4386             }
4387             elsif ($fw_type{$out->{firmwareType}} =~ m{\A rac|imc \z}xms) {
4388                 my $name = $out->{firmwareTypeName}; $name =~ s/\s//gxms;
4389                 $sysinfo{'rac'} = 1;
4390                 $sysinfo{'rac_name'} = $name;
4391                 $sysinfo{'rac_fw'} = $out->{firmwareVersionName};
4392             }
4393         }
4394     }
4395     else {
4396         foreach my $key (keys %nrpe_output) {
4397             next if !defined $nrpe_output{$key};
4398             if ($key eq 'BMC Version' or $key eq 'Baseboard Management Controller Version') {
4399                 $sysinfo{'bmc'} = 1;
4400                 $sysinfo{'bmc_fw'} = $nrpe_output{$key};
4401             }
4402             elsif ($key =~ m{\A (i?DRAC)\s*(\d?)\s+Version}xms) {
4403                 my $name = "$1$2";
4404                 $sysinfo{'rac'} = 1;
4405                 $sysinfo{'rac_fw'} = $nrpe_output{$key};
4406                 $sysinfo{'rac_name'} = $name;
4407             }
4408         }
4409     }
4410
4411     return;
4412 }
4413
4414
4415
4416 #=====================================================================
4417 # Main program
4418 #=====================================================================
4419
4420 # Here we do the actual checking of components
4421 # Check global status if applicable
4422 if ($global) {
4423     $globalstatus = check_global();
4424 }
4425
4426 # Do multiple selected checks
4427 if ($check{storage})     { check_storage();       }
4428 if ($check{memory})      { check_memory();        }
4429 if ($check{fans})        { check_fans();          }
4430 if ($check{power})       { check_powersupplies(); }
4431 if ($check{temp})        { check_temperatures();  }
4432 if ($check{cpu})         { check_processors();    }
4433 if ($check{voltage})     { check_volts();         }
4434 if ($check{batteries})   { check_batteries();     }
4435 if ($check{amperage})    { check_pwrmonitoring(); }
4436 if ($check{intrusion})   { check_intrusion();     }
4437 if ($check{alertlog})    { check_alertlog();      }
4438 if ($check{esmlog})      { check_esmlog();        }
4439 if ($check{esmhealth})   { check_esmlog_health(); }
4440
4441
4442 #---------------------------------------------------------------------
4443 # Finish up
4444 #---------------------------------------------------------------------
4445
4446 # Counter variable
4447 %nagios_alert_count
4448   = (
4449      'OK'       => 0,
4450      'WARNING'  => 0,
4451      'CRITICAL' => 0,
4452      'UNKNOWN'  => 0,
4453     );
4454
4455 # Get system information
4456 get_sysinfo();
4457
4458 # Get firmware info if requested via option
4459 if ($opt{okinfo} >= 1) {
4460     get_firmware_info();
4461 }
4462
4463 # Close SNMP session
4464 if ($snmp) {
4465     $snmp_session->close;
4466 }
4467
4468 # Print messages
4469 if ($opt{debug}) {
4470     print "   System:      $sysinfo{model}$sysinfo{rev}\n";
4471     print "   ServiceTag:  $sysinfo{serial}";
4472     print q{ } x (25 - length $sysinfo{serial}), "OMSA version:    $sysinfo{om}\n";
4473     print "   BIOS/date:   $sysinfo{bios} $sysinfo{biosdate}";
4474     print q{ } x (25 - length "$sysinfo{bios} $sysinfo{biosdate}"), "Plugin version:  $VERSION\n";
4475     if ($#report_storage >= 0) {
4476         print "-----------------------------------------------------------------------------\n";
4477         print "   Storage Components                                                        \n";
4478         print "=============================================================================\n";
4479         print "  STATE  |    ID    |  MESSAGE TEXT                                          \n";
4480         print "---------+----------+--------------------------------------------------------\n";
4481         foreach (@report_storage) {
4482             my ($msg, $level, $nexus) = @{$_};
4483             print q{ } x (8 - length $reverse_exitcode{$level}) . "$reverse_exitcode{$level} | "
4484               . q{ } x (8 - length $nexus) . "$nexus | $msg\n";
4485             $nagios_alert_count{$reverse_exitcode{$level}}++;
4486         }
4487     }
4488     if ($#report_chassis >= 0) {
4489         print "-----------------------------------------------------------------------------\n";
4490         print "   Chassis Components                                                        \n";
4491         print "=============================================================================\n";
4492         print "  STATE  |  ID  |  MESSAGE TEXT                                              \n";
4493         print "---------+------+------------------------------------------------------------\n";
4494         foreach (@report_chassis) {
4495             my ($msg, $level, $nexus) = @{$_};
4496             print q{ } x (8 - length $reverse_exitcode{$level}) . "$reverse_exitcode{$level} | "
4497               . q{ } x (4 - length $nexus) . "$nexus | $msg\n";
4498             $nagios_alert_count{$reverse_exitcode{$level}}++;
4499         }
4500     }
4501     if ($#report_other >= 0) {
4502         print "-----------------------------------------------------------------------------\n";
4503         print "   Other messages                                                            \n";
4504         print "=============================================================================\n";
4505         print "  STATE  |  MESSAGE TEXT                                                     \n";
4506         print "---------+-------------------------------------------------------------------\n";
4507         foreach (@report_other) {
4508             my ($msg, $level, $nexus) = @{$_};
4509             print q{ } x (8 - length $reverse_exitcode{$level}) . "$reverse_exitcode{$level} | $msg\n";
4510             $nagios_alert_count{$reverse_exitcode{$level}}++;
4511         }
4512     }
4513 }
4514 else {
4515     my $c = 0;  # counter to determine linebreaks
4516
4517     # Run through each message, sorted by severity level
4518   ALERT:
4519     foreach (sort {$a->[1] < $b->[1]} (@report_storage, @report_chassis, @report_other)) {
4520         my ($msg, $level, $nexus) = @{ $_ };
4521         next ALERT if $level == $E_OK;
4522
4523         if (defined $opt{only}) {
4524             # If user wants only critical alerts
4525             next ALERT if ($opt{only} eq 'critical' and $level == $E_WARNING);
4526
4527             # If user wants only warning alerts
4528             next ALERT if ($opt{only} eq 'warning' and $level == $E_CRITICAL);
4529         }
4530
4531         # Prefix with service tag if specified with option '-i|--info'
4532         if ($opt{info}) {
4533             if (defined $opt{htmlinfo}) {
4534                 $msg = '[<a href="' . warranty_url($sysinfo{serial})
4535                   . "\">$sysinfo{serial}</a>] " . $msg;
4536             }
4537             else {
4538                 $msg = "[$sysinfo{serial}] " . $msg;
4539             }
4540         }
4541
4542         # Prefix with nagios level if specified with option '--state'
4543         $msg = $reverse_exitcode{$level} . ": $msg" if $opt{state};
4544
4545         # Prefix with one-letter nagios level if specified with option '--short-state'
4546         $msg = (substr $reverse_exitcode{$level}, 0, 1) . ": $msg" if $opt{shortstate};
4547
4548         ($c++ == 0) ? print $msg : print $linebreak, $msg;
4549
4550         $nagios_alert_count{$reverse_exitcode{$level}}++;
4551     }
4552 }
4553
4554 # Determine our exit code
4555 $exit_code = $E_OK;
4556 $exit_code = $E_UNKNOWN  if $nagios_alert_count{'UNKNOWN'} > 0;
4557 $exit_code = $E_WARNING  if $nagios_alert_count{'WARNING'} > 0;
4558 $exit_code = $E_CRITICAL if $nagios_alert_count{'CRITICAL'} > 0;
4559
4560 # Global status via SNMP.. extra safety check
4561 if ($globalstatus != $E_OK && $exit_code == $E_OK && !defined $opt{only}) {
4562     print "OOPS! Something is wrong with this server, but I don't know what. ";
4563     print "The global system health status is $reverse_exitcode{$globalstatus}, ";
4564     print "but every component check is OK. This may be a bug in the Nagios plugin, ";
4565     print "please file a bug report.\n";
4566     exit $E_UNKNOWN;
4567 }
4568
4569 # Print OK message
4570 if ($exit_code == $E_OK && defined $opt{only} && $opt{only} !~ m{\A critical|warning|chassis \z}xms && !$opt{debug}) {
4571     my %okmsg
4572       = ( 'storage'     => "STORAGE OK - $count{pdisk} physical drives, $count{vdisk} logical drives",
4573           'fans'        => $count{fan} == 0 && $blade ? 'OK - blade system with no fan probes' : "FANS OK - $count{fan} fan probes checked",
4574           'temp'        => "TEMPERATURES OK - $count{temp} temperature probes checked",
4575           'memory'      => "MEMORY OK - $count{dimm} memory modules, $count{mem} MB total memory",
4576           'power'       => $count{power} == 0 ? 'OK - no instrumented power supplies found' : "POWER OK - $count{power} power supplies checked",
4577           'cpu'         => "PROCESSORS OK - $count{cpu} processors checked",
4578           'voltage'     => "VOLTAGE OK - $count{volt} voltage probes checked",
4579           'batteries'   => $count{bat} == 0 ? 'OK - no batteries found' : "BATTERIES OK - $count{bat} batteries checked",
4580           'amperage'    => $count{amp} == 0 ? 'OK - no power monitoring probes found' : "AMPERAGE OK - $count{amp} amperage (power monitoring) probes checked",
4581           'intrusion'   => $count{intr} == 0 ? 'OK - no intrusion detection probes found' : "INTRUSION OK - $count{intr} intrusion detection probes checked",
4582           'alertlog'    => $snmp ? 'OK - not supported via snmp' : "OK - Alert Log content: $count{alert}{Ok} ok, $count{alert}{'Non-Critical'} warning and $count{alert}{Critical} critical",
4583           'esmlog'      => "OK - ESM Log content: $count{esm}{Ok} ok, $count{esm}{'Non-Critical'} warning and $count{esm}{Critical} critical",
4584           'esmhealth'   => "ESM LOG OK - less than 80% used",
4585         );
4586
4587     print $okmsg{$opt{only}};
4588 }
4589 elsif ($exit_code == $E_OK && !$opt{debug}) {
4590     if (defined $opt{htmlinfo}) {
4591         printf q{OK - System: '<a href="%s">%s%s</a>', SN: '<a href="%s">%s</a>'},
4592           documentation_url($sysinfo{model}), $sysinfo{model}, $sysinfo{rev},
4593             warranty_url($sysinfo{serial}), $sysinfo{serial};
4594     }
4595     else {
4596         printf q{OK - System: '%s%s', SN: '%s'},
4597           $sysinfo{model}, $sysinfo{rev}, $sysinfo{serial};
4598     }
4599
4600     if ($check{memory}) {
4601         my $unit = 'MB';
4602         if ($count{mem} >= 1024) {
4603             $count{mem} /= 1024;
4604             $unit = 'GB';
4605         }
4606         printf ', %d %s ram (%d dimms)', $count{mem}, $unit, $count{dimm};
4607     }
4608     else {
4609         print ', not checking memory';
4610     }
4611
4612     if ($check{storage}) {
4613         printf ', %d logical drives, %d physical drives',
4614           $count{vdisk}, $count{pdisk};
4615     }
4616     else {
4617         print ', not checking storage';
4618     }
4619
4620     if ($opt{okinfo} >= 1) {
4621         print $linebreak;
4622         printf q{----- BIOS='%s %s'}, $sysinfo{bios}, $sysinfo{biosdate};
4623
4624         if ($sysinfo{rac}) {
4625             printf q{, %s='%s'}, $sysinfo{rac_name}, $sysinfo{rac_fw};
4626         }
4627         if ($sysinfo{bmc}) {
4628             printf q{, BMC='%s'}, $sysinfo{bmc_fw};
4629         }
4630     }
4631
4632     if ($opt{okinfo} >= 2) {
4633         if ($check{storage}) {
4634             my @storageprint = ();
4635             foreach my $id (sort keys %{ $sysinfo{controller} }) {
4636                 chomp $sysinfo{controller}{$id}{driver};
4637                 my $msg = sprintf q{----- Ctrl %s [%s]: Fw='%s', Dr='%s'},
4638                   $sysinfo{controller}{$id}{id}, $sysinfo{controller}{$id}{name},
4639                     $sysinfo{controller}{$id}{firmware}, $sysinfo{controller}{$id}{driver};
4640                 if (defined $sysinfo{controller}{$id}{storport}) {
4641                     $msg .= sprintf q{, Storport: '%s'}, $sysinfo{controller}{$id}{storport};
4642                 }
4643                 push @storageprint, $msg;
4644             }
4645             foreach my $id (sort keys %{ $sysinfo{enclosure} }) {
4646                 push @storageprint, sprintf q{----- Encl %s [%s]: Fw='%s'},
4647                   $sysinfo{enclosure}{$id}->{id}, $sysinfo{enclosure}{$id}->{name},
4648                     $sysinfo{enclosure}{$id}->{firmware};
4649             }
4650
4651             # print stuff
4652             foreach my $line (@storageprint) {
4653                 print $linebreak, $line;
4654             }
4655         }
4656     }
4657
4658     if ($opt{okinfo} >= 3) {
4659         print "$linebreak----- OpenManage Server Administrator (OMSA) version: '$sysinfo{om}'";
4660     }
4661
4662 }
4663 else {
4664     if ($opt{extinfo}) {
4665         print $linebreak;
4666         if (defined $opt{htmlinfo}) {
4667             printf '------ SYSTEM: <a href="%s">%s%s</a>, SN: <a href="%s">%s</a>',
4668               documentation_url($sysinfo{model}), $sysinfo{model}, $sysinfo{rev},
4669                 warranty_url($sysinfo{serial}), $sysinfo{serial};
4670         }
4671         else {
4672             printf '------ SYSTEM: %s%s, SN: %s',
4673               $sysinfo{model}, $sysinfo{rev}, $sysinfo{serial};
4674         }
4675     }
4676     if (defined $opt{postmsg}) {
4677         my $post = undef;
4678         if (-f $opt{postmsg}) {
4679             open my $POST, '<', $opt{postmsg}
4680               or ( print $linebreak
4681                    and print "ERROR: Couldn't open post message file $opt{postmsg}: $!\n"
4682                    and exit $E_UNKNOWN );
4683             $post = <$POST>;
4684             close $POST;
4685             chomp $post;
4686         }
4687         else {
4688             $post = $opt{postmsg};
4689         }
4690         if (defined $post) {
4691             print $linebreak;
4692             $post =~ s{[%]s}{$sysinfo{serial}}gxms;
4693             $post =~ s{[%]m}{$sysinfo{model}$sysinfo{rev}}gxms;
4694             $post =~ s{[%]b}{$sysinfo{bios}}gxms;
4695             $post =~ s{[%]d}{$sysinfo{biosdate}}gxms;
4696             $post =~ s{[%]o}{$sysinfo{osname}}gxms;
4697             $post =~ s{[%]r}{$sysinfo{osver}}gxms;
4698             $post =~ s{[%]p}{$count{pdisk}}gxms;
4699             $post =~ s{[%]l}{$count{vdisk}}gxms;
4700             $post =~ s{[%]n}{$linebreak}gxms;
4701             $post =~ s{[%]{2}}{%}gxms;
4702             print $post;
4703         }
4704     }
4705 }
4706
4707 # Reset the WARN signal
4708 $SIG{__WARN__} = 'DEFAULT';
4709
4710 # Print any perl warnings that have occured
4711 if (@perl_warnings) {
4712     foreach (@perl_warnings) {
4713         chop @$_;
4714         print "${linebreak}INTERNAL ERROR: @$_";
4715     }
4716     $exit_code = $E_UNKNOWN;
4717 }
4718
4719 # Print performance data
4720 if (defined $opt{perfdata} && !$opt{debug} && @perfdata) {
4721     my $lb = $opt{perfdata} eq 'multiline' ? "\n" : q{ };  # line break for perfdata
4722     print q{|};
4723
4724     # Sort routine for performance data
4725     sub perfsort {
4726         my %order = ( fan => 0, pwr => 1, tem => 2, enc => 3, );
4727         return ($order{(substr $a->{label}, 0, 3)} cmp $order{(substr $b->{label}, 0, 3)}) ||
4728           $a->{label} cmp $b->{label};
4729     }
4730
4731     # Print performance data sorted
4732     my $type = $opt{perfdata} eq 'minimal' ? 'mini' : 'label';
4733     print join $lb, map { "$_->{$type}=$_->{value};$_->{warn};$_->{crit}" } sort perfsort @perfdata;
4734 }
4735
4736 # Print a linebreak at the end
4737 print "\n" if !$opt{debug};
4738
4739 # Exit with proper exit code
4740 exit $exit_code;