#!/usr/bin/perl -w
#
# $Id: check_xserve_raid.in,v 1.5 2005/12/05 22:02:17 rader Exp $
#
#

use strict;
use XML::Parser;

my $prefix = "/usr/local";
my $lib_dir = "$prefix/lib/xserve-raid-tools";
my $etc_dir = "$prefix/etc";
my $conf_file = "xserve-raid-tools.conf";
my $status_template = "xserve-status-page.template";
my $port = "80";
my $debug = 0;
my ($UNKNOWN,$OKAY,$WARNING,$CRITICAL) = (-1,0,1,2);
my $plugin_output = '';
my $exit_status = $OKAY;

my $addr;
my $passwd;
my ($loc,$model,$size,$type,$status);
my $xml;
my %raids;
my %raid_attrs;
my %raid_members;
my %rebuilding_disks;
my %disks;
my %disk_attrs;
my %cf;


#------------------------------------------------------------------------------

&read_conf_file;
$addr = $cf{'addr'};
$passwd = $cf{'passwd'};

#------------------------------------------------------------------------------

while ( $ARGV[0] && $ARGV[0] =~ /^-/ ) {
  if ( $ARGV[0] eq '-d' || $ARGV[0] eq '--debug' ) { 
    $debug++;
    shift @ARGV;
    next;
  }
  if ( $ARGV[0] eq '-a' || $ARGV[0] eq '--address' ) { 
    shift @ARGV;
    if ( ! $ARGV[0] ) { &usage; exit $UNKNOWN; }
    $addr = $ARGV[0];
    shift @ARGV;
    next;
  }
  if ( $ARGV[0] eq '-p' || $ARGV[0] eq '--passwd' ) {
    shift @ARGV;
    if ( ! $ARGV[0] ) { &usage; exit $UNKNOWN; }
    $passwd = $ARGV[0];
    shift @ARGV;
    next;
  }
  &usage;
  exit $UNKNOWN;
}
if ( $ARGV[0] ) { 
  &usage;
  exit $UNKNOWN;
}

#------------------------------------------------------------------------------

my $p = new XML::Parser(Style=>'Tree');

#------------------------------------------------------------------------------

&parse_page1('top','lhs');
&parse_page1('bottom','rhs');
&parse_page16('top','lhs');
&parse_page16('bottom','rhs');

for my $i (sort keys %raids ) {
  &check_raid($i);
  for my $j (sort keys %{$raid_members{$i}} ) {
    &check_disk($j,'');
  }
}

$plugin_output =~ s/, $//;

if ( $exit_status == 2 ) { print "CRITICAL - $plugin_output\n"; }
if ( $exit_status == 1 ) { print "WARNING - $plugin_output\n"; }
if ( $exit_status == 0 ) { print "OK - $plugin_output\n"; }

exit $exit_status;

#==============================================================================

sub parse_page1 {
  my ($controller,$side) = @_;
  my $raid_num = -1;
  my $prev = '';

  #------------------------------------------------------------------
  open(IN,"<$lib_dir/$status_template");
  open(OUT,">/tmp/xserve-raid-query.$$");
  while(<IN>) { 
    $_ =~ s/_ADDR_/$addr/;
    $_ =~ s/_CONTROLLER_/$controller/;
    $_ =~ s/_PAGE_ID_/1/;
    $_ =~ s/_PASSWD_/$passwd/;
    print OUT $_;
  }
  close(OUT);
  close(IN);

  #------------------------------------------------------------------
  if ( $debug ) { 
    print "** DEBUG QUERY ** $controller ($side) ARRAYS ** /bin/nc $addr $port < /tmp/xserve-raid-query.$$  **\n";
  }
  open(IN,"/bin/nc $addr $port < /tmp/xserve-raid-query.$$ |");

  #------------------------------------------------------------------
  while(<IN>) { 
    if ( $debug > 1 ) { print $_; }
    if ( $_ =~ /^<\?xml/ ) { 
      $xml = $_; last;  
    }
  }
  while(<IN>) {
    $xml .= $_;
    if ( $debug > 1 ) { print $_; }
  }

  #------------------------------------------------------------------
  unlink("/tmp/xserve-raid-query.$$");
  if ( ! close(IN) ) { 
    print "CRITICAL - http communications failure\n";
    exit $CRITICAL;
  }

  #------------------------------------------------------------------
  my $tree = $p->parse($xml);

  #------------------------------------------------------------------
  if ( $tree->[1][4][4][2] eq 'status' ) {
    if ( $debug ) { print "** DEBUG STATUS ** query status is $tree->[1][4][8][2] **\n"; }
    if ( $tree->[1][4][8][2] eq '-6745' ) { 
      print "CRITICAL - standby power mode\n";
      exit $CRITICAL;
    }
    if ( $tree->[1][4][8][2] eq '-16' ) { 
      print "UNKNOWN - incorrect password\n";
      exit $UNKNOWN;
    }
    print "CRITICAL - unknown status error: \"$tree->[1][4][8][2]\"\n";
    exit $CRITICAL;
  }

  #------------------------------------------------------------------
  for (my $i = 4; $i < $#{$tree->[1][4][8][48]}; $i += 4) {
    #--------------------
    # attrs...
    my $id = "$side.array$tree->[1][4][8][48][$i][8][2]";
    $raids{$id} = 1;
    $raid_attrs{$id}{'raid-status'} = $tree->[1][4][8][48][$i][48][2];
    my $member_offset;
    if ( $tree->[1][4][8][48][$i][52][2] eq 'sector-capacity' ) {
      # xml from pre-1.5 firmware...
      $raid_attrs{$id}{'sector-capacity'} = $tree->[1][4][8][48][$i][56][2];
      $raid_attrs{$id}{'initialize'} = $tree->[1][4][8][48][$i][72][8][2];
      $raid_attrs{$id}{'add-member'} = $tree->[1][4][8][48][$i][72][16][2];
      $member_offset = 80;
    } else {
      # xml from 1.5 firmware...
      $raid_attrs{$id}{'sector-capacity'} = $tree->[1][4][8][48][$i][64][2];
      $raid_attrs{$id}{'initialize'} = $tree->[1][4][8][48][$i][80][8][2];
      $raid_attrs{$id}{'add-member'} = $tree->[1][4][8][48][$i][80][16][2];
      $member_offset = 88;
    }

    if ( $debug ) { 
      print "** DEBUG DATA  ** found $id\n";
      print "** DEBUG DATA  **   $id raid-status is $raid_attrs{$id}{'raid-status'}\n";
      print "** DEBUG DATA  **   $id sector-capacity is $raid_attrs{$id}{'sector-capacity'}\n"; 
      print "** DEBUG DATA  **   $id initialize is $raid_attrs{$id}{'initialize'}\n";
      print "** DEBUG DATA  **   $id add-member is $raid_attrs{$id}{'add-member'}\n";
    }
    #--------------------
    # members...
    for (my $j = 4; $j < $#{$tree->[1][4][8][48][$i][$member_offset]}; $j += 4) {
      if ( $tree->[1][4][8][48][$i][$member_offset][$j][16][2] ne '0' ) {
        my $member = "$side.slot$tree->[1][4][8][48][$i][$member_offset][$j][16][2]";
        if ( $debug ) { print "** DEBUG DATA  **   $id has member $member\n"; }
        $raid_members{$id}{$member} = 1;
      }
    }
  }

  #------------------------------------------------------------------
  for (my $i = 4; $i < $#{$tree->[1][4][8][56]}; $i += 4) {
    #--------------------
    # slots...
    my $id = "$side.slot$tree->[1][4][8][56][$i][8][2]";
    if ($tree->[1][4][8][56][$i][16][23] eq 'true' ) {
      if ( $debug ) { print "** DEBUG DATA  ** $id is rebuilding\n"; }
      $rebuilding_disks{$id} = 1;
    } else {
      if ( $debug ) { print "** DEBUG DATA  ** $id is not rebuilding\n"; }
    }
  }

}

#==============================================================================

sub parse_page16 {
  my ($controller,$side) = @_;
  my $prev = '';

  #------------------------------------------------------------------
  open(IN,"<$lib_dir/$status_template");
  open(OUT,">/tmp/xserve-raid-query.$$");
  while(<IN>) {
    $_ =~ s/_ADDR_/$addr/;
    $_ =~ s/_CONTROLLER_/$controller/;
    $_ =~ s/_PAGE_ID_/16/;
    $_ =~ s/_PASSWD_/$passwd/;
    print OUT $_;
  }
  close(OUT);
  close(IN);

  #------------------------------------------------------------------
  if ( $debug ) {
    print "-" x 78, "\n";
    print "** DEBUG QUERY ** $controller ($side) DISKS ** /bin/nc $addr $port < /tmp/xserve-raid-query.$$  **\n";
  }
  open(IN,"/bin/nc $addr $port < /tmp/xserve-raid-query.$$ |");

  #------------------------------------------------------------------
  while(<IN>) {
    if ( $debug > 1 ) { print $_; }
    if ( $_ =~ /^<\?xml/ ) { 
      $xml = $_; last;  
    }
  }
  while(<IN>) {
    $xml .= $_;
    if ( $debug > 1 ) { print $_; }
  }

  #------------------------------------------------------------------
  unlink("/tmp/xserve-raid-query.$$");
  if ( ! close(IN) ) { 
    print "CRITICAL - http communications failure\n";
    exit $CRITICAL;
  }
  
  #------------------------------------------------------------------
  my $tree = $p->parse($xml);

  #------------------------------------------------------------------
  if ( $tree->[1][4][4][2] eq 'status' ) {
    if ( $debug ) { print "** DEBUG STATUS ** query status is $tree->[1][4][8][2] **\n"; }
    if ( $tree->[1][4][8][2] eq '-6745' ) {
      print "CRITICAL - standby power mode\n";
      exit $CRITICAL;
    }
    if ( $tree->[1][4][8][2] eq '-16' ) {
      print "UNKNOWN - incorrect password\n";
      exit $UNKNOWN;
    }
    print "CRITICAL - unknown status error: \"$tree->[1][4][8][2]\"\n";
    exit $CRITICAL;
  }

  #------------------------------------------------------------------
  for (my $i = 4; $i < $#{$tree->[1][4][8][16]}; $i += 4) {
    my $id = "$side.slot$tree->[1][4][8][16][$i][8][2]";
    $disks{$id} = 1;
    if ( $debug ) {
      print "** DEBUG DATA  ** found $id\n";
    }
  }

}

#==============================================================================

sub check_disk {
  my ($which,$prefix) = @_;
  $loc = "${prefix}$which";
  if ( $disks{$which} ) {
    if ( $rebuilding_disks{$which} ) {
      $plugin_output .= "$loc rebuilding, ";
      if ( $exit_status < $WARNING ) {
        $exit_status = $WARNING;
      }
    } 
  } else {
    $plugin_output .= "$loc missing, ";
    $exit_status = $CRITICAL;
  }
}

#==============================================================================

sub check_raid {
  my ($which) = $_[0];
  $loc = $which;
  $status = $raid_attrs{$which}{'raid-status'};
  CASE: { 
    if ( $raid_attrs{$which}{'initialize'} < 101 ) {
      $plugin_output .= "$loc $raid_attrs{$which}{'initialize'}% built, ";
      if ( $exit_status < $WARNING ) { $exit_status = $WARNING; }
      last;
    }
    if ( $raid_attrs{$which}{'add-member'} < 101 ) {
      $plugin_output .= "$loc $raid_attrs{$which}{'add-member'}% rebuilt, ";
      if ( $exit_status < $WARNING ) { $exit_status = $WARNING; }
      last;
    }
    if ( $status eq 'degraded' ) {
      $plugin_output .= "$loc degraded, ";
      if ( $exit_status < $WARNING ) { $exit_status = $WARNING; }
      last;
    }
    if ( $status eq 'offline' ) {
      $plugin_output .= "$loc dead, ";
      $exit_status = $CRITICAL;
      last;
    }
    $plugin_output .= "$loc optimal, ";
  }
}

#==============================================================================

sub read_conf_file {
  my $s = '';
  my $cf_file = "$etc_dir/$conf_file";
  if ( $debug ) { print "** DEBUG CONF ** $cf_file **\n"; }
  if ( ! open(IN,"<$cf_file") ) {
    print "xserve-raid-status: $etc_dir/$conf_file: $!\n";
    exit $UNKNOWN;
  }
  while (<IN>) {
    if ( $_ =~ /^\s*$/ ) { next; }
    chop; $_ =~ s/(.*?)\#.*/$1/;
    $s .= $_;
    while ( $_ !~ /;/ && !(eof(IN)) ) {
      $_ = <IN>;
      chop; $_ =~ s/(.*?)\#.*/$1/;
      $s .= $_;
    }
    if ( $s !~ /;/ ) {
      print "xserve-raid-status: $cf_file: syntax error: no semicolon for the last entry\n";
      exit $UNKNOWN;
    }
    $s =~ s/(.*?);.*/$1/;
    if ( $s =~ /(\S+)\s*=\s*(.*)/ ) {
      my $k = $1; my $v = $2;
      if ( $k =~ /_list$/ ) {
        $cf{$k} = [ split(/\s+/,$v) ];
        if ( $debug > 1 ) {
          print "  \$cf{\'$k\'} = wq( ";
          print join " ", @{ $cf{$k} };
          print " );\n";
        }
      } else {
        $cf{$k} = $v;
        if ( $debug > 1 ) { print "  \$cf{\'$k\'} = $v;\n"; }
      }
    } else {
      print "xserve-raid-status: $cf_file: $s: syntax error\n";
      exit $UNKNOWN;
    }
    $s = '';
  }
  close(IN);
}


#==============================================================================

sub usage {
print <<EOT
usage: check_xserve_raid [args] 
  --list mode        list logical, physical or both (default is both)
  --passwd passwd    encrypted passwd (default is $passwd aka "public")
  --address a.b.c.d  check status of raid at a.b.c.d (default is $addr)
  --debug            for debug output (use twice to get source xml output)
EOT
}
