Current File : //proc/self/root/proc/self/root/usr/share/perl5/vendor_perl/Munin/Master/LimitsOld.pm
package Munin::Master::LimitsOld;
# -*- perl -*-
=head1 NAME
Munin::Master::LimitsOld - Abstract base class for workers.
=head1 SYNOPSIS
This is Munin::Master::LimitsOld, a minimal package shell to make
munin-limits modular (so it can be loaded persistently in a daemon for
example) without making it object oriented yet. The non-'old' module
will feature proper object orientation like munin-update and will
have to wait until later.
=begin comment
Copyright (C) 2004-2009 Jimmy Olsen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 dated June,
1991.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=end comment
=cut
use warnings;
use strict;
use Exporter;
our (@ISA, @EXPORT);
@ISA = qw ( Exporter );
@EXPORT = qw ( limits_startup limits_main );
use POSIX qw ( strftime );
use Getopt::Long;
use Time::HiRes;
use Text::Balanced qw ( extract_bracketed );
use Log::Log4perl qw ( :easy );
use Scalar::Util qw( looks_like_number );
use Munin::Master::Logger;
use Munin::Master::Utils;
use Munin::Common::Defaults;
my $DEBUG = 0;
my $conffile = "$Munin::Common::Defaults::MUNIN_CONFDIR/munin.conf";
my $do_usage = 0;
my $do_version = 0;
my @limit_hosts = ();
my @limit_services = ();
my @limit_contacts = ();
my @always_send = ();
my $stdout = 0;
my $force = 0;
my $force_run_as_root = 0;
my %notes = ();
my $config;
my $oldnotes;
my $modified = 0;
my %default_text = (
"default" =>
'${var:group} :: ${var:host} :: ${var:graph_title}${if:cfields \n\tCRITICALs:${loop<,>:cfields ${var:label} is ${var:value} (outside range [${var:crange}])${if:extinfo : ${var:extinfo}}}.}${if:wfields \n\tWARNINGs:${loop<,>:wfields ${var:label} is ${var:value} (outside range [${var:wrange}])${if:extinfo : ${var:extinfo}}}.}${if:ufields \n\tUNKNOWNs:${loop<,>:ufields ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}${if:fofields \n\tOKs:${loop<,>:fofields ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}\n',
"nagios" =>
'${var:host}\t${var:graph_title}\t${var:worstid}\t${strtrunc:350 ${if:cfields CRITICALs:${loop<,>:cfields ${var:label} is ${var:value} (outside range [${var:crange}])${if:extinfo : ${var:extinfo}}}.}${if:wfields WARNINGs:${loop<,>:wfields ${var:label} is ${var:value} (outside range [${var:wrange}])${if:extinfo : ${var:extinfo}}}.}${if:ufields UNKNOWNs:${loop<,>:ufields ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}${if:fofields OKs:${loop<,>:fofields ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}}',
"old-nagios" =>
'${var:host}\t${var:plugin}\t${var:worstid}\t${strtrunc:350 ${var:graph_title}:${if:cfields CRITICALs:${loop<,>:cfields ${var:label} is ${var:value} (outside range [${var:crange}])${if:extinfo : ${var:extinfo}}}.}${if:wfields WARNINGs:${loop<,>:wfields ${var:label} is ${var:value} (outside range [${var:wrange}])${if:extinfo : ${var:extinfo}}}.}${if:ufields UNKNOWNs:${loop<,>:ufields ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}${if:fofields OKs:${loop<,>:fofields ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}}'
);
sub limits_startup {
# Get options
my ($args) = @_;
local @ARGV = @{$args};
$do_usage = 1
unless GetOptions(
"host=s" => \@limit_hosts,
"service=s" => \@limit_services,
"contact=s" => \@limit_contacts,
"config=s" => \$conffile,
"debug!" => \$DEBUG,
"stdout!" => \$stdout,
"force!" => \$force,
"always-send=s" => \@always_send,
"force-run-as-root!" => \$force_run_as_root,
"version!" => \$do_version,
"help" => \$do_usage
);
print_usage_and_exit() if $do_usage;
print_version_and_exit() if $do_version;
exit_if_run_by_super_user() unless $force_run_as_root;
@always_send = qw{ok warning critical unknown} if $force;
munin_readconfig_base($conffile);
# XXX: check if it does actually need that part
$config = munin_readconfig_part('datafile', 0);
logger_open($config->{'logdir'});
logger_debug() if $DEBUG;
}
sub limits_main {
# We're liable to receive SIGPIPEs if the given commands don't work
$SIG{PIPE} = 'IGNORE';
my $update_time = Time::HiRes::time;
my $lockfile = "$config->{rundir}/munin-limits.lock";
INFO "[INFO] Starting munin-limits, getting lock $lockfile";
munin_runlock("$config->{rundir}/munin-limits.lock");
$oldnotes = &munin_readconfig_part('limits', 1);
initialize_for_nagios();
initialize_contacts();
process_limits();
close_pipes();
&munin_writeconfig("$config->{dbdir}/limits", \%notes);
&munin_writeconfig_storable("$config->{dbdir}/limits.storable", \%notes);
$update_time = sprintf("%.2f", (Time::HiRes::time - $update_time));
munin_removelock("$config->{rundir}/munin-limits.lock");
INFO "[INFO] munin-limits finished ($update_time sec)";
}
sub close_pipes {
foreach my $cont (@{munin_get_children($config->{"contact"})}) {
if($cont->{pipe}) {
my $c = munin_get_node_name($cont);
DEBUG "[DEBUG] Closing pipe for contact $c";
close $cont->{pipe} or WARN "[WARNING] Failed to close pipe for contact $c: $!";
}
}
}
sub process_limits {
# Make array of what needs to be checked
my %work_hash_tmp;
my $work_array = [];
foreach my $workfield (
@{munin_find_field_for_limits($config, qr/^(critical|warning)/)}) {
my $parent;
if (defined $workfield->{'graph_title'}) {
# Limit is defined on a service and inherits to all fields, queue it
$parent = $workfield;
} else {
# Limit is defined on a field, or a non-existent service
# Assume field, grab parent service, and verify it is valid
$parent = munin_get_parent($workfield);
if (!defined $parent->{'graph_title'}) {
DEBUG "[DEBUG] Ignoring work item for non-existent service: " . munin_get_node_name($parent) if ($DEBUG);
next;
}
}
if (!defined $work_hash_tmp{$parent}) {
$work_hash_tmp{$parent} = 1;
push @$work_array, $parent;
}
}
# Process array containing services we need to check
foreach my $workservice (@$work_array) {
process_service($workservice);
}
}
sub initialize_contacts {
my $defaultcontacts = munin_get($config, "contacts", "");
if (!length $defaultcontacts) {
my @tmpcontacts = ();
foreach my $cont (@{munin_get_children($config->{"contact"})}) {
if (munin_get($cont, "command")) {
push @tmpcontacts, munin_get_node_name($cont);
}
}
$defaultcontacts = join(' ', @tmpcontacts);
}
munin_set_var_loc($config, ["contacts"], $defaultcontacts);
DEBUG "[DEBUG] Set default \"contacts\" to \"$defaultcontacts\"";
}
sub initialize_for_nagios {
if ( !defined $config->{'contact'}->{'nagios'}->{'command'}
and defined $config->{'nsca'}) {
$config->{'contact'}->{'old-nagios'}->{'command'}
= "$config->{nsca} $config->{nsca_server} -c $config->{nsca_config} -to 60";
$config->{'contact'}->{'old-nagios'}->{'always_send'}
= "critical warning";
}
if (!defined $config->{'contact'}->{'nagios'}->{'always_send'}) {
$config->{'contact'}->{'nagios'}->{'always_send'} = "critical warning";
}
}
sub print_usage_and_exit {
print "Usage: $0 [options]
Options:
--help View this message.
--debug View debug messages.
--stdout Log to stdout as well as the log file.
--always-send <severity list>
Send messages to contacts even if state has
not changed since the last run. The list is a
space or comma separated list of severities.
Choose from one or more of \"critical\",
\"warning\", \"unknown\" and \"ok\".
--force Alias for \"--always-send ok,warning,critical,unknown\".
Overrides --always-send command line, as well as the
always_send contact configuration options.
--service <service> Limit notified services to <service>. Multiple
--service options may be supplied.
--host <host> Limit notified hosts to <host>. Multiple --host
options may be supplied.
--contact <contact> Limit notified contacts to <contact>. Multiple
--contact options may be supplied.
--config <file> Use <file> as configuration file.
[/etc/munin/munin.conf]
";
exit 0;
}
# Get the host of the service in question
sub get_host_node {
my $service = shift || return undef;
my $parent = munin_get_parent($service) || return undef;
if (munin_has_subservices($parent)) {
return get_host_node($parent);
} else {
return $parent;
}
}
sub get_notify_name {
my $hash = shift || return;
if (defined $hash->{'notify_alias'}) {
return $hash->{'notify_alias'};
} elsif (defined $hash->{'graph_title'}) {
return $hash->{'graph_title'};
} else {
return munin_get_node_name($hash);
}
}
# Joined "sub-path" under host level
sub get_full_service_name {
my $service = shift || return undef;
my $parent = munin_get_parent($service);
my $name = get_notify_name($service);
if (defined $parent and munin_has_subservices($parent)) {
return (get_full_service_name($parent) . " :: " . $name);
} else {
return $name;
}
}
# Joined group path above host level
sub get_full_group_path {
my $group = shift || return undef;
my $parent = munin_get_parent($group);
my $name = get_notify_name($group);
if (defined $parent and munin_get_node_name($parent) ne "root") {
return (get_full_group_path($parent) . "-" . $name);
} else {
return $name;
}
}
sub process_service {
my $hash = shift || return;
my $hobj = get_host_node($hash);
my $host = munin_get_node_name($hobj);
my $hostalias = get_notify_name($hobj);
my $service = munin_get_node_name($hash);
my $hparentobj = munin_get_parent($hobj);
my $parent = munin_get_node_name($hobj);
my $gparent = munin_get_node_name($hparentobj);
my $field_order = munin_get_field_order($hash);
if (!ref $hash) {
LOGCROAK("I was passed a non-hash!");
}
return if (@limit_hosts and !grep (/^$host$/, @limit_hosts));
return if (@limit_services and !grep (/^$service$/, @limit_services));
DEBUG "[DEBUG] processing service: $service";
# Some fields that are nice to have in the plugin output
$hash->{'fields'} = join(' ', @$field_order);
$hash->{'plugin'} = $service;
$hash->{'graph_title'} = get_full_service_name($hash);
$hash->{'host'} = $hostalias;
$hash->{'group'} = get_full_group_path($hparentobj);
$hash->{'worst'} = "OK";
$hash->{'worstid'} = 0;
$hash->{'recovered'} = {};
my $state_file = sprintf ('%s/state-%s-%s.storable', $config->{dbdir}, $hash->{group}, $host);
DEBUG "[DEBUG] state_file: $state_file";
my $state = munin_read_storable($state_file) || {};
my %seen = ();
foreach my $fname (@$field_order) {
# If field has an alias, strip it away and store it for use in munin_get_rrd_filename
my $path = undef;
$path = $1 if ($fname =~ s/=(.+)//);
# Field order contains duplicates sometimes, skip if already seen
next if (exists($seen{$fname}));
$seen{$fname} = 1;
my $field = munin_get_node($hash, [$fname]);
next if (!defined $field or ref($field) ne "HASH");
my $fpath = munin_get_node_loc($field);
my $onfield = munin_get_node($oldnotes, $fpath);
my $oldstate = 'ok';
my ($warn, $crit, $unknown_limit) = get_limits($field);
# Skip fields without warning/critical definitions
next if (!defined $warn and !defined $crit);
# get the old state if there is one, or leave it empty.
if ( defined($onfield) and
defined($onfield->{"state"}) ) {
$oldstate = $onfield->{"state"};
}
DEBUG "[DEBUG] processing field: " . join('::', @$fpath);
DEBUG "[DEBUG] field: " . munin_dumpconfig_as_str($field);
my $value;
{
my $rrd_filename = munin_get_rrd_filename($field, $path);
my ($current_updated_timestamp, $current_updated_value) = @{ $state->{value}{"$rrd_filename:42"}{current} || [ ] };
my ($previous_updated_timestamp, $previous_updated_value) = @{ $state->{value}{"$rrd_filename:42"}{previous} || [ ] };
my $heartbeat = 600; # XXX - $heartbeat is a fixed 10 min (2 runs of 5 min).
if (! defined $current_updated_value || $current_updated_value eq "U") {
# No value yet. Report unknown.
$value = "U";
} elsif (time > $current_updated_timestamp + $heartbeat) {
# Current value is too old. Report unknown.
$value = "U";
} elsif (! $field->{type} || $field->{type} eq "GAUGE") {
# Non-compute up-to-date value.
$value = $current_updated_value;
} elsif (! defined $previous_updated_value || $previous_updated_value eq "U") {
# No derive computing possible. Report unknown.
$value = "U";
} elsif ($current_updated_timestamp == $previous_updated_timestamp || $current_updated_timestamp > $previous_updated_timestamp + $heartbeat) {
# Old value does not exist or is too old. Report unknown.
$value = "U";
} elsif ($field->{type} eq "ABSOLUTE") {
# The previous value is unimportant, as if ABSOLUTE, the counter is reset every time the value is read
$value = $current_updated_value / ($current_updated_timestamp - $previous_updated_timestamp);
} elsif ($field->{type} eq "COUNTER" && $current_updated_value < $previous_updated_value) {
# COUNTER never decrease. Report unknown.
$value = "U";
} else {
# Everything is ok for DERIVE/COUNTER
# compute the value per timeunit
$value = ($current_updated_value - $previous_updated_value) / ($current_updated_timestamp - $previous_updated_timestamp);
}
}
# De-taint.
if ($value eq "U") {
$value = "unknown";
}
else {
my $formatted_value = sprintf "%.2f", $value;
if (($formatted_value == 0) && !looks_like_number($value)) {
warn "Failed to interpret expected numeric value of field '$fname' (host '$host'): '$value'";
}
$value = $formatted_value;
}
# Some fields that are nice to have in the plugin output
$field->{'value'} = $value;
$field->{'crange'} = (defined $crit->[0] ? $crit->[0] : "") . ":"
. (defined $crit->[1] ? $crit->[1] : "");
$field->{'wrange'} = (defined $warn->[0] ? $warn->[0] : "") . ":"
. (defined $warn->[1] ? $warn->[1] : "");
DEBUG("[DEBUG] value: "
. join('::', @$fpath)
. ": $value (crit: "
. $field->{'crange'}
. ") (warn: "
. $field->{'wrange'}
. ")");
if ($value eq "unknown") {
$crit->[0] ||= "";
$crit->[1] ||= "";
my $newstate = "unknown";
my $extinfo = defined $field->{"extinfo"}
? "unknown: " . $field->{"extinfo"}
: "Value is unknown.";
my $num_unknowns;
# First we'll need to check whether the user wants to ignore
# a few UNKNOWN values before actually changing the state to
# UNKNOWN.
if (($oldstate ne "unknown") and ($unknown_limit > 1)) {
if (!defined($onfield->{"num_unknowns"}) or ($onfield->{"num_unknowns"} < $unknown_limit)) {
$newstate = $oldstate;
$extinfo = $onfield->{$newstate};
if (defined($onfield->{"num_unknowns"})) {
# Increment the number of UNKNOWN values seen.
$num_unknowns = $onfield->{"num_unknowns"} + 1;
} else {
# Start counting the number of consecutive UNKNOWN values seen.
$num_unknowns = 1;
}
}
}
# the state only changes if the above "unknown" counter is not used (i.e. the limit is not reached, yet)
if (($oldstate ne "unknown") and !defined($num_unknowns)) {
$hash->{'state_changed'} = 1;
}
if ($newstate eq "unknown") {
$hash->{'worst'} = "UNKNOWN" if $hash->{"worst"} eq "OK";
$hash->{'worstid'} = 3 if $hash->{"worstid"} == 0;
}
elsif ($newstate eq "critical") {
$hash->{'worst'} = "CRITICAL";
$hash->{'worstid'} = 2;
}
elsif ($newstate eq "warning") {
$hash->{'worst'} = "WARNING" if $hash->{"worst"} ne "CRITICAL";
$hash->{'worstid'} = 1 if $hash->{"worstid"} != 2;
}
munin_set_var_loc(\%notes, [@$fpath, "state"], $newstate);
munin_set_var_loc(\%notes, [@$fpath, $newstate], $extinfo);
if (defined $num_unknowns) {
munin_set_var_loc(\%notes, [@$fpath, "num_unknowns"],
$num_unknowns);
}
}
elsif ((defined($crit->[0]) and $value < $crit->[0])
or (defined($crit->[1]) and $value > $crit->[1])) {
$crit->[0] ||= "";
$crit->[1] ||= "";
$hash->{'worst'} = "CRITICAL";
$hash->{'worstid'} = 2;
munin_set_var_loc(\%notes, [@$fpath, "state"], "critical");
munin_set_var_loc(
\%notes,
[@$fpath, "critical"], (
defined $field->{"extinfo"}
? "$value (not in "
. $field->{'crange'} . "): "
. $field->{"extinfo"}
: "Value is $value. Critical range ("
. $field->{'crange'}
. ") exceeded"
));
if ( $oldstate ne "critical") {
$hash->{'state_changed'} = 1;
}
}
elsif ((defined($warn->[0]) and $value < $warn->[0])
or (defined($warn->[1]) and $value > $warn->[1])) {
$warn->[0] ||= "";
$warn->[1] ||= "";
$hash->{'worst'} = "WARNING" if $hash->{"worst"} ne "CRITICAL";
$hash->{'worstid'} = 1 if $hash->{"worstid"} != 2;
munin_set_var_loc(\%notes, [@$fpath, "state"], "warning");
munin_set_var_loc(
\%notes,
[@$fpath, "warning"], (
defined $field->{"extinfo"}
? "$value (not in "
. $field->{'wrange'} . "): "
. $field->{"extinfo"}
: "Value is $value. Warning range ("
. $field->{'wrange'}
. ") exceeded"
));
if ( $oldstate ne "warning") {
$hash->{'state_changed'} = 1;
}
}
else {
munin_set_var_loc(\%notes, [@$fpath, "state"], "ok");
munin_set_var_loc(\%notes, [@$fpath, "ok"], "OK");
if ($oldstate ne 'ok') {
$hash->{'state_changed'} = 1;
$hash->{'recovered'}{$fname} = 1;
}
}
}
generate_service_message($hash);
}
sub get_limits {
my $hash = shift || return;
# This hash will have values that we can look up such as these:
my $critical = undef;
my $warning = undef;
my $crit = munin_get($hash, "critical", undef);
my $warn = munin_get($hash, "warning", undef);
my $unknown_limit = munin_get($hash, "unknown_limit", 3);
my $name = munin_get_node_name($hash);
if (defined $crit and $crit =~ /^\s*([-+\d.]*):([-+\d.]*)\s*$/) {
$critical = [undef, undef];
${$critical}[0] = $1 if length $1;
${$critical}[1] = $2 if length $2;
}
elsif (defined $crit and $crit =~ /^\s*([-+\d.]+)\s*$/) {
$critical = [undef, $1];
}
elsif (defined $crit) {
$critical = [0, 0];
}
if(defined $crit) {
DEBUG "[DEBUG] processing critical: $name -> "
. (defined ${$critical}[0]? ${$critical}[0] : "")
. " : "
. (defined ${$critical}[1]? ${$critical}[1] : "");
}
if (defined $warn and $warn =~ /^\s*([-+\d.]*):([-+\d.]*)\s*$/) {
${$warning}[0] = $1 if length $1;
${$warning}[1] = $2 if length $2;
}
elsif (defined $warn and $warn =~ /^\s*([-+\d.]+)\s*$/) {
$warning = [undef, $1];
}
elsif (defined $warn) {
$warning = [0, 0];
}
if(defined $warn) {
DEBUG "[DEBUG] processing warning: $name -> "
. (defined ${$warning}[0]? ${$warning}[0] : "")
. " : "
. (defined ${$warning}[1]? ${$warning}[1] : "");
}
if ($unknown_limit =~ /^\s*(\d+)\s*$/) {
$unknown_limit = $1 if defined $1;
if (defined $unknown_limit) {
if ($unknown_limit < 1) {
# Zero and negative numbers are not valid.
$unknown_limit = 1;
}
}
DEBUG "[DEBUG] processing unknown_limit: $name -> $unknown_limit";
}
return ($warning, $critical, $unknown_limit);
}
sub generate_service_message {
my $hash = shift || return;
my $critical = undef;
my $worst = $hash->{"worst"};
my %stats = (
'critical' => [],
'warning' => [],
'unknown' => [],
'foks' => [],
'ok' => []);
my $contacts = munin_get_children(munin_get_node($config, ["contact"]));
DEBUG "[DEBUG] generating service message: "
. join('::', @{munin_get_node_loc($hash)});
my $children =
munin_get_children(
munin_get_node(\%notes,
munin_get_node_loc($hash)));
if ( defined($children) ) {
foreach my $field (@$children) {
if (defined $field->{"state"}) {
my $fname = munin_get_node_name($field);
push @{$stats{$field->{'state'}}}, $fname;
if ($field->{'state'} eq 'ok' and defined $hash->{'recovered'}{$fname}) {
push @{$stats{'foks'}}, $fname;
}
}
}
}
$hash->{'cfields'} = join " ", @{$stats{'critical'}};
$hash->{'wfields'} = join " ", @{$stats{'warning'}};
$hash->{'ufields'} = join " ", @{$stats{'unknown'}};
$hash->{'fofields'} = join " ", @{$stats{'foks'}};
$hash->{'ofields'} = join " ", @{$stats{'ok'}};
# The "fofields" (datasets that changed state from "failed" to "OK") can be empty under certain
# legitimate circumstances (e.g. "munin-limits --force" sends messages also for unchanged "OK"
# states). But we may never allow the fourth output field for NSCA to be empty - otherwise the
# recipient (nagios/icinga) cannot determine, which fields are affected (and thus which test
# succeeded). Thus we need to make sure, that "fofields" is always defined (since our
# self-made trivial template language does not support expressions like "fofields || ofields").
# Here "ofields" is a reasonable fallback value: it contains all datasets with status "OK".
if ($hash->{'fofields'} eq '') {
$hash->{'fofields'} = $hash->{'ofields'};
}
$hash->{'numcfields'} = scalar @{$stats{'critical'}};
$hash->{'numwfields'} = scalar @{$stats{'warning'}};
$hash->{'numufields'} = scalar @{$stats{'unknown'}};
$hash->{'numfofields'} = scalar @{$stats{'foks'}};
$hash->{'numofields'} = scalar @{$stats{'ok'}};
my $contactlist = munin_get($hash, "contacts", "");
DEBUG("[DEBUG] Contact list for "
. join('::', @{munin_get_node_loc($hash)})
. ": $contactlist");
foreach my $c (split(/\s+/, $contactlist)) {
next if $c eq "none";
my $contactobj = munin_get_node($config, ["contact", $c]);
if(!defined $contactobj) {
WARN("[WARNING] Missing configuration options for contact $c; skipping");
next;
}
if (@limit_contacts and !grep (/^$c$/, @limit_contacts)) {
next;
}
my $obsess = 0;
my $always_send;
if (@always_send) {
# List of severities from command line argument
$always_send = \@always_send;
}
else {
# List of severities from contact configuration
my $always_send_config = munin_get( $contactobj, "always_send" );
my @always_send_config = ($always_send_config);
$always_send = \@always_send_config;
}
$always_send = validate_severities($always_send);
foreach my $cas ( @{$always_send} ) {
if(defined($stats{$cas})) {
$obsess += scalar @{$stats{$cas}};
}
}
if (!$hash->{'state_changed'} and !$obsess) {
next; # No need to send notification
}
INFO("[INFO] state of $hash->{'group'}::$hash->{'host'}::$hash->{'plugin'} has changed to $hash->{'worst'}, notifying $c");
my $precmd = munin_get($contactobj, "command", undef);
if(!defined $precmd) {
WARN("[WARNING] Missing command option for contact $c; skipping");
next;
}
my $pretxt = munin_get(
$contactobj,
"text",
munin_get(
munin_get_node($config, ["contact", "default"]),
"text",
$default_text{$c} || $default_text{"default"}));
my $txt = message_expand($hash, $pretxt, "");
my $cmd = message_expand($hash, $precmd, "");
$txt =~ s/\\n/\n/g;
$txt =~ s/\\t/\t/g;
if($cmd =~ /^\s*([|><]+)/) {
WARN "[WARNING] Found \"$1\" at beginning of command. This should no longer be necessary and will be removed from the command before execution";
$cmd =~ s/^\s*[|><]+//;
}
my $maxmess = munin_get($contactobj, "max_messages", 0);
my $curmess = munin_get($contactobj, "num_messages", 0);
my $curcmd = munin_get($contactobj, "pipe_command", undef);
my $pipe = munin_get($contactobj, "pipe", undef);
if ($maxmess and $curmess >= $maxmess) {
DEBUG "[DEBUG] Resetting pipe for $c because max messages was reached";
close($pipe) or WARN "[WARNING] Failed to close pipe for $c: $!";
$pipe = undef;
munin_set($contactobj, "pipe", undef);
}
elsif ($curcmd and $curcmd ne $cmd) {
DEBUG "[DEBUG] Resetting pipe for $c because the command has changed";
close($pipe) or WARN "[WARNING] Failed to close pipe for $c: $!";
$pipe = undef;
munin_set($contactobj, "pipe", undef);
}
if (!defined $pipe) {
DEBUG "[DEBUG] Opening pipe for $c";
pipe(my $r, my $w) or WARN "[WARNING] Failed to open pipe for $c: $!";
my $pid = fork();
defined($pid) or WARN "[WARNING] Failed fork for pipe for $c: $!";
if($pid) { # parent
DEBUG "[DEBUG] Opened pipe for $c as pid $pid";
close $r;
$pipe = $w;
munin_set($contactobj, "pipe_command", $cmd);
munin_set($contactobj, "pipe", $pipe);
munin_set($contactobj, "num_messages", 0);
$curmess = 0;
} else { # child
close $w;
open(STDIN, "<&", $r);
# We want to close stdout before calling the send command. This prevents the
# notification program (e.g. "send_nsca") to write irrelevant status messages
# to stdout and thus trigger notification emails (e.g. via cron).
# See https://github.com/munin-monitoring/munin/issues/382
# and https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=291168.
close(STDOUT);
exec($cmd) or WARN "[WARNING] Failed to exec for contact $c in pid $$";
exit;
}
}
DEBUG "[DEBUG] sending message to $c: \"$txt\"";
if(defined $pipe and !print $pipe $txt, "\n") {
WARN "[WARNING] Writing to pipe for $c failed: $!";
close($pipe) or WARN "[WARNING] Failed to close pipe for $c: $!";
$pipe = undef;
munin_set($contactobj, "pipe", undef);
}
munin_set($contactobj, "num_messages", $curmess + 1);
}
}
sub message_expand {
my $hash = shift;
my $text = shift;
my @res = ();
while (defined($text) && length($text)) {
if ($text =~ /^([^\$]+|)(?:\$(\{.*)|)$/) {
push @res, $1;
$text = $2;
}
my @a = extract_bracketed($text, '{}');
if(!defined $a[0]) {
$text = $a[1];
next;
}
if ($a[0] =~ /^\{var:(\S+)\}$/) {
$a[0] = munin_get($hash, $1, "");
}
elsif ($a[0] =~ /^\{loop<([^>]+)>:\s*(\S+)\s(.+)\}$/) {
my $d = $1;
my $f = $2;
my $t = $3;
my $fields = munin_get($hash, $f, "");
my @res = ();
if ($fields) {
foreach my $sub (split /\s+/, $fields) {
if (defined $hash->{$sub}) {
push @res, message_expand($hash->{$sub}, $t);
}
}
}
$a[0] = join($d, @res);
}
elsif ($a[0] =~ /^\{loop:\s*(\S+)\s(.+)\}$/) {
my $f = $1;
my $t = $2;
my $fields = munin_get($hash, $f, "");
my $res = "";
if ($fields) {
foreach my $sub (split /\s+/, $fields) {
if (defined $hash->{$sub}) {
push @res, message_expand($hash->{$sub}, $t);
}
}
}
$a[0] = $res;
}
elsif ($a[0] =~ /^\{strtrunc:\s*(\S+)\s(.+)\}$/) {
my $f = "%." . $1 . "s";
my $t = $2;
$a[0] = sprintf($f, message_expand($hash, $t));
}
elsif ($a[0] =~ /^\{if:\s*(\!)?(\S+)\s(.+)\}$/) {
my $n = $1;
my $f = $2;
my $t = $3;
my $res = "";
my $field = munin_get($hash, $f, 0);
my $check = (defined $field and $field ne "0" and length($field));
$check = (!$check) if $n;
if ($check) {
$res .= message_expand($hash, $t);
}
$a[0] = $res;
}
push @res, $a[0];
$text = $a[1];
}
return join('', @res);
}
=pod
Get a list of severities, and return a sorted, lower cased, unique and
validated list of severities.
Expects and returns an array reference.
If none of the severities given are on the allowed_severities list, it
will return a reference to an empty array.
=cut
sub validate_severities {
my $severities_ref = shift;
my @severities = grep { defined $_ } @{$severities_ref};
my @allowed_severities = qw{ok warning critical unknown};
# Flatten, split on comma and whitespace, and lowercase
my @expanded_severities =
split( /[[:space:],]+/, lc( join( ',', @severities ) ) );
# Remove duplicates
my %seen;
my @unique_severities = grep { !$seen{$_}++ } @expanded_severities;
# Filter on allowed values
my %count;
my @validated_severities;
foreach my $severity ( @unique_severities, @allowed_severities ) {
$count{$severity}++;
}
foreach my $severity ( keys %count ) {
if ( $count{$severity} == 2 ) {
push @validated_severities, $severity;
}
}
# Sort the final list
my @sorted_severities = sort(@validated_severities);
return \@sorted_severities;
}
1;