diff options
40 files changed, 4584 insertions, 250 deletions
@@ -12,7 +12,10 @@ In it's current form, this module can be used on CentOS and Debian. Overview ======== -To use the nagios resources, activate storeconfigs on the puppetmaster. +To use the nagios resources, activate storeconfigs on the +puppetmaster. + +You need to be running verison 0.25 or later of puppet. Monitor @@ -28,8 +31,11 @@ Hosts ----- On a node which shall be monitored with nagios, include the "nagios::target". -This just creates a host declaration for this hosts "$fqdn". -Set the $nagios_parent variable in the node scope for enabling the reachability +This just creates a host declaration for this host's "$ipaddress" fact. If +the $ipaddress of your target is not the one you wish to modify, you can use +"nagios::target::fqdn" instead, which will use the $fqdn fact of the host instead. + +Set the $nagios_parents variable in the node scope for enabling the reachability features of nagios. If a node needs more customisation, use the native "@@nagios_host" type directly (the double-ampersand declares the object as an exported resource). @@ -60,6 +66,37 @@ Obviously, the check command must either be defined using nagios_command objects files directly. +Upgrade Notes +============= + +The nagios::target bits have been reworked, the notable changes that +may affect an upgrade are: + +. previous versions had nagios::target::nat which used the $fqdn for +the address part of nagios::target, this has been renamed to +nagios::target::fqdn to be more clear. if you were using +nagios::target::nat then you will need to change those references to +::fqdn + +. previous versions of this module used $fqdn for the nagios::target +address, now it is using $ipaddress. If you need $fqdn, use +nagios::target::fqdn instead of nagios::target + +. previous versions of nagios_host used the parameter named 'ip', that +has been changed to 'address' + + +IRC bot +======= + +Notifications can easily be sent to an IRC channel by using a bot. To do so, +simply include 'nagios::irc_bot' on the nagios server and define the right +$nagios_nsa_* variables (see the 'Variables' section below). + +You can then use the notification commands 'notify-by-irc' and +'host-notify-by-irc' with service and host definitions to make them report +state changes over IRC. + Caveats ======= @@ -102,6 +139,29 @@ Options to change the behavior of the nagios module: may also need to flip "check_external_commands" in "nagios.cfg" to enable this functionality. +- nagios_nsa_socket: This optional variable can be used to specify the path to + the socket file that the IRC daemon should use. + +- nagios_nsa_server: When using the IRC bot, this defines the server address of + the IRC network on which the bot will connect. + +- nagios_nsa_port: Defines the port number on the IRC server on which the bot + should connect. When this variable is not set, the port used + by default is 6667. + +- nagios_nsa_nickname: This is the nickname that the IRC bot will take. + +- nagios_nsa_password: Some networks require a password to connect to them. + This defines such a password. + +- nagios_nsa_channel: The name of the channel that the IRC bot will join and + will post notifications to. + +- nagios_nsa_pidfile: This optional variable can be used to define the path to + the file that will contain the process ID of the IRC bot + daemon. +- nagios_nsa_realname: The IRC bot user's real name that will be displayed. By + default, the real name is 'Nagios'. Examples ======== @@ -137,8 +197,8 @@ $HOSTADDRESS$' node target { - # Monitor this host - $nagios_parent = 'router01' + # Monitor th () is host + $nagios_parents = 'router01' include nagios::target # monitor a service @@ -167,3 +227,5 @@ License Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at> See the file LICENSE in the top directory for the full license. +Copyright (C) 2010 Riseup Networks <micah@riseup.net> + diff --git a/files/configs/Debian/nagios.cfg b/files/configs/Debian/nagios.cfg index a588f55..8eae393 100644 --- a/files/configs/Debian/nagios.cfg +++ b/files/configs/Debian/nagios.cfg @@ -20,13 +20,14 @@ log_file=/var/log/nagios3/nagios.log # host groups, contacts, contact groups, services, etc. # You can split your object definitions across several config files # if you wish (as shown below), or keep them all in a single config file. +cfg_file=/etc/nagios3/commands.cfg # Puppet-managed configuration files cfg_dir=/etc/nagios3/conf.d # Debian also defaults to using the check commands defined by the debian # nagios-plugins package -#cfg_dir=/etc/nagios-plugins/config +cfg_dir=/etc/nagios-plugins/config diff --git a/files/configs/Debian/private/resource.cfg.x86_64 b/files/configs/Debian/private/resource.cfg.x86_64 index 3ed732b..4d5f0a3 100644..120000 --- a/files/configs/Debian/private/resource.cfg.x86_64 +++ b/files/configs/Debian/private/resource.cfg.x86_64 @@ -1,31 +1 @@ -########################################################################### -# -# RESOURCE.CFG - Resource File for Nagios -# -# You can define $USERx$ macros in this file, which can in turn be used -# in command definitions in your host config file(s). $USERx$ macros are -# useful for storing sensitive information such as usernames, passwords, -# etc. They are also handy for specifying the path to plugins and -# event handlers - if you decide to move the plugins or event handlers to -# a different directory in the future, you can just update one or two -# $USERx$ macros, instead of modifying a lot of command definitions. -# -# The CGIs will not attempt to read the contents of resource files, so -# you can set restrictive permissions (600 or 660) on them. -# -# Nagios supports up to 32 $USERx$ macros ($USER1$ through $USER32$) -# -# Resource files may also be used to store configuration directives for -# external data sources like MySQL... -# -########################################################################### - -# Sets $USER1$ to be the path to the plugins -$USER1$=/usr/lib/nagios/plugins - -# Sets $USER2$ to be the path to event handlers -#$USER2$=/usr/lib/nagios/plugins/eventhandlers - -# Store some usernames and passwords (hidden from the CGIs) -#$USER3$=someuser -#$USER4$=somepassword +resource.cfg.i386
\ No newline at end of file diff --git a/files/configs/apache2.conf b/files/configs/apache2.conf new file mode 100644 index 0000000..14bb38b --- /dev/null +++ b/files/configs/apache2.conf @@ -0,0 +1,55 @@ +# apache configuration for nagios 3.x +# note to users of nagios 1.x and 2.x: +# throughout this file are commented out sections which preserve +# backwards compatibility with bookmarks/config for older nagios versios. +# simply look for lines following "nagios 1.x:" and "nagios 2.x" comments. + +ScriptAlias /cgi-bin/nagios3 /usr/lib/cgi-bin/nagios3 +ScriptAlias /nagios3/cgi-bin /usr/lib/cgi-bin/nagios3 +# nagios 1.x: +#ScriptAlias /cgi-bin/nagios /usr/lib/cgi-bin/nagios3 +#ScriptAlias /nagios/cgi-bin /usr/lib/cgi-bin/nagios3 +# nagios 2.x: +#ScriptAlias /cgi-bin/nagios2 /usr/lib/cgi-bin/nagios3 +#ScriptAlias /nagios2/cgi-bin /usr/lib/cgi-bin/nagios3 + +# Where the stylesheets (config files) reside +Alias /nagios3/stylesheets /etc/nagios3/stylesheets +# nagios 1.x: +#Alias /nagios/stylesheets /etc/nagios3/stylesheets +# nagios 2.x: +#Alias /nagios2/stylesheets /etc/nagios3/stylesheets + +# Where the HTML pages live +Alias /nagios3 /usr/share/nagios3/htdocs +# nagios 2.x: +#Alias /nagios2 /usr/share/nagios3/htdocs +# nagios 1.x: +#Alias /nagios /usr/share/nagios3/htdocs + +<DirectoryMatch (/usr/share/nagios3/htdocs|/usr/lib/cgi-bin/nagios3|/etc/nagios3/stylesheets)> + Options FollowSymLinks + + DirectoryIndex index.php index.html + + AllowOverride AuthConfig + Order Allow,Deny + Allow From All + + AuthName "Nagios Access" + AuthType Basic + AuthUserFile /etc/nagios3/htpasswd.users + # nagios 1.x: + #AuthUserFile /etc/nagios/htpasswd.users + require valid-user +</DirectoryMatch> + +# Enable this ScriptAlias if you want to enable the grouplist patch. +# See http://apan.sourceforge.net/download.html for more info +# It allows you to see a clickable list of all hostgroups in the +# left pane of the Nagios web interface +# XXX This is not tested for nagios 2.x use at your own peril +#ScriptAlias /nagios3/side.html /usr/lib/cgi-bin/nagios3/grouplist.cgi +# nagios 1.x: +#ScriptAlias /nagios/side.html /usr/lib/cgi-bin/nagios3/grouplist.cgi + diff --git a/files/htpasswd.users b/files/htpasswd.users new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/files/htpasswd.users diff --git a/files/irc_bot/riseup-nagios-client.pl b/files/irc_bot/riseup-nagios-client.pl new file mode 100644 index 0000000..2467058 --- /dev/null +++ b/files/irc_bot/riseup-nagios-client.pl @@ -0,0 +1,72 @@ +#!/usr/bin/perl -w + +# ############################################################################## +# Infrabot-Client - a simple Infrabot client which sends it's whole command +# line arguments to a local UNIX domain socket. +# ############################################################################## + +use strict; +use IO::Socket; + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# >> CONFIGURATION >> + +# Read a configuration file +# The arg can be a relative or full path, or +# it can be a file located somewhere in @INC. +sub ReadCfg +{ + my $file = $_[0]; + + our $err; + + { # Put config data into a separate namespace + package CFG; + + # Process the contents of the config file + my $rc = do($file); + + # Check for errors + if ($@) { + $::err = "ERROR: Failure compiling '$file' - $@"; + } elsif (! defined($rc)) { + $::err = "ERROR: Failure reading '$file' - $!"; + } elsif (! $rc) { + $::err = "ERROR: Failure processing '$file'"; + } + } + + return ($err); +} + +# Get our configuration information +if (my $err = ReadCfg('/etc/nagios_nsa.cfg')) { + print(STDERR $err, "\n"); + exit(1); +} + +# << CONFIGURATION << +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +if (@ARGV == 0) { + print "Hey - specify a message, sucker!\n"; + exit(1); +} + +unless (-S $CFG::Nsa{'socket'}) { + die "Socket '$CFG::Nsa{'socket'}' doesn't exist or isn't a socket!\n"; +} + +unless (-r $CFG::Nsa{'socket'}) { + die "Socket '$CFG::Nsa{'socket'}' can't be read!\n"; +} + +my $sock = IO::Socket::UNIX->new ( + Peer => $CFG::Nsa{'socket'}, + Type => SOCK_DGRAM, + Timeout => 10 +) || die "Can't open socket '$CFG::Nsa{'socket'}'!\n"; + +print $sock "@ARGV"; +close($sock); diff --git a/files/irc_bot/riseup-nagios-server.pl b/files/irc_bot/riseup-nagios-server.pl new file mode 100644 index 0000000..7880dde --- /dev/null +++ b/files/irc_bot/riseup-nagios-server.pl @@ -0,0 +1,157 @@ +#!/usr/bin/perl -w + +# ############################################################################## +# a simple IRC bot which dispatches messages received via local domain sockets +# ############################################################################## + +use strict; +use File::Basename; + +BEGIN { + unshift @INC, dirname($0); +} + +my $VERSION = '0.2'; +my $running = 1; + +# Read a configuration file +# The arg can be a relative or full path, or +# it can be a file located somewhere in @INC. +sub ReadCfg +{ + my $file = $_[0]; + + our $err; + + { # Put config data into a separate namespace + package CFG; + + # Process the contents of the config file + my $rc = do($file); + + # Check for errors + if ($@) { + $::err = "ERROR: Failure compiling '$file' - $@"; + } elsif (! defined($rc)) { + $::err = "ERROR: Failure reading '$file' - $!"; + } elsif (! $rc) { + $::err = "ERROR: Failure processing '$file'"; + } + } + + return ($err); +} + +# Get our configuration information +if (my $err = ReadCfg('/etc/nagios_nsa.cfg')) { + print(STDERR $err, "\n"); + exit(1); +} + +use POSIX qw(setsid); +use IO::Socket; +use Net::IRC; + +sub new { + my $self = { + socket => undef, + irc => undef, + conn => undef + }; + + return bless($self, __PACKAGE__); +} + +sub daemonize { + my $self = shift; + my $pid; + + chdir '/' or die "Can't chdir to /: $!"; + + open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; + open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; + + defined ($pid = fork) or die "Can't fork: $!"; + + if ($pid && $CFG::Nsa{'pidfile'}) { # write pid of child + open PID, ">$CFG::Nsa{'pidfile'}" or die "Can't open pid file: $!"; + print PID $pid; + close PID; + } + exit if $pid; + setsid or die "Can't start a new session: $!"; + + #open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; +} + +sub run { + my $self = shift; + + $self->{irc}->do_one_loop(); +} + +sub shutdown { + my $sig = shift; + + print STDERR "Received SIG$sig, shutting down...\n"; + $running = 0; +} + +sub socket_has_data { + my $self = shift; + + $self->{socket}->recv(my $data, 1024); + $self->{conn}->privmsg($CFG::Nsa{'channel'}, $data); +} + +sub irc_on_connect { + my $self = shift; + + print STDERR "Joining channel '$CFG::Nsa{'channel'}'...\n"; + $self->join($CFG::Nsa{'channel'}); +} + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +my $bot = &new; + +if (-e $CFG::Nsa{'socket'}) { + die "Socket '$CFG::Nsa{'socket'}' exists!\n"; +} + +$bot->{socket} = IO::Socket::UNIX->new ( + Local => $CFG::Nsa{'socket'}, + Type => SOCK_DGRAM, + Listen => 5 +) || die "Can't create socket '$CFG::Nsa{'socket'}'!\n"; + +$SIG{INT} = $SIG{TERM} = \&shutdown; + +$bot->daemonize(); +$bot->{irc} = new Net::IRC; + +$bot->{conn} = $bot->{irc}->newconn ( + Server => $CFG::Nsa{'server'}, + Port => $CFG::Nsa{'port'}, + Nick => $CFG::Nsa{'nickname'}, + Username => $CFG::Nsa{'nickname'}, + Password => $CFG::Nsa{'password'}, + Ircname => $CFG::Nsa{'realname'} . " (NSA $VERSION)", +) || die "Can't connect to server '$CFG::Nsa{'server'}'!\n"; + +$bot->{conn}->add_global_handler(376, \&irc_on_connect); +$bot->{conn}->add_global_handler('nomotd', \&irc_on_connect); +$bot->{irc}->addfh($bot->{socket}, \&socket_has_data, 'r', $bot); + +while ($running) { + $bot->run(); +} + +close($bot->{socket}); +unlink($CFG::Nsa{'socket'}); + +exit(0); + +1; + +__END__ diff --git a/files/plugins/check_dns2 b/files/plugins/check_dns2 new file mode 100644 index 0000000..2195631 --- /dev/null +++ b/files/plugins/check_dns2 @@ -0,0 +1,102 @@ +#!/bin/bash +# Written by Damien Gy +# damien.gy+nagiosexchange(AT)gmail.com +# 2007-09-28 + +PROGNAME=`basename $0` +REVISION=1.00 +TMP=/tmp/tmpdig +DIG=/usr/bin/dig + +print_revision() { + echo $PROGNAME $REVISION +} + +print_usage() { + echo "Usage:" + echo " $PROGNAME -c|--check <host> <type> <server>" + echo " $PROGNAME -h|--help" + echo " $PROGNAME -v|--version" +} + +print_help() { + print_revision + echo "" + print_usage + echo "Where:" + echo " host the name of the resource record to be looked up" + echo " type indicates the query required (any, a, mx, etc.)" + echo " server the name or IP address of the name server to query" + echo "" + echo " -h|--help prints this help screen" + echo "" + echo " -v|--version prints version and license information" + echo "" + echo " Created by Damien Gy, questions or problems e-mail damien.gy+nagiosexchange(AT)gmail.com" + echo "" +} + +check_dns() { + + if [ $# -ne 3 ] + then + echo "Number of arguments incorrect" + exit 3 + fi + if [ ! -e $DIG ] + then + echo "$DIG not found" + exit 3 + fi + $DIG $1 $2 @$3 > $TMP + + if ( grep "status" $TMP > /dev/null ) + then + # DNS server answered + if ( grep "NOERROR" $TMP > /dev/null ) + then + echo "DNS OK "`grep "time:" $TMP` + rm -f $TMP + exit 0 + else + echo "WARNING "`grep "time:" $TMP` + rm -f $TMP + exit 1 + fi + + else + # no answer + echo "CRITICAL - Connection timed out" + rm -f $TMP + exit 2 + fi +} + +case "$1" in +--help) + print_help + exit 0 + ;; +-h) + print_help + exit 0 + ;; +--version) + print_revision + exit 0 + ;; +-v) + print_revision + exit 0 + ;; +--check) + check_dns $2 $3 $4 + ;; +-c) + check_dns $2 $3 $4 + ;; +*) + print_usage + exit 3 + +esac diff --git a/files/plugins/check_jabber_login b/files/plugins/check_jabber_login deleted file mode 100644 index dac0e1f..0000000 --- a/files/plugins/check_jabber_login +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env ruby -require 'rubygems' -require 'xmpp4r' - - -def usage - puts "#{$0} jabberid password" - exit 3 -end - -usage unless ARGV.size == 2 - -begin - my_client = Jabber::Client.new(ARGV[0]) - my_client.connect - my_client.auth(ARGV[1]) -rescue Jabber::ClientAuthenticationFailure => detail - puts "CRITICAL: Login Error" - exit 2 -rescue Errno::ECONNREFUSED => detail - puts "CRITICAL: Connection refused" - exit 2 -rescue SocketError => detail - puts "CRITICAL: Socket Error" - exit 2 -#rescue -# puts "CRITICAL: Unknown Error" -# exit 2 -end -puts "OK: Login for #{ARGV[0]} successfull" diff --git a/files/plugins/check_mysql_health b/files/plugins/check_mysql_health new file mode 100755 index 0000000..f0599f2 --- /dev/null +++ b/files/plugins/check_mysql_health @@ -0,0 +1,3342 @@ +#! /usr/bin/perl -w +# nagios: -epn + +my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 ); +my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' ); +package DBD::MySQL::Server::Instance::Innodb; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + internals => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::innodb/) { + $self->{internals} = + DBD::MySQL::Server::Instance::Innodb::Internals->new(%params); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::instance::innodb/) { + $self->{internals}->nagios(%params); + $self->merge_nagios($self->{internals}); + } +} + + +package DBD::MySQL::Server::Instance::Innodb::Internals; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance::Innodb); + +our $internals; # singleton, nur ein einziges mal instantiierbar + +sub new { + my $class = shift; + my %params = @_; + unless ($internals) { + $internals = { + handle => $params{handle}, + bufferpool_hitrate => undef, + wait_free => undef, + log_waits => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless($internals, $class); + $internals->init(%params); + } + return($internals); +} + +sub init { + my $self = shift; + my %params = @_; + my $dummy; + $self->debug("enter init"); + $self->init_nagios(); + if ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) { + ($dummy, $self->{bufferpool_reads}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_reads' + }); + ($dummy, $self->{bufferpool_read_requests}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_read_requests' + }); + if (! defined $self->{bufferpool_reads}) { + $self->add_nagios_critical("no innodb buffer pool info available"); + } else { + $self->valdiff(\%params, qw(bufferpool_reads + bufferpool_read_requests)); + $self->{bufferpool_hitrate_now} = + $self->{delta_bufferpool_read_requests} > 0 ? + 100 - (100 * $self->{delta_bufferpool_reads} / + $self->{delta_bufferpool_read_requests}) : 100; + $self->{bufferpool_hitrate} = + $self->{bufferpool_read_requests} > 0 ? + 100 - (100 * $self->{bufferpool_reads} / + $self->{bufferpool_read_requests}) : 100; + } + } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) { + ($dummy, $self->{bufferpool_wait_free}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_wait_free' + }); + if (! defined $self->{bufferpool_wait_free}) { + $self->add_nagios_critical("no innodb buffer pool info available"); + } else { + $self->valdiff(\%params, qw(bufferpool_wait_free)); + $self->{bufferpool_wait_free_rate} = + $self->{delta_bufferpool_wait_free} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /server::instance::innodb::logwaits/) { + ($dummy, $self->{log_waits}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_log_waits' + }); + if (! defined $self->{log_waits}) { + $self->add_nagios_critical("no innodb log info available"); + } else { + $self->valdiff(\%params, qw(log_waits)); + $self->{log_waits_rate} = + $self->{delta_log_waits} / $self->{delta_timestamp}; + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + my $now = $params{lookback} ? '_now' : ''; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) { + my $refkey = 'bufferpool_hitrate'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "99:", "95:"), + sprintf "innodb buffer pool hitrate at %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "bufferpool_hitrate=%.2f%%;%s;%s;0;100", + $self->{bufferpool_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "bufferpool_hitrate_now=%.2f%%", + $self->{bufferpool_hitrate_now}); + } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) { + $self->add_nagios( + $self->check_thresholds($self->{bufferpool_wait_free_rate}, "1", "10"), + sprintf "%ld innodb buffer pool waits in %ld seconds (%.4f/sec)", + $self->{delta_bufferpool_wait_free}, $self->{delta_timestamp}, + $self->{bufferpool_wait_free_rate}); + $self->add_perfdata(sprintf "bufferpool_free_waits_rate=%.4f;%s;%s;0;100", + $self->{bufferpool_wait_free_rate}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::innodb::logwaits/) { + $self->add_nagios( + $self->check_thresholds($self->{log_waits_rate}, "1", "10"), + sprintf "%ld innodb log waits in %ld seconds (%.4f/sec)", + $self->{delta_log_waits}, $self->{delta_timestamp}, + $self->{log_waits_rate}); + $self->add_perfdata(sprintf "innodb_log_waits_rate=%.4f;%s;%s;0;100", + $self->{log_waits_rate}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + + + + +package DBD::MySQL::Server::Instance::MyISAM; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + internals => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::myisam/) { + $self->{internals} = + DBD::MySQL::Server::Instance::MyISAM::Internals->new(%params); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::instance::myisam/) { + $self->{internals}->nagios(%params); + $self->merge_nagios($self->{internals}); + } +} + + +package DBD::MySQL::Server::Instance::MyISAM::Internals; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance::MyISAM); + +our $internals; # singleton, nur ein einziges mal instantiierbar + +sub new { + my $class = shift; + my %params = @_; + unless ($internals) { + $internals = { + handle => $params{handle}, + keycache_hitrate => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless($internals, $class); + $internals->init(%params); + } + return($internals); +} + +sub init { + my $self = shift; + my %params = @_; + my $dummy; + $self->debug("enter init"); + $self->init_nagios(); + if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) { + ($dummy, $self->{key_reads}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Key_reads' + }); + ($dummy, $self->{key_read_requests}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Key_read_requests' + }); + if (! defined $self->{key_read_requests}) { + $self->add_nagios_critical("no myisam keycache info available"); + } else { + $self->valdiff(\%params, qw(key_reads key_read_requests)); + $self->{keycache_hitrate} = + $self->{key_read_requests} > 0 ? + 100 - (100 * $self->{key_reads} / + $self->{key_read_requests}) : 100; + $self->{keycache_hitrate_now} = + $self->{delta_key_read_requests} > 0 ? + 100 - (100 * $self->{delta_key_reads} / + $self->{delta_key_read_requests}) : 100; + } + } elsif ($params{mode} =~ /server::instance::myisam::sonstnochwas/) { + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) { + my $refkey = 'keycache_hitrate'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "99:", "95:"), + sprintf "myisam keycache hitrate at %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "keycache_hitrate=%.2f%%;%s;%s", + $self->{keycache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "keycache_hitrate_now=%.2f%%;%s;%s", + $self->{keycache_hitrate_now}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + + +package DBD::MySQL::Server::Instance::Replication; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + internals => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::replication/) { + $self->{internals} = + DBD::MySQL::Server::Instance::Replication::Internals->new(%params); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::instance::replication/) { + $self->{internals}->nagios(%params); + $self->merge_nagios($self->{internals}); + } +} + + +package DBD::MySQL::Server::Instance::Replication::Internals; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance::Replication); + +our $internals; # singleton, nur ein einziges mal instantiierbar + +sub new { + my $class = shift; + my %params = @_; + unless ($internals) { + $internals = { + handle => $params{handle}, + seconds_behind_master => undef, + slave_io_running => undef, + slave_sql_running => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless($internals, $class); + $internals->init(%params); + } + return($internals); +} + +sub init { + my $self = shift; + my %params = @_; + $self->debug("enter init"); + $self->init_nagios(); + if ($params{mode} =~ /server::instance::replication::slavelag/) { + # "show slave status", "Seconds_Behind_Master" + my $slavehash = $self->{handle}->selectrow_hashref(q{ + SHOW SLAVE STATUS + }); + if ((! defined $slavehash->{Seconds_Behind_Master}) && + (lc $slavehash->{Slave_IO_Running} eq 'no')) { + $self->add_nagios_critical( + "unable to get slave lag, because io thread is not running"); + } elsif (! defined $slavehash->{Seconds_Behind_Master}) { + $self->add_nagios_critical(sprintf "unable to get replication info%s", + $self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); + } else { + $self->{seconds_behind_master} = $slavehash->{Seconds_Behind_Master}; + } + } elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) { + # "show slave status", "Slave_IO_Running" + my $slavehash = $self->{handle}->selectrow_hashref(q{ + SHOW SLAVE STATUS + }); + if (! defined $slavehash->{Slave_IO_Running}) { + $self->add_nagios_critical(sprintf "unable to get replication info%s", + $self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); + } else { + $self->{slave_io_running} = $slavehash->{Slave_IO_Running}; + } + } elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) { + # "show slave status", "Slave_SQL_Running" + my $slavehash = $self->{handle}->selectrow_hashref(q{ + SHOW SLAVE STATUS + }); + if (! defined $slavehash->{Slave_SQL_Running}) { + $self->add_nagios_critical(sprintf "unable to get replication info%s", + $self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); + } else { + $self->{slave_sql_running} = $slavehash->{Slave_SQL_Running}; + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::replication::slavelag/) { + $self->add_nagios( + $self->check_thresholds($self->{seconds_behind_master}, "10", "20"), + sprintf "Slave is %d seconds behind master", + $self->{seconds_behind_master}); + $self->add_perfdata(sprintf "slave_lag=%d;%s;%s", + $self->{seconds_behind_master}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) { + if (lc $self->{slave_io_running} eq "yes") { + $self->add_nagios_ok("Slave io is running"); + } else { + $self->add_nagios_critical("Slave io is not running"); + } + } elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) { + if (lc $self->{slave_sql_running} eq "yes") { + $self->add_nagios_ok("Slave sql is running"); + } else { + $self->add_nagios_critical("Slave sql is not running"); + } + } + } +} + + + +package DBD::MySQL::Server::Instance; + +use strict; + +our @ISA = qw(DBD::MySQL::Server); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + uptime => $params{uptime}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + threads_connected => undef, + threads_created => undef, + connections => undef, + threadcache_hitrate => undef, + querycache_hitrate => undef, + lowmem_prunes_per_sec => undef, + slow_queries_per_sec => undef, + longrunners => undef, + tablecache_hitrate => undef, + index_usage => undef, + engine_innodb => undef, + engine_myisam => undef, + replication => undef, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + my $dummy; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::connectedthreads/) { + ($dummy, $self->{threads_connected}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_connected' + }); + } elsif ($params{mode} =~ /server::instance::threadcachehitrate/) { + ($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_created' + }); + ($dummy, $self->{connections}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Connections' + }); + $self->valdiff(\%params, qw(threads_created connections)); + if ($self->{delta_connections} > 0) { + $self->{threadcache_hitrate_now} = + 100 - ($self->{delta_threads_created} * 100.0 / + $self->{delta_connections}); + } else { + $self->{threadcache_hitrate_now} = 100; + } + $self->{threadcache_hitrate} = 100 - + ($self->{threads_created} * 100.0 / $self->{connections}); + $self->{connections_per_sec} = $self->{delta_connections} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::querycachehitrate/) { + ($dummy, $self->{com_select}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Com_select' + }); + ($dummy, $self->{qcache_hits}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Qcache_hits' + }); + # SHOW VARIABLES WHERE Variable_name = 'have_query_cache' for 5.x, but LIKE is compatible + ($dummy, $self->{have_query_cache}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'have_query_cache' + }); + # SHOW VARIABLES WHERE Variable_name = 'query_cache_size' + ($dummy, $self->{query_cache_size}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'query_cache_size' + }); + $self->valdiff(\%params, qw(com_select qcache_hits)); + $self->{querycache_hitrate_now} = + ($self->{delta_com_select} + $self->{delta_qcache_hits}) > 0 ? + 100 * $self->{delta_qcache_hits} / + ($self->{delta_com_select} + $self->{delta_qcache_hits}) : + 0; + $self->{querycache_hitrate} = + 100 * $self->{qcache_hits} / ($self->{com_select} + $self->{qcache_hits}); + $self->{selects_per_sec} = + $self->{delta_com_select} / $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) { + ($dummy, $self->{lowmem_prunes}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Qcache_lowmem_prunes' + }); + $self->valdiff(\%params, qw(lowmem_prunes)); + $self->{lowmem_prunes_per_sec} = $self->{delta_lowmem_prunes} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::slowqueries/) { + ($dummy, $self->{slow_queries}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Slow_queries' + }); + $self->valdiff(\%params, qw(slow_queries)); + $self->{slow_queries_per_sec} = $self->{delta_slow_queries} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::longprocs/) { + if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1")) { + ($self->{longrunners}) = $self->{handle}->fetchrow_array(q{ + SELECT + COUNT(*) + FROM + information_schema.processlist + WHERE user <> 'replication' + AND id <> CONNECTION_ID() + AND time > 60 + AND command <> 'Sleep' + }); + } else { + $self->{longrunners} = 0 if ! defined $self->{longrunners}; + foreach ($self->{handle}->fetchall_array(q{ + SHOW PROCESSLIST + })) { + my($id, $user, $host, $db, $command, $tme, $state, $info) = @{$_}; + if (($user ne 'replication') && + ($tme > 60) && + ($command ne 'Sleep')) { + $self->{longrunners}++; + } + } + } + } elsif ($params{mode} =~ /server::instance::tablecachehitrate/) { + ($dummy, $self->{open_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Open_tables' + }); + ($dummy, $self->{opened_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Opened_tables' + }); + if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1.3")) { + # SHOW VARIABLES WHERE Variable_name = 'table_open_cache' + ($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'table_open_cache' + }); + } else { + # SHOW VARIABLES WHERE Variable_name = 'table_cache' + ($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'table_cache' + }); + } + $self->{table_cache} ||= 0; + #$self->valdiff(\%params, qw(open_tables opened_tables table_cache)); + # _now ist hier sinnlos, da opened_tables waechst, aber open_tables wieder + # schrumpfen kann weil tabellen geschlossen werden. + if ($self->{opened_tables} != 0 && $self->{table_cache} != 0) { + $self->{tablecache_hitrate} = + 100 * $self->{open_tables} / $self->{opened_tables}; + $self->{tablecache_fillrate} = + 100 * $self->{open_tables} / $self->{table_cache}; + } elsif ($self->{opened_tables} == 0 && $self->{table_cache} != 0) { + $self->{tablecache_hitrate} = 100; + $self->{tablecache_fillrate} = + 100 * $self->{open_tables} / $self->{table_cache}; + } else { + $self->{tablecache_hitrate} = 0; + $self->{tablecache_fillrate} = 0; + $self->add_nagios_critical("no table cache"); + } + } elsif ($params{mode} =~ /server::instance::tablelockcontention/) { + ($dummy, $self->{table_locks_waited}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Table_locks_waited' + }); + ($dummy, $self->{table_locks_immediate}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Table_locks_immediate' + }); + $self->valdiff(\%params, qw(table_locks_waited table_locks_immediate)); + $self->{table_lock_contention} = + ($self->{table_locks_waited} + $self->{table_locks_immediate}) > 0 ? + 100 * $self->{table_locks_waited} / + ($self->{table_locks_waited} + $self->{table_locks_immediate}) : + 100; + $self->{table_lock_contention_now} = + ($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) > 0 ? + 100 * $self->{delta_table_locks_waited} / + ($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) : + 100; + } elsif ($params{mode} =~ /server::instance::tableindexusage/) { + # http://johnjacobm.wordpress.com/2007/06/ + # formula for calculating the percentage of full table scans + ($dummy, $self->{handler_read_first}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_first' + }); + ($dummy, $self->{handler_read_key}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_key' + }); + ($dummy, $self->{handler_read_next}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_next' + }); + ($dummy, $self->{handler_read_prev}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_prev' + }); + ($dummy, $self->{handler_read_rnd}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd' + }); + ($dummy, $self->{handler_read_rnd_next}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd_next' + }); + $self->valdiff(\%params, qw(handler_read_first handler_read_key + handler_read_next handler_read_prev handler_read_rnd + handler_read_rnd_next)); + $self->{index_usage_now} = 100 - (100.0 * ($self->{delta_handler_read_rnd} + + $self->{delta_handler_read_rnd_next}) / + ($self->{delta_handler_read_first} + + $self->{delta_handler_read_key} + + $self->{delta_handler_read_next} + + $self->{delta_handler_read_prev} + + $self->{delta_handler_read_rnd} + + $self->{delta_handler_read_rnd_next})); + $self->{index_usage} = 100 - (100.0 * ($self->{handler_read_rnd} + + $self->{handler_read_rnd_next}) / + ($self->{handler_read_first} + + $self->{handler_read_key} + + $self->{handler_read_next} + + $self->{handler_read_prev} + + $self->{handler_read_rnd} + + $self->{handler_read_rnd_next})); + } elsif ($params{mode} =~ /server::instance::tabletmpondisk/) { + ($dummy, $self->{created_tmp_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_tables' + }); + ($dummy, $self->{created_tmp_disk_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_disk_tables' + }); + $self->valdiff(\%params, qw(created_tmp_tables created_tmp_disk_tables)); + $self->{pct_tmp_on_disk} = $self->{created_tmp_tables} > 0 ? + 100 * $self->{created_tmp_disk_tables} / $self->{created_tmp_tables} : + 100; + $self->{pct_tmp_on_disk_now} = $self->{delta_created_tmp_tables} > 0 ? + 100 * $self->{delta_created_tmp_disk_tables} / $self->{delta_created_tmp_tables} : + 100; + } elsif ($params{mode} =~ /server::instance::openfiles/) { + ($dummy, $self->{open_files_limit}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'open_files_limit' + }); + ($dummy, $self->{open_files}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Open_files' + }); + $self->{pct_open_files} = 100 * $self->{open_files} / $self->{open_files_limit}; + } elsif ($params{mode} =~ /server::instance::myisam/) { + $self->{engine_myisam} = DBD::MySQL::Server::Instance::MyISAM->new( + %params + ); + } elsif ($params{mode} =~ /server::instance::innodb/) { + $self->{engine_innodb} = DBD::MySQL::Server::Instance::Innodb->new( + %params + ); + } elsif ($params{mode} =~ /server::instance::replication/) { + $self->{replication} = DBD::MySQL::Server::Instance::Replication->new( + %params + ); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::connectedthreads/) { + $self->add_nagios( + $self->check_thresholds($self->{threads_connected}, 10, 20), + sprintf "%d client connection threads", $self->{threads_connected}); + $self->add_perfdata(sprintf "threads_connected=%d;%d;%d", + $self->{threads_connected}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::threadcachehitrate/) { + my $refkey = 'threadcache_hitrate'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "90:", "80:"), + sprintf "thread cache hitrate %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "thread_cache_hitrate=%.2f%%;%s;%s", + $self->{threadcache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "thread_cache_hitrate_now=%.2f%%", + $self->{threadcache_hitrate_now}); + $self->add_perfdata(sprintf "connections_per_sec=%.2f", + $self->{connections_per_sec}); + } elsif ($params{mode} =~ /server::instance::querycachehitrate/) { + my $refkey = 'querycache_hitrate'.($params{lookback} ? '_now' : ''); + if ((lc $self->{have_query_cache} eq 'yes') && ($self->{query_cache_size})) { + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "90:", "80:"), + sprintf "query cache hitrate %.2f%%", $self->{$refkey}); + } else { + $self->check_thresholds($self->{$refkey}, "90:", "80:"); + $self->add_nagios_ok( + sprintf "query cache hitrate %.2f%% (because it's turned off)", + $self->{querycache_hitrate}); + } + $self->add_perfdata(sprintf "qcache_hitrate=%.2f%%;%s;%s", + $self->{querycache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "qcache_hitrate_now=%.2f%%", + $self->{querycache_hitrate_now}); + $self->add_perfdata(sprintf "selects_per_sec=%.2f", + $self->{selects_per_sec}); + } elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) { + $self->add_nagios( + $self->check_thresholds($self->{lowmem_prunes_per_sec}, "1", "10"), + sprintf "%d query cache lowmem prunes in %d seconds (%.2f/sec)", + $self->{delta_lowmem_prunes}, $self->{delta_timestamp}, + $self->{lowmem_prunes_per_sec}); + $self->add_perfdata(sprintf "qcache_lowmem_prunes_rate=%.2f;%s;%s", + $self->{lowmem_prunes_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::slowqueries/) { + $self->add_nagios( + $self->check_thresholds($self->{slow_queries_per_sec}, "0.1", "1"), + sprintf "%d slow queries in %d seconds (%.2f/sec)", + $self->{delta_slow_queries}, $self->{delta_timestamp}, + $self->{slow_queries_per_sec}); + $self->add_perfdata(sprintf "slow_queries_rate=%.2f%%;%s;%s", + $self->{slow_queries_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::longprocs/) { + $self->add_nagios( + $self->check_thresholds($self->{longrunners}, 10, 20), + sprintf "%d long running processes", $self->{longrunners}); + $self->add_perfdata(sprintf "long_running_procs=%d;%d;%d", + $self->{longrunners}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::tablecachehitrate/) { + if ($self->{tablecache_fillrate} < 95) { + $self->add_nagios_ok( + sprintf "table cache hitrate %.2f%%, %.2f%% filled", + $self->{tablecache_hitrate}, + $self->{tablecache_fillrate}); + $self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"); + } else { + $self->add_nagios( + $self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"), + sprintf "table cache hitrate %.2f%%", $self->{tablecache_hitrate}); + } + $self->add_perfdata(sprintf "tablecache_hitrate=%.2f%%;%s;%s", + $self->{tablecache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "tablecache_fillrate=%.2f%%", + $self->{tablecache_fillrate}); + } elsif ($params{mode} =~ /server::instance::tablelockcontention/) { + my $refkey = 'table_lock_contention'.($params{lookback} ? '_now' : ''); + if ($self->{uptime} > 10800) { # MySQL Bug #30599 + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "1", "2"), + sprintf "table lock contention %.2f%%", $self->{$refkey}); + } else { + $self->check_thresholds($self->{$refkey}, "1", "2"); + $self->add_nagios_ok( + sprintf "table lock contention %.2f%% (uptime < 10800)", + $self->{refkey}); + } + $self->add_perfdata(sprintf "tablelock_contention=%.2f%%;%s;%s", + $self->{table_lock_contention}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "tablelock_contention_now=%.2f%%", + $self->{table_lock_contention_now}); + } elsif ($params{mode} =~ /server::instance::tableindexusage/) { + my $refkey = 'index_usage'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "90:", "80:"), + sprintf "index usage %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "index_usage=%.2f%%;%s;%s", + $self->{index_usage}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "index_usage_now=%.2f%%", + $self->{index_usage_now}); + } elsif ($params{mode} =~ /server::instance::tabletmpondisk/) { + my $refkey = 'pct_tmp_on_disk'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "25", "50"), + sprintf "%.2f%% of %d tables were created on disk", + $self->{$refkey}, $self->{delta_created_tmp_tables}); + $self->add_perfdata(sprintf "pct_tmp_table_on_disk=%.2f%%;%s;%s", + $self->{pct_tmp_on_disk}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "pct_tmp_table_on_disk_now=%.2f%%", + $self->{pct_tmp_on_disk_now}); + } elsif ($params{mode} =~ /server::instance::openfiles/) { + $self->add_nagios( + $self->check_thresholds($self->{pct_open_files}, 80, 95), + sprintf "%.2f%% of the open files limit reached (%d of max. %d)", + $self->{pct_open_files}, + $self->{open_files}, $self->{open_files_limit}); + $self->add_perfdata(sprintf "pct_open_files=%.3f%%;%.3f;%.3f", + $self->{pct_open_files}, + $self->{warningrange}, + $self->{criticalrange}); + $self->add_perfdata(sprintf "open_files=%d;%d;%d", + $self->{open_files}, + $self->{open_files_limit} * $self->{warningrange} / 100, + $self->{open_files_limit} * $self->{criticalrange} / 100); + } elsif ($params{mode} =~ /server::instance::myisam/) { + $self->{engine_myisam}->nagios(%params); + $self->merge_nagios($self->{engine_myisam}); + } elsif ($params{mode} =~ /server::instance::innodb/) { + $self->{engine_innodb}->nagios(%params); + $self->merge_nagios($self->{engine_innodb}); + } elsif ($params{mode} =~ /server::instance::replication/) { + $self->{replication}->nagios(%params); + $self->merge_nagios($self->{replication}); + } + } +} + + + +package DBD::MySQL::Server; + +use strict; +use Time::HiRes; +use IO::File; +use File::Copy 'cp'; +use Data::Dumper; + + +{ + our $verbose = 0; + our $scream = 0; # scream if something is not implemented + our $access = "dbi"; # how do we access the database. + our $my_modules_dyn_dir = ""; # where we look for self-written extensions + + my @servers = (); + my $initerrors = undef; + + sub add_server { + push(@servers, shift); + } + + sub return_servers { + return @servers; + } + + sub return_first_server() { + return $servers[0]; + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + access => $params{method} || 'dbi', + hostname => $params{hostname}, + database => $params{database} || 'information_schema', + port => $params{port}, + socket => $params{socket}, + username => $params{username}, + password => $params{password}, + timeout => $params{timeout}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + version => 'unknown', + instance => undef, + handle => undef, + }; + bless $self, $class; + $self->init_nagios(); + if ($self->dbconnect(%params)) { + ($self->{dummy}, $self->{version}) = $self->{handle}->fetchrow_array( + #q{ SHOW VARIABLES WHERE Variable_name = 'version' } + q{ SHOW VARIABLES LIKE 'version' } + ); + $self->{version} = (split "-", $self->{version})[0]; + ($self->{dummy}, $self->{uptime}) = $self->{handle}->fetchrow_array( + q{ SHOW STATUS LIKE 'Uptime' } + ); + DBD::MySQL::Server::add_server($self); + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $params{handle} = $self->{handle}; + $params{uptime} = $self->{uptime}; + if ($params{mode} =~ /^server::instance/) { + $self->{instance} = DBD::MySQL::Server::Instance->new(%params); + } elsif ($params{mode} =~ /^server::sql/) { + $self->{genericsql} = $self->{handle}->fetchrow_array($params{selectname}); + if ($self->{genericsql} =~ /^\s*\d+\s*$/) { + $self->{genericsql} = sprintf "%d", $self->{genericsql}; + } elsif ($self->{genericsql} =~ /^\s*\d*\.\d+\s*$/) { + $self->{genericsql} = sprintf "%.2f", $self->{genericsql}; + } else { + $self->add_nagios_unknown(sprintf "got no valid response for %s", + $params{selectname}); + } + } elsif ($params{mode} =~ /^server::uptime/) { + # already set with the connection. but use minutes here + } elsif ($params{mode} =~ /^server::connectiontime/) { + $self->{connection_time} = $self->{tac} - $self->{tic}; + } elsif ($params{mode} =~ /^my::([^:.]+)/) { + my $class = $1; + my $loaderror = undef; + substr($class, 0, 1) = uc substr($class, 0, 1); + foreach my $libpath (split(":", $DBD::MySQL::Server::my_modules_dyn_dir)) { + foreach my $extmod (glob $libpath."/CheckMySQLHealth*.pm") { + eval { + $self->trace(sprintf "loading module %s", $extmod); + require $extmod; + }; + if ($@) { + $loaderror = $extmod; + $self->trace(sprintf "failed loading module %s: %s", $extmod, $@); + } + } + } + my $obj = { + handle => $params{handle}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $obj, "My$class"; + $self->{my} = $obj; + if ($self->{my}->isa("DBD::MySQL::Server")) { + my $dos_init = $self->can("init"); + my $dos_nagios = $self->can("nagios"); + my $my_init = $self->{my}->can("init"); + my $my_nagios = $self->{my}->can("nagios"); + if ($my_init == $dos_init) { + $self->add_nagios_unknown( + sprintf "Class %s needs an init() method", ref($self->{my})); + } elsif ($my_nagios == $dos_nagios) { + $self->add_nagios_unknown( + sprintf "Class %s needs a nagios() method", ref($self->{my})); + } else { + $self->{my}->init_nagios(%params); + $self->{my}->init(%params); + } + } else { + $self->add_nagios_unknown( + sprintf "Class %s is not a subclass of DBD::MySQL::Server%s", + ref($self->{my}), + $loaderror ? sprintf " (syntax error in %s?)", $loaderror : "" ); + } + } else { + printf "broken mode %s\n", $params{mode}; + } +} + +sub dump { + my $self = shift; + my $message = shift || ""; + printf "%s %s\n", $message, Data::Dumper::Dumper($self); +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /^server::instance/) { + $self->{instance}->nagios(%params); + $self->merge_nagios($self->{instance}); + } elsif ($params{mode} =~ /^server::database/) { + $self->{database}->nagios(%params); + $self->merge_nagios($self->{database}); + } elsif ($params{mode} =~ /^server::uptime/) { + $self->add_nagios( + $self->check_thresholds($self->{uptime} / 60, "10:", "5:"), + sprintf "database is up since %d minutes", $self->{uptime} / 60); + $self->add_perfdata(sprintf "uptime=%ds", + $self->{uptime}); + } elsif ($params{mode} =~ /^server::connectiontime/) { + $self->add_nagios( + $self->check_thresholds($self->{connection_time}, 1, 5), + sprintf "%.2f seconds to connect as %s", + $self->{connection_time}, ($self->{username} || getpwuid($<))); + $self->add_perfdata(sprintf "connection_time=%.4fs;%d;%d", + $self->{connection_time}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::sql/) { + $self->add_nagios( + $self->check_thresholds($self->{genericsql}, 1, 5), + sprintf "%s: %s%s", + $params{name2} ? lc $params{name2} : lc $params{selectname}, + $self->{genericsql}, + $params{units} ? $params{units} : ""); + $self->add_perfdata(sprintf "\'%s\'=%s%s;%s;%s", + $params{name2} ? lc $params{name2} : lc $params{selectname}, + $self->{genericsql}, + $params{units} ? $params{units} : "", + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^my::([^:.]+)/) { + $self->{my}->nagios(%params); + $self->merge_nagios($self->{my}); + } + } +} + + +sub init_nagios { + my $self = shift; + no strict 'refs'; + if (! ref($self)) { + my $nagiosvar = $self."::nagios"; + my $nagioslevelvar = $self."::nagios_level"; + $$nagiosvar = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $$nagioslevelvar = $ERRORS{OK}, + } else { + $self->{nagios} = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $self->{nagios_level} = $ERRORS{OK}, + } +} + +sub check_thresholds { + my $self = shift; + my $value = shift; + my $defaultwarningrange = shift; + my $defaultcriticalrange = shift; + my $level = $ERRORS{OK}; + $self->{warningrange} = defined $self->{warningrange} ? + $self->{warningrange} : $defaultwarningrange; + $self->{criticalrange} = defined $self->{criticalrange} ? + $self->{criticalrange} : $defaultcriticalrange; + if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) { + # warning = 10, critical = 20, warn if > 10, crit if > 20 + $level = $ERRORS{WARNING} if $value > $self->{warningrange}; + $level = $ERRORS{CRITICAL} if $value > $self->{criticalrange}; + } elsif ($self->{warningrange} =~ /([\d\.]+):/ && + $self->{criticalrange} =~ /([\d\.]+):/) { + # warning = 98:, critical = 95:, warn if < 98, crit if < 95 + $self->{warningrange} =~ /([\d\.]+):/; + $level = $ERRORS{WARNING} if $value < $1; + $self->{criticalrange} =~ /([\d\.]+):/; + $level = $ERRORS{CRITICAL} if $value < $1; + } + return $level; + # + # syntax error must be reported with returncode -1 + # +} + +sub add_nagios { + my $self = shift; + my $level = shift; + my $message = shift; + push(@{$self->{nagios}->{messages}->{$level}}, $message); + # recalc current level + foreach my $llevel qw(CRITICAL WARNING UNKNOWN OK) { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) { + $self->{nagios_level} = $ERRORS{$llevel}; + } + } +} + +sub add_nagios_ok { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{OK}, $message); +} + +sub add_nagios_warning { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{WARNING}, $message); +} + +sub add_nagios_critical { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{CRITICAL}, $message); +} + +sub add_nagios_unknown { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{UNKNOWN}, $message); +} + +sub add_perfdata { + my $self = shift; + my $data = shift; + push(@{$self->{nagios}->{perfdata}}, $data); +} + +sub merge_nagios { + my $self = shift; + my $child = shift; + foreach my $level (0..3) { + foreach (@{$child->{nagios}->{messages}->{$level}}) { + $self->add_nagios($level, $_); + } + #push(@{$self->{nagios}->{messages}->{$level}}, + # @{$child->{nagios}->{messages}->{$level}}); + } + push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}}); +} + + +sub calculate_result { + my $self = shift; + if ($ENV{NRPE_MULTILINESUPPORT} && + length join(" ", @{$self->{nagios}->{perfdata}}) > 200) { + foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { + # first the bad news + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_message} .= + "\n".join("\n", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}); + } + } + $self->{nagios_message} =~ s/^\n//g; + $self->{perfdata} = join("\n", @{$self->{nagios}->{perfdata}}); + } else { + foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { + # first the bad news + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_message} .= + join(", ", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}).", "; + } + } + $self->{nagios_message} =~ s/, $//g; + $self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}}); + } + foreach my $level ("OK", "UNKNOWN", "WARNING", "CRITICAL") { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_level} = $ERRORS{$level}; + } + } +} + +sub debug { + my $self = shift; + my $msg = shift; + if ($DBD::MySQL::Server::verbose) { + printf "%s %s\n", $msg, ref($self); + } +} + +sub dbconnect { + my $self = shift; + my %params = @_; + my $retval = undef; + $self->{tic} = Time::HiRes::time(); + $self->{handle} = DBD::MySQL::Server::Connection->new(%params); + if ($self->{handle}->{errstr}) { + if ($params{mode} =~ /^server::tnsping/ && + $self->{handle}->{errstr} =~ /ORA-01017/) { + $self->add_nagios($ERRORS{OK}, + sprintf "connection established to %s.", $self->{connect}); + $retval = undef; + } elsif ($self->{handle}->{errstr} eq "alarm\n") { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "connection could not be established within %d seconds", + $self->{timeout}); + } else { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "cannot connect to %s. %s", + $self->{database}, $self->{handle}->{errstr}); + $retval = undef; + } + } else { + $retval = $self->{handle}; + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub trace { + my $self = shift; + my $format = shift; + $self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0; + if ($self->{verbose}) { + printf("%s: ", scalar localtime); + printf($format, @_); + } + if ($self->{trace}) { + my $logfh = new IO::File; + $logfh->autoflush(1); + if ($logfh->open("/tmp/check_mysql_health.trace", "a")) { + $logfh->printf("%s: ", scalar localtime); + $logfh->printf($format, @_); + $logfh->printf("\n"); + $logfh->close(); + } + } +} + +sub DESTROY { + my $self = shift; + my $handle1 = "null"; + my $handle2 = "null"; + if (defined $self->{handle}) { + $handle1 = ref($self->{handle}); + if (defined $self->{handle}->{handle}) { + $handle2 = ref($self->{handle}->{handle}); + } + } + $self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Server") { + } + $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Server") { + #printf "humpftata\n"; + } +} + +sub save_state { + my $self = shift; + my %params = @_; + my $extension = ""; + mkdir $params{statefilesdir} unless -d $params{statefilesdir}; + my $statefile = sprintf "%s/%s_%s", + $params{statefilesdir}, $params{hostname}, $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + open(STATE, ">$statefile"); + if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) { + $params{save}->{localtime} = scalar localtime $params{save}->{timestamp}; + } + printf STATE Data::Dumper::Dumper($params{save}); + close STATE; + $self->debug(sprintf "saved %s to %s", + Data::Dumper::Dumper($params{save}), $statefile); +} + +sub load_state { + my $self = shift; + my %params = @_; + my $extension = ""; + my $statefile = sprintf "%s/%s_%s", + $params{statefilesdir}, $params{hostname}, $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + if ( -f $statefile) { + our $VAR1; + eval { + require $statefile; + }; + if($@) { +printf "rumms\n"; + } + $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); + return $VAR1; + } else { + return undef; + } +} + +sub valdiff { + my $self = shift; + my $pparams = shift; + my %params = %{$pparams}; + my @keys = @_; + my $now = time; + my $last_values = $self->load_state(%params) || eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = 0; + } + $empty_events->{timestamp} = 0; + if ($params{lookback}) { + $empty_events->{lookback_history} = {}; + } + $empty_events; + }; + foreach (@keys) { + if ($params{lookback}) { + # find a last_value in the history which fits lookback best + # and overwrite $last_values->{$_} with historic data + if (exists $last_values->{lookback_history}->{$_}) { + foreach my $date (sort {$a <=> $b} keys %{$last_values->{lookback_history}->{$_}}) { + if ($date >= ($now - $params{lookback})) { + $last_values->{$_} = $last_values->{lookback_history}->{$_}->{$date}; + $last_values->{timestamp} = $date; + last; + } else { + delete $last_values->{lookback_history}->{$_}->{$date}; + } + } + } + } + $last_values->{$_} = 0 if ! exists $last_values->{$_}; + if ($self->{$_} >= $last_values->{$_}) { + $self->{'delta_'.$_} = $self->{$_} - $last_values->{$_}; + } else { + # vermutlich db restart und zaehler alle auf null + $self->{'delta_'.$_} = $self->{$_}; + } + $self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_}); + } + $self->{'delta_timestamp'} = $now - $last_values->{timestamp}; + $params{save} = eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = $self->{$_}; + } + $empty_events->{timestamp} = $now; + if ($params{lookback}) { + $empty_events->{lookback_history} = $last_values->{lookback_history}; + foreach (@keys) { + $empty_events->{lookback_history}->{$_}->{$now} = $self->{$_}; + } + } + $empty_events; + }; + $self->save_state(%params); +} + +sub requires_version { + my $self = shift; + my $version = shift; + my @instances = DBD::MySQL::Server::return_servers(); + my $instversion = $instances[0]->{version}; + if (! $self->version_is_minimum($version)) { + $self->add_nagios($ERRORS{UNKNOWN}, + sprintf "not implemented/possible for MySQL release %s", $instversion); + } +} + +sub version_is_minimum { + # the current version is newer or equal + my $self = shift; + my $version = shift; + my $newer = 1; + my @instances = DBD::MySQL::Server::return_servers(); + my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version); + my @v2 = split(/\./, $instances[0]->{version}); + if (scalar(@v1) > scalar(@v2)) { + push(@v2, (0) x (scalar(@v1) - scalar(@v2))); + } elsif (scalar(@v2) > scalar(@v1)) { + push(@v1, (0) x (scalar(@v2) - scalar(@v1))); + } + foreach my $pos (0..$#v1) { + if ($v2[$pos] > $v1[$pos]) { + $newer = 1; + last; + } elsif ($v2[$pos] < $v1[$pos]) { + $newer = 0; + last; + } + } + #printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1); + return $newer; +} + +sub instance_thread { + my $self = shift; + my @instances = DBD::MySQL::Server::return_servers(); + return $instances[0]->{thread}; +} + +sub windows_server { + my $self = shift; + my @instances = DBD::MySQL::Server::return_servers(); + if ($instances[0]->{os} =~ /Win/i) { + return 1; + } else { + return 0; + } +} + +sub system_vartmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $self->system_tmpdir(); + } else { + return "/var/tmp/check_mysql_health"; + } +} + +sub system_oldvartmpdir { + my $self = shift; + return "/tmp"; +} + +sub system_tmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $ENV{TEMP} if defined $ENV{TEMP}; + return $ENV{TMP} if defined $ENV{TMP}; + return File::Spec->catfile($ENV{windir}, 'Temp') + if defined $ENV{windir}; + return 'C:\Temp'; + } else { + return "/tmp"; + } +} + + +package DBD::MySQL::Server::Connection; + +use strict; + +our @ISA = qw(DBD::MySQL::Server); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + mode => $params{mode}, + timeout => $params{timeout}, + access => $params{method} || "dbi", + hostname => $params{hostname}, + database => $params{database} || "information_schema", + port => $params{port}, + socket => $params{socket}, + username => $params{username}, + password => $params{password}, + handle => undef, + }; + bless $self, $class; + if ($params{method} eq "dbi") { + bless $self, "DBD::MySQL::Server::Connection::Dbi"; + } elsif ($params{method} eq "mysql") { + bless $self, "DBD::MySQL::Server::Connection::Mysql"; + } elsif ($params{method} eq "sqlrelay") { + bless $self, "DBD::MySQL::Server::Connection::Sqlrelay"; + } + $self->init(%params); + return $self; +} + + +package DBD::MySQL::Server::Connection::Dbi; + +use strict; +use Net::Ping; + +our @ISA = qw(DBD::MySQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + if ($self->{mode} =~ /^server::tnsping/) { + if (! $self->{connect}) { + $self->{errstr} = "Please specify a database"; + } else { + $self->{sid} = $self->{connect}; + $self->{username} ||= time; # prefer an existing user + $self->{password} = time; + } + } else { + if (($self->{hostname} ne 'localhost') && (! $self->{username} || ! $self->{password})) { + $self->{errstr} = "Please specify hostname, username and password"; + return undef; + } + $self->{dsn} = "DBI:mysql:"; + $self->{dsn} .= sprintf "database=%s", $self->{database}; + $self->{dsn} .= sprintf ";host=%s", $self->{hostname}; + $self->{dsn} .= sprintf ";port=%s", $self->{port} + unless $self->{socket} || $self->{hostname} eq 'localhost'; + $self->{dsn} .= sprintf ";mysql_socket=%s", $self->{socket} + if $self->{socket}; + } + if (! exists $self->{errstr}) { + eval { + require DBI; + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "alarm\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + if ($self->{handle} = DBI->connect( + $self->{dsn}, + $self->{username}, + $self->{password}, + { RaiseError => 0, AutoCommit => 0, PrintError => 0 })) { +# $self->{handle}->do(q{ +# ALTER SESSION SET NLS_NUMERIC_CHARACTERS=".," }); + $retval = $self; + } else { + $self->{errstr} = DBI::errstr(); + } + }; + if ($@) { + $self->{errstr} = $@; + $retval = undef; + } + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub selectrow_hashref { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $hashref = undef; + eval { + $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + # helm auf! jetzt wirds dreckig. + if ($sql =~ /^\s*SHOW/) { + $hashref = $self->{handle}->selectrow_hashref($sql); + } else { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $hashref = $sth->selectrow_hashref(); + } + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($hashref)); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + # keine lust auf den scheiss + } + return $hashref; +} + +sub fetchrow_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my @row = (); + eval { + $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + @row = $sth->fetchrow_array(); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper(\@row)); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @row = split(/\s+/, (split(/\n/, $simulation))[0]); + } + return $row[0] unless wantarray; + return @row; +} + +sub fetchall_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $rows = undef; + eval { + $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $rows = $sth->fetchall_arrayref(); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($rows)); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); + } + return @{$rows}; +} + +sub func { + my $self = shift; + $self->{handle}->func(@_); +} + + +sub execute { + my $self = shift; + my $sql = shift; + eval { + my $sth = $self->{handle}->prepare($sql); + $sth->execute(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } +} + +sub errstr { + my $self = shift; + return $self->{errstr}; +} + +sub DESTROY { + my $self = shift; + $self->trace(sprintf "disconnecting DBD %s", + $self->{handle} ? "with handle" : "without handle"); + $self->{handle}->disconnect() if $self->{handle}; +} + +package DBD::MySQL::Server::Connection::Mysql; + +use strict; +use File::Temp qw/tempfile/; + +our @ISA = qw(DBD::MySQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + $self->{loginstring} = "traditional"; + ($self->{sql_commandfile_handle}, $self->{sql_commandfile}) = + tempfile($self->{mode}."XXXXX", SUFFIX => ".sql", + DIR => $self->system_tmpdir() ); + close $self->{sql_commandfile_handle}; + ($self->{sql_resultfile_handle}, $self->{sql_resultfile}) = + tempfile($self->{mode}."XXXXX", SUFFIX => ".out", + DIR => $self->system_tmpdir() ); + close $self->{sql_resultfile_handle}; + if ($self->{mode} =~ /^server::tnsping/) { + if (! $self->{connect}) { + $self->{errstr} = "Please specify a database"; + } else { + $self->{sid} = $self->{connect}; + $self->{username} ||= time; # prefer an existing user + $self->{password} = time; + } + } else { + if (! $self->{username} || ! $self->{password}) { + $self->{errstr} = "Please specify database, username and password"; + return undef; + } elsif (! (($self->{hostname} && $self->{port}) || $self->{socket})) { + $self->{errstr} = "Please specify hostname and port or socket"; + return undef; + } + } + if (! exists $self->{errstr}) { + eval { + my $mysql = '/'.'usr'.'/'.'bin'.'/'.'mysql'; + if (! -x $mysql) { + die "nomysql\n"; + } + if ($self->{loginstring} eq "traditional") { + $self->{sqlplus} = sprintf "%s ", $mysql; + $self->{sqlplus} .= sprintf "--batch --raw --skip-column-names "; + $self->{sqlplus} .= sprintf "--database=%s ", $self->{database}; + $self->{sqlplus} .= sprintf "--host=%s ", $self->{hostname}; + $self->{sqlplus} .= sprintf "--port=%s ", $self->{port} + unless $self->{socket} || $self->{hostname} eq "localhost"; + $self->{sqlplus} .= sprintf "--socket=%s ", $self->{socket} + if $self->{socket}; + $self->{sqlplus} .= sprintf "--user=%s --password=%s < %s > %s", + $self->{username}, $self->{password}, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } + + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "alarm\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + + my $answer = $self->fetchrow_array( + q{ SELECT 42 FROM dual}); + die unless defined $answer and $answer == 42; + $retval = $self; + }; + if ($@) { + $self->{errstr} = $@; + $self->{errstr} =~ s/at $0 .*//g; + chomp $self->{errstr}; + $retval = undef; + } + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub selectrow_hashref { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $hashref = undef; + foreach (@arguments) { + # replace the ? by the parameters + if (/^\d+$/) { + $sql =~ s/\?/$_/; + } else { + $sql =~ s/\?/'$_'/; + } + } + if ($sql =~ /^\s*SHOW/) { + $sql .= '\G'; # http://dev.mysql.com/doc/refman/5.1/de/show-slave-status.html + } + $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $self->create_commandfile($sql); + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "fetchrow_array exit bumm \n"; + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @oerrs = map { + /((ERROR \d+).*)/ ? $1 : (); + } split(/\n/, $output); + $self->{errstr} = join(" ", @oerrs); + } else { + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + if ($sql =~ /^\s*SHOW/) { + map { + if (/^\s*([\w_]+):\s*(.*)/) { + $hashref->{$1} = $2; + } + } split(/\n/, $output); + } else { + # i dont mess around here and you shouldn't either + } + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($hashref)); + } + unlink $self->{sql_commandfile}; + unlink $self->{sql_resultfile}; + return $hashref; +} + +sub fetchrow_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my @row = (); + foreach (@arguments) { + # replace the ? by the parameters + if (/^\d+$/) { + $sql =~ s/\?/$_/; + } else { + $sql =~ s/\?/'$_'/; + } + } + $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $self->create_commandfile($sql); + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "fetchrow_array exit bumm \n"; + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @oerrs = map { + /((ERROR \d+).*)/ ? $1 : (); + } split(/\n/, $output); + $self->{errstr} = join(" ", @oerrs); + } else { + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + @row = map { convert($_) } + map { s/^\s+([\.\d]+)$/$1/g; $_ } # strip leading space from numbers + map { s/\s+$//g; $_ } # strip trailing space + split(/\t/, (split(/\n/, $output))[0]); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper(\@row)); + } + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + unlink $self->{sql_commandfile}; + unlink $self->{sql_resultfile}; + return $row[0] unless wantarray; + return @row; +} + +sub fetchall_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $rows = undef; + foreach (@arguments) { + # replace the ? by the parameters + if (/^\d+$/) { + $sql =~ s/\?/$_/; + } else { + $sql =~ s/\?/'$_'/; + } + } + $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $self->create_commandfile($sql); + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "fetchrow_array exit bumm %s\n", $exit_output; + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @oerrs = map { + /((ERROR \d+).*)/ ? $1 : (); + } split(/\n/, $output); + $self->{errstr} = join(" ", @oerrs); + } else { + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @rows = map { [ + map { convert($_) } + map { s/^\s+([\.\d]+)$/$1/g; $_ } + map { s/\s+$//g; $_ } + split /\t/ + ] } grep { ! /^\d+ rows selected/ } + grep { ! /^Elapsed: / } + grep { ! /^\s*$/ } split(/\n/, $output); + $rows = \@rows; + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($rows)); + } + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + unlink $self->{sql_commandfile}; + unlink $self->{sql_resultfile}; + return @{$rows}; +} + +sub func { + my $self = shift; + my $function = shift; + $self->{handle}->func(@_); +} + +sub convert { + my $n = shift; + # mostly used to convert numbers in scientific notation + if ($n =~ /^\s*\d+\s*$/) { + return $n; + } elsif ($n =~ /^\s*([-+]?)(\d*[\.,]*\d*)[eE]{1}([-+]?)(\d+)\s*$/) { + my ($vor, $num, $sign, $exp) = ($1, $2, $3, $4); + $n =~ s/E/e/g; + $n =~ s/,/\./g; + $num =~ s/,/\./g; + my $sig = $sign eq '-' ? "." . ($exp - 1 + length $num) : ''; + my $dec = sprintf "%${sig}f", $n; + $dec =~ s/\.[0]+$//g; + return $dec; + } elsif ($n =~ /^\s*([-+]?)(\d+)[\.,]*(\d*)\s*$/) { + return $1.$2.".".$3; + } elsif ($n =~ /^\s*(.*?)\s*$/) { + return $1; + } else { + return $n; + } +} + + +sub execute { + my $self = shift; + my $sql = shift; + eval { + my $sth = $self->{handle}->prepare($sql); + $sth->execute(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } +} + +sub errstr { + my $self = shift; + return $self->{errstr}; +} + +sub DESTROY { + my $self = shift; + $self->trace("try to clean up command and result files"); + unlink $self->{sql_commandfile} if -f $self->{sql_commandfile}; + unlink $self->{sql_resultfile} if -f $self->{sql_resultfile}; +} + +sub create_commandfile { + my $self = shift; + my $sql = shift; + open CMDCMD, "> $self->{sql_commandfile}"; + printf CMDCMD "%s\n", $sql; + close CMDCMD; +} + + +package DBD::MySQL::Server::Connection::Sqlrelay; + +use strict; +use Net::Ping; + +our @ISA = qw(DBD::MySQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + if ($self->{mode} =~ /^server::tnsping/) { + if (! $self->{connect}) { + $self->{errstr} = "Please specify a database"; + } else { + if ($self->{connect} =~ /([\.\w]+):(\d+)/) { + $self->{host} = $1; + $self->{port} = $2; + $self->{socket} = ""; + } elsif ($self->{connect} =~ /([\.\w]+):([\w\/]+)/) { + $self->{host} = $1; + $self->{socket} = $2; + $self->{port} = ""; + } + } + } else { + if (! $self->{hostname} || ! $self->{username} || ! $self->{password}) { + if ($self->{hostname} && $self->{hostname} =~ /(\w+)\/(\w+)@([\.\w]+):(\d+)/) { + $self->{username} = $1; + $self->{password} = $2; + $self->{hostname} = $3; + $self->{port} = $4; + $self->{socket} = ""; + } elsif ($self->{hostname} && $self->{hostname} =~ /(\w+)\/(\w+)@([\.\w]+):([\w\/]+)/) { + $self->{username} = $1; + $self->{password} = $2; + $self->{hostname} = $3; + $self->{socket} = $4; + $self->{port} = ""; + } else { + $self->{errstr} = "Please specify database, username and password"; + return undef; + } + } else { + if ($self->{hostname} =~ /([\.\w]+):(\d+)/) { + $self->{hostname} = $1; + $self->{port} = $2; + $self->{socket} = ""; + } elsif ($self->{hostname} =~ /([\.\w]+):([\w\/]+)/) { + $self->{hostname} = $1; + $self->{socket} = $2; + $self->{port} = ""; + } else { + $self->{errstr} = "Please specify hostname, username, password and port/socket"; + return undef; + } + } + } + if (! exists $self->{errstr}) { + eval { + require DBI; + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "alarm\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + if ($self->{handle} = DBI->connect( + sprintf("DBI:SQLRelay:host=%s;port=%d;socket=%s", + $self->{hostname}, $self->{port}, $self->{socket}), + $self->{username}, + $self->{password}, + { RaiseError => 1, AutoCommit => 0, PrintError => 1 })) { + $retval = $self; + if ($self->{mode} =~ /^server::tnsping/ && $self->{handle}->ping()) { + # database connected. fake a "unknown user" + $self->{errstr} = "ORA-01017"; + } + } else { + $self->{errstr} = DBI::errstr(); + } + }; + if ($@) { + $self->{errstr} = $@; + $self->{errstr} =~ s/at [\w\/\.]+ line \d+.*//g; + $retval = undef; + } + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub fetchrow_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my @row = (); + $self->trace(sprintf "fetchrow_array: %s", $sql); + eval { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + @row = $sth->fetchrow_array(); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @row = split(/\s+/, (split(/\n/, $simulation))[0]); + } + return $row[0] unless wantarray; + return @row; +} + +sub fetchall_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $rows = undef; + $self->trace(sprintf "fetchall_array: %s", $sql); + eval { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $rows = $sth->fetchall_arrayref(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); + } + return @{$rows}; +} + +sub func { + my $self = shift; + $self->{handle}->func(@_); +} + +sub execute { + my $self = shift; + my $sql = shift; + eval { + my $sth = $self->{handle}->prepare($sql); + $sth->execute(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } +} + +sub DESTROY { + my $self = shift; + #$self->trace(sprintf "disconnecting DBD %s", + # $self->{handle} ? "with handle" : "without handle"); + #$self->{handle}->disconnect() if $self->{handle}; +} + + + + +package DBD::MySQL::Cluster; + +use strict; +use Time::HiRes; +use IO::File; +use Data::Dumper; + + +{ + our $verbose = 0; + our $scream = 0; # scream if something is not implemented + our $access = "dbi"; # how do we access the database. + our $my_modules_dyn_dir = ""; # where we look for self-written extensions + + my @clusters = (); + my $initerrors = undef; + + sub add_cluster { + push(@clusters, shift); + } + + sub return_clusters { + return @clusters; + } + + sub return_first_cluster() { + return $clusters[0]; + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + hostname => $params{hostname}, + port => $params{port}, + username => $params{username}, + password => $params{password}, + timeout => $params{timeout}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + version => 'unknown', + nodes => [], + ndbd_nodes => 0, + ndb_mgmd_nodes => 0, + mysqld_nodes => 0, + }; + bless $self, $class; + $self->init_nagios(); + if ($self->connect(%params)) { + DBD::MySQL::Cluster::add_cluster($self); + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + if ($self->{show}) { + my $type = undef; + foreach (split /\n/, $self->{show}) { + if (/\[(\w+)\((\w+)\)\]\s+(\d+) node/) { + $type = uc $2; + } elsif (/id=(\d+)(.*)/) { + push(@{$self->{nodes}}, DBD::MySQL::Cluster::Node->new( + type => $type, + id => $1, + status => $2, + )); + } + } + } else { + } + if ($params{mode} =~ /^cluster::ndbdrunning/) { + foreach my $node (@{$self->{nodes}}) { + $node->{type} eq "NDB" && $node->{status} eq "running" && $self->{ndbd_nodes}++; + $node->{type} eq "MGM" && $node->{status} eq "running" && $self->{ndb_mgmd_nodes}++; + $node->{type} eq "API" && $node->{status} eq "running" && $self->{mysqld_nodes}++; + } + } else { + printf "broken mode %s\n", $params{mode}; + } +} + +sub dump { + my $self = shift; + my $message = shift || ""; + printf "%s %s\n", $message, Data::Dumper::Dumper($self); +} + +sub nagios { + my $self = shift; + my %params = @_; + my $dead_ndb = 0; + my $dead_api = 0; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /^cluster::ndbdrunning/) { + foreach my $node (grep { $_->{type} eq "NDB"} @{$self->{nodes}}) { + next if $params{selectname} && $params{selectname} ne $_->{id}; + if (! $node->{connected}) { + $self->add_nagios_critical( + sprintf "ndb node %d is not connected", $node->{id}); + $dead_ndb++; + } + } + foreach my $node (grep { $_->{type} eq "API"} @{$self->{nodes}}) { + next if $params{selectname} && $params{selectname} ne $_->{id}; + if (! $node->{connected}) { + $self->add_nagios_critical( + sprintf "api node %d is not connected", $node->{id}); + $dead_api++; + } + } + if (! $dead_ndb) { + $self->add_nagios_ok("all ndb nodes are connected"); + } + if (! $dead_api) { + $self->add_nagios_ok("all api nodes are connected"); + } + } + } + $self->add_perfdata(sprintf "ndbd_nodes=%d ndb_mgmd_nodes=%d mysqld_nodes=%d", + $self->{ndbd_nodes}, $self->{ndb_mgmd_nodes}, $self->{mysqld_nodes}); +} + + +sub init_nagios { + my $self = shift; + no strict 'refs'; + if (! ref($self)) { + my $nagiosvar = $self."::nagios"; + my $nagioslevelvar = $self."::nagios_level"; + $$nagiosvar = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $$nagioslevelvar = $ERRORS{OK}, + } else { + $self->{nagios} = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $self->{nagios_level} = $ERRORS{OK}, + } +} + +sub check_thresholds { + my $self = shift; + my $value = shift; + my $defaultwarningrange = shift; + my $defaultcriticalrange = shift; + my $level = $ERRORS{OK}; + $self->{warningrange} = $self->{warningrange} ? + $self->{warningrange} : $defaultwarningrange; + $self->{criticalrange} = $self->{criticalrange} ? + $self->{criticalrange} : $defaultcriticalrange; + if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) { + # warning = 10, critical = 20, warn if > 10, crit if > 20 + $level = $ERRORS{WARNING} if $value > $self->{warningrange}; + $level = $ERRORS{CRITICAL} if $value > $self->{criticalrange}; + } elsif ($self->{warningrange} =~ /([\d\.]+):/ && + $self->{criticalrange} =~ /([\d\.]+):/) { + # warning = 98:, critical = 95:, warn if < 98, crit if < 95 + $self->{warningrange} =~ /([\d\.]+):/; + $level = $ERRORS{WARNING} if $value < $1; + $self->{criticalrange} =~ /([\d\.]+):/; + $level = $ERRORS{CRITICAL} if $value < $1; + } + return $level; + # + # syntax error must be reported with returncode -1 + # +} + +sub add_nagios { + my $self = shift; + my $level = shift; + my $message = shift; + push(@{$self->{nagios}->{messages}->{$level}}, $message); + # recalc current level + foreach my $llevel qw(CRITICAL WARNING UNKNOWN OK) { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) { + $self->{nagios_level} = $ERRORS{$llevel}; + } + } +} + +sub add_nagios_ok { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{OK}, $message); +} + +sub add_nagios_warning { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{WARNING}, $message); +} + +sub add_nagios_critical { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{CRITICAL}, $message); +} + +sub add_nagios_unknown { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{UNKNOWN}, $message); +} + +sub add_perfdata { + my $self = shift; + my $data = shift; + push(@{$self->{nagios}->{perfdata}}, $data); +} + +sub merge_nagios { + my $self = shift; + my $child = shift; + foreach my $level (0..3) { + foreach (@{$child->{nagios}->{messages}->{$level}}) { + $self->add_nagios($level, $_); + } + #push(@{$self->{nagios}->{messages}->{$level}}, + # @{$child->{nagios}->{messages}->{$level}}); + } + push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}}); +} + + +sub calculate_result { + my $self = shift; + if ($ENV{NRPE_MULTILINESUPPORT} && + length join(" ", @{$self->{nagios}->{perfdata}}) > 200) { + foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { + # first the bad news + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_message} .= + "\n".join("\n", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}); + } + } + $self->{nagios_message} =~ s/^\n//g; + $self->{perfdata} = join("\n", @{$self->{nagios}->{perfdata}}); + } else { + foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { + # first the bad news + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_message} .= + join(", ", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}).", "; + } + } + $self->{nagios_message} =~ s/, $//g; + $self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}}); + } + foreach my $level ("OK", "UNKNOWN", "WARNING", "CRITICAL") { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_level} = $ERRORS{$level}; + } + } +} + +sub debug { + my $self = shift; + my $msg = shift; + if ($DBD::MySQL::Cluster::verbose) { + printf "%s %s\n", $msg, ref($self); + } +} + +sub connect { + my $self = shift; + my %params = @_; + my $retval = undef; + $self->{tic} = Time::HiRes::time(); + eval { + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "connection timeout\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + my $ndb_mgm = "ndb_mgm"; + $params{hostname} = "127.0.0.1" if ! $params{hostname}; + $ndb_mgm .= sprintf " --ndb-connectstring=%s", $params{hostname} + if $params{hostname}; + $ndb_mgm .= sprintf ":%d", $params{port} + if $params{port}; + $self->{show} = `$ndb_mgm -e show 2>&1`; + if ($? == -1) { + $self->add_nagios_critical("ndb_mgm failed to execute $!"); + } elsif ($? & 127) { + $self->add_nagios_critical("ndb_mgm failed to execute $!"); + } elsif ($? >> 8 != 0) { + $self->add_nagios_critical("ndb_mgm unable to connect"); + } else { + if ($self->{show} !~ /Cluster Configuration/) { + $self->add_nagios_critical("got no cluster configuration"); + } else { + $retval = 1; + } + } + }; + if ($@) { + $self->{errstr} = $@; + $self->{errstr} =~ s/at $0 .*//g; + chomp $self->{errstr}; + $self->add_nagios_critical($self->{errstr}); + $retval = undef; + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub trace { + my $self = shift; + my $format = shift; + $self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0; + if ($self->{verbose}) { + printf("%s: ", scalar localtime); + printf($format, @_); + } + if ($self->{trace}) { + my $logfh = new IO::File; + $logfh->autoflush(1); + if ($logfh->open("/tmp/check_mysql_health.trace", "a")) { + $logfh->printf("%s: ", scalar localtime); + $logfh->printf($format, @_); + $logfh->printf("\n"); + $logfh->close(); + } + } +} + +sub DESTROY { + my $self = shift; + my $handle1 = "null"; + my $handle2 = "null"; + if (defined $self->{handle}) { + $handle1 = ref($self->{handle}); + if (defined $self->{handle}->{handle}) { + $handle2 = ref($self->{handle}->{handle}); + } + } + $self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Cluster") { + } + $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Cluster") { + #printf "humpftata\n"; + } +} + +sub save_state { + my $self = shift; + my %params = @_; + my $extension = ""; + mkdir $params{statefilesdir} unless -d $params{statefilesdir}; + my $statefile = sprintf "%s/%s_%s", + $params{statefilesdir}, $params{hostname}, $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + open(STATE, ">$statefile"); + if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) { + $params{save}->{localtime} = scalar localtime $params{save}->{timestamp}; + } + printf STATE Data::Dumper::Dumper($params{save}); + close STATE; + $self->debug(sprintf "saved %s to %s", + Data::Dumper::Dumper($params{save}), $statefile); +} + +sub load_state { + my $self = shift; + my %params = @_; + my $extension = ""; + my $statefile = sprintf "%s/%s_%s", + $params{statefilesdir}, $params{hostname}, $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + if ( -f $statefile) { + our $VAR1; + eval { + require $statefile; + }; + if($@) { +printf "rumms\n"; + } + $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); + return $VAR1; + } else { + return undef; + } +} + +sub valdiff { + my $self = shift; + my $pparams = shift; + my %params = %{$pparams}; + my @keys = @_; + my $last_values = $self->load_state(%params) || eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = 0; + } + $empty_events->{timestamp} = 0; + $empty_events; + }; + foreach (@keys) { + $self->{'delta_'.$_} = $self->{$_} - $last_values->{$_}; + $self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_}); + } + $self->{'delta_timestamp'} = time - $last_values->{timestamp}; + $params{save} = eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = $self->{$_}; + } + $empty_events->{timestamp} = time; + $empty_events; + }; + $self->save_state(%params); +} + +sub requires_version { + my $self = shift; + my $version = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + my $instversion = $instances[0]->{version}; + if (! $self->version_is_minimum($version)) { + $self->add_nagios($ERRORS{UNKNOWN}, + sprintf "not implemented/possible for MySQL release %s", $instversion); + } +} + +sub version_is_minimum { + # the current version is newer or equal + my $self = shift; + my $version = shift; + my $newer = 1; + my @instances = DBD::MySQL::Cluster::return_clusters(); + my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version); + my @v2 = split(/\./, $instances[0]->{version}); + if (scalar(@v1) > scalar(@v2)) { + push(@v2, (0) x (scalar(@v1) - scalar(@v2))); + } elsif (scalar(@v2) > scalar(@v1)) { + push(@v1, (0) x (scalar(@v2) - scalar(@v1))); + } + foreach my $pos (0..$#v1) { + if ($v2[$pos] > $v1[$pos]) { + $newer = 1; + last; + } elsif ($v2[$pos] < $v1[$pos]) { + $newer = 0; + last; + } + } + #printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1); + return $newer; +} + +sub instance_rac { + my $self = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + return (lc $instances[0]->{parallel} eq "yes") ? 1 : 0; +} + +sub instance_thread { + my $self = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + return $instances[0]->{thread}; +} + +sub windows_cluster { + my $self = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + if ($instances[0]->{os} =~ /Win/i) { + return 1; + } else { + return 0; + } +} + +sub system_vartmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $self->system_tmpdir(); + } else { + return "/var/tmp/check_mysql_health"; + } +} + +sub system_oldvartmpdir { + my $self = shift; + return "/tmp"; +} + +sub system_tmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $ENV{TEMP} if defined $ENV{TEMP}; + return $ENV{TMP} if defined $ENV{TMP}; + return File::Spec->catfile($ENV{windir}, 'Temp') + if defined $ENV{windir}; + return 'C:\Temp'; + } else { + return "/tmp"; + } +} + + +package DBD::MySQL::Cluster::Node; + +use strict; + +our @ISA = qw(DBD::MySQL::Cluster); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + mode => $params{mode}, + timeout => $params{timeout}, + type => $params{type}, + id => $params{id}, + status => $params{status}, + }; + bless $self, $class; + $self->init(%params); + if ($params{type} eq "NDB") { + bless $self, "DBD::MySQL::Cluster::Node::NDB"; + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + if ($self->{status} =~ /@(\d+\.\d+\.\d+\.\d+)\s/) { + $self->{addr} = $1; + $self->{connected} = 1; + } elsif ($self->{status} =~ /accepting connect from (\d+\.\d+\.\d+\.\d+)/) { + $self->{addr} = $1; + $self->{connected} = 0; + } + if ($self->{status} =~ /starting,/) { + $self->{status} = "starting"; + } elsif ($self->{status} =~ /shutting,/) { + $self->{status} = "shutting"; + } else { + $self->{status} = $self->{connected} ? "running" : "dead"; + } +} + + +package DBD::MySQL::Cluster::Node::NDB; + +use strict; + +our @ISA = qw(DBD::MySQL::Cluster::Node); + + +sub init { + my $self = shift; + my %params = @_; + if ($self->{status} =~ /Nodegroup:\s*(\d+)/) { + $self->{nodegroup} = $1; + } + $self->{master} = ($self->{status} =~ /Master\)/) ? 1 : 0; +} + + +package Extraopts; + +use strict; +use File::Basename; +use Data::Dumper; + +sub new { + my $class = shift; + my %params = @_; + my $self = { + file => $params{file}, + commandline => $params{commandline}, + config => {}, + section => 'default_no_section', + }; + bless $self, $class; + $self->prepare_file_and_section(); + $self->init(); + return $self; +} + +sub prepare_file_and_section { + my $self = shift; + if (! defined $self->{file}) { + # ./check_stuff --extra-opts + $self->{section} = basename($0); + $self->{file} = $self->get_default_file(); + } elsif ($self->{file} =~ /^[^@]+$/) { + # ./check_stuff --extra-opts=special_opts + $self->{section} = $self->{file}; + $self->{file} = $self->get_default_file(); + } elsif ($self->{file} =~ /^@(.*)/) { + # ./check_stuff --extra-opts=@/etc/myconfig.ini + $self->{section} = basename($0); + $self->{file} = $1; + } elsif ($self->{file} =~ /^(.*?)@(.*)/) { + # ./check_stuff --extra-opts=special_opts@/etc/myconfig.ini + $self->{section} = $1; + $self->{file} = $2; + } +} + +sub get_default_file { + my $self = shift; + foreach my $default (qw(/etc/nagios/plugins.ini + /usr/local/nagios/etc/plugins.ini + /usr/local/etc/nagios/plugins.ini + /etc/opt/nagios/plugins.ini + /etc/nagios-plugins.ini + /usr/local/etc/nagios-plugins.ini + /etc/opt/nagios-plugins.ini)) { + if (-f $default) { + return $default; + } + } + return undef; +} + +sub init { + my $self = shift; + if (! defined $self->{file}) { + $self->{errors} = sprintf 'no extra-opts file specified and no default file found'; + } elsif (! -f $self->{file}) { + $self->{errors} = sprintf 'could not open %s', $self->{file}; + } else { + my $data = do { local (@ARGV, $/) = $self->{file}; <> }; + my $in_section = 'default_no_section'; + foreach my $line (split(/\n/, $data)) { + if ($line =~ /\[(.*)\]/) { + $in_section = $1; + } elsif ($line =~ /(.*)=(.*)/) { + $self->{config}->{$in_section}->{$1} = $2; + } + } + } +} + +sub is_valid { + my $self = shift; + return ! exists $self->{errors}; +} + +sub overwrite { + my $self = shift; + my %commandline = (); + if (scalar(keys %{$self->{config}->{default_no_section}}) > 0) { + foreach (keys %{$self->{config}->{default_no_section}}) { + $commandline{$_} = $self->{config}->{default_no_section}->{$_}; + } + } + if (exists $self->{config}->{$self->{section}}) { + foreach (keys %{$self->{config}->{$self->{section}}}) { + $commandline{$_} = $self->{config}->{$self->{section}}->{$_}; + } + } + foreach (keys %commandline) { + if (! exists $self->{commandline}->{$_}) { + $self->{commandline}->{$_} = $commandline{$_}; + } + } +} + + + +package main; + +use strict; +use Getopt::Long qw(:config no_ignore_case); +use File::Basename; +use lib dirname($0); + + + +use vars qw ($PROGNAME $REVISION $CONTACT $TIMEOUT $STATEFILESDIR $needs_restart %commandline); + +$PROGNAME = "check_mysql_health"; +$REVISION = '$Revision: 2.1.3 $'; +$CONTACT = 'gerhard.lausser@consol.de'; +$TIMEOUT = 60; +$STATEFILESDIR = '/var/run'; +$needs_restart = 0; + +my @modes = ( + ['server::connectiontime', + 'connection-time', undef, + 'Time to connect to the server' ], + ['server::uptime', + 'uptime', undef, + 'Time the server is running' ], + ['server::instance::connectedthreads', + 'threads-connected', undef, + 'Number of currently open connections' ], + ['server::instance::threadcachehitrate', + 'threadcache-hitrate', undef, + 'Hit rate of the thread-cache' ], + ['server::instance::replication::slavelag', + 'slave-lag', ['replication-slave-lag'], + 'Seconds behind master' ], + ['server::instance::replication::slaveiorunning', + 'slave-io-running', ['replication-slave-io-running'], + 'Slave io running: Yes' ], + ['server::instance::replication::slavesqlrunning', + 'slave-sql-running', ['replication-slave-sql-running'], + 'Slave sql running: Yes' ], + ['server::instance::querycachehitrate', + 'qcache-hitrate', ['querycache-hitrate'], + 'Query cache hitrate' ], + ['server::instance::querycachelowmemprunes', + 'qcache-lowmem-prunes', ['querycache-lowmem-prunes'], + 'Query cache entries pruned because of low memory' ], + ['server::instance::myisam::keycache::hitrate', + 'keycache-hitrate', ['myisam-keycache-hitrate'], + 'MyISAM key cache hitrate' ], + ['server::instance::innodb::bufferpool::hitrate', + 'bufferpool-hitrate', ['innodb-bufferpool-hitrate'], + 'InnoDB buffer pool hitrate' ], + ['server::instance::innodb::bufferpool::waitfree', + 'bufferpool-wait-free', ['innodb-bufferpool-wait-free'], + 'InnoDB buffer pool waits for clean page available' ], + ['server::instance::innodb::logwaits', + 'log-waits', ['innodb-log-waits'], + 'InnoDB log waits because of a too small log buffer' ], + ['server::instance::tablecachehitrate', + 'tablecache-hitrate', undef, + 'Table cache hitrate' ], + ['server::instance::tablelockcontention', + 'table-lock-contention', undef, + 'Table lock contention' ], + ['server::instance::tableindexusage', + 'index-usage', undef, + 'Usage of indices' ], + ['server::instance::tabletmpondisk', + 'tmp-disk-tables', undef, + 'Percent of temp tables created on disk' ], + ['server::instance::openfiles', + 'open-files', undef, + 'Percent of opened files' ], + ['server::instance::slowqueries', + 'slow-queries', undef, + 'Slow queries' ], + ['server::instance::longprocs', + 'long-running-procs', undef, + 'long running processes' ], + ['cluster::ndbdrunning', + 'cluster-ndbd-running', undef, + 'ndnd nodes are up and running' ], + ['server::sql', + 'sql', undef, + 'any sql command returning a single number' ], +); + +sub print_usage () { + print <<EOUS; + Usage: + $PROGNAME [-v] [-t <timeout>] [[--hostname <hostname>] + [--port <port> | --socket <socket>] + --username <username> --password <password>] --mode <mode> + [--method mysql] + $PROGNAME [-h | --help] + $PROGNAME [-V | --version] + + Options: + --hostname + the database server's hostname + --port + the database's port. (default: 3306) + --socket + the database's unix socket. + --username + the mysql db user + --password + the mysql db user's password + --database + the database's name. (default: information_schema) + --warning + the warning range + --critical + the critical range + --mode + the mode of the plugin. select one of the following keywords: +EOUS + my $longest = length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0]); + my $format = " %-". + (length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0])). + "s\t(%s)\n"; + foreach (@modes) { + printf $format, $_->[1], $_->[3]; + } + printf "\n"; + print <<EOUS; + --name + the name of something that needs to be further specified, + currently only used for sql statements + --name2 + if name is a sql statement, this statement would appear in + the output and the performance data. This can be ugly, so + name2 can be used to appear instead. + --regexp + if this parameter is used, name will be interpreted as a + regular expression. + --units + one of %, KB, MB, GB. This is used for a better output of mode=sql + and for specifying thresholds for mode=tablespace-free + + In mode sql you can url-encode the statement so you will not have to mess + around with special characters in your Nagios service definitions. + Instead of + --name="select count(*) from v\$session where status = 'ACTIVE'" + you can say + --name=select%20count%28%2A%29%20from%20v%24session%20where%20status%20%3D%20%27ACTIVE%27 + For your convenience you can call check_mysql_health with the --mode encode + option and it will encode the standard input. + + You can find the full documentation at + http://www.consol.de/opensource/nagios/check-mysql-health + +EOUS + +} + +sub print_help () { + print "Copyright (c) 2009 Gerhard Lausser\n\n"; + print "\n"; + print " Check various parameters of MySQL databases \n"; + print "\n"; + print_usage(); + support(); +} + + +sub print_revision ($$) { + my $commandName = shift; + my $pluginRevision = shift; + $pluginRevision =~ s/^\$Revision: //; + $pluginRevision =~ s/ \$\s*$//; + print "$commandName ($pluginRevision)\n"; + print "This nagios plugin comes with ABSOLUTELY NO WARRANTY. You may redistribute\ncopies of this plugin under the terms of the GNU General Public License.\n"; +} + +sub support () { + my $support='Send email to gerhard.lausser@consol.de if you have questions\nregarding use of this software. \nPlease include version information with all correspondence (when possible,\nuse output from the --version option of the plugin itself).\n'; + $support =~ s/@/\@/g; + $support =~ s/\\n/\n/g; + print $support; +} + +sub contact_author ($$) { + my $item = shift; + my $strangepattern = shift; + if ($commandline{verbose}) { + printf STDERR + "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n". + "You found a line which is not recognized by %s\n". + "This means, certain components of your system cannot be checked.\n". + "Please contact the author %s and\nsend him the following output:\n\n". + "%s /%s/\n\nThank you!\n". + "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n", + $PROGNAME, $CONTACT, $item, $strangepattern; + } +} + +%commandline = (); +my @params = ( + "timeout|t=i", + "version|V", + "help|h", + "verbose|v", + "debug|d", + "hostname|H=s", + "database=s", + "port|P=s", + "socket|S=s", + "username|u=s", + "password|p=s", + "mode|m=s", + "name=s", + "name2=s", + "regexp", + "perfdata", + "warning=s", + "critical=s", + "absolute|a", + "environment|e=s%", + "method=s", + "runas|r=s", + "scream", + "shell", + "eyecandy", + "encode", + "units=s", + "lookback=i", + "3", + "with-mymodules-dyn-dir=s", + "extra-opts:s"); + +if (! GetOptions(\%commandline, @params)) { + print_help(); + exit $ERRORS{UNKNOWN}; +} + +if (exists $commandline{'extra-opts'}) { + # read the extra file and overwrite other parameters + my $extras = Extraopts->new(file => $commandline{'extra-opts'}, commandline => + \%commandline); + if (! $extras->is_valid()) { + printf "extra-opts are not valid: %s\n", $extras->{errors}; + exit $ERRORS{UNKNOWN}; + } else { + $extras->overwrite(); + } +} + +if (exists $commandline{version}) { + print_revision($PROGNAME, $REVISION); + exit $ERRORS{OK}; +} + +if (exists $commandline{help}) { + print_help(); + exit $ERRORS{OK}; +} elsif (! exists $commandline{mode}) { + printf "Please select a mode\n"; + print_help(); + exit $ERRORS{OK}; +} + +if ($commandline{mode} eq "encode") { + my $input = <>; + chomp $input; + $input =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; + printf "%s\n", $input; + exit $ERRORS{OK}; +} + +if (exists $commandline{3}) { + $ENV{NRPE_MULTILINESUPPORT} = 1; +} + +if (exists $commandline{timeout}) { + $TIMEOUT = $commandline{timeout}; +} + +if (exists $commandline{verbose}) { + $DBD::MySQL::Server::verbose = exists $commandline{verbose}; +} + +if (exists $commandline{scream}) { +# $DBD::MySQL::Server::hysterical = exists $commandline{scream}; +} + +if (exists $commandline{method}) { + # snmp or mysql cmdline +} else { + $commandline{method} = "dbi"; +} + +if (exists $commandline{'with-mymodules-dyn-dir'}) { + $DBD::MYSQL::Server::my_modules_dyn_dir = $commandline{'with-mymodules-dyn-dir +'}; +} else { + $DBD::MYSQL::Server::my_modules_dyn_dir = '/usr/local/nagios/libexec'; +} + +if (exists $commandline{environment}) { + # if the desired environment variable values are different from + # the environment of this running script, then a restart is necessary. + # because setting $ENV does _not_ change the environment of the running script. + foreach (keys %{$commandline{environment}}) { + if ((! $ENV{$_}) || ($ENV{$_} ne $commandline{environment}->{$_})) { + $needs_restart = 1; + $ENV{$_} = $commandline{environment}->{$_}; + printf STDERR "new %s=%s forces restart\n", $_, $ENV{$_} + if $DBD::MySQL::Server::verbose; + } + } + # e.g. called with --runas dbnagio. shlib_path environment variable is stripped + # during the sudo. + # so the perl interpreter starts without a shlib_path. but --runas cares for + # a --environment shlib_path=... + # so setting the environment variable in the code above and restarting the + # perl interpreter will help it find shared libs +} + +if (exists $commandline{runas}) { + # remove the runas parameter + # exec sudo $0 ... the remaining parameters + $needs_restart = 1; + # if the calling script has a path for shared libs and there is no --environment + # parameter then the called script surely needs the variable too. + foreach my $important_env qw(LD_LIBRARY_PATH SHLIB_PATH + ORACLE_HOME TNS_ADMIN ORA_NLS ORA_NLS33 ORA_NLS10) { + if ($ENV{$important_env} && ! scalar(grep { /^$important_env=/ } + keys %{$commandline{environment}})) { + $commandline{environment}->{$important_env} = $ENV{$important_env}; + printf STDERR "add important --environment %s=%s\n", + $important_env, $ENV{$important_env} if $DBD::MySQL::Server::verbose; + } + } +} + +if ($needs_restart) { + my @newargv = (); + my $runas = undef; + if (exists $commandline{runas}) { + $runas = $commandline{runas}; + delete $commandline{runas}; + } + foreach my $option (keys %commandline) { + if (grep { /^$option/ && /=/ } @params) { + if (ref ($commandline{$option}) eq "HASH") { + foreach (keys %{$commandline{$option}}) { + push(@newargv, sprintf "--%s", $option); + push(@newargv, sprintf "%s=%s", $_, $commandline{$option}->{$_}); + } + } else { + push(@newargv, sprintf "--%s", $option); + push(@newargv, sprintf "%s", $commandline{$option}); + } + } else { + push(@newargv, sprintf "--%s", $option); + } + } + if ($runas) { + exec "sudo", "-S", "-u", $runas, $0, @newargv; + } else { + exec $0, @newargv; + # this makes sure that even a SHLIB or LD_LIBRARY_PATH are set correctly + # when the perl interpreter starts. Setting them during runtime does not + # help loading e.g. libclntsh.so + } + exit; +} + +if (exists $commandline{shell}) { + # forget what you see here. + system("/bin/sh"); +} + +if (exists $commandline{name}) { + # objects can be encoded like an url + # with s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; + if (($commandline{mode} ne "sql") || + (($commandline{mode} eq "sql") && + ($commandline{name} =~ /select%20/i))) { # protect ... like '%cac%' ... from decoding + $commandline{name} =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; + } + if ($commandline{name} =~ /^0$/) { + # without this, $params{selectname} would be treated like undef + $commandline{name} = "00"; + } +} + +$SIG{'ALRM'} = sub { + printf "UNKNOWN - %s timed out after %d seconds\n", $PROGNAME, $TIMEOUT; + exit $ERRORS{UNKNOWN}; +}; +alarm($TIMEOUT); + +my $nagios_level = $ERRORS{UNKNOWN}; +my $nagios_message = ""; +my $perfdata = ""; +if ($commandline{mode} =~ /^my-([^\-.]+)/) { + my $param = $commandline{mode}; + $param =~ s/\-/::/g; + push(@modes, [$param, $commandline{mode}, undef, 'my extension']); +} elsif ((! grep { $commandline{mode} eq $_ } map { $_->[1] } @modes) && + (! grep { $commandline{mode} eq $_ } map { defined $_->[2] ? @{$_->[2]} : () } @modes)) { + printf "UNKNOWN - mode %s\n", $commandline{mode}; + print_usage(); + exit 3; +} + +$commandline{mode} = ( + map { $_->[0] } + grep { + ($commandline{mode} eq $_->[1]) || + ( defined $_->[2] && grep { $commandline{mode} eq $_ } @{$_->[2]}) + } @modes +)[0]; +my %params = ( + timeout => $TIMEOUT, + mode => $commandline{mode}, + method => $commandline{method} || + $ENV{NAGIOS__SERVICEMYSQL_METH} || + $ENV{NAGIOS__HOSTMYSQL_METH} || 'dbi', + hostname => $commandline{hostname} || + $ENV{NAGIOS__SERVICEMYSQL_HOST} || + $ENV{NAGIOS__HOSTMYSQL_HOST} || 'localhost', + database => $commandline{database} || + $ENV{NAGIOS__SERVICEMYSQL_DATABASE} || + $ENV{NAGIOS__HOSTMYSQL_DATABASE} || 'information_schema', + port => $commandline{port} || (($commandline{mode} =~ /^cluster/) ? + ($ENV{NAGIOS__SERVICENDBMGM_PORT} || $ENV{NAGIOS__HOSTNDBMGM_PORT} || 1186) : + ($ENV{NAGIOS__SERVICEMYSQL_PORT} || $ENV{NAGIOS__HOSTMYSQL_PORT} || 3306)), + socket => $commandline{socket} || + $ENV{NAGIOS__SERVICEMYSQL_SOCKET} || + $ENV{NAGIOS__HOSTMYSQL_SOCKET}, + username => $commandline{username} || + $ENV{NAGIOS__SERVICEMYSQL_USER} || + $ENV{NAGIOS__HOSTMYSQL_USER}, + password => $commandline{password} || + $ENV{NAGIOS__SERVICEMYSQL_PASS} || + $ENV{NAGIOS__HOSTMYSQL_PASS}, + warningrange => $commandline{warning}, + criticalrange => $commandline{critical}, + absolute => $commandline{absolute}, + lookback => $commandline{lookback}, + selectname => $commandline{name} || $commandline{tablespace} || $commandline{datafile}, + regexp => $commandline{regexp}, + name => $commandline{name}, + name2 => $commandline{name2} || $commandline{name}, + units => $commandline{units}, + lookback => $commandline{lookback} || 0, + eyecandy => $commandline{eyecandy}, + statefilesdir => $STATEFILESDIR, +); + +my $server = undef; +my $cluster = undef; + +if ($params{mode} =~ /^(server|my)/) { + $server = DBD::MySQL::Server->new(%params); + $server->nagios(%params); + $server->calculate_result(); + $nagios_message = $server->{nagios_message}; + $nagios_level = $server->{nagios_level}; + $perfdata = $server->{perfdata}; +} elsif ($params{mode} =~ /^cluster/) { + $cluster = DBD::MySQL::Cluster->new(%params); + $cluster->nagios(%params); + $cluster->calculate_result(); + $nagios_message = $cluster->{nagios_message}; + $nagios_level = $cluster->{nagios_level}; + $perfdata = $cluster->{perfdata}; +} + +printf "%s - %s", $ERRORCODES{$nagios_level}, $nagios_message; +printf " | %s", $perfdata if $perfdata; +printf "\n"; +exit $nagios_level; + + +__END__ + + diff --git a/lib/facter/mountpoints.rb b/lib/facter/mountpoints.rb new file mode 100644 index 0000000..c51e6e6 --- /dev/null +++ b/lib/facter/mountpoints.rb @@ -0,0 +1,50 @@ +begin + + mountpoints = [] + # we show devices, but we avoid outputing duplicate devices + devices = [] + Facter.add("mountpoints") do + ignorefs = ["NFS", "nfs", "nfs4", "nfsd", "afs", "binfmt_misc", "proc", "smbfs", + "autofs", "iso9660", "ncpfs", "coda", "devpts", "ftpfs", "devfs", + "mfs", "shfs", "sysfs", "cifs", "lustre_lite", "tmpfs", "usbfs", "udf", + "fusectl", "fuse.snapshotfs", "rpc_pipefs"] + begin + require 'filesystem' + rescue Exception => e + confine :kernel => :linux + ENV["PATH"]="/bin:/sbin:/usr/bin:/usr/sbin" + fs_source = nil + if FileTest.exists?("/etc/mtab") + fs_source = "/etc/mtab" + elsif FileTest.exists?("/proc/mounts") + fs_source = "/proc/mounts" + end + + mounts = File.read(fs_source).split("\n") + mounts.each do |mount| + mount = mount.split(" ") + if ((not ignorefs.include?(mount[2])) && (mount[3] !~ /bind/) && (not devices.include?(mount[0])) && (not mountpoints.include?(mount[1]))) + mountpoints.push(mount[1]) + end + devices.push(mount[0]) if not devices.include?(mount[0]) + end + else + FileSystem.mounts.each do |m| + if ((not ignorefs.include?(m.fstype)) && (m.options !~ /bind/) && !devices.include?(mount[0])) + mountpoints.push(m.mount) + end + devices.push(m.mount) if not devices.include?(m.mount) + end + end + setcode do + mountpoints.join(",") + end + end + Facter.add("devices") do + setcode do + devices.join(",") + end + end + +rescue Exception => e +end diff --git a/manifests/apache.pp b/manifests/apache.pp index 36994f8..c560e5b 100644 --- a/manifests/apache.pp +++ b/manifests/apache.pp @@ -1,4 +1,21 @@ -class nagios::apache { +class nagios::apache inherits ::apache { $nagios_httpd = 'apache' include nagios + + case $operatingsystem { + 'debian': { + file { "${nagios::defaults::vars::int_nagios_cfgdir}/apache2.conf": + ensure => present, + source => ["puppet:///site-nagios/configs/${fqdn}/apache2.conf", + "puppet:///site-nagios/configs/apache2.conf", + "puppet:///nagios/configs/apache2.conf"], + } + + apache::config::global { "nagios3.conf": + ensure => link, + target => "${nagios::defaults::vars::int_nagios_cfgdir}/apache2.conf", + require => File["${nagios::defaults::vars::int_nagios_cfgdir}/apache2.conf"], + } + } + } } diff --git a/manifests/base.pp b/manifests/base.pp index 6bf2533..8b96308 100644 --- a/manifests/base.pp +++ b/manifests/base.pp @@ -1,4 +1,6 @@ class nagios::base { + # include the variables + include nagios::defaults::vars package { 'nagios': alias => 'nagios', @@ -14,38 +16,37 @@ class nagios::base { # this file should contain all the nagios_puppet-paths: file { 'nagios_main_cfg': - path => "${nagios_cfgdir}/nagios.cfg", - source => [ "puppet://$server/modules/site-nagios/configs/${fqdn}/nagios.cfg", - "puppet://$server/modules/site-nagios/configs/${operatingsystem}/nagios.cfg", - "puppet://$server/modules/site-nagios/configs/nagios.cfg", - "puppet://$server/modules/nagios/configs/${operatingsystem}/nagios.cfg", - "puppet://$server/modules/nagios/configs/nagios.cfg" ], + path => "${nagios::defaults::vars::int_nagios_cfgdir}/nagios.cfg", + source => [ "puppet:///modules/site-nagios/configs/${fqdn}/nagios.cfg", + "puppet:///modules/site-nagios/configs/${operatingsystem}/nagios.cfg", + "puppet:///modules/site-nagios/configs/nagios.cfg", + "puppet:///modules/nagios/configs/${operatingsystem}/nagios.cfg", + "puppet:///modules/nagios/configs/nagios.cfg" ], notify => Service['nagios'], mode => 0644, owner => root, group => root; } file { 'nagios_cgi_cfg': - path => "${nagios_cfgdir}/cgi.cfg", - source => [ "puppet://$server/modules/site-nagios/configs/${fqdn}/cgi.cfg", - "puppet://$server/modules/site-nagios/configs/${operatingsystem}/cgi.cfg", - "puppet://$server/modules/site-nagios/configs/cgi.cfg", - "puppet://$server/modules/nagios/configs/${operatingsystem}/cgi.cfg", - "puppet://$server/modules/nagios/configs/cgi.cfg" ], + path => "${nagios::defaults::vars::int_nagios_cfgdir}/cgi.cfg", + source => [ "puppet:///modules/site-nagios/configs/${fqdn}/cgi.cfg", + "puppet:///modules/site-nagios/configs/${operatingsystem}/cgi.cfg", + "puppet:///modules/site-nagios/configs/cgi.cfg", + "puppet:///modules/nagios/configs/${operatingsystem}/cgi.cfg", + "puppet:///modules/nagios/configs/cgi.cfg" ], mode => '0644', owner => 'root', group => 0, notify => Service['apache'], } file { 'nagios_htpasswd': - path => "${nagios_cfgdir}/htpasswd.users", - source => [ "puppet://$server/modules/site-nagios/htpasswd.users", - "puppet://$server/modules/nagios/htpasswd.users", - "puppet://$server/files/etc/nagios3/htpasswd.users" ], + path => "${nagios::defaults::vars::int_nagios_cfgdir}/htpasswd.users", + source => [ "puppet:///modules/site-nagios/htpasswd.users", + "puppet:///modules/nagios/htpasswd.users" ], mode => 0640, owner => root, group => apache; } file { 'nagios_private': - path => "${nagios_cfgdir}/private/", - source => "puppet://$server/modules/common/empty", + path => "${nagios::defaults::vars::int_nagios_cfgdir}/private/", + source => "puppet:///modules/common/empty", ensure => directory, purge => true, recurse => true, @@ -54,96 +55,123 @@ class nagios::base { } file { 'nagios_private_resource_cfg': - path => "${nagios_cfgdir}/private/resource.cfg", - source => "puppet://$server/modules/nagios/configs/${operatingsystem}/private/resource.cfg.${architecture}", + path => "${nagios::defaults::vars::int_nagios_cfgdir}/private/resource.cfg", + source => [ "puppet:///modules/site-nagios/configs/${operatingsystem}/private/resource.cfg.${architecture}", + "puppet:///modules/nagios/configs/${operatingsystem}/private/resource.cfg.${architecture}" ], notify => Service['nagios'], owner => root, group => nagios, mode => '0640'; } file { 'nagios_confd': - path => "${nagios_cfgdir}/conf.d/", - source => "puppet://$server/modules/common/empty", + path => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/", + source => "puppet:///modules/common/empty", ensure => directory, purge => true, recurse => true, notify => Service['nagios'], mode => '0750', owner => root, group => nagios; } - + Package <<| tag == 'nagios::plugin::deploy::package' |>> + File <<| tag == 'nagios::plugin::deploy::file' |>> Nagios_command <<||>> - Nagios_contact <<||>> Nagios_contactgroup <<||>> - Nagios_host <<||>> + Nagios_contact <<||>> + Nagios_hostdependency <<||>> + Nagios_hostescalation <<||>> Nagios_hostextinfo <<||>> Nagios_hostgroup <<||>> - Nagios_hostgroupescalation <<||>> - Nagios_service <<||>> + Nagios_host <<||>> Nagios_servicedependency <<||>> Nagios_serviceescalation <<||>> + Nagios_servicegroup <<||>> Nagios_serviceextinfo <<||>> + Nagios_service <<||>> Nagios_timeperiod <<||>> + File <<| tag == 'nagios_plugin' |>> Nagios_command <||> { - target => "${nagios_cfgdir}/conf.d/nagios_command.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_command.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_contact <||> { - target => "${nagios_cfgdir}/conf.d/nagios_contact.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_contact.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_contactgroup <||> { - target => "${nagios_cfgdir}/conf.d/nagios_contactgroup.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_contactgroup.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_host <||> { - target => "${nagios_cfgdir}/conf.d/nagios_host.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_host.cfg", + require => File['nagios_confd'], + notify => Service['nagios'], + } + Nagios_hostdependency <||> { + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostdependency.cfg", + notify => Service['nagios'], + } + Nagios_hostescalation <||> { + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostescalation.cfg", notify => Service['nagios'], } Nagios_hostextinfo <||> { - target => "${nagios_cfgdir}/conf.d/nagios_hostextinfo.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostextinfo.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_hostgroup <||> { - target => "${nagios_cfgdir}/conf.d/nagios_hostgroup.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostgroup.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } - Nagios_hostgroupescalation <||> { - target => "${nagios_cfgdir}/conf.d/nagios_hostgroupescalation.cfg", + Nagios_service <||> { + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_service.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } - Nagios_service <||> { - target => "${nagios_cfgdir}/conf.d/nagios_service.cfg", + Nagios_servicegroup <||> { + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_servicegroup.cfg", notify => Service['nagios'], } Nagios_servicedependency <||> { - target => "${nagios_cfgdir}/conf.d/nagios_servicedependency.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_servicedependency.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_serviceescalation <||> { - target => "${nagios_cfgdir}/conf.d/nagios_serviceescalation.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_serviceescalation.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_serviceextinfo <||> { - target => "${nagios_cfgdir}/conf.d/nagios_serviceextinfo.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_serviceextinfo.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } Nagios_timeperiod <||> { - target => "${nagios_cfgdir}/conf.d/nagios_timeperiod.cfg", + target => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_timeperiod.cfg", + require => File['nagios_confd'], notify => Service['nagios'], } - file{[ "${nagios_cfgdir}/conf.d/nagios_command.cfg", - "${nagios_cfgdir}/conf.d/nagios_contact.cfg", - "${nagios_cfgdir}/conf.d/nagios_contactgroup.cfg", - "${nagios_cfgdir}/conf.d/nagios_host.cfg", - "${nagios_cfgdir}/conf.d/nagios_hostextinfo.cfg", - "${nagios_cfgdir}/conf.d/nagios_hostgroup.cfg", - "${nagios_cfgdir}/conf.d/nagios_hostgroupescalation.cfg", - "${nagios_cfgdir}/conf.d/nagios_service.cfg", - "${nagios_cfgdir}/conf.d/nagios_servicedependency.cfg", - "${nagios_cfgdir}/conf.d/nagios_serviceescalation.cfg", - "${nagios_cfgdir}/conf.d/nagios_serviceextinfo.cfg", - "${nagios_cfgdir}/conf.d/nagios_timeperiod.cfg" ]: + file{[ "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_command.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_contact.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_contactgroup.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_host.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostdependency.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostescalation.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostextinfo.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostgroup.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_hostgroupescalation.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_service.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_servicedependency.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_serviceescalation.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_serviceextinfo.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_servicegroup.cfg", + "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_timeperiod.cfg" ]: ensure => file, replace => false, notify => Service['nagios'], @@ -153,8 +181,8 @@ class nagios::base { # manage nagios cfg files # must be defined after exported resource overrides and cfg file defs file { 'nagios_cfgdir': - path => "${nagios_cfgdir}/", - source => "puppet://$server/modules/common/empty", + path => "${nagios::defaults::vars::int_nagios_cfgdir}/", + source => "puppet:///modules/common/empty", ensure => directory, recurse => true, purge => true, diff --git a/manifests/centos.pp b/manifests/centos.pp index 4d9507b..35379b8 100644 --- a/manifests/centos.pp +++ b/manifests/centos.pp @@ -1,6 +1,6 @@ class nagios::centos inherits nagios::base { - package { [ 'nagios-plugins', 'nagios-plugins-smtp','nagios-plugins-http', 'nagios-plugins-ssh', 'nagios-plugins-udp', 'nagios-plugins-tcp', 'nagios-plugins-dig', 'nagios-plugins-nrpe', 'nagios-plugins-load', 'nagios-plugins-dns', 'nagios-plugins-ping', 'nagios-plugins-procs', 'nagios-plugins-users', 'nagios-plugins-ldap', 'nagios-plugins-disk', 'nagios-plugins-swap', 'nagios-plugins-nagios', 'nagios-plugins-perl', 'nagios-plugins-ntp', 'nagios-plugins-snmp' ]: + package { [ 'nagios-plugins', 'nagios-plugins-smtp','nagios-plugins-http', 'nagios-plugins-ssh', 'nagios-plugins-tcp', 'nagios-plugins-dig', 'nagios-plugins-nrpe', 'nagios-plugins-load', 'nagios-plugins-dns', 'nagios-plugins-ping', 'nagios-plugins-procs', 'nagios-plugins-users', 'nagios-plugins-ldap', 'nagios-plugins-disk', 'nagios-plugins-swap', 'nagios-plugins-nagios', 'nagios-plugins-perl', 'nagios-plugins-ntp', 'nagios-plugins-snmp' ]: ensure => 'present', notify => Service['nagios'], } diff --git a/manifests/command/imap_pop3.pp b/manifests/command/imap_pop3.pp new file mode 100644 index 0000000..174b09c --- /dev/null +++ b/manifests/command/imap_pop3.pp @@ -0,0 +1,14 @@ +class nagios::command::imap_pop3 { + nagios_command{ + 'check_imap': + command_line => '$USER1$/check_imap -H $ARG1$ -p $ARG2$'; + 'check_imap_ssl': + command_line => '$USER1$/check_imap -H $ARG1$ -p $ARG2$ -S'; + 'check_pop3': + command_line => '$USER1$/check_pop -H $ARG1$ -p $ARG2$'; + 'check_pop3_ssl': + command_line => '$USER1$/check_pop -H $ARG1$ -p $ARG2$ -S'; + 'check_managesieve': + command_line => '$USER1$/check_tcp -H $ARG1$ -p 2000'; + } +} diff --git a/manifests/command/smtp.pp b/manifests/command/smtp.pp new file mode 100644 index 0000000..04f8543 --- /dev/null +++ b/manifests/command/smtp.pp @@ -0,0 +1,14 @@ +class nagios::command::smtp { + nagios_command{ + 'check_smtp': + command_line => '$USER1$/check_smtp -H $ARG1$ -p $ARG2$'; + 'check_smtp_tls': + command_line => '$USER1$/check_smtp -H $ARG1$ -p $ARG2$ -S'; + 'check_smtp_cert': + command_line => '$USER1$/check_smtp -H $ARG1$ -p $ARG2$ -S -D $ARG3$'; + 'check_ssmtp': + command_line => '$USER1$/check_ssmtp -H $ARG1$ -p $ARG2$ -S'; + 'check_ssmtp_cert': + command_line => '$USER1$/check_ssmtp -H $ARG1$ -p $ARG2$ -S -D $ARG3$'; + } +} diff --git a/manifests/debian.pp b/manifests/debian.pp index a503324..b9e87ec 100644 --- a/manifests/debian.pp +++ b/manifests/debian.pp @@ -14,7 +14,7 @@ class nagios::debian inherits nagios::base { File['nagios_htpasswd', 'nagios_cgi_cfg'] { group => 'www-data' } - file { "${nagios_cfgdir}/stylesheets": + file { "${nagios::defaults::vars::int_nagios_cfgdir}/stylesheets": ensure => directory, purge => false, recurse => true, diff --git a/manifests/defaults.pp b/manifests/defaults.pp index 32ba335..7c25ac9 100644 --- a/manifests/defaults.pp +++ b/manifests/defaults.pp @@ -8,5 +8,5 @@ class nagios::defaults { include nagios::defaults::hostgroups include nagios::defaults::templates include nagios::defaults::timeperiods - + include nagios::defaults::plugins } diff --git a/manifests/defaults/commands.pp b/manifests/defaults/commands.pp index c3190af..f542d7b 100644 --- a/manifests/defaults/commands.pp +++ b/manifests/defaults/commands.pp @@ -1,55 +1,91 @@ class nagios::defaults::commands { - # common service commands + include nagios::command::smtp + include nagios::command::imap_pop3 - nagios_command { - check_dummy: + # common service commands + case $operatingsystem { + debian,ubuntu: { + nagios_command { + check_dummy: + command_line => '$USER1$/check_dummy $ARG1$'; + check_https_cert: + command_line => '$USER1$/check_http --ssl -C 20 -H $HOSTADDRESS$ -I $HOSTADDRESS$'; + check_http_url: + command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$'; + check_http_url_regex: + command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$ -e $ARG3$'; + check_https_url: + command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$'; + check_https_url_regex: + command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$ -e $ARG3$'; + check_mysql_db: + command_line => '$USER1$/check_mysql -H $ARG1$ -P $ARG2$ -u $ARG3$ -p $ARG4$ -d $ARG5$'; + check_ntp_time: + command_line => '$USER1$/check_ntp_time -H $HOSTADDRESS$ -w 0.5 -c 1'; + check_silc: + command_line => '$USER1$/check_tcp -p 706 -H $ARG1$'; + check_sobby: + command_line => '$USER1$/check_tcp -H $ARG1$ -p $ARG2$'; + check_jabber: + command_line => '$USER1$/check_jabber -H $ARG1$'; + } + } + default: { + nagios_command { + check_dummy: command_line => '$USER1$/check_dummy $ARG1$'; - check_ping: + check_ping: command_line => '$USER1$/check_ping -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$'; - check-host-alive: + check-host-alive: command_line => '$USER1$/check_ping -H $HOSTADDRESS$ -w 5000,100% -c 5000,100% -p 1'; - check_tcp: + check_tcp: command_line => '$USER1$/check_tcp -H $HOSTADDRESS$ -p $ARG1$'; - check_udp: + check_udp: command_line => '$USER1$/check_udp -H $HOSTADDRESS$ -p $ARG1$'; - check_load: + check_load: command_line => '$USER1$/check_load --warning=$ARG1$,$ARG2$,$ARG3$ --critical=$ARG4$,$ARG5$,$ARG6$'; - check_disk: + check_disk: command_line => '$USER1$/check_disk -w $ARG1$ -c $ARG2$ -e -p $ARG3$'; - check_all_disks: + check_all_disks: command_line => '$USER1$/check_disk -w $ARG1$ -c $ARG2$ -e'; - check_ssh: + check_ssh: command_line => '$USER1$/check_ssh $HOSTADDRESS$'; - check_ssh_port: + check_ssh_port: command_line => '$USER1$/check_ssh -p $ARG1$ $HOSTADDRESS$'; - check_http: + check_ssh_port_host: + command_line => '$USER1$/check_ssh -p $ARG1$ $ARG2$'; + check_http: command_line => '$USER1$/check_http -H $HOSTADDRESS$ -I $HOSTADDRESS$'; - check_https: + check_https: command_line => '$USER1$/check_http --ssl -H $HOSTADDRESS$ -I $HOSTADDRESS$'; - check_https_cert: + check_https_cert: command_line => '$USER1$/check_http --ssl -C 20 -H $HOSTADDRESS$ -I $HOSTADDRESS$'; - check_http_url: + check_http_url: command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$'; - check_http_url_regex: + check_http_url_regex: command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$ -e $ARG3$'; - check_https_url: + check_https_url: command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$'; - check_https_url_regex: + check_https_url_regex: command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$ -e $ARG3$'; - check_mysql: + check_mysql: command_line => '$USER1$/check_mysql -H $ARG1$ -P $ARG2$ -u $ARG3$ -p $ARG4$'; - check_mysql_db: + check_mysql_db: command_line => '$USER1$/check_mysql -H $ARG1$ -P $ARG2$ -u $ARG3$ -p $ARG4$ -d $ARG5$'; - check_ntp_time: + check_ntp_time: command_line => '$USER1$/check_ntp_time -H $HOSTADDRESS$ -w 0.5 -c 1'; - check_silc: + check_silc: command_line => '$USER1$/check_tcp -p 706 -H $ARG1$'; - check_sobby: + check_sobby: command_line => '$USER1$/check_tcp -H $ARG1$ -p $ARG2$'; - check_jabber: + check_jabber: command_line => '$USER1$/check_jabber -H $ARG1$'; - } + check_git: + command_line => '$USER1$/check_tcp -H $ARG1$ -p 9418'; + } + } + } # commands for services defined by other modules @@ -57,19 +93,41 @@ class nagios::defaults::commands { # from apache module http_port: command_line => '$USER1$/check_http -p $ARG1$ -H $HOSTADDRESS$ -I $HOSTADDRESS$'; + + check_http_port_url_content: + command_line => '$USER1$/check_http -H $ARG1$ -p $ARG2$ -u $ARG3$ -s $ARG4$'; + check_https_port_url_content: + command_line => '$USER1$/check_http --ssl -H $ARG1$ -p $ARG2$ -u $ARG3$ -s $ARG4$'; + check_http_url_content: + command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$ -s $ARG3$'; + check_https_url_content: + command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$ -s $ARG3$'; + # from bind module check_dig2: command_line => '$USER1$/check_dig -H $HOSTADDRESS$ -l $ARG1$ --record_type=$ARG2$'; + + # from mysql module + check_mysql_health: + command_line => '$USER1$/check_mysql_health --hostname $ARG1$ --port $ARG2$ --username $ARG3$ --password $ARG4$ --mode $ARG5$ --database $ARG6$'; + + # better check_dns + check_dns2: + command_line => '$USER1$/check_dns2 -c $ARG1$ A $ARG2$'; } # notification commands + $mail_cmd_location = $operatingsystem ? { + centos => '/bin/mail', + default => '/usr/bin/mail' + } + nagios_command { 'notify-host-by-email': - command_line => '/usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$'; + command_line => "/usr/bin/printf \"%b\" \"***** Nagios *****\\n\\nNotification Type: \$NOTIFICATIONTYPE\$\\nHost: \\$HOSTNAME\\$\\nState: \$HOSTSTATE\$\\nAddress: \$HOSTADDRESS\$\\nInfo: \$HOSTOUTPUT\$\\n\\nDate/Time: \$LONGDATETIME\$\\n\" | ${mail_cmd_location} -s \"** \$NOTIFICATIONTYPE\$ Host Alert: \$HOSTNAME\$ is \$HOSTSTATE\$ **\" \$CONTACTEMAIL\$"; 'notify-service-by-email': - command_line => '/usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\n\nService: $SERVICEDESC$\nHost: $HOSTALIAS$\nAddress: $HOSTADDRESS$\nState: $SERVICESTATE$\n\nDate/Time: $LONGDATETIME$\n\nAdditional Info:\n\n$SERVICEOUTPUT$" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Service Alert: $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$ **" $CONTACTEMAIL$' - + command_line => "/usr/bin/printf \"%b\" \"***** Nagios *****\\n\\nNotification Type: \$NOTIFICATIONTYPE\$\\n\\nService: \$SERVICEDESC\$\\nHost: \$HOSTALIAS\$\\nAddress: \$HOSTADDRESS\$\\nState: \$SERVICESTATE\$\\n\\nDate/Time: \$LONGDATETIME\$\\n\\nAdditional Info:\\n\\n\$SERVICEOUTPUT\$\" | ${mail_cmd_location} -s \"** \$NOTIFICATIONTYPE\$ Service Alert: \$HOSTALIAS\$/\$SERVICEDESC\$ is \$SERVICESTATE\$ **\" \$CONTACTEMAIL\$"; } } diff --git a/manifests/defaults/host_templates.pp b/manifests/defaults/host_templates.pp index 699b965..0f47324 100644 --- a/manifests/defaults/host_templates.pp +++ b/manifests/defaults/host_templates.pp @@ -12,12 +12,12 @@ class nagios::defaults::host_templates { process_perf_data => '1', retain_status_information => '1', retain_nonstatus_information => '1', - check_command => 'check-host-alive', - max_check_attempts => '10', - notification_interval => '0', - notification_period => '24x7', - notification_options => 'd,u,r', - contact_groups => 'admins', + check_command => 'check-host-alive', + max_check_attempts => '10', + notification_interval => '0', + notification_period => '24x7', + notification_options => 'd,u,r', + contact_groups => 'admins', register => '0', } diff --git a/manifests/defaults/hostgroups.pp b/manifests/defaults/hostgroups.pp index 3a4f4d1..8715ade 100644 --- a/manifests/defaults/hostgroups.pp +++ b/manifests/defaults/hostgroups.pp @@ -1,13 +1,11 @@ class nagios::defaults::hostgroups { - - nagios_hostgroup { - 'all': - alias => 'All Servers', - members => '*'; - 'debian-servers': - alias => 'Debian GNU/Linux Servers'; - 'centos-servers': - alias => 'CentOS GNU/Linux Servers'; - } - + nagios_hostgroup { + 'all': + alias => 'All Servers', + members => '*'; + 'debian-servers': + alias => 'Debian GNU/Linux Servers'; + 'centos-servers': + alias => 'CentOS GNU/Linux Servers'; + } } diff --git a/manifests/defaults/plugins.pp b/manifests/defaults/plugins.pp new file mode 100644 index 0000000..842f9ce --- /dev/null +++ b/manifests/defaults/plugins.pp @@ -0,0 +1,10 @@ +class nagios::defaults::plugins { + + nagios::plugin { + 'check_mysql_health': + source => 'nagios/plugins/check_mysql_health'; + 'check_dns2': + source => 'nagios/plugins/check_dns2'; + } + +} diff --git a/manifests/defaults/service_templates.pp b/manifests/defaults/service_templates.pp index 326ce05..84740af 100644 --- a/manifests/defaults/service_templates.pp +++ b/manifests/defaults/service_templates.pp @@ -17,15 +17,15 @@ class nagios::defaults::service_templates { process_perf_data => '1', retain_status_information => '1', retain_nonstatus_information => '1', - notification_interval => '0', - is_volatile => '0', - check_period => '24x7', - normal_check_interval => '5', - retry_check_interval => '1', - max_check_attempts => '4', - notification_period => '24x7', - notification_options => 'w,u,c,r', - contact_groups => 'admins', + notification_interval => '0', + is_volatile => '0', + check_period => '24x7', + normal_check_interval => '5', + retry_check_interval => '1', + max_check_attempts => '4', + notification_period => '24x7', + notification_options => 'w,u,c,r', + contact_groups => 'admins', register => '0', } diff --git a/manifests/defaults/templates.pp b/manifests/defaults/templates.pp index 5a3d23d..c97eee4 100644 --- a/manifests/defaults/templates.pp +++ b/manifests/defaults/templates.pp @@ -1,12 +1,13 @@ class nagios::defaults::templates { + include nagios::defaults::vars file { 'nagios_templates': - path => "${nagios::nagios_cfgdir}/conf.d/nagios_templates.cfg", - source => [ "puppet://$server/modules/site-nagios/configs/${fqdn}/nagios_templates.cfg", - "puppet://$server/modules/site-nagios/configs/${operatingsystem}/nagios_templates.cfg", - "puppet://$server/modules/site-nagios/configs/nagios_templates.cfg", - "puppet://$server/modules/nagios/configs/${operatingsystem}/nagios_templates.cfg", - "puppet://$server/modules/nagios/configs/nagios_templates.cfg" ], + path => "${nagios::defaults::vars::int_nagios_cfgdir}/conf.d/nagios_templates.cfg", + source => [ "puppet:///modules/site-nagios/configs/${fqdn}/nagios_templates.cfg", + "puppet:///modules/site-nagios/configs/${operatingsystem}/nagios_templates.cfg", + "puppet:///modules/site-nagios/configs/nagios_templates.cfg", + "puppet:///modules/nagios/configs/${operatingsystem}/nagios_templates.cfg", + "puppet:///modules/nagios/configs/nagios_templates.cfg" ], notify => Service['nagios'], mode => 0644, owner => root, group => root; } diff --git a/manifests/defaults/vars.pp b/manifests/defaults/vars.pp new file mode 100644 index 0000000..6302f13 --- /dev/null +++ b/manifests/defaults/vars.pp @@ -0,0 +1,10 @@ +class nagios::defaults::vars { + case $nagios_cfgdir { + '': { $int_nagios_cfgdir = $operatingsystem ? { + centos => '/etc/nagios/', + default => '/etc/nagios3' + } + } + default: { $int_nagios_cfgdir = $nagios_cfgdir } + } +} diff --git a/manifests/irc_bot.pp b/manifests/irc_bot.pp new file mode 100644 index 0000000..961755a --- /dev/null +++ b/manifests/irc_bot.pp @@ -0,0 +1,93 @@ +class nagios::irc_bot { + if ( ! ($nagios_nsa_server and $nagios_nsa_nickname and $nagios_nsa_channel) ) { + fail("Please provide values at least for \$nagios_nsa_server, \$nagios_nsa_nickname and \$nagios_nsa_channel") + } + + $nagios_nsa_socket = $nagios_nsa_socket ? { + '' => $operatingsystem ? { + centos => '/var/run/nagios-nsa/nsa.socket', + default => '/var/run/nagios3/nsa.socket' + }, + default => $nagios_nsa_socket, + } + $nagios_nsa_pidfile = $nagios_nsa_pidfile ? { + '' => $operatingsystem ? { + centos => '/var/run/nagios-nsa/nsa.pid', + default => '/var/run/nagios3/nsa.pid' + }, + default => $nagios_nsa_pidfile, + } + $nagios_nsa_port = $nagios_nsa_port ? { + '' => '6667', + default => $nagios_nsa_port, + } + $nagios_nsa_realname = $nagios_nsa_realname ? { + '' => 'Nagios', + default => $nagios_nsa_realname, + } + + if (! $nagios_nsa_password) { + $nagios_nsa_password = '' + } + + file { "/usr/local/bin/riseup-nagios-client.pl": + owner => root, group => root, mode => 0755, + source => "puppet:///modules/nagios/irc_bot/riseup-nagios-client.pl", + } + file { "/usr/local/bin/riseup-nagios-server.pl": + owner => root, group => root, mode => 0755, + source => "puppet:///modules/nagios/irc_bot/riseup-nagios-server.pl", + } + file { "/etc/init.d/nagios-nsa": + owner => root, group => root, mode => 0755, + content => template("nagios/irc_bot/${operatingsystem}/nagios-nsa.sh.erb"), + require => File["/usr/local/bin/riseup-nagios-server.pl"], + } + file { "/etc/nagios_nsa.cfg": + ensure => present, + owner => nagios, group => root, mode => 0400, + content => template('nagios/irc_bot/nsa.cfg.erb'), + } + + package { "libnet-irc-perl": + ensure => present, + } + + service { "nagios-nsa": + ensure => "running", + hasstatus => true, + require => [File["/etc/nagios_nsa.cfg"], + Package["libnet-irc-perl"], + Service['nagios'] ], + } + + case $operatingsystem { + centos: { + Package['libnet-irc-perl']{ + name => 'perl-Net-IRC', + } + Service['nagios-nsa']{ + enable => true, + } + } + debian,ubuntu: { + exec { "nagios_nsa_init_script": + command => "/usr/sbin/update-rc.d nagios-nsa defaults", + unless => "/bin/ls /etc/rc3.d/ | /bin/grep nagios-nsa", + require => File["/etc/init.d/nagios-nsa"], + before => Service['nagios-nsa'], + } + } + } + + nagios_command { + "notify-by-irc": + command_line => '/usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ n.$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$"'; + "host-notify-by-irc": + command_line => '/usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($HOSTALIAS$) $NOTIFICATIONTYPE$ n.$HOSTATTEMPT$ $HOSTSTATETYPE$ took $HOSTEXECUTIONTIME$s $HOSTOUTPUT$ $HOSTPERFDATA$ $HOSTLATENCY$s"'; + } + + if $use_shorewall { + include shorewall::rules::out::irc + } +} diff --git a/manifests/nsca/client.pp b/manifests/nsca/client.pp index b4ca878..6e2c472 100644 --- a/manifests/nsca/client.pp +++ b/manifests/nsca/client.pp @@ -3,9 +3,9 @@ class nagios::nsca::client { package { 'nsca': ensure => installed } file { '/etc/send_nsca.cfg': - source => [ "puppet://${server}/site-nagios/nsca/{$fqdn}/send_nsca.cfg", - "puppet://${server}/site-nagios/nsca/send_nsca.cfg", - "puppet://${server}/nagios/nsca/send_nsca.cfg" ], + source => [ "puppet:///modules/site-nagios/nsca/{$fqdn}/send_nsca.cfg", + "puppet:///modules/site-nagios/nsca/send_nsca.cfg", + "puppet:///modules/nagios/nsca/send_nsca.cfg" ], owner => 'nagios', group => 'nogroup', mode => '400', diff --git a/manifests/nsca/server.pp b/manifests/nsca/server.pp index 8cdfe75..9239f21 100644 --- a/manifests/nsca/server.pp +++ b/manifests/nsca/server.pp @@ -9,9 +9,9 @@ class nagios::nsca::server { } file { '/etc/nsca.cfg': - source => [ "puppet://${server}/site-nagios/nsca/{$fqdn}/nsca.cfg", - "puppet://${server}/site-nagios/nsca/nsca.cfg", - "puppet://${server}/modules/nagios/nsca/nsca.cfg" ], + source => [ "puppet:///modules/site-nagios/nsca/{$fqdn}/nsca.cfg", + "puppet:///modules/site-nagios/nsca/nsca.cfg", + "puppet:///modules/nagios/nsca/nsca.cfg" ], owner => 'nagios', group => 'nogroup', mode => '400', diff --git a/manifests/plugin.pp b/manifests/plugin.pp index 4789345..abaa6e5 100644 --- a/manifests/plugin.pp +++ b/manifests/plugin.pp @@ -1,13 +1,18 @@ define nagios::plugin( + $source = 'absent', $ensure = present ){ - file{$name: + @@file{$name: path => $hardwaremodel ? { 'x86_64' => "/usr/lib64/nagios/plugins/$name", default => "/usr/lib/nagios/plugins/$name", }, ensure => $ensure, - source => "puppet://$server/modules/nagios/plugins/$name", + source => $source ? { + 'absent' => "puppet:///modules/nagios/plugins/$name", + default => "puppet:///modules/$source" + }, + tag => 'nagios_plugin', require => Package['nagios-plugins'], owner => root, group => 0, mode => 0755; } diff --git a/manifests/plugin/deploy.pp b/manifests/plugin/deploy.pp new file mode 100644 index 0000000..75f87d7 --- /dev/null +++ b/manifests/plugin/deploy.pp @@ -0,0 +1,30 @@ +define nagios::plugin::deploy($source = '', $ensure = 'present', $config = '', $require_package = 'nagios-plugins') { + $plugin_src = $ensure ? { + 'present' => $name, + 'absent' => $name, + default => $ensure + } + $real_source = $source ? { + '' => "nagios/plugins/$plugin_src", + default => $source + } + + if !defined(Package[$require_package]) { + @@package { $require_package: + ensure => installed, + tag => "nagios::plugin::deploy::package"; + } + } + + include nagios::plugin::scriptpaths + @@file { "nagios_plugin_${name}": + path => "$nagios::plugin::scriptpaths::script_path/${name}", + source => "puppet://$server/modules/$real_source", + mode => 0755, owner => root, group => 0, + require => Package[$require_package], + tag => "nagios::plugin::deploy::file"; + } + + # register the plugin + @@nagios::plugin{$name: ensure => $ensure, require => Package['nagios-plugins'] } +} diff --git a/manifests/plugin/scriptpaths.pp b/manifests/plugin/scriptpaths.pp new file mode 100644 index 0000000..6065709 --- /dev/null +++ b/manifests/plugin/scriptpaths.pp @@ -0,0 +1,6 @@ +class nagios::plugin::scriptpaths { + case $hardwaremodel { + x86_64: { $script_path = "/usr/lib64/nagios/plugins/" } + default: { $script_path = "/usr/lib/nagios/plugins" } + } +} diff --git a/manifests/service.pp b/manifests/service.pp index 3e732b7..afc8650 100644 --- a/manifests/service.pp +++ b/manifests/service.pp @@ -10,7 +10,7 @@ define nagios::service ( $notification_period = '', $notification_options = '', $contact_groups = '', - $use = 'absent', + $use = 'generic-service', $service_description = 'absent' ) { @@ -22,7 +22,12 @@ define nagios::service ( ensure => $ensure, check_command => $check_command, host_name => $host_name, + use => $use, notify => Service[nagios], + service_description => $service_description ?{ + 'absent' => $name, + default => $service_description + } } if ($check_period != '') { @@ -53,17 +58,8 @@ define nagios::service ( Nagios_service["${real_name}"] { notification_options => $notification_options } } - if ($use == 'absent') { - Nagios_service["${real_name}"] { use => 'generic-service' } - } else { - Nagios_service["${real_name}"] { use => $use } + if ($contact_groups != '') { + Nagios_service["${real_name}"] { contact_groups => $contact_groups } } - - if ($service_description == 'absent') { - Nagios_service["${real_name}"] { service_description => $name } - } else { - Nagios_service["${real_name}"] { service_description => $service_description } - } - } diff --git a/manifests/service/http.pp b/manifests/service/http.pp index 5fd9890..8fd5059 100644 --- a/manifests/service/http.pp +++ b/manifests/service/http.pp @@ -8,6 +8,7 @@ define nagios::service::http( $check_domain = 'absent', $check_url = '/', $check_code = 'OK', + $use = 'generic-service', $ssl_mode = false ){ $real_check_domain = $check_domain ? { @@ -18,12 +19,14 @@ define nagios::service::http( 'force',true,'only': { nagios::service{"https_${name}_${check_code}": ensure => $ensure, + use => $use, check_command => "check_https_url_regex!${real_check_domain}!${check_url}!'${check_code}'", } case $ssl_mode { 'force': { nagios::service{"httprd_${name}": ensure => $ensure, + use => $use, check_command => "check_http_url_regex!${real_check_domain}!${check_url}!'301'", } } @@ -34,6 +37,7 @@ define nagios::service::http( false,true: { nagios::service{"http_${name}_${check_code}": ensure => $ensure, + use => $use, check_command => "check_http_url_regex!${real_check_domain}!${check_url}!'${check_code}'", } } diff --git a/manifests/service/mysql.pp b/manifests/service/mysql.pp index ca2ae15..d501f06 100644 --- a/manifests/service/mysql.pp +++ b/manifests/service/mysql.pp @@ -1,48 +1,47 @@ -# Checks a mysql instance via tcp or socket +# Checks a mysql instance via tcp or socket define nagios::service::mysql( - $ensure = present, - $check_hostname = 'absent', - $check_port = '3306', - $check_username = 'nagios', - $check_password = '', - $check_database = 'absent', - $check_mode = 'tcp' -){ - if ($check_hostname == 'absent') { - fail("Please specify a hostname, ip address or socket to check a mysql instance.") - } - - case $check_mode { - 'tcp': { - if ($check_hostname == 'localhost') { - $real_check_hostname = '127.0.0.1' - } - else { - $real_check_hostname = $check_hostname - } - } - default: { - if ($check_hostname == '127.0.0.1') { - $real_check_hostname = 'localhost' - } - else { - $real_check_hostname = $check_hostname - } - } - } + $ensure = present, + $check_host = 'absent', + $check_port = '3306', + $check_username = 'nagios', + $check_password = $nagios_mysql_password, + $check_database = 'information_schema', + $check_warning = undef, + $check_critical = undef, + $check_health_mode = $name, + $check_name = undef, + $check_name2 = undef, + $check_regexp = undef, + $check_units = undef, + $check_mode = 'tcp' ) +{ - if ($check_database == 'absent') { - nagios::service { 'mysql': - ensure => $ensure, - check_command => "check_mysql!${real_check_hostname}!${check_port}!${check_username}!${check_password}", - } + if ($check_host == 'absent') { + fail("Please specify a hostname, ip address or socket to check a mysql instance.") + } + + case $check_mode { + 'tcp': { + if ($check_host == 'localhost') { + $real_check_host = '127.0.0.1' + } + else { + $real_check_host = $check_host + } } - else { - nagios::service { "mysql_${check_database}": - ensure => $ensure, - check_command => "check_mysql_db!${real_check_hostname}!${check_port}!${check_username}!${check_password}!${check_database}", - } + default: { + if ($check_host == '127.0.0.1') { + $real_check_host = 'localhost' + } + else { + $real_check_host = $check_host + } } - + } + + nagios::service { "mysql_health_${name}": + ensure => $ensure, + check_command => "check_mysql_health!${check_host}!${check_port}!${check_username}!${check_password}!${name}!${check_database}", + } } diff --git a/manifests/service/smtp.pp b/manifests/service/smtp.pp new file mode 100644 index 0000000..14237a9 --- /dev/null +++ b/manifests/service/smtp.pp @@ -0,0 +1,50 @@ +# true: +# - true : check tls and plain connect *defualt* +# - false : check plain connection only +# cert_days: +# If tls is used add an additionl check +# to check for validity for cert. +# - 'absent' : do not execute that check +# - INTEGER : Minimum number of days a certificate +# has to be valid. Default: 10 +define nagios::service::smtp( + $ensure = 'present', + $host = 'absent', + $port = '25', + $tls = true, + $cert_days = 10 +){ + $real_host = $host ? { + 'absent' => $name, + default => $host + } + + nagios::service{ + "smtp_${name}_${port}": + ensure => $ensure; + "smtp_tls_${name}_${port}": + ensure => $tls ? { + true => $ensure, + default => 'absent' + }; + "smtp_tls_cert_${name}_${port}": + ensure => $cert_days ? { + 'absent' => 'absent', + default => $ensure + }; + } + + if $ensure != 'absent' { + Nagios::Service["smtp_${name}_${port}"]{ + check_command => "check_smtp!${real_host}!${port}", + } + Nagios::Service["smtp_tls_${name}_${port}"]{ + check_command => "check_smtp_tls!${real_host}!${port}", + } + if $cert_days != 'absent' { + Nagios::Service["smtp_tls_cert_${name}_${port}"]{ + check_command => "check_smtp_cert!${real_host}!${port}!${cert_days}", + } + } + } +} diff --git a/manifests/service/ssmtp.pp b/manifests/service/ssmtp.pp new file mode 100644 index 0000000..b05678a --- /dev/null +++ b/manifests/service/ssmtp.pp @@ -0,0 +1,32 @@ +define nagios::service::ssmtp( + $ensure = 'present', + $host = 'absent', + $port = '465', + $cert_days = 10 +){ + $real_host = $host ? { + 'absent' => $name, + default => $host + } + + nagios::service{ + "ssmtp_${name}_${port}": + ensure => $ensure; + "ssmtp_cert_${name}_${port}": + ensure => $cert_days ? { + 'absent' => 'absent', + default => $ensure + }; + } + + if $ensure != 'absent' { + Nagios::Service["ssmtp_${name}_${port}"]{ + check_command => "check_ssmtp!${real_host}!${port}", + } + if $cert_days != 'absent' { + Nagios::Service["ssmtp_cert_${name}_${port}"]{ + check_command => "check_ssmtp_cert!${real_host}!${port}!${cert_days}", + } + } + } +} diff --git a/manifests/target/nat.pp b/manifests/target/fqdn.pp index abb0a1e..acac5d6 100644 --- a/manifests/target/nat.pp +++ b/manifests/target/fqdn.pp @@ -1,4 +1,4 @@ -class nagios::target::nat inherits nagios::target { +class nagios::target::fqdn inherits nagios::target { Nagios_host["${fqdn}"] { address => "${fqdn}" } diff --git a/templates/irc_bot/CentOS/nagios-nsa.sh.erb b/templates/irc_bot/CentOS/nagios-nsa.sh.erb new file mode 100644 index 0000000..1561afd --- /dev/null +++ b/templates/irc_bot/CentOS/nagios-nsa.sh.erb @@ -0,0 +1,104 @@ +#!/bin/sh +# +# nagios-nsa - manage nagios irc bot +# +# chkconfig: - 99 01 +# description: Nagios Simple IRC Agent + +### BEGIN INIT INFO +# Provides: nagios-nsa +# Required-Start: $nagios +# Required-Stop: $nagios +# Default-Start: 2 3 4 5 +# Default-Stop: 1 6 0 +# Short-Description: Nagios Simple IRC Agent +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +exec="/usr/local/bin/riseup-nagios-server.pl" +prog="nsa" +PIDFILE=<%= nagios_nsa_pidfile %> +SOCKFILE=<%= nagios_nsa_socket %> + +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog + +lockfile=/var/lock/subsys/$prog +mkdir -p /var/run/nagios-nsa 2>/dev/null +chown nagios /var/run/nagios-nsa + +start() { + [ -x $exec ] || exit 5 + [ -f $config ] || exit 6 + echo -n $"Starting $prog: " + daemon --pidfile $PIDFILE --user nagios /usr/local/bin/riseup-nagios-server.pl + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval +} + +stop() { + echo -n $"Stopping $prog: " + killproc -p $PIDFILE $prog + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +restart() { + stop + start +} + +reload() { + restart +} + +force_reload() { + restart +} + +rh_status() { + # run checks to determine if the service is running or use generic status + status -p $PIDFILE $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? diff --git a/templates/irc_bot/Debian/nagios-nsa.sh.erb b/templates/irc_bot/Debian/nagios-nsa.sh.erb new file mode 100644 index 0000000..6e608db --- /dev/null +++ b/templates/irc_bot/Debian/nagios-nsa.sh.erb @@ -0,0 +1,71 @@ +#! /bin/sh + +### BEGIN INIT INFO +# Provides: nagios-nsa +# Required-Start: $remote_fs $syslog $nagios +# Required-Stop: $remote_fs $syslog $nagios +# Default-Start: 2 3 4 5 +# Default-Stop: 1 6 0 +# Short-Description: Nagios Simple IRC Agent +### END INIT INFO + +PIDFILE=<%= nagios_nsa_pidfile %> +SOCKFILE=<%= nagios_nsa_socket %> + +. /lib/lsb/init-functions + +start() { + log_daemon_msg "Starting nagios IRC bot" "nagios-nsa" + if start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --user nagios --chuid nagios --exec /usr/local/bin/riseup-nagios-server.pl; then + log_end_msg 0 + else + log_end_msg 1 + fi +} + +stop () { + log_daemon_msg "Stopping nagios IRC bot" "nagios-nsa" + if start-stop-daemon --stop --quiet --pidfile $PIDFILE; then + log_end_msg 0 + else + log_end_msg 1 + fi +} + +remove_socket() { + [ -e $SOCKFILE ] && rm $SOCKFILE +} + +cleanup() { + if [ -r $PIDFILE ]; then + ps -p `cat $PIDFILE` | grep -v 'PID' || { + echo "not running" + remove_socket + } + else + echo "no pid file" + remove_socket + fi +} + +case $1 +in + start) + start + ;; + stop) + stop + ;; + restart) + stop + cleanup + start + ;; + status) + status_of_proc -p $PIDFILE /usr/local/bin/riseup-nagios-server.pl && exit 0 || exit $? + ;; + *) + log_action_msg "Usage: /etc/init.d/nagios-nsa {start|stop|restart|status}" + exit 1 +esac + diff --git a/templates/irc_bot/nsa.cfg.erb b/templates/irc_bot/nsa.cfg.erb new file mode 100644 index 0000000..7cba62c --- /dev/null +++ b/templates/irc_bot/nsa.cfg.erb @@ -0,0 +1,13 @@ +%Nsa = ( + 'socket' => '<%= nagios_nsa_socket %>', + 'server' => '<%= nagios_nsa_server %>', + 'port' => '<%= nagios_nsa_port %>', + 'nickname' => '<%= nagios_nsa_nickname %>', + 'password' => '<%= nagios_nsa_password %>', + # this needs libio-socket-ssl-perl + # doesn't actually works because Net::IRC is braindead and tries to use IO::Socket::SSL->read/write instead of the builtin print, see http://search.cpan.org/dist/IO-Socket-SSL/SSL.pm + #'SSL' => 0, + 'channel' => '<%= nagios_nsa_channel %>', + 'pidfile' => '<%= nagios_nsa_pidfile %>', # set to undef to disable + 'realname' => "<%= nagios_nsa_realname %>", +); |