# -*- perl -*-
#
# $Revision: 1.12 $
#/s/nagios/bin/nagioscmd service post <host> <service> \
# <result_code> <service_message>


package threeware;

require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(&print_usage &print_version &service_test);

use FileHandle;
use Data::Dumper;
use XML::Simple;
use IPC::Open2;

use lib $main::runtimedir;
use csl_utils qw($TIMEOUT %ERRORS);

use strict;

my $CONFIG = '/etc/3ware.conf';

my ($critical_error, $warning_error, $output);

sub print_usage ();
sub print_version ();
sub service_test (%);

sub print_usage () {

    print "
Usage:  Check 3ware hardware raid service.
Options:
    -H <hostname> -P <portnumber> [--verbose]
    --help
    --version
    --usage
";
    return $ERRORS{'OK'};
}

sub print_version () {
    my $version = '$Revision: 1.12 $';
    $version =~ s/^\$Revision: //;
    $version =~ s/ \$\s*$//;

    print "3ware.pm - $version\n";
    return $ERRORS{'OK'};
}

sub service_test (%) {
    my %options = @_;

    $critical_error = $warning_error = 0;
    $output = "";

    if ($options{"verbose"}) {
        foreach my $opt (sort keys(%options)) {
            if ($options{$opt}) {
                print "$opt - $options{$opt}\n";
            }
        }
    }

    # Load the correct library
    if (defined $options{opt_libdir}) {
        push @INC, $options{opt_libdir};
    } else {
        require CSL::ThreeWare;
    }
    require ThreeWare::Query;

    # Read raid config file
    my $config = read_config($CONFIG);
    defined ($config) or critical ("# Couldn't read $CONFIG ");
    print Dumper ($config) if ($options{"verbose"});

    # Read the live status
    my $live = read_live ();
    print Dumper ($live) if ($options{"verbose"});

    # Check and report differences
    checkdata ($config, $live);

    # See how we did, and report
    if ($critical_error) {
        print "CRITICAL: $output\n";
        return $ERRORS{CRITICAL};
    } elsif ($warning_error) {
        print "WARNING: $output\n";
        return $ERRORS{WARNING};
    }
    print "OK: raid configuration OK $output\n";
    return $ERRORS{OK};

}

# Parse the raid config file into some kinda structure
sub read_config {
    my $filename = shift;

    my $config = XMLin (
        $filename,
        keyattr => ['name'],
        forcearray => ['unit', 'sub', 'controller',],
    );

    $config->{controller};
}


# Use the module to read the live data

sub read_live {
    my $live;

    my $tw = new ThreeWare::Query;
    return undef unless defined ($tw);

    my $controllers = $tw->list_controllers;
    my ($controller, $unit);
    foreach $controller (@$controllers) {
        $live->{$controller} = $tw->controller_info ($controller);
        foreach $unit (keys %{$live->{$controller}->{unit}}) {
            $live->{$controller}->{unit}->{$unit}->{sub} =
                        $tw->unit_info ($controller, $unit, $live->{$controller});
        }
    }
    $live;
}


# Compare the config and live data and report problems
sub checkdata {
    my $config = shift;
    my $live = shift;

    my ($cu, $lu, $ckey, $ukey, $port_key, $testingbattery);
    foreach $ckey (sort keys %$config) {
        if ($live->{$ckey}->{bbu}) {
            if ($live->{$ckey}->{bbu}->{"BBU Status"} eq "Testing") {
                info ("# $ckey: Testing Battery ");
                $testingbattery = 1;
            } elsif ($live->{$ckey}->{bbu}->{"BBU Status"} eq "Charging") {
                info ("# $ckey: Charging Battery ");
                $testingbattery = 1;
            } else {
                info ("# $ckey: Battery Found ");
            }
        } 
        foreach $ukey (sort keys %{$config->{$ckey}->{unit}}) {


            $cu = $config->{$ckey}->{unit}->{$ukey};
            if (!exists $live->{$ckey}->{unit}->{$ukey}) {
                critical ("# $ckey$ukey: missing from live data ");
                next;
            }
            $lu = $live->{$ckey}->{unit}->{$ukey};

            if ($lu->{Status} ne 'OK') {
                if ($lu->{Status} =~ /(REBUILDING|INITIALIZING)/) {
                    warning ("# $ckey$ukey: Status=$lu->{Status} ");
                    warning ("($lu->{PercentComplete}\% Complete) ")
                        if defined $lu->{PercentComplete};
                } elsif ($lu->{Status} =~ /VERIFYING/) {
                    info ("# $ckey$ukey: Status=$lu->{Status} ");
                    info ("($lu->{PercentComplete}\% Complete) ")
                        if defined $lu->{PercentComplete};
                } else {
                    critical ("# $ckey$ukey: Status=$lu->{Status} ");
                }
            }

            # Raid levels should agree
            $lu->{'UnitType'} =~ s/-/ /g if defined $lu->{UnitType};
            if (!defined $cu->{Unit_Type} || !defined ($lu->{UnitType}) ||
                                        ($cu->{Unit_Type} ne $lu->{'UnitType'})) {
                critical ("# $ckey$ukey: raid level mismatch ");
            }

            # Chunck sizes should also agree
            if ($cu->{Unit_Type} =~ /^RAID (0|5|10)$/) {
                $lu->{Stripe} = lc ($lu->{Stripe}) if defined $lu->{Stripe};
                if (!defined $cu->{Stripe_Size} || !defined ($lu->{Stripe}) ||
                                ($cu->{Stripe_Size} ne $lu->{Stripe})) {
                    critical ("# $ckey$ukey: chunk size mismatch ");
                }
            }

            # Cache status should agree
            if (defined $cu->{Cache}) {
                if (!defined $lu->{Cache}) {
                    warning ("# $ckey$ukey: cache status missing from live data ");
                } elsif (lc($cu->{Cache}) ne lc($lu->{Cache})) {
                    critical ("# $ckey$ukey: cache status incorrect ($cu->{Cache} vs $lu->{Cache})") unless ($testingbattery);
                }
            } else {
                if (defined $lu->{Cache} && ($lu->{Cache} ne '-')) {
                    warning ("# $ckey$ukey: cache status missing from config ");
                }
            }

            # Command queueing status should agree
            $lu->{CommandQueuing} = $lu->{sub}->{attr}->{CommandQueuing}
                                            unless defined $lu->{CommandQueuing};
            if (defined $cu->{CommandQueuing}) {
                if (!defined $lu->{CommandQueuing}) {
                    warning ("# $ckey$ukey: Command Queuing Policy not returned from controller ");
                } elsif ($cu->{CommandQueuing} ne $lu->{CommandQueuing}) {
                    critical ("# $ckey$ukey: Command Queuing Policy incorrect (config=$cu->{CommandQueuing}, live=$lu->{CommandQueuing}) ");
                }
            } elsif (defined $lu->{CommandQueuing}) {
                warning ("# $ckey$ukey: Command Queuing Policy missing from config ");
            }

            # The number of disks must agree
            if (!defined ($cu->{sub}) || !exists ($lu->{sub}) ||
                    (scalar (keys %{$cu->{sub}}) != scalar (keys %{$lu->{sub}->{slot}}))) {
                critical ("# $ckey$ukey: config: Not all raid disks specified ");
            } else {
                # Check that devices actually match.  This is a warning only
                my ($ok, $cd, $ld, $n, $n1) = (1, $cu->{sub}, $lu->{sub}->{slot});
                foreach $n (sort keys %$cd) {
                    ($n1 = $n) =~ s/^s//;
                    (exists ($ld->{$n1}) && ("p$cd->{$n}->{physical}" eq $ld->{$n1}->{Port})) or $ok = 0;
                }
                $ok or warning ("# $ckey$ukey: config/live raid disk name mismatch ");
            }
            if (exists $lu->{resync}) {
                my $msg = "# $ckey$ukey: resync = $lu->{resync} ";
                if (exists $lu->{resync_finish}) {
                    $msg .= "(finish=$lu->{resync_finish}) ";
                }
                warning ($msg);
            }
        }
    }

    # Finally check to be sure all the mdstat devices are described
    # in the config file
    foreach $ckey (sort keys %$live) {
        foreach $ukey (sort keys %{$live->{$ckey}->{unit}}) {
            if (!exists $config->{$ckey}->{unit}->{$ukey}) {
                critical ("# $ckey$ukey: unit missing from 3ware.conf ");
            }
        }
    }


    # check that no disks are reporting anything other than OK -- SMART-FAILURE, whatever
    # p8     OK               u3     698.63 GB   1465149168    5QD5E4YS            
    # p9     SMART-FAILURE    u3     698.63 GB   1465149168    5QD3352R            
    foreach $ckey (sort keys %$live) {
        foreach $port_key (sort keys %{$live->{$ckey}->{port}}) {
            if ($live->{$ckey}->{port}->{$port_key}->{Status} =~ /(OK|NOT-PRESENT)/) {
                #do nothing -- everything is OK
            } elsif ($live->{$ckey}->{port}->{$port_key}->{Status} =~ /VERIFYING/) {
                info ("$ckey$port_key: $live->{$ckey}->{port}->{$port_key}->{Status}");
            } else {
                critical ("$ckey$port_key: $live->{$ckey}->{port}->{$port_key}->{Status}");
            }
        }
    }
}

# Report a warning
sub warning {
    my $warning = shift;
    $output .= "$warning ";
    $warning_error = 1;
}

# Report a critical error
sub critical {
    my $error = shift;
    $output .= "$error ";
    $critical_error = 1;
}

# Report info
sub info {
    my $error = shift;
    $output .= "$error ";
}

1;
