#!/usr/bin/perl

# ******************************************************************************
# *
# * check_rac   2016-10-28
# *
# * Rony Zeitoun (rz2@rony-zeitoun.fr)
# *
# * Description: Nagios plug-in for Oracle rac (11g and above)
# *
# *   This plugin is written with 2 main concerns:
# *   
# *   1: works with Linux / Windows and regardless of the language (ENG / FR / etc.)
# *   2: use exclusively the new crsctl command (11g-12c) and not the old deprecated crs_stat
# *
# *   Precisions:
# *
# *   Before to perform a check we have to see if HAS and clusterware are started and healthy
# *   because if this condition not met, all the subsequent checks will fail
# *   That's the reason why the check clusterstatus return only the version of the clusterware
# *
# *   So, one advice, the minimal checks that you have to implement are :
# *      clusterstatus
# *      resstatus
# *   
# *   The checks:
# *   
# *   (implicit check allways performed) HAS + clusterware on local node        -> crsctl check crs
# *   
# *   (1) clusterstatus     : clusterware stack status (clusterware version)    -> crsctl query crs softwareversion
# *   (2) votedisk          : Voting disks status                               -> crsctl query css votedisk
# *   (3) resstatus         : resource status (ONLINE/OFFLINE);                 -> crsctl stat res
# *                           you can define exceptions with exclude option (resource list, delimiter , or ;)
# *   (4) instancestatus    : database instance status                          -> crsctl stat res -w "TYPE = ora.database.type"
# *
# * This plug-in needs to be run as clusterware binaries owner (usually grid) under LINUX
# * Configure sudo to work with nrpe-owner.
# *
# * Run 'check_rac --help' for full description.
# *
# *******************
# * Setup for LINUX
# *******************
# *
# * 1. disable 'requiretty' for nrpe-owner in /etc/sudoers
# *    Defaults:nagios    !requiretty
# *   
# * 2. enable sudo for nrpe-owner to run this script
# *    nagios          ALL=(grid) NOPASSWD: /usr/lib64/nagios/plugins/check_rac
# * 
# * 3. edit nrpe.cfg and insert required checks with options:
# * 
# *    command[check_rac_clusterstatus]=sudo -u grid /usr/local/nagios/libexec/check_rac.pl --grid_home=$GRID_HOME --action=clusterstatus
# *    command[check_rac_votedisk]=sudo -u grid /usr/local/nagios/libexec/check_rac.pl --grid_home=$GRID_HOME --action=votedisk
# *    command[check_rac_resstatus]=sudo -u grid /usr/local/nagios/libexec/check_rac.pl --grid_home=$GRID_HOME --action=resstatus
# *    command[check_rac_instancestatus]=sudo -u grid /usr/local/nagios/libexec/check_rac.pl --grid_home=$GRID_HOME --action=instancestatus
# * 
# *******************
# * Setup for WINDOWS
# *******************
# *
# * 1. Install Strawberry-perl
# *   
# * 2. Install NSclient++ (0.5)
# *    copy the file check_rac.pl in custom plugin folder [NSCLIENT_HOME]\scripts\custom
# *
# * 3. edit nsclient.ini and insert required checks with options:
# * 
# *    check_rac_clusterstatus     =[PERL_HOME]\perl\bin\perl.exe scripts\custom\check_rac.pl --grid_home=$GRID_HOME --action=clusterstatus
# *    check_rac_votedisk          =[PERL_HOME]\perl\bin\perl.exe scripts\custom\check_rac.pl --grid_home=$GRID_HOME --action=votedisk
# *    check_rac_resstatus         =[PERL_HOME]\perl\bin\perl.exe scripts\custom\check_rac.pl --grid_home=$GRID_HOME --action=resstatus
# *    check_rac_instancestatus    =[PERL_HOME]\perl\bin\perl.exe scripts\custom\check_rac.pl --grid_home=$GRID_HOME --action=instancestatus
# *
# * --exclude is optional
# *
# *****************************************************************************

#use warnings;
#use strict;
use Getopt::Long qw(:config no_ignore_case);

my ($grid_home, $action, $line, $crsversion, $output, $offlineres);

my $exclude_resource = '';
my %nagios_exit_codes = ( 'UNKNOWN', 3, 'OK', 0, 'WARNING', 1, 'CRITICAL', 2 );
my $output_msg = '';
my $status = 'OK';
my $action = '';
my $help = '';

my $res_name = '';
my $res_type = '';
my @res;
my @res_target = ();
my @res_state = ();

my $param = $ARGV[0];
if (not defined $param) {
    usage();
}

if( ! GetOptions ('help' => \$help, 'grid_home=s' => \$grid_home, 'action=s' => \$action, 'exclude=s' => \$exclude_resource)) {
  usage();
}

if( $help ) {
 usage();
}

$ENV{ORACLE_HOME} = $grid_home;
delete $ENV{TWO_TASK};

# First we must check if the clusterware is healthy and reachable (ie checkable)
# Because if not started or not healthy, all the others check will fail
# if everything is OK, crsctl check crs must return these 4 lines
#
# CRS-4638: Oracle High Availability Services is online
# CRS-4537: Cluster Ready Services is online
# CRS-4529: Cluster Synchronization Services is online
# CRS-4533: Event Manager is online

$output = qx[${grid_home}/bin/crsctl check crs];
if( ! ($output =~ m/CRS-4638/ && $output =~ m/CRS-4537/ && $output =~ m/CRS-4529/ && $output =~ m/CRS-4533/ )) {
  print "[CRITICAL] At least one clusterware service is down!\n";
  exit $nagios_exit_codes{'CRITICAL'};
}

# Now we can perfom the checks

if( $action eq 'clusterstatus' ){

  $crsversion = qx[${grid_home}/bin/crsctl query crs softwareversion]; 
  chomp $crsversion;
  $output_msg = "All clusterware services are up ($crsversion)";
  
}
elsif( $action eq 'votedisk' ) {

  $listvotedisk = '';
  $output = qx[${grid_home}/bin/crsctl query css votedisk];
  my $nlt = $output =~ tr/\n//;

  $ok = 1;
  my $nl = 1;
  while( $output =~ /(.+)$/mg) {
    if ( $nl > 2 && $nl < $nlt ) {
      $line = $1;
      if ( ! ($line =~ m/ONLINE /) ) {
	    $listvotedisk .= " - $line";
	    $ok = 0;
      }
	}
    $nl = $nl + 1;
  }

  if( $ok ) {
    $output_msg = "Voting disks status check succeeded";
	}
  else {
    $output_msg = "Voting disk status check failed! -> $listvotedisk";
    $status = 'CRITICAL';
  }
}
elsif( $action eq 'resstatus' ) {

  $output_msg = "All required resources are up";

  $offlineres = '';
  $output = qx[${grid_home}/bin/crsctl stat res];

  # transform resource 4 lines blocks into lines with delimiter ;
  # NAME=ora.ons
  # TYPE=ora.ons.type
  # TARGET=ONLINE
  # STATE=ONLINE on verseau1
  # 
  # NAME=ora.rac1.db
  # TYPE=ora.database.type
  # TARGET=ONLINE            , ONLINE
  # STATE=ONLINE on verseau1, OFFLINE
  
  # becomes
  
  # NAME=ora.ons;TYPE=ora.ons.type;TARGET=ONLINE;STATE=ONLINE on verseau1;
  # NAME=ora.rac1.db;TYPE=ora.database.type;TARGET=ONLINE            , ONLINE;STATE=ONLINE on verseau1, OFFLINE;
  
  $output =~ s/(\S)\n/$1;/gs;
  #print "$output";
  
  while( $output =~ /(.+)$/mg) {
    $line = $1;
    @res = split(/;/, $line);
	$res_name   = (split(/=/,$res[0]))[1];
	$res_type   = (split(/=/,$res[1]))[1];
	@res_target = split(/,/,(split(/=/,$res[2]))[1]);
	@res_state  = split(/,/,(split(/=/,$res[3]))[1]);
	
	# resource exclusion
	if ( ! ($exclude_resource =~ m/$res_name/) ) {
	  # if TARGET=ONLINE STATE must be ONLINE
	  # according to its type a resource can be local to only one node or localized on all the nodes
	  # that is the purpose of the use of the arrays res_state and res_target

  	  my $i = 0;
	  while ($i <= $#res_target) {
        # print " ($i) - ",trim($res_target[$i])," - ",trim($res_state[$i]), "\n";
	    if ( (trim($res_target[$i]) eq 'ONLINE') && !($res_state[$i] =~ m/ONLINE/ ) ) {
		  $offlineres .= $res_name . ' ' . trim($res_state[$i]) . ' - ';
          $status = 'CRITICAL';
	    }
		$i += 1;
	  }
    }	
  }  

  if ( $offlineres ne '' ) {
    $output_msg = "Offline resources: $offlineres";
  }

}
elsif( $action eq 'instancestatus' ) {

  $output_msg = "All required database instances are up";

  $offlineres = '';
  $output = qx[${grid_home}/bin/crsctl stat res -w "TYPE = ora.database.type"];

  $output =~ s/(\S)\n/$1;/gs;
  #print "$output";
  
  while( $output =~ /(.+)$/mg) {
    $line = $1;
    @res = split(/;/, $line);
	$res_name   = (split(/=/,$res[0]))[1];
	$res_type   = (split(/=/,$res[1]))[1];
	@res_target = split(/,/,(split(/=/,$res[2]))[1]);
	@res_state  = split(/,/,(split(/=/,$res[3]))[1]);
	
	#print "TARGET -> @res_target\n";
	#print "STATE  -> @res_state\n";
	
	# resource exclusion
	if ( ! ($exclude_resource =~ m/$res_name/) ) {
	  # if TARGET=ONLINE STATE must be ONLINE
	  # For an instance, ONLINE significate OPEN, INTERMEDIATE significate MOUNT 

	  my $i = 0;
	  while ($i <= $#res_target) {
        # print " ($i) - ",trim($res_target[$i])," - ",trim($res_state[$i]), "\n";
	    if ( (trim($res_target[$i]) eq 'ONLINE') && !($res_state[$i] =~ m/ONLINE/ ) ) {
		  $offlineres .= $res_name . ' ' . trim($res_state[$i]) . ' - ';
          $status = 'CRITICAL';
	    }
		$i += 1;
	  }
    }	
  }  

  if ( $offlineres ne '' ) {
    $output_msg = "Offline instances: $offlineres";
  }
}

print "[${status}] $output_msg\n";
exit $nagios_exit_codes{ $status };

########################
# functions
########################

sub usage {
   print qq[
Usage: $0 --help --grid_home <ORACLE_HOME for clusterware> --action <ACTION> [ --exclude=resource1[,resourceN ..] ]

--help:         prints this info
--grid_home:    ORACLE_HOME for clusterware
--action:       clusterstatus|votedisk|resstatus|instancestatus|dbservicelocation
--exclude:      resource1[,resourceN ..] - list of resources excluded from status check - use for <resstatus> action
                or
                instance1[,instanceN ..] - list of instances excluded from db instance chec - use for <instancestatus> action

];
   exit $nagios_exit_codes{'WARNING'};
}

sub trim { 
  my $s = shift;
  $s =~ s/^\s+|\s+$//g; 
  return $s 
};
