#!/usr/bin/perl

######
###### $Id: check_raid_gmirror.pl 270 2008-01-05 16:47:07Z seklecki $
######

#######################################
### check_raid_gmirror.pl #############
# Check a FreeBSD geom(4)/gmirror(4) ##
# volume by parsing the output of #####
# gmirror(8).  5x/6x compat     #######
# by Brian A. Seklecki          #######
# <bseklecki@collaborativefusion.com> #
# for Collaborative Fusion, Inc. ######
#######################################

#/*
# * Copyright (c) 2006-2007 Collaborative Fusion, Inc.  All rights reserved.
# * 
# * Developed by: Collaborative Fusion, Inc.
# *                                                     http://www.collaborativefusion.com
# * 
# * Permission is hereby granted, free of charge, to any person obtaining a copy
# * of this software and associated documentation files (the "Software"), to
# * deal with the Software without restriction, including without limitation the
# * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# * sell copies of the Software, and to permit persons to whom the Software is
# * furnished to do so, subject to the following conditions:
# *   1. Redistributions of source code must retain the above copyright notice,
# *      this list of conditions and the following disclaimers.
# *   2. Redistributions in binary form must reproduce the above copyright
# *      notice, this list of conditions and the following disclaimers in the
# *      documentation and/or other materials provided with the distribution.
# *   3. All advertising materials mentioning features or use of this software
# *      must display the following acknowledgement: This product includes 
# *      software developed by Collaborative Fusion, Inc.
# *   4. Neither the name of Collaborative Fusion, Inc. or any of its employees
# *      may be used to endorse or promote products derived from this Software
# *      without specific prior written permission.
#
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
# * COLLABORATIVE FUSION, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
# * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# */
#
#/*
# * This code was written under funding by Collaborative Fusion, Inc..
# */





#use warnings;
use strict;
use Switch;


my %NAGIOS_API_ECODES = ( 'OK' => 0, 'WARNING' => 1, 'CRITICAL' => 2, 'UNKNOWN' => 3 ); 

my $USAGE = "Usage: $0 [-h] [-v]\n";
my $_boolVerbose = 0;

if (@ARGV && ($ARGV[0] =~ "-h" || $ARGV[0] !~ "-v")) {
	print $USAGE;
	exit $NAGIOS_API_ECODES{UNKNOWN};
}

# Check and enable a global boolean to switch into verbose
# mode. It's important to 
if (@ARGV > 0) { 

	if ($ARGV[0] =~ m/^\-v$/) {
		$_boolVerbose = 1;
	}
}

my $_strFreeBSDBranch = `uname -r | cut -c 1 2>&1`;

chomp ($_strFreeBSDBranch);

switch ($_strFreeBSDBranch) {
	case "5" {
		if (! open(GMLIST, "/sbin/gmirror list |") ) {
			die "Cannot locate gmirror(8) or command failed no kernel support";
			exit $NAGIOS_API_ECODES{UNKNOWN}; 
		}
		
	};
	case "6" {
		if (! open(GMLIST, "/sbin/gmirror status -s | ") ) {
			die "Cannot locate gmirror(8) or command failed no kernel support";
			exit $NAGIOS_API_ECODES{UNKNOWN}; 
		}
	};
}

my $_strCurGM = "";
my $_strCurGMState = "";

my $_intGMCount = 0;

my $_intGMCompCount = 0;
my $_intGMDegrCount = 0;

my @_arrGMProviders = ();

switch ($_strFreeBSDBranch) {
	case "5" {
		my @_arrGMStatus = ();
		while (my $line = <GMLIST>) {
		
		if ($line =~ m/^Geom\ name\:\ (gm[0-9]+)$/) {
			$_strCurGM = $1;
			$_intGMCount++;
		}
		
		if ($line =~ m/^State\:\ (COMPLETE|DEGRADED)$/) {
			$_strCurGMState = $1;
			if ($_strCurGMState =~ "COMPLETE") {
				$_intGMCompCount++;
			} else {
				$_intGMDegrCount++;
			}
		}
		
		$_arrGMStatus[$_intGMCount][0] = $_strCurGM;
		$_arrGMStatus[$_intGMCount][1] = $_strCurGMState;
		
		# end while
		}
		
		# Print a summary
		print "Total GMs: $_intGMCount | COMPLETE: $_intGMCompCount | DEGRADED: $_intGMDegrCount";
		

	};
	case "6" {
		#we have to process this entirely different
		# @_arrGMStatus is still used, but utilized differently
		my %_arrGMStatus = ();
		my $_intGMProvider = 0;
		while (my $line = <GMLIST>) {

			if ($line =~ m/^mirror\/(gm\d+)\ \ (COMPLETE|DEGRADED)\ \ (\w{2,2}\d+)(\ \(\d+\%\))?$/) {
				$_arrGMProviders[$_intGMProvider][0] = $1; #Provider
				$_arrGMProviders[$_intGMProvider][1] = $2; #Status of Member Disk
				$_arrGMProviders[$_intGMProvider][2] = $3; #Member Disk
				$_arrGMProviders[$_intGMProvider][3] = $4; #Potential Rebuild Percent
				#print "DEBUG: True Match " . $_arrGMProviders[$_intGMProvider][3]; 
				$_intGMProvider++;	
			}
		# end while
		}

		# this routine looks through each provider regex'd from @_arrGMProviders
		# and parses it into the named-Array (wtf is it called?) %_arrGMStatus which looks like:
		# $_arrGMProviders{'mirror/gm0'}{'VARIABLE_NAME'} = Value;

		foreach (@_arrGMProviders) {
			#$_arrGMStatus{'gm0'}{'numProvs'} = 2; = [0]
			#$_arrGMStatus{'gm0'}{'member0'} = ad8; = [1]
			#$_arrGMStatus{'gm0'}{'member0status'} = COMPLETE; = [2]
			#$_arrGMStatus{'gm0'}{'member1'} = ad10; = [3]
			#$_arrGMStatus{'gm0'}{'member1status'} = DEGRADED; = [4]
			#$_arrGMStatus{'gm0'}{'degradedCount'} = 1; = [5] # Local to this gmirror
			#$_arrGMStatus{'gm0'}{'completeCount'} = 1; = [6] # Local to this gmirror
			#$_arrGMStatus{'gm0'}{'gmHealth'} = DEGRADED; = [6]
		
			# each of the next routines can say:
			# $_arrGMStatus{'gmBLAH'}{'variable'} = whatever;

			# there should be at least two providers, count them
			$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'numProvs'}++;
			#print "DEBUG: Processing " . $_arrGMProviders[$_intGMCount][0] . " ";

			
			# there can only be two providers, map them into member0/member1
			# Note this function maps disk providers into member0/member1 on the order
			# in which they were processed, not the consumer ID in "gmirror list"
			# (but that's presumably the same thing), however, if things go wrong on 
			# a system, a higher scsi/ata/sata Bus ID could be assigned to member0

			if ( ! $_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member0'} ) {
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member0'} = $_arrGMProviders[$_intGMCount][2];
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member0status'} = $_arrGMProviders[$_intGMCount][1]; 
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member0rbldstat'} = $_arrGMProviders[$_intGMCount][3]; 
			} else {
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member1'} = $_arrGMProviders[$_intGMCount][2];
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member1status'} = $_arrGMProviders[$_intGMCount][1] ;
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'member1rbldstat'} = $_arrGMProviders[$_intGMCount][3] ;
			}

			#print "DEBUG: $_arrGMProviders[$_intGMCount][2]\n";

			if ($_arrGMProviders[$_intGMCount][1] =~ "DEGRADED" ) {
				# We have no way of knowing if the overall status of a mirror
				# is "COMPLETE" without parsing "gmirror list" instead of "status -s"
				# Come to think of it...at this point, why are we bothering to parse
				# "status" instead of "list"? It ended up being more work than
				# it was worth.  ANYWAY, if one component is bad, flag the entire
				# mirror as DEGRADED *and* increment the global bad count
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'degradedCount'}++;
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'gmHealth'} = "DEGRADED";
				# Later on: This was a good idea, but since:
					# - We're not defaulting gmHealth to anything
					# - It might not get set
					# ....it can't be relied on
			} else {
				$_arrGMStatus{$_arrGMProviders[$_intGMCount][0]}{'completeCount'}++;
			} 
				
				
			# move to the next provider in the array and
			$_intGMCount++;

		# end foreach()
		#print "DEBUG: Leaving foreach() \n";
		}

		my $_intTallyDegradedCounter = 0;
		
		# What the fuck is this code?
		# What the fuck.

		while ($_intTallyDegradedCounter < keys(%_arrGMStatus)) {
			if ($_arrGMStatus{'gm'. $_intTallyDegradedCounter}{'member0status'} =~ "DEGRADED"  || \
			$_arrGMStatus{'gm'. $_intTallyDegradedCounter}{'member1status'} =~ "DEGRADED" ) {
				$_intGMDegrCount++;
			}
			$_intTallyDegradedCounter++;
		}

		# well this was certainly an exercise in poor data structures in perl
		# and excessively long variable names.  Now, let's print a summary:
		
		my $_intTestVal = 0;
		
		#$_intTestVal = keys(%_arrGMStatus);
		#print "DEBUG: " . $_intTestVal . " \n";

		#print "Total GMs: " . keys(%_arrGMStatus) . " | ";
		print "Total GMs: " . $_intGMCount . " | ";

		if ($_boolVerbose) {
			print "Total Members: " . $_intGMCount . " | ";
		}

		print "Total GMs Degraded: " . $_intGMDegrCount;

		
		if ($_boolVerbose) {
			my $_intVerboseDiskCounter = 0;
		
			while ($_intVerboseDiskCounter < keys(%_arrGMStatus)) {
				print " | GM" . $_intVerboseDiskCounter . ":(" . 
				$_arrGMStatus{'gm'. $_intVerboseDiskCounter}{'member0'} . ": " .
				$_arrGMStatus{'gm'. $_intVerboseDiskCounter}{'member0status'} .
				$_arrGMStatus{'gm'. $_intVerboseDiskCounter}{'member0rbldstat'} .
				" " . 
				$_arrGMStatus{'gm'. $_intVerboseDiskCounter}{'member1'} . ": " .
				$_arrGMStatus{'gm'. $_intVerboseDiskCounter}{'member1status'} .
				$_arrGMStatus{'gm'. $_intVerboseDiskCounter}{'member1rbldstat'} .
				" " . 
				")";
				$_intVerboseDiskCounter++;
			}

		}


	# End case FBSD6
	
	};

# End switch
}


if ($_intGMDegrCount > 0) {
	exit  $NAGIOS_API_ECODES{CRITICAL};
} else {
	exit $NAGIOS_API_ECODES{OK};
}

