423 lines
13 KiB
Perl
Executable File
423 lines
13 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
###############################################################################
|
|
# Pandora FMS Daemon Watchdog
|
|
###############################################################################
|
|
# Copyright (c) 2018-2021 Artica Soluciones Tecnologicas S.L
|
|
###############################################################################
|
|
|
|
use strict;
|
|
use warnings;
|
|
use DBI;
|
|
use Getopt::Std;
|
|
use POSIX qw(setsid strftime :sys_wait_h);
|
|
use threads;
|
|
use threads::shared;
|
|
use File::Path qw(rmtree);
|
|
|
|
# Default lib dir for Pandora FMS RPM and DEB packages.
|
|
use lib '/usr/lib/perl5';
|
|
|
|
use PandoraFMS::Tools;
|
|
use PandoraFMS::DB;
|
|
use PandoraFMS::Core;
|
|
use PandoraFMS::Config;
|
|
|
|
use Data::Dumper;
|
|
$Data::Dumper::Sortkeys = 1;
|
|
|
|
# Pandora server configuration.
|
|
my %Conf;
|
|
|
|
# Command line options.
|
|
my %Opts;
|
|
|
|
# Run as a daemon.
|
|
my $DAEMON = 0;
|
|
|
|
# Avoid retry old processing orders.
|
|
my $First_Cleanup = 1;
|
|
|
|
# PID file.
|
|
my $PID_FILE = '/var/run/pandora_ha.pid';
|
|
|
|
# Server service handler.
|
|
my $Pandora_Service;
|
|
|
|
# Controlled exit
|
|
my $Running = 0;
|
|
|
|
########################################################################
|
|
# Print the given message with a preceding timestamp.
|
|
########################################################################
|
|
sub log_message($$$;$) {
|
|
my ($conf, $source, $message, $verbosity_level) = @_;
|
|
|
|
my $level = $verbosity_level;
|
|
$level = 5 unless defined($level);
|
|
|
|
if (ref($conf) eq "HASH") {
|
|
logger($conf, 'HA (' . $source . ') ' . "$message", $level);
|
|
}
|
|
|
|
if ($source eq '') {
|
|
print $message;
|
|
}
|
|
else {
|
|
print strftime("%H:%M:%S", localtime()) . ' [' . $source . '] ' . "$message\n";
|
|
}
|
|
}
|
|
|
|
########################################################################
|
|
# Run as a daemon in the background.
|
|
########################################################################
|
|
sub ha_daemonize($) {
|
|
my ($pa_config) = @_;
|
|
|
|
$PID_FILE = $pa_config->{'ha_pid_file'} if defined($pa_config->{'ha_pid_file'});
|
|
|
|
open STDIN, "$DEVNULL" or die "Can't read $DEVNULL: $!";
|
|
open STDOUT, ">>$DEVNULL" or die "Can't write to $DEVNULL: $!";
|
|
open STDERR, ">>$DEVNULL" or die "Can't write to $DEVNULL: $!";
|
|
chdir '/tmp' or die "Can't chdir to /tmp: $!";
|
|
|
|
# Fork!
|
|
defined(my $pid = fork) or die "Can't fork: $!";
|
|
if ($pid) {
|
|
# Store PID of this process in file presented by config token
|
|
if ($PID_FILE ne "") {
|
|
if ( -e $PID_FILE && open (FILE, $PID_FILE)) {
|
|
$pid = <FILE> + 0;
|
|
close FILE;
|
|
|
|
# Check if pandora_ha is running.
|
|
die "[ERROR] pandora_ha is already running with pid: $pid." if (kill (0, $pid));
|
|
}
|
|
umask 0022;
|
|
open (FILE, '>', $PID_FILE) or die "[FATAL] $!";
|
|
print FILE $pid;
|
|
close (FILE);
|
|
}
|
|
exit;
|
|
}
|
|
setsid or die "Can't start a new session: $!";
|
|
}
|
|
|
|
########################################################################
|
|
# Check command line parameters.
|
|
########################################################################
|
|
sub ha_init_pandora($) {
|
|
my $conf = shift;
|
|
|
|
log_message($conf, '', "\nPandora FMS Daemon Watchdog " . $PandoraFMS::Tools::VERSION . " Copyright (c) Artica ST\n");
|
|
|
|
getopts('dp:', \%Opts);
|
|
|
|
# Run as a daemon.
|
|
$DAEMON = 1 if (defined($Opts{'d'}));
|
|
|
|
# PID file.
|
|
$PID_FILE = $Opts{'p'} if (defined($Opts{'p'}));
|
|
|
|
# Load config file from command line.
|
|
help_screen () if ($#ARGV != 0);
|
|
|
|
$conf->{'_pandora_path'} = $ARGV[0];
|
|
|
|
}
|
|
|
|
########################################################################
|
|
# Read external configuration file.
|
|
########################################################################
|
|
sub ha_load_pandora_conf($) {
|
|
my $conf = shift;
|
|
|
|
# Set some defaults.
|
|
$conf->{"servername"} = `hostname`;
|
|
chomp($conf->{"servername"});
|
|
$conf->{"ha_file"} = '/etc/pandora/pandora_ha.bin' unless defined $conf->{"ha_file"};
|
|
|
|
pandora_init($conf, 'Pandora HA');
|
|
pandora_load_config ($conf);
|
|
|
|
# Check conf tokens.
|
|
foreach my $param ('dbuser', 'dbpass', 'dbname', 'dbhost', 'log_file') {
|
|
die ("[ERROR] Bad config values. Make sure " . $conf->{'_pandora_path'} . " is a valid config file.\n\n") unless defined ($conf->{$param});
|
|
}
|
|
$conf->{'dbengine'} = 'mysql' unless defined ($conf->{'dbengine'});
|
|
$conf->{'dbport'} = '3306' unless defined ($conf->{'dbport'});
|
|
$conf->{'ha_interval'} = 10 unless defined ($conf->{'ha_interval'});
|
|
$conf->{'ha_monitoring_interval'} = 60 unless defined ($conf->{'ha_monitoring_interval'});
|
|
}
|
|
|
|
##############################################################################
|
|
# Print a help screen and exit.
|
|
##############################################################################
|
|
sub help_screen {
|
|
log_message(undef, '', "Usage: $0 [options] <path to pandora_server.conf>\n\nOptions:\n\t-p <PID file> Write the PID of the process to the specified file.\n\t-d Run in the background.\n\n");
|
|
exit 1;
|
|
}
|
|
|
|
##############################################################################
|
|
# Keep server running
|
|
##############################################################################
|
|
sub ha_keep_pandora_running($$) {
|
|
my ($conf, $dbh) = @_;
|
|
my $OSNAME = $^O;
|
|
my $control_command;
|
|
|
|
$conf->{'pandora_service_cmd'} = 'service pandora_server' unless defined($conf->{'pandora_service_cmd'});
|
|
|
|
# Check if all servers are running
|
|
# Restart if crashed or keep interval is over.
|
|
my $component_last_contact = get_db_value(
|
|
$dbh,
|
|
'SELECT count(*) AS "delayed"
|
|
FROM tserver
|
|
WHERE ((status = -1) OR ( (unix_timestamp() - unix_timestamp(keepalive)) > (server_keepalive+1) AND status != 0 ))
|
|
AND server_type NOT IN (?, ?) AND name = ?',
|
|
PandoraFMS::Tools::SATELLITESERVER,
|
|
PandoraFMS::Tools::MFSERVER,
|
|
$conf->{'servername'}
|
|
);
|
|
|
|
my $nservers = get_db_value ($dbh, 'SELECT count(*) FROM tserver where name = ?', $conf->{'servername'});
|
|
|
|
$Pandora_Service = $conf->{'pandora_service_cmd'};
|
|
|
|
# Check if service is running
|
|
$control_command = "status-server";
|
|
if ($OSNAME eq "freebsd") {
|
|
$control_command = "status_server";
|
|
}
|
|
my $pid = `$Pandora_Service $control_command | awk '{print \$NF*1}' | tr -d '\.'`;
|
|
|
|
if ( ($pid > 0) && ($component_last_contact > 0)) {
|
|
# service running but not all components
|
|
log_message($conf, 'LOG', 'Pandora service running but not all components.');
|
|
print ">> service running but delayed...\n";
|
|
$control_command = "restart-server";
|
|
if ($OSNAME eq "freebsd") {
|
|
$control_command = "restart_server";
|
|
}
|
|
`$Pandora_Service $control_command 2>/dev/null`;
|
|
} elsif ($pid == 0) {
|
|
# service not running
|
|
log_message($conf, 'LOG', 'Pandora service not running.');
|
|
print ">> service not running...\n";
|
|
$control_command = "start-server";
|
|
if ($OSNAME eq "freebsd") {
|
|
$control_command = "start_server";
|
|
}
|
|
`$Pandora_Service $control_command 2>/dev/null`;
|
|
} elsif ($pid > 0
|
|
&& $nservers == 0
|
|
) {
|
|
my @server_list = get_enabled_servers($conf);
|
|
my $nservers = $#server_list;
|
|
# Process running but no servers active, restart.
|
|
# Try to restart pandora_server if no servers are found.
|
|
# Do not restart if is a configuration issue.
|
|
log_message($conf, 'LOG', 'Pandora service running without servers ['.$nservers.'].');
|
|
if ($nservers >= 0) {
|
|
log_message($conf, 'LOG', 'Restarting Pandora service...');
|
|
$control_command = "restart-server";
|
|
if ($OSNAME eq "freebsd") {
|
|
$control_command = "restart_server";
|
|
}
|
|
`$Pandora_Service $control_command 2>/dev/null`;
|
|
}
|
|
}
|
|
}
|
|
|
|
###############################################################################
|
|
# Update pandora services.
|
|
###############################################################################
|
|
sub ha_update_server($$) {
|
|
my ($config, $dbh) = @_;
|
|
my $OSNAME = $^O;
|
|
|
|
my $repoServer = pandora_get_tconfig_token(
|
|
$dbh, 'remote_config', '/var/spool/pandora/data_in'
|
|
);
|
|
$repoServer .= '/updates/server/';
|
|
|
|
my $lockFile = $repoServer.'/'.$config->{'servername'}.'.installed';
|
|
my $workDir = $config->{"temporal"}.'/server_update/';
|
|
my $versionFile = $repoServer.'version.txt';
|
|
return if (-e $lockFile) || (!-e $versionFile);
|
|
|
|
log_message($config, 'LOG', 'Detected server update: '.`cat "$versionFile"`);
|
|
|
|
if(!-e "$workDir" && !mkdir ($workDir)) {
|
|
log_message($config, 'ERROR', 'Server update failed: '.$!);
|
|
return;
|
|
}
|
|
|
|
my $r = `cd "$workDir/" && tar xzf "$repoServer/pandorafms_server.tar.gz" 2>&1`;
|
|
if ($? ne 0) {
|
|
log_message($config, 'ERROR', 'Failed to uncompress file: '.$r);
|
|
return;
|
|
}
|
|
|
|
$r = `cd "$workDir/pandora_server/" && ./pandora_server_installer --install 2>&1 >/dev/null`;
|
|
if ($? ne 0) {
|
|
log_message($config, 'ERROR', 'Failed to install server update: '.$r);
|
|
return;
|
|
} else {
|
|
log_message($config, 'LOG', 'Server update '.`cat "$versionFile"`.' installed');
|
|
}
|
|
|
|
# Cleanup
|
|
rmtree($workDir);
|
|
|
|
# Restart service
|
|
$config->{'pandora_service_cmd'} = 'service pandora_server'
|
|
unless defined($config->{'pandora_service_cmd'});
|
|
my $control_command = "restart-server";
|
|
if ($OSNAME eq "freebsd") {
|
|
$control_command = "restart_server";
|
|
}
|
|
`$config->{'pandora_service_cmd'} $control_command 2>/dev/null`;
|
|
`touch "$lockFile"`;
|
|
|
|
# After apply update, permission over files are changed, allow group to
|
|
# modify/delete files.
|
|
`chmod 770 "$repoServer"`;
|
|
`chmod 770 "$repoServer/../"`;
|
|
`chmod 660 "$repoServer"/*`;
|
|
|
|
}
|
|
|
|
###############################################################################
|
|
# Connect to ha database, falling back to direct connection to db.
|
|
###############################################################################
|
|
sub ha_database_connect($) {
|
|
my $conf = shift;
|
|
|
|
my $dbh = enterprise_hook('ha_connect', [$conf]);
|
|
|
|
if (!defined($dbh)) {
|
|
$dbh = db_connect ('mysql', $conf->{'dbname'}, $conf->{'dbhost'}, $conf->{'dbport'}, $conf->{'dbuser'}, $conf->{'dbpass'});
|
|
}
|
|
|
|
return $dbh;
|
|
}
|
|
|
|
###############################################################################
|
|
# Main
|
|
###############################################################################
|
|
sub ha_main($) {
|
|
my ($conf) = @_;
|
|
|
|
# Set the PID file.
|
|
$conf->{'PID'} = $PID_FILE;
|
|
|
|
# Log to a separate file if needed.
|
|
$conf->{'log_file'} = $conf->{'ha_log_file'} if defined ($conf->{'ha_log_file'});
|
|
|
|
ha_daemonize($conf) if ($DAEMON == 1);
|
|
|
|
$Running = 1;
|
|
|
|
while ($Running) {
|
|
eval {
|
|
eval {
|
|
local $SIG{__DIE__};
|
|
# Load enterprise components.
|
|
enterprise_load($conf, 1);
|
|
|
|
# Register Enterprise logger
|
|
enterprise_hook('pandoraha_logger', [\&log_message]);
|
|
log_message($conf, 'LOG', 'Enterprise capabilities loaded');
|
|
|
|
};
|
|
if ($@) {
|
|
# No enterprise capabilities.
|
|
log_message($conf, 'LOG', 'No enterprise capabilities');
|
|
}
|
|
|
|
# Start the Pandora FMS server if needed.
|
|
log_message($conf, 'LOG', 'Checking the pandora_server service.');
|
|
|
|
# Connect to a DB.
|
|
my $dbh = ha_database_connect($conf);
|
|
|
|
if ($First_Cleanup == 1) {
|
|
log_message($conf, 'LOG', 'Cleaning previous unfinished actions');
|
|
enterprise_hook('pandoraha_cleanup_states', [$conf, $dbh]);
|
|
$First_Cleanup = 0;
|
|
}
|
|
|
|
# Check if there are updates pending.
|
|
ha_update_server($conf, $dbh);
|
|
|
|
# Keep pandora running
|
|
ha_keep_pandora_running($conf, $dbh);
|
|
|
|
# Are we the master?
|
|
pandora_set_master($conf, $dbh);
|
|
if (!pandora_is_master($conf)) {
|
|
log_message($conf, 'LOG', $conf->{'servername'} . ' is not the current master. Skipping DB-HA actions and monitoring.');
|
|
# Exit current eval.
|
|
return;
|
|
}
|
|
|
|
# Synchronize database.
|
|
enterprise_hook('pandoraha_sync_node', [$conf, $dbh]);
|
|
|
|
# Monitoring.
|
|
enterprise_hook('pandoraha_monitoring', [$conf, $dbh]);
|
|
|
|
# Pending actions.
|
|
enterprise_hook('pandoraha_process_queue', [$conf, $dbh, $First_Cleanup]);
|
|
|
|
# Cleanup and exit
|
|
db_disconnect ($dbh);
|
|
};
|
|
log_message($conf, 'WARNING', $@) if ($@);
|
|
|
|
log_message($conf, 'LOG', "Sleep.");
|
|
sleep($conf->{'ha_interval'});
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Stop pandora server
|
|
################################################################################
|
|
sub stop {
|
|
my $OSNAME = $^O;
|
|
|
|
if ($Running == 1) {
|
|
$Running = 0;
|
|
# cleanup and stop pandora_server
|
|
print ">> stopping server...\n";
|
|
my $control_command = "stop-server";
|
|
if ($OSNAME eq "freebsd") {
|
|
$control_command = "stop_server";
|
|
}
|
|
`$Pandora_Service $control_command 2>/dev/null`;
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# END block.
|
|
################################################################################
|
|
END {
|
|
stop();
|
|
}
|
|
|
|
|
|
$SIG{INT} = \&stop;
|
|
$SIG{TERM} = \&stop;
|
|
|
|
# Init
|
|
ha_init_pandora(\%Conf);
|
|
|
|
# Read config file
|
|
ha_load_pandora_conf (\%Conf);
|
|
|
|
# Main
|
|
ha_main(\%Conf);
|
|
|
|
exit 0;
|