diff --git a/pandora_console/extras/mr/48.sql b/pandora_console/extras/mr/48.sql index 153b9aa98d..a0e1f0756f 100644 --- a/pandora_console/extras/mr/48.sql +++ b/pandora_console/extras/mr/48.sql @@ -1,5 +1,15 @@ START TRANSACTION; +CREATE TABLE IF NOT EXISTS `talert_execution_queue` ( + `id` int(10) unsigned NOT NULL auto_increment, + `id_alert_template_module` int(10) unsigned NOT NULL, + `alert_mode` tinyint(1) NOT NULL, + `data` mediumtext NOT NULL, + `extra_macros` text, + `utimestamp` bigint(20) NOT NULL default '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + UPDATE `tlink` SET `link` = 'https://pandorafms.com/manual/' WHERE `id_link` = 0000000001; UPDATE pandora.tuser_task diff --git a/pandora_console/extras/pandoradb_migrate_6.0_to_7.0.mysql.sql b/pandora_console/extras/pandoradb_migrate_6.0_to_7.0.mysql.sql index 8f888b3a52..59d0909d8a 100644 --- a/pandora_console/extras/pandoradb_migrate_6.0_to_7.0.mysql.sql +++ b/pandora_console/extras/pandoradb_migrate_6.0_to_7.0.mysql.sql @@ -4042,4 +4042,17 @@ DELETE FROM `tconfig` WHERE `token` = 'ipam_recon_script_id'; ALTER TABLE `tperfil` DROP COLUMN `incident_view`; ALTER TABLE `tperfil` DROP COLUMN `incident_edit`; -ALTER TABLE `tperfil` DROP COLUMN `incident_management`; \ No newline at end of file +ALTER TABLE `tperfil` DROP COLUMN `incident_management`; + +-- ----------------------------------------------------- +-- Table `talert_execution_queue` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `talert_execution_queue` ( + `id` int(10) unsigned NOT NULL auto_increment, + `id_alert_template_module` int(10) unsigned NOT NULL, + `alert_mode` tinyint(1) NOT NULL, + `data` mediumtext NOT NULL, + `extra_macros` text, + `utimestamp` bigint(20) NOT NULL default '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/pandora_console/include/constants.php b/pandora_console/include/constants.php index c6c4a190e4..cc06711ac9 100644 --- a/pandora_console/include/constants.php +++ b/pandora_console/include/constants.php @@ -422,6 +422,7 @@ define('SERVER_TYPE_WUX', 17); define('SERVER_TYPE_SYSLOG', 18); define('SERVER_TYPE_AUTOPROVISION', 19); define('SERVER_TYPE_MIGRATION', 20); +define('SERVER_TYPE_ALERT', 21); // REPORTS. define('REPORT_TOP_N_MAX', 1); diff --git a/pandora_console/include/functions_servers.php b/pandora_console/include/functions_servers.php index 69f14e42f1..ecc84f7f04 100644 --- a/pandora_console/include/functions_servers.php +++ b/pandora_console/include/functions_servers.php @@ -826,6 +826,19 @@ function servers_get_info($id_server=-1) $id_modulo = 0; break; + case SERVER_TYPE_ALERT: + $server['img'] = html_print_image( + 'images/alerts_extern.png', + true, + [ + 'title' => __('Alert server'), + 'class' => 'invert_filter', + ] + ); + $server['type'] = 'alert'; + $id_modulo = 0; + break; + default: $server['img'] = ''; $server['type'] = 'unknown'; diff --git a/pandora_console/pandoradb.sql b/pandora_console/pandoradb.sql index ec7d1a2072..8c832f287a 100644 --- a/pandora_console/pandoradb.sql +++ b/pandora_console/pandoradb.sql @@ -582,6 +582,19 @@ CREATE TABLE IF NOT EXISTS `talert_special_days` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- ----------------------------------------------------- +-- Table `talert_execution_queue` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `talert_execution_queue` ( + `id` int(10) unsigned NOT NULL auto_increment, + `id_alert_template_module` int(10) unsigned NOT NULL, + `alert_mode` tinyint(1) NOT NULL, + `data` mediumtext NOT NULL, + `extra_macros` text, + `utimestamp` bigint(20) NOT NULL default '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + -- ----------------------------------------------------- -- Table `tattachment` -- ----------------------------------------------------- diff --git a/pandora_server/bin/pandora_server b/pandora_server/bin/pandora_server index 585e1c105f..6a45ad4ac2 100755 --- a/pandora_server/bin/pandora_server +++ b/pandora_server/bin/pandora_server @@ -31,6 +31,7 @@ use PandoraFMS::DB; use PandoraFMS::Config; use PandoraFMS::Tools; use PandoraFMS::Core; +use PandoraFMS::AlertServer; use PandoraFMS::DataServer; use PandoraFMS::NetworkServer; use PandoraFMS::SNMPServer; @@ -147,6 +148,7 @@ sub pandora_startup () { # Load servers if (!is_metaconsole(\%Config)) { pandora_reset_server (\%Config, $DBH); + push (@Servers, new PandoraFMS::AlertServer (\%Config, $DBH)); push (@Servers, new PandoraFMS::DataServer (\%Config, $DBH)); push (@Servers, new PandoraFMS::NetworkServer (\%Config, $DBH)); push (@Servers, new PandoraFMS::DiscoveryServer (\%Config, $DBH)); diff --git a/pandora_server/conf/pandora_server.conf.new b/pandora_server/conf/pandora_server.conf.new index 0a80181b2f..f1ad0256c7 100644 --- a/pandora_server/conf/pandora_server.conf.new +++ b/pandora_server/conf/pandora_server.conf.new @@ -686,3 +686,12 @@ ha_interval 30 # Pandora FMS Database HA Tool monitoring interval in seconds. Must be a multiple of ha_interval (PANDORA FMS ENTERPRISE ONLY). ha_monitoring_interval 60 +# Enable (1) or disable (0) Pandora FMS Alert Server. +alertserver 0 + +# Pandora FMS Alert Server threads. +alertserver_threads 4 + +# Generate an hourly warning event if alert execution is being delayed more than alertserver_warn seconds. +alertserver_warn 180 + diff --git a/pandora_server/lib/PandoraFMS/AlertServer.pm b/pandora_server/lib/PandoraFMS/AlertServer.pm new file mode 100644 index 0000000000..01cd255c64 --- /dev/null +++ b/pandora_server/lib/PandoraFMS/AlertServer.pm @@ -0,0 +1,201 @@ +package PandoraFMS::AlertServer; +########################################################################## +# Pandora FMS Alert Server. +# Pandora FMS. the Flexible Monitoring System. http://www.pandorafms.org +########################################################################## +# Copyright (c) 2005-2021 Artica Soluciones Tecnologicas S.L +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; version 2 +# 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. +########################################################################## + +use strict; +use warnings; + +use threads; +use threads::shared; +use Thread::Semaphore; + +use JSON; +use POSIX qw(strftime); + +# Default lib dir for RPM and DEB packages +use lib '/usr/lib/perl5'; + +use PandoraFMS::Tools; +use PandoraFMS::DB; +use PandoraFMS::Core; +use PandoraFMS::ProducerConsumerServer; + +# Inherits from PandoraFMS::ProducerConsumerServer +our @ISA = qw(PandoraFMS::ProducerConsumerServer); + +# Global variables +my @TaskQueue :shared; +my %PendingTasks :shared; +my $Sem :shared; +my $TaskSem :shared; +my $AlertSem :shared; +my %Alerts :shared; +my $EventRef :shared = 0; + +######################################################################################## +# Alert Server class constructor. +######################################################################################## +sub new ($$$) { + my ($class, $config, $dbh) = @_; + + return undef unless $config->{'alertserver'} == 1; + + # Initialize semaphores and queues + @TaskQueue = (); + %PendingTasks = (); + $Sem = Thread::Semaphore->new; + $TaskSem = Thread::Semaphore->new (0); + $AlertSem = Thread::Semaphore->new (1); + + # Call the constructor of the parent class + my $self = $class->SUPER::new($config, ALERTSERVER, \&PandoraFMS::AlertServer::data_producer, \&PandoraFMS::AlertServer::data_consumer, $dbh); + + bless $self, $class; + return $self; +} + +############################################################################### +# Run. +############################################################################### +sub run ($) { + my $self = shift; + my $pa_config = $self->getConfig (); + + print_message ($pa_config, " [*] Starting " . $pa_config->{'rb_product_name'} . " Alert Server.", 1); + $self->setNumThreads ($pa_config->{'alertserver_threads'}); + $self->SUPER::run (\@TaskQueue, \%PendingTasks, $Sem, $TaskSem); +} + +############################################################################### +# Data producer. +############################################################################### +sub data_producer ($) { + my $self = shift; + my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ()); + + my @tasks; + my @rows; + + # Make a local copy of locked alerts. + $AlertSem->down (); + my $locked_alerts = {%Alerts}; + $AlertSem->up (); + + # Check the execution queue. + if (pandora_is_master($pa_config) == 1) { + @rows = get_db_rows ($dbh, 'SELECT id, utimestamp FROM talert_execution_queue ORDER BY utimestamp ASC'); + } + + # Queue alerts. + foreach my $row (@rows) { + next if (alert_lock($pa_config, $row->{'id'}, $locked_alerts) == 0); + push (@tasks, $row->{'id'}); + + # Generate an event if execution delay is high (every 1 hour at most). + my $now = time(); + if (($pa_config->{'alertserver_warn'} > 0) && + ($now - $row->{'utimestamp'} > $pa_config->{'alertserver_warn'}) && + ($EventRef + 3600 < $now)) { + $EventRef = $now; + pandora_event ($pa_config, "Alert execution delay has exceeded " . $pa_config->{'alertserver_warn'} . " seconds.", 0, 0, 3, 0, 0, 'system', 0, $dbh); + } + + } + + return @tasks; +} + +############################################################################### +# Data consumer. +############################################################################### +sub data_consumer ($$) { + my ($self, $task_id) = @_; + my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ()); + + eval {{ + # Get the alert from the queue. + my $task = get_db_single_row ($dbh, 'SELECT * FROM talert_execution_queue WHERE id = ?', $task_id); + if (! defined ($task)) { + logger ($pa_config,"[ERROR] Executing invalid alert", 0); + last 0; + } + + # Get the alert data. + my $alert = get_db_single_row ($dbh, 'SELECT talert_template_modules.id as id_template_module, + talert_template_modules.*, talert_templates.* + FROM talert_template_modules, talert_templates + WHERE talert_template_modules.id_alert_template = talert_templates.id + AND talert_template_modules.id = ?', $task->{'id_alert_template_module'}); + if (! defined ($alert)) { + logger($pa_config, "Alert ID " . $task->{'id_alert_template_module'} . " not found.", 10); + last; + } + + # Get the agent and module associated with the alert + my $module = get_db_single_row ($dbh, 'SELECT * FROM tagente_modulo WHERE id_agente_modulo = ?', $alert->{'id_agent_module'}); + if (! defined ($module)) { + logger($pa_config, "Module ID " . $alert->{'id_agent_module'} . " not found for alert ID " . $alert->{'id_template_module'} . ".", 10); + last; + } + my $agent = get_db_single_row ($dbh, 'SELECT * FROM tagente WHERE id_agente = ?', $module->{'id_agente'}); + if (! defined ($agent)) { + logger($pa_config, "Agent ID " . $module->{'id_agente'} . " not found for module ID " . $module->{'id_agente_modulo'} . " alert ID " . $alert->{'id_template_module'} . ".", 10); + last; + } + + # Execute the alert. + pandora_execute_alert ($pa_config, $task->{'data'}, $agent, $module, $alert, $task->{'alert_mode'}, + $dbh, strftime ("%Y-%m-%d %H:%M:%S", localtime()), 0, decode_json($task->{'extra_macros'})); + }}; + + # Remove the alert from the queue and unlock. + db_do($dbh, 'DELETE FROM talert_execution_queue WHERE id=?', $task_id); + alert_unlock($pa_config, $task_id); +} + +########################################################################## +# Get a lock on the given alert. Return 1 on success, 0 otherwise. +########################################################################## +sub alert_lock { + my ($pa_config, $alert, $locked_alerts) = @_; + + if (defined($locked_alerts->{$alert})) { + return 0; + } + + $locked_alerts->{$alert} = 1; + $AlertSem->down (); + $Alerts{$alert} = 1; + $AlertSem->up (); + + return 1; +} + +########################################################################## +# Remove the lock on the given alert. +########################################################################## +sub alert_unlock { + my ($pa_config, $alert) = @_; + + $AlertSem->down (); + delete ($Alerts{$alert}); + $AlertSem->up (); +} + +1; +__END__ diff --git a/pandora_server/lib/PandoraFMS/Config.pm b/pandora_server/lib/PandoraFMS/Config.pm index 7d9a4f89fc..3dcc42f5a6 100644 --- a/pandora_server/lib/PandoraFMS/Config.pm +++ b/pandora_server/lib/PandoraFMS/Config.pm @@ -551,6 +551,10 @@ sub pandora_load_config { $pa_config->{"event_inhibit_alerts"} = 0; # 7.0 737 + $pa_config->{"alertserver"} = 0; # 7.0 756 + $pa_config->{"alertserver_threads"} = 1; # 7.0 756 + $pa_config->{"alertserver_warn"} = 180; # 7.0 756 + # Check for UID0 if ($pa_config->{"quiet"} != 0){ if ($> == 0){ @@ -1252,6 +1256,15 @@ sub pandora_load_config { elsif ($parametro =~ m/^fsnmp\s(.*)/i) { $pa_config->{'fsnmp'}= clean_blank($1); } + elsif ($parametro =~ m/^alertserver\s+([0-9]*)/i){ + $pa_config->{'alertserver'}= clean_blank($1); + } + elsif ($parametro =~ m/^alertserver_threads\s+([0-9]*)/i) { + $pa_config->{'alertserver_threads'}= clean_blank($1); + } + elsif ($parametro =~ m/^alertserver_warn\s+([0-9]*)/i) { + $pa_config->{'alertserver_warn'}= clean_blank($1); + } # Pandora HA extra elsif ($parametro =~ m/^ha_file\s(.*)/i) { diff --git a/pandora_server/lib/PandoraFMS/Core.pm b/pandora_server/lib/PandoraFMS/Core.pm index 42db13c000..b24d6696a1 100644 --- a/pandora_server/lib/PandoraFMS/Core.pm +++ b/pandora_server/lib/PandoraFMS/Core.pm @@ -732,7 +732,11 @@ sub pandora_process_alert ($$$$$$$$;$$) { db_do($dbh, 'UPDATE talert_template_module_actions SET last_execution = 0 WHERE id_alert_template_module = ?', $id); } - pandora_execute_alert ($pa_config, $data, $agent, $module, $alert, 0, $dbh, $timestamp, 0, $extra_macros); + if ($pa_config->{'alertserver'} == 1 && defined ($alert->{'id_template_module'})) { + pandora_queue_alert($pa_config, $dbh, $data, $alert, 0, $extra_macros); + } else { + pandora_execute_alert ($pa_config, $data, $agent, $module, $alert, 0, $dbh, $timestamp, 0, $extra_macros); + } return; } @@ -772,8 +776,12 @@ sub pandora_process_alert ($$$$$$$$;$$) { last_fired = ?, internal_counter = ? ' . $new_interval . ' WHERE id = ?', $alert->{'times_fired'}, $utimestamp, $alert->{'internal_counter'}, $id); - pandora_execute_alert ($pa_config, $data, $agent, $module, $alert, 1, - $dbh, $timestamp, 0, $extra_macros, $is_correlated_alert); + if ($pa_config->{'alertserver'} == 1 && defined ($alert->{'id_template_module'})) { + pandora_queue_alert($pa_config, $dbh, $data, $alert, 1, $extra_macros); + } else { + pandora_execute_alert ($pa_config, $data, $agent, $module, $alert, 1, + $dbh, $timestamp, 0, $extra_macros, $is_correlated_alert); + } return; } } @@ -1005,6 +1013,26 @@ sub pandora_execute_alert ($$$$$$$$$;$$) { } } +########################################################################## +=head2 C<< pandora_queue_alert (I<$pa_config>, I<$dbh>, I<$data>, I<$alert>, I<$extra_macros> >> + +Queue the given alert for execution. + +=cut +########################################################################## +sub pandora_queue_alert ($$$$$;$) { + my ($pa_config, $dbh, $data, $alert, $alert_mode, $extra_macros) = @_; + my $json_macros = '{}'; + + eval { + local $SIG{__DIE__}; + $json_macros = encode_json($extra_macros); + }; + + db_do ($dbh, "INSERT INTO talert_execution_queue (id_alert_template_module, data, alert_mode, extra_macros, utimestamp) + VALUES (?, ?, ?, ?, ?)", $alert->{'id_template_module'}, $data, $alert_mode, $json_macros, time()); +} + ########################################################################## =head2 C<< pandora_execute_action (I<$pa_config>, I<$data>, I<$agent>, I<$alert>, I<$alert_mode>, I<$action>, I<$module>, I<$dbh>, I<$timestamp>) >> diff --git a/pandora_server/lib/PandoraFMS/Tools.pm b/pandora_server/lib/PandoraFMS/Tools.pm index d4109d7d54..f499c39f38 100755 --- a/pandora_server/lib/PandoraFMS/Tools.pm +++ b/pandora_server/lib/PandoraFMS/Tools.pm @@ -55,6 +55,7 @@ our @ISA = ("Exporter"); our %EXPORT_TAGS = ( 'all' => [ qw( ) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw( + ALERTSERVER DATASERVER NETWORKSERVER SNMPCONSOLE @@ -189,6 +190,7 @@ use constant WUXSERVER => 17; use constant SYSLOGSERVER => 18; use constant PROVISIONINGSERVER => 19; use constant MIGRATIONSERVER => 20; +use constant ALERTSERVER => 21; # Module status use constant MODULE_NORMAL => 0; @@ -2519,6 +2521,7 @@ sub get_server_name { return "SYSLOGSERVER" if ($server_type eq SYSLOGSERVER); return "PROVISIONINGSERVER" if ($server_type eq PROVISIONINGSERVER); return "MIGRATIONSERVER" if ($server_type eq MIGRATIONSERVER); + return "ALERTSERVER" if ($server_type eq ALERTSERVER); return "UNKNOWN"; }