#!/usr/bin/perl -w # Script per il check dello status dei volume managers # Solaris: SVM, ZFS, VXVM # Linux: MD, LVM # # Alberto Menichetti @ TAI # # - configurazione snmpd.conf - # extend volMgrCheck /opt/monitoring/scripts/checkvolmanager -S # OID: # NET-SNMP-EXTEND-MIB::nsExtendOutLine.\"volMgrCheck\".1 # .1.3.6.1.4.1.8072.1.3.2.3.1.1.11.118.111.108.77.103.114.67.104.101.99.107 # # - configurazione snmpd.conf vecchie release (Solaris SMA) - # exec .1.3.6.1.4.1.2021.2789.51 volMgrCheck /opt/monitoring/scripts/checkvolmanager -S # # # CHANGELOG: # v1.0: initial release # # v1.1: added support for MD on Linux # added support for LVM on Linux # # v1.2: fix duplicate zpool message when pool is not healthy # # v1.3: fix to support the change in the zpool list output # # v1.4: code refactoring # # v1.5: skip SVM/SDS checks if the execution enviroment is a Solaris Container # added support for VxVM on Solaris # added \n as the last character in the NRPE output (it seems required for NagiosXI?!) # code cleanup # fix output returned when a single device contains more than 1 metadb replica (code not active) # # v1.6: fix broken zfs check # # v1.7: no more need to be run as root under linux # # v1.8: added option -L: comma-separated list of lvm vg to check # # v1.9: fix broken mdadm check: added --verbose option in mdmadm command (thanks to Andrea Tartaglia) # use strict; use POSIX; use Getopt::Long; &Getopt::Long::config('bundling'); sub _debug($); sub _msg($); sub _verbosemsg($); sub check_zfs(); sub check_svm(); sub check_md(); sub check_lvm(); sub check_vxvm(); sub process_metastat($); sub process_metadb($); sub parse_options(); sub print_help(); sub change_status($); my $PROGNAME = "checkvolmanager"; my $VERSION = "1.9"; my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3); my $status = 'OK'; my $msg = ""; my $snmpout; my $verbOutput; my $debugOutput; my $no_svm=0; my $svm_status = 'OK'; my $lvm_status = 'OK'; my $md_status = 'OK'; my $zfs_status = 'OK'; my $vxvm_status = 'OK'; my @mdadm_paths = ( "/usr/sbin/mdadm", "/sbin/mdadm", "/usr/bin/mdadm", "/bin/mdadm" ); my @dmsetup_paths = ( "/usr/sbin/dmsetup", "/sbin/dmsetup", "/usr/bin/dmsetup", "/bin/dmsetup" ); my @lvm_paths = ( "/etc/lvm/backup", "/etc/lvm2/backup" ); my @vxdg_paths = ( "/sbin/vxdg", "/usr/sbin/vxdg" ); my @vxprint_paths = ( "/sbin/vxprint", "/usr/sbin/vxprint" ); my $vglist=""; sub _debug($) { print "$_[0]\n" if(defined $debugOutput); } sub _msg($) { $msg .= $_[0]; } sub _verbosemsg($) { $msg .= $_[0] if(defined $verbOutput); } # change the global status in a consistent way sub change_status($) { if($status ne "CRITICAL") { $status = $_[0]; } } # prints help message sub print_help() { printf "$PROGNAME: Nagios plugin for monitoring Volume Managers on Linux (LVM, MD) and Solaris (SVM, ZFS)\n"; printf "\nUsage:\n"; printf " -V (--version) Plugin version\n"; printf " -v (--verbose) Produces a more verbose response (default is not verbose)\n"; printf " -D (--debug) Print debug informations on STDOUT (to be called by cli)\n"; printf " -S (--snmpout) Produces an output that can be consumed by check-snmp-extend\n"; printf " -L (--vg-list) List of volume-groups to check (currently for LVM only)\n"; printf " -h (--help) Usage help \n\n"; } # stdin parameters parsing sub parse_options() { my $opt_h ; my $opt_V ; my $status; $status = GetOptions( "V" => \$opt_V, "version" => \$opt_V, "v" => \$verbOutput, "verbose" => \$verbOutput, "D" => \$debugOutput, "debug" => \$debugOutput, "S" => \$snmpout, "snmpout" => \$snmpout, "L=s" => \$vglist, "vg-list=s" => \$vglist, "h" => \$opt_h, "help" => \$opt_h ); if ($status == 0){ print_help(); exit 1; } if ($opt_V) { print("\n\n$PROGNAME - version: $VERSION\nAlberto Menichetti @ TAI\n\n"); exit 1; } if ($opt_h) { print_help(); exit 1; } return(0); } ################### ZFS CHECKS ####################################################################################################################################################### # checks all the pools it finds on the system sub check_zfs() { my @pool_list = `/usr/sbin/zpool list -H -o name`; _debug("POOL LIST:\n@pool_list"); my $pool_status = ""; if(($pool_list[0] =~ /no\s+pools\s+available/)||($pool_list[0] =~ /^$/)) { _msg("No ZFS volumes found - "); # executes the check foreach pool } else { foreach(@pool_list) { chomp($_); $pool_status = `/usr/sbin/zpool list -H -o health $_`; chomp($pool_status); _debug("POOL $_ STATUS: $pool_status"); if($pool_status !~ /online/i) { _msg("zpool $_: status $pool_status - "); change_status("CRITICAL"); $zfs_status = "CRITICAL"; } else { _verbosemsg("zpool $_: status $pool_status - "); } } if($zfs_status eq "OK" && not defined $verbOutput) { _msg("ZFS Status is OK - "); } } } ###################################################################################################################################################################################### ################### SVM/SDS CHECKS ################################################################################################################################################### # checks all SVM objects it finds on the system; for shared disksets, only currently imported disksets are checked sub check_svm() { my $mset = ""; my $nodename = `hostname`; _debug("HOSTNAME: $nodename"); chomp($nodename); my @metasets; if ( `zonename` !~ /global/ ) { $msg .= "Can't check SVM on Solaris Containers - "; return; } process_metadb(""); # process the default metadb configuration return if($no_svm); # exit if SVM is not used process_metastat(""); # process the default metastat configuration @metasets = `ls -l /dev/md | awk '{print \$9}'`; foreach(@metasets) { next if(/^$/); next if(/^admin$/); next if(/^shared$/); next if(/^r{0,1}dsk$/); chomp($_); _debug("=== METASET: $_ ==="); open METASETS, "-|", "/usr/sbin/metaset -s $_ 2>&1"; $mset = $_; while() { last if($_ =~ /no\s+such\s+set$/); if($_ =~ /$nodename\s+Yes$/i) { _debug("node is current owner of $mset"); process_metadb($mset); process_metastat($mset); } } close(METASETS); } if($svm_status eq "OK" && not defined $verbOutput) { _msg("SVM Status is OK - "); } } # parses the output produced by metadb command sub process_metadb($) { my $metaset = shift; my $metadb_cmd; my $flags = ""; my $device = ""; # my %devhm; if($metaset) { $metadb_cmd = "/usr/sbin/metadb -s $metaset 2>&1"; _debug("processing metadb for $metaset metaset"); } else { $metadb_cmd = "/usr/sbin/metadb 2>&1"; _debug("processing metadb for default metaset"); } open METADB, "-|", "$metadb_cmd" || die "can't run: $!"; while () { chomp($_); if($_ =~ /there\s+are\s+no\s+existing\s+databases/) { # SVM not configured _msg("No SVM volumes found - "); $no_svm=1; last; } next if ( $_ =~ /flags/ ); # skip the initial comment line _debug("METADB LINE: $_"); $flags = substr($_, 0, 20); $_ =~ /(\/dev\/.*dsk\/.+)$/; $device = $1; _debug("DEVICE:$device - FLAGS:$flags"); # if(! defined $devhm{$device}) { # $devhm{$device} = 1; if($flags =~ /[A-Z]/) { change_status("CRITICAL"); $svm_status = "CRITICAL"; if($metaset) { _msg("SVM metadb $device in metaset $metaset has errors - "); } else { _msg("SVM metadb $device has errors - "); } } else { if($metaset) { _verbosemsg("SVM metadb $device in $metaset: OK - "); } else { _verbosemsg("SVM metadb $device: OK - "); } } # } else { _debug("skipping already checked device: $device"); } } close(METADB); } # parses the output produced by metastat command sub process_metastat($) { my $metaset = shift; my $metastat_cmd; my $mirror = ""; my $softpartition = ""; my $device = ""; my $submirror = ""; my $concat = ""; my $objstate = ""; my $nextl = ""; my @tmparr; if($metaset) { $metastat_cmd = "/usr/sbin/metastat -s $metaset 2>&1"; _debug("processing metastat for $metaset metaset"); } else { $metastat_cmd = "/usr/sbin/metastat 2>&1"; _debug("processing metastat for default metaset"); } open METASTAT, "-|", "$metastat_cmd" || die "can't run: $!"; while () { chomp(); if($_ =~ /^(.+):\s+Mirror$/) { # mirror object _debug("MIRROR LINE: $_"); $mirror=$1; while(($nextl = ) !~ /^$/ ) { if($nextl =~ /^\s+Submirror\s+\d+:\s+(.+)$/) { # submirror object _debug("SUBMIRROR LINE: $nextl"); $submirror=$1; } elsif($nextl =~ /^\s+State:\s+(.+)$/) { # submirror status _debug("SUBMIRROR STATE: $nextl"); $objstate=$1; if($objstate !~ /okay/i) { change_status("CRITICAL"); $svm_status = "CRITICAL"; _msg("SVM mirror: $mirror, submirror: $submirror, state: $objstate - "); } _verbosemsg("SVM mirror: $mirror, submirror: $submirror, state: OK - "); } } }elsif($_ =~ /^(.+):\s+Soft\s+Partition$/) { # softpartition object _debug("SOFTPARTITION LINE: $_"); $softpartition=$1; while(($nextl = ) !~ /^$/ ) { _debug("SOFTPARTITION LINE: $nextl"); if($nextl =~ /^\s+Device:\s+(.+)$/) { # device which composes the softpartition $device=$1; }elsif($nextl =~ /^\s+State:\s+(.+)$/) { # softpartition status $objstate=$1; _debug("SOFTPARTITION:$softpartition - DEVICE:$device - STATUS:$objstate"); if($objstate !~ /okay/i) { change_status("CRITICAL"); $svm_status = "CRITICAL"; _msg("SVM soft-partition: $softpartition, device: $device, state: $objstate - "); } _verbosemsg("SVM soft-partition: $softpartition, device: $device, state: OK - "); } } }elsif($_ =~ /^(.+):\s+Concat\/Stripe$/) { # concat/stripe object _debug("CONCAT LINE: $_"); $concat=$1; while(($nextl = ) !~ /^$/ ) { _debug("CONCAT LINE: $nextl"); if($nextl =~ /^\s+Device\s+Start\s+Block\s+Dbase\s+State\s+Reloc\s+Hot\s+Spare$/) { @tmparr = split('\s+',); $device = $tmparr[1]; # device which composes the concat/stripe $objstate = $tmparr[4]; # status _debug("CONCAT:$concat - DEVICE:$device - STATUS:$objstate"); if($objstate !~ /okay/i) { change_status("CRITICAL"); $svm_status = "CRITICAL"; _msg("SVM concat: $concat, device: $device, state: $objstate - "); } _verbosemsg("SVM concat: $concat, device: $device, state: OK - "); } } } } close(METASTAT); } ###################################################################################################################################################################################### ################### VXVM CHECKS ###################################################################################################################################################### # checks all VxVM volumes it finds on the system; for shared diskgroups, only currently imported diskgroups are checked sub check_vxvm() { my $VXDGCMD = ""; my $VXPRINTCMD = ""; my $dg; my $vol; my $kstate; my $state; my $volcount = 0; if ( `zonename` !~ /global/ ) { _msg("Can't check VxVM on Solaris Containers - "); return; } # verifies vxdg command presence foreach(@vxdg_paths) { if( -e $_ ) { $VXDGCMD = $_; _debug("using vxdg in $VXDGCMD"); last; } } if($VXDGCMD eq "") { _msg("VxVM not configured on this system (vxdg command not found) - "); return; } # verifies vxprint command presence foreach(@vxprint_paths) { if( -e $_ ) { $VXPRINTCMD = $_; _debug("using vxprint in $VXPRINTCMD"); last; } } if($VXPRINTCMD eq "") { _msg("VxVM not configured on this system (vxprint command not found) - "); return; } open DGLIST, "-|", "$VXDGCMD -q list 2>&1" || die "can't run: $!"; while() { $_ =~ /^(\S+)\s+.+$/; $dg = $1; _debug("=== DG: $dg ==="); open VOLLIST, "-|", "$VXPRINTCMD -qvt -g $dg 2>&1" || die "can't run: $!"; # VOL KSTATES: { ENABLED, DISABLED, DETACHED } # VOL STATES: { ACTIVE, EMPTY, CLEAN, INVALID, NEEDSYNC, REPLAY, SYNC } while() { if ($_ =~ /^v\s+(\S+)\s+-\s+(\S+)\s+(\S+)/) { $volcount++; $vol = $1; $kstate = $2; $state = $3; _debug("VOL:$vol - KSTATE:$kstate - STATE:$state"); # volume disabled if ($kstate =~ /DISABLED/) { # EMPTY: il volume non e' ancora stato inizializzato # CLEAN: il volume nno รจ stato avviato if(($state =~ /EMPTY/)||($state =~ /CLEAN/)) { _verbosemsg("Vol $vol(KSTATE:$kstate STATE:$state) OK - "); } else { _msg("Vol $vol(KSTATE:$kstate STATE:$state) CRITICAL - "); change_status("CRITICAL"); $vxvm_status = "CRITICAL"; } # volume in maintenance } elsif ($kstate =~ /DETACHED/) { _msg("Vol $vol(KSTATE:$kstate STATE:$state) WARNING - "); change_status("WARNING"); $vxvm_status = "WARNING"; # volume enabled } elsif ($kstate =~ /ENABLED/) { if($state =~ /ACTIVE/) { _verbosemsg("Vol $vol(KSTATE:$kstate STATE:$state) OK - "); } else { _msg("Vol $vol(KSTATE:$kstate STATE:$state) WARNING - "); change_status("WARNING"); $vxvm_status = "WARNING"; } # volume kstate non riconosciuto } else { _msg("Vol $vol(KSTATE:$kstate STATE:$state) KSTATE UNKNOWN - "); change_status("UNKNOWN"); $vxvm_status = "UNKNOWN"; } } } close(VOLLIST); } close(DGLIST); _debug("Vol count: $volcount"); if($volcount == 0) { _msg("No VxVM volumes found - "); } elsif(($vxvm_status eq "OK") && (! defined $verbOutput)) { _msg("VxVM status is OK - "); } } ###################################################################################################################################################################################### ################### LINUX LVM CHECKS ################################################################################################################################################ # checks all LVM2 volumes it finds on the system sub check_lvm() { my $DMSETUPCMD = ""; my $cfg_dir = ""; my @tmp_arr; my @tmp_arr2; my $volgroup=""; my $logicalvolume=""; my $lvol_flag = 0; my $open_brackets = 0; my $state_ok = 0; foreach(@lvm_paths) { if( -d $_) { $cfg_dir = $_; _debug("using configuration directory $cfg_dir"); last; } } if($cfg_dir eq "") { _msg("LVM not configured on this system (config not found) - "); return; } if($vglist ne "") { @tmp_arr2 = split(',', $vglist); foreach(@tmp_arr2) { push(@tmp_arr, $cfg_dir . "/" . $_); } } else { @tmp_arr = `find $cfg_dir -type f 2>/dev/null`; } if(!@tmp_arr) { _msg("LVM not configured on this system (config dir is empty) - "); return; } foreach(@dmsetup_paths) { if( -e $_ ) { $DMSETUPCMD = $_; _debug("using dmsetup in $DMSETUPCMD"); last; } } if($DMSETUPCMD eq "") { _msg("LVM not configured on this system (dmsetup not found) - "); return; } foreach(@tmp_arr) { $_ =~ /.+\/([^\/]+)$/; $volgroup = $1; chomp($volgroup); _debug("VOLGROUP: $volgroup"); open(VGCONFIG , "< $_") || die "can't open: $! "; $lvol_flag = 0; $open_brackets = 0; while() { if($_ =~ /\s+logical_volumes\s+{/) { $lvol_flag = 1; $open_brackets++; } elsif($lvol_flag && $open_brackets == 1 && $_ =~ /\s+(\S+)\s+{\s*$/) { $open_brackets++; $logicalvolume = $1; chomp($logicalvolume); _debug("found LVM device $logicalvolume, volume-group $volgroup"); open DMSETUPOUT, "-|", "$DMSETUPCMD info $volgroup-$logicalvolume 2>/dev/null" || die "can't run: $!"; $state_ok = 0; while() { if($_ =~ /^\s*State:\s+ACTIVE\s*$/) { $state_ok = 1; last; } } close(DMSETUPOUT); if(! $state_ok) { $lvm_status = "CRITICAL"; change_status("CRITICAL"); _msg("LVM $logicalvolume (VG $volgroup): status CRITICAL - "); } else { _verbosemsg("LVM $logicalvolume (VG $volgroup): status OK - "); } } elsif($_ =~ /\s+(\S+)\s+{\s*$/) { $open_brackets++; } elsif($_ =~ /^\s+}\s+$/) { $open_brackets--; } } close(VGCONFIG); } if($lvm_status eq "OK" && not defined $verbOutput) { $msg .= "LVM Status is OK - "; } } ###################################################################################################################################################################################### ################### LINUX MD CHECKS ################################################################################################################################################## # checks all MD volumes it finds on the system sub check_md() { my $MDADMCMD = ""; my $device = ""; my $subdevice = ""; my $device_status = ""; my $subdevice_status = ""; my $raid_type = ""; my $subdevice_role = ""; my $raid_count = 0; my $lines = 0; foreach(@mdadm_paths) { if( -e $_ ) { $MDADMCMD = $_; _debug("using mdadm in $MDADMCMD"); last; } } if($MDADMCMD eq "") { _msg("MD not configured on this system (mdadm not found) - "); return; } open MDSCAN, "-|", "$MDADMCMD --detail --scan --verbose 2>&1" || die "can't run: $!"; while() { $lines++; if($_ =~ /^ARRAY/) { $_ =~ /^ARRAY\s+(\S+)\s+level=(\S+)\s+/; $device = $1; $raid_type = $2; $raid_count++; _debug("found MD device $device, raid-level $raid_type"); open MDDETAIL, "-|", "$MDADMCMD --detail $device" || die "can't run: $!"; while() { if($_ =~ /\s+State\s+:\s+(.+)$/) { # md device status $device_status = $1; chomp($device_status); _debug("device status: $device_status"); if($device_status !~ /clean$/) { change_status("CRITICAL"); $md_status = "CRITICAL"; _msg("MD $device ($raid_type): status $device_status - "); } else { _verbosemsg("MD $device ($raid_type): status OK - "); } } elsif($_ =~ /^\s+(\d+|-)\s+(\d+|-)\s+(\d+|-)\s+(\d+|-)\s+(\w+)\s+(\w+)\s+(\S+)\s*$/) { $subdevice_status = $5; $subdevice_role = $6; $subdevice = $7; _debug("device:$device - subdevice:$subdevice - role:$subdevice_role - status:$subdevice_status"); if($subdevice_status !~ /active/) { change_status("CRITICAL"); $md_status = "CRITICAL"; _msg("MD $device, sub-device $subdevice ($subdevice_role): status $subdevice_status - "); } else { _verbosemsg("MD $device, sub-device $subdevice ($subdevice_role): status OK - "); } } } close(MDDETAIL); } } close(MDSCAN); # no md devices found, but command output wasn't empty if($raid_count == 0 && $lines > 0) { _msg("MD status is UNKNOWN (can't get configuration info) - "); } elsif($raid_count == 0) { _msg("MD not configured on this system - "); } elsif($md_status eq "OK" && not defined $verbOutput) { _msg("MD Status is OK - "); } } ###################################################################################################################################################################################### #### MAIN START HERE #### parse_options(); # OS-Type my $OS = `uname -s`; chomp($OS); _debug("UNAME: $OS"); if(! $OS) { _msg("Unable to determine current OS"); $status = "UNKNOWN"; goto _FORMATOUTPUT; } if($OS =~ /sunos/i) { print "OS is Solaris\n" if(defined $debugOutput); check_zfs(); check_svm(); check_vxvm(); } elsif($OS =~ /linux/i) { print "OS is Linux\n" if(defined $debugOutput); unless ($> == 0 || $< == 0) { _debug("UID: $>"); _msg("$PROGNAME must be run as root - "); $status = "UNKNOWN"; goto _FORMATOUTPUT; } check_md(); check_lvm(); } _FORMATOUTPUT: chop($msg); chop($msg); chop($msg); if(defined $snmpout) { _debug("\n\nTEXT MSG: $msg#$status\n"); print "$msg#$status"; exit(0); } else { _debug("\n\nTEXT MSG: $msg\n"); print "$msg\n"; exit($ERRORS{$status}); }