#! /usr/bin/perl -w
# Program         : check_sun_vm
# Author          : Michael Simoni
# Purpose         : Check the Sun Volume manager for errors
# Date            : Fri Sep 26 15:54:16 EDT 2008
################################################################################
# CVS Information
# $Source: /var/lib/cvs/nagios/check_sun_vm.pl,v $
# $Author: msimoni $
# $Date: 2008-11-08 06:25:08 $
# $Id: check_sun_vm.pl,v 1.4 2008-11-08 06:25:08 msimoni Exp $
# $Revision: 1.4 $
################################################################################
# Notes
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# you should have received a copy of the GNU General Public License
# along with this program (or with Nagios);  if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA
################################################################################
$| = 1;
use strict;
use English;
use Sys::Hostname;
use Data::Dumper;
use Getopt::Long;
use vars qw($PROGNAME);
use lib "/usr/lib/nagios/plugins";
use utils qw (%ERRORS &print_revision &support);
################################################################################
# Globals
################################################################################
my $PROGNAME = $0;
my $hostname = hostname();    # always the current host
my $debug    = 0;

my ( $opt_b, $opt_e, $opt_h, $opt_m, $opt_r, $opt_v, $opt_V );
my $opt_c = 3;
my $opt_d = "";
my $opt_n = 2;

my $result  = "OK";
my $message = "";

my %exceptions          = ();
my $exempt_file         = "/usr/lib/nagios/plugins/svm_exceptions";
my %mirrors             = ();
my %metadbs             = ();
my %metadb_device_count = ();
my %raids               = ();

################################################################################
# show a usage statement
################################################################################
sub print_usage ()
{
    print <<EOD;
Usage:
$PROGNAME:   -h -V -v -b -e -d device -c num -m -r -n num
          where:
                -d device of metadata 
                -b Check the meta databases. Default is not to check
                -m Check the mirror devices. Default is not to check
                -r Check the raid devices.   Default is not to check
                -c required number of database replicas. Default is $opt_c
                -n required number of database replicas devices. Default is $opt_n
                -e use the exceptions file: $exempt_file.
                   if the file does not exist, the program continues
                -h| --help 
                -V| --version
                -v| --verbose
EOD
}
################################################################################
# print the standard help stuff
################################################################################
sub print_help ()
{
    print_revision( $PROGNAME, '$Revision: 1.4 $' );
    print "Copyright (c) 2008 Michael Simoni\n\n";
    print_usage();
    print "\n";
    print "This check is used to check Sun Volume Manager.\n";
    print "\n";
    support();
}
################################################################################
# make some error checks
################################################################################
sub error_checks()
{
}
################################################################################
# get a list of exceoptions that we dont care about.
# the default file for exceeption is "svm_exceptions"
# this can be useful if you are sharing a filesystem like a qfs file system
# built upon a svm device. The client system will not understand the underlying
# svm device and state that it needs maintenace. So for those we just ignore
# using this exceptions file. If you use the -e option is used and there is
# no exceptions file then the check is simply not made
# expect nothing
# returns nothing - %exceptions is a global hash
################################################################################
sub get_exceptions()
{
    stat($exempt_file);
    if ( -s _ )
    {

        open( EXCEPTIONS, "$exempt_file" ) or die "Could not open $exempt_file: $!\n";
        while ( my $data_line = <EXCEPTIONS> )
        {
            chomp $data_line;
            next if $data_line =~ /^#/ or $data_line =~ /^\s*$/;    #skip comments and blank lines
            $exceptions{$data_line} = 1;
        }
        close(EXCEPTIONS);
        if ($opt_v)
        {
            $Data::Dumper::Varname = "exceptions";
            print Dumper( \%exceptions ) . "\n";
            $Data::Dumper::Varname = "VAR";
        }
    }
}
################################################################################
# Get metadb data
################################################################################
sub get_metadb_data()
{
    my ( $active_flag, $info_flag, $first_block, $block_count, $device );
    my $lines = 0;

    open METADB, "-|", "/usr/sbin/metadb 2>&1" || die "Couldn't run metadb ($!)";
    while ( my $line = <METADB> )
    {
        chomp($line);
        $lines++;
        next if ( $line =~ /flags/ );
        my $flags = substr $line, 0, 20;
        my $remainder = substr $line, 20;
        $remainder =~ s/^\s+//;
        ( $first_block, $block_count, $device ) = split( /\s+/, $remainder );
        $metadbs{"$device:$first_block"} = {
            flags       => "$flags",
            first_block => "$first_block",
            block_count => "$block_count",
            device      => "$device"
        };

        # lets count the copies of the database
        if ( !defined $metadb_device_count{$device} )
        {
            $metadb_device_count{$device} = 1;
        }
        else
        {
            $metadb_device_count{$device}++;
        }
    }
    close(METADB);

    if ( $lines == 0 )
    {
        print "CRITICAL - there are no existing databases\n";
        exit $ERRORS{'CRITICAL'};
    }
}
################################################################################
# process the metadb data
################################################################################
sub process_metadb_data()
{
    my $num_devices_with_db++;

    if ($opt_v)
    {
        $Data::Dumper::Varname = "metadb";
        print Dumper( \%metadbs ) . "\n";
        $Data::Dumper::Varname = "metadb_device_count";
        print Dumper( \%metadb_device_count ) . "\n";
        $Data::Dumper::Varname = "VAR";
    }

    foreach my $device ( keys %metadbs )
    {

        # check for valid devices
        # normal SCSI devices
        # normal IDE devices - alsofound on vmware installations
        # /dev/dsk/c6t20000014C3E0E3DBd0s7 
        #if (   ( $metadbs{$device}{device} =~ /^\s*\/dev\/dsk\/c[0-9]t[0-9]d[0-9]s[0-9]\s*$/ )
        #    or ( $metadbs{$device}{device} =~ /^\s*\/dev\/dsk\/c[0-9]d[0-9]s[0-9]\s*$/ ) 
        #    or ( $metadbs{$device}{device} =~ /^\s*\/dev\/dsk\/c[0-9]t[0-9]*s[0-9]\s*$/ ) )
        #{
        #    # we are ok here, nothing to do.
        #}
        #else
        #{
        #    $message .= "Slice looks wrong: $metadbs{$device}{device} ";
        #    $result = "CRITICAL";
        #}

        # check for error flags. Any uppercase letters is an error
        if ( $metadbs{$device}{flags} =~ /[A-Z]/ )
        {
            $message .= "Error flags found on metadb: $metadbs{$device}{device}: $metadbs{$device}{flags}";
            $result = "CRITICAL";
        }
    }

    # we want $opt_c  replicas per device. If we dont get it we get and error
    foreach my $device ( keys %metadb_device_count )
    {
        if ( $metadb_device_count{$device} < $opt_c )
        {
            $message .= " There is not enough database relicas. $opt_c replicas are required";
            $result = "CRITICAL";
            last;
        }
    }
    $num_devices_with_db = keys %metadb_device_count;    # get tehenumber of keys in a hash
    if ( $num_devices_with_db < $opt_n )
    {
        $message .= " Not enough database relicas spread accross different devices. At least $opt_n devices are required";
        $result = "CRITICAL";
    }

    $message =~ s/^ and //;
}
################################################################################
# Get the data in a usable structure like a hash or an array.
# then you can do anything you want with it
################################################################################
sub get_mirror_data()
{
    my $device;
    my $type = "";
    my $submirror;
    my $submirrors;
    my $state;

    open METASTAT, "-|", "/usr/sbin/metastat $opt_d 2>&1" || die "Couldn't run metastat ($!)";

    while (<METASTAT>)
    {
        chomp;
        s/^\s+//;
        if (/^metastat: \S+ there are no existing databases/)
        {
            print "OK - there are no existing databases\n";
            exit $ERRORS{'OK'};
        }
        if (/^(d\d+): (.+)/)    # begin new device
        {
            $device = $1;
            $type   = $2;
        }
        if ( $type eq "Mirror" )    # begins new device of type "Mirror"
        {
            if (/^Submirror \d+: (d\d+)/)    # begins new device of type "Submirror"
            {
                $submirror = $1;
                $submirrors++;               # count submirrors
            }
            if (/^State: (.+)/)             # read in submirror's "State"
            {
                $state = $1;
                $mirrors{$device}{$submirror} = "$state";
            }
        }
        if ( $_ eq "" )                      # finished with device of type "Mirror" on blank line
        {
            undef $submirrors;               # clear our flags
            undef $device;
            next;
        }
    }
    close METASTAT;
}
################################################################################
# process the mirror data
################################################################################
sub process_mirror_data()
{
    my $submirror_count = 0;

    if ($opt_v)
    {
        $Data::Dumper::Varname = "mirrors";
        print Dumper( \%mirrors ) . "\n";
        $Data::Dumper::Varname = "VAR";
    }

    foreach my $device ( keys %mirrors )
    {
        next if ( $exceptions{$device} );
        foreach my $submirror ( keys %{ $mirrors{$device} } )
        {

            #print "$submirror: $mirrors{ $device }{ $submirror }\n";
            my $state = $mirrors{$device}{$submirror};
            $state =~ s/^\s+//;    #strip leading and
            $state =~ s/\s+$//;    #trailing blanks

            if ( $state ne "Okay" )
            {
                $message .= " and submirror $submirror of mirror $device is \"$state\"";
                $result = "CRITICAL";
            }
            $submirror_count++;
        }
        if ( $submirror_count == 1 )
        {
            $message .= " and $device has only one submirror!";
            $result = "CRITICAL";
        }
        $submirror_count = 0;
    }

    $message =~ s/^ and //;
}
################################################################################
# Get the data in a usable structure like a hash or an array.
# then you can do anything you want with it
################################################################################
sub get_raid_data()
{
    my $device;
    my $type = "";
    my $state;

    open METASTAT, "-|", "/usr/sbin/metastat $opt_d 2>&1" || die "Couldn't run metastat ($!)";

    while (<METASTAT>)
    {
        chomp;
        s/^\s+//;
        if (/^metastat: \S+ there are no existing databases/)
        {
            print "OK - there are no existing databases\n";
            exit $ERRORS{'OK'};
        }
        if (/^(d\d+): (.+)/)    # begin new device
        {
            $device = $1;
            $type   = $2;
        }
        if ( $type eq "RAID" )    # begins new device of type "Mirror or RAID"
        {
            if (/^State: (\S+)/)    # read in RAID's "State"
            {
                s/^State://;
                $state = $_;
                $raids{$device} = "$state";
            }
        }
        if ( $_ eq "" )             # finished with device of type "Raid" on blank line
        {
            undef $device;
            next;
        }
    }
    close METASTAT;
}
################################################################################
# process the raid data
################################################################################
sub process_raid_data()
{
    if ($opt_v)
    {
        $Data::Dumper::Varname = "raids";
        print Dumper( \%raids ) . "\n";
        $Data::Dumper::Varname = "VAR";
    }
    foreach my $device ( keys %raids )
    {
        next if ( $exceptions{$device} );
        my $state = $raids{$device};
        $state =~ s/^\s+//;    #strip leading and
        $state =~ s/\s+$//;    #trailing blanks

        if ( $state ne "Okay" )
        {
            $message .= " and raid $device is \"$state\"";
            $result = "CRITICAL";
        }
    }
    $message =~ s/^ and //;
}
################################################################################
# Main
################################################################################

Getopt::Long::Configure('bundling');
GetOptions(
    "V"           => \$opt_V,
    "version"     => \$opt_V,
    "v"           => \$opt_v,
    "verbose"     => \$opt_v,
    "h"           => \$opt_h,
    "help"        => \$opt_h,
    "checkmetadb" => \$opt_b,
    "b"           => \$opt_b,
    "checkraid"   => \$opt_r,
    "r"           => \$opt_r,
    "checkmirror" => \$opt_m,
    "m"           => \$opt_m,
    "copies=i"    => \$opt_c,
    "c=i"         => \$opt_c,
    "except"      => \$opt_e,
    "e"           => \$opt_e,
    "device=s"    => \$opt_d,
    "d=s"         => \$opt_d,
    "dbdevices"   => \$opt_n,
    "n=i"         => \$opt_n
);

if ($opt_V)
{
    print_revision( $PROGNAME, '$Revision: 1.4 $' );
    exit $ERRORS{'OK'};
}

if ($opt_h)
{
    print_help();
    exit $ERRORS{'OK'};
}

my $check_made = 0;

#error_checks();
if ($opt_e)
{
    get_exceptions();
}
if ($opt_b)
{
    get_metadb_data();
    process_metadb_data();
    $check_made++;
}

if ($opt_r)
{
    get_raid_data();
    process_raid_data();
    $check_made++;
}
if ($opt_m)
{
    get_mirror_data();
    process_mirror_data();
    $check_made++;
}

if ( not $check_made )
{
    $result  = "UNKNOWN";
    $message = "No check options used";
}
if ( $result eq "OK" ) { $message = "No disk failures detected"; }
print "$result: VM on $hostname - $message.\n";
exit $ERRORS{$result};

