From faf23aa972ac6d8e8c89ceb9f1e82299204b9da9 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen <anguyen@merethis.com> Date: Mon, 11 Mar 2013 13:07:59 +0100 Subject: [PATCH] Perl code refactoring. * New centreon perl library * Rewritten script (purge, logAnalyser) Conflicts: www/install/sql/centstorage/Update-CSTG-2.4.1_to_2.4.2.sql --- cron/centstorage_purge | 8 + cron/logAnalyser | 15 + cron/nightly_tasks_manager | 44 ++ lib/perl/centreon/db.pm | 283 +++++++++++ lib/perl/centreon/lock.pm | 141 ++++++ lib/perl/centreon/logger.pm | 129 +++++ lib/perl/centreon/script.pm | 98 ++++ lib/perl/centreon/script/centstorage_purge.pm | 106 ++++ lib/perl/centreon/script/logAnalyser.pm | 466 ++++++++++++++++++ www/install/createTablesCentstorage.sql | 1 + .../Update-CSTG-2.4.1_to_2.4.2.sql | 3 +- 11 files changed, 1293 insertions(+), 1 deletion(-) create mode 100755 cron/centstorage_purge create mode 100644 cron/logAnalyser create mode 100755 cron/nightly_tasks_manager create mode 100644 lib/perl/centreon/db.pm create mode 100644 lib/perl/centreon/lock.pm create mode 100644 lib/perl/centreon/logger.pm create mode 100644 lib/perl/centreon/script.pm create mode 100644 lib/perl/centreon/script/centstorage_purge.pm create mode 100644 lib/perl/centreon/script/logAnalyser.pm diff --git a/cron/centstorage_purge b/cron/centstorage_purge new file mode 100755 index 0000000000..7559b9f92c --- /dev/null +++ b/cron/centstorage_purge @@ -0,0 +1,8 @@ +#!/usr/bin/env perl + +use warnings; +use FindBin qw($Bin); +use lib "$Bin/../lib/perl"; +use centreon::script::centstorage_purge; + +centreon::script::centstorage_purge->new()->run(); diff --git a/cron/logAnalyser b/cron/logAnalyser new file mode 100644 index 0000000000..74b0b072f6 --- /dev/null +++ b/cron/logAnalyser @@ -0,0 +1,15 @@ +#!/usr/bin/env perl + +use warnings; +use FindBin qw($Bin); +use lib "$Bin/../lib/perl"; +use centreon::script::logAnalyser; + +$SIG{__DIE__} = sub { + my $error = shift; + + print "Error: $error"; + exit 1; +}; + +centreon::script::logAnalyser->new()->run(); diff --git a/cron/nightly_tasks_manager b/cron/nightly_tasks_manager new file mode 100755 index 0000000000..885a27f210 --- /dev/null +++ b/cron/nightly_tasks_manager @@ -0,0 +1,44 @@ +#!/usr/bin/env perl + +use warnings; +use strict; +use Getopt::Long; +use FindBin qw($Bin); +use lib "$Bin/../lib/perl"; +use centreon::logger; + +use vars qw(@jobs); +my $logger = centreon::logger->new(); + +sub run_task { + my ($name) = @_; + + if ($name[0] ne '/') { + $name = "$Bin/../$name"; + } + $logger->writeLogInfo("$name: start"); + my $output = qx|$name 2>&1|; + + ($? || !defined $output) + ? $logger->writeLogError("Unexpected end!\n$output") + : $logger->writeLogInfo($output, withdate => 0); + $logger->writeLogInfo("$name: stop"); +} + +=head1 main program + +Main program starts here. + +=cut + +my $cfgfile = "/etc/centreon/nightly_tasks.pm"; +my $logfile = "/tmp/$0.log"; +my $result = GetOptions("config=s" => \$cfgfile, + "logfile=s" => \$logfile); + +require $cfgfile; + +$logger->file_mode($logfile); +foreach my $task (@jobs) { + run_task($task); +} diff --git a/lib/perl/centreon/db.pm b/lib/perl/centreon/db.pm new file mode 100644 index 0000000000..ec5fd17d78 --- /dev/null +++ b/lib/perl/centreon/db.pm @@ -0,0 +1,283 @@ +################################################################################ +# Copyright 2005-2011 MERETHIS +# Centreon is developped by : Julien Mathis and Romain Le Merlus under +# GPL Licence 2.0. +# +# 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 ; either version 2 of the License. +# +# 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, see <http://www.gnu.org/licenses>. +# +# Linking this program statically or dynamically with other modules is making a +# combined work based on this program. Thus, the terms and conditions of the GNU +# General Public License cover the whole combination. +# +# As a special exception, the copyright holders of this program give MERETHIS +# permission to link this program with independent modules to produce an executable, +# regardless of the license terms of these independent modules, and to copy and +# distribute the resulting executable under terms of MERETHIS choice, provided that +# MERETHIS also meet, for each linked independent module, the terms and conditions +# of the license of that module. An independent module is a module which is not +# derived from this program. If you modify this program, you may extend this +# exception to your version of the program, but you are not obliged to do so. If you +# do not wish to do so, delete this exception statement from your version. +# +# For more information : contact@centreon.com +# +# SVN : $URL +# SVN : $Id +# +#################################################################################### +package centreon::db; + +use strict; +use warnings; +use DBI; + + +sub new { + my ($class, %options) = @_; + my %defaults = + ( + logger => undef, + db => undef, + host => "localhost", + user => undef, + password => undef, + port => 3306, + force => 0 + ); + my $self = {%defaults, %options}; + + $self->{"instance"} = undef; + $self->{"type"} = "mysql"; + $self->{"args"} = []; + bless $self, $class; + return $self; +} + +# Getter/Setter DB name +sub db { + my $self = shift; + if (@_) { + $self->{"db"} = shift; + } + return $self->{"db"}; +} + +# Getter/Setter DB host +sub host { + my $self = shift; + if (@_) { + $self->{"host"} = shift; + } + return $self->{"host"}; +} + +# Getter/Setter DB port +sub port { + my $self = shift; + if (@_) { + $self->{"port"} = shift; + } + return $self->{"port"}; +} + +# Getter/Setter DB user +sub user { + my $self = shift; + if (@_) { + $self->{"user"} = shift; + } + return $self->{"user"}; +} + +# Getter/Setter DB force +sub force { + my $self = shift; + if (@_) { + $self->{"force"} = shift; + } + return $self->{"force"}; +} + +# Getter/Setter DB password +sub password { + my $self = shift; + if (@_) { + $self->{"password"} = shift; + } + return $self->{"password"}; +} + +sub last_insert_id { + my $self = shift; + return $self->{instance}->last_insert_id(undef, undef, undef, undef); +} + +sub quote { + my $self = shift; + + if (defined($self->{'instance'})) { + return $self->{'instance'}->quote($_[0]); + } + my $num = scalar(@{$self->{"args"}}); + push @{$self->{"args"}}, $_[0]; + return "##__ARG__$num##"; +} + +sub set_inactive_destroy { + my $self = shift; + + if (defined($self->{'instance'})) { + $self->{'instance'}->{InactiveDestroy} = 1; + } +} + +sub transaction_mode { + my ($self, $status) = @_; + + if ($status) { + $self->{instance}->begin_work; + $self->{instance}->{RaiseError} = 1; + } else { + $self->{instance}->{AutoCommit} = 1; + $self->{instance}->{RaiseError} = 0; + } +} + +sub commit { shift->{instance}->commit; } + +sub rollback { shift->{instance}->rollback; } + +sub kill { + my $self = shift; + + if (defined($self->{'instance'})) { + $self->{"logger"}->writeLogInfo("KILL QUERY\n"); + my $rv = $self->{'instance'}->do("KILL QUERY " . $self->{'instance'}->{'mysql_thread_id'}); + if (!$rv) { + my ($package, $filename, $line) = caller; + $self->{'logger'}->writeLogError("MySQL error : " . $self->{'instance'}->errstr . " (caller: $package:$filename:$line)\n"); + } + } +} + +# Connection initializer +sub connect() { + my $self = shift; + my $logger = $self->{logger}; + my $status = 0; + + while (1) { + $self->{"instance"} = DBI->connect( + "DBI:".$self->{"type"} + .":".$self->{"db"} + .":".$self->{"host"} + .":".$self->{"port"}, + $self->{"user"}, + $self->{"password"}, + { "RaiseError" => 0, "PrintError" => 0, "AutoCommit" => 1 } + ); + if (defined($self->{"instance"})) { + last; + } + + my ($package, $filename, $line) = caller; + $logger->writeLogError("MySQL error : cannot connect to database " . $self->{"db"} . ": " . $DBI::errstr . " (caller: $package:$filename:$line)\n"); + if ($self->{'force'} == 0) { + $status = -1; + last; + } + sleep(5); + } + return $status; +} + +# Destroy connection +sub disconnect { + my $self = shift; + my $instance = $self->{"instance"}; + if (defined($instance)) { + $instance->disconnect; + $self->{"instance"} = undef; + } +} + +sub do { + my ($self, $query) = @_; + + if (!defined $self->{instance}) { + if ($self->connect() == -1) { + $self->{logger}->writeLogError("Can't connect to the database"); + return -1; + } + } + my $numrows = $self->{instance}->do($query); + die $self->{instance}->errstr if !defined $numrows; + return $numrows; +} + +sub error { + my ($self, $error, $query) = @_; + my ($package, $filename, $line) = caller 1; + + chomp($query); + $self->{logger}->writeLogError(<<"EOE"); +MySQL error: $error (caller: $package:$filename:$line) +Query: $query +EOE + $self->disconnect(); + $self->{"instance"} = undef; +} + +sub query { + my $self = shift; + my $query = shift; + my $logger = $self->{logger}; + my $status = 0; + my $statement_handle; + + while (1) { + if (!defined($self->{"instance"})) { + $status = $self->connect(); + if ($status != -1) { + for (my $i = 0; $i < scalar(@{$self->{"args"}}); $i++) { + my $str_quoted = $self->quote(${$self->{"args"}}[0]); + $query =~ s/##__ARG__$i##/$str_quoted/; + } + $self->{"args"} = []; + } + if ($status == -1 && $self->{'force'} == 0) { + $self->{"args"} = []; + last; + } + } + + $statement_handle = $self->{"instance"}->prepare($query); + if (!defined $statement_handle) { + $self->error($statement_handle->errstr, $query); + $status = -1; + last if $self->{'force'} == 0; + next; + } + + my $rv = $statement_handle->execute; + if (!$rv) { + $self->error($statement_handle->errstr, $query); + $status = -1; + last if $self->{force} == 0; + next; + } + last; + } + return ($status, $statement_handle); +} + +1; diff --git a/lib/perl/centreon/lock.pm b/lib/perl/centreon/lock.pm new file mode 100644 index 0000000000..f4fd55369a --- /dev/null +++ b/lib/perl/centreon/lock.pm @@ -0,0 +1,141 @@ +package centreon::lock; + +use strict; +use warnings; + +sub new { + my ($class, $name, %options) = @_; + my %defaults = (name => $name, timeout => 10); + my $self = {%defaults, %options}; + + bless $self, $class; + return $self; +} + +sub is_set { + die "Not implemented"; +} + +sub set { + my $self = shift; + + for (my $i = 0; $self->is_set() && $i < $self->{timeout}; $i++) { + sleep 1; + } + die "Failed to set lock for $self->{name}" if $self->is_set(); +} + +package centreon::lock::file; + +use base qw(centreon::lock); + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + + if (!defined $self->{pid} || !defined $self->{storagedir}) { + die "Can't build lock, required arguments not provided"; + } + bless $self, $class; + $self->{pidfile} = "$self->{storagedir}/$self->{name}.lock"; + return $self; +} + +sub is_set { + return -e shift->{pidfile}; +} + +sub set { + my $self = shift; + + $self->SUPER::set(); + open LOCK, ">", $self->{pidfile}; + print LOCK $self->{pid}; + close LOCK; +} + +sub DESTROY { + my $self = shift; + + if (defined $self->{pidfile} && -e $self->{pidfile}) { + unlink $self->{pidfile}; + } +} + +package centreon::lock::sql; + +use base qw(centreon::lock); + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + + if (!defined $self->{dbc}) { + die "Can't build lock, required arguments not provided"; + } + bless $self, $class; + $self->{launch_time} = time(); + return $self; +} + +sub is_set { + my $self = shift; + my ($status, $sth) = $self->{dbc}->query( + "SELECT id,running,time_launch FROM cron_operation WHERE name LIKE '$self->{name}'" + ); + my $data = $sth->fetchrow_hashref(); + + if (!defined $data->{id}) { + $self->{not_created_yet} = 1; + $self->{previous_launch_time} = 0; + return 0; + } + $self->{id} = $data->{id}; + $self->{previous_launch_time} = $data->{time_launch}; + if (defined $data->{running} && $data->{running} == 1) { + return 1; + } + return 0; +} + +sub set { + my $self = shift; + my $status; + + $self->SUPER::set(); + if (defined $self->{not_created_yet}) { + $status = $self->{dbc}->do(<<"EOQ"); +INSERT INTO cron_operation +(name, system, activate) +VALUES ('$self->{name}', '1', '1') +EOQ + goto error if $status == -1; + $self->{id} = $self->{dbc}->last_insert_id(); + return; + } + $status = $self->{dbc}->do(<<"EOQ"); +UPDATE cron_operation +SET running = '1', time_launch = '$self->{launch_time}' +WHERE id = '$self->{id}' +EOQ + goto error if $status == -1; + return; + + error: + die "Failed to set lock for $self->{name}"; +} + +sub DESTROY { + my $self = shift; + + if (defined $self->{dbc}) { + my $exectime = time() - $self->{launch_time}; + $self->{dbc}->do(<<"EOQ"); +UPDATE cron_operation +SET running = '0', last_execution_time = '$exectime' +WHERE id = '$self->{id}' +EOQ + } +} + +1; diff --git a/lib/perl/centreon/logger.pm b/lib/perl/centreon/logger.pm new file mode 100644 index 0000000000..1e53f19a4e --- /dev/null +++ b/lib/perl/centreon/logger.pm @@ -0,0 +1,129 @@ +package centreon::logger; + +=head1 NOM + +centreon::logger - Simple logging module + +=head1 SYNOPSIS + + #!/usr/bin/perl -w + + use strict; + use warnings; + + use centreon::polling; + + my $logger = new centreon::logger(); + + $logger->writeLogInfo("information"); + +=head1 DESCRIPTION + +This module offers a simple interface to write log messages to various output: + +* standard output +* file +* syslog + +=cut + +use strict; +use warnings; +use Sys::Syslog qw(:standard :macros); +use IO::Handle; + +my %severities = (1 => LOG_INFO, + 2 => LOG_ERR); + +sub new { + my $class = shift; + + my $self = bless + { + file => 0, + filehandler => undef, + # 0 = nothing, 1 = critical, 3 = info + severity => 3, + # 0 = stdout, 1 = file, 2 = syslog + log_mode => 0, + # syslog + log_facility => undef, + log_option => LOG_PID, + }, $class; + return $self; +} + +sub file_mode($$) { + my ($self, $file) = @_; + + if (open($self->{filehandler}, ">>", $file)){ + $self->{log_mode} = 1; + $self->{filehandler}->autoflush(1); + return 1; + } + $self->{filehandler} = undef; + print STDERR "Cannot open file $file: $!\n"; + return 0; +} + +sub syslog_mode($$$) { + my ($self, $logopt, $facility) = @_; + + $self->{log_mode} = 2; + openlog($0, $logopt, $facility); + return 1; +} + +# Getter/Setter Log severity +sub severity { + my $self = shift; + if (@_) { + $self->{"severity"} = $_[0]; + } + return $self->{"severity"}; +} + +sub get_date { + my $self = shift; + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time()); + return sprintf("%04d-%02d-%02d %02d:%02d:%02d", + $year+1900, $mon+1, $mday, $hour, $min, $sec); +} + +sub writeLog($$$%) { + my ($self, $severity, $msg, %options) = @_; + my $withdate = (defined $options{withdate}) ? $options{withdate} : 1; + my $newmsg = ($withdate) + ? $self->get_date . " - $msg" : $msg; + + if (($self->{severity} & $severity) == 0) { + return; + } + if ($self->{log_mode} == 0) { + print "$newmsg\n"; + } elsif ($self->{log_mode} == 1) { + if (defined $self->{filehandler}) { + print { $self->{filehandler} } "$newmsg\n"; + } + } elsif ($self->{log_mode} == 2) { + syslog($severities{$severity}, $msg); + } +} + +sub writeLogInfo { + shift->writeLog(1, @_); +} + +sub writeLogError { + shift->writeLog(2, @_); +} + +sub DESTROY { + my $self = shift; + + if (defined $self->{filehandler}) { + $self->{filehandler}->close(); + } +} + +1; diff --git a/lib/perl/centreon/script.pm b/lib/perl/centreon/script.pm new file mode 100644 index 0000000000..48edffde99 --- /dev/null +++ b/lib/perl/centreon/script.pm @@ -0,0 +1,98 @@ +package centreon::script; + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use centreon::logger; +use centreon::db; +use centreon::lock; + +use vars qw($centreon_config); + +sub new { + my ($class, $name, %options) = @_; + my %defaults = + ( + config_file => "/etc/centreon/centreon-config.pm", + log_file => undef, + centreon_db_conn => 0, + centstorage_db_conn => 0, + debug_mode => 0 + ); + my $self = {%defaults, %options}; + + bless $self, $class; + $self->{name} = $name; + $self->{logger} = centreon::logger->new(); + $self->{options} = { + "config=s" => \$self->{config_file}, + "logfile=s" => \$self->{log_file}, + "debug" => \$self->{debug_mode}, + "help|?" => \$self->{help} + }; + return $self; +} + +sub init { + my $self = shift; + + if (defined $self->{log_file}) { + $self->{logger}->file_mode($self->{log_file}); + } + + if ($self->{centreon_db_conn}) { + $self->{cdb} = centreon::db->new + (db => $self->{centreon_config}->{centreon_db}, + host => $self->{centreon_config}->{db_host}, + user => $self->{centreon_config}->{db_user}, + password => $self->{centreon_config}->{db_passwd}, + logger => $self->{logger}); + $self->{lock} = centreon::lock::sql->new($self->{name}, dbc => $self->{cdb}); + $self->{lock}->set(); + } + if ($self->{centstorage_db_conn}) { + $self->{csdb} = centreon::db->new + (db => $self->{centreon_config}->{centstorage_db}, + host => $self->{centreon_config}->{db_host}, + user => $self->{centreon_config}->{db_user}, + password => $self->{centreon_config}->{db_passwd}, + logger => $self->{logger}); + } +} + +sub DESTROY { + my $self = shift; + + if (defined $self->{cdb}) { + $self->{cdb}->disconnect(); + } + if (defined $self->{csdb}) { + $self->{csdb}->disconnect(); + } +} + +sub add_options { + my ($self, %options) = @_; + + $self->{options} = {%{$self->{options}}, %options}; +} + +sub parse_options { + my $self = shift; + + Getopt::Long::Configure('bundling'); + die "Command line error" if !GetOptions(%{$self->{options}}); + pod2usage(1) if $self->{help}; + require $self->{config_file}; + $self->{centreon_config} = $centreon_config; +} + +sub run { + my $self = shift; + + $self->parse_options(); + $self->init(); +} + +1; diff --git a/lib/perl/centreon/script/centstorage_purge.pm b/lib/perl/centreon/script/centstorage_purge.pm new file mode 100644 index 0000000000..4136c9cd01 --- /dev/null +++ b/lib/perl/centreon/script/centstorage_purge.pm @@ -0,0 +1,106 @@ +package centreon::script::centstorage_purge; + +use strict; +use warnings; +use centreon::script; +use centreon::lock; + +use base qw(centreon::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new("centstorage_purge", + centreon_db_conn => 1, + centstorage_db_conn => 1 + ); + + bless $self, $class; + $self->{broker} = "ndo"; + my ($status, $sth) = $self->{csdb}->query(<<"EOQ"); +SELECT len_storage_mysql,archive_retention,reporting_retention +FROM config +EOQ + die "Failed to retrieve configuration from database" if $status == -1; + $self->{config} = $sth->fetchrow_hashref(); + + ($status, $sth) = $self->{cdb}->query(<<"EOQ"); +SELECT `value` FROM `options` WHERE `key` = 'broker' +EOQ + die "Failed to retrieve the broker type from database" if $status == -1; + $self->{broker} = $sth->fetchrow_hashref()->{value}; + return $self; +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + if (defined $self->{config}->{len_storage_mysql} && + $self->{config}->{len_storage_mysql} != 0) { + my $delete_limit = time() - 60 * 60 * 24 * $self->{config}->{len_storage_mysql}; + + $self->{logger}->writeLogInfo("Purging centstorage.data_bin table..."); + $self->{csdb}->do("DELETE FROM data_bin WHERE ctime < '$delete_limit'"); + $self->{logger}->writeLogInfo("Done"); + } + + if (defined($self->{config}->{archive_retention}) + && $self->{config}->{archive_retention} != 0) { + my $last_log = time() - ($self->{config}->{archive_retention} * 24 * 60 * 60); + my $table = ($self->{broker} eq "broker") ? "logs" : "log"; + + $self->{logger}->writeLogInfo("Purging centstorage.$table table..."); + eval { + my $lock = undef; + if ($self->{broker} eq "ndo") { + $lock = centreon::lock::sql("logAnalyser", dbc => $self->{cdb}); + $lock->set(); + } + $self->{csdb}->do("DELETE FROM `$table` WHERE `ctime` < '$last_log'"); + }; + if ($@) { + $self->{logger}->writeLogError("Failed: $@"); + } else { + $self->{logger}->writeLogInfo("Done"); + } + } + + if (defined($self->{config}->{reporting_retention}) + && $self->{config}->{reporting_retention} != 0) { + my $last_log = time() - ($self->{config}->{reporting_retention} * 24 * 60 * 60); + + $self->{logger}->writeLogInfo("Purging log archive tables..."); + $self->{csdb}->do("DELETE FROM `log_archive_host` WHERE `date_end` < '$last_log'"); + $self->{csdb}->do("DELETE FROM `log_archive_service` WHERE `date_end` < '$last_log'"); + $self->{logger}->writeLogInfo("Done"); + } +} + +1; + +__END__ + +=head1 NAME + +centstorage_purge - purge centstorage database + +=head1 SYNOPSIS + +centstorage_purge [options] + +=head1 OPTIONS + +=over 8 + +=item B<-help> + +Print a brief help message and exits. + +=back + +=head1 DESCRIPTION + +B<This program> will read the given input file(s) and do something +useful with the contents thereof. + +=cut diff --git a/lib/perl/centreon/script/logAnalyser.pm b/lib/perl/centreon/script/logAnalyser.pm new file mode 100644 index 0000000000..c4a801a782 --- /dev/null +++ b/lib/perl/centreon/script/logAnalyser.pm @@ -0,0 +1,466 @@ +package centreon::script::logAnalyser; + +use strict; +use warnings; +use File::Path qw(mkpath); +use centreon::script; + +use base qw(centreon::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new("logAnalyser", + centreon_db_conn => 1, + centstorage_db_conn => 1 + ); + + bless $self, $class; + $self->add_options( + "a" => \$self->{opt_a}, "archives" => \$self->{opt_a}, + "p=s" => \$self->{opt_p}, "poller" => \$self->{opt_p}, + "s=s" => \$self->{opt_s}, "startdate" => \$self->{opt_s} + ); + $self->{launch_time} = time(); + $self->{msg_type5_disabled} = 0; + $self->{queries_per_transaction} = 500; + return $self; +} + +sub read_config { + my $self = shift; + my ($status, $sth) = $self->{cdb}->query("SELECT `value` FROM `options` WHERE `key` = 'broker'"); + + goto error if $status == -1; + if ($sth->fetchrow_hashref()->{value} eq "broker") { + die "This script is only suitable for NDO"; + } + ($status, $sth) = $self->{csdb}->query(<<"EOQ"); +SELECT archive_log, archive_retention FROM config +EOQ + goto error if $status == -1; + $self->{config} = $sth->fetchrow_hashref(); + die "No configuration found in database" if !defined $self->{config}->{archive_log}; + return; + + error: + die "Failed to read configuration from database" +} + +=head2 date_to_time($date) + +Convert $date to a timestamp. + +=cut +sub date_to_time($$) { + my ($self, $date) = @_; + + $date =~ s|-|/|g; + return int(`date -d $date +%s`); +} + +=head2 time_to_date($timestamp) + +Convert $timestamp to a human readable date. + +=cut +sub time_to_date($$) { + my ($self, $timestamp) = @_; + chomp(my $result = `date -d \@$timestamp +%m-%d-%Y`); + + return $result; +} + +sub reset_position_flag { + my ($self, $instance) = @_; + my $status = $self->{csdb}->do(<<"EOQ"); +UPDATE instance SET log_flag = '0' WHERE instance_id = '$instance' +EOQ + die "Failed to reset the position flag into database" if $status == -1; +} + +sub commit_to_log { + my ($self, $instance, $ctime, $counter) = @_; + + $self->{csdb}->do(<<"EOQ"); +UPDATE instance SET log_flag='$counter', last_ctime='$ctime' WHERE instance_id = '$instance' +EOQ + $self->{csdb}->commit; + $self->{csdb}->transaction_mode(1); +} + +=head2 parse_file($logFile, $instance) + +Parse a nagios log file. + +=cut +sub parse_file($$$) { + my ($self, $logfile, $instance) = @_; + my $ctime = 0; + my $logdir = "$self->{centreon_config}->{VarLib}/log/$instance"; + my ($last_position, $nbqueries, $counter) = (0, 0, 0); + + if (!-d $logdir) { + mkpath($logdir); + } + my ($status, $sth) = $self->{csdb}->query(<<"EOQ"); +SELECT `log_flag`,`last_ctime` FROM `instance` WHERE `instance_id`='$instance' +EOQ + die "Cannot read previous run information from database" if $status == -1; + my $prev_run_info = $sth->fetchrow_hashref(); + + # Get History Flag + if (open LOG, $logfile) { + my $fline = <LOG>; + close LOG; + if ($fline =~ m/\[([0-9]*)\]\ /) { + chomp($ctime = $1); + } else { + $self->{logger}->writeLogError("Cannot find ctime in first line for poller $instance"); + } + } + + # Decide if we have to read the nagios.log from the begining + if ($ctime && $prev_run_info->{ctime} && $ctime == $prev_run_info->{ctime}) { + $last_position = $prev_run_info->{log_flag}; + } + + # Open Log File for parsing + if (!open FILE, $logfile) { + $self->{logger}->writeLogError("Cannot open file: $logfile"); + return; + } + + # Skip old lines (already read) + if (!$self->{opt_a} && $last_position) { + while ($counter < $last_position && <FILE>) { + $counter++; + } + } + + $self->{csdb}->transaction_mode(1); + eval { + while (<FILE>) { + my $cur_ctime; + + if ($_ =~ m/^\[([0-9]*)\]\sSERVICE ALERT\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + $tab[1] =~ s/\\/\\\\/g; + $tab[1] =~ s/\'/\\\'/g; + $tab[5] =~ s/\\/\\\\/g; + $tab[5] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`,`ctime`, `host_name` , `service_description`, `status`, `type`, `retry`, `output`, `instance`) VALUES ('0', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[2]."', '".$tab[3]."','".$tab[4]."','".$tab[5]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sHOST ALERT\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + if (defined($tab[4]) && $tab[4]) { + $tab[4] =~ s/\\/\\\\/g; + $tab[4] =~ s/\'/\\\'/g; + } + my $rq = "INSERT INTO `log` (`msg_type`,`ctime`, `host_name` , `status`, `type`, `retry`, `output`, `instance`) VALUES ('1', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[2]."','".$tab[3]."','".$tab[4]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sSERVICE NOTIFICATION\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[2] =~ s/\\/\\\\/g; + $tab[2] =~ s/\'/\\\'/g; + $tab[1] =~ s/\\/\\\\/g; + $tab[1] =~ s/\'/\\\'/g; + if (defined($tab[5])) { + $tab[5] =~ s/\\/\\\\/g; + $tab[5] =~ s/\'/\\\'/g; + } else { + $tab[5] = ""; + } + my $rq = "INSERT INTO `log` (`msg_type`,`ctime`, `host_name` , `service_description`, `status`, `notification_cmd`, `notification_contact`, `output`, `instance`) VALUES ('2', '$cur_ctime', '".$tab[1]."', '".$tab[2]."', '".$tab[3]."', '".$tab[4]."','".$tab[0]."','".$tab[5]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sHOST NOTIFICATION\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + if (defined($tab[4])) { + $tab[4] =~ s/\\/\\\\/g; + $tab[4] =~ s/\'/\\\'/g; + } else { + $tab[4] = ""; + } + my $rq = "INSERT INTO `log` (`msg_type`,`ctime`, `notification_contact`, `host_name` , `status`, `notification_cmd`, `output`, `instance`) VALUES ('3', '$cur_ctime', '".$tab[0]."','".$tab[1]."', '".$tab[2]."', '".$tab[3]."','".$tab[4]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sCURRENT\sHOST\sSTATE\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`, `ctime`, `host_name` , `status`, `type`, `instance`) VALUES ('7', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[2]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sCURRENT\sSERVICE\sSTATE\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + $tab[1] =~ s/\\/\\\\/g; + $tab[1] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`, `ctime`, `host_name`, `service_description` , `status`, `type`, `instance`) VALUES ('6', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[2]."', '".$tab[3]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sINITIAL\sHOST\sSTATE\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`, `ctime`, `host_name` , `status`, `type`, `instance`) VALUES ('9', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[2]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sINITIAL\sSERVICE\sSTATE\:\s(.*)$/) { + my @tab = split(/;/, $2); + $cur_ctime = $1; + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + $tab[1] =~ s/\\/\\\\/g; + $tab[1] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`, `ctime`, `host_name`, `service_description` , `status`, `type`, `instance`) VALUES ('8', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[2]."', '".$tab[3]."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sEXTERNAL\sCOMMAND\:\sACKNOWLEDGE\_SVC\_PROBLEM\;(.*)$/) { + $cur_ctime = $1; + my @tab = split(/;/, $2); + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + $tab[1] =~ s/\\/\\\\/g; + $tab[1] =~ s/\'/\\\'/g; + if (!defined($tab[6])) { + $tab[6] = ""; + } + $tab[6] =~ s/\\/\\\\/g; + $tab[6] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`, `ctime`, `host_name`, `service_description`, `notification_contact`, `output`, `instance`) VALUES ('10', '$cur_ctime', '".$tab[0]."', '".$tab[1]."', '".$tab[5]."', '".$tab[6]."','".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sEXTERNAL\sCOMMAND\:\sACKNOWLEDGE\_HOST\_PROBLEM\;(.*)$/) { + $cur_ctime = $1; + my @tab = split(/;/, $2); + $tab[0] =~ s/\\/\\\\/g; + $tab[0] =~ s/\'/\\\'/g; + $tab[5] =~ s/\\/\\\\/g; + $tab[5] =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`, `ctime`, `host_name`, `notification_contact`, `output`, `instance`) VALUES ('11', '$cur_ctime', '".$tab[0]."', '".$tab[4]."', '".$tab[5]."','".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\sWarning\:\s(.*)$/) { + my $tab = $2; + $cur_ctime = $1; + $tab =~ s/\\/\\\\/g; + $tab =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`,`ctime`, `output`, `instance`) VALUES ('4','$cur_ctime', '".$tab."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } elsif ($_ =~ m/^\[([0-9]*)\]\s(.*)$/ && (!$self->{msg_type5_disabled})) { + $cur_ctime = $1; + my $tab = $2; + $tab =~ s/\\/\\\\/g; + $tab =~ s/\'/\\\'/g; + my $rq = "INSERT INTO `log` (`msg_type`,`ctime`, `output`, `instance`) VALUES ('5','$cur_ctime', '".$tab."', '".$instance."')"; + my $res = $self->{csdb}->do($rq); + } + $counter++; + $nbqueries++; + if ($nbqueries == $self->{queries_per_transaction}) { + $self->commit_to_log($instance, $ctime, $counter); + $nbqueries = 0; + } + } + $self->commit_to_log($instance, $ctime, $counter); + }; + close FILE; + if ($@) { + $self->{csdb}->rollback; + die "Database error: $@"; + } + $self->{csdb}->transaction_mode(0); +} + +=head2 parse_archives($instance, $localhost, $startdate) + +Parse log file archices for a given poller (B<$instance>). An +optionnal B<$startdate> can be provided. + +=cut +sub parse_archives { + my ($self, $instance, $localhost, $startdate) = @_; + my $archives; + + if ($localhost) { + my ($status, $sth) = $self->{cdb}->query(<<"EOQ"); +SELECT `log_archive_path` FROM `cfg_nagios`, `nagios_server` +WHERE `nagios_server_id` = '$instance' +AND `nagios_server`.`id` = `cfg_nagios`.`nagios_server_id` +AND `nagios_server`.`ns_activate` = '1' +AND `cfg_nagios`.`nagios_activate` = '1' +EOQ + die "Failed to read instance configuration" if $status == -1; + $archives = $sth->fetchrow_hashref()->{log_archive_path}; + } else { + $archives = "$self->{centreon_config}->{VarLib}/log/$instance/archives/"; + } + + $archives .= "/" if (!($archives =~ /\/$/)); + if (!-d $archives) { + $self->{logger}->writeLogError("No archives for poller $instance"); + return; + } + + my @log_files = split /\s/, `ls $archives`; + my $last_log = undef; + + if (!defined $startdate) { + $last_log = time() - ($self->{config}->{archive_retention} * 24 * 60 * 60); + } else { + $last_log = $self->date_to_time($startdate); + } + foreach (@log_files) { + $_ =~ /nagios\-([0-9\-]+).log/; + my @time = split /\-/, $1; + my $temp = "$time[0]/$time[1]/$time[2]"; + $temp = `date -d $temp +%s`; + if ($temp > $last_log) { + my $curarchive = "$archives$_"; + + $self->{logger}->writeLogInfo("Parsing log file: $curarchive"); + if (!-r $curarchive) { + $self->{logger}->writeLogError("Cannot read file $curarchive"); + next; + } + $self->parse_file($curarchive, $instance); + } + } +} + +=head2 parse_logfile($instance, $localhost, $previous_launch_time) + +Parse the current nagios log file for a given poller. + +=cut +sub parse_logfile($$$) { + my ($self, $instance, $localhost, $previous_launch_time) = @_; + my ($logfile, $archivepath); + + if ($localhost) { + my ($status, $sth) = $self->{cdb}->query(<<"EOQ"); +SELECT `log_file`, `log_archive_path` +FROM `cfg_nagios`, `nagios_server` +WHERE `nagios_server_id` = '$instance' +AND `nagios_server`.`id` = `cfg_nagios`.`nagios_server_id` +AND `nagios_server`.`ns_activate` = '1' +AND `cfg_nagios`.`nagios_activate` = '1' +EOQ + die "Cannot read logfile from database" if $status == -1; + my $data = $sth->fetchrow_hashref(); + $logfile = $data->{log_file}; + $archivepath = $data->{log_archive_path}; + $archivepath .= "/" if ($archivepath !~ /\/$/); + die "Failed to open $logfile" if !-r $logfile; + + my @now = localtime(); + my $archname = "$archivepath/nagios-" . $self->time_to_date($self->{launch_time}) . "-$now[2].log"; + if (-f $archname) { + my $st = stat($archname); + if ($st->mtime > $previous_launch_time) { + $self->{logger}->writeLogInfo("Parsing rotated file for instance $instance"); + $self->parse_file($archname, $instance); + } + } + } else { + $logfile = "$self->{centreon_config}->{VarLib}/log/$instance/nagios.log"; + my $rotate_file = "$logfile.rotate"; + + if (-e $rotate_file) { + $self->{logger}->writeLogInfo("Parsing rotated file for instance $instance"); + $self->parse_file($rotate_file, $instance); + unlink $rotate_file; + } + } + + $self->parse_file($logfile, $instance); +} + +=head2 run() + +Main method. + +Several working modes: + +* Parse the current log file of each poller (default) +* Parse the archives of each poller (-a) +* Parse the archives of a given poller (-p) + +When parsing the archives, a start date can be specified. + +=cut +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->read_config(); + + if (defined $self->{opt_s}) { + if ($self->{opt_s} !~ m/\d{2}-\d{2}-\d{4}/) { + $self->{logger}->writeLogError("Invalid start date provided"); + exit 1; + } + } + + if (defined $self->{opt_p}) { + $self->reset_position_flag($self->{opt_p}); + $self->{csdb}->do("DELETE FROM `log` WHERE instance='$self->{opt_p}'"); + $self->parse_archives($self->{opt_p}, 0, $self->{opt_s}); + return; + } + + my $flag = 0; + my ($status, $list_sth) = $self->{cdb}->query(<<"EOQ"); +SELECT `id`, `name`, `localhost` FROM `nagios_server` WHERE `ns_activate`=1 +EOQ + die "Cannot read pollers list from database" if $status == -1; + + while (my $ns_server = $list_sth->fetchrow_hashref()) { + my $sth; + ($status, $sth) = $self->{csdb}->query(<<"EOQ"); +SELECT `instance_name` FROM `instance` WHERE `instance_id` = '$ns_server->{id}' LIMIT 1 +EOQ + die "Cannot read instance name from database" if $status == -1; + if (!$sth->rows()) { + $status = $self->{csdb}->do(<<"EOQ"); +INSERT INTO `instance` +(`instance_id`, `instance_name`, `log_flag`) +VALUES ('$ns_server->{id}', '$ns_server->{name}', '0') +EOQ + die "Cannot save instance to database" if $status == -1; + } else { + $status = $self->{csdb}->do(<<"EOQ"); +UPDATE `instance` SET `instance_name` = '$ns_server->{name}' +WHERE `instance_id` = '$ns_server->{id}' LIMIT 1 +EOQ + die "Cannot update instance from database" if $status == -1; + } + $self->{logger}->writeLogInfo("Poller: $ns_server->{name}"); + if ($self->{opt_a}) { + if (!$flag) { + if (!defined $self->{opt_s}) { + $status = $self->{csdb}->do("TRUNCATE TABLE `log`"); + $self->{logger}->writeLogError("Failed to truncate 'log' table") if $status == -1; + } else { + my $limit = $self->date_to_time($self->{opt_s}); + $status = $self->{csdb}->do("DELETE FROM `log` WHERE `ctime` >= $limit"); + $self->{logger}->writeLogError("Failed to purge 'log' table") if $status == -1; + } + $flag = 1; + } + $self->reset_position_flag($ns_server->{id}); + $self->parse_archives($ns_server->{id}, $ns_server->{localhost}, $self->{opt_s}); + } else { + $self->parse_logfile($ns_server->{id}, $ns_server->{localhost}, + $self->{lock}->{previous_launch_time}); + } + } + $self->{logger}->writeLogInfo("Done"); +} + +1; diff --git a/www/install/createTablesCentstorage.sql b/www/install/createTablesCentstorage.sql index 42328a33b8..80a5f46198 100644 --- a/www/install/createTablesCentstorage.sql +++ b/www/install/createTablesCentstorage.sql @@ -173,6 +173,7 @@ CREATE TABLE `instance` ( `instance_alias` varchar(254) DEFAULT NULL, `log_flag` int(11) DEFAULT NULL, `log_md5` varchar(255) DEFAULT NULL, + `last_ctime` int(11) DEFAULT 0, PRIMARY KEY (`instance_id`), UNIQUE KEY `instance_name` (`instance_name`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/www/install/sql/centstorage/Update-CSTG-2.4.1_to_2.4.2.sql b/www/install/sql/centstorage/Update-CSTG-2.4.1_to_2.4.2.sql index 14118bb95f..7d11785866 100644 --- a/www/install/sql/centstorage/Update-CSTG-2.4.1_to_2.4.2.sql +++ b/www/install/sql/centstorage/Update-CSTG-2.4.1_to_2.4.2.sql @@ -1 +1,2 @@ -ALTER TABLE `downtimes` ADD INDEX `downtimeManager_hostList` (`host_id`, `start_time`); \ No newline at end of file +ALTER TABLE `downtimes` ADD INDEX `downtimeManager_hostList` (`host_id`, `start_time`); +alter table instance add last_ctime int(11) default 0 after log_md5; -- GitLab