#!/usr/bin/perl
# 
# This perl script uses the _admin volume to query nss volume and pool information.
#
# You need to run it like # ./check_oesnss_vol.pl -V DATA -w 10 -c 5
# but take care that the user has apropriate right to write to the nss management file,
# you might need sudo for the nagios user.
# 
# You need to have the perl-XML-* modules installed on the system where you run
# this script as well as have nss active :=))
#
# Brunold Rainer - version 1.4 - rainer.brunold@brunold.at
#
# changelog:
# 2010/03/07 Brunold Rainer - 1.4
# - adopted the performance data to follow the offical rules at
#   http://nagiosplug.sourceforge.net/developer-guidelines.html#AEN201
# 2010/02/09 Brunold Rainer - 1.3
# - adopted the performance data output to follow the official
#   plugin rules (thanks for the input to Sebastian and Kai)
# 2009/12/13 Brunold Rainer - 1.2
# - added percentage definition for warning and critical level
#   (requested by Roland Kool)
# 2009/11/24 Brunold Rainer - 1.1
# - used space was calculated the wrong way (freeable size was ignored)
#
use POSIX;
use Getopt::Long;
use XML::Parser;
use XML::DOM;
use strict;

use vars qw($sversion $help $debug $volume $xmlcommand $xmlreply $error $size);
use vars qw($status $result $exitcode $warn $crit $mega $giga $kilo $ent $addcomp);
use vars qw($div $percused $percfree $usedall $warnval $critval);

sub print_usage ();

my $version = "1.3";
my $parservol = XML::DOM::Parser->new();
my $parserpool = XML::DOM::Parser->new();
my $nssmgmtfile = "+</_admin/Manage_NSS/manage.cmd";


Getopt::Long::Configure('bundling');
GetOptions
  ("v"     => \$sversion, "sversion"     => \$sversion,
   "w=s"   => \$warn,     "warn=s"       => \$warn,
   "c=s"   => \$crit,     "crit=s"       => \$crit,
   "h"     => \$help,     "help"         => \$help,
   "d"     => \$debug,    "debug"        => \$debug,
   "M"     => \$mega,     "mega"         => \$mega,
   "G"     => \$giga,     "giga"         => \$giga,
   "k"     => \$kilo,     "kilo"         => \$kilo,
   "V=s"   => \$volume,   "volume=s"     => \$volume);

# print the program version
if ($sversion) {
  print "Program version: " . $version . "\n\n";
  exit 3;
}

# print the help screen
if ($help) {
  print_usage;
  exit 3;
}

# check if the volume name is provided
if (!$volume) {
  print "ERROR: missing volumes name \n\n";
  print_usage;
  exit 3;
}

sub print_usage () {
  print "This check program queries several nss volume statistics and compare the
the free space against the warning and critical levels. It provides automatically
performance data and is highly exetnsible to all parameters that can be queried
via the nss management file. As that nss management file ".$nssmgmtfile."
is not writeable for all user, you might need to add a sudo entry for the nagios
user to allow the access to that file.

You can define with the -M, -G and -k options in which format you would like to
have the service results listed, the performance data itself is allways reported
in bytes.


Usage: check_oesnss_vol.pl -V <NSS Volume> -w <Warn Level> -c <Crit Value> [-d] [-h] [-v] [-M] [-G] [-k]

Parameters:
  -c              ... critical level for the free space in [k|M|G|%]
                      if no extension is given I assume it is in MB
  -d              ... print debug information
  -h              ... print help / usage
  -k              ... results are shown in kB
  -G              ... results are shown in GB
  -M              ... results are shown in MB
  -v              ... version
  -V <NSS Volume> ... define the NSS volume you want to monitor
  -w              ... warning level for the free space in [k|M|G|%]
                      if no extension is given I assume it is in MB

Samples:
# check_oesnss_vol.pl -V DATA1 -w 10000 -c 5000 -M
CRITICAL - 0% used, NSS Vol: DATA1, Pool: POOL1, Size: 554 MB, Used: 1 MB, Free: 553 MB, Files: 13, Purgeable: 0 MB, addComp: 0 MB|VolumeSize=579862528B UsedSpaceTotal=569344B;10475520000;5237760000 FreeSpaceTotal=579293184B NumberOfFiles=13 PurgeableSpace=16384B WonByCompression=0B UsedSpacePercent=0% FreeSpacePercent=100%

# check_oesnss_vol.pl -V DATA -w 20% -c 10%
OK - 0% used, NSS Vol: DATA, Pool: POOL1, Size: 579862528 Byte, Used: 569344 Byte, Free: 579293184 Byte, Files: 13, Purgeable: 16384 Byte, addComp: 0 Byte|VolumeSize=579862528B UsedSpaceTotal=569344B;52377600;10475520 FreeSpaceTotal=579293184B NumberOfFiles=13 PurgeableSpace=16384B WonByCompression=0B UsedSpacePercent=0% FreeSpacePercent=100%


The performance data has the following order:

1. VolumeSize       = volume size in byte
2. UsedSpaceTotal   = used space in byte; warning level;critical level
3. FreeSpaceTotal   = free space in byte
4. NumberOfFiles    = number of files in this volume
5. PurgeableSpace   = purgable space in bytes
6. WonByCompression = won space by compression in byte
7. UsedSpacePercent = percent used of this volume
8. FreeSpacePercent = percent free of this volume

If you want to see any enhancements, feel free to contact me via email.
The email is in the header of the file.

Rainer Brunold, 17.10.2009 \n\n";
}

sub query_nss_admin () {
  local($xmlcommand) = @_;

  if ($debug) {
    print "NSS management file: " . $nssmgmtfile . "\n\n";
    print "NSS XML request:     " . $xmlcommand . "\n\n";
  }

  # open the nss management file
  open(NSSMGMT, $nssmgmtfile) or die "Error opening NSS management file ($!) on server";

  if (!syswrite(NSSMGMT, $xmlcommand, length($xmlcommand))) 
  {
    seek NSSMGMT, 0, 0;	               # set to the beginning of the file.
    sysread (NSSMGMT, $error, 10000);  # check the error message.
    print "Error writing initialization to management file. \n";
    print "$error  \n\n";	       # print the error message to the screen.
    close (NSSMGMT);
  }
  else
  {
    seek NSSMGMT, 0, 0;	  # Make sure to start at the beginning of the file.
    sysread (NSSMGMT, $xmlreply, 10000);  #Read the reply.
    close (NSSMGMT);
    if ($debug) {
      print "NSS XML response:    " . $xmlreply . "\n\n";
    }
    return $xmlreply;
  }
}

###################################
# START WITH NSS VOLUME INFORMATION
###################################

if ($debug) {
  print "NSS volume:          " . $volume . "\n\n";
}

# generate the xml query
my $volcommand = "<virtualIO><datastream name=\"command\"/></virtualIO>".
                 "<nssRequest><volume><getVolumeInfo><volumeName>".$volume.
                 "</volumeName></getVolumeInfo></volume></nssRequest>";

my $volxml = &query_nss_admin($volcommand);
	
# sample response for a volume

# <nssReply>
# <volume>
# <getVolumeInfo>
# <basicInfo>
# <volumeName>DATAHK07</volumeName><mountPofloor>/media/nss/DATAHK07</mountPofloor><device>DATAHK07</device><poolName>DATAHK07</poolName><ndsVolumeName>.CN=CL_HK02_DATAHK07.OU=HK02.OU=CLUS.O=SERVICES.T=ALLIANZGRUPPE.</ndsVolumeName><ndsVolumeGUID>c2a8df7d-3288-4125-d3-9a-7ddfa8c28832</ndsVolumeGUID><volumeGUID>4641f3c6-b339-01de-80-00-574147f1d402</volumeGUID><owner>[Supervisor]</owner><volumeState>mounted</volumeState><nameSpaces value="23">DOS Long Macfloorosh Unix </nameSpaces>
# <blockSize>4096</blockSize><volumeQuota>none</volumeQuota><usedSize>3557314560</usedSize><totalObjects>553</totalObjects><totalFiles>250</totalFiles><createdTime value="1254922784">Oct 7, 2009   3:39:44 pm</createdTime>
# <modifiedTime value="1255001261">Oct 8, 2009   1:27:41 pm</modifiedTime>
# <archivedTime value="0">Invalid UTC Time</archivedTime>
# <volumeReadAhead>65536</volumeReadAhead><authorizationModel>NetWare</authorizationModel></basicInfo>
# <attributeInfo>
# <supportedAttributes value="16039018491"><volumeEncrypted/><volumeHardlinks/><salvage/><compression/><directoryQuota/><userQuota/><flushFiles/><cfsMaster/><mfl/><snapshot/><shredding/><userTransaction/><migration/><noAccessTime/></supportedAttributes>
# <enabledAttributes value="4295228411"><salvage/><compression/><userQuota/><backup/></enabledAttributes>
# </attributeInfo>
# <salvageInfo>
# <freeableSize>1774010368</freeableSize><nonFreeableSize>0</nonFreeableSize><deletedFiles>124</deletedFiles><oldestDeletedTime value="0">Invalid UTC Time</oldestDeletedTime>
# <minKeepTime>0</minKeepTime><maxKeepTime>0</maxKeepTime><lowWaterMark>10</lowWaterMark><highWaterMark>20</highWaterMark></salvageInfo>
# <compressionInfo>
# <compressedFiles>91</compressedFiles><compressedDeletedFiles>91</compressedDeletedFiles><uncompressibleFiles>235</uncompressibleFiles><preCompressionBytes>9338880</preCompressionBytes><compressedBytes>2097152</compressedBytes></compressionInfo>
# <result value="0"><description>success</description></result>
# </getVolumeInfo>
# </volume>
# <result value="0"><description>zOK</description></result>
# </nssReply>
	
my $doc      = $parservol->parse($volxml);
my $result   = $doc->getElementsByTagName('result')->item(0)->getAttribute('value');

# check the result of the xml response to see if we got the volume info
if ($result ne "0") {
  print "ERROR: problems getting volume information. Is the volume name correct ? \n";
  exit 3;
}

# extract the different values
my $bas      = $doc->getElementsByTagName('basicInfo')->item(0);
my $vol      = $bas->getElementsByTagName("volumeName")->item(0)->getFirstChild->getData;
my $pool     = $bas->getElementsByTagName("poolName")->item(0)->getFirstChild->getData;
my $usedall  = $bas->getElementsByTagName("usedSize")->item(0)->getFirstChild->getData;
my $quota    = $bas->getElementsByTagName("volumeQuota")->item(0)->getFirstChild->getData;
my $files    = $bas->getElementsByTagName("totalFiles")->item(0)->getFirstChild->getData;
my $sal      = $doc->getElementsByTagName('salvageInfo')->item(0);
my $purge    = $sal->getElementsByTagName("freeableSize")->item(0)->getFirstChild->getData;
my $comp     = $doc->getElementsByTagName('compressionInfo')->item(0);
my $precomp  = $comp->getElementsByTagName("preCompressionBytes")->item(0)->getFirstChild->getData;
my $postcomp = $comp->getElementsByTagName("compressedBytes")->item(0)->getFirstChild->getData;

# calculate the used space and recognize the freeable space
my $used=$usedall-$purge;

# check if there is a quota set for this volume, otherwise we need to query the
# nss pool size as well.
if ($quota eq "none") {

  ####################################
  # CONTINUE WITH NSS POOL INFORMATION
  ####################################

  if ($debug) {
    print "NSS pool:            " . $pool . "\n\n";
  }

  # generate the xml query
  my $poolcommand = "<virtualIO><datastream name=\"command\"/></virtualIO>".
                    "<nssRequest><pool><getPoolInfo><poolName>".$pool."</poolName>".
                    "</getPoolInfo></pool></nssRequest>";

  my $poolxml = &query_nss_admin($poolcommand);

  # this is the reply we should get

  # <nssReply>
  # <pool>
  # <getPoolInfo>
  # <basicInfo>
  # <poolName>DATAHK07</poolName><mountPofloor>/opt/novell/nss/mnt/.pools/DATAHK07</mountPofloor><ndsPoolName>.CN=CL_HK02_DATAHK07_POOL.OU=HK02.OU=CLUS.O=SERVICES.T=ALLIANZGRUPPE.</ndsPoolName><ndsPoolGUID>a63755a5-4b54-47fe-23-90-a55537a6544b</ndsPoolGUID><poolGUID>9155ed00-b338-01de-80-00-574147f1d402</poolGUID><poolState>active</poolState><nameSpaces value="3085691102">Long Macfloorosh Unix ExtendedAttribute DataStream </nameSpaces>
  # <blockSize>4096</blockSize><size>42949672960</size><usedSize>3685838848</usedSize><createdTime value="1254922678">Oct 7, 2009   3:37:58 pm</createdTime>
  # <modifiedTime value="1254922678">Oct 7, 2009   3:37:58 pm</modifiedTime>
  # <lssType>ZLSS</lssType></basicInfo>
  # <salvageInfo>
  # <freeableSize>1774010368</freeableSize><nonFreeableSize>0</nonFreeableSize></salvageInfo>
  # <attributeInfo>
  # <supportedAttributes value="251"><persistentFeatures/><shared/><verify/><rebuild/><multipleVolumes/><snapshot/><msap/></supportedAttributes>
  # <enabledAttributes value="187"><persistentFeatures/><shared/><verify/><rebuild/><multipleVolumes/><msap/></enabledAttributes>
  # </attributeInfo>
  # <volumeInfo>
  # <volumeName>DATAHK07</volumeName>
  # </volumeInfo>
  # <deletedVolumeInfo>
  # </deletedVolumeInfo>
  # <segmentInfo>
  # <segment>
  # <deviceName>sdc</deviceName><segmentName>sdc1.1</segmentName><deviceID>sdc</deviceID><segmentID>sdc1.1</segmentID><offset>32</offset><size>42950705152</size>
  # </segment>
  # </segmentInfo>
  # <result value="0"><description>success</description></result>
  # </getPoolInfo>
  # </pool>
  # <result value="0"><description>zOK</description></result>
  # </nssReply>

  my $doc   = $parserpool->parse($poolxml);
  my $bas   = $doc->getElementsByTagName('basicInfo')->item(0);
  $size  = $bas->getElementsByTagName("size")->item(0)->getFirstChild->getData;

}
else
{

  $size = $quota;

}

my $free = $size - $used;

# recalculate warning and critical value depending on the value type
# warning
if (substr($warn,-1,1) eq "k") {
  $warnval=substr($warn,0,length($warn)-1)*1024;
} elsif (substr($warn,-1,1) eq "M") {
  $warnval=substr($warn,0,length($warn)-1)*1047552;
} elsif (substr($warn,-1,1) eq "G") {
  $warnval=substr($warn,0,length($warn)-1)*1073741824;
} elsif (substr($warn,-1,1) eq "%") {
  $warnval=$size*substr($warn,0,length($warn)-1)/100;
} else {
  # default value is in MB
  $warnval=$warn*1047552;
}
# critical
if (substr($crit,-1,1) eq "k") {
  $critval=substr($crit,0,length($crit)-1)*1024;
} elsif (substr($crit,-1,1) eq "M") {
  $critval=substr($crit,0,length($crit)-1)*1047552;
} elsif (substr($crit,-1,1) eq "G") {
  $critval=substr($crit,0,length($crit)-1)*1073741824;
} elsif (substr($crit,-1,1) eq "%") {
  $critval=$size*substr($crit,0,length($crit)-1)/100;
} else {
  # default value is in MB
  $critval=$crit*1047552;
}

if ($debug) {
  print "warning level:  " . $warnval . " byte\n";
  print "critical level: " . $critval . " byte\n\n";
}

# check the warning and critical levels
if (!$warnval || !$critval || $warnval<$critval) {
  print "ERROR: incorrect warning and critial levels defined \n\n";
  print_usage;
  exit 3;
}


# critical and warning levels are defined in MB, therefor multiply it with 
# 1047552 to compare it against the current used space
if (abs($free)<=($critval)) {
  $status="CRITICAL";
  $exitcode=2;
} elsif (abs($free)<=($warnval)) {
  $status="WARNING";
  $exitcode=1;
} else {
  $status="OK";
  $exitcode=0;
}

# calculate the additional space used by compressed file
$addcomp=$precomp-$postcomp;

# calculate the used space in percent
$percused=($used/$size)*100;
$percfree=($free/$size)*100;


if ($giga) {
  $div      = 1073741824;
  $ent      = " GB";
} elsif ($mega) {
  $div      = 1047552;
  $ent      = " MB";
} elsif ($kilo) {
  $div      = 1024;
  $ent      = " kB";
} else {
  $div      = 1;
  $ent      = " Byte";
}

print $status . " - ";
printf ("%.0f", $percused);
print "% used";
print ", NSS Vol: " . $vol .", Pool: " . $pool . ", Size: ";
printf ("%.0f", $size/$div);
print $ent . ", Used: ";
printf ("%.0f", $used/$div);
print $ent . ", Free: "; 
printf ("%.0f", $free/$div);
print $ent . ", Files: " . $files . ", Purgeable: ";
printf ("%.0f", $purge/$div);
print $ent . ", addComp: ";
printf ("%.0f", $addcomp/$div);
print $ent . "|VolumeSize=" . $size . "B UsedSpaceTotal=" . $used . "B;" . int($warnval) . ";" . int($critval) . " FreeSpaceTotal=" . $free . "B NumberOfFiles=" . $files . " PurgeableSpace=" . $purge . "B WonByCompression=" . $addcomp . "B UsedSpacePercent=";
printf ("%.0f", $percused);
print "% FreeSpacePercent=";
printf ("%.0f", $percfree);
print "%\n";
exit $exitcode;
