%PDF- %PDF-
Direktori : /usr/share/perl5/vendor_perl/Munin/Master/ |
Current File : //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;