From 1b3c9db30d8832d9c893927f11b1047db8b48497 Mon Sep 17 00:00:00 2001 From: fermin831 Date: Tue, 20 Feb 2018 17:45:51 +0100 Subject: [PATCH] [Linux Agent] Added cron functions and remove cron_module_interval --- pandora_agents/unix/pandora_agent | 399 ++++++++++++++++++++++++------ 1 file changed, 325 insertions(+), 74 deletions(-) diff --git a/pandora_agents/unix/pandora_agent b/pandora_agents/unix/pandora_agent index 30e695f542..fe0b9908b6 100755 --- a/pandora_agents/unix/pandora_agent +++ b/pandora_agents/unix/pandora_agent @@ -30,6 +30,7 @@ use File::Basename; use File::Copy; use IO::Socket; use Sys::Syslog; +use Time::Local; # Agent XML data my $Xml; @@ -515,10 +516,11 @@ sub parse_conf_modules($) { } } } elsif ($line =~ /^\s*module_crontab\s+(((\*|(\d+(-\d+){0,1}))\s*){5}).*$/) { - $module->{'cron'} = $1; - chomp ($module->{'cron'}); - } elsif ($line =~ /^\s*module_cron_interval\s+(\d+).*$/) { - $module->{'cron_interval'} = $1; + my $cron_text = $1; + chomp ($cron_text); + if (cron_check_syntax($cron_text)) { + $module->{'cron'} = $cron_text; + } } elsif ($line =~ /^\s*module_end\s*$/) { $module_begin = 0; @@ -1632,8 +1634,8 @@ sub guess_os_version ($) { ################################################################################ # Execute the given module. ################################################################################ -sub exec_module ($) { - my $module = shift; +sub exec_module { + my ($module, $interval) = @_; # Need something to execute if ($module->{'func'} == 0) { @@ -1648,7 +1650,7 @@ sub exec_module ($) { } # Check module cron - if (check_module_cron ($module) != 1) { + if (check_module_cron ($module, $interval) != 1) { $ThreadSem->up () if (defined ($ThreadSem) && $Conf{'agent_threads'} > 1); return; } @@ -2044,85 +2046,335 @@ sub evaluate_module_intensive_conditions ($$) { return 1; } +############################################################################### +# Get the number of seconds left to the next execution of the given cron entry. +############################################################################### +sub cron_next_execution { + my ($cron, $interval) = @_; + + # Check cron conf format + if ($cron !~ /^((\*|(\d+(-\d+){0,1}))\s*){5}$/) { + return $interval; + } + + # Get day of the week and month from cron config + my ($mday, $wday) = (split (/\s/, $cron))[2, 4]; + + # Get current time and day of the week + my $cur_time = time(); + my $cur_wday = (localtime ($cur_time))[6]; + + # Any day of the week + if ($wday eq '*') { + my $nex_time = cron_next_execution_date ($cron, $cur_time, $interval); + return $nex_time - time(); + } + # A range? + else { + $wday = cron_get_closest_in_range ($cur_wday, $wday); + } + + # A specific day of the week + my $count = 0; + my $nex_time = $cur_time; + do { + $nex_time = cron_next_execution_date ($cron, $nex_time, $interval); + my $nex_time_wd = $nex_time; + my ($nex_mon, $nex_wday) = (localtime ($nex_time_wd))[4, 6]; + my $nex_mon_wd; + do { + # Check the day of the week + if ($nex_wday == $wday) { + return $nex_time_wd - time(); + } + + # Move to the next day of the month + $nex_time_wd += 86400; + ($nex_mon_wd, $nex_wday) = (localtime ($nex_time_wd))[4, 6]; + } while ($mday eq '*' && $nex_mon_wd == $nex_mon); + $count++; + } while ($count < 60); + + # Something went wrong, default to 5 minutes + return $interval; +} + +############################################################################### +# Get the number of seconds left to the next execution of the given cron entry. +############################################################################### +sub cron_check_syntax ($) { + my ($cron) = @_; + + return 0 if !defined ($cron); + return ($cron =~ m/^(\d|\*|-)+ (\d|\*|-)+ (\d|\*|-)+ (\d|\*|-)+ (\d|\*|-)+$/); +} + +############################################################################### +# Get the next execution date for the given cron entry in seconds since epoch. +############################################################################### +sub cron_next_execution_date { + my ($cron, $cur_time, $interval) = @_; + + # Get cron configuration + my ($min, $hour, $mday, $mon, $wday) = split (/\s/, $cron); + + # Months start from 0 + if($mon ne '*') { + my ($mon_down, $mon_up) = cron_get_interval ($mon); + if (defined($mon_up)) { + $mon = ($mon_down - 1) . "-" . ($mon_up - 1); + } else { + $mon = $mon_down - 1; + } + } + + # Get current time + if (! defined ($cur_time)) { + $cur_time = time(); + } + # Check if current time + interval is on cron too + my $nex_time = $cur_time + $interval; + my ($cur_min, $cur_hour, $cur_mday, $cur_mon, $cur_year) + = (localtime ($nex_time))[1, 2, 3, 4, 5]; + + my @cron_array = ($min, $hour, $mday, $mon); + my @curr_time_array = ($cur_min, $cur_hour, $cur_mday, $cur_mon); + return ($nex_time) if cron_is_in_cron(\@cron_array, \@curr_time_array) == 1; + + # Get first next date candidate from next cron configuration + # Initialize some vars + my @nex_time_array = @curr_time_array; + + # Update minutes + my ($min_down, undef) = cron_get_interval ($min); + $nex_time_array[0] = ($min_down eq '*') ? 0 : $min_down; + + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time >= $cur_time) { + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + } + + # Check if next hour is in cron + $nex_time_array[1]++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + + if ($nex_time == 0) { + #Update the month day if overflow + $nex_time_array[1] = 0; + $nex_time_array[2]++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time == 0) { + #Update the month if overflow + $nex_time_array[2] = 1; + $nex_time_array[3]++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time == 0) { + #Update the year if overflow + $cur_year++; + $nex_time_array[3] = 0; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + } + } + } + + #Check the hour + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + + #Update the hour if fails + my ($hour_down, undef) = cron_get_interval ($hour); + $nex_time_array[1] = ($hour_down eq '*') ? 0 : $hour_down; + + # When an overflow is passed check the hour update again + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time >= $cur_time) { + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + } + + # Check if next day is in cron + $nex_time_array[2]++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time == 0) { + #Update the month if overflow + $nex_time_array[2] = 1; + $nex_time_array[3]++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time == 0) { + #Update the year if overflow + $nex_time_array[3] = 0; + $cur_year++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + } + } + + #Check the day + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + + #Update the day if fails + my ($mday_down, undef) = cron_get_interval ($mday); + $nex_time_array[2] = ($mday_down eq '*') ? 1 : $mday_down; + + # When an overflow is passed check the hour update in the next execution + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time >= $cur_time) { + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + } + + # Check if next month is in cron + $nex_time_array[3]++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time == 0) { + #Update the year if overflow + $nex_time_array[3] = 0; + $cur_year++; + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + } + + #Check the month + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + + #Update the month if fails + my ($mon_down, undef) = cron_get_interval ($mon); + $nex_time_array[3] = ($mon_down eq '*') ? 0 : $mon_down; + + # When an overflow is passed check the hour update in the next execution + $nex_time = cron_valid_date(@nex_time_array, $cur_year); + if ($nex_time >= $cur_time) { + return $nex_time if cron_is_in_cron(\@cron_array, \@nex_time_array); + } + + $nex_time = cron_valid_date(@nex_time_array, $cur_year + 1); + + return $nex_time; +} + +############################################################################### +# Returns if a date is in a cron. Recursive. +# Needs the cron like an array reference and +# current time in cron format to works properly +############################################################################### +sub cron_is_in_cron { + my ($elems_cron, $elems_curr_time) = @_; + + my @deref_elems_cron = @$elems_cron; + my @deref_elems_curr_time = @$elems_curr_time; + + my $elem_cron = shift(@deref_elems_cron); + my $elem_curr_time = shift (@deref_elems_curr_time); + + #If there is no elements means that is in cron + return 1 unless (defined($elem_cron) || defined($elem_curr_time)); + + # Go to last element if current is a wild card + if ($elem_cron ne '*') { + my ($down, $up) = cron_get_interval($elem_cron); + # Check if there is no a range + return 0 if (!defined($up) && ($down != $elem_curr_time)); + # Check if there is on the range + if (defined($up)) { + if ($down < $up) { + return 0 if ($elem_curr_time < $down || $elem_curr_time > $up); + } else { + return 0 if ($elem_curr_time > $down || $elem_curr_time < $up); + } + } + } + return cron_is_in_cron(\@deref_elems_cron, \@deref_elems_curr_time); +} + +############################################################################### +# Returns the interval of a cron element. If there is not a range, +# returns an array with the first element in the first place of array +# and the second place undefined. +############################################################################### +sub cron_get_interval { + my ($element) = @_; + + # Not a range + if ($element !~ /(\d+)\-(\d+)/) { + return ($element, undef); + } + + return ($1, $2); +} + +############################################################################### +# Returns the closest number to the target inside the given range (including +# the target itself). +############################################################################### +sub cron_get_closest_in_range ($$) { + my ($target, $range) = @_; + + # Not a range + if ($range !~ /(\d+)\-(\d+)/) { + return $range; + } + + # Search the closes number to the target in the given range + my $range_start = $1; + my $range_end = $2; + + # Outside the range + if ($target <= $range_start || $target > $range_end) { + return $range_start; + } + + # Inside the range + return $target; +} + +############################################################################### +# Check if a date is valid to get timelocal +############################################################################### +sub cron_valid_date { + my ($min, $hour, $mday, $month, $year) = @_; + my $utime; + eval { + local $SIG{__DIE__} = sub {}; + $utime = timelocal(0, $min, $hour, $mday, $month, $year); + }; + if ($@) { + return 0; + } + return $utime; +} + ################################################################################ # Checks the module's cron string. Returns 1 if the module should be run, 0 if # not. ################################################################################ -sub check_module_cron ($) { - my $module = shift; +sub check_module_cron { + my ($module, $main_interval) = @_; # No cron string defined return 1 unless ($module->{'cron'} ne ''); + my $now = time(); + # Check if the module was already executed - return 0 unless (time() >= $module->{'cron_utimestamp'}); + return 0 unless ($now >= $module->{'cron_utimestamp'}); - # Get cron configuration - my @cron_params = split (/\s/, $module->{'cron'}); + my $interval = $main_interval * $module->{'interval'}; - # Get current time - my $current_time = time(); - my @time = localtime($current_time); - - # Minutes, hours, day of the month, month and day of the week - my @time_params = @time[1, 2, 3, 4, 6]; - - # Fix month (localtime retuns 0..11 and we need 1..12) - $time_params[3] += 1; + $module->{'cron_utimestamp'} = $now + cron_next_execution( + $module->{'cron'}, + $interval + ); - # Check cron parameters - for (my $i = 0; $i < 5; $i++) { - - # Wildcard - next if ($cron_params[$i] eq '*'); - - # Get interval - my ($bottom, $top) = split (/-/, $cron_params[$i]); - $top = $bottom unless defined ($top); - - # Check if next execution will overflow the cron (only minutes overflow) - # If overflow, execute the module - my $overflow_cron = 0; - if ($i == 0) { - my $start_cron_seconds = $bottom*60; - my $current_exec_seconds = $time_params[$i]*60; - my $next_exec_seconds = $time_params[$i]*60 + $module->{'interval'}*$Conf{'interval'}; - if ($current_exec_seconds > $start_cron_seconds && $current_exec_seconds > $next_exec_seconds) { - $start_cron_seconds += 3600; - } - if (($current_exec_seconds <= $start_cron_seconds) && ($start_cron_seconds <= $next_exec_seconds)) { - $overflow_cron = 1 - } - } - - # Check interval - if ($bottom <= $top) { - return 0 if (($time_params[$i] < $bottom || $time_params[$i] > $top) && !$overflow_cron); - } else { - return 0 if (($time_params[$i] < $bottom && $time_params[$i] > $top) && !$overflow_cron); - } + if ($Conf{'debug'} eq '1') { + log_message ('debug', "Cron for module $module->{'name'} will be executed next time at timestamp: $module->{'cron_utimestamp'}."); } - # Do not check in the next minute, hour, day or month. - my $offset = 0; - if ($module->{'cron_interval'} >= 0) { - $offset = $module->{'cron_interval'}; - } elsif($cron_params[0] ne '*') { - # 1 minute - $offset = 60; - } elsif($cron_params[1] ne '*') { - # 1 hour - $offset = 3600; - } elsif($cron_params[2] ne '*' || $cron_params[4] ne '*') { - # 1 day - $offset = 86400; - } elsif($cron_params[3] ne '*') { - # 31 days - $offset = 2678400; - } + # On first execution checking if cron is valid is required + return 1 unless ($module->{'cron_utimestamp'} == 0); - $module->{'cron_utimestamp'} = $current_time + $offset; - return 1; + # Check if current timestamp is a valid cron date + my $next_execution = cron_next_execution_date( + $module->{'cron'}, + $now - $interval, + $interval + ); + return 1 if ($next_execution == $now); + return 0; } ################################################################################ @@ -2461,7 +2713,6 @@ sub init_module ($) { $module->{'conditions'} = []; $module->{'cron'} = ''; $module->{'cron_utimestamp'} = 0; - $module->{'cron_interval'} = -1; $module->{'precondition'} = []; $module->{'is_intensive'} = 0; $module->{'intensive_conditions'} = []; @@ -2770,7 +3021,7 @@ while (1) { # Execute the module in a separate thread if (defined ($ThreadSem) && $Conf{'agent_threads'} > 1) { $ThreadSem->down (); - my $thr = threads->create (\&exec_module, $module); + my $thr = threads->create (\&exec_module, $module, $Conf{'interval'}); if (! defined ($thr)) { $ThreadSem->up (); } else { @@ -2778,7 +3029,7 @@ while (1) { } # Execute the module } else { - exec_module($module); + exec_module($module, $Conf{'interval'}); } }