#! /usr/bin/perl -w

# Check Microsoft Cluster Server (MSCS) 
# via HP Management Agents for Server 
#
# Plugin uses SNMP connection to server (CPQCLUS1.MIB).
#
# Copyright (C) 2007 by Herbert Stadler
# email: hestadler@gmx.at

# License Information:
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License 
# along with this program; if not, see <http://www.gnu.org/licenses/>. 
#
#

############################################################################


use POSIX;
use strict;
use Getopt::Long;
#use Data::Dumper;

use lib ".";
use lib "/usr/lib/nagios/plugins";
use lib "/usr/lib64/nagios/plugins";
use lib "/usr/local/nagios/libexec";

use utils qw(%ERRORS);
use Net::SNMP qw(oid_lex_sort oid_base_match :debug);

my ($opt_version,$opt_help,$opt_verbose,$opt_pgo);
my ($opt_addhosts,$opt_timeout,$opt_license,$opt_quorumdisk);
my ($opt_hostname,$opt_community,$opt_port,$opt_snmpvers);
my ($opt_snmpdebug,$opt_snmptimeout);
my ($PROGNAME,$REVISION);
my ($state,$msg);
my ($g_snmpdebug);
my (@t_pgo);

my $DEFAULT_TIMEOUT			=30;
my $DEFAULT_SNMPTIMEOUT			=2;

# MIB OID's of CPQCLUS1.MIB
my $Cluster_oid 			="1.3.6.1.4.1.232.15";

my $ClMibRev_group			="1.3.6.1.4.1.232.15.1";
my  $ClMibRevMajor_scalar		="1.3.6.1.4.1.232.15.1.1.0";
my  $ClMibRevMinor_scalar		="1.3.6.1.4.1.232.15.1.2.0";
my  $ClMibCondition_scalar		="1.3.6.1.4.1.232.15.1.3.0";

my $ClOsCommon_group			="1.3.6.1.4.1.232.15.2.1.4";
my  $ClOsCommonPollFreq_scalar		="1.3.6.1.4.1.232.15.2.1.4.1.0";
my  $ClOsCommonModuleTable		="1.3.6.1.4.1.232.15.2.1.4.2";
my   $ClOsCommonModuleEntry		="1.3.6.1.4.1.232.15.2.1.4.2.1";
my    $ClOsCommonModuleIndex_tabular	="1.3.6.1.4.1.232.15.2.1.4.2.1.1";
my    $ClOsCommonModuleName_tabular	="1.3.6.1.4.1.232.15.2.1.4.2.1.2";
my    $ClOsCommonModuleVersion_tabular	="1.3.6.1.4.1.232.15.2.1.4.2.1.3";
my    $ClOsCommonModuleDate_tabular	="1.3.6.1.4.1.232.15.2.1.4.2.1.4";
my    $ClOsCommonModulePurpose_tabular	="1.3.6.1.4.1.232.15.2.1.4.2.1.5";

my $ClInfo_group			="1.3.6.1.4.1.232.15.2.2";
my  $ClName_scalar			="1.3.6.1.4.1.232.15.2.2.1.0";
my  $ClCondition_scalar			="1.3.6.1.4.1.232.15.2.2.2.0";
my  $ClIpAddress_scalar			="1.3.6.1.4.1.232.15.2.2.3.0";
my  $ClQuorumResource_scalar		="1.3.6.1.4.1.232.15.2.2.4.0";
my  $ClMajorVersion_scalar		="1.3.6.1.4.1.232.15.2.2.5.0";
my  $ClMinorVersion_scalar		="1.3.6.1.4.1.232.15.2.2.6.0";
my  $ClCSDVersion_scalar		="1.3.6.1.4.1.232.15.2.2.7.0";
my  $ClVendorId_scalar			="1.3.6.1.4.1.232.15.2.2.8.0";
my  $ClResourceAggregateCondition_scalar="1.3.6.1.4.1.232.15.2.2.9.0";
my  $ClNetworkAggregateCondition_scalar	="1.3.6.1.4.1.232.15.2.2.10.0";

my $ClNode_group			="1.3.6.1.4.1.232.15.2.3";
my  $ClNodeTable			="1.3.6.1.4.1.232.15.2.3.1";
my   $ClNodeEntry			="1.3.6.1.4.1.232.15.2.3.1.1";
my    $ClNodeIndex_tabular		="1.3.6.1.4.1.232.15.2.3.1.1.1";
my    $ClNodeName_tabular		="1.3.6.1.4.1.232.15.2.3.1.1.2";
my    $ClNodeStatus_tabular		="1.3.6.1.4.1.232.15.2.3.1.1.3";
my    $ClNodeCondition_tabular		="1.3.6.1.4.1.232.15.2.3.1.1.4";

my $ClResource_group			="1.3.6.1.4.1.232.15.2.4";
my  $ClResourceTable			="1.3.6.1.4.1.232.15.2.4.1";
my   $ClResourceEntry			="1.3.6.1.4.1.232.15.2.4.1.1";
my    $ClResourceIndex_tabular		="1.3.6.1.4.1.232.15.2.4.1.1.1";
my    $ClResourceName_tabular		="1.3.6.1.4.1.232.15.2.4.1.1.2";
my    $ClResourceType_tabular		="1.3.6.1.4.1.232.15.2.4.1.1.3";
my    $ClResourceState_tabular		="1.3.6.1.4.1.232.15.2.4.1.1.4";
my    $ClResourceOwnerNode_tabular	="1.3.6.1.4.1.232.15.2.4.1.1.5";
my    $ClResourcePhysId_tabular		="1.3.6.1.4.1.232.15.2.4.1.1.6";
my    $ClResourceCondition_tabular	="1.3.6.1.4.1.232.15.2.4.1.1.7";
my    $ClResourceDriveLetter_tabular	="1.3.6.1.4.1.232.15.2.4.1.1.8";
my    $ClResourceIpAddress_tabular	="1.3.6.1.4.1.232.15.2.4.1.1.9";
my    $ClResourceGroupName_tabular	="1.3.6.1.4.1.232.15.2.4.1.1.10";

my $ClInterconnect_group		="1.3.6.1.4.1.232.15.2.5";
my  $ClInterconnectTable		="1.3.6.1.4.1.232.15.2.5.1";
my   $ClInterconnectEntry		="1.3.6.1.4.1.232.15.2.5.1.1";
my    $ClInterconnectIndex_tabular	="1.3.6.1.4.1.232.15.2.5.1.1.1";
my    $ClInterconnectPhysId_tabular	="1.3.6.1.4.1.232.15.2.5.1.1.2";
my    $ClInterconnectTransport_tabular	="1.3.6.1.4.1.232.15.2.5.1.1.3";
my    $ClInterconnectAddress_tabular	="1.3.6.1.4.1.232.15.2.5.1.1.4";
my    $ClInterconnectNetworkName_tabular="1.3.6.1.4.1.232.15.2.5.1.1.5";
my    $ClInterconnectNodeName_tabular	="1.3.6.1.4.1.232.15.2.5.1.1.6";
my    $ClInterconnectRole_tabular	="1.3.6.1.4.1.232.15.2.5.1.1.7";

my $ClNetwork_group			="1.3.6.1.4.1.232.15.2.6";
my  $ClNetworkTable			="1.3.6.1.4.1.232.15.2.6.1";
my   $ClNetworkEntry			="1.3.6.1.4.1.232.15.2.6.1.1";
my    $ClNetworkIndex_tabular		="1.3.6.1.4.1.232.15.2.6.1.1.1";
my    $ClNetworkName_tabular		="1.3.6.1.4.1.232.15.2.6.1.1.2";
my    $ClNetworkAddressMask_tabular	="1.3.6.1.4.1.232.15.2.6.1.1.3";
my    $ClNetworkDescription_tabular	="1.3.6.1.4.1.232.15.2.6.1.1.4";
my    $ClNetworkRole_tabular		="1.3.6.1.4.1.232.15.2.6.1.1.5";
my    $ClNetworkState_tabular		="1.3.6.1.4.1.232.15.2.6.1.1.6";
my    $ClNetworkCondition_tabular	="1.3.6.1.4.1.232.15.2.6.1.1.7";


# Textual description of the different values
my %ClMibCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );

my %ClCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );

my %ClResourceAggregateCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );

my %ClNetworkAggregateCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );

my %ClNodeStatus_val = (
	1 =>		"other",
	2 =>		"nodeUp",
	3 =>		"nodeDown",
	4 =>		"nodePaused",
	5 =>		"nodeJoining",
   );

my %ClNodeCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );

my %ClResourceState_val = (
	1 =>		"other",
	2 =>		"online",
	3 =>		"offline",
	4 =>		"failed",
	5 =>		"onlinePending",
	6 =>		"offlinePending",
   );

my %ClResourceCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );

my %ClInterconnectRole_val = (
	1 =>		"none",
	2 =>		"client",
	3 =>		"internal",
	4 =>		"clientAndInternal",
   );

my %ClNetworkRole_val = (
	1 =>		"none",
	2 =>		"client",
	3 =>		"internal",
	4 =>		"clientAndInternal",
   );

my %ClNetworkState_val = (
	1 =>		"other",
	2 =>		"online",
	3 =>		"offline",
	4 =>		"partitioned",
	5 =>		"unavailable",
   );

my %ClNetworkCondition_val = (
	1 =>		"other",
	2 =>		"ok",
	3 =>		"degraded",
	4 =>		"failed",
   );



$ENV{'PATH'}='';
$ENV{'BASH_ENV'}=''; 
$ENV{'ENV'}='';
$PROGNAME = "check_mscs_hpma";
$REVISION = "1.8";

# checking commandline arguments
my $arg_status = check_args();
if ($arg_status){
  print "ERROR: some arguments wrong\n";
  exit $ERRORS{"UNKNOWN"};
}

if ($opt_snmpdebug) {
  my $module="Net::SNMP";
  printf( "%-20s  Version: %s\n", $module, $module->VERSION );
}

# set alarmhandler for timeouthandling
$SIG{'ALRM'} = sub {
  print ("ERROR: plugin timed out after $opt_timeout seconds \n");
  exit $ERRORS{"UNKNOWN"};
};

alarm($opt_timeout);

# build array of hostnames in the cluster
my @t_hostnames;
push @t_hostnames,$opt_hostname;
if (defined $opt_addhosts){
  my @t_temphosts=split(/,/,$opt_addhosts);  
  foreach (@t_temphosts) {
    push @t_hostnames,$_;
  }
}

# check the hosts if they want to speak with us, maybe some are down
# we take the first who is answering

my $s_found=0;
foreach my $l_hostname (@t_hostnames) {
  my ($snmp_session_tmp,$snmp_error_tmp)=open_snmp_session($l_hostname);
  if ( ! defined ($snmp_session_tmp)) {
    next;
  }

  $snmp_session_tmp->translate(['-endofmibview'=>0,'-nosuchobject'=>0,'-nosuchinstance'=>0]);

  my @oid_tmp;
  push @oid_tmp,$ClName_scalar;
  my $l_snmp_result_tmp=$snmp_session_tmp->get_request(
 	-varbindlist	=>	\@oid_tmp,
  	);
  #if ( ! defined ($l_snmp_result_tmp)) {
  if ($snmp_session_tmp->error_status != 0) {
    $snmp_session_tmp->close;
    next;
  }
  # this one speaks to us, should normally be the host defined with "-H"
  $opt_hostname=$l_hostname;
  $snmp_session_tmp->close;
  $s_found++;
  last;
}
if ($s_found == 0) {
  print "CLUSTER ERROR: Could not open connection to the host(s), all probably down\n";
  exit $ERRORS{'UNKNOWN'};
}

# now we have a host who wants to speak with us
my ($snmp_session,$snmp_error)=open_snmp_session($opt_hostname);
if ( ! defined ($snmp_session)) {
  print "CLUSTER ERROR: Could not open connection: $snmp_error \n";
  exit $ERRORS{'UNKNOWN'};
}

$snmp_session->translate(['-endofmibview'=>0,'-nosuchobject'=>0,'-nosuchinstance'=>0]);

#  get Cluster OS Common Module Table
my $p_ClOsCommonModuleTable=get_cluster_info_get_table_request ($ClOsCommonModuleTable);

#  get Cluster Node Table
my $p_ClNodeTable=get_cluster_info_get_table_request ($ClNodeTable);

#  get Cluster Resource Table
my $p_ClResourceTable=get_cluster_info_get_table_request ($ClResourceTable);

#  get Cluster Interconnect Table
my $p_ClInterconnectTable=get_cluster_info_get_table_request ($ClInterconnectTable);

#  get Cluster Network Table
my $p_ClNetworkTable=get_cluster_info_get_table_request ($ClNetworkTable);

#  Reading necessary OIDs
my $oids=build_oid_table();
my $p_ClOids=get_cluster_info_get_request ($oids);

$snmp_session->close;

# Check if Cluster is installed on server
if ( $p_ClOids->{$ClName_scalar} eq "noSuchObject" ) {
  print "CLUSTER ERROR: Please check Cluster Installation, something wrong!\n";
  exit $ERRORS{'UNKNOWN'};
}

my $l_quorum_holder=get_quorum_holder($p_ClOids->{$ClQuorumResource_scalar});

if ( $opt_verbose ) {
  print_Cluster_Info ();
  print_Cluster_MIB_Revision ();
  print_Cluster_OS_Common ();
  print_Cluster_Node ();
  print_Cluster_Resource ();
  print_Cluster_Interconnect ();
  print_Cluster_Network ();
}

# set standard values for program exit
$msg = "CLUSTER OK - No Problems found";
$state = $ERRORS{'OK'};

my $l_tmsg;

# checking of condition and status flags for the returncode
if ( $ClCondition_val{$p_ClOids->{$ClCondition_scalar}} ne "ok" ) {
  # the overall cluster condition is not OK

  # get some status information from the cluster nodes
  my $l_node_status=get_cluster_node_status();

  # get some status information from the cluster resources
  my $l_resource_status=get_cluster_resource_status();

  # get some status information from the cluster network
  my $l_network_status=get_cluster_network_status();

  # build the output message
  create_msg($l_node_status,\$l_tmsg);
  create_msg($l_resource_status,\$l_tmsg);
  create_msg($l_network_status,\$l_tmsg);

  if ( $ClCondition_val{$p_ClOids->{$ClCondition_scalar}} eq "degraded" ) {
    $state = $ERRORS{'WARNING'};
  } else {
    $state = $ERRORS{'CRITICAL'};
  }
}

# and now we check if some cluster group failed-over to another node
my $l_GroupFailedOver_status=get_GroupFailedOver_status();
if (defined $l_GroupFailedOver_status) {
  create_msg($l_GroupFailedOver_status,\$l_tmsg);
  if ($state == $ERRORS{'OK'}){
    $state = $ERRORS{'WARNING'};
  }
}

if ($state == $ERRORS{'WARNING'}){
  $msg = "CLUSTER WARNING - ".$l_tmsg;
}elsif ($state == $ERRORS{'CRITICAL'}){
  $msg = "CLUSTER CRITICAL - ".$l_tmsg;
}

if ( $opt_quorumdisk ) {
  $msg.=sprintf(" (QuDisk on: %s)",$l_quorum_holder);
}

# and now "over and out"

print "$msg\n";
exit $state;




#--------------------------------------------------------------------------#
# S U B R O U T I N E S                                                    #
#--------------------------------------------------------------------------#

sub open_snmp_session {
  my ($l_host)=@_;

  # open SNMP Session to Server
  my ($snmp_session,$snmp_error)=Net::SNMP->session(
	-hostname 	=> 	$l_host,
	-community 	=> 	$opt_community || 'public',
	-port		=>	$opt_port || 161,
	-timeout	=>	$opt_snmptimeout,
	-retries	=>	3,
	-debug		=>	$g_snmpdebug,
	#-maxmsgsize	=>	16384,
	-maxmsgsize	=>	32768,
	-version	=>	$opt_snmpvers,
	);
  return ($snmp_session,$snmp_error);
}

sub  create_msg {
  my ($l_txt,$l_msg)=@_;

  if (! defined $l_txt) {return};

  if (defined $$l_msg) {
    $$l_msg.=", ";
  }
  $$l_msg.=$l_txt;
}

sub build_oid_table {
  my @l_oids;
  push @l_oids,$ClMibRevMajor_scalar;
  push @l_oids,$ClMibRevMinor_scalar;
  push @l_oids,$ClMibCondition_scalar;
  push @l_oids,$ClOsCommonPollFreq_scalar;
  push @l_oids,$ClName_scalar;
  push @l_oids,$ClCondition_scalar;
  push @l_oids,$ClIpAddress_scalar;
  push @l_oids,$ClQuorumResource_scalar;
  push @l_oids,$ClMajorVersion_scalar;
  push @l_oids,$ClMinorVersion_scalar;
  push @l_oids,$ClCSDVersion_scalar;
  push @l_oids,$ClVendorId_scalar;
  push @l_oids,$ClResourceAggregateCondition_scalar;
  push @l_oids,$ClNetworkAggregateCondition_scalar;

  return \@l_oids;
}

sub get_cluster_info_get_table_request {
  my ($l_oid)=@_;

  my $l_snmp_result=$snmp_session->get_table(
  	-baseoid 	=>	$l_oid
  	);

  #if ( ! defined ($l_snmp_result)) {
  if ($snmp_session->error_status != 0) {
    printf ("ERROR %d: get_table_request: (%s) %s\n",$snmp_session->error_status,$l_oid,$snmp_session->error);
    $snmp_session->close;
    exit $ERRORS{'UNKNOWN'};
  }
  return $l_snmp_result;
}

sub get_cluster_info_get_request {
  my ($l_oid)=@_;

  my $l_snmp_result=$snmp_session->get_request(
 	-varbindlist	=>	$l_oid,
  	);

  #if ( ! defined ($l_snmp_result)) {
  if ($snmp_session->error_status != 0) {
    print "ERROR %d get_request: ",$snmp_session->error_status,$snmp_session->error,"\n";
    $snmp_session->close;
    exit $ERRORS{'UNKNOWN'};
  }
  return $l_snmp_result;
}

sub check_args {
  Getopt::Long::Configure('bundling');
  GetOptions
	("V"   			=> \$opt_version,
	 "version"   		=> \$opt_version,
	 "L"   			=> \$opt_license, 
	 "license"   		=> \$opt_license, 
	 "v"   			=> \$opt_verbose, 
	 "verbose"   		=> \$opt_verbose, 
	 "D"   			=> \$opt_snmpdebug, 
	 "debug"     		=> \$opt_snmpdebug, 
	 "h|?" 			=> \$opt_help,
	 "help"   		=> \$opt_help,
	 "T=i" 			=> \$opt_snmptimeout, 
	 "snmptimeout=i" 	=> \$opt_snmptimeout, 
	 "t=i" 			=> \$opt_timeout, 
	 "timeout=i" 		=> \$opt_timeout, 
	 "H=s" 			=> \$opt_hostname, 
	 "hostname=s" 		=> \$opt_hostname, 
	 "o=s" 			=> \$opt_pgo, 
	 "cgowner=s"		=> \$opt_pgo, 
	 "a=s" 			=> \$opt_addhosts, 
	 "addnodes=s" 		=> \$opt_addhosts, 
	 "C=s" 			=> \$opt_community, 
	 "community=s" 		=> \$opt_community, 
	 "p=i" 			=> \$opt_port, 
	 "port=i" 		=> \$opt_port, 
	 "s=s" 			=> \$opt_snmpvers, 
	 "snmpvers=s" 		=> \$opt_snmpvers, 
	 "Q"   			=> \$opt_quorumdisk, 
	 "qudisk"   		=> \$opt_quorumdisk, 
	 );

  if ($opt_license) {
    print_gpl($PROGNAME,$REVISION);
    exit $ERRORS{'OK'};
  }

  if ($opt_version) {
    print_revision($PROGNAME,$REVISION);
    exit $ERRORS{'OK'};
  }

  if ($opt_help) {
    print_help();
    exit $ERRORS{'OK'};
  }

  if ( ! defined($opt_hostname)){
    print "\nERROR: Hostname not defined\n\n";
    print_usage();
    exit $ERRORS{'UNKNOWN'};
  }

  unless (defined $opt_snmpvers) {
    $opt_snmpvers = "snmpv2c";
  }
  if ( ($opt_snmpvers ne "snmpv1") && ($opt_snmpvers ne "snmpv2c") ){
    print "\nERROR: SNMP version unknown\n\n";
    print_usage();
    exit $ERRORS{'UNKNOWN'};
  }

  unless (defined $opt_timeout) {
    $opt_timeout = $DEFAULT_TIMEOUT;
  }

  unless (defined $opt_snmptimeout) {
    $opt_snmptimeout = $DEFAULT_SNMPTIMEOUT;
  }

  unless (defined $opt_port) {
    $opt_port = 161;
  }

  if (defined $opt_pgo) {
    # Separation of DoubleSlash //
    my @l_hlp1=split(/\/\//,$opt_pgo);
    foreach my $l (@l_hlp1){
      my ($l_node,$l_others)=split(/:/,$l);
      my @l_hlp2=split(/,/,$l_others);
      foreach my $g (@l_hlp2){
        my @tmp=($l_node,$g);
        push @t_pgo,\@tmp;
      } 
    }
  }

  unless (defined $opt_quorumdisk) {
    $opt_quorumdisk = 0;
  }

  if ($opt_snmpdebug) {
    $g_snmpdebug = DEBUG_ALL;
  }else{
    $g_snmpdebug = DEBUG_NONE;
  }

  return $ERRORS{'OK'};
}

sub print_usage {
  print "Usage: $PROGNAME [-h] [-L] [-Q] [-t timeout] [-T snmptimeout] [-v] [-V] [-C community] [-p port] [-s snmpv1|snmpv2c] [-a addhosts] [-o cgroupowner] -H hostname  \n";
}

sub print_help {
  print_revision($PROGNAME,$REVISION);
  print "\n";
  print_usage();
  print "\n";
  print "   Check Microsoft Cluster Server via SNMP\n";
  print "   using HP Managements Agents.\n\n";
  print "-t (--timeout)     Timeout in seconds (default = $DEFAULT_TIMEOUT)\n";
  print "-T (--snmptimeout) SNMP CallTimeout in seconds (default = $DEFAULT_SNMPTIMEOUT)\n";
  print "-H (--hostname)    Host to monitor\n";
  print "-a (--addhosts)    additional hosts in clustergroup '-a host1,host2' \n";
  print "-o (--cgowner)     preferred clustergroup owner e.g: \n";
  print "                   '-o 'node1:group1,group2//node2:group3,group4'\n";
  print "-s (--snmpvers)    SNMP Version [snmpv1|snmpv2c]\n";
  print "-C (--community)   SNMP Community\n";
  print "-p (--port)        SNMP Port\n";
  print "-h (--help)        Help\n";
  print "-Q (--qudisk)      Print information in message on which server QDisk is attached\n";
  print "-V (--version)     Programm version\n";
  print "-v (--verbose)     Print some useful information\n";
  print "-L (--license)     Print license information\n";
  print "\n\n";
}

sub print_Cluster_Info {
  printhead   ("Cluster Info");
  print       ("============\n");
  printscalar ("Cluster Name",           $p_ClOids->{$ClName_scalar});
  printscalar ("Cluster Condition",      $ClCondition_val{$p_ClOids->{$ClCondition_scalar}});
  printscalar ("Cluster IpAddress",      $p_ClOids->{$ClIpAddress_scalar});
  my $l_quorum=get_quorum_resource($p_ClOids->{$ClQuorumResource_scalar});
  printscalar ("Cluster QuorumResource", $l_quorum);
  printscalar ("Cluster SW MajorVersion",$p_ClOids->{$ClMajorVersion_scalar});
  printscalar ("Cluster SW MinorVersion",$p_ClOids->{$ClMinorVersion_scalar});
  printscalar ("Cluster CSDVersion",     $p_ClOids->{$ClCSDVersion_scalar});
  printscalar ("Cluster VendorId",       $p_ClOids->{$ClVendorId_scalar});
  printscalar ("Cluster ResourceAggregateCondition", $ClResourceAggregateCondition_val{$p_ClOids->{$ClResourceAggregateCondition_scalar}});
  printscalar ("Cluster NetworkAggregateCondition", $ClNetworkAggregateCondition_val{$p_ClOids->{$ClNetworkAggregateCondition_scalar}});
}

sub get_quorum_holder {
  my ($l_index)=@_;

  my $l_quorum_holder="";

  if ($l_index =~/^\d+$/) {
    if ($l_index >= 0) {
      $l_quorum_holder=$p_ClResourceTable->{$ClResourceOwnerNode_tabular.".".$l_index};
    }
  }
  return ($l_quorum_holder);
}

sub get_quorum_resource {
  my ($l_index)=@_;

  my $l_quorum="";
  if ($l_index =~/^\d+$/) {
    if ($l_index >= 0) {
      $l_quorum=$p_ClResourceTable->{$ClResourceName_tabular.".".$l_index};
    }
  }
  return $l_quorum;
}

sub print_Cluster_MIB_Revision {
  printhead   ("Cluster MIB Revision Group");
  print       ("==========================\n");
  printscalar ("Cluster MibRevMajor",    $p_ClOids->{$ClMibRevMajor_scalar});
  printscalar ("Cluster MibRevMinor",    $p_ClOids->{$ClMibRevMinor_scalar});
  printscalar ("Cluster MibCondition",   $ClMibCondition_val{$p_ClOids->{$ClMibCondition_scalar}});
}

sub print_Cluster_OS_Common {
  printhead   ("Cluster OS Common Group");
  print       ("=======================\n");
  printscalar ("Os Poll Frequency in sec",$p_ClOids->{$ClOsCommonPollFreq_scalar});
  printtable  ("Cluster OS Common Module Table");
  print       ("==============================\n");
  foreach my $l_key (oid_lex_sort(keys(%$p_ClOsCommonModuleTable))){
    if (!(oid_base_match($ClOsCommonModuleIndex_tabular,$l_key))) {
      next;
    }
    my $l_val=$p_ClOsCommonModuleTable->{$l_key};
    printtabular("Os Common Module Name",    $p_ClOsCommonModuleTable->{$ClOsCommonModuleName_tabular.".".$l_val});
    printtabular("Os Common Module Version", $p_ClOsCommonModuleTable->{$ClOsCommonModuleVersion_tabular.".".$l_val});
    printtabular("Os Common Module Date",    unpackDate($p_ClOsCommonModuleTable->{$ClOsCommonModuleDate_tabular.".".$l_val}));
    printtabular("Os Common Module Purpose", $p_ClOsCommonModuleTable->{$ClOsCommonModulePurpose_tabular.".".$l_val});
    print ("\n");
  }
}

sub print_Cluster_Node {
  printhead   ("Cluster Node Group");
  print       ("==================\n");
  printtable  ("Cluster Node Table");
  print       ("==================\n");
  foreach my $l_key (oid_lex_sort(keys(%$p_ClNodeTable))){
    if (!(oid_base_match($ClNodeIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClNodeTable->{$l_key};
    printtabular("Node Name",     $p_ClNodeTable->{$ClNodeName_tabular.".".$l_val});
    printtabular("Node Status",   $ClNodeStatus_val{$p_ClNodeTable->{$ClNodeStatus_tabular.".".$l_val}});
    printtabular("Node Condition",$ClNodeCondition_val{$p_ClNodeTable->{$ClNodeCondition_tabular.".".$l_val}});
    print ("\n");
  }
}

sub print_Cluster_Resource {
  printhead   ("Cluster Resource Group");
  print       ("======================\n");
  printtable  ("Cluster Resource Table");
  print       ("======================\n");
  foreach my $l_key (oid_lex_sort(keys(%$p_ClResourceTable))){
    if (!(oid_base_match($ClResourceIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClResourceTable->{$l_key};
    printtabular("Resource Name",         $p_ClResourceTable->{$ClResourceName_tabular.".".$l_val});
    printtabular("Resource Type",         $p_ClResourceTable->{$ClResourceType_tabular.".".$l_val});
    printtabular("Resource State",        $ClResourceState_val{$p_ClResourceTable->{$ClResourceState_tabular.".".$l_val}});
    printtabular("Resource Owner Node",   $p_ClResourceTable->{$ClResourceOwnerNode_tabular.".".$l_val});
    printtabular("Resource PhysId",       $p_ClResourceTable->{$ClResourcePhysId_tabular.".".$l_val});
    printtabular("Resource Condition",    $ClResourceCondition_val{$p_ClResourceTable->{$ClResourceCondition_tabular.".".$l_val}});
    printtabular("Resource Drive Letter", $p_ClResourceTable->{$ClResourceDriveLetter_tabular.".".$l_val});
    printtabular("Resource Ip Address",   $p_ClResourceTable->{$ClResourceIpAddress_tabular.".".$l_val});
    printtabular("Resource Group Name",   $p_ClResourceTable->{$ClResourceGroupName_tabular.".".$l_val});
    print ("\n");
  }
}

sub print_Cluster_Interconnect {
  printhead   ("Cluster Interconnect Group");
  print       ("==========================\n");
  printtable  ("Cluster Interconnect Table");
  print       ("==========================\n");
  foreach my $l_key (oid_lex_sort(keys(%$p_ClInterconnectTable))){
    if (!(oid_base_match($ClInterconnectIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClInterconnectTable->{$l_key};
    printtabular("Interconnect PhysId",      $p_ClInterconnectTable->{$ClInterconnectPhysId_tabular.".".$l_val});
    printtabular("Interconnect Transport",   $p_ClInterconnectTable->{$ClInterconnectTransport_tabular.".".$l_val});
    printtabular("Interconnect Address",     $p_ClInterconnectTable->{$ClInterconnectAddress_tabular.".".$l_val});
    printtabular("Interconnect Network Name",$p_ClInterconnectTable->{$ClInterconnectNetworkName_tabular.".".$l_val});
    printtabular("Interconnect Node Name",   $p_ClInterconnectTable->{$ClInterconnectNodeName_tabular.".".$l_val});
    printtabular("Interconnect Role",        $ClInterconnectRole_val{$p_ClInterconnectTable->{$ClInterconnectRole_tabular.".".$l_val}});
    print ("\n");
  }
}

sub print_Cluster_Network {
  printhead   ("Cluster Network Group");
  print       ("=====================\n");
  printtable  ("Cluster Network Table");
  print       ("=====================\n");
  foreach my $l_key (oid_lex_sort(keys(%$p_ClNetworkTable))){
    if (!(oid_base_match($ClNetworkIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClNetworkTable->{$l_key};
    printtabular("Network Name",        $p_ClNetworkTable->{$ClNetworkName_tabular.".".$l_val});
    printtabular("Network Address Mask",$p_ClNetworkTable->{$ClNetworkAddressMask_tabular.".".$l_val});
    printtabular("Network Description", $p_ClNetworkTable->{$ClNetworkDescription_tabular.".".$l_val});
    printtabular("Network Role",        $ClNetworkRole_val{$p_ClNetworkTable->{$ClNetworkRole_tabular.".".$l_val}});
    printtabular("Network State",       $ClNetworkState_val{$p_ClNetworkTable->{$ClNetworkState_tabular.".".$l_val}});
    printtabular("Network Condition",   $ClNetworkCondition_val{$p_ClNetworkTable->{$ClNetworkCondition_tabular.".".$l_val}});
    print ("\n");
  }
}

sub printhead {
  my ($l_head)=@_;

  printf ("\n%-40s\n",$l_head);
}

sub printtable {
  my ($l_head)=@_;

  printf ("%-40s\n",$l_head);
}

sub printscalar {
  my ($l_arg,$l_oid)=@_;

  printf ("%-35s: %-30s\n",$l_arg,$l_oid);
}

sub printtabular {
  my ($l_arg,$l_oid)=@_;

  printf ("%-35s: %-30s\n",$l_arg,$l_oid);
}

sub unpackDate {
  my ($l_octet)=@_;

  # field octets contents range
  # ===== ====== ======= =====
  # 1     1-2    year 0..65536
  # 2     3      month 1..12
  # 3     4      day 1..31
  # 4     5      hour 0..23
  # 5     6      minute 0..59
  # 6     7      second 0..60

  my @l_d=unpack("SCCCCC",$l_octet);

  if ( $l_d[0] == 0 ) {
    return "";
  }

  my $l_rdate=sprintf("%04d-%02-%02d %02d:%02d:%02d",$l_d[0],$l_d[1],$l_d[2],$l_d[3],$l_d[4],$l_d[5]);

  return ($l_rdate);
}

sub print_gpl {
  print <<EOD;

  Copyright (C) 2007 by Herbert Stadler
  email: hestadler\@gmx.at

  License Information:
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 3 of the License, or
  (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public License 
  along with this program; if not, see <http://www.gnu.org/licenses/>. 

EOD

}

sub print_revision {
  my ($l_prog,$l_revision)=@_;

  print <<EOD

$l_prog $l_revision, Copyright (C) 2007 Herbert Stadler

This program comes with ABSOLUTELY NO WARRANTY; 
for details type "$l_prog -L".
EOD
}

sub get_cluster_node_status {
  my $l_txt;

  foreach my $l_key (oid_lex_sort(keys(%$p_ClNodeTable))){
    if (!(oid_base_match($ClNodeIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClNodeTable->{$l_key};

    if ($ClNodeCondition_val{$p_ClNodeTable->{$ClNodeCondition_tabular.".".$l_val}} ne "ok" ) {
      if ( defined $l_txt ) {
        $l_txt.=", ";
      }

      $l_txt.=$p_ClNodeTable->{$ClNodeName_tabular.".".$l_val}.": ".$ClNodeStatus_val{$p_ClNodeTable->{$ClNodeStatus_tabular.".".$l_val}};
    }
  }
  return $l_txt;
}

sub get_cluster_resource_status {
  my $l_txt;

  foreach my $l_key (oid_lex_sort(keys(%$p_ClResourceTable))){
    if (!(oid_base_match($ClResourceIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClResourceTable->{$l_key};

    if ( $ClResourceCondition_val{$p_ClResourceTable->{$ClResourceCondition_tabular.".".$l_val}} ne "ok" ) {
      if ( defined $l_txt ) {
        $l_txt.=", ";
      }

      $l_txt.=$p_ClResourceTable->{$ClResourceName_tabular.".".$l_val}.": ".$ClResourceState_val{$p_ClResourceTable->{$ClResourceState_tabular.".".$l_val}};
    }
  }
  return $l_txt;
}


sub get_cluster_network_status {
  my $l_txt;

  foreach my $l_key (oid_lex_sort(keys(%$p_ClNetworkTable))){
    if (!(oid_base_match($ClNetworkIndex_tabular,$l_key))) {
      next;
    }

    my $l_val=$p_ClNetworkTable->{$l_key};

    if ($ClNetworkCondition_val{$p_ClNetworkTable->{$ClNetworkCondition_tabular.".".$l_val}} ne "ok" ) {
      if ( defined $l_txt ) {
        $l_txt.=", ";
      }

      $l_txt.=$p_ClNetworkTable->{$ClNetworkName_tabular.".".$l_val}.": ".$ClNetworkState_val{$p_ClNetworkTable->{$ClNetworkState_tabular.".".$l_val}};
    }
  }
  return $l_txt;
}

sub get_GroupFailedOver_status {
  my $l_txt;
  my %h_pgo;

  foreach my $l_key (oid_lex_sort(keys(%$p_ClResourceTable))){
    if (!(oid_base_match($ClResourceIndex_tabular,$l_key))) {
      next;
    }
    my $l_val=$p_ClResourceTable->{$l_key};
    my $l_ro =$p_ClResourceTable->{$ClResourceOwnerNode_tabular.".".$l_val};
    my $l_rg =$p_ClResourceTable->{$ClResourceGroupName_tabular.".".$l_val};
    $h_pgo{$l_ro}{$l_rg}="dummy";
  }

  foreach my $x (@t_pgo) {
    #print "$$x[0],$$x[1]\n";
    # $$x[0] = owner node
    # $$x[1] = group name
    if (defined $h_pgo{$$x[0]}{$$x[1]}){
      next;
    } else {
      if (defined $l_txt ) {
        $l_txt.=", ";
      }

      if ( ! defined $l_txt ) {
        $l_txt.="FailedOver: ";
      }
      $l_txt.=$$x[1];
    }
  }
  return $l_txt;
}


#__END__


=head1 NAME

 check_mscs_hpma

=head1 DESCRIPTION

 Checking Microsoft Cluster Server via SNMP protocol.

 HP Managements Agents have to be installed on the Server.
 Plugin created for Nagios Monitoring.

=head1 SYNOPSIS

 check_mscs_hpma -H <hostname> 

 To check some additional hosts in the cluster group (in case of a 
 server down of the primary node).

     check_mscs_hpma -H <hostname> -a <hostname1>,<hostname2>

 To check if a cluster group is running on the preferred node, 
 you can enter nodes with the defined groups:

     check_mscs_hpma -H <hostname>
                     -o "node1:group1,group2//node2:group3,group4"

 If group1 is not running on node1 a warning will be given to nagios.

 for more information call:

     check_mscs_hpma -h


=head1 AUTHOR

 Herbert Stadler, Austria (hestadler@gmx.at)
 December 2007

 This plugin is my contribution to the nagios community.

=head1 REQUIRED SOFTWARE

 from search.cpan.org
   Net::SNMP Package   	e.g: Net-SNMP-5.2.0.tar.gz

 Used MIB (not necessary for executing the script)
    CPQCLUS1.MIB

    easy to find on "www.mibdepot.com" (enterprise compaq)

=head1 HOW TO CHECK THE WINDOWS SERVER FUNCTIONALITY

 snmpwalk 172.29.130.201 -v2c -c public enterprises.232.15

 should return a lot of lines like these:

    SNMPv2-SMI::enterprises.232.15.1.1.0 = INTEGER: 1
    SNMPv2-SMI::enterprises.232.15.1.2.0 = INTEGER: 3
    SNMPv2-SMI::enterprises.232.15.1.3.0 = INTEGER: 3
    SNMPv2-SMI::enterprises.232.15.2.1.4.1.0 = INTEGER: 120
    SNMPv2-SMI::enterprises.232.15.2.1.4.2.1.1.0 = INTEGER: 0
    SNMPv2-SMI::enterprises.232.15.2.1.4.2.1.1.1 = INTEGER: 1
    SNMPv2-SMI::enterprises.232.15.2.1.4.2.1.2.0 = STRING: "CPQCLUS.DLL"
    SNMPv2-SMI::enterprises.232.15.2.1.4.2.1.2.1 = STRING: "HOSTMIB.DLL"

 if not, check on windows server if "Clustering Information" 
 is an "Active Agent".

 how can I check this ?
    Control Panel
      HP-Management Agents
        Clustering Information (in Active Agents)

=head1 CONFIGURATION IN NAGIOS

 Copy this plugin to the nagios plugin installation directory 
 e.g.: /usr/lib(64)/nagios/plugins

 COMMAND DEFINITION:

 # "check_mscs_hpma" command definition

 define command{
    command_name    check_mscs_hpma
    command_line    $USER1$/check_mscs_hpma -H $HOSTADDRESS$
    }


 # "check_mscs_hpma_a" command definition with additional cluster nodes

 define command{
    command_name    check_mscs_hpma_a
    command_line    $USER1$/check_mscs_hpma -H $HOSTADDRESS$ -a $ARG1$
    }


 # "check_mscs_hpma_ao" command definition with additional cluster nodes
 #                       and clustergroup checking
 # Please use apostrophes in case you have clustergroup definitions 
 # with spaces (like "SQL Server Group") for option "-o"

 define command{
    command_name    check_mscs_hpma_a
    command_line    $USER1$/check_mscs_hpma -H $HOSTADDRESS$ -a $ARG1$
                    -o "$ARG2"
    }

=head1 PLUGIN HISTORY

 Version 1.8 - 2010-03-24       check error_status of snmp call
 Version 1.7 - 2009-08-31       Checking if Cluster is installed and running
 Version 1.6 - 2009-02-20       Debugging Parameter -D added
                                          Parameter -T added
 Version 1.5 - 2009-02-17       print OID in case of error
                                function get_cluster_info_get_table_request
 Version 1.4 - 2008-11-26       show quorum disk holder (node) in message (flag -Q)
				additional library path added
 Version 1.3 - 2008-09-26       separation of parameters of -o option changed
				from ";" to "//" because ";" is start of
				comment under nagios control
 Version 1.2 - 2007-12-19       fixed problem with **ePN
                                (Missing right curly or square ...)
 Version 1.1 - 2007-12-04	small changes in documentation
 Version 1.0 - 2007-12-01	first release

=head1 COPYRIGHT AND DISCLAIMER

 Copyright (C) 2007 by Herbert Stadler
 email: hestadler@gmx.at

 License Information:
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License 
 along with this program; if not, see <http://www.gnu.org/licenses/>. 
 

=cut
