2021-06-14 13:56:42 +02:00
#!/usr/bin/perl
###############################################################################
2021-08-25 11:47:30 +02:00
# Pandora FMS Daemon Watchdog
2021-06-14 13:56:42 +02:00
###############################################################################
# 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.
########################################################################
2021-09-21 12:48:57 +02:00
sub log_message ($$$;$) {
my ( $ conf , $ source , $ message , $ verbosity_level ) = @ _ ;
my $ level = $ verbosity_level ;
$ level = 5 unless defined ( $ level ) ;
2021-06-14 13:56:42 +02:00
if ( ref ( $ conf ) eq "HASH" ) {
2021-09-21 12:48:57 +02:00
logger ( $ conf , 'HA (' . $ source . ') ' . "$message" , $ level ) ;
2021-06-14 13:56:42 +02:00
}
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 ;
2021-08-25 11:47:30 +02:00
log_message ( $ conf , '' , "\nPandora FMS Daemon Watchdog " . $ PandoraFMS:: Tools:: VERSION . " Copyright (c) Artica ST\n" ) ;
2021-06-14 13:56:42 +02:00
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 ) = @ _ ;
2021-07-19 06:47:24 +02:00
my $ OSNAME = $^O ;
my $ control_command ;
2021-06-14 13:56:42 +02:00
$ 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 ) )
2021-07-28 15:06:00 +02:00
AND server_type NOT IN ( ? , ? ) AND name = ? ' ,
2021-06-14 13:56:42 +02:00
PandoraFMS::Tools:: SATELLITESERVER ,
2021-07-28 15:06:00 +02:00
PandoraFMS::Tools:: MFSERVER ,
2021-06-14 13:56:42 +02:00
$ 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
2021-07-21 02:08:01 +02:00
$ control_command = "status-server" ;
2021-07-19 06:47:24 +02:00
if ( $ OSNAME eq "freebsd" ) {
$ control_command = "status_server" ;
}
my $ pid = `$Pandora_Service $control_command | awk '{print \$NF*1}' | tr -d '\.'` ;
2021-06-14 13:56:42 +02:00
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" ;
2021-07-19 06:47:24 +02:00
$ control_command = "restart-server" ;
if ( $ OSNAME eq "freebsd" ) {
$ control_command = "restart_server" ;
}
`$Pandora_Service $control_command 2>/dev/null` ;
2021-06-14 13:56:42 +02:00
} elsif ( $ pid == 0 ) {
# service not running
log_message ( $ conf , 'LOG' , 'Pandora service not running.' ) ;
print ">> service not running...\n" ;
2021-07-19 06:47:24 +02:00
$ control_command = "start-server" ;
if ( $ OSNAME eq "freebsd" ) {
$ control_command = "start_server" ;
}
`$Pandora_Service $control_command 2>/dev/null` ;
2021-06-14 13:56:42 +02:00
} 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...' ) ;
2021-07-19 06:47:24 +02:00
$ control_command = "restart-server" ;
if ( $ OSNAME eq "freebsd" ) {
$ control_command = "restart_server" ;
}
`$Pandora_Service $control_command 2>/dev/null` ;
2021-06-14 13:56:42 +02:00
}
}
}
###############################################################################
# Update pandora services.
###############################################################################
sub ha_update_server ($$) {
my ( $ config , $ dbh ) = @ _ ;
2021-07-19 06:47:24 +02:00
my $ OSNAME = $^O ;
2021-06-14 13:56:42 +02:00
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' } ) ;
2021-07-19 06:47:24 +02:00
my $ control_command = "restart-server" ;
if ( $ OSNAME eq "freebsd" ) {
$ control_command = "restart_server" ;
}
`$config->{'pandora_service_cmd'} $control_command 2>/dev/null` ;
2021-06-14 13:56:42 +02:00
`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 {
2021-06-14 19:18:45 +02:00
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' ) ;
}
2021-06-14 13:56:42 +02:00
# 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 ;
}
2021-06-14 19:18:45 +02:00
# Synchronize database.
enterprise_hook ( 'pandoraha_sync_node' , [ $ conf , $ dbh ] ) ;
2021-06-14 13:56:42 +02:00
# 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 {
2021-07-19 06:47:24 +02:00
my $ OSNAME = $^O ;
2021-06-14 13:56:42 +02:00
if ( $ Running == 1 ) {
$ Running = 0 ;
# cleanup and stop pandora_server
print ">> stopping server...\n" ;
2021-07-19 06:47:24 +02:00
my $ control_command = "stop-server" ;
if ( $ OSNAME eq "freebsd" ) {
$ control_command = "stop_server" ;
}
`$Pandora_Service $control_command 2>/dev/null` ;
2021-06-14 13:56:42 +02:00
}
}
################################################################################
# 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 ;